diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/mojo | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) | |
download | qtwebengine-chromium-ab0a50979b9eb4dfa3320eff7e187e41efedf7a9.tar.gz |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/mojo')
669 files changed, 71022 insertions, 318 deletions
diff --git a/chromium/mojo/BUILD.gn b/chromium/mojo/BUILD.gn new file mode 100644 index 00000000000..da17324bd8f --- /dev/null +++ b/chromium/mojo/BUILD.gn @@ -0,0 +1,12 @@ +# 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. + +group("mojo") { + deps = [ + "//mojo/public/cpp/bindings", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/interfaces/service_provider:service_provider", + "//mojo/public/js/bindings", + ] +} diff --git a/chromium/mojo/DEPS b/chromium/mojo/DEPS new file mode 100644 index 00000000000..22cb4c1ac91 --- /dev/null +++ b/chromium/mojo/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+base", + "+build", + "+mojo", + "-mojo/system", + "+testing", +] diff --git a/chromium/mojo/OWNERS b/chromium/mojo/OWNERS new file mode 100644 index 00000000000..49426f5ff58 --- /dev/null +++ b/chromium/mojo/OWNERS @@ -0,0 +1,10 @@ +aa@chromium.org +abarth@chromium.org +ben@chromium.org +darin@chromium.org +davemoore@chromium.org +jamesr@chromium.org +mpcomplete@chromium.org +sky@chromium.org +viettrungluu@chromium.org +yzshen@chromium.org diff --git a/chromium/mojo/README.md b/chromium/mojo/README.md new file mode 100644 index 00000000000..5af1dcb50be --- /dev/null +++ b/chromium/mojo/README.md @@ -0,0 +1,6 @@ +Mojo +==== + +Mojo is an effort to extract a common platform out of Chrome's renderer and +plugin processes that can support multiple types of sandboxed content, such as +HTML, Pepper, or NaCl. diff --git a/chromium/mojo/android/DEPS b/chromium/mojo/android/DEPS new file mode 100644 index 00000000000..c80012b5621 --- /dev/null +++ b/chromium/mojo/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+jni", +] diff --git a/chromium/mojo/android/javatests/AndroidManifest.xml b/chromium/mojo/android/javatests/AndroidManifest.xml new file mode 100644 index 00000000000..8968941f5b5 --- /dev/null +++ b/chromium/mojo/android/javatests/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- Copyright (c) 2012 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. --> + <!-- package name must be unique so suffix with "tests" so package loader + doesn't ignore this. --> + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.chromium.mojo.tests"> + <!-- We add an application tag here just so that we can indicate that this + package needs to link against the android.test library, which is + needed when building test cases. --> + <application> + <uses-library android:name="android.test.runner" /> + </application> + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" /> + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="org.chromium.mojo.tests" + android:label="Tests for org.chromium.mojo"/> + <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" /> + <uses-permission android:name="android.permission.INJECT_EVENTS" /> +</manifest> diff --git a/chromium/mojo/android/javatests/DEPS b/chromium/mojo/android/javatests/DEPS new file mode 100644 index 00000000000..78cf465ca0c --- /dev/null +++ b/chromium/mojo/android/javatests/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # out should be allowed by default, but bots are failing on this. + "+out", +] diff --git a/chromium/mojo/android/javatests/apk/.empty b/chromium/mojo/android/javatests/apk/.empty new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/android/javatests/apk/.empty diff --git a/chromium/mojo/android/javatests/init_library.cc b/chromium/mojo/android/javatests/init_library.cc new file mode 100644 index 00000000000..28cee79443a --- /dev/null +++ b/chromium/mojo/android/javatests/init_library.cc @@ -0,0 +1,40 @@ +// 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/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "mojo/android/javatests/mojo_test_case.h" +#include "mojo/android/system/core_impl.h" + +namespace { + +base::android::RegistrationMethod kMojoRegisteredMethods[] = { + { "CoreImpl", mojo::android::RegisterCoreImpl }, + { "MojoTestCase", mojo::android::RegisterMojoTestCase }, +}; + +bool RegisterMojoJni(JNIEnv* env) { + return RegisterNativeMethods(env, kMojoRegisteredMethods, + arraysize(kMojoRegisteredMethods)); +} + +} // namespace + +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + + if (!base::android::RegisterLibraryLoaderEntryHook(env)) + return -1; + + if (!base::android::RegisterJni(env)) + return -1; + + if (!RegisterMojoJni(env)) + return -1; + + return JNI_VERSION_1_4; +} diff --git a/chromium/mojo/android/javatests/mojo_test_case.cc b/chromium/mojo/android/javatests/mojo_test_case.cc new file mode 100644 index 00000000000..f14dfd8cce4 --- /dev/null +++ b/chromium/mojo/android/javatests/mojo_test_case.cc @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/android/javatests/mojo_test_case.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/test_support_android.h" +#include "jni/MojoTestCase_jni.h" +#include "mojo/public/cpp/environment/environment.h" + +namespace { + +struct TestEnvironment { + base::ShadowingAtExitManager at_exit; + base::MessageLoopForUI message_loop; +}; + +} // namespace + +namespace mojo { +namespace android { + +static void InitApplicationContext(JNIEnv* env, + jobject jcaller, + jobject context) { + base::android::ScopedJavaLocalRef<jobject> scoped_context(env, context); + base::android::InitApplicationContext(env, scoped_context); + base::InitAndroidTestMessageLoop(); +} + +static jlong SetupTestEnvironment(JNIEnv* env, jobject jcaller) { + return reinterpret_cast<intptr_t>(new TestEnvironment()); +} + +static void TearDownTestEnvironment(JNIEnv* env, + jobject jcaller, + jlong test_environment) { + delete reinterpret_cast<TestEnvironment*>(test_environment); +} + +static void RunLoop(JNIEnv* env, jobject jcaller, jlong timeout_ms) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(timeout_ms)); + base::RunLoop run_loop; + run_loop.Run(); +} + +bool RegisterMojoTestCase(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/chromium/mojo/android/javatests/mojo_test_case.h b/chromium/mojo/android/javatests/mojo_test_case.h new file mode 100644 index 00000000000..2ce342848e2 --- /dev/null +++ b/chromium/mojo/android/javatests/mojo_test_case.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ +#define MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterMojoTestCase(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_CORE_TEST_H_ diff --git a/chromium/mojo/android/system/core_impl.cc b/chromium/mojo/android/system/core_impl.cc new file mode 100644 index 00000000000..142cd0595fe --- /dev/null +++ b/chromium/mojo/android/system/core_impl.cc @@ -0,0 +1,368 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/android/system/core_impl.h" + +#include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "jni/CoreImpl_jni.h" +#include "mojo/embedder/embedder.h" +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/c/system/core.h" +#include "mojo/public/cpp/environment/environment.h" + +namespace { + +// |AsyncWait| is guaranteed never to return 0. +const MojoAsyncWaitID kInvalidHandleCancelID = 0; + +struct AsyncWaitCallbackData { + base::android::ScopedJavaGlobalRef<jobject> core_impl; + base::android::ScopedJavaGlobalRef<jobject> callback; + base::android::ScopedJavaGlobalRef<jobject> cancellable; + + AsyncWaitCallbackData(JNIEnv* env, jobject core_impl, jobject callback) { + this->core_impl.Reset(env, core_impl); + this->callback.Reset(env, callback); + } +}; + +void AsyncWaitCallback(void* data, MojoResult result) { + scoped_ptr<AsyncWaitCallbackData> callback_data( + static_cast<AsyncWaitCallbackData*>(data)); + mojo::android::Java_CoreImpl_onAsyncWaitResult( + base::android::AttachCurrentThread(), + callback_data->core_impl.obj(), + result, + callback_data->callback.obj(), + callback_data->cancellable.obj()); +} + +} // namespace + +namespace mojo { +namespace android { + +static void Constructor(JNIEnv* env, jobject jcaller) { + mojo::embedder::Init(); +} + +static jlong GetTimeTicksNow(JNIEnv* env, jobject jcaller) { + return MojoGetTimeTicksNow(); +} + +static jint WaitMany(JNIEnv* env, + jobject jcaller, + jobject buffer, + jlong deadline) { + // Buffer contains first the list of handles, then the list of flags. + const void* buffer_start = env->GetDirectBufferAddress(buffer); + DCHECK(buffer_start); + const size_t record_size = 8; + const size_t buffer_size = env->GetDirectBufferCapacity(buffer); + DCHECK_EQ(buffer_size % record_size, 0u); + + const size_t nb_handles = buffer_size / record_size; + const MojoHandle* handle_start = static_cast<const MojoHandle*>(buffer_start); + const MojoHandleSignals* signals_start = + static_cast<const MojoHandleSignals*>(handle_start + nb_handles); + return MojoWaitMany(handle_start, signals_start, nb_handles, deadline); +} + +static jobject CreateMessagePipe(JNIEnv* env, jobject jcaller) { + MojoHandle handle1; + MojoHandle handle2; + // TODO(vtl): Add support for the options struct. + MojoResult result = MojoCreateMessagePipe(NULL, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2) + .Release(); +} + +static jobject CreateDataPipe(JNIEnv* env, + jobject jcaller, + jobject options_buffer) { + const MojoCreateDataPipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateDataPipeOptions)); + options = static_cast<const MojoCreateDataPipeOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateDataPipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2) + .Release(); +} + +static jobject CreateSharedBuffer(JNIEnv* env, + jobject jcaller, + jobject options_buffer, + jlong num_bytes) { + const MojoCreateSharedBufferOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateSharedBufferOptions)); + options = static_cast<const MojoCreateSharedBufferOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoCreateSharedBuffer(options, num_bytes, &handle); + return Java_CoreImpl_newNativeCreationResult(env, result, handle, 0) + .Release(); +} + +static jint Close(JNIEnv* env, jobject jcaller, jint mojo_handle) { + return MojoClose(mojo_handle); +} + +static jint Wait(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jint signals, + jlong deadline) { + return MojoWait(mojo_handle, signals, deadline); +} + +static jint WriteMessage(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jobject bytes, + jint num_bytes, + jobject handles_buffer, + jint flags) { + const void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + DCHECK(env->GetDirectBufferCapacity(bytes) >= num_bytes); + buffer_size = num_bytes; + } + const MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast<MojoHandle*>(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + // Java code will handle invalidating handles if the write succeeded. + return MojoWriteMessage( + mojo_handle, buffer_start, buffer_size, handles, num_handles, flags); +} + +static jobject ReadMessage(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jobject bytes, + jobject handles_buffer, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + buffer_size = env->GetDirectBufferCapacity(bytes); + } + MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast<MojoHandle*>(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + MojoResult result = MojoReadMessage( + mojo_handle, buffer_start, &buffer_size, handles, &num_handles, flags); + // Jave code will handle taking ownership of any received handle. + return Java_CoreImpl_newReadMessageResult( + env, result, buffer_size, num_handles).Release(); +} + +static jint ReadData(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jobject elements, + jint elements_capacity, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = elements_capacity; + if (elements) { + buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(elements_capacity <= env->GetDirectBufferCapacity(elements)); + } + MojoResult result = + MojoReadData(mojo_handle, buffer_start, &buffer_size, flags); + if (result < 0) { + return result; + } + return buffer_size; +} + +static jobject BeginReadData(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void const* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginReadData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = + env->NewDirectByteBuffer(const_cast<void*>(buffer), buffer_size); + } + return Java_CoreImpl_newNativeCodeAndBufferResult(env, result, byte_buffer) + .Release(); +} + +static jint EndReadData(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jint num_bytes_read) { + return MojoEndReadData(mojo_handle, num_bytes_read); +} + +static jint WriteData(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jobject elements, + jint limit, + jint flags) { + void* buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(limit <= env->GetDirectBufferCapacity(elements)); + uint32_t buffer_size = limit; + MojoResult result = + MojoWriteData(mojo_handle, buffer_start, &buffer_size, flags); + if (result < 0) { + return result; + } + return buffer_size; +} + +static jobject BeginWriteData(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginWriteData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, buffer_size); + } + return Java_CoreImpl_newNativeCodeAndBufferResult(env, result, byte_buffer) + .Release(); +} + +static jint EndWriteData(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jint num_bytes_written) { + return MojoEndWriteData(mojo_handle, num_bytes_written); +} + +static jobject Duplicate(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jobject options_buffer) { + const MojoDuplicateBufferHandleOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoDuplicateBufferHandleOptions)); + options = + static_cast<const MojoDuplicateBufferHandleOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoDuplicateBufferHandle(mojo_handle, options, &handle); + return Java_CoreImpl_newNativeCreationResult(env, result, handle, 0) + .Release(); +} + +static jobject Map(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jlong offset, + jlong num_bytes, + jint flags) { + void* buffer = 0; + MojoResult result = + MojoMapBuffer(mojo_handle, offset, num_bytes, &buffer, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, num_bytes); + } + return Java_CoreImpl_newNativeCodeAndBufferResult(env, result, byte_buffer) + .Release(); +} + +static int Unmap(JNIEnv* env, jobject jcaller, jobject buffer) { + void* buffer_start = env->GetDirectBufferAddress(buffer); + DCHECK(buffer_start); + return MojoUnmapBuffer(buffer_start); +} + +static jobject AsyncWait(JNIEnv* env, + jobject jcaller, + jint mojo_handle, + jint signals, + jlong deadline, + jobject callback) { + AsyncWaitCallbackData* callback_data = + new AsyncWaitCallbackData(env, jcaller, callback); + MojoAsyncWaitID cancel_id; + if (static_cast<MojoHandle>(mojo_handle) != MOJO_HANDLE_INVALID) { + cancel_id = mojo::Environment::GetDefaultAsyncWaiter()->AsyncWait( + mojo_handle, signals, deadline, AsyncWaitCallback, callback_data); + } else { + cancel_id = kInvalidHandleCancelID; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind( + &AsyncWaitCallback, callback_data, MOJO_RESULT_INVALID_ARGUMENT)); + } + base::android::ScopedJavaLocalRef<jobject> cancellable = + Java_CoreImpl_newAsyncWaiterCancellableImpl( + env, jcaller, cancel_id, reinterpret_cast<intptr_t>(callback_data)); + callback_data->cancellable.Reset(env, cancellable.obj()); + return cancellable.Release(); +} + +static void CancelAsyncWait(JNIEnv* env, + jobject jcaller, + jlong id, + jlong data_ptr) { + if (id == 0) { + // If |id| is |kInvalidHandleCancelID|, the async wait was done on an + // invalid handle, so the AsyncWaitCallback will be called and will clear + // the data_ptr. + return; + } + scoped_ptr<AsyncWaitCallbackData> deleter( + reinterpret_cast<AsyncWaitCallbackData*>(data_ptr)); + mojo::Environment::GetDefaultAsyncWaiter()->CancelWait(id); +} + +bool RegisterCoreImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/chromium/mojo/android/system/core_impl.h b/chromium/mojo/android/system/core_impl.h new file mode 100644 index 00000000000..c6249994e5c --- /dev/null +++ b/chromium/mojo/android/system/core_impl.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterCoreImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ diff --git a/chromium/mojo/apps/js/DEPS b/chromium/mojo/apps/js/DEPS new file mode 100644 index 00000000000..d974b6806fc --- /dev/null +++ b/chromium/mojo/apps/js/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+gin", + "+v8", +] diff --git a/chromium/mojo/apps/js/bindings/connection_unittests.js b/chromium/mojo/apps/js/bindings/connection_unittests.js new file mode 100644 index 00000000000..0b0a71b2d03 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/connection_unittests.js @@ -0,0 +1,256 @@ +// 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/bindings/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/bindings/support", + "mojo/public/js/bindings/core", + "mojo/public/js/bindings/connection", + "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom", + "mojo/public/interfaces/bindings/tests/sample_service.mojom", + "mojo/apps/js/bindings/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 testClientServer() { + var receivedFrobinate = false; + var receivedDidFrobinate = false; + + // ServiceImpl ------------------------------------------------------------- + + function ServiceImpl(peer) { + this.peer = peer; + } + + ServiceImpl.prototype = Object.create(sample_service.ServiceStub.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); + + this.peer.didFrobinate(42); + }; + + // ServiceImpl ------------------------------------------------------------- + + function ServiceClientImpl(peer) { + this.peer = peer; + } + + ServiceClientImpl.prototype = + Object.create(sample_service.ServiceClientStub.prototype); + + ServiceClientImpl.prototype.didFrobinate = function(result) { + receivedDidFrobinate = true; + + expect(result).toBe(42); + }; + + var pipe = core.createMessagePipe(); + var anotherPipe = core.createMessagePipe(); + var sourcePipe = core.createMessagePipe(); + + var connection0 = new connection.Connection( + pipe.handle0, ServiceImpl, sample_service.ServiceClientProxy); + + var connection1 = new connection.Connection( + pipe.handle1, ServiceClientImpl, sample_service.ServiceProxy); + + 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(); + expect(receivedDidFrobinate).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 = new connection.Connection( + pipe.handle1, function() {}, sample_service.ServiceProxy); + + // 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(); + // TODO(darin): crbug.com/357043: pass null in place of |foo| here. + connection1.remote.frobinate(foo, 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(peer) { + this.peer = peer; + } + + ProviderImpl.prototype = + Object.create(sample_interfaces.ProviderStub.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}); + }; + + // ProviderClientImpl ------------------------------------------------------ + + function ProviderClientImpl(peer) { + this.peer = peer; + } + + ProviderClientImpl.prototype = + Object.create(sample_interfaces.ProviderClientStub.prototype); + + var pipe = core.createMessagePipe(); + + var connection0 = new connection.Connection( + pipe.handle0, ProviderImpl, sample_interfaces.ProviderClientProxy); + + var connection1 = new connection.Connection( + pipe.handle1, ProviderClientImpl, sample_interfaces.ProviderProxy); + + 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/chromium/mojo/apps/js/bindings/gl/context.cc b/chromium/mojo/apps/js/bindings/gl/context.cc new file mode 100644 index 00000000000..18c37e793d2 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/gl/context.cc @@ -0,0 +1,187 @@ +// 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 "mojo/apps/js/bindings/gl/context.h" + +#include <GLES2/gl2.h> + +#include "gin/arguments.h" +#include "gin/array_buffer.h" +#include "gin/object_template_builder.h" +#include "gin/per_context_data.h" +#include "mojo/public/c/gles2/gles2.h" + +namespace gin { +template<> +struct Converter<GLboolean> { + static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val, + GLboolean* out) { + bool bool_val = false; + if (!Converter<bool>::FromV8(isolate, val, &bool_val)) + return false; + *out = static_cast<GLboolean>(bool_val); + return true; + } +}; +} + +namespace mojo { +namespace js { +namespace gl { + +gin::WrapperInfo Context::kWrapperInfo = { gin::kEmbedderNativeGin }; + +gin::Handle<Context> Context::Create( + v8::Isolate* isolate, + mojo::Handle handle, + v8::Handle<v8::Function> context_lost_callback) { + return gin::CreateHandle(isolate, + new Context(isolate, handle, context_lost_callback)); +} + +void Context::BufferData(GLenum target, const gin::ArrayBufferView& buffer, + GLenum usage) { + glBufferData(target, static_cast<GLsizeiptr>(buffer.num_bytes()), + buffer.bytes(), usage); +} + +void Context::CompileShader(const gin::Arguments& args, GLuint shader) { + glCompileShader(shader); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + args.ThrowTypeError(std::string("Could not compile shader: ") + + GetShaderInfoLog(shader)); + } +} + +GLuint Context::CreateBuffer() { + GLuint result = 0; + glGenBuffers(1, &result); + return result; +} + +void Context::DrawElements(GLenum mode, GLsizei count, GLenum type, + uint64_t indices) { + // This looks scary, but it's what WebGL does too: + // http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.1 + glDrawElements(mode, count, type, reinterpret_cast<void*>(indices)); +} + +GLint Context::GetAttribLocation(GLuint program, const std::string& name) { + return glGetAttribLocation(program, name.c_str()); +} + +std::string Context::GetProgramInfoLog(GLuint program) { + GLint info_log_length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_log_length); + std::string info_log(info_log_length, 0); + glGetProgramInfoLog(program, info_log_length, NULL, &info_log.at(0)); + return info_log; +} + +std::string Context::GetShaderInfoLog(GLuint shader) { + GLint info_log_length = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length); + std::string info_log(info_log_length, 0); + glGetShaderInfoLog(shader, info_log_length, NULL, &info_log.at(0)); + return info_log; +} + +GLint Context::GetUniformLocation(GLuint program, const std::string& name) { + return glGetUniformLocation(program, name.c_str()); +} + +void Context::ShaderSource(GLuint shader, const std::string& source) { + const char* source_chars = source.c_str(); + glShaderSource(shader, 1, &source_chars, NULL); +} + +void Context::UniformMatrix4fv(GLint location, GLboolean transpose, + const gin::ArrayBufferView& buffer) { + glUniformMatrix4fv(location, 1, transpose, + static_cast<float*>(buffer.bytes())); +} + +void Context::VertexAttribPointer(GLuint index, GLint size, GLenum type, + GLboolean normalized, GLsizei stride, + uint64_t offset) { + glVertexAttribPointer(index, size, type, normalized, stride, + reinterpret_cast<void*>(offset)); +} + +gin::ObjectTemplateBuilder Context::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return gin::ObjectTemplateBuilder(isolate) + .SetValue("ARRAY_BUFFER", GL_ARRAY_BUFFER) + .SetValue("COLOR_BUFFER_BIT", GL_COLOR_BUFFER_BIT) + .SetValue("ELEMENT_ARRAY_BUFFER", GL_ELEMENT_ARRAY_BUFFER) + .SetValue("FLOAT", GL_FLOAT) + .SetValue("FRAGMENT_SHADER", GL_FRAGMENT_SHADER) + .SetValue("STATIC_DRAW", GL_STATIC_DRAW) + .SetValue("TRIANGLES", GL_TRIANGLES) + .SetValue("UNSIGNED_SHORT", GL_UNSIGNED_SHORT) + .SetValue("VERTEX_SHADER", GL_VERTEX_SHADER) + .SetMethod("attachShader", glAttachShader) + .SetMethod("bindBuffer", glBindBuffer) + .SetMethod("bufferData", BufferData) + .SetMethod("clear", glClear) + .SetMethod("clearColor", glClearColor) + .SetMethod("compileShader", CompileShader) + .SetMethod("createBuffer", CreateBuffer) + .SetMethod("createProgram", glCreateProgram) + .SetMethod("createShader", glCreateShader) + .SetMethod("deleteShader", glDeleteShader) + .SetMethod("drawElements", DrawElements) + .SetMethod("enableVertexAttribArray", glEnableVertexAttribArray) + .SetMethod("getAttribLocation", GetAttribLocation) + .SetMethod("getProgramInfoLog", GetProgramInfoLog) + .SetMethod("getShaderInfoLog", GetShaderInfoLog) + .SetMethod("getUniformLocation", GetUniformLocation) + .SetMethod("linkProgram", glLinkProgram) + .SetMethod("shaderSource", ShaderSource) + .SetMethod("swapBuffers", MojoGLES2SwapBuffers) + .SetMethod("uniformMatrix4fv", UniformMatrix4fv) + .SetMethod("useProgram", glUseProgram) + .SetMethod("vertexAttribPointer", VertexAttribPointer) + .SetMethod("viewport", glViewport); +} + +Context::Context(v8::Isolate* isolate, + mojo::Handle handle, + v8::Handle<v8::Function> context_lost_callback) { + v8::Handle<v8::Context> context = isolate->GetCurrentContext(); + runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); + context_lost_callback_.Reset(isolate, context_lost_callback); + context_ = MojoGLES2CreateContext( + handle.value(), + &ContextLostThunk, + NULL, + this); + MojoGLES2MakeCurrent(context_); +} + +Context::~Context() { + MojoGLES2DestroyContext(context_); +} + +void Context::ContextLost() { + if (!runner_) + return; + gin::Runner::Scope scope(runner_.get()); + v8::Isolate* isolate = runner_->GetContextHolder()->isolate(); + + v8::Handle<v8::Function> callback = v8::Local<v8::Function>::New( + isolate, context_lost_callback_); + + runner_->Call(callback, runner_->global(), 0, NULL); +} + +void Context::ContextLostThunk(void* closure) { + static_cast<Context*>(closure)->ContextLost(); +} + +} // namespace gl +} // namespace js +} // namespace mojo diff --git a/chromium/mojo/apps/js/bindings/gl/context.h b/chromium/mojo/apps/js/bindings/gl/context.h new file mode 100644 index 00000000000..905f0fd00de --- /dev/null +++ b/chromium/mojo/apps/js/bindings/gl/context.h @@ -0,0 +1,77 @@ +// 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 MOJO_APPS_JS_BINDINGS_GL_CONTEXT_H_ +#define MOJO_APPS_JS_BINDINGS_GL_CONTEXT_H_ + +#include <GLES2/gl2.h> + +#include "base/memory/weak_ptr.h" +#include "gin/handle.h" +#include "gin/public/wrapper_info.h" +#include "gin/runner.h" +#include "gin/wrappable.h" +#include "mojo/bindings/js/handle.h" +#include "mojo/public/c/gles2/gles2.h" +#include "v8/include/v8.h" + +namespace gin { +class Arguments; +class ArrayBufferView; +} + +namespace mojo { +namespace js { +namespace gl { + +// Context implements WebGLRenderingContext. +class Context : public gin::Wrappable<Context> { + public: + static gin::WrapperInfo kWrapperInfo; + + // TODO(piman): draw animation frame callback. + static gin::Handle<Context> Create( + v8::Isolate* isolate, + mojo::Handle handle, + v8::Handle<v8::Function> context_lost_callback); + + static void BufferData(GLenum target, const gin::ArrayBufferView& buffer, + GLenum usage); + static void CompileShader(const gin::Arguments& args, GLuint shader); + static GLuint CreateBuffer(); + static void DrawElements(GLenum mode, GLsizei count, GLenum type, + uint64_t indices); + static GLint GetAttribLocation(GLuint program, const std::string& name); + static std::string GetProgramInfoLog(GLuint program); + static std::string GetShaderInfoLog(GLuint shader); + static GLint GetUniformLocation(GLuint program, const std::string& name); + static void ShaderSource(GLuint shader, const std::string& source); + static void UniformMatrix4fv(GLint location, GLboolean transpose, + const gin::ArrayBufferView& buffer); + static void VertexAttribPointer(GLuint index, GLint size, GLenum type, + GLboolean normalized, GLsizei stride, + uint64_t offset); + + private: + virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) OVERRIDE; + + explicit Context(v8::Isolate* isolate, + mojo::Handle handle, + v8::Handle<v8::Function> context_lost_callback); + virtual ~Context(); + + void ContextLost(); + static void ContextLostThunk(void* closure); + + base::WeakPtr<gin::Runner> runner_; + v8::Persistent<v8::Function> context_lost_callback_; + MojoGLES2Context context_; +}; + +} // namespace gl +} // namespace js +} // namespace mojo + +#endif // MOJO_APPS_JS_BINDINGS_GL_CONTEXT_H_ diff --git a/chromium/mojo/apps/js/bindings/gl/module.cc b/chromium/mojo/apps/js/bindings/gl/module.cc new file mode 100644 index 00000000000..413f22ed3d5 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/gl/module.cc @@ -0,0 +1,50 @@ +// 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 "mojo/apps/js/bindings/gl/module.h" + +#include "base/logging.h" +#include "gin/arguments.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "gin/wrappable.h" +#include "mojo/apps/js/bindings/gl/context.h" +#include "mojo/bindings/js/handle.h" + +namespace mojo { +namespace js { +namespace gl { + +const char* kModuleName = "mojo/apps/js/bindings/gl"; + +namespace { + +gin::WrapperInfo kWrapperInfo = { gin::kEmbedderNativeGin }; + +gin::Handle<Context> CreateContext( + const gin::Arguments& args, + mojo::Handle handle, + v8::Handle<v8::Function> context_lost_callback) { + return Context::Create(args.isolate(), handle, context_lost_callback); +} + +} // namespace + +v8::Local<v8::Value> GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(&kWrapperInfo); + + if (templ.IsEmpty()) { + templ = gin::ObjectTemplateBuilder(isolate) + .SetMethod("Context", CreateContext) + .Build(); + data->SetObjectTemplate(&kWrapperInfo, templ); + } + + return templ->NewInstance(); +} + +} // namespace gl +} // namespace js +} // namespace mojo diff --git a/chromium/mojo/apps/js/bindings/gl/module.h b/chromium/mojo/apps/js/bindings/gl/module.h new file mode 100644 index 00000000000..0ae0d14357b --- /dev/null +++ b/chromium/mojo/apps/js/bindings/gl/module.h @@ -0,0 +1,22 @@ +// 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 MOJO_APPS_JS_BINDINGS_GL_MODULE_H_ +#define MOJO_APPS_JS_BINDINGS_GL_MODULE_H_ + +#include "gin/public/wrapper_info.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace js { +namespace gl { + +extern const char* kModuleName; +v8::Local<v8::Value> GetModule(v8::Isolate* isolate); + +} // namespace gl +} // namespace js +} // namespace mojo + +#endif // MOJO_APPS_JS_BINDINGS_GL_H_ diff --git a/chromium/mojo/apps/js/bindings/monotonic_clock.cc b/chromium/mojo/apps/js/bindings/monotonic_clock.cc new file mode 100644 index 00000000000..733af8a69e2 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/monotonic_clock.cc @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/apps/js/bindings/monotonic_clock.h" + +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "gin/public/wrapper_info.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace apps { + +namespace { + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +double GetMonotonicSeconds() { + const double kMicrosecondsPerSecond = 1000000; + return static_cast<double>(mojo::GetTimeTicksNow()) / kMicrosecondsPerSecond; +} + +} // namespace + +const char MonotonicClock::kModuleName[] = "monotonic_clock"; + +v8::Local<v8::Value> MonotonicClock::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("seconds", GetMonotonicSeconds) + .Build(); + data->SetObjectTemplate(&g_wrapper_info, templ); + } + return templ->NewInstance(); +} + +} // namespace apps +} // namespace mojo diff --git a/chromium/mojo/apps/js/bindings/monotonic_clock.h b/chromium/mojo/apps/js/bindings/monotonic_clock.h new file mode 100644 index 00000000000..6278821e8f8 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/monotonic_clock.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 MOJO_APPS_JS_BINDING_MONOTONIC_CLOCK_H_ +#define MOJO_APPS_JS_BINDING_MONOTONIC_CLOCK_H_ + +#include "v8/include/v8.h" + +namespace mojo { +namespace apps { + +class MonotonicClock { + public: + static const char kModuleName[]; + static v8::Local<v8::Value> GetModule(v8::Isolate* isolate); +}; + +} // namespace apps +} // namespace mojo + +#endif // MOJO_APPS_JS_BINDING_MONOTONIC_CLOCK_H_ diff --git a/chromium/mojo/apps/js/bindings/monotonic_clock_unittests.js b/chromium/mojo/apps/js/bindings/monotonic_clock_unittests.js new file mode 100644 index 00000000000..a1a253ed68e --- /dev/null +++ b/chromium/mojo/apps/js/bindings/monotonic_clock_unittests.js @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define([ + "console", + "gin/test/expect", + "monotonic_clock", + "timer", + "mojo/apps/js/bindings/threading" +], function(console, expect, monotonicClock, timer, threading) { + var global = this; + var then = monotonicClock.seconds(); + var t = timer.createOneShot(100, function() { + var now = monotonicClock.seconds(); + expect(now).toBeGreaterThan(then); + global.result = "PASS"; + threading.quit(); + }); +}); diff --git a/chromium/mojo/apps/js/bindings/sample_service_unittests.js b/chromium/mojo/apps/js/bindings/sample_service_unittests.js new file mode 100644 index 00000000000..c01ddee8bb2 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/sample_service_unittests.js @@ -0,0 +1,169 @@ +// 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", + "mojo/apps/js/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", + ], function(console, hexdump, expect, sample, imported, imported2) { + + 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.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.TYPE_VERTICAL : sample.Bar.Type.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.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.TYPE_VERTICAL : sample.Bar.Type.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.TYPE_VERTICAL); + + var foo = new sample.Foo(); + expect(foo.name).toBe("Fooby"); + expect(foo.a).toBeTruthy(); + // TODO(vtl): crbug.com/375845 + // 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); + // TODO(vtl): crbug.com/375522 + // expect(defaults.a5).toBe(3456789012); + expect(defaults.a6).toBe(111111111111); + // TODO(vtl): crbug.com/375522 (Also, can we get exact values for large + // int64/uint64's in JS?) + // 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.TYPE_BOTH); + expect(defaults.a21).toBeNull(); + expect(defaults.a22).toBeTruthy(); + expect(defaults.a22.shape).toBe(imported.Shape.SHAPE_RECTANGLE); + expect(defaults.a22.color).toBe(imported2.Color.COLOR_BLACK); + // TODO(vtl): crbug.com/375845 + // expect(defaults.a21).toBeNull(); + // expect(defaults.a22).toBeNull(); + } + + function ServiceImpl() { + } + + ServiceImpl.prototype = Object.create(sample.ServiceStub.prototype); + + ServiceImpl.prototype.frobinate = function(foo, baz, port) { + checkFoo(foo); + expect(baz).toBe(sample.ServiceStub.BazOptions.BAZ_EXTRA); + expect(port).toBe(10); + global.result = "PASS"; + }; + + function SimpleMessageReceiver() { + } + + SimpleMessageReceiver.prototype.accept = 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(); + serviceImpl.accept(message); + }; + + var receiver = new SimpleMessageReceiver(); + var serviceProxy = new sample.ServiceProxy(receiver); + + checkDefaultValues(); + + var foo = makeFoo(); + checkFoo(foo); + + var port = 10; + serviceProxy.frobinate(foo, sample.ServiceProxy.BazOptions.BAZ_EXTRA, port); +}); diff --git a/chromium/mojo/apps/js/bindings/threading.cc b/chromium/mojo/apps/js/bindings/threading.cc new file mode 100644 index 00000000000..64e32fc8f21 --- /dev/null +++ b/chromium/mojo/apps/js/bindings/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 "mojo/apps/js/bindings/threading.h" + +#include "base/message_loop/message_loop.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "mojo/bindings/js/handle.h" + +namespace mojo { +namespace apps { + +namespace { + +void Quit() { + base::MessageLoop::current()->QuitNow(); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Threading::kModuleName[] = "mojo/apps/js/bindings/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 apps +} // namespace mojo diff --git a/chromium/mojo/apps/js/bindings/threading.h b/chromium/mojo/apps/js/bindings/threading.h new file mode 100644 index 00000000000..ac8e221377a --- /dev/null +++ b/chromium/mojo/apps/js/bindings/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 MOJO_APPS_JS_BINDINGS_THREADING_H_ +#define MOJO_APPS_JS_BINDINGS_THREADING_H_ + +#include "gin/public/wrapper_info.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace apps { + +class Threading { + public: + static const char kModuleName[]; + static v8::Local<v8::Value> GetModule(v8::Isolate* isolate); + private: + Threading(); +}; + +} // namespace apps +} // namespace mojo + +#endif // MOJO_APPS_JS_BINDINGS_THREADING_H_ diff --git a/chromium/mojo/apps/js/main.cc b/chromium/mojo/apps/js/main.cc new file mode 100644 index 00000000000..84231e5971e --- /dev/null +++ b/chromium/mojo/apps/js/main.cc @@ -0,0 +1,43 @@ +// 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 "base/message_loop/message_loop.h" +#include "gin/public/isolate_holder.h" +#include "mojo/apps/js/mojo_runner_delegate.h" +#include "mojo/public/cpp/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/macros.h" + +#if defined(WIN32) +#if !defined(CDECL) +#define CDECL __cdecl +#endif +#define MOJO_APPS_JS_EXPORT __declspec(dllexport) +#else +#define CDECL +#define MOJO_APPS_JS_EXPORT __attribute__((visibility("default"))) +#endif + +namespace mojo { +namespace apps { + +void Start(MojoHandle pipe, const std::string& module) { + base::MessageLoop loop; + + gin::IsolateHolder instance(gin::IsolateHolder::kStrictMode); + MojoRunnerDelegate delegate; + gin::ShellRunner runner(&delegate, instance.isolate()); + delegate.Start(&runner, pipe, module); + + base::MessageLoop::current()->Run(); +} + +} // namespace apps +} // namespace mojo + +extern "C" MOJO_APPS_JS_EXPORT MojoResult CDECL MojoMain(MojoHandle pipe) { + mojo::GLES2Initializer gles2; + mojo::apps::Start(pipe, "mojo/apps/js/main"); + return MOJO_RESULT_OK; +} diff --git a/chromium/mojo/apps/js/main.js b/chromium/mojo/apps/js/main.js new file mode 100644 index 00000000000..a216126b35b --- /dev/null +++ b/chromium/mojo/apps/js/main.js @@ -0,0 +1,398 @@ +// 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', + 'monotonic_clock', + 'timer', + 'mojo/public/js/bindings/connection', + 'mojo/public/js/bindings/core', + 'mojo/apps/js/bindings/gl', + 'mojo/apps/js/bindings/threading', + 'mojo/services/native_viewport/native_viewport.mojom', + 'mojo/public/interfaces/service_provider/service_provider.mojom', +], function(console, + monotonicClock, + timer, + connection, + core, + gljs, + threading, + nativeViewport, + service_provider) { + + const VERTEX_SHADER_SOURCE = [ + 'uniform mat4 u_mvpMatrix;', + 'attribute vec4 a_position;', + 'void main()', + '{', + ' gl_Position = u_mvpMatrix * a_position;', + '}' + ].join('\n'); + + const FRAGMENT_SHADER_SOURCE = [ + 'precision mediump float;', + 'void main()', + '{', + ' gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );', + '}' + ].join('\n'); + + function ESMatrix() { + this.m = new Float32Array(16); + } + + ESMatrix.prototype.getIndex = function(x, y) { + return x * 4 + y; + } + + ESMatrix.prototype.set = function(x, y, v) { + this.m[this.getIndex(x, y)] = v; + }; + + ESMatrix.prototype.get = function(x, y) { + return this.m[this.getIndex(x, y)]; + }; + + ESMatrix.prototype.loadZero = function() { + for (var i = 0; i < this.m.length; i++) { + this.m[i] = 0; + } + }; + + ESMatrix.prototype.loadIdentity = function() { + this.loadZero(); + for (var i = 0; i < 4; i++) { + this.set(i, i, 1); + } + }; + + ESMatrix.prototype.multiply = function(a, b) { + var result = new ESMatrix(); + for (var i = 0; i < 4; i++) { + result.set(i, 0, + (a.get(i, 0) * b.get(0, 0)) + + (a.get(i, 1) * b.get(1, 0)) + + (a.get(i, 2) * b.get(2, 0)) + + (a.get(i, 3) * b.get(3, 0))); + + result.set(i, 1, + (a.get(i, 0) * b.get(0, 1)) + + (a.get(i, 1) * b.get(1, 1)) + + (a.get(i, 2) * b.get(2, 1)) + + (a.get(i, 3) * b.get(3, 1))); + + result.set(i, 2, + (a.get(i, 0) * b.get(0, 2)) + + (a.get(i, 1) * b.get(1, 2)) + + (a.get(i, 2) * b.get(2, 2)) + + (a.get(i, 3) * b.get(3, 2))); + + result.set(i, 3, + (a.get(i, 0) * b.get(0, 3)) + + (a.get(i, 1) * b.get(1, 3)) + + (a.get(i, 2) * b.get(2, 3)) + + (a.get(i, 3) * b.get(3, 3))); + } + for (var i = 0; i < result.m.length; i++) { + this.m[i] = result.m[i]; + } + }; + + ESMatrix.prototype.frustrum = function(left, right, bottom, top, nearZ, + farZ) { + var deltaX = right - left; + var deltaY = top - bottom; + var deltaZ = farZ - nearZ; + + if (nearZ < 0 || farZ < 0 || deltaZ < 0 || deltaY < 0 || deltaX < 0) { + return; + } + + var frust = new ESMatrix(); + frust.set(0, 0, 2 * nearZ / deltaX); + + frust.set(1, 1, 2 * nearZ / deltaY); + + frust.set(2, 0, (right + left) / deltaX); + frust.set(2, 1, (top + bottom) / deltaY); + frust.set(2, 2, -(nearZ + farZ) / deltaZ); + frust.set(2, 3, -1); + + frust.set(3, 2, -2 * nearZ * farZ / deltaZ); + + this.multiply(frust, this); + }; + + ESMatrix.prototype.perspective = function(fovY, aspect, nearZ, farZ) { + var frustrumH = Math.tan(fovY / 360 * Math.PI) * nearZ; + var frustrumW = frustrumH * aspect; + this.frustrum(-frustrumW, frustrumW, -frustrumH, frustrumH, nearZ, farZ); + }; + + ESMatrix.prototype.translate = function(tx, ty, tz) { + this.set(3, 0, this.get(3, 0) + this.get(0, 0) * + tx + this.get(1, 0) * ty + this.get(2, 0) * tz); + this.set(3, 1, this.get(3, 1) + this.get(0, 1) * + tx + this.get(1, 1) * ty + this.get(2, 1) * tz); + this.set(3, 2, this.get(3, 2) + this.get(0, 2) * + tx + this.get(1, 2) * ty + this.get(2, 2) * tz); + this.set(3, 3, this.get(3, 3) + this.get(0, 3) * + tx + this.get(1, 3) * ty + this.get(2, 3) * tz); + }; + + ESMatrix.prototype.rotate = function(angle, x, y, z) { + var mag = Math.sqrt(x * x + y * y + z * z); + var sinAngle = Math.sin(angle * Math.PI / 180); + var cosAngle = Math.cos(angle * Math.PI / 180); + if (mag <= 0) { + return; + } + + var xx, yy, zz, xy, yz, zx, xs, ys, zs, oneMinusCos; + var rotation = new ESMatrix(); + + x /= mag; + y /= mag; + z /= mag; + + xx = x * x; + yy = y * y; + zz = z * z; + xy = x * y; + yz = y * z; + zx = z * x; + xs = x * sinAngle; + ys = y * sinAngle; + zs = z * sinAngle; + oneMinusCos = 1 - cosAngle; + + rotation.set(0, 0, (oneMinusCos * xx) + cosAngle); + rotation.set(0, 1, (oneMinusCos * xy) - zs); + rotation.set(0, 2, (oneMinusCos * zx) + ys); + rotation.set(0, 3, 0); + + rotation.set(1, 0, (oneMinusCos * xy) + zs); + rotation.set(1, 1, (oneMinusCos * yy) + cosAngle); + rotation.set(1, 2, (oneMinusCos * yz) - xs); + rotation.set(1, 3, 0); + + rotation.set(2, 0, (oneMinusCos * zx) - ys); + rotation.set(2, 1, (oneMinusCos * yz) + xs); + rotation.set(2, 2, (oneMinusCos * zz) + cosAngle); + rotation.set(2, 3, 0); + + rotation.set(3, 0, 0); + rotation.set(3, 1, 0); + rotation.set(3, 2, 0); + rotation.set(3, 3, 1); + + this.multiply(rotation, this); + }; + + function loadProgram(gl) { + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE); + gl.compileShader(vertexShader); + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE); + gl.compileShader(fragmentShader); + + var program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + + gl.linkProgram(program); + // TODO(aa): Check for errors using getProgramiv and LINK_STATUS. + + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + + return program; + } + + var vboVertices; + var vboIndices; + function generateCube(gl) { + var numVertices = 24 * 3; + var numIndices = 12 * 3; + + var cubeVertices = new Float32Array([ + -0.5, -0.5, -0.5, + -0.5, -0.5, 0.5, + 0.5, -0.5, 0.5, + 0.5, -0.5, -0.5, + -0.5, 0.5, -0.5, + -0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, + 0.5, 0.5, -0.5, + -0.5, -0.5, -0.5, + -0.5, 0.5, -0.5, + 0.5, 0.5, -0.5, + 0.5, -0.5, -0.5, + -0.5, -0.5, 0.5, + -0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, + 0.5, -0.5, 0.5, + -0.5, -0.5, -0.5, + -0.5, -0.5, 0.5, + -0.5, 0.5, 0.5, + -0.5, 0.5, -0.5, + 0.5, -0.5, -0.5, + 0.5, -0.5, 0.5, + 0.5, 0.5, 0.5, + 0.5, 0.5, -0.5 + ]); + + var cubeIndices = new Uint16Array([ + 0, 2, 1, + 0, 3, 2, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11, + 12, 15, 14, + 12, 14, 13, + 16, 17, 18, + 16, 18, 19, + 20, 23, 22, + 20, 22, 21 + ]); + + // TODO(aa): The C++ program branches here on whether the pointer is + // non-NULL. + vboVertices = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vboVertices); + gl.bufferData(gl.ARRAY_BUFFER, cubeVertices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, 0); + + vboIndices = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vboIndices); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, cubeIndices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0); + + return cubeIndices.length; + } + + function SampleApp(service_provider) { + this.service_provider_ = service_provider; + + var pipe = new core.createMessagePipe(); + this.service_provider_.connect('mojo:mojo_native_viewport_service', + pipe.handle1); + new connection.Connection(pipe.handle0, NativeViewportClientImpl, + nativeViewport.NativeViewportProxy); + } + // TODO(aa): It is a bummer to need this stub object in JavaScript. We should + // have a 'client' object that contains both the sending and receiving bits of + // the client side of the interface. Since JS is loosely typed, we do not need + // a separate base class to inherit from to receive callbacks. + SampleApp.prototype = + Object.create(service_provider.ServiceProviderStub.prototype); + + + function NativeViewportClientImpl(remote) { + this.remote_ = remote; + + var pipe = core.createMessagePipe(); + + var rect = new nativeViewport.Rect; + rect.position = new nativeViewport.Point; + rect.size = new nativeViewport.Size; + rect.size.width = 800; + rect.size.height = 600; + this.remote_.create(rect); + this.remote_.show(); + this.remote_.createGLES2Context(pipe.handle1); + this.gles2_ = new GLES2ClientImpl(pipe.handle0); + } + NativeViewportClientImpl.prototype = + Object.create(nativeViewport.NativeViewportClientStub.prototype); + + NativeViewportClientImpl.prototype.onCreated = function() { + console.log('NativeViewportClientImpl.prototype.OnCreated'); + }; + + NativeViewportClientImpl.prototype.onBoundsChanged = function(bounds) { + console.log('NativeViewportClientImpl.prototype.OnBoundsChanged'); + this.gles2_.setDimensions(bounds.size); + } + + function GLES2ClientImpl(remotePipe) { + this.gl_ = new gljs.Context(remotePipe, this.contextLost.bind(this)); + this.lastTime_ = monotonicClock.seconds(); + this.angle_ = 45; + + this.program_ = loadProgram(this.gl_); + this.positionLocation_ = + this.gl_.getAttribLocation(this.program_, 'a_position'); + this.mvpLocation_ = + this.gl_.getUniformLocation(this.program_, 'u_mvpMatrix'); + this.numIndices_ = generateCube(this.gl_); + this.mvpMatrix_ = new ESMatrix(); + this.mvpMatrix_.loadIdentity(); + + this.gl_.clearColor(0, 0, 0, 0); + } + + GLES2ClientImpl.prototype.setDimensions = function(size) { + this.width_ = size.width; + this.height_ = size.height; + this.timer_ = timer.createRepeating(16, this.handleTimer.bind(this)); + } + + GLES2ClientImpl.prototype.drawCube = function() { + this.gl_.viewport(0, 0, this.width_, this.height_); + this.gl_.clear(this.gl_.COLOR_BUFFER_BIT); + this.gl_.useProgram(this.program_); + this.gl_.bindBuffer(this.gl_.ARRAY_BUFFER, vboVertices); + this.gl_.bindBuffer(this.gl_.ELEMENT_ARRAY_BUFFER, vboIndices); + this.gl_.vertexAttribPointer(this.positionLocation_, 3, this.gl_.FLOAT, + false, 12, 0); + this.gl_.enableVertexAttribArray(this.positionLocation_); + this.gl_.uniformMatrix4fv(this.mvpLocation_, false, this.mvpMatrix_.m); + this.gl_.drawElements(this.gl_.TRIANGLES, this.numIndices_, + this.gl_.UNSIGNED_SHORT, 0); + this.gl_.swapBuffers(); + }; + + GLES2ClientImpl.prototype.handleTimer = function() { + var now = monotonicClock.seconds(); + var secondsDelta = now - this.lastTime_; + this.lastTime_ = now; + + this.angle_ += this.getRotationForTimeDelta(secondsDelta); + this.angle_ = this.angle_ % 360; + + var aspect = this.width_ / this.height_; + + var perspective = new ESMatrix(); + perspective.loadIdentity(); + perspective.perspective(60, aspect, 1, 20); + + var modelView = new ESMatrix(); + modelView.loadIdentity(); + modelView.translate(0, 0, -2); + modelView.rotate(this.angle_, 1, 0, 1); + + this.mvpMatrix_.multiply(modelView, perspective); + + this.drawCube(); + }; + + GLES2ClientImpl.prototype.getRotationForTimeDelta = function(secondsDelta) { + return secondsDelta * 40; + }; + + GLES2ClientImpl.prototype.contextLost = function() { + console.log('GLES2ClientImpl.prototype.contextLost'); + }; + + + return function(handle) { + new connection.Connection( + handle, SampleApp, service_provider.ServiceProviderProxy); + }; +}); diff --git a/chromium/mojo/apps/js/mojo_runner_delegate.cc b/chromium/mojo/apps/js/mojo_runner_delegate.cc new file mode 100644 index 00000000000..8bec392564d --- /dev/null +++ b/chromium/mojo/apps/js/mojo_runner_delegate.cc @@ -0,0 +1,82 @@ +// 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 "mojo/apps/js/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 "mojo/apps/js/bindings/gl/module.h" +#include "mojo/apps/js/bindings/monotonic_clock.h" +#include "mojo/apps/js/bindings/threading.h" +#include "mojo/bindings/js/core.h" +#include "mojo/bindings/js/handle.h" +#include "mojo/bindings/js/support.h" + +namespace mojo { +namespace apps { + +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::gl::kModuleName, js::gl::GetModule); + AddBuiltinModule(MonotonicClock::kModuleName, MonotonicClock::GetModule); + AddBuiltinModule(Threading::kModuleName, 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 apps +} // namespace mojo diff --git a/chromium/mojo/apps/js/mojo_runner_delegate.h b/chromium/mojo/apps/js/mojo_runner_delegate.h new file mode 100644 index 00000000000..71ee4aff167 --- /dev/null +++ b/chromium/mojo/apps/js/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 MOJO_APPS_JS_MOJO_RUNNER_DELEGATE_H_ +#define MOJO_APPS_JS_MOJO_RUNNER_DELEGATE_H_ + +#include "base/compiler_specific.h" +#include "gin/modules/module_runner_delegate.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace apps { + +class MojoRunnerDelegate : public gin::ModuleRunnerDelegate { + public: + MojoRunnerDelegate(); + virtual ~MojoRunnerDelegate(); + + void Start(gin::Runner* runner, MojoHandle pipe, const std::string& module); + + private: + // From ModuleRunnerDelegate: + virtual void UnhandledException(gin::ShellRunner* runner, + gin::TryCatch& try_catch) OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(MojoRunnerDelegate); +}; + +} // namespace apps +} // namespace mojo + +#endif // MOJO_APPS_JS_MOJO_RUNNER_DELEGATE_H_ diff --git a/chromium/mojo/aura/DEPS b/chromium/mojo/aura/DEPS new file mode 100644 index 00000000000..ad60823f193 --- /dev/null +++ b/chromium/mojo/aura/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "+cc", + "+skia", + "+ui/aura", + "+ui/compositor", + "+ui/events", + "+ui/gfx", + "+ui/gl", + "+webkit/common/gpu", +] diff --git a/chromium/mojo/aura/aura_init.cc b/chromium/mojo/aura/aura_init.cc new file mode 100644 index 00000000000..a99ce25fc3a --- /dev/null +++ b/chromium/mojo/aura/aura_init.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/aura/aura_init.h" + +#include "mojo/aura/context_factory_mojo.h" +#include "mojo/aura/screen_mojo.h" +#include "ui/aura/env.h" + +namespace mojo { + +AuraInit::AuraInit() { + aura::Env::CreateInstance(true); + + context_factory_.reset(new ContextFactoryMojo); + aura::Env::GetInstance()->set_context_factory(context_factory_.get()); + + screen_.reset(ScreenMojo::Create()); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get()); +} + +AuraInit::~AuraInit() { +} + +} // namespace mojo diff --git a/chromium/mojo/aura/aura_init.h b/chromium/mojo/aura/aura_init.h new file mode 100644 index 00000000000..dea16843761 --- /dev/null +++ b/chromium/mojo/aura/aura_init.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_AURA_AURA_INIT_MOJO_H_ +#define MOJO_AURA_AURA_INIT_MOJO_H_ + +#include "base/memory/scoped_ptr.h" + +namespace ui { +class ContextFactory; +} + +namespace mojo { + +class ScreenMojo; + +// Sets up necessary state for aura when run with the viewmanager. +class AuraInit { + public: + AuraInit(); + ~AuraInit(); + + private: + scoped_ptr<ui::ContextFactory> context_factory_; + scoped_ptr<ScreenMojo> screen_; + + DISALLOW_COPY_AND_ASSIGN(AuraInit); +}; + +} // namespace mojo + +#endif // MOJO_AURA_AURA_INIT_MOJO_H_ diff --git a/chromium/mojo/aura/context_factory_mojo.cc b/chromium/mojo/aura/context_factory_mojo.cc new file mode 100644 index 00000000000..6b2b709cdcd --- /dev/null +++ b/chromium/mojo/aura/context_factory_mojo.cc @@ -0,0 +1,136 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/aura/context_factory_mojo.h" + +#include "base/bind.h" +#include "cc/output/output_surface.h" +#include "cc/output/software_output_device.h" +#include "cc/resources/shared_bitmap_manager.h" +#include "mojo/aura/window_tree_host_mojo.h" +#include "skia/ext/platform_canvas.h" +#include "ui/compositor/reflector.h" + +namespace mojo { +namespace { + +void FreeSharedBitmap(cc::SharedBitmap* shared_bitmap) { + delete shared_bitmap->memory(); +} + +void IgnoreSharedBitmap(cc::SharedBitmap* shared_bitmap) {} + +class SoftwareOutputDeviceViewManager : public cc::SoftwareOutputDevice { + public: + explicit SoftwareOutputDeviceViewManager(ui::Compositor* compositor) + : compositor_(compositor) { + } + virtual ~SoftwareOutputDeviceViewManager() {} + + // cc::SoftwareOutputDevice: + virtual void EndPaint(cc::SoftwareFrameData* frame_data) OVERRIDE { + WindowTreeHostMojo* window_tree_host = + WindowTreeHostMojo::ForCompositor(compositor_); + DCHECK(window_tree_host); + window_tree_host->SetContents( + skia::GetTopDevice(*canvas_)->accessBitmap(true)); + + SoftwareOutputDevice::EndPaint(frame_data); + } + + private: + ui::Compositor* compositor_; + + DISALLOW_COPY_AND_ASSIGN(SoftwareOutputDeviceViewManager); +}; + +// TODO(sky): this is a copy from cc/test. Copy to a common place. +class TestSharedBitmapManager : public cc::SharedBitmapManager { + public: + TestSharedBitmapManager() {} + virtual ~TestSharedBitmapManager() {} + + virtual scoped_ptr<cc::SharedBitmap> AllocateSharedBitmap( + const gfx::Size& size) OVERRIDE { + base::AutoLock lock(lock_); + scoped_ptr<base::SharedMemory> memory(new base::SharedMemory); + memory->CreateAndMapAnonymous(size.GetArea() * 4); + cc::SharedBitmapId id = cc::SharedBitmap::GenerateId(); + bitmap_map_[id] = memory.get(); + return scoped_ptr<cc::SharedBitmap>( + new cc::SharedBitmap(memory.release(), id, + base::Bind(&FreeSharedBitmap))); + } + + virtual scoped_ptr<cc::SharedBitmap> GetSharedBitmapFromId( + const gfx::Size&, + const cc::SharedBitmapId& id) OVERRIDE { + base::AutoLock lock(lock_); + if (bitmap_map_.find(id) == bitmap_map_.end()) + return scoped_ptr<cc::SharedBitmap>(); + return scoped_ptr<cc::SharedBitmap>( + new cc::SharedBitmap(bitmap_map_[id], id, + base::Bind(&IgnoreSharedBitmap))); + } + + virtual scoped_ptr<cc::SharedBitmap> GetBitmapForSharedMemory( + base::SharedMemory* memory) OVERRIDE { + base::AutoLock lock(lock_); + cc::SharedBitmapId id = cc::SharedBitmap::GenerateId(); + bitmap_map_[id] = memory; + return scoped_ptr<cc::SharedBitmap>( + new cc::SharedBitmap(memory, id, base::Bind(&IgnoreSharedBitmap))); + } + + private: + base::Lock lock_; + std::map<cc::SharedBitmapId, base::SharedMemory*> bitmap_map_; + + DISALLOW_COPY_AND_ASSIGN(TestSharedBitmapManager); +}; + +} // namespace + +ContextFactoryMojo::ContextFactoryMojo() + : shared_bitmap_manager_(new TestSharedBitmapManager()) { +} + +ContextFactoryMojo::~ContextFactoryMojo() {} + +scoped_ptr<cc::OutputSurface> ContextFactoryMojo::CreateOutputSurface( + ui::Compositor* compositor, + bool software_fallback) { + scoped_ptr<cc::SoftwareOutputDevice> output_device( + new SoftwareOutputDeviceViewManager(compositor)); + return make_scoped_ptr(new cc::OutputSurface(output_device.Pass())); +} + +scoped_refptr<ui::Reflector> ContextFactoryMojo::CreateReflector( + ui::Compositor* mirroed_compositor, + ui::Layer* mirroring_layer) { + return new ui::Reflector(); +} + +void ContextFactoryMojo::RemoveReflector( + scoped_refptr<ui::Reflector> reflector) { +} + +scoped_refptr<cc::ContextProvider> +ContextFactoryMojo::SharedMainThreadContextProvider() { + return scoped_refptr<cc::ContextProvider>(NULL); +} + +void ContextFactoryMojo::RemoveCompositor(ui::Compositor* compositor) {} + +bool ContextFactoryMojo::DoesCreateTestContexts() { return false; } + +cc::SharedBitmapManager* ContextFactoryMojo::GetSharedBitmapManager() { + return shared_bitmap_manager_.get(); +} + +base::MessageLoopProxy* ContextFactoryMojo::GetCompositorMessageLoop() { + return NULL; +} + +} // namespace mojo diff --git a/chromium/mojo/aura/context_factory_mojo.h b/chromium/mojo/aura/context_factory_mojo.h new file mode 100644 index 00000000000..800afa7bd6d --- /dev/null +++ b/chromium/mojo/aura/context_factory_mojo.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_AURA_CONTEXT_FACTORY_MOJO_H_ +#define MOJO_AURA_CONTEXT_FACTORY_MOJO_H_ + +#include "ui/compositor/compositor.h" + +namespace mojo { + +class ContextFactoryMojo : public ui::ContextFactory { + public: + ContextFactoryMojo(); + virtual ~ContextFactoryMojo(); + + private: + // ContextFactory: + virtual scoped_ptr<cc::OutputSurface> CreateOutputSurface( + ui::Compositor* compositor, + bool software_fallback) OVERRIDE; + virtual scoped_refptr<ui::Reflector> CreateReflector( + ui::Compositor* mirrored_compositor, + ui::Layer* mirroring_layer) OVERRIDE; + virtual void RemoveReflector(scoped_refptr<ui::Reflector> reflector) OVERRIDE; + virtual scoped_refptr<cc::ContextProvider> SharedMainThreadContextProvider() + OVERRIDE; + virtual void RemoveCompositor(ui::Compositor* compositor) OVERRIDE; + virtual bool DoesCreateTestContexts() OVERRIDE; + virtual cc::SharedBitmapManager* GetSharedBitmapManager() OVERRIDE; + virtual base::MessageLoopProxy* GetCompositorMessageLoop() OVERRIDE; + + scoped_ptr<cc::SharedBitmapManager> shared_bitmap_manager_; + + DISALLOW_COPY_AND_ASSIGN(ContextFactoryMojo); +}; + +} // namespace mojo + +#endif // MOJO_AURA_CONTEXT_FACTORY_MOJO_H_ diff --git a/chromium/mojo/aura/screen_mojo.cc b/chromium/mojo/aura/screen_mojo.cc new file mode 100644 index 00000000000..b07c320a601 --- /dev/null +++ b/chromium/mojo/aura/screen_mojo.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/aura/screen_mojo.h" + +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect_conversions.h" + +namespace mojo { + +// static +ScreenMojo* ScreenMojo::Create() { + return new ScreenMojo(gfx::Rect(0, 0, 800, 600)); +} + +ScreenMojo::~ScreenMojo() { +} + +bool ScreenMojo::IsDIPEnabled() { + NOTIMPLEMENTED(); + return true; +} + +gfx::Point ScreenMojo::GetCursorScreenPoint() { + NOTIMPLEMENTED(); + return gfx::Point(); +} + +gfx::NativeWindow ScreenMojo::GetWindowUnderCursor() { + return GetWindowAtScreenPoint(GetCursorScreenPoint()); +} + +gfx::NativeWindow ScreenMojo::GetWindowAtScreenPoint(const gfx::Point& point) { + NOTIMPLEMENTED(); + return NULL; +} + +int ScreenMojo::GetNumDisplays() const { + return 1; +} + +std::vector<gfx::Display> ScreenMojo::GetAllDisplays() const { + return std::vector<gfx::Display>(1, display_); +} + +gfx::Display ScreenMojo::GetDisplayNearestWindow( + gfx::NativeWindow window) const { + return display_; +} + +gfx::Display ScreenMojo::GetDisplayNearestPoint(const gfx::Point& point) const { + return display_; +} + +gfx::Display ScreenMojo::GetDisplayMatching(const gfx::Rect& match_rect) const { + return display_; +} + +gfx::Display ScreenMojo::GetPrimaryDisplay() const { + return display_; +} + +void ScreenMojo::AddObserver(gfx::DisplayObserver* observer) { +} + +void ScreenMojo::RemoveObserver(gfx::DisplayObserver* observer) { +} + +ScreenMojo::ScreenMojo(const gfx::Rect& screen_bounds) { + static int64 synthesized_display_id = 2000; + display_.set_id(synthesized_display_id++); + display_.SetScaleAndBounds(1.0f, screen_bounds); +} + +} // namespace mojo diff --git a/chromium/mojo/aura/screen_mojo.h b/chromium/mojo/aura/screen_mojo.h new file mode 100644 index 00000000000..ad50c1cee71 --- /dev/null +++ b/chromium/mojo/aura/screen_mojo.h @@ -0,0 +1,54 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_AURA_SCREEN_MOJO_H_ +#define MOJO_AURA_SCREEN_MOJO_H_ + +#include "base/compiler_specific.h" +#include "ui/gfx/display.h" +#include "ui/gfx/screen.h" + +namespace gfx { +class Rect; +class Transform; +} + +namespace mojo { + +// A minimal implementation of gfx::Screen for mojo. +class ScreenMojo : public gfx::Screen { + public: + static ScreenMojo* Create(); + virtual ~ScreenMojo(); + + protected: + // gfx::Screen overrides: + virtual bool IsDIPEnabled() OVERRIDE; + virtual gfx::Point GetCursorScreenPoint() OVERRIDE; + virtual gfx::NativeWindow GetWindowUnderCursor() OVERRIDE; + virtual gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) + OVERRIDE; + virtual int GetNumDisplays() const OVERRIDE; + virtual std::vector<gfx::Display> GetAllDisplays() const OVERRIDE; + virtual gfx::Display GetDisplayNearestWindow( + gfx::NativeView view) const OVERRIDE; + virtual gfx::Display GetDisplayNearestPoint( + const gfx::Point& point) const OVERRIDE; + virtual gfx::Display GetDisplayMatching( + const gfx::Rect& match_rect) const OVERRIDE; + virtual gfx::Display GetPrimaryDisplay() const OVERRIDE; + virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE; + virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE; + + private: + explicit ScreenMojo(const gfx::Rect& screen_bounds); + + gfx::Display display_; + + DISALLOW_COPY_AND_ASSIGN(ScreenMojo); +}; + +} // namespace mojo + +#endif // MOJO_AURA_SCREEN_MOJO_H_ diff --git a/chromium/mojo/aura/window_tree_host_mojo.cc b/chromium/mojo/aura/window_tree_host_mojo.cc new file mode 100644 index 00000000000..75fdb7dff93 --- /dev/null +++ b/chromium/mojo/aura/window_tree_host_mojo.cc @@ -0,0 +1,179 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/aura/window_tree_host_mojo.h" + +#include <vector> + +#include "mojo/aura/window_tree_host_mojo_delegate.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" + +namespace mojo { +namespace { + +const char kTreeHostsKey[] = "tree_hosts"; + +typedef std::vector<WindowTreeHostMojo*> Managers; + +class TreeHosts : public base::SupportsUserData::Data { + public: + TreeHosts() {} + virtual ~TreeHosts() {} + + static TreeHosts* Get() { + TreeHosts* hosts = static_cast<TreeHosts*>( + aura::Env::GetInstance()->GetUserData(kTreeHostsKey)); + if (!hosts) { + hosts = new TreeHosts; + aura::Env::GetInstance()->SetUserData(kTreeHostsKey, hosts); + } + return hosts; + } + + void Add(WindowTreeHostMojo* manager) { + managers_.push_back(manager); + } + + void Remove(WindowTreeHostMojo* manager) { + Managers::iterator i = std::find(managers_.begin(), managers_.end(), + manager); + DCHECK(i != managers_.end()); + managers_.erase(i); + } + + const std::vector<WindowTreeHostMojo*> managers() const { + return managers_; + } + + private: + Managers managers_; + + DISALLOW_COPY_AND_ASSIGN(TreeHosts); +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostMojo, public: + +WindowTreeHostMojo::WindowTreeHostMojo(view_manager::Node* node, + WindowTreeHostMojoDelegate* delegate) + : node_(node), + bounds_(node->bounds()), + delegate_(delegate) { + node_->AddObserver(this); + CreateCompositor(GetAcceleratedWidget()); + + TreeHosts::Get()->Add(this); +} + +WindowTreeHostMojo::~WindowTreeHostMojo() { + node_->RemoveObserver(this); + TreeHosts::Get()->Remove(this); + DestroyCompositor(); + DestroyDispatcher(); +} + +// static +WindowTreeHostMojo* WindowTreeHostMojo::ForCompositor( + ui::Compositor* compositor) { + const Managers& managers = TreeHosts::Get()->managers(); + for (size_t i = 0; i < managers.size(); ++i) { + if (managers[i]->compositor() == compositor) + return managers[i]; + } + return NULL; +} + +void WindowTreeHostMojo::SetContents(const SkBitmap& contents) { + delegate_->CompositorContentsChanged(contents); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostMojo, aura::WindowTreeHost implementation: + +ui::EventSource* WindowTreeHostMojo::GetEventSource() { + return this; +} + +gfx::AcceleratedWidget WindowTreeHostMojo::GetAcceleratedWidget() { + return gfx::kNullAcceleratedWidget; +} + +void WindowTreeHostMojo::Show() { + window()->Show(); +} + +void WindowTreeHostMojo::Hide() { +} + +gfx::Rect WindowTreeHostMojo::GetBounds() const { + return bounds_; +} + +void WindowTreeHostMojo::SetBounds(const gfx::Rect& bounds) { + window()->SetBounds(gfx::Rect(bounds.size())); +} + +gfx::Point WindowTreeHostMojo::GetLocationOnNativeScreen() const { + return gfx::Point(0, 0); +} + +void WindowTreeHostMojo::SetCapture() { + NOTIMPLEMENTED(); +} + +void WindowTreeHostMojo::ReleaseCapture() { + NOTIMPLEMENTED(); +} + +void WindowTreeHostMojo::PostNativeEvent( + const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostMojo::OnDeviceScaleFactorChanged( + float device_scale_factor) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostMojo::SetCursorNative(gfx::NativeCursor cursor) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostMojo::MoveCursorToNative(const gfx::Point& location) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostMojo::OnCursorVisibilityChangedNative(bool show) { + NOTIMPLEMENTED(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostMojo, ui::EventSource implementation: + +ui::EventProcessor* WindowTreeHostMojo::GetEventProcessor() { + return dispatcher(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostMojo, view_manager::NodeObserver implementation: + +void WindowTreeHostMojo::OnNodeBoundsChange( + view_manager::Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + view_manager::NodeObserver::DispositionChangePhase phase) { + bounds_ = new_bounds; + if (old_bounds.origin() != new_bounds.origin()) + OnHostMoved(bounds_.origin()); + if (old_bounds.size() != new_bounds.size()) + OnHostResized(bounds_.size()); +} + +} // namespace mojo diff --git a/chromium/mojo/aura/window_tree_host_mojo.h b/chromium/mojo/aura/window_tree_host_mojo.h new file mode 100644 index 00000000000..993c4b1a02c --- /dev/null +++ b/chromium/mojo/aura/window_tree_host_mojo.h @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_H_ +#define MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_H_ + +#include "mojo/services/public/cpp/view_manager/node_observer.h" +#include "ui/aura/window_tree_host.h" +#include "ui/events/event_source.h" +#include "ui/gfx/geometry/rect.h" + +class SkBitmap; + +namespace ui { +class Compositor; +} + +namespace mojo { + +class WindowTreeHostMojoDelegate; + +class WindowTreeHostMojo : public aura::WindowTreeHost, + public ui::EventSource, + public view_manager::NodeObserver { + public: + WindowTreeHostMojo(view_manager::Node* node, + WindowTreeHostMojoDelegate* delegate); + virtual ~WindowTreeHostMojo(); + + // Returns the WindowTreeHostMojo for the specified compositor. + static WindowTreeHostMojo* ForCompositor(ui::Compositor* compositor); + + const gfx::Rect& bounds() const { return bounds_; } + + // Sets the contents to show in this WindowTreeHost. This forwards to the + // delegate. + void SetContents(const SkBitmap& contents); + + ui::EventDispatchDetails SendEventToProcessor(ui::Event* event) { + return ui::EventSource::SendEventToProcessor(event); + } + + private: + // WindowTreeHost: + virtual ui::EventSource* GetEventSource() OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual gfx::Rect GetBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void PostNativeEvent(const base::NativeEvent& native_event) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void SetCursorNative(gfx::NativeCursor cursor) OVERRIDE; + virtual void MoveCursorToNative(const gfx::Point& location) OVERRIDE; + virtual void OnCursorVisibilityChangedNative(bool show) OVERRIDE; + + // ui::EventSource: + virtual ui::EventProcessor* GetEventProcessor() OVERRIDE; + + // view_manager::NodeObserver: + virtual void OnNodeBoundsChange( + view_manager::Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + view_manager::NodeObserver::DispositionChangePhase phase) OVERRIDE; + + view_manager::Node* node_; + + gfx::Rect bounds_; + + WindowTreeHostMojoDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeHostMojo); +}; + +} // namespace mojo + +#endif // MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_H_ diff --git a/chromium/mojo/aura/window_tree_host_mojo_delegate.h b/chromium/mojo/aura/window_tree_host_mojo_delegate.h new file mode 100644 index 00000000000..9ab13b27d25 --- /dev/null +++ b/chromium/mojo/aura/window_tree_host_mojo_delegate.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_DELEGATE_H_ +#define MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_DELEGATE_H_ + +class SkBitmap; + +namespace mojo { + +class WindowTreeHostMojoDelegate { + public: + // Invoked when the contents of the composite associated with the + // WindowTreeHostMojo are updated. + virtual void CompositorContentsChanged(const SkBitmap& bitmap) = 0; + + protected: + virtual ~WindowTreeHostMojoDelegate() {} +}; + +} // namespace mojo + +#endif // MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_DELEGATE_H_ diff --git a/chromium/mojo/bindings/js/DEPS b/chromium/mojo/bindings/js/DEPS new file mode 100644 index 00000000000..d974b6806fc --- /dev/null +++ b/chromium/mojo/bindings/js/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+gin", + "+v8", +] diff --git a/chromium/mojo/bindings/js/codec_unittests.js b/chromium/mojo/bindings/js/codec_unittests.js new file mode 100644 index 00000000000..6a223ffe89b --- /dev/null +++ b/chromium/mojo/bindings/js/codec_unittests.js @@ -0,0 +1,222 @@ +// 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([ + "gin/test/expect", + "mojo/public/js/bindings/codec", + "mojo/public/interfaces/bindings/tests/sample_service.mojom", + ], function(expect, codec, sample) { + testBar(); + testFoo(); + testTypes(); + testAlign(); + testUtf8(); + this.result = "PASS"; + + function testBar() { + var bar = new sample.Bar(); + bar.alpha = 1; + bar.beta = 2; + bar.gamma = 3; + bar.type = 0x08070605; + bar.extraProperty = "banana"; + + var messageName = 42; + var payloadSize = sample.Bar.encodedSize; + + var builder = new codec.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(sample.Bar, bar); + + var message = builder.finish(); + + var expectedMemory = new Uint8Array([ + 16, 0, 0, 0, + 2, 0, 0, 0, + 42, 0, 0, 0, + 0, 0, 0, 0, + + 16, 0, 0, 0, + 4, 0, 0, 0, + + 1, 2, 3, 0, + 5, 6, 7, 8, + ]); + + var actualMemory = new Uint8Array(message.buffer.arrayBuffer); + expect(actualMemory).toEqual(expectedMemory); + + var reader = new codec.MessageReader(message); + + expect(reader.payloadSize).toBe(payloadSize); + expect(reader.messageName).toBe(messageName); + + var bar2 = reader.decodeStruct(sample.Bar); + + expect(bar2.alpha).toBe(bar.alpha); + expect(bar2.beta).toBe(bar.beta); + expect(bar2.gamma).toBe(bar.gamma); + expect("extraProperty" in bar2).toBeFalsy(); + } + + function testFoo() { + var foo = new sample.Foo(); + foo.x = 0x212B4D5; + foo.y = 0x16E93; + foo.a = 1; + foo.b = 0; + foo.c = 3; // This will get truncated to one bit. + foo.bar = new sample.Bar(); + foo.bar.alpha = 91; + foo.bar.beta = 82; + foo.bar.gamma = 73; + foo.data = [ + 4, 5, 6, 7, 8, + ]; + foo.extra_bars = [ + new sample.Bar(), new sample.Bar(), new sample.Bar(), + ]; + for (var i = 0; i < foo.extra_bars.length; ++i) { + foo.extra_bars[i].alpha = 1 * i; + foo.extra_bars[i].beta = 2 * i; + foo.extra_bars[i].gamma = 3 * i; + } + foo.name = "I am a banana"; + // This is supposed to be a handle, but we fake it with an integer. + foo.source = 23423782; + foo.array_of_array_of_bools = [ + [true], [false, true] + ]; + + var messageName = 31; + var payloadSize = 304; + + var builder = new codec.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(sample.Foo, foo); + + var message = builder.finish(); + + var expectedMemory = new Uint8Array([ + /* 0: */ 16, 0, 0, 0, 2, 0, 0, 0, + /* 8: */ 31, 0, 0, 0, 0, 0, 0, 0, + /* 16: */ 88, 0, 0, 0, 14, 0, 0, 0, + /* 24: */ 0xD5, 0xB4, 0x12, 0x02, 0x93, 0x6E, 0x01, 0, + /* 32: */ 5, 0, 0, 0, 0, 0, 0, 0, + /* 40: */ 64, 0, 0, 0, 0, 0, 0, 0, + ]); + // TODO(abarth): Test more of the message's raw memory. + var actualMemory = new Uint8Array(message.buffer.arrayBuffer, + 0, expectedMemory.length); + expect(actualMemory).toEqual(expectedMemory); + + var expectedHandles = [ + 23423782, + ]; + + expect(message.handles).toEqual(expectedHandles); + + var reader = new codec.MessageReader(message); + + expect(reader.payloadSize).toBe(payloadSize); + expect(reader.messageName).toBe(messageName); + + var foo2 = reader.decodeStruct(sample.Foo); + + expect(foo2.x).toBe(foo.x); + expect(foo2.y).toBe(foo.y); + + expect(foo2.a).toBe(foo.a & 1 ? true : false); + expect(foo2.b).toBe(foo.b & 1 ? true : false); + expect(foo2.c).toBe(foo.c & 1 ? true : false); + + expect(foo2.bar).toEqual(foo.bar); + expect(foo2.data).toEqual(foo.data); + + expect(foo2.extra_bars).toEqual(foo.extra_bars); + expect(foo2.name).toBe(foo.name); + expect(foo2.source).toEqual(foo.source); + } + + function testTypes() { + function encodeDecode(cls, input, expectedResult, encodedSize) { + var messageName = 42; + var payloadSize = encodedSize || cls.encodedSize; + + var builder = new codec.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(cls, input) + var message = builder.finish(); + + var reader = new codec.MessageReader(message); + expect(reader.payloadSize).toBe(payloadSize); + expect(reader.messageName).toBe(messageName); + var result = reader.decodeStruct(cls); + expect(result).toEqual(expectedResult); + } + encodeDecode(codec.String, "banana", "banana", 24); + encodeDecode(codec.Int8, -1, -1); + encodeDecode(codec.Int8, 0xff, -1); + encodeDecode(codec.Int16, -1, -1); + encodeDecode(codec.Int16, 0xff, 0xff); + encodeDecode(codec.Int16, 0xffff, -1); + encodeDecode(codec.Int32, -1, -1); + encodeDecode(codec.Int32, 0xffff, 0xffff); + encodeDecode(codec.Int32, 0xffffffff, -1); + encodeDecode(codec.Float, 1.0, 1.0); + encodeDecode(codec.Double, 1.0, 1.0); + } + + function testAlign() { + var aligned = [ + 0, // 0 + 8, // 1 + 8, // 2 + 8, // 3 + 8, // 4 + 8, // 5 + 8, // 6 + 8, // 7 + 8, // 8 + 16, // 9 + 16, // 10 + 16, // 11 + 16, // 12 + 16, // 13 + 16, // 14 + 16, // 15 + 16, // 16 + 24, // 17 + 24, // 18 + 24, // 19 + 24, // 20 + ]; + for (var i = 0; i < aligned.length; ++i) + expect(codec.align(i)).toBe(aligned[i]); + } + + function testUtf8() { + var str = "B\u03ba\u1f79"; // some UCS-2 codepoints + var messageName = 42; + var payloadSize = 24; + + var builder = new codec.MessageBuilder(messageName, payloadSize); + var encoder = builder.createEncoder(8); + encoder.encodeStringPointer(str); + var message = builder.finish(); + var expectedMemory = new Uint8Array([ + /* 0: */ 16, 0, 0, 0, 2, 0, 0, 0, + /* 8: */ 42, 0, 0, 0, 0, 0, 0, 0, + /* 16: */ 8, 0, 0, 0, 0, 0, 0, 0, + /* 24: */ 14, 0, 0, 0, 6, 0, 0, 0, + /* 32: */ 0x42, 0xCE, 0xBA, 0xE1, 0xBD, 0xB9, 0, 0, + ]); + var actualMemory = new Uint8Array(message.buffer.arrayBuffer); + expect(actualMemory.length).toEqual(expectedMemory.length); + expect(actualMemory).toEqual(expectedMemory); + + var reader = new codec.MessageReader(message); + expect(reader.payloadSize).toBe(payloadSize); + expect(reader.messageName).toBe(messageName); + var str2 = reader.decoder.decodeStringPointer(); + expect(str2).toEqual(str); + } +}); diff --git a/chromium/mojo/bindings/js/core.cc b/chromium/mojo/bindings/js/core.cc new file mode 100644 index 00000000000..d6bd2ea68a8 --- /dev/null +++ b/chromium/mojo/bindings/js/core.cc @@ -0,0 +1,278 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/bindings/js/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 "mojo/bindings/js/handle.h" + +namespace mojo { +namespace js { + +namespace { + +MojoResult CloseHandle(gin::Handle<gin::HandleWrapper> handle) { + if (!handle->get().is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + handle->Close(); + return MOJO_RESULT_OK; +} + +MojoResult WaitHandle(mojo::Handle handle, + MojoHandleSignals signals, + MojoDeadline deadline) { + return MojoWait(handle.value(), signals, deadline); +} + +MojoResult WaitMany( + const std::vector<mojo::Handle>& handles, + const std::vector<MojoHandleSignals>& signals, + MojoDeadline deadline) { + return mojo::WaitMany(handles, signals, deadline); +} + +gin::Dictionary CreateMessagePipe(const gin::Arguments& args) { + MojoHandle handle0 = MOJO_HANDLE_INVALID; + MojoHandle handle1 = MOJO_HANDLE_INVALID; + // TODO(vtl): Add support for the options struct. + MojoResult result = MojoCreateMessagePipe(NULL, &handle0, &handle1); + CHECK(result == MOJO_RESULT_OK); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + 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<gin::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, + v8::Handle<v8::Value> options_value) { + 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; + + 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 if (options_value->IsNull() || options_value->IsUndefined()) { + result = MojoCreateDataPipe(NULL, &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; +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Core::kModuleName[] = "mojo/public/js/bindings/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) + + .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_READABLE", MOJO_HANDLE_SIGNAL_READABLE) + + .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("CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD", + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD) + + .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) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +} // namespace js +} // namespace mojo diff --git a/chromium/mojo/bindings/js/core.h b/chromium/mojo/bindings/js/core.h new file mode 100644 index 00000000000..bde327ce5ad --- /dev/null +++ b/chromium/mojo/bindings/js/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 MOJO_BINDINGS_JS_CORE_H_ +#define MOJO_BINDINGS_JS_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 // MOJO_BINDINGS_JS_CORE_H_ diff --git a/chromium/mojo/bindings/js/core_unittests.js b/chromium/mojo/bindings/js/core_unittests.js new file mode 100644 index 00000000000..20446dbc260 --- /dev/null +++ b/chromium/mojo/bindings/js/core_unittests.js @@ -0,0 +1,91 @@ +// 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([ + "gin/test/expect", + "mojo/public/js/bindings/core", + "gc", + ], function(expect, core, gc) { + runWithMessagePipe(testNop); + runWithMessagePipe(testReadAndWriteMessage); + runWithDataPipe(testNop); + runWithDataPipe(testReadAndWriteDataPipe); + gc.collectGarbage(); // should not crash + this.result = "PASS"; + + function runWithMessagePipe(test) { + var pipe = core.createMessagePipe(); + + test(pipe); + + expect(core.close(pipe.handle0)).toBe(core.RESULT_OK); + expect(core.close(pipe.handle1)).toBe(core.RESULT_OK); + } + + function runWithDataPipe(test) { + var pipe = core.createDataPipe({ + flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + elementNumBytes: 1, + capacityNumBytes: 64 + }); + expect(pipe.result).toBe(core.RESULT_OK); + + test(pipe); + + expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK); + expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK); + } + + function testNop(pipe) { + } + + function testReadAndWriteMessage(pipe) { + var senderData = new Uint8Array(42); + for (var i = 0; i < senderData.length; ++i) { + senderData[i] = i * i; + } + + var result = core.writeMessage( + pipe.handle0, senderData, [], + core.WRITE_MESSAGE_FLAG_NONE); + + expect(result).toBe(core.RESULT_OK); + + var read = core.readMessage( + pipe.handle1, core.READ_MESSAGE_FLAG_NONE); + + expect(read.result).toBe(core.RESULT_OK); + expect(read.buffer.byteLength).toBe(42); + expect(read.handles.length).toBe(0); + + var memory = new Uint8Array(read.buffer); + for (var i = 0; i < memory.length; ++i) + expect(memory[i]).toBe((i * i) & 0xFF); + } + + function testReadAndWriteDataPipe(pipe) { + var senderData = new Uint8Array(42); + for (var i = 0; i < senderData.length; ++i) { + senderData[i] = i * i; + } + + var write = core.writeData( + pipe.producerHandle, senderData, + core.WRITE_DATA_FLAG_ALL_OR_NONE); + + expect(write.result).toBe(core.RESULT_OK); + expect(write.numBytes).toBe(42); + + var read = core.readData( + pipe.consumerHandle, core.READ_DATA_FLAG_ALL_OR_NONE); + + expect(read.result).toBe(core.RESULT_OK); + expect(read.buffer.byteLength).toBe(42); + + var memory = new Uint8Array(read.buffer); + for (var i = 0; i < memory.length; ++i) + expect(memory[i]).toBe((i * i) & 0xFF); + } + +}); diff --git a/chromium/mojo/bindings/js/handle.cc b/chromium/mojo/bindings/js/handle.cc new file mode 100644 index 00000000000..e1d0fb04b60 --- /dev/null +++ b/chromium/mojo/bindings/js/handle.cc @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/bindings/js/handle.h" + +namespace gin { + +gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin }; + +HandleWrapper::HandleWrapper(MojoHandle handle) + : handle_(mojo::Handle(handle)) { +} + +HandleWrapper::~HandleWrapper() { +} + +v8::Handle<v8::Value> Converter<mojo::Handle>::ToV8(v8::Isolate* isolate, + const mojo::Handle& val) { + if (!val.is_valid()) + return v8::Null(isolate); + return 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<HandleWrapper> handle; + if (!Converter<gin::Handle<HandleWrapper> >::FromV8(isolate, val, &handle)) + return false; + + *out = handle->get(); + return true; +} + +} // namespace gin diff --git a/chromium/mojo/bindings/js/handle.h b/chromium/mojo/bindings/js/handle.h new file mode 100644 index 00000000000..35202b07ea6 --- /dev/null +++ b/chromium/mojo/bindings/js/handle.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_BINDINGS_JS_HANDLE_H_ +#define MOJO_BINDINGS_JS_HANDLE_H_ + +#include "gin/converter.h" +#include "gin/handle.h" +#include "gin/wrappable.h" +#include "mojo/public/cpp/system/core.h" + +namespace gin { + +// 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() { handle_.reset(); } + + protected: + HandleWrapper(MojoHandle handle); + virtual ~HandleWrapper(); + mojo::ScopedHandle handle_; +}; + +// 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); +}; + +// 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<gin::HandleWrapper> > { + static v8::Handle<v8::Value> ToV8( + v8::Isolate* isolate, const gin::Handle<gin::HandleWrapper>& val) { + return val.ToV8(); + } + + static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val, + gin::Handle<gin::HandleWrapper>* out) { + if (val->IsNull()) { + *out = HandleWrapper::Create(isolate, MOJO_HANDLE_INVALID); + return true; + } + + gin::HandleWrapper* object = NULL; + if (!Converter<gin::HandleWrapper*>::FromV8(isolate, val, &object)) { + return false; + } + *out = gin::Handle<gin::HandleWrapper>(val, object); + return true; + } +}; + +} // namespace gin + +#endif // MOJO_BINDINGS_JS_HANDLE_H_ diff --git a/chromium/mojo/bindings/js/run_js_tests.cc b/chromium/mojo/bindings/js/run_js_tests.cc new file mode 100644 index 00000000000..976a0b17591 --- /dev/null +++ b/chromium/mojo/bindings/js/run_js_tests.cc @@ -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. + +#include "base/file_util.h" +#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 "mojo/bindings/js/core.h" +#include "mojo/common/test/test_utils.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); + } + + 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("bindings") + .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) { + // TODO(yzshen): Remove this check once isolated tests are supported on the + // Chromium waterfall. (http://crbug.com/351214) + const base::FilePath test_file_path( + test::GetFilePathForJSResource( + "mojo/public/interfaces/bindings/tests/sample_service.mojom")); + if (!base::PathExists(test_file_path)) { + LOG(WARNING) << "Mojom binding files don't exist. Skipping the test."; + return; + } + + RunTest("codec_unittests.js", true); +} + +} // namespace +} // namespace js +} // namespace mojo diff --git a/chromium/mojo/bindings/js/support.cc b/chromium/mojo/bindings/js/support.cc new file mode 100644 index 00000000000..dc97865cca7 --- /dev/null +++ b/chromium/mojo/bindings/js/support.cc @@ -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. + +#include "mojo/bindings/js/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/bindings/js/handle.h" +#include "mojo/bindings/js/waiting_callback.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace js { + +namespace { + +WaitingCallback* AsyncWait(const gin::Arguments& args, mojo::Handle 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/bindings/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/chromium/mojo/bindings/js/support.h b/chromium/mojo/bindings/js/support.h new file mode 100644 index 00000000000..0f6eb07c2b1 --- /dev/null +++ b/chromium/mojo/bindings/js/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 MOJO_BINDINGS_JS_SUPPORT_H_ +#define MOJO_BINDINGS_JS_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 // MOJO_BINDINGS_JS_SUPPORT_H_ diff --git a/chromium/mojo/bindings/js/waiting_callback.cc b/chromium/mojo/bindings/js/waiting_callback.cc new file mode 100644 index 00000000000..692551bb0e7 --- /dev/null +++ b/chromium/mojo/bindings/js/waiting_callback.cc @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/bindings/js/waiting_callback.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, + mojo::Handle handle, + MojoHandleSignals signals) { + gin::Handle<WaitingCallback> waiting_callback = + gin::CreateHandle(isolate, new WaitingCallback(isolate, callback)); + waiting_callback->wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait( + handle.value(), + signals, + MOJO_DEADLINE_INDEFINITE, + &WaitingCallback::CallOnHandleReady, + waiting_callback.get()); + return waiting_callback; +} + +void WaitingCallback::Cancel() { + if (!wait_id_) + return; + + Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_); + wait_id_ = 0; +} + +WaitingCallback::WaitingCallback(v8::Isolate* isolate, + v8::Handle<v8::Function> callback) + : wait_id_() { + 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::OnHandleReady(MojoResult result) { + wait_id_ = 0; + + 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); +} + +} // namespace js +} // namespace mojo diff --git a/chromium/mojo/bindings/js/waiting_callback.h b/chromium/mojo/bindings/js/waiting_callback.h new file mode 100644 index 00000000000..973a500cdf0 --- /dev/null +++ b/chromium/mojo/bindings/js/waiting_callback.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_BINDINGS_JS_WAITING_CALLBACK_H_ +#define MOJO_BINDINGS_JS_WAITING_CALLBACK_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" + +namespace mojo { +namespace js { + +class WaitingCallback : public gin::Wrappable<WaitingCallback> { + public: + static gin::WrapperInfo kWrapperInfo; + + // Creates a new WaitingCallback. + static gin::Handle<WaitingCallback> Create( + v8::Isolate* isolate, + v8::Handle<v8::Function> callback, + mojo::Handle handle, + 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); + virtual ~WaitingCallback(); + + // 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); + + base::WeakPtr<gin::Runner> runner_; + MojoAsyncWaitID wait_id_; + + DISALLOW_COPY_AND_ASSIGN(WaitingCallback); +}; + +} // namespace js +} // namespace mojo + +#endif // MOJO_BINDINGS_JS_WAITING_CALLBACK_H_ diff --git a/chromium/mojo/cc/DEPS b/chromium/mojo/cc/DEPS new file mode 100644 index 00000000000..1d0b8872407 --- /dev/null +++ b/chromium/mojo/cc/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+cc", +] diff --git a/chromium/mojo/cc/context_provider_mojo.cc b/chromium/mojo/cc/context_provider_mojo.cc new file mode 100644 index 00000000000..7356b587c40 --- /dev/null +++ b/chromium/mojo/cc/context_provider_mojo.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 "mojo/cc/context_provider_mojo.h" + +#include "base/logging.h" + +namespace mojo { + +ContextProviderMojo::ContextProviderMojo( + ScopedMessagePipeHandle command_buffer_handle) + : command_buffer_handle_(command_buffer_handle.Pass()) {} + +bool ContextProviderMojo::BindToCurrentThread() { + DCHECK(command_buffer_handle_.is_valid()); + context_ = MojoGLES2CreateContext( + command_buffer_handle_.release().value(), + &ContextLostThunk, + NULL, + this); + return !!context_; +} + +gpu::gles2::GLES2Interface* ContextProviderMojo::ContextGL() { + if (!context_) + return NULL; + return static_cast<gpu::gles2::GLES2Interface*>( + MojoGLES2GetGLES2Interface(context_)); +} + +gpu::ContextSupport* ContextProviderMojo::ContextSupport() { + if (!context_) + return NULL; + return static_cast<gpu::ContextSupport*>( + MojoGLES2GetContextSupport(context_)); +} + +class GrContext* ContextProviderMojo::GrContext() { return NULL; } + +cc::ContextProvider::Capabilities ContextProviderMojo::ContextCapabilities() { + return capabilities_; +} + +bool ContextProviderMojo::IsContextLost() { return !context_; } +bool ContextProviderMojo::DestroyedOnMainThread() { return !context_; } + +ContextProviderMojo::~ContextProviderMojo() { + if (context_) + MojoGLES2DestroyContext(context_); +} + +void ContextProviderMojo::ContextLost() { + if (context_) { + MojoGLES2DestroyContext(context_); + context_ = NULL; + } +} + +} // namespace mojo diff --git a/chromium/mojo/cc/context_provider_mojo.h b/chromium/mojo/cc/context_provider_mojo.h new file mode 100644 index 00000000000..eda6e43b2e6 --- /dev/null +++ b/chromium/mojo/cc/context_provider_mojo.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_CC_CONTEXT_PROVIDER_MOJO_H_ +#define MOJO_CC_CONTEXT_PROVIDER_MOJO_H_ + +#include "cc/output/context_provider.h" +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class ContextProviderMojo : public cc::ContextProvider { + public: + explicit ContextProviderMojo(ScopedMessagePipeHandle command_buffer_handle); + + // cc::ContextProvider implementation. + virtual bool BindToCurrentThread() OVERRIDE; + virtual gpu::gles2::GLES2Interface* ContextGL() OVERRIDE; + virtual gpu::ContextSupport* ContextSupport() OVERRIDE; + virtual class GrContext* GrContext() OVERRIDE; + virtual Capabilities ContextCapabilities() OVERRIDE; + virtual bool IsContextLost() OVERRIDE; + virtual void VerifyContexts() OVERRIDE {} + virtual void DeleteCachedResources() OVERRIDE {} + virtual bool DestroyedOnMainThread() OVERRIDE; + virtual void SetLostContextCallback( + const LostContextCallback& lost_context_callback) OVERRIDE {} + virtual void SetMemoryPolicyChangedCallback( + const MemoryPolicyChangedCallback& memory_policy_changed_callback) + OVERRIDE {} + + protected: + friend class base::RefCountedThreadSafe<ContextProviderMojo>; + virtual ~ContextProviderMojo(); + + private: + static void ContextLostThunk(void* closure) { + static_cast<ContextProviderMojo*>(closure)->ContextLost(); + } + void ContextLost(); + + cc::ContextProvider::Capabilities capabilities_; + ScopedMessagePipeHandle command_buffer_handle_; + MojoGLES2Context context_; +}; + +} // namespace mojo + +#endif // MOJO_CC_CONTEXT_PROVIDER_MOJO_H_ diff --git a/chromium/mojo/common/BUILD.gn b/chromium/mojo/common/BUILD.gn new file mode 100644 index 00000000000..79610b9d190 --- /dev/null +++ b/chromium/mojo/common/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. + +# GYP version: mojo/mojo.gyp:mojo_common_lib +component("common") { + output_name = "mojo_common_lib" + + sources = [ + "common_type_converters.cc", + "common_type_converters.h", + "data_pipe_utils.cc", + "data_pipe_utils.h", + "handle_watcher.cc", + "handle_watcher.h", + "message_pump_mojo.cc", + "message_pump_mojo.h", + "message_pump_mojo_handler.h", + "time_helper.cc", + "time_helper.h", + ] + + defines = [ "MOJO_COMMON_IMPLEMENTATION" ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + "//mojo/system", + ] +} diff --git a/chromium/mojo/common/DEPS b/chromium/mojo/common/DEPS new file mode 100644 index 00000000000..e8ac42887d1 --- /dev/null +++ b/chromium/mojo/common/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + # common must not depend on embedder. + "-mojo", + "+mojo/common", + "+mojo/public", +] diff --git a/chromium/mojo/common/common_type_converters.cc b/chromium/mojo/common/common_type_converters.cc new file mode 100644 index 00000000000..a82aa51cda1 --- /dev/null +++ b/chromium/mojo/common/common_type_converters.cc @@ -0,0 +1,41 @@ +// 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 "mojo/common/common_type_converters.h" + +#include <string> + +#include "base/strings/utf_string_conversions.h" + +namespace mojo { + +// static +String TypeConverter<String, base::StringPiece>::ConvertFrom( + const base::StringPiece& input) { + if (input.empty()) { + char c = 0; + return String(&c, 0); + } + return String(input.data(), input.size()); +} +// static +base::StringPiece TypeConverter<String, base::StringPiece>::ConvertTo( + const String& input) { + return input.get(); +} + +// static +String TypeConverter<String, base::string16>::ConvertFrom( + const base::string16& input) { + return TypeConverter<String, base::StringPiece>::ConvertFrom( + base::UTF16ToUTF8(input)); +} +// static +base::string16 TypeConverter<String, base::string16>::ConvertTo( + const String& input) { + return base::UTF8ToUTF16(TypeConverter<String, base::StringPiece>::ConvertTo( + input)); +} + +} // namespace mojo diff --git a/chromium/mojo/common/common_type_converters.h b/chromium/mojo/common/common_type_converters.h new file mode 100644 index 00000000000..a75e734a9f1 --- /dev/null +++ b/chromium/mojo/common/common_type_converters.h @@ -0,0 +1,32 @@ +// 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 MOJO_COMMON_COMMON_TYPE_CONVERTERS_H_ +#define MOJO_COMMON_COMMON_TYPE_CONVERTERS_H_ + +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +namespace mojo { + +template <> +class MOJO_COMMON_EXPORT TypeConverter<String, base::StringPiece> { + public: + static String ConvertFrom(const base::StringPiece& input); + static base::StringPiece ConvertTo(const String& input); +}; + +template <> +class MOJO_COMMON_EXPORT TypeConverter<String, base::string16> { + public: + static String ConvertFrom(const base::string16& input); + static base::string16 ConvertTo(const String& input); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_COMMON_TYPE_CONVERTERS_H_ diff --git a/chromium/mojo/common/common_type_converters_unittest.cc b/chromium/mojo/common/common_type_converters_unittest.cc new file mode 100644 index 00000000000..47771393260 --- /dev/null +++ b/chromium/mojo/common/common_type_converters_unittest.cc @@ -0,0 +1,72 @@ +// 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 "mojo/common/common_type_converters.h" + +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace test { +namespace { + +void ExpectEqualsStringPiece(const std::string& expected, + const base::StringPiece& str) { + EXPECT_EQ(expected, str.as_string()); +} + +void ExpectEqualsMojoString(const std::string& expected, + const String& str) { + EXPECT_EQ(expected, str.get()); +} + +void ExpectEqualsString16(const base::string16& expected, + const base::string16& actual) { + EXPECT_EQ(expected, actual); +} + +void ExpectEqualsMojoString(const base::string16& expected, + const String& str) { + EXPECT_EQ(expected, str.To<base::string16>()); +} + +} // namespace + +TEST(CommonTypeConvertersTest, StringPiece) { + std::string kText("hello world"); + + base::StringPiece string_piece(kText); + String mojo_string(String::From(string_piece)); + + ExpectEqualsMojoString(kText, mojo_string); + ExpectEqualsStringPiece(kText, mojo_string.To<base::StringPiece>()); + + // Test implicit construction and conversion: + ExpectEqualsMojoString(kText, String::From(string_piece)); + ExpectEqualsStringPiece(kText, mojo_string.To<base::StringPiece>()); + + // Test null String: + base::StringPiece empty_string_piece = String().To<base::StringPiece>(); + EXPECT_TRUE(empty_string_piece.empty()); +} + +TEST(CommonTypeConvertersTest, String16) { + const base::string16 string16(base::ASCIIToUTF16("hello world")); + const String mojo_string(String::From(string16)); + + ExpectEqualsMojoString(string16, mojo_string); + EXPECT_EQ(string16, mojo_string.To<base::string16>()); + + // Test implicit construction and conversion: + ExpectEqualsMojoString(string16, String::From(string16)); + ExpectEqualsString16(string16, mojo_string.To<base::string16>()); + + // Test empty string conversion. + ExpectEqualsMojoString(base::string16(), String::From(base::string16())); +} + +} // namespace test +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/data_pipe_utils.cc b/chromium/mojo/common/data_pipe_utils.cc new file mode 100644 index 00000000000..cf9dc78c30d --- /dev/null +++ b/chromium/mojo/common/data_pipe_utils.cc @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/common/data_pipe_utils.h" + +#include <stdio.h> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/message_loop/message_loop.h" +#include "base/task_runner_util.h" +#include "mojo/common/handle_watcher.h" + +namespace mojo { +namespace common { + +bool BlockingCopyToFile(ScopedDataPipeConsumerHandle source, + const base::FilePath& destination) { + base::ScopedFILE fp(base::OpenFile(destination, "wb")); + if (!fp) + return false; + + for (;;) { + const void* buffer; + uint32_t num_bytes; + MojoResult result = BeginReadDataRaw(source.get(), &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + size_t bytes_written = fwrite(buffer, 1, num_bytes, fp.get()); + result = EndReadDataRaw(source.get(), num_bytes); + if (bytes_written < num_bytes || result != MOJO_RESULT_OK) + return false; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(source.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + if (result != MOJO_RESULT_OK) { + // If the producer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else if (result == MOJO_RESULT_FAILED_PRECONDITION) { + // If the producer handle was closed, then treat as EOF. + return true; + } else { + // Some other error occurred. + break; + } + } + + return false; +} + +void CompleteBlockingCopyToFile(const base::Callback<void(bool)>& callback, + bool result) { + callback.Run(result); +} + +void CopyToFile(ScopedDataPipeConsumerHandle source, + const base::FilePath& destination, + base::TaskRunner* task_runner, + const base::Callback<void(bool)>& callback) { + base::PostTaskAndReplyWithResult( + task_runner, + FROM_HERE, + base::Bind(&BlockingCopyToFile, base::Passed(&source), destination), + callback); +} + +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/data_pipe_utils.h b/chromium/mojo/common/data_pipe_utils.h new file mode 100644 index 00000000000..dc2f66a0059 --- /dev/null +++ b/chromium/mojo/common/data_pipe_utils.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_DATA_PIPE_UTILS_H_ +#define MOJO_SHELL_DATA_PIPE_UTILS_H_ + +#include "base/callback_forward.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/core.h" + +namespace base { +class FilePath; +class TaskRunner; +} + +namespace mojo { +namespace common { + +// Asynchronously copies data from source to the destination file. The given +// |callback| is run upon completion. File writes will be scheduled to the +// given |task_runner|. +void MOJO_COMMON_EXPORT CopyToFile( + ScopedDataPipeConsumerHandle source, + const base::FilePath& destination, + base::TaskRunner* task_runner, + const base::Callback<void(bool /*success*/)>& callback); + +} // namespace common +} // namespace mojo + +#endif // MOJO_SHELL_DATA_PIPE_UTILS_H_ diff --git a/chromium/mojo/common/handle_watcher.cc b/chromium/mojo/common/handle_watcher.cc new file mode 100644 index 00000000000..84e8b64fb9a --- /dev/null +++ b/chromium/mojo/common/handle_watcher.cc @@ -0,0 +1,312 @@ +// 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 "mojo/common/handle_watcher.h" + +#include <map> + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/memory/singleton.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/common/message_pump_mojo_handler.h" +#include "mojo/common/time_helper.h" + +namespace mojo { +namespace common { + +typedef int WatcherID; + +namespace { + +const char kWatcherThreadName[] = "handle-watcher-thread"; + +// TODO(sky): this should be unnecessary once MessageLoop has been refactored. +MessagePumpMojo* message_pump_mojo = NULL; + +scoped_ptr<base::MessagePump> CreateMessagePumpMojo() { + message_pump_mojo = new MessagePumpMojo; + return scoped_ptr<base::MessagePump>(message_pump_mojo).Pass(); +} + +base::TimeTicks MojoDeadlineToTimeTicks(MojoDeadline deadline) { + return deadline == MOJO_DEADLINE_INDEFINITE ? base::TimeTicks() : + internal::NowTicks() + base::TimeDelta::FromMicroseconds(deadline); +} + +// Tracks the data for a single call to Start(). +struct WatchData { + WatchData() + : id(0), + handle_signals(MOJO_HANDLE_SIGNAL_NONE), + message_loop(NULL) {} + + WatcherID id; + Handle handle; + MojoHandleSignals handle_signals; + base::TimeTicks deadline; + base::Callback<void(MojoResult)> callback; + scoped_refptr<base::MessageLoopProxy> message_loop; +}; + +// WatcherBackend -------------------------------------------------------------- + +// WatcherBackend is responsible for managing the requests and interacting with +// MessagePumpMojo. All access (outside of creation/destruction) is done on the +// thread WatcherThreadManager creates. +class WatcherBackend : public MessagePumpMojoHandler { + public: + WatcherBackend(); + virtual ~WatcherBackend(); + + void StartWatching(const WatchData& data); + void StopWatching(WatcherID watcher_id); + + private: + typedef std::map<Handle, WatchData> HandleToWatchDataMap; + + // Invoked when a handle needs to be removed and notified. + void RemoveAndNotify(const Handle& handle, MojoResult result); + + // Searches through |handle_to_data_| for |watcher_id|. Returns true if found + // and sets |handle| to the Handle. Returns false if not a known id. + bool GetMojoHandleByWatcherID(WatcherID watcher_id, Handle* handle) const; + + // MessagePumpMojoHandler overrides: + virtual void OnHandleReady(const Handle& handle) OVERRIDE; + virtual void OnHandleError(const Handle& handle, MojoResult result) OVERRIDE; + + // Maps from assigned id to WatchData. + HandleToWatchDataMap handle_to_data_; + + DISALLOW_COPY_AND_ASSIGN(WatcherBackend); +}; + +WatcherBackend::WatcherBackend() { +} + +WatcherBackend::~WatcherBackend() { +} + +void WatcherBackend::StartWatching(const WatchData& data) { + RemoveAndNotify(data.handle, MOJO_RESULT_CANCELLED); + + DCHECK_EQ(0u, handle_to_data_.count(data.handle)); + + handle_to_data_[data.handle] = data; + message_pump_mojo->AddHandler(this, data.handle, + data.handle_signals, + data.deadline); +} + +void WatcherBackend::StopWatching(WatcherID watcher_id) { + // Because of the thread hop it is entirely possible to get here and not + // have a valid handle registered for |watcher_id|. + Handle handle; + if (!GetMojoHandleByWatcherID(watcher_id, &handle)) + return; + + handle_to_data_.erase(handle); + message_pump_mojo->RemoveHandler(handle); +} + +void WatcherBackend::RemoveAndNotify(const Handle& handle, + MojoResult result) { + if (handle_to_data_.count(handle) == 0) + return; + + const WatchData data(handle_to_data_[handle]); + handle_to_data_.erase(handle); + message_pump_mojo->RemoveHandler(handle); + data.message_loop->PostTask(FROM_HERE, base::Bind(data.callback, result)); +} + +bool WatcherBackend::GetMojoHandleByWatcherID(WatcherID watcher_id, + Handle* handle) const { + for (HandleToWatchDataMap::const_iterator i = handle_to_data_.begin(); + i != handle_to_data_.end(); ++i) { + if (i->second.id == watcher_id) { + *handle = i->second.handle; + return true; + } + } + return false; +} + +void WatcherBackend::OnHandleReady(const Handle& handle) { + RemoveAndNotify(handle, MOJO_RESULT_OK); +} + +void WatcherBackend::OnHandleError(const Handle& handle, MojoResult result) { + RemoveAndNotify(handle, result); +} + +// WatcherThreadManager -------------------------------------------------------- + +// WatcherThreadManager manages the background thread that listens for handles +// to be ready. All requests are handled by WatcherBackend. +class WatcherThreadManager { + public: + ~WatcherThreadManager(); + + // Returns the shared instance. + static WatcherThreadManager* GetInstance(); + + // Starts watching the requested handle. Returns a unique ID that is used to + // stop watching the handle. When the handle is ready |callback| is notified + // on the thread StartWatching() was invoked on. + // This may be invoked on any thread. + WatcherID StartWatching(const Handle& handle, + MojoHandleSignals handle_signals, + base::TimeTicks deadline, + const base::Callback<void(MojoResult)>& callback); + + // Stops watching a handle. + // This may be invoked on any thread. + void StopWatching(WatcherID watcher_id); + + private: + friend struct DefaultSingletonTraits<WatcherThreadManager>; + WatcherThreadManager(); + + base::Thread thread_; + + base::AtomicSequenceNumber watcher_id_generator_; + + WatcherBackend backend_; + + DISALLOW_COPY_AND_ASSIGN(WatcherThreadManager); +}; + +WatcherThreadManager::~WatcherThreadManager() { + thread_.Stop(); +} + +WatcherThreadManager* WatcherThreadManager::GetInstance() { + return Singleton<WatcherThreadManager>::get(); +} + +WatcherID WatcherThreadManager::StartWatching( + const Handle& handle, + MojoHandleSignals handle_signals, + base::TimeTicks deadline, + const base::Callback<void(MojoResult)>& callback) { + WatchData data; + data.id = watcher_id_generator_.GetNext(); + data.handle = handle; + data.callback = callback; + data.handle_signals = handle_signals; + data.deadline = deadline; + data.message_loop = base::MessageLoopProxy::current(); + DCHECK_NE(static_cast<base::MessageLoopProxy*>(NULL), + data.message_loop.get()); + // We outlive |thread_|, so it's safe to use Unretained() here. + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&WatcherBackend::StartWatching, + base::Unretained(&backend_), + data)); + return data.id; +} + +void WatcherThreadManager::StopWatching(WatcherID watcher_id) { + // We outlive |thread_|, so it's safe to use Unretained() here. + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&WatcherBackend::StopWatching, + base::Unretained(&backend_), + watcher_id)); +} + +WatcherThreadManager::WatcherThreadManager() + : thread_(kWatcherThreadName) { + base::Thread::Options thread_options; + thread_options.message_pump_factory = base::Bind(&CreateMessagePumpMojo); + thread_.StartWithOptions(thread_options); +} + +} // namespace + +// HandleWatcher::State -------------------------------------------------------- + +// Represents the state of the HandleWatcher. Owns the user's callback and +// monitors the current thread's MessageLoop to know when to force the callback +// to run (with an error) even though the pipe hasn't been signaled yet. +class HandleWatcher::State : public base::MessageLoop::DestructionObserver { + public: + State(HandleWatcher* watcher, + const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline, + const base::Callback<void(MojoResult)>& callback) + : watcher_(watcher), + callback_(callback), + weak_factory_(this) { + base::MessageLoop::current()->AddDestructionObserver(this); + + watcher_id_ = WatcherThreadManager::GetInstance()->StartWatching( + handle, + handle_signals, + MojoDeadlineToTimeTicks(deadline), + base::Bind(&State::OnHandleReady, weak_factory_.GetWeakPtr())); + } + + virtual ~State() { + base::MessageLoop::current()->RemoveDestructionObserver(this); + + WatcherThreadManager::GetInstance()->StopWatching(watcher_id_); + } + + private: + virtual void WillDestroyCurrentMessageLoop() OVERRIDE { + // The current thread is exiting. Simulate a watch error. + OnHandleReady(MOJO_RESULT_ABORTED); + } + + void OnHandleReady(MojoResult result) { + base::Callback<void(MojoResult)> callback = callback_; + watcher_->Stop(); // Destroys |this|. + + callback.Run(result); + } + + HandleWatcher* watcher_; + WatcherID watcher_id_; + base::Callback<void(MojoResult)> callback_; + + // Used to weakly bind |this| to the WatcherThreadManager. + base::WeakPtrFactory<State> weak_factory_; +}; + +// HandleWatcher --------------------------------------------------------------- + +HandleWatcher::HandleWatcher() { +} + +HandleWatcher::~HandleWatcher() { +} + +void HandleWatcher::Start(const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline, + const base::Callback<void(MojoResult)>& callback) { + DCHECK(handle.is_valid()); + DCHECK_NE(MOJO_HANDLE_SIGNAL_NONE, handle_signals); + + state_.reset(new State(this, handle, handle_signals, deadline, callback)); +} + +void HandleWatcher::Stop() { + state_.reset(); +} + +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/handle_watcher.h b/chromium/mojo/common/handle_watcher.h new file mode 100644 index 00000000000..9fac3f51edc --- /dev/null +++ b/chromium/mojo/common/handle_watcher.h @@ -0,0 +1,59 @@ +// 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 MOJO_COMMON_HANDLE_WATCHER_H_ +#define MOJO_COMMON_HANDLE_WATCHER_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/core.h" + +namespace base { +class Thread; +} + +namespace mojo { +namespace common { +namespace test { +class HandleWatcherTest; +} + +// HandleWatcher is used to asynchronously wait on a handle and notify a Closure +// when the handle is ready, or the deadline has expired. +class MOJO_COMMON_EXPORT HandleWatcher { + public: + HandleWatcher(); + ~HandleWatcher(); + + // Starts listening for |handle|. This implicitly invokes Stop(). In other + // words, Start() performs one asynchronous watch at a time. It is ok to call + // Start() multiple times, but it cancels any existing watches. |callback| is + // notified when the handle is ready, invalid or deadline has passed and is + // notified on the thread Start() was invoked on. If the current thread exits + // before the handle is ready, then |callback| is invoked with a result of + // MOJO_RESULT_ABORTED. + void Start(const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline, + const base::Callback<void(MojoResult)>& callback); + + // Stops listening. Does nothing if not in the process of listening. + void Stop(); + + private: + class State; + + // If non-NULL Start() has been invoked. + scoped_ptr<State> state_; + + DISALLOW_COPY_AND_ASSIGN(HandleWatcher); +}; + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_HANDLE_WATCHER_H_ diff --git a/chromium/mojo/common/handle_watcher_unittest.cc b/chromium/mojo/common/handle_watcher_unittest.cc new file mode 100644 index 00000000000..02cc6105d6a --- /dev/null +++ b/chromium/mojo/common/handle_watcher_unittest.cc @@ -0,0 +1,341 @@ +// 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 "mojo/common/handle_watcher.h" + +#include <string> + +#include "base/at_exit.h" +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "mojo/common/time_helper.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace test { + +void ObserveCallback(bool* was_signaled, + MojoResult* result_observed, + MojoResult result) { + *was_signaled = true; + *result_observed = result; +} + +void RunUntilIdle() { + base::RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +void DeleteWatcherAndForwardResult( + HandleWatcher* watcher, + base::Callback<void(MojoResult)> next_callback, + MojoResult result) { + delete watcher; + next_callback.Run(result); +} + +// Helper class to manage the callback and running the message loop waiting for +// message to be received. Typical usage is something like: +// Schedule callback returned from GetCallback(). +// RunUntilGotCallback(); +// EXPECT_TRUE(got_callback()); +// clear_callback(); +class CallbackHelper { + public: + CallbackHelper() + : got_callback_(false), + run_loop_(NULL), + weak_factory_(this) {} + ~CallbackHelper() {} + + // See description above |got_callback_|. + bool got_callback() const { return got_callback_; } + void clear_callback() { got_callback_ = false; } + + // Runs the current MessageLoop until the callback returned from GetCallback() + // is notified. + void RunUntilGotCallback() { + ASSERT_TRUE(run_loop_ == NULL); + base::RunLoop run_loop; + base::AutoReset<base::RunLoop*> reseter(&run_loop_, &run_loop); + run_loop.Run(); + } + + base::Callback<void(MojoResult)> GetCallback() { + return base::Bind(&CallbackHelper::OnCallback, weak_factory_.GetWeakPtr()); + } + + void Start(HandleWatcher* watcher, const MessagePipeHandle& handle) { + StartWithCallback(watcher, handle, GetCallback()); + } + + void StartWithCallback(HandleWatcher* watcher, + const MessagePipeHandle& handle, + const base::Callback<void(MojoResult)>& callback) { + watcher->Start(handle, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, callback); + } + + private: + void OnCallback(MojoResult result) { + got_callback_ = true; + if (run_loop_) + run_loop_->Quit(); + } + + // Set to true when the callback is called. + bool got_callback_; + + // If non-NULL we're in RunUntilGotCallback(). + base::RunLoop* run_loop_; + + base::WeakPtrFactory<CallbackHelper> weak_factory_; + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackHelper); +}; + +class HandleWatcherTest : public testing::Test { + public: + HandleWatcherTest() {} + virtual ~HandleWatcherTest() { + test::SetTickClockForTest(NULL); + } + + protected: + void InstallTickClock() { + test::SetTickClockForTest(&tick_clock_); + } + + base::SimpleTestTickClock tick_clock_; + + private: + base::ShadowingAtExitManager at_exit_; + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(HandleWatcherTest); +}; + +// Trivial test case with a single handle to watch. +TEST_F(HandleWatcherTest, SingleHandler) { + MessagePipe test_pipe; + ASSERT_TRUE(test_pipe.handle0.is_valid()); + CallbackHelper callback_helper; + HandleWatcher watcher; + callback_helper.Start(&watcher, test_pipe.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper.got_callback()); + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe.handle1.get(), + std::string())); + callback_helper.RunUntilGotCallback(); + EXPECT_TRUE(callback_helper.got_callback()); +} + +// Creates three handles and notfies them in reverse order ensuring each one is +// notified appropriately. +TEST_F(HandleWatcherTest, ThreeHandles) { + MessagePipe test_pipe1; + MessagePipe test_pipe2; + MessagePipe test_pipe3; + CallbackHelper callback_helper1; + CallbackHelper callback_helper2; + CallbackHelper callback_helper3; + ASSERT_TRUE(test_pipe1.handle0.is_valid()); + ASSERT_TRUE(test_pipe2.handle0.is_valid()); + ASSERT_TRUE(test_pipe3.handle0.is_valid()); + + HandleWatcher watcher1; + callback_helper1.Start(&watcher1, test_pipe1.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); + + HandleWatcher watcher2; + callback_helper2.Start(&watcher2, test_pipe2.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); + + HandleWatcher watcher3; + callback_helper3.Start(&watcher3, test_pipe3.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); + + // Write to 3 and make sure it's notified. + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe3.handle1.get(), + std::string())); + callback_helper3.RunUntilGotCallback(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_TRUE(callback_helper3.got_callback()); + callback_helper3.clear_callback(); + + // Write to 1 and 3. Only 1 should be notified since 3 was is no longer + // running. + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe1.handle1.get(), + std::string())); + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe3.handle1.get(), + std::string())); + callback_helper1.RunUntilGotCallback(); + EXPECT_TRUE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); + callback_helper1.clear_callback(); + + // Write to 1 and 2. Only 2 should be notified (since 1 was already notified). + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe1.handle1.get(), + std::string())); + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe2.handle1.get(), + std::string())); + callback_helper2.RunUntilGotCallback(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_TRUE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); +} + +// Verifies Start() invoked a second time works. +TEST_F(HandleWatcherTest, Restart) { + MessagePipe test_pipe1; + MessagePipe test_pipe2; + CallbackHelper callback_helper1; + CallbackHelper callback_helper2; + ASSERT_TRUE(test_pipe1.handle0.is_valid()); + ASSERT_TRUE(test_pipe2.handle0.is_valid()); + + HandleWatcher watcher1; + callback_helper1.Start(&watcher1, test_pipe1.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + + HandleWatcher watcher2; + callback_helper2.Start(&watcher2, test_pipe2.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + + // Write to 1 and make sure it's notified. + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe1.handle1.get(), + std::string())); + callback_helper1.RunUntilGotCallback(); + EXPECT_TRUE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + callback_helper1.clear_callback(); + EXPECT_TRUE(mojo::test::DiscardMessage(test_pipe1.handle0.get())); + + // Write to 2 and make sure it's notified. + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe2.handle1.get(), + std::string())); + callback_helper2.RunUntilGotCallback(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_TRUE(callback_helper2.got_callback()); + callback_helper2.clear_callback(); + + // Listen on 1 again. + callback_helper1.Start(&watcher1, test_pipe1.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + + // Write to 1 and make sure it's notified. + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe1.handle1.get(), + std::string())); + callback_helper1.RunUntilGotCallback(); + EXPECT_TRUE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); +} + +// Verifies deadline is honored. +TEST_F(HandleWatcherTest, Deadline) { + InstallTickClock(); + + MessagePipe test_pipe1; + MessagePipe test_pipe2; + MessagePipe test_pipe3; + CallbackHelper callback_helper1; + CallbackHelper callback_helper2; + CallbackHelper callback_helper3; + ASSERT_TRUE(test_pipe1.handle0.is_valid()); + ASSERT_TRUE(test_pipe2.handle0.is_valid()); + ASSERT_TRUE(test_pipe3.handle0.is_valid()); + + // Add a watcher with an infinite timeout. + HandleWatcher watcher1; + callback_helper1.Start(&watcher1, test_pipe1.handle0.get()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); + + // Add another watcher wth a timeout of 500 microseconds. + HandleWatcher watcher2; + watcher2.Start(test_pipe2.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE, 500, + callback_helper2.GetCallback()); + RunUntilIdle(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_FALSE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); + + // Advance the clock passed the deadline. We also have to start another + // watcher to wake up the background thread. + tick_clock_.Advance(base::TimeDelta::FromMicroseconds(501)); + + HandleWatcher watcher3; + callback_helper3.Start(&watcher3, test_pipe3.handle0.get()); + + callback_helper2.RunUntilGotCallback(); + EXPECT_FALSE(callback_helper1.got_callback()); + EXPECT_TRUE(callback_helper2.got_callback()); + EXPECT_FALSE(callback_helper3.got_callback()); +} + +TEST_F(HandleWatcherTest, DeleteInCallback) { + MessagePipe test_pipe; + CallbackHelper callback_helper; + + HandleWatcher* watcher = new HandleWatcher(); + callback_helper.StartWithCallback(watcher, test_pipe.handle1.get(), + base::Bind(&DeleteWatcherAndForwardResult, + watcher, + callback_helper.GetCallback())); + EXPECT_TRUE(mojo::test::WriteTextMessage(test_pipe.handle0.get(), + std::string())); + callback_helper.RunUntilGotCallback(); + EXPECT_TRUE(callback_helper.got_callback()); +} + +TEST(HandleWatcherCleanEnvironmentTest, AbortedOnMessageLoopDestruction) { + bool was_signaled = false; + MojoResult result = MOJO_RESULT_OK; + + base::ShadowingAtExitManager at_exit; + MessagePipe pipe; + HandleWatcher watcher; + { + base::MessageLoop loop; + + watcher.Start(pipe.handle0.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + base::Bind(&ObserveCallback, &was_signaled, &result)); + + // Now, let the MessageLoop get torn down. We expect our callback to run. + } + + EXPECT_TRUE(was_signaled); + EXPECT_EQ(MOJO_RESULT_ABORTED, result); +} + +} // namespace test +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/message_pump_mojo.cc b/chromium/mojo/common/message_pump_mojo.cc new file mode 100644 index 00000000000..37d636bc8f4 --- /dev/null +++ b/chromium/mojo/common/message_pump_mojo.cc @@ -0,0 +1,243 @@ +// 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 "mojo/common/message_pump_mojo.h" + +#include <algorithm> +#include <vector> + +#include "base/debug/alias.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "mojo/common/message_pump_mojo_handler.h" +#include "mojo/common/time_helper.h" + +namespace mojo { +namespace common { + +// State needed for one iteration of WaitMany. The first handle and flags +// corresponds to that of the control pipe. +struct MessagePumpMojo::WaitState { + std::vector<Handle> handles; + std::vector<MojoHandleSignals> wait_signals; +}; + +struct MessagePumpMojo::RunState { + RunState() : should_quit(false) { + CreateMessagePipe(NULL, &read_handle, &write_handle); + } + + base::TimeTicks delayed_work_time; + + // Used to wake up WaitForWork(). + ScopedMessagePipeHandle read_handle; + ScopedMessagePipeHandle write_handle; + + bool should_quit; +}; + +MessagePumpMojo::MessagePumpMojo() : run_state_(NULL), next_handler_id_(0) { +} + +MessagePumpMojo::~MessagePumpMojo() { +} + +// static +scoped_ptr<base::MessagePump> MessagePumpMojo::Create() { + return scoped_ptr<MessagePump>(new MessagePumpMojo()); +} + +void MessagePumpMojo::AddHandler(MessagePumpMojoHandler* handler, + const Handle& handle, + MojoHandleSignals wait_signals, + base::TimeTicks deadline) { + DCHECK(handler); + DCHECK(handle.is_valid()); + // Assume it's an error if someone tries to reregister an existing handle. + DCHECK_EQ(0u, handlers_.count(handle)); + Handler handler_data; + handler_data.handler = handler; + handler_data.wait_signals = wait_signals; + handler_data.deadline = deadline; + handler_data.id = next_handler_id_++; + handlers_[handle] = handler_data; +} + +void MessagePumpMojo::RemoveHandler(const Handle& handle) { + handlers_.erase(handle); +} + +void MessagePumpMojo::Run(Delegate* delegate) { + RunState run_state; + // TODO: better deal with error handling. + CHECK(run_state.read_handle.is_valid()); + CHECK(run_state.write_handle.is_valid()); + RunState* old_state = NULL; + { + base::AutoLock auto_lock(run_state_lock_); + old_state = run_state_; + run_state_ = &run_state; + } + DoRunLoop(&run_state, delegate); + { + base::AutoLock auto_lock(run_state_lock_); + run_state_ = old_state; + } +} + +void MessagePumpMojo::Quit() { + base::AutoLock auto_lock(run_state_lock_); + if (run_state_) + run_state_->should_quit = true; +} + +void MessagePumpMojo::ScheduleWork() { + base::AutoLock auto_lock(run_state_lock_); + if (run_state_) + SignalControlPipe(*run_state_); +} + +void MessagePumpMojo::ScheduleDelayedWork( + const base::TimeTicks& delayed_work_time) { + base::AutoLock auto_lock(run_state_lock_); + if (!run_state_) + return; + run_state_->delayed_work_time = delayed_work_time; + SignalControlPipe(*run_state_); +} + +void MessagePumpMojo::DoRunLoop(RunState* run_state, Delegate* delegate) { + bool more_work_is_plausible = true; + for (;;) { + const bool block = !more_work_is_plausible; + DoInternalWork(*run_state, block); + + // There isn't a good way to know if there are more handles ready, we assume + // not. + more_work_is_plausible = false; + + if (run_state->should_quit) + break; + + more_work_is_plausible |= delegate->DoWork(); + if (run_state->should_quit) + break; + + more_work_is_plausible |= delegate->DoDelayedWork( + &run_state->delayed_work_time); + if (run_state->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = delegate->DoIdleWork(); + if (run_state->should_quit) + break; + } +} + +void MessagePumpMojo::DoInternalWork(const RunState& run_state, bool block) { + const MojoDeadline deadline = block ? GetDeadlineForWait(run_state) : 0; + const WaitState wait_state = GetWaitState(run_state); + const MojoResult result = + WaitMany(wait_state.handles, wait_state.wait_signals, deadline); + if (result == 0) { + // Control pipe was written to. + uint32_t num_bytes = 0; + ReadMessageRaw(run_state.read_handle.get(), NULL, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + } else if (result > 0) { + const size_t index = static_cast<size_t>(result); + DCHECK(handlers_.find(wait_state.handles[index]) != handlers_.end()); + handlers_[wait_state.handles[index]].handler->OnHandleReady( + wait_state.handles[index]); + } else { + switch (result) { + case MOJO_RESULT_CANCELLED: + case MOJO_RESULT_FAILED_PRECONDITION: + case MOJO_RESULT_INVALID_ARGUMENT: + RemoveFirstInvalidHandle(wait_state); + break; + case MOJO_RESULT_DEADLINE_EXCEEDED: + break; + default: + base::debug::Alias(&result); + // Unexpected result is likely fatal, crash so we can determine cause. + CHECK(false); + } + } + + // Notify and remove any handlers whose time has expired. Make a copy in case + // someone tries to add/remove new handlers from notification. + const HandleToHandler cloned_handlers(handlers_); + const base::TimeTicks now(internal::NowTicks()); + for (HandleToHandler::const_iterator i = cloned_handlers.begin(); + i != cloned_handlers.end(); ++i) { + // Since we're iterating over a clone of the handlers, verify the handler is + // still valid before notifying. + if (!i->second.deadline.is_null() && i->second.deadline < now && + handlers_.find(i->first) != handlers_.end() && + handlers_[i->first].id == i->second.id) { + i->second.handler->OnHandleError(i->first, MOJO_RESULT_DEADLINE_EXCEEDED); + } + } +} + +void MessagePumpMojo::RemoveFirstInvalidHandle(const WaitState& wait_state) { + // TODO(sky): deal with control pipe going bad. + for (size_t i = 1; i < wait_state.handles.size(); ++i) { + const MojoResult result = + Wait(wait_state.handles[i], wait_state.wait_signals[i], 0); + if (result == MOJO_RESULT_INVALID_ARGUMENT || + result == MOJO_RESULT_FAILED_PRECONDITION || + result == MOJO_RESULT_CANCELLED) { + // Remove the handle first, this way if OnHandleError() tries to remove + // the handle our iterator isn't invalidated. + DCHECK(handlers_.find(wait_state.handles[i]) != handlers_.end()); + MessagePumpMojoHandler* handler = + handlers_[wait_state.handles[i]].handler; + handlers_.erase(wait_state.handles[i]); + handler->OnHandleError(wait_state.handles[i], result); + return; + } + } +} + +void MessagePumpMojo::SignalControlPipe(const RunState& run_state) { + // TODO(sky): deal with error? + WriteMessageRaw(run_state.write_handle.get(), NULL, 0, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); +} + +MessagePumpMojo::WaitState MessagePumpMojo::GetWaitState( + const RunState& run_state) const { + WaitState wait_state; + wait_state.handles.push_back(run_state.read_handle.get()); + wait_state.wait_signals.push_back(MOJO_HANDLE_SIGNAL_READABLE); + + for (HandleToHandler::const_iterator i = handlers_.begin(); + i != handlers_.end(); ++i) { + wait_state.handles.push_back(i->first); + wait_state.wait_signals.push_back(i->second.wait_signals); + } + return wait_state; +} + +MojoDeadline MessagePumpMojo::GetDeadlineForWait( + const RunState& run_state) const { + base::TimeTicks min_time = run_state.delayed_work_time; + for (HandleToHandler::const_iterator i = handlers_.begin(); + i != handlers_.end(); ++i) { + if (min_time.is_null() && i->second.deadline < min_time) + min_time = i->second.deadline; + } + return min_time.is_null() ? MOJO_DEADLINE_INDEFINITE : + std::max(static_cast<MojoDeadline>(0), + static_cast<MojoDeadline>( + (min_time - internal::NowTicks()).InMicroseconds())); +} + +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/message_pump_mojo.h b/chromium/mojo/common/message_pump_mojo.h new file mode 100644 index 00000000000..751311850eb --- /dev/null +++ b/chromium/mojo/common/message_pump_mojo.h @@ -0,0 +1,107 @@ +// 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 MOJO_COMMON_MESSAGE_PUMP_MOJO_H_ +#define MOJO_COMMON_MESSAGE_PUMP_MOJO_H_ + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace common { + +class MessagePumpMojoHandler; + +// Mojo implementation of MessagePump. +class MOJO_COMMON_EXPORT MessagePumpMojo : public base::MessagePump { + public: + MessagePumpMojo(); + virtual ~MessagePumpMojo(); + + // Static factory function (for using with |base::Thread::Options|, wrapped + // using |base::Bind()|). + static scoped_ptr<base::MessagePump> Create(); + + // Registers a MessagePumpMojoHandler for the specified handle. Only one + // handler can be registered for a specified handle. + void AddHandler(MessagePumpMojoHandler* handler, + const Handle& handle, + MojoHandleSignals wait_signals, + base::TimeTicks deadline); + + void RemoveHandler(const Handle& handle); + + // MessagePump: + virtual void Run(Delegate* delegate) OVERRIDE; + virtual void Quit() OVERRIDE; + virtual void ScheduleWork() OVERRIDE; + virtual void ScheduleDelayedWork( + const base::TimeTicks& delayed_work_time) OVERRIDE; + + private: + struct RunState; + struct WaitState; + + // Contains the data needed to track a request to AddHandler(). + struct Handler { + Handler() : handler(NULL), wait_signals(MOJO_HANDLE_SIGNAL_NONE), id(0) {} + + MessagePumpMojoHandler* handler; + MojoHandleSignals wait_signals; + base::TimeTicks deadline; + // See description of |MessagePumpMojo::next_handler_id_| for details. + int id; + }; + + typedef std::map<Handle, Handler> HandleToHandler; + + // Implementation of Run(). + void DoRunLoop(RunState* run_state, Delegate* delegate); + + // Services the set of handles ready. If |block| is true this waits for a + // handle to become ready, otherwise this does not block. + void DoInternalWork(const RunState& run_state, bool block); + + // Removes the first invalid handle. This is called if MojoWaitMany finds an + // invalid handle. + void RemoveFirstInvalidHandle(const WaitState& wait_state); + + void SignalControlPipe(const RunState& run_state); + + WaitState GetWaitState(const RunState& run_state) const; + + // Returns the deadline for the call to MojoWaitMany(). + MojoDeadline GetDeadlineForWait(const RunState& run_state) const; + + // If non-NULL we're running (inside Run()). Member is reference to value on + // stack. + RunState* run_state_; + + // Lock for accessing |run_state_|. In general the only method that we have to + // worry about is ScheduleWork(). All other methods are invoked on the same + // thread. + base::Lock run_state_lock_; + + HandleToHandler handlers_; + + // An ever increasing value assigned to each Handler::id. Used to detect + // uniqueness while notifying. That is, while notifying expired timers we copy + // |handlers_| and only notify handlers whose id match. If the id does not + // match it means the handler was removed then added so that we shouldn't + // notify it. + int next_handler_id_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpMojo); +}; + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_MESSAGE_PUMP_MOJO_H_ diff --git a/chromium/mojo/common/message_pump_mojo_handler.h b/chromium/mojo/common/message_pump_mojo_handler.h new file mode 100644 index 00000000000..5809051e1d9 --- /dev/null +++ b/chromium/mojo/common/message_pump_mojo_handler.h @@ -0,0 +1,29 @@ +// 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 MOJO_COMMON_MESSAGE_PUMP_MOJO_HANDLER_H_ +#define MOJO_COMMON_MESSAGE_PUMP_MOJO_HANDLER_H_ + +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace common { + +// Used by MessagePumpMojo to notify when a handle is either ready or has become +// invalid. +class MOJO_COMMON_EXPORT MessagePumpMojoHandler { + public: + virtual void OnHandleReady(const Handle& handle) = 0; + + virtual void OnHandleError(const Handle& handle, MojoResult result) = 0; + + protected: + virtual ~MessagePumpMojoHandler() {} +}; + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_MESSAGE_PUMP_MOJO_HANDLER_H_ diff --git a/chromium/mojo/common/message_pump_mojo_unittest.cc b/chromium/mojo/common/message_pump_mojo_unittest.cc new file mode 100644 index 00000000000..fb5ae2470e1 --- /dev/null +++ b/chromium/mojo/common/message_pump_mojo_unittest.cc @@ -0,0 +1,22 @@ +// 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 "mojo/common/message_pump_mojo.h" + +#include "base/message_loop/message_loop_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace test { + +scoped_ptr<base::MessagePump> CreateMojoMessagePump() { + return scoped_ptr<base::MessagePump>(new MessagePumpMojo()); +} + +RUN_MESSAGE_LOOP_TESTS(Mojo, &CreateMojoMessagePump); + +} // namespace test +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/mojo_common_export.h b/chromium/mojo/common/mojo_common_export.h new file mode 100644 index 00000000000..48d21d0d3d2 --- /dev/null +++ b/chromium/mojo/common/mojo_common_export.h @@ -0,0 +1,32 @@ +// 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 MOJO_COMMON_MOJO_COMMON_EXPORT_H_ +#define MOJO_COMMON_MOJO_COMMON_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_COMMON_IMPLEMENTATION) +#define MOJO_COMMON_EXPORT __declspec(dllexport) +#else +#define MOJO_COMMON_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_COMMON_IMPLEMENTATION) +#define MOJO_COMMON_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_COMMON_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_COMMON_EXPORT +#endif + +#endif // MOJO_COMMON_MOJO_COMMON_EXPORT_H_ diff --git a/chromium/mojo/common/time_helper.cc b/chromium/mojo/common/time_helper.cc new file mode 100644 index 00000000000..36fd0879e34 --- /dev/null +++ b/chromium/mojo/common/time_helper.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/common/time_helper.h" + +#include "base/time/tick_clock.h" + +namespace mojo { +namespace common { + +namespace { + +base::TickClock* tick_clock = NULL; + +} // namespace + +namespace test { + +void SetTickClockForTest(base::TickClock* clock) { + tick_clock = clock; +} +} // namespace test + +namespace internal { + +base::TimeTicks NowTicks() { + return tick_clock ? tick_clock->NowTicks() : base::TimeTicks::Now(); +} + +} // namespace internal +} // namespace common +} // namespace mojo diff --git a/chromium/mojo/common/time_helper.h b/chromium/mojo/common/time_helper.h new file mode 100644 index 00000000000..365ae04993b --- /dev/null +++ b/chromium/mojo/common/time_helper.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_COMMON_TIME_HELPER_H_ +#define MOJO_COMMON_TIME_HELPER_H_ + +#include "base/time/time.h" +#include "mojo/common/mojo_common_export.h" + +namespace base { +class TickClock; +} + +namespace mojo { +namespace common { +namespace test { + +// Sets the TickClock used for getting TimeTicks::Now(). This is currently used +// by both HandleWatcher and MessagePumpMojo. +MOJO_COMMON_EXPORT void SetTickClockForTest(base::TickClock* clock); + +} // namespace test + +namespace internal { + +// Returns now. Used internally; generally not useful. +MOJO_COMMON_EXPORT base::TimeTicks NowTicks(); + +} // namespace internal +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_TIME_HELPER_H_ diff --git a/chromium/mojo/dbus/DEPS b/chromium/mojo/dbus/DEPS new file mode 100644 index 00000000000..0abf6831571 --- /dev/null +++ b/chromium/mojo/dbus/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+base", + "+dbus", +] diff --git a/chromium/mojo/dbus/dbus_external_service.cc b/chromium/mojo/dbus/dbus_external_service.cc new file mode 100644 index 00000000000..a0b8981f3f4 --- /dev/null +++ b/chromium/mojo/dbus/dbus_external_service.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 "mojo/dbus/dbus_external_service.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "dbus/bus.h" +#include "dbus/exported_object.h" +#include "dbus/file_descriptor.h" +#include "dbus/message.h" +#include "dbus/object_path.h" +#include "mojo/embedder/channel_init.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/shell/external_service.mojom.h" + +namespace mojo { + +DBusExternalServiceBase::DBusExternalServiceBase( + const std::string& service_name) + : service_name_(service_name), + exported_object_(NULL) { +} +DBusExternalServiceBase::~DBusExternalServiceBase() {} + +void DBusExternalServiceBase::Start() { + InitializeDBus(); + ExportMethods(); + TakeDBusServiceOwnership(); + DVLOG(1) << "External service started"; +} + +void DBusExternalServiceBase::ConnectChannel( + dbus::MethodCall* method_call, + dbus::ExportedObject::ResponseSender sender) { + dbus::MessageReader reader(method_call); + dbus::FileDescriptor wrapped_fd; + if (!reader.PopFileDescriptor(&wrapped_fd)) { + sender.Run( + dbus::ErrorResponse::FromMethodCall( + method_call, + "org.chromium.Mojo.BadHandle", + "Invalid FD.").PassAs<dbus::Response>()); + return; + } + wrapped_fd.CheckValidity(); + channel_init_.reset(new mojo::embedder::ChannelInit); + mojo::ScopedMessagePipeHandle message_pipe = + channel_init_->Init(wrapped_fd.TakeValue(), + base::MessageLoopProxy::current()); + CHECK(message_pipe.is_valid()); + + Connect(message_pipe.Pass()); + sender.Run(dbus::Response::FromMethodCall(method_call)); +} + +void DBusExternalServiceBase::ExportMethods() { + CHECK(exported_object_); + CHECK(exported_object_->ExportMethodAndBlock( + kMojoDBusInterface, kMojoDBusConnectMethod, + base::Bind(&DBusExternalServiceBase::ConnectChannel, + base::Unretained(this)))); +} + +void DBusExternalServiceBase::InitializeDBus() { + CHECK(!bus_); + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SESSION; + bus_ = new dbus::Bus(options); + CHECK(bus_->Connect()); + CHECK(bus_->SetUpAsyncOperations()); + + exported_object_ = + bus_->GetExportedObject(dbus::ObjectPath(kMojoDBusImplPath)); +} + +void DBusExternalServiceBase::TakeDBusServiceOwnership() { + CHECK(bus_->RequestOwnershipAndBlock( + service_name_, + dbus::Bus::REQUIRE_PRIMARY_ALLOW_REPLACEMENT)) + << "Unable to take ownership of " << service_name_; +} + +} // namespace mojo diff --git a/chromium/mojo/dbus/dbus_external_service.h b/chromium/mojo/dbus/dbus_external_service.h new file mode 100644 index 00000000000..213c074f993 --- /dev/null +++ b/chromium/mojo/dbus/dbus_external_service.h @@ -0,0 +1,88 @@ +// 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/memory/scoped_ptr.h" +#include "dbus/bus.h" +#include "dbus/exported_object.h" +#include "dbus/message.h" +#include "dbus/object_path.h" +#include "mojo/embedder/channel_init.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/shell/external_service.mojom.h" + +namespace mojo { +const char kMojoDBusImplPath[] = "/org/chromium/MojoImpl"; +const char kMojoDBusInterface[] = "org.chromium.Mojo"; +const char kMojoDBusConnectMethod[] = "ConnectChannel"; + +class DBusExternalServiceBase { + public: + explicit DBusExternalServiceBase(const std::string& service_name); + virtual ~DBusExternalServiceBase(); + + void Start(); + + protected: + // TODO(cmasone): Enable multiple peers to connect/disconnect + virtual void Connect(ScopedMessagePipeHandle client_handle) = 0; + virtual void Disconnect() = 0; + + private: + // Implementation of org.chromium.Mojo.ConnectChannel, exported over DBus. + // Takes a file descriptor and uses it to create a MessagePipe that is then + // hooked to an implementation of ExternalService. + void ConnectChannel(dbus::MethodCall* method_call, + dbus::ExportedObject::ResponseSender sender); + + void ExportMethods(); + void InitializeDBus(); + void TakeDBusServiceOwnership(); + + const std::string service_name_; + scoped_refptr<dbus::Bus> bus_; + dbus::ExportedObject* exported_object_; // Owned by bus_; + scoped_ptr<embedder::ChannelInit> channel_init_; + DISALLOW_COPY_AND_ASSIGN(DBusExternalServiceBase); +}; + +template <class ServiceImpl> +class DBusExternalService : public DBusExternalServiceBase { + public: + explicit DBusExternalService(const std::string& service_name) + : DBusExternalServiceBase(service_name) { + } + virtual ~DBusExternalService() {} + + protected: + virtual void Connect(ScopedMessagePipeHandle client_handle) OVERRIDE { + external_service_.reset(BindToPipe(new Impl(this), client_handle.Pass())); + } + + virtual void Disconnect() OVERRIDE { + external_service_.reset(); + } + + private: + class Impl : public InterfaceImpl<ExternalService> { + public: + explicit Impl(DBusExternalService* service) : service_(service) { + } + virtual void OnConnectionError() OVERRIDE { + service_->Disconnect(); + } + virtual void Activate(ScopedMessagePipeHandle service_provider_handle) + OVERRIDE { + app_.reset(new Application(service_provider_handle.Pass())); + app_->AddService<ServiceImpl>(); + } + private: + DBusExternalService* service_; + scoped_ptr<Application> app_; + }; + + scoped_ptr<Impl> external_service_; +}; + +} // namespace mojo diff --git a/chromium/mojo/embedder/DEPS b/chromium/mojo/embedder/DEPS new file mode 100644 index 00000000000..1c627b70d37 --- /dev/null +++ b/chromium/mojo/embedder/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+mojo/system/system_impl_export.h", +] + +specific_include_rules = { + # Implementation files may freely access mojo/system, but we don't want to + # leak implementation details through the headers. + ".*\.cc": [ + "+mojo/system", + ] +} diff --git a/chromium/mojo/embedder/README.md b/chromium/mojo/embedder/README.md new file mode 100644 index 00000000000..5510e99f65f --- /dev/null +++ b/chromium/mojo/embedder/README.md @@ -0,0 +1,13 @@ +Mojo Embedder API +================= + +The Mojo Embedder API is an unstable, internal API to the Mojo system +implementation. It should be used by code running on top of the system-level +APIs to set up the Mojo environment (instead of directly instantiating things +from src/mojo/system). + +Example uses: Mojo shell, to set up the Mojo environment for Mojo apps; Chromium +code, to set up the Mojo IPC system for use between processes. Note that most +code should use the Mojo Public API (under src/mojo/public) instead. The +Embedder API should only be used to initialize the environment, set up the +initial MessagePipe between two processes, etc. diff --git a/chromium/mojo/embedder/channel_init.cc b/chromium/mojo/embedder/channel_init.cc new file mode 100644 index 00000000000..136a5389817 --- /dev/null +++ b/chromium/mojo/embedder/channel_init.cc @@ -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. + +#include "mojo/embedder/channel_init.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "mojo/embedder/embedder.h" + +namespace mojo { +namespace embedder { + +ChannelInit::ChannelInit() + : channel_info_(NULL), + weak_factory_(this) { +} + +ChannelInit::~ChannelInit() { + if (channel_info_) { + io_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&mojo::embedder::DestroyChannelOnIOThread, channel_info_)); + } +} + +mojo::ScopedMessagePipeHandle ChannelInit::Init( + base::PlatformFile file, + scoped_refptr<base::TaskRunner> io_thread_task_runner) { + DCHECK(!io_thread_task_runner_.get()); // Should only init once. + io_thread_task_runner_ = io_thread_task_runner; + mojo::ScopedMessagePipeHandle message_pipe = mojo::embedder::CreateChannel( + mojo::embedder::ScopedPlatformHandle( + mojo::embedder::PlatformHandle(file)), + io_thread_task_runner, + base::Bind(&ChannelInit::OnCreatedChannel, weak_factory_.GetWeakPtr(), + io_thread_task_runner), + base::MessageLoop::current()->message_loop_proxy()).Pass(); + return message_pipe.Pass(); +} + +// static +void ChannelInit::OnCreatedChannel( + base::WeakPtr<ChannelInit> host, + scoped_refptr<base::TaskRunner> io_thread, + embedder::ChannelInfo* channel) { + // By the time we get here |host| may have been destroyed. If so, shutdown the + // channel. + if (!host.get()) { + io_thread->PostTask( + FROM_HERE, + base::Bind(&mojo::embedder::DestroyChannelOnIOThread, channel)); + return; + } + host->channel_info_ = channel; +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/channel_init.h b/chromium/mojo/embedder/channel_init.h new file mode 100644 index 00000000000..002bd868aec --- /dev/null +++ b/chromium/mojo/embedder/channel_init.h @@ -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. + +#ifndef MOJO_EMBEDDER_CHANNEL_INIT_H_ +#define MOJO_EMBEDDER_CHANNEL_INIT_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/system/system_impl_export.h" + +namespace base { +class MessageLoopProxy; +class TaskRunner; +} + +namespace mojo { +namespace embedder { +struct ChannelInfo; +} + +namespace embedder { + +// ChannelInit handle creation (and destruction) of the mojo channel. It is +// expected that this class is created and destroyed on the main thread. +class MOJO_SYSTEM_IMPL_EXPORT ChannelInit { + public: + ChannelInit(); + ~ChannelInit(); + + // Initializes the channel. This takes ownership of |file|. Returns the + // primordial MessagePipe for the channel. + mojo::ScopedMessagePipeHandle Init( + base::PlatformFile file, + scoped_refptr<base::TaskRunner> io_thread_task_runner); + + private: + // Invoked on the main thread once the channel has been established. + static void OnCreatedChannel( + base::WeakPtr<ChannelInit> host, + scoped_refptr<base::TaskRunner> io_thread, + embedder::ChannelInfo* channel); + + scoped_refptr<base::TaskRunner> io_thread_task_runner_; + + // If non-null the channel has been established. + embedder::ChannelInfo* channel_info_; + + base::WeakPtrFactory<ChannelInit> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ChannelInit); +}; + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_CHANNEL_INIT_H_ diff --git a/chromium/mojo/embedder/embedder.cc b/chromium/mojo/embedder/embedder.cc new file mode 100644 index 00000000000..d4fd3814392 --- /dev/null +++ b/chromium/mojo/embedder/embedder.cc @@ -0,0 +1,179 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/embedder.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/system/channel.h" +#include "mojo/system/core.h" +#include "mojo/system/entrypoints.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/message_pipe_dispatcher.h" +#include "mojo/system/platform_handle_dispatcher.h" +#include "mojo/system/raw_channel.h" + +namespace mojo { +namespace embedder { + +// This is defined here (instead of a header file), since it's opaque to the +// outside world. But we need to define it before our (internal-only) functions +// that use it. +struct ChannelInfo { + explicit ChannelInfo(scoped_refptr<system::Channel> channel) + : channel(channel) {} + ~ChannelInfo() {} + + scoped_refptr<system::Channel> channel; +}; + +namespace { + +// Helper for |CreateChannelOnIOThread()|. (Note: May return null for some +// failures.) +scoped_refptr<system::Channel> MakeChannel( + ScopedPlatformHandle platform_handle, + scoped_refptr<system::MessagePipe> message_pipe) { + DCHECK(platform_handle.is_valid()); + + // Create and initialize a |system::Channel|. + scoped_refptr<system::Channel> channel = new system::Channel(); + if (!channel->Init(system::RawChannel::Create(platform_handle.Pass()))) { + // This is very unusual (e.g., maybe |platform_handle| was invalid or we + // reached some system resource limit). + LOG(ERROR) << "Channel::Init() failed"; + // Return null, since |Shutdown()| shouldn't be called in this case. + return scoped_refptr<system::Channel>(); + } + // Once |Init()| has succeeded, we have to return |channel| (since + // |Shutdown()| will have to be called on it). + + // Attach the message pipe endpoint. + system::MessageInTransit::EndpointId endpoint_id = + channel->AttachMessagePipeEndpoint(message_pipe, 1); + if (endpoint_id == system::MessageInTransit::kInvalidEndpointId) { + // This means that, e.g., the other endpoint of the message pipe was closed + // first. But it's not necessarily an error per se. + DVLOG(2) << "Channel::AttachMessagePipeEndpoint() failed"; + return channel; + } + CHECK_EQ(endpoint_id, system::Channel::kBootstrapEndpointId); + + if (!channel->RunMessagePipeEndpoint(system::Channel::kBootstrapEndpointId, + system::Channel::kBootstrapEndpointId)) { + // Currently, there's no reason for this to fail. + NOTREACHED() << "Channel::RunMessagePipeEndpoint() failed"; + return channel; + } + + return channel; +} + +void CreateChannelOnIOThread( + ScopedPlatformHandle platform_handle, + scoped_refptr<system::MessagePipe> message_pipe, + DidCreateChannelCallback callback, + scoped_refptr<base::TaskRunner> callback_thread_task_runner) { + scoped_ptr<ChannelInfo> channel_info( + new ChannelInfo(MakeChannel(platform_handle.Pass(), message_pipe))); + + // Hand the channel back to the embedder. + if (callback_thread_task_runner) { + callback_thread_task_runner->PostTask(FROM_HERE, + base::Bind(callback, + channel_info.release())); + } else { + callback.Run(channel_info.release()); + } +} + +} // namespace + +void Init() { + system::entrypoints::SetCore(new system::Core()); +} + +ScopedMessagePipeHandle CreateChannel( + ScopedPlatformHandle platform_handle, + scoped_refptr<base::TaskRunner> io_thread_task_runner, + DidCreateChannelCallback callback, + scoped_refptr<base::TaskRunner> callback_thread_task_runner) { + DCHECK(platform_handle.is_valid()); + + std::pair<scoped_refptr<system::MessagePipeDispatcher>, + scoped_refptr<system::MessagePipe> > remote_message_pipe = + system::MessagePipeDispatcher::CreateRemoteMessagePipe(); + + system::Core* core = system::entrypoints::GetCore(); + DCHECK(core); + ScopedMessagePipeHandle rv( + MessagePipeHandle(core->AddDispatcher(remote_message_pipe.first))); + // TODO(vtl): Do we properly handle the failure case here? + if (rv.is_valid()) { + io_thread_task_runner->PostTask(FROM_HERE, + base::Bind(&CreateChannelOnIOThread, + base::Passed(&platform_handle), + remote_message_pipe.second, + callback, + callback_thread_task_runner)); + } + return rv.Pass(); +} + +void DestroyChannelOnIOThread(ChannelInfo* channel_info) { + DCHECK(channel_info); + if (!channel_info->channel) { + // Presumably, |Init()| on the channel failed. + return; + } + + channel_info->channel->Shutdown(); + delete channel_info; +} + +MojoResult CreatePlatformHandleWrapper( + ScopedPlatformHandle platform_handle, + MojoHandle* platform_handle_wrapper_handle) { + DCHECK(platform_handle_wrapper_handle); + + scoped_refptr<system::Dispatcher> dispatcher( + new system::PlatformHandleDispatcher(platform_handle.Pass())); + + system::Core* core = system::entrypoints::GetCore(); + DCHECK(core); + MojoHandle h = core->AddDispatcher(dispatcher); + if (h == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + *platform_handle_wrapper_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle, + ScopedPlatformHandle* platform_handle) { + DCHECK(platform_handle); + + system::Core* core = system::entrypoints::GetCore(); + DCHECK(core); + scoped_refptr<system::Dispatcher> dispatcher( + core->GetDispatcher(platform_handle_wrapper_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (dispatcher->GetType() != system::Dispatcher::kTypePlatformHandle) + return MOJO_RESULT_INVALID_ARGUMENT; + + *platform_handle = static_cast<system::PlatformHandleDispatcher*>( + dispatcher.get())->PassPlatformHandle().Pass(); + return MOJO_RESULT_OK; +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/embedder.h b/chromium/mojo/embedder/embedder.h new file mode 100644 index 00000000000..148e5d6d1af --- /dev/null +++ b/chromium/mojo/embedder/embedder.h @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_EMBEDDER_H_ +#define MOJO_EMBEDDER_EMBEDDER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/task_runner.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace embedder { + +// Must be called first to initialize the (global, singleton) system. +MOJO_SYSTEM_IMPL_EXPORT void Init(); + +// Creates a new "channel", returning a handle to the bootstrap message pipe on +// that channel. |platform_handle| should be an OS-dependent handle to one side +// of a suitable bidirectional OS "pipe" (e.g., a file descriptor to a socket on +// POSIX, a handle to a named pipe on Windows); this "pipe" should be connected +// and ready for operation (e.g., to be written to or read from). +// |io_thread_task_runner| should be a |TaskRunner| for the thread on which the +// "channel" will run (read data and demultiplex). +// +// On completion, it will run |callback| with a pointer to a |ChannelInfo| +// (which is meant to be opaque to the embedder). If +// |callback_thread_task_runner| is non-null, it the callback will be posted to +// that task runner. Otherwise, it will be run on the I/O thread directly. +// +// Returns an invalid |MOJO_HANDLE_INVALID| on error. Note that this will happen +// only if, e.g., the handle table is full (operation of the channel begins +// asynchronously and if, e.g., the other end of the "pipe" is closed, this will +// report an error to the returned handle in the usual way). +// +// Notes: The handle returned is ready for use immediately, with messages +// written to it queued. E.g., it would be perfectly valid for a message to be +// immediately written to the returned handle and the handle closed, all before +// the channel has begun operation on the IO thread. In this case, the channel +// is expected to connect as usual, send the queued message, and report that the +// handle was closed to the other side. (This message may well contain another +// handle, so there may well still be message pipes "on" this channel.) +// +// TODO(vtl): Figure out channel teardown. +struct ChannelInfo; +typedef base::Callback<void(ChannelInfo*)> DidCreateChannelCallback; +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle CreateChannel( + ScopedPlatformHandle platform_handle, + scoped_refptr<base::TaskRunner> io_thread_task_runner, + DidCreateChannelCallback callback, + scoped_refptr<base::TaskRunner> callback_thread_task_runner); + +MOJO_SYSTEM_IMPL_EXPORT void DestroyChannelOnIOThread( + ChannelInfo* channel_info); + +// Creates a |MojoHandle| that wraps the given |PlatformHandle| (taking +// ownership of it). This |MojoHandle| can then, e.g., be passed through message +// pipes. Note: This takes ownership (and thus closes) |platform_handle| even on +// failure, which is different from what you'd expect from a Mojo API, but it +// makes for a more convenient embedder API. +MOJO_SYSTEM_IMPL_EXPORT MojoResult CreatePlatformHandleWrapper( + ScopedPlatformHandle platform_handle, + MojoHandle* platform_handle_wrapper_handle); +// Retrieves the |PlatformHandle| that was wrapped into a |MojoHandle| (using +// |CreatePlatformHandleWrapper()| above). Note that the |MojoHandle| must still +// be closed separately. +MOJO_SYSTEM_IMPL_EXPORT MojoResult PassWrappedPlatformHandle( + MojoHandle platform_handle_wrapper_handle, + ScopedPlatformHandle* platform_handle); + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_EMBEDDER_H_ diff --git a/chromium/mojo/embedder/embedder_unittest.cc b/chromium/mojo/embedder/embedder_unittest.cc new file mode 100644 index 00000000000..32bac635122 --- /dev/null +++ b/chromium/mojo/embedder/embedder_unittest.cc @@ -0,0 +1,518 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/embedder.h" + +#include <string.h> + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "mojo/common/test/multiprocess_test_helper.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/embedder/test_embedder.h" +#include "mojo/public/c/system/core.h" +#include "mojo/system/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace embedder { +namespace { + +class ScopedTestChannel { + public: + // Creates a channel that lives on a given I/O thread (determined by the given + // |TaskRunner|) attached to the given |platform_handle|. After construction, + // |bootstrap_message_pipe()| gives the Mojo handle for the bootstrap message + // pipe on this channel; it is up to the caller to close this handle. + // Note: The I/O thread must outlive this object (and its message loop must + // continue pumping messages while this object is alive). + ScopedTestChannel(scoped_refptr<base::TaskRunner> io_thread_task_runner, + ScopedPlatformHandle platform_handle) + : io_thread_task_runner_(io_thread_task_runner), + bootstrap_message_pipe_(MOJO_HANDLE_INVALID), + did_create_channel_event_(true, false), + channel_info_(NULL) { + bootstrap_message_pipe_ = CreateChannel( + platform_handle.Pass(), io_thread_task_runner_, + base::Bind(&ScopedTestChannel::DidCreateChannel, + base::Unretained(this)), NULL).release().value(); + CHECK_NE(bootstrap_message_pipe_, MOJO_HANDLE_INVALID); + } + + // Destructor: Shuts down the channel. (As noted above, for this to happen, + // the I/O thread must be alive and pumping messages.) + ~ScopedTestChannel() { + system::test::PostTaskAndWait( + io_thread_task_runner_, + FROM_HERE, + base::Bind(&ScopedTestChannel::DestroyChannel, base::Unretained(this))); + } + + // Waits for channel creation to be completed. + void WaitForChannelCreationCompletion() { + did_create_channel_event_.Wait(); + } + + MojoHandle bootstrap_message_pipe() const { return bootstrap_message_pipe_; } + + // Call only after |WaitForChannelCreationCompletion()|. Use only to check + // that it's not null. + const ChannelInfo* channel_info() const { return channel_info_; } + + private: + void DidCreateChannel(ChannelInfo* channel_info) { + CHECK(channel_info); + CHECK(!channel_info_); + channel_info_ = channel_info; + did_create_channel_event_.Signal(); + } + + void DestroyChannel() { + CHECK(channel_info_); + DestroyChannelOnIOThread(channel_info_); + channel_info_ = NULL; + } + + scoped_refptr<base::TaskRunner> io_thread_task_runner_; + + // Valid from creation until whenever it gets closed (by the "owner" of this + // object). + // Note: We don't want use the C++ wrappers here, since we want to test the + // API at the lowest level. + MojoHandle bootstrap_message_pipe_; + + // Set after channel creation has been completed (i.e., the callback to + // |CreateChannel()| has been called). + base::WaitableEvent did_create_channel_event_; + + // Valid after channel creation completion until destruction. + ChannelInfo* channel_info_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTestChannel); +}; + +class EmbedderTest : public testing::Test { + public: + EmbedderTest() : test_io_thread_(system::test::TestIOThread::kAutoStart) {} + virtual ~EmbedderTest() {} + + protected: + system::test::TestIOThread* test_io_thread() { return &test_io_thread_; } + + private: + system::test::TestIOThread test_io_thread_; + + DISALLOW_COPY_AND_ASSIGN(EmbedderTest); +}; + +TEST_F(EmbedderTest, ChannelsBasic) { + Init(); + + { + PlatformChannelPair channel_pair; + ScopedTestChannel server_channel(test_io_thread()->task_runner(), + channel_pair.PassServerHandle()); + MojoHandle server_mp = server_channel.bootstrap_message_pipe(); + EXPECT_NE(server_mp, MOJO_HANDLE_INVALID); + ScopedTestChannel client_channel(test_io_thread()->task_runner(), + channel_pair.PassClientHandle()); + MojoHandle client_mp = client_channel.bootstrap_message_pipe(); + EXPECT_NE(client_mp, MOJO_HANDLE_INVALID); + + // We can write to a message pipe handle immediately. + const char kHello[] = "hello"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, kHello, + static_cast<uint32_t>(sizeof(kHello)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Now wait for the other side to become readable. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + + char buffer[1000] = {}; + uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), num_bytes); + EXPECT_STREQ(kHello, buffer); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); + + // By this point, these waits should basically be no-ops (since we've waited + // for the client message pipe to become readable, which implies that both + // the server and client channels were completely created). + server_channel.WaitForChannelCreationCompletion(); + client_channel.WaitForChannelCreationCompletion(); + EXPECT_TRUE(server_channel.channel_info() != NULL); + EXPECT_TRUE(client_channel.channel_info() != NULL); + } + + EXPECT_TRUE(test::Shutdown()); +} + +TEST_F(EmbedderTest, ChannelsHandlePassing) { + Init(); + + { + PlatformChannelPair channel_pair; + ScopedTestChannel server_channel(test_io_thread()->task_runner(), + channel_pair.PassServerHandle()); + MojoHandle server_mp = server_channel.bootstrap_message_pipe(); + EXPECT_NE(server_mp, MOJO_HANDLE_INVALID); + ScopedTestChannel client_channel(test_io_thread()->task_runner(), + channel_pair.PassClientHandle()); + MojoHandle client_mp = client_channel.bootstrap_message_pipe(); + EXPECT_NE(client_mp, MOJO_HANDLE_INVALID); + + MojoHandle h0, h1; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &h0, &h1)); + + // Write a message to |h0| (attaching nothing). + const char kHello[] = "hello"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h0, kHello, + static_cast<uint32_t>(sizeof(kHello)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Write one message to |server_mp|, attaching |h1|. + const char kWorld[] = "world!!!"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, kWorld, + static_cast<uint32_t>(sizeof(kWorld)), &h1, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + h1 = MOJO_HANDLE_INVALID; + + // Write another message to |h0|. + const char kFoo[] = "foo"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h0, kFoo, + static_cast<uint32_t>(sizeof(kFoo)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |client_mp| to become readable. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + + // Read a message from |client_mp|. + char buffer[1000] = {}; + uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer)); + MojoHandle handles[10] = {}; + uint32_t num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, buffer, &num_bytes, handles, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kWorld), num_bytes); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(1u, num_handles); + EXPECT_NE(handles[0], MOJO_HANDLE_INVALID); + h1 = handles[0]; + + // Wait for |h1| to become readable. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(h1, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + + // Read a message from |h1|. + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + memset(handles, 0, sizeof(handles)); + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h1, buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), num_bytes); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(0u, num_handles); + + // Wait for |h1| to become readable (again). + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(h1, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + + // Read the second message from |h1|. + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h1, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kFoo), num_bytes); + EXPECT_STREQ(kFoo, buffer); + + // Write a message to |h1|. + const char kBarBaz[] = "barbaz"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h1, kBarBaz, + static_cast<uint32_t>(sizeof(kBarBaz)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |h0| to become readable. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + + // Read a message from |h0|. + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h0, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kBarBaz), num_bytes); + EXPECT_STREQ(kBarBaz, buffer); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1)); + + server_channel.WaitForChannelCreationCompletion(); + client_channel.WaitForChannelCreationCompletion(); + EXPECT_TRUE(server_channel.channel_info() != NULL); + EXPECT_TRUE(client_channel.channel_info() != NULL); + } + + EXPECT_TRUE(test::Shutdown()); +} + +// The sequence of messages sent is: +// server_mp client_mp mp0 mp1 mp2 mp3 +// 1. "hello" +// 2. "world!" +// 3. "FOO" +// 4. "Bar"+mp1 +// 5. (close) +// 6. (close) +// 7. "baz" +// 8. (closed) +// 9. "quux"+mp2 +// 10. (close) +// 11. (wait/cl.) +// 12. (wait/cl.) +TEST_F(EmbedderTest, MultiprocessChannels) { + Init(); + mojo::test::MultiprocessTestHelper multiprocess_test_helper; + multiprocess_test_helper.StartChild("MultiprocessChannelsClient"); + + { + ScopedTestChannel server_channel( + test_io_thread()->task_runner(), + multiprocess_test_helper.server_platform_handle.Pass()); + MojoHandle server_mp = server_channel.bootstrap_message_pipe(); + EXPECT_NE(server_mp, MOJO_HANDLE_INVALID); + server_channel.WaitForChannelCreationCompletion(); + EXPECT_TRUE(server_channel.channel_info() != NULL); + + // 1. Write a message to |server_mp| (attaching nothing). + const char kHello[] = "hello"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, kHello, + static_cast<uint32_t>(sizeof(kHello)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // TODO(vtl): If the scope were ended immediately here (maybe after closing + // |server_mp|), we die with a fatal error in |Channel::HandleLocalError()|. + + // 2. Read a message from |server_mp|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(server_mp, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + char buffer[1000] = {}; + uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(server_mp, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + const char kWorld[] = "world!"; + EXPECT_EQ(sizeof(kWorld), num_bytes); + EXPECT_STREQ(kWorld, buffer); + + // Create a new message pipe (endpoints |mp0| and |mp1|). + MojoHandle mp0, mp1; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &mp0, &mp1)); + + // 3. Write something to |mp0|. + const char kFoo[] = "FOO"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp0, kFoo, + static_cast<uint32_t>(sizeof(kFoo)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // 4. Write a message to |server_mp|, attaching |mp1|. + const char kBar[] = "Bar"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, kBar, + static_cast<uint32_t>(sizeof(kBar)), &mp1, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + mp1 = MOJO_HANDLE_INVALID; + + // 5. Close |server_mp|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + + // 9. Read a message from |mp0|, which should have |mp2| attached. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(mp0, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + MojoHandle mp2 = MOJO_HANDLE_INVALID; + uint32_t num_handles = 1; + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(mp0, buffer, &num_bytes, &mp2, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + const char kQuux[] = "quux"; + EXPECT_EQ(sizeof(kQuux), num_bytes); + EXPECT_STREQ(kQuux, buffer); + EXPECT_EQ(1u, num_handles); + EXPECT_NE(mp2, MOJO_HANDLE_INVALID); + + // 7. Read a message from |mp2|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(mp2, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(mp2, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + const char kBaz[] = "baz"; + EXPECT_EQ(sizeof(kBaz), num_bytes); + EXPECT_STREQ(kBaz, buffer); + + // 10. Close |mp0|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp0)); + + // 12. Wait on |mp2| (which should eventually fail) and then close it. +// TODO(vtl): crbug.com/351768 +#if 0 + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoWait(mp2, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); +#endif + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp2)); + } + + EXPECT_TRUE(multiprocess_test_helper.WaitForChildTestShutdown()); + EXPECT_TRUE(test::Shutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(MultiprocessChannelsClient) { + embedder::ScopedPlatformHandle client_platform_handle = + mojo::test::MultiprocessTestHelper::client_platform_handle.Pass(); + EXPECT_TRUE(client_platform_handle.is_valid()); + + system::test::TestIOThread + test_io_thread(system::test::TestIOThread::kAutoStart); + Init(); + + { + ScopedTestChannel client_channel(test_io_thread.task_runner(), + client_platform_handle.Pass()); + MojoHandle client_mp = client_channel.bootstrap_message_pipe(); + EXPECT_NE(client_mp, MOJO_HANDLE_INVALID); + client_channel.WaitForChannelCreationCompletion(); + CHECK(client_channel.channel_info() != NULL); + + // 1. Read the first message from |client_mp|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + char buffer[1000] = {}; + uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + const char kHello[] = "hello"; + EXPECT_EQ(sizeof(kHello), num_bytes); + EXPECT_STREQ(kHello, buffer); + + // 2. Write a message to |client_mp| (attaching nothing). + const char kWorld[] = "world!"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(client_mp, kWorld, + static_cast<uint32_t>(sizeof(kWorld)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // 4. Read a message from |client_mp|, which should have |mp1| attached. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + // TODO(vtl): If the scope were to end here (and |client_mp| closed), we'd + // die (again due to |Channel::HandleLocalError()|). + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + MojoHandle mp1 = MOJO_HANDLE_INVALID; + uint32_t num_handles = 1; + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, buffer, &num_bytes, &mp1, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + const char kBar[] = "Bar"; + EXPECT_EQ(sizeof(kBar), num_bytes); + EXPECT_STREQ(kBar, buffer); + EXPECT_EQ(1u, num_handles); + EXPECT_NE(mp1, MOJO_HANDLE_INVALID); + // TODO(vtl): If the scope were to end here (and the two handles closed), + // we'd die due to |Channel::RunRemoteMessagePipeEndpoint()| not handling + // write errors (assuming the parent had closed the pipe). + + // 6. Close |client_mp|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); + + // Create a new message pipe (endpoints |mp2| and |mp3|). + MojoHandle mp2, mp3; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &mp2, &mp3)); + + // 7. Write a message to |mp3|. + const char kBaz[] = "baz"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp3, kBaz, + static_cast<uint32_t>(sizeof(kBaz)), NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // 8. Close |mp3|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp3)); + + // 9. Write a message to |mp1|, attaching |mp2|. + const char kQuux[] = "quux"; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, kQuux, + static_cast<uint32_t>(sizeof(kQuux)), &mp2, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + mp2 = MOJO_HANDLE_INVALID; + + // 3. Read a message from |mp1|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + memset(buffer, 0, sizeof(buffer)); + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(mp1, buffer, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + const char kFoo[] = "FOO"; + EXPECT_EQ(sizeof(kFoo), num_bytes); + EXPECT_STREQ(kFoo, buffer); + + // 11. Wait on |mp1| (which should eventually fail) and then close it. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp1)); + } + + EXPECT_TRUE(test::Shutdown()); +} + +// TODO(vtl): Test immediate write & close. +// TODO(vtl): Test broken-connection cases. + +} // namespace +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_channel_pair.cc b/chromium/mojo/embedder/platform_channel_pair.cc new file mode 100644 index 00000000000..783ced8b711 --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_pair.cc @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/platform_channel_pair.h" + +#include "base/logging.h" + +namespace mojo { +namespace embedder { + +const char PlatformChannelPair::kMojoPlatformChannelHandleSwitch[] = + "mojo-platform-channel-handle"; + +PlatformChannelPair::~PlatformChannelPair() { +} + +ScopedPlatformHandle PlatformChannelPair::PassServerHandle() { + return server_handle_.Pass(); +} + +ScopedPlatformHandle PlatformChannelPair::PassClientHandle() { + return client_handle_.Pass(); +} + +void PlatformChannelPair::ChildProcessLaunched() { + DCHECK(client_handle_.is_valid()); + client_handle_.reset(); +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_channel_pair.h b/chromium/mojo/embedder/platform_channel_pair.h new file mode 100644 index 00000000000..126bd3d98e1 --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_pair.h @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ +#define MOJO_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/launch.h" +#include "build/build_config.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/system_impl_export.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace embedder { + +// It would be nice to refactor base/process/launch.h to have a more platform- +// independent way of representing handles that are passed to child processes. +#if defined(OS_WIN) +typedef base::HandlesToInheritVector HandlePassingInformation; +#elif defined(OS_POSIX) +typedef base::FileHandleMappingVector HandlePassingInformation; +#else +#error "Unsupported." +#endif + +// This is used to create a pair of |PlatformHandle|s that are connected by a +// suitable (platform-specific) bidirectional "pipe" (e.g., socket on POSIX, +// named pipe on Windows). The resulting handles can then be used in the same +// process (e.g., in tests) or between processes. (The "server" handle is the +// one that will be used in the process that created the pair, whereas the +// "client" handle is the one that will be used in a different process.) +// +// This class provides facilities for passing the client handle to a child +// process. The parent should call |PrepareToPassClientHandlelToChildProcess()| +// to get the data needed to do this, spawn the child using that data, and then +// call |ChildProcessLaunched()|. Note that on Windows this facility (will) only +// work on Vista and later (TODO(vtl)). +// +// Note: |PlatformChannelPair()|, |PassClientHandleFromParentProcess()| and +// |PrepareToPassClientHandleToChildProcess()| have platform-specific +// implementations. +// +// Note: On POSIX platforms, to write to the "pipe", use +// |PlatformChannel{Write,Writev}()| (from platform_channel_utils_posix.h) +// instead of |write()|, |writev()|, etc. Otherwise, you have to worry about +// platform differences in suppressing |SIGPIPE|. +class MOJO_SYSTEM_IMPL_EXPORT PlatformChannelPair { + public: + PlatformChannelPair(); + ~PlatformChannelPair(); + + ScopedPlatformHandle PassServerHandle(); + + // For in-process use (e.g., in tests or to pass over another channel). + ScopedPlatformHandle PassClientHandle(); + + // To be called in the child process, after the parent process called + // |PrepareToPassClientHandleToChildProcess()| and launched the child (using + // the provided data), to create a client handle connected to the server + // handle (in the parent process). + static ScopedPlatformHandle PassClientHandleFromParentProcess( + const base::CommandLine& command_line); + + // Prepares to pass the client channel to a new child process, to be launched + // using |LaunchProcess()| (from base/launch.h). Modifies |*command_line| and + // |*handle_passing_info| as needed. + // Note: For Windows, this method only works on Vista and later. + void PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + HandlePassingInformation* handle_passing_info) const; + + // To be called once the child process has been successfully launched, to do + // any cleanup necessary. + void ChildProcessLaunched(); + + private: + static const char kMojoPlatformChannelHandleSwitch[]; + + ScopedPlatformHandle server_handle_; + ScopedPlatformHandle client_handle_; + + DISALLOW_COPY_AND_ASSIGN(PlatformChannelPair); +}; + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ diff --git a/chromium/mojo/embedder/platform_channel_pair_posix.cc b/chromium/mojo/embedder/platform_channel_pair_posix.cc new file mode 100644 index 00000000000..4eaea474c0b --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_pair_posix.cc @@ -0,0 +1,111 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/platform_channel_pair.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/posix/global_descriptors.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "mojo/embedder/platform_handle.h" + +namespace mojo { +namespace embedder { + +namespace { + +bool IsTargetDescriptorUsed( + const base::FileHandleMappingVector& file_handle_mapping, + int target_fd) { + for (size_t i = 0; i < file_handle_mapping.size(); i++) { + if (file_handle_mapping[i].second == target_fd) + return true; + } + return false; +} + +} // namespace + +PlatformChannelPair::PlatformChannelPair() { + // Create the Unix domain socket and set the ends to nonblocking. + int fds[2]; + // TODO(vtl): Maybe fail gracefully if |socketpair()| fails. + PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); + PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0); + PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0); + +#if defined(OS_MACOSX) + // This turns off |SIGPIPE| when writing to a closed socket (causing it to + // fail with |EPIPE| instead). On Linux, we have to use |send...()| with + // |MSG_NOSIGNAL| -- which is not supported on Mac -- instead. + int no_sigpipe = 1; + PCHECK(setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, + sizeof(no_sigpipe)) == 0); + PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, + sizeof(no_sigpipe)) == 0); +#endif // defined(OS_MACOSX) + + server_handle_.reset(PlatformHandle(fds[0])); + DCHECK(server_handle_.is_valid()); + client_handle_.reset(PlatformHandle(fds[1])); + DCHECK(client_handle_.is_valid()); +} + +// static +ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + std::string client_fd_string = + command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + int client_fd = -1; + if (client_fd_string.empty() || + !base::StringToInt(client_fd_string, &client_fd) || + client_fd < base::GlobalDescriptors::kBaseDescriptor) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } + + return ScopedPlatformHandle(PlatformHandle(client_fd)); +} + +void PlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + base::FileHandleMappingVector* handle_passing_info) const { + DCHECK(command_line); + DCHECK(handle_passing_info); + // This is an arbitrary sanity check. (Note that this guarantees that the loop + // below will terminate sanely.) + CHECK_LT(handle_passing_info->size(), 1000u); + + DCHECK(client_handle_.is_valid()); + + // Find a suitable FD to map our client handle to in the child process. + // This has quadratic time complexity in the size of |*handle_passing_info|, + // but |*handle_passing_info| should be very small (usually/often empty). + int target_fd = base::GlobalDescriptors::kBaseDescriptor; + while (IsTargetDescriptorUsed(*handle_passing_info, target_fd)) + target_fd++; + + handle_passing_info->push_back(std::pair<int, int>(client_handle_.get().fd, + target_fd)); + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch)) + << "Child command line already has switch --" + << kMojoPlatformChannelHandleSwitch << "=" + << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchASCII(kMojoPlatformChannelHandleSwitch, + base::IntToString(target_fd)); +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_channel_pair_posix_unittest.cc b/chromium/mojo/embedder/platform_channel_pair_posix_unittest.cc new file mode 100644 index 00000000000..6568e7acdfb --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_pair_posix_unittest.cc @@ -0,0 +1,243 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/platform_channel_pair.h" + +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + +#include <deque> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/common/test/test_utils.h" +#include "mojo/embedder/platform_channel_utils_posix.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/platform_handle_vector.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace embedder { +namespace { + +void WaitReadable(PlatformHandle h) { + struct pollfd pfds = {}; + pfds.fd = h.fd; + pfds.events = POLLIN; + CHECK_EQ(poll(&pfds, 1, -1), 1); +} + +class PlatformChannelPairPosixTest : public testing::Test { + public: + PlatformChannelPairPosixTest() {} + virtual ~PlatformChannelPairPosixTest() {} + + virtual void SetUp() OVERRIDE { + // Make sure |SIGPIPE| isn't being ignored. + struct sigaction action = {}; + action.sa_handler = SIG_DFL; + ASSERT_EQ(0, sigaction(SIGPIPE, &action, &old_action_)); + } + + virtual void TearDown() OVERRIDE { + // Restore the |SIGPIPE| handler. + ASSERT_EQ(0, sigaction(SIGPIPE, &old_action_, NULL)); + } + + private: + struct sigaction old_action_; + + DISALLOW_COPY_AND_ASSIGN(PlatformChannelPairPosixTest); +}; + +TEST_F(PlatformChannelPairPosixTest, NoSigPipe) { + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass(); + + // Write to the client. + static const char kHello[] = "hello"; + EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)), + write(client_handle.get().fd, kHello, sizeof(kHello))); + + // Close the client. + client_handle.reset(); + + // Read from the server; this should be okay. + char buffer[100] = {}; + EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)), + read(server_handle.get().fd, buffer, sizeof(buffer))); + EXPECT_STREQ(kHello, buffer); + + // Try reading again. + ssize_t result = read(server_handle.get().fd, buffer, sizeof(buffer)); + // We should probably get zero (for "end of file"), but -1 would also be okay. + EXPECT_TRUE(result == 0 || result == -1); + if (result == -1) + PLOG(WARNING) << "read (expected 0 for EOF)"; + + // Test our replacement for |write()|/|send()|. + result = PlatformChannelWrite(server_handle.get(), kHello, sizeof(kHello)); + EXPECT_EQ(-1, result); + if (errno != EPIPE) + PLOG(WARNING) << "write (expected EPIPE)"; + + // Test our replacement for |writev()|/|sendv()|. + struct iovec iov[2] = { + { const_cast<char*>(kHello), sizeof(kHello) }, + { const_cast<char*>(kHello), sizeof(kHello) } + }; + result = PlatformChannelWritev(server_handle.get(), iov, 2); + EXPECT_EQ(-1, result); + if (errno != EPIPE) + PLOG(WARNING) << "write (expected EPIPE)"; +} + +TEST_F(PlatformChannelPairPosixTest, SendReceiveData) { + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass(); + + for (size_t i = 0; i < 10; i++) { + std::string send_string(1 << i, 'A' + i); + + EXPECT_EQ(static_cast<ssize_t>(send_string.size()), + PlatformChannelWrite(server_handle.get(), send_string.data(), + send_string.size())); + + WaitReadable(client_handle.get()); + + char buf[10000] = {}; + std::deque<PlatformHandle> received_handles; + ssize_t result = PlatformChannelRecvmsg(client_handle.get(), buf, + sizeof(buf), &received_handles); + EXPECT_EQ(static_cast<ssize_t>(send_string.size()), result); + EXPECT_EQ(send_string, std::string(buf, static_cast<size_t>(result))); + EXPECT_TRUE(received_handles.empty()); + } +} + +TEST_F(PlatformChannelPairPosixTest, SendReceiveFDs) { + static const char kHello[] = "hello"; + + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass(); + + for (size_t i = 1; i < kPlatformChannelMaxNumHandles; i++) { + // Make |i| files, with the j-th file consisting of j copies of the digit i. + PlatformHandleVector platform_handles; + for (size_t j = 1; j <= i; j++) { + base::FilePath ignored; + base::ScopedFILE fp(base::CreateAndOpenTemporaryFile(&ignored)); + ASSERT_TRUE(fp); + fwrite(std::string(j, '0' + i).data(), 1, j, fp.get()); + platform_handles.push_back( + test::PlatformHandleFromFILE(fp.Pass()).release()); + ASSERT_TRUE(platform_handles.back().is_valid()); + } + + // Send the FDs (+ "hello"). + struct iovec iov = { const_cast<char*>(kHello), sizeof(kHello) }; + // We assume that the |sendmsg()| actually sends all the data. + EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)), + PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, + &platform_handles[0], + platform_handles.size())); + + WaitReadable(client_handle.get()); + + char buf[100] = {}; + std::deque<PlatformHandle> received_handles; + // We assume that the |recvmsg()| actually reads all the data. + EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)), + PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), + &received_handles)); + EXPECT_STREQ(kHello, buf); + EXPECT_EQ(i, received_handles.size()); + + for (size_t j = 0; !received_handles.empty(); j++) { + base::ScopedFILE fp(test::FILEFromPlatformHandle( + ScopedPlatformHandle(received_handles.front()), "rb")); + received_handles.pop_front(); + ASSERT_TRUE(fp); + rewind(fp.get()); + char read_buf[100]; + size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); + EXPECT_EQ(j + 1, bytes_read); + EXPECT_EQ(std::string(j + 1, '0' + i), std::string(read_buf, bytes_read)); + } + } +} + +TEST_F(PlatformChannelPairPosixTest, AppendReceivedFDs) { + static const char kHello[] = "hello"; + + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass(); + + const std::string file_contents("hello world"); + + { + base::FilePath ignored; + base::ScopedFILE fp(base::CreateAndOpenTemporaryFile(&ignored)); + ASSERT_TRUE(fp); + fwrite(file_contents.data(), 1, file_contents.size(), fp.get()); + PlatformHandleVector platform_handles; + platform_handles.push_back( + test::PlatformHandleFromFILE(fp.Pass()).release()); + ASSERT_TRUE(platform_handles.back().is_valid()); + + // Send the FD (+ "hello"). + struct iovec iov = { const_cast<char*>(kHello), sizeof(kHello) }; + // We assume that the |sendmsg()| actually sends all the data. + EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)), + PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, + &platform_handles[0], + platform_handles.size())); + } + + WaitReadable(client_handle.get()); + + // Start with an invalid handle in the deque. + std::deque<PlatformHandle> received_handles; + received_handles.push_back(PlatformHandle()); + + char buf[100] = {}; + // We assume that the |recvmsg()| actually reads all the data. + EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)), + PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), + &received_handles)); + EXPECT_STREQ(kHello, buf); + ASSERT_EQ(2u, received_handles.size()); + EXPECT_FALSE(received_handles[0].is_valid()); + EXPECT_TRUE(received_handles[1].is_valid()); + + { + base::ScopedFILE fp(test::FILEFromPlatformHandle( + ScopedPlatformHandle(received_handles[1]), "rb")); + received_handles[1] = PlatformHandle(); + ASSERT_TRUE(fp); + rewind(fp.get()); + char read_buf[100]; + size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); + EXPECT_EQ(file_contents.size(), bytes_read); + EXPECT_EQ(file_contents, std::string(read_buf, bytes_read)); + } +} + +} // namespace +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_channel_pair_win.cc b/chromium/mojo/embedder/platform_channel_pair_win.cc new file mode 100644 index 00000000000..f3eee9277b3 --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_pair_win.cc @@ -0,0 +1,117 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/platform_channel_pair.h" + +#include <windows.h> + +#include <string> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" +#include "mojo/embedder/platform_handle.h" + +namespace mojo { +namespace embedder { + +namespace { + +std::wstring GeneratePipeName() { + return base::StringPrintf( + L"\\\\.\\pipe\\mojo.%u.%u.%I64u", + GetCurrentProcessId(), GetCurrentThreadId(), base::RandUint64()); +} + +} // namespace + +PlatformChannelPair::PlatformChannelPair() { + std::wstring pipe_name = GeneratePipeName(); + + const DWORD kOpenMode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE; + const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE; + server_handle_.reset(PlatformHandle( + CreateNamedPipeW(pipe_name.c_str(), + kOpenMode, + kPipeMode, + 1, // Max instances. + 4096, // Out buffer size. + 4096, // In buffer size. + 5000, // Timeout in milliseconds. + NULL))); // Default security descriptor. + PCHECK(server_handle_.is_valid()); + + const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; + // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate + // the client. + const DWORD kFlags = SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | + FILE_FLAG_OVERLAPPED; + // Allow the handle to be inherited by child processes. + SECURITY_ATTRIBUTES security_attributes = { + sizeof(SECURITY_ATTRIBUTES), NULL, TRUE + }; + client_handle_.reset(PlatformHandle( + CreateFileW(pipe_name.c_str(), + kDesiredAccess, + 0, // No sharing. + &security_attributes, + OPEN_EXISTING, + kFlags, + NULL))); // No template file. + PCHECK(client_handle_.is_valid()); + + // Since a client has connected, ConnectNamedPipe() should return zero and + // GetLastError() should return ERROR_PIPE_CONNECTED. + CHECK(!ConnectNamedPipe(server_handle_.get().handle, NULL)); + PCHECK(GetLastError() == ERROR_PIPE_CONNECTED); +} + +// static +ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + std::string client_handle_string = + command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + + int client_handle_value = 0; + if (client_handle_string.empty() || + !base::StringToInt(client_handle_string, &client_handle_value)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } + + return ScopedPlatformHandle( + PlatformHandle(LongToHandle(client_handle_value))); +} + +void PlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + base::HandlesToInheritVector* handle_passing_info) const { + DCHECK(command_line); + DCHECK(handle_passing_info); + DCHECK(client_handle_.is_valid()); + + CHECK_GE(base::win::GetVersion(), base::win::VERSION_VISTA); + + handle_passing_info->push_back(client_handle_.get().handle); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch)) + << "Child command line already has switch --" + << kMojoPlatformChannelHandleSwitch << "=" + << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchASCII( + kMojoPlatformChannelHandleSwitch, + base::IntToString(HandleToLong(client_handle_.get().handle))); +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_channel_utils_posix.cc b/chromium/mojo/embedder/platform_channel_utils_posix.cc new file mode 100644 index 00000000000..8b7c36222c0 --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_utils_posix.cc @@ -0,0 +1,187 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/platform_channel_utils_posix.h" + +#include <sys/socket.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" + +namespace mojo { +namespace embedder { + +// On Linux, |SIGPIPE| is suppressed by passing |MSG_NOSIGNAL| to +// |send()|/|sendmsg()|. (There is no way of suppressing |SIGPIPE| on +// |write()|/|writev().) On Mac, |SIGPIPE| is suppressed by setting the +// |SO_NOSIGPIPE| option on the socket. +// +// Performance notes: +// - On Linux, we have to use |send()|/|sendmsg()| rather than +// |write()|/|writev()| in order to suppress |SIGPIPE|. This is okay, since +// |send()| is (slightly) faster than |write()| (!), while |sendmsg()| is +// quite comparable to |writev()|. +// - On Mac, we may use |write()|/|writev()|. Here, |write()| is considerably +// faster than |send()|, whereas |sendmsg()| is quite comparable to +// |writev()|. +// - On both platforms, an appropriate |sendmsg()|/|writev()| is considerably +// faster than two |send()|s/|write()|s. +// - Relative numbers (minimum real times from 10 runs) for one |write()| of +// 1032 bytes, one |send()| of 1032 bytes, one |writev()| of 32+1000 bytes, +// one |sendmsg()| of 32+1000 bytes, two |write()|s of 32 and 1000 bytes, two +// |send()|s of 32 and 1000 bytes: +// - Linux: 0.81 s, 0.77 s, 0.87 s, 0.89 s, 1.31 s, 1.22 s +// - Mac: 2.21 s, 2.91 s, 2.98 s, 3.08 s, 3.59 s, 4.74 s + +// Flags to use with calling |send()| or |sendmsg()| (see above). +#if defined(OS_MACOSX) +const int kSendFlags = 0; +#else +const int kSendFlags = MSG_NOSIGNAL; +#endif + +ssize_t PlatformChannelWrite(PlatformHandle h, + const void* bytes, + size_t num_bytes) { + DCHECK(h.is_valid()); + DCHECK(bytes); + DCHECK_GT(num_bytes, 0u); + +#if defined(OS_MACOSX) + return HANDLE_EINTR(write(h.fd, bytes, num_bytes)); +#else + return send(h.fd, bytes, num_bytes, kSendFlags); +#endif +} + +ssize_t PlatformChannelWritev(PlatformHandle h, + struct iovec* iov, + size_t num_iov) { + DCHECK(h.is_valid()); + DCHECK(iov); + DCHECK_GT(num_iov, 0u); + +#if defined(OS_MACOSX) + return HANDLE_EINTR(writev(h.fd, iov, static_cast<int>(num_iov))); +#else + struct msghdr msg = {}; + msg.msg_iov = iov; + msg.msg_iovlen = num_iov; + return HANDLE_EINTR(sendmsg(h.fd, &msg, kSendFlags)); +#endif +} + +ssize_t PlatformChannelSendmsgWithHandles(PlatformHandle h, + struct iovec* iov, + size_t num_iov, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + DCHECK(iov); + DCHECK_GT(num_iov, 0u); + DCHECK(platform_handles); + DCHECK_GT(num_platform_handles, 0u); + DCHECK_LE(num_platform_handles, kPlatformChannelMaxNumHandles); + + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = iov; + msg.msg_iovlen = num_iov; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(num_platform_handles * sizeof(int)); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_platform_handles * sizeof(int)); + for (size_t i = 0; i < num_platform_handles; i++) { + DCHECK(platform_handles[i].is_valid()); + reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = platform_handles[i].fd; + } + + return HANDLE_EINTR(sendmsg(h.fd, &msg, kSendFlags)); +} + +bool PlatformChannelSendHandles(PlatformHandle h, + PlatformHandle* handles, + size_t num_handles) { + DCHECK(handles); + DCHECK_GT(num_handles, 0u); + DCHECK_LE(num_handles, kPlatformChannelMaxNumHandles); + + // Note: |sendmsg()| fails on Mac if we don't write at least one character. + struct iovec iov = { const_cast<char*>(""), 1 }; + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(num_handles * sizeof(int)); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_handles * sizeof(int)); + for (size_t i = 0; i < num_handles; i++) { + DCHECK(handles[i].is_valid()); + reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = handles[i].fd; + } + + ssize_t result = HANDLE_EINTR(sendmsg(h.fd, &msg, kSendFlags)); + if (result < 1) { + DCHECK_EQ(result, -1); + return false; + } + + for (size_t i = 0; i < num_handles; i++) + handles[i].CloseIfNecessary(); + return true; +} + +ssize_t PlatformChannelRecvmsg(PlatformHandle h, + void* buf, + size_t num_bytes, + std::deque<PlatformHandle>* platform_handles) { + DCHECK(buf); + DCHECK_GT(num_bytes, 0u); + DCHECK(platform_handles); + + struct iovec iov = { buf, num_bytes }; + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + ssize_t result = HANDLE_EINTR(recvmsg(h.fd, &msg, MSG_DONTWAIT)); + if (result < 0) + return result; + + // Success; no control messages. + if (msg.msg_controllen == 0) + return result; + + DCHECK(!(msg.msg_flags & MSG_CTRUNC)); + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + size_t payload_length = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK_EQ(payload_length % sizeof(int), 0u); + size_t num_fds = payload_length / sizeof(int); + const int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + for (size_t i = 0; i < num_fds; i++) { + platform_handles->push_back(PlatformHandle(fds[i])); + DCHECK(platform_handles->back().is_valid()); + } + } + } + + return result; +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_channel_utils_posix.h b/chromium/mojo/embedder/platform_channel_utils_posix.h new file mode 100644 index 00000000000..6d0ffcd2de5 --- /dev/null +++ b/chromium/mojo/embedder/platform_channel_utils_posix.h @@ -0,0 +1,78 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ +#define MOJO_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ + +#include <stddef.h> +#include <sys/types.h> // For |ssize_t|. + +#include <deque> + +#include "base/memory/scoped_ptr.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/system/system_impl_export.h" + +struct iovec; // Declared in <sys/uio.h>. + +namespace mojo { +namespace embedder { + +// The maximum number of handles that can be sent "at once" using +// |PlatformChannelSendmsgWithHandles()|. +// TODO(vtl): This number is taken from ipc/file_descriptor_set_posix.h: +// |FileDescriptorSet::kMaxDescriptorsPerMessage|. Where does it come from? +const size_t kPlatformChannelMaxNumHandles = 7; + +// Use these to write to a socket created using |PlatformChannelPair| (or +// equivalent). These are like |write()| and |writev()|, but handle |EINTR| and +// never raise |SIGPIPE|. (Note: On Mac, the suppression of |SIGPIPE| is set up +// by |PlatformChannelPair|.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t PlatformChannelWrite(PlatformHandle h, + const void* bytes, + size_t num_bytes); +MOJO_SYSTEM_IMPL_EXPORT ssize_t PlatformChannelWritev(PlatformHandle h, + struct iovec* iov, + size_t num_iov); + +// Writes data, and the given set of |PlatformHandle|s (i.e., file descriptors) +// over the Unix domain socket given by |h| (e.g., created using +// |PlatformChannelPair()|). All the handles must be valid, and there must be at +// least one and at most |kPlatformChannelMaxNumHandles| handles. The return +// value is as for |sendmsg()|, namely -1 on failure and otherwise the number of +// bytes of data sent on success (note that this may not be all the data +// specified by |iov|). (The handles are not closed, regardless of success or +// failure.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t PlatformChannelSendmsgWithHandles( + PlatformHandle h, + struct iovec* iov, + size_t num_iov, + PlatformHandle* platform_handles, + size_t num_platform_handles); + +// TODO(vtl): Remove this once I've switched things over to +// |PlatformChannelSendmsgWithHandles()|. +// Sends |PlatformHandle|s (i.e., file descriptors) over the Unix domain socket +// (e.g., created using PlatformChannelPair|). (These will be sent in a single +// message having one null byte of data and one control message header with all +// the file descriptors.) All of the handles must be valid, and there must be at +// most |kPlatformChannelMaxNumHandles| (and at least one handle). Returns true +// on success, in which case it closes all the handles. +MOJO_SYSTEM_IMPL_EXPORT bool PlatformChannelSendHandles(PlatformHandle h, + PlatformHandle* handles, + size_t num_handles); + +// Wrapper around |recvmsg()|, which will extract any attached file descriptors +// (in the control message) to |PlatformHandle|s (and append them to +// |platform_handles|). (This also handles |EINTR|.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t PlatformChannelRecvmsg( + PlatformHandle h, + void* buf, + size_t num_bytes, + std::deque<PlatformHandle>* platform_handles); + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ diff --git a/chromium/mojo/embedder/platform_handle.cc b/chromium/mojo/embedder/platform_handle.cc new file mode 100644 index 00000000000..6cce5e8aab2 --- /dev/null +++ b/chromium/mojo/embedder/platform_handle.cc @@ -0,0 +1,40 @@ +// 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 "mojo/embedder/platform_handle.h" + +#include "build/build_config.h" +#if defined(OS_POSIX) +#include <unistd.h> +#elif defined(OS_WIN) +#include <windows.h> +#else +#error "Platform not yet supported." +#endif + +#include "base/compiler_specific.h" +#include "base/logging.h" + +namespace mojo { +namespace embedder { + +void PlatformHandle::CloseIfNecessary() { + if (!is_valid()) + return; + +#if defined(OS_POSIX) + bool success = (close(fd) == 0); + DPCHECK(success); + fd = -1; +#elif defined(OS_WIN) + bool success = !!CloseHandle(handle); + DPCHECK(success); + handle = INVALID_HANDLE_VALUE; +#else +#error "Platform not yet supported." +#endif +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_handle.h b/chromium/mojo/embedder/platform_handle.h new file mode 100644 index 00000000000..39da4a96249 --- /dev/null +++ b/chromium/mojo/embedder/platform_handle.h @@ -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. + +#ifndef MOJO_EMBEDDER_PLATFORM_HANDLE_H_ +#define MOJO_EMBEDDER_PLATFORM_HANDLE_H_ + +#include "build/build_config.h" +#include "mojo/system/system_impl_export.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace mojo { +namespace embedder { + +#if defined(OS_POSIX) +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { + PlatformHandle() : fd(-1) {} + explicit PlatformHandle(int fd) : fd(fd) {} + + void CloseIfNecessary(); + + bool is_valid() const { return fd != -1; } + + int fd; +}; +#elif defined(OS_WIN) +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { + PlatformHandle() : handle(INVALID_HANDLE_VALUE) {} + explicit PlatformHandle(HANDLE handle) : handle(handle) {} + + void CloseIfNecessary(); + + bool is_valid() const { return handle != INVALID_HANDLE_VALUE; } + + HANDLE handle; +}; +#else +#error "Platform not yet supported." +#endif + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_PLATFORM_HANDLE_H_ diff --git a/chromium/mojo/embedder/platform_handle_utils.h b/chromium/mojo/embedder/platform_handle_utils.h new file mode 100644 index 00000000000..aad3d2a889c --- /dev/null +++ b/chromium/mojo/embedder/platform_handle_utils.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ +#define MOJO_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ + +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace embedder { + +// Closes all the |PlatformHandle|s in the given container. +template <typename PlatformHandleContainer> +MOJO_SYSTEM_IMPL_EXPORT inline void CloseAllPlatformHandles( + PlatformHandleContainer* platform_handles) { + for (typename PlatformHandleContainer::iterator it = + platform_handles->begin(); + it != platform_handles->end(); + ++it) + it->CloseIfNecessary(); +} + +// Duplicates the given |PlatformHandle| (which must be valid). (Returns an +// invalid |ScopedPlatformHandle| on failure.) +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle DuplicatePlatformHandle( + PlatformHandle platform_handle); + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ diff --git a/chromium/mojo/embedder/platform_handle_utils_posix.cc b/chromium/mojo/embedder/platform_handle_utils_posix.cc new file mode 100644 index 00000000000..b6d93bb41da --- /dev/null +++ b/chromium/mojo/embedder/platform_handle_utils_posix.cc @@ -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. + +#include "mojo/embedder/platform_handle_utils.h" + +#include <unistd.h> + +#include "base/logging.h" + +namespace mojo { +namespace embedder { + +ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) { + DCHECK(platform_handle.is_valid()); + // Note that |dup()| returns -1 on error (which is exactly the value we use + // for invalid |PlatformHandle| FDs). + return ScopedPlatformHandle(PlatformHandle(dup(platform_handle.fd))); +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_handle_utils_win.cc b/chromium/mojo/embedder/platform_handle_utils_win.cc new file mode 100644 index 00000000000..446250f4c83 --- /dev/null +++ b/chromium/mojo/embedder/platform_handle_utils_win.cc @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/platform_handle_utils.h" + +#include <windows.h> + +#include "base/logging.h" + +namespace mojo { +namespace embedder { + +ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) { + DCHECK(platform_handle.is_valid()); + + HANDLE new_handle; + if (!DuplicateHandle(GetCurrentProcess(), + platform_handle.handle, + GetCurrentProcess(), + &new_handle, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + return ScopedPlatformHandle(); + DCHECK_NE(new_handle, INVALID_HANDLE_VALUE); + return ScopedPlatformHandle(PlatformHandle(new_handle)); +} + +} // namespace embedder +} // namespace mojo diff --git a/chromium/mojo/embedder/platform_handle_vector.h b/chromium/mojo/embedder/platform_handle_vector.h new file mode 100644 index 00000000000..db534196233 --- /dev/null +++ b/chromium/mojo/embedder/platform_handle_vector.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ +#define MOJO_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/platform_handle_utils.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace embedder { + +typedef std::vector<PlatformHandle> PlatformHandleVector; + +// A deleter (for use with |scoped_ptr|) which closes all handles and then +// |delete|s the |PlatformHandleVector|. +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandleVectorDeleter { + void operator()(PlatformHandleVector* platform_handles) const { + CloseAllPlatformHandles(platform_handles); + delete platform_handles; + } +}; + +typedef scoped_ptr<PlatformHandleVector, PlatformHandleVectorDeleter> + ScopedPlatformHandleVectorPtr; + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ diff --git a/chromium/mojo/embedder/scoped_platform_handle.h b/chromium/mojo/embedder/scoped_platform_handle.h new file mode 100644 index 00000000000..b9cfb58ea05 --- /dev/null +++ b/chromium/mojo/embedder/scoped_platform_handle.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ +#define MOJO_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ + +#include "base/compiler_specific.h" +#include "base/move.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace embedder { + +class MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle { + MOVE_ONLY_TYPE_FOR_CPP_03(ScopedPlatformHandle, RValue) + + public: + ScopedPlatformHandle() {} + explicit ScopedPlatformHandle(PlatformHandle handle) : handle_(handle) {} + ~ScopedPlatformHandle() { handle_.CloseIfNecessary(); } + + // Move-only constructor and operator=. + ScopedPlatformHandle(RValue other) : handle_(other.object->release()) {} + ScopedPlatformHandle& operator=(RValue other) { + handle_ = other.object->release(); + return *this; + } + + const PlatformHandle& get() const { return handle_; } + + void swap(ScopedPlatformHandle& other) { + PlatformHandle temp = handle_; + handle_ = other.handle_; + other.handle_ = temp; + } + + PlatformHandle release() WARN_UNUSED_RESULT { + PlatformHandle rv = handle_; + handle_ = PlatformHandle(); + return rv; + } + + void reset(PlatformHandle handle = PlatformHandle()) { + handle_.CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { + return handle_.is_valid(); + } + + private: + PlatformHandle handle_; +}; + +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ diff --git a/chromium/mojo/embedder/test_embedder.cc b/chromium/mojo/embedder/test_embedder.cc new file mode 100644 index 00000000000..049855d2504 --- /dev/null +++ b/chromium/mojo/embedder/test_embedder.cc @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/embedder/test_embedder.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/system/core.h" +#include "mojo/system/entrypoints.h" +#include "mojo/system/handle_table.h" + +namespace mojo { + +namespace system { +namespace internal { + +bool ShutdownCheckNoLeaks(Core* core_impl) { + // No point in taking the lock. + const HandleTable::HandleToEntryMap& handle_to_entry_map = + core_impl->handle_table_.handle_to_entry_map_; + + if (handle_to_entry_map.empty()) + return true; + + for (HandleTable::HandleToEntryMap::const_iterator it = + handle_to_entry_map.begin(); + it != handle_to_entry_map.end(); + ++it) { + LOG(ERROR) << "Mojo embedder shutdown: Leaking handle " << (*it).first; + } + return false; +} + +} // namespace internal +} // namespace system + +namespace embedder { +namespace test { + +bool Shutdown() { + system::Core* core = system::entrypoints::GetCore(); + CHECK(core); + system::entrypoints::SetCore(NULL); + + bool rv = system::internal::ShutdownCheckNoLeaks(core); + delete core; + return rv; +} + +} // namespace test +} // namespace embedder + +} // namespace mojo diff --git a/chromium/mojo/embedder/test_embedder.h b/chromium/mojo/embedder/test_embedder.h new file mode 100644 index 00000000000..a29adafe985 --- /dev/null +++ b/chromium/mojo/embedder/test_embedder.h @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EMBEDDER_TEST_EMBEDDER_H_ +#define MOJO_EMBEDDER_TEST_EMBEDDER_H_ + +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace embedder { +namespace test { + +// This shuts down the global, singleton instance. (Note: "Real" embedders are +// not expected to ever shut down this instance. This |Shutdown()| function will +// do more work to ensure that tests don't leak, etc.) Returns true if there +// were no problems, false if there were leaks -- i.e., handles still open -- or +// any other problems. +MOJO_SYSTEM_IMPL_EXPORT bool Shutdown(); + +} // namespace test +} // namespace embedder +} // namespace mojo + +#endif // MOJO_EMBEDDER_EMBEDDER_H_ diff --git a/chromium/mojo/environment/BUILD.gn b/chromium/mojo/environment/BUILD.gn new file mode 100644 index 00000000000..d25a94d58ea --- /dev/null +++ b/chromium/mojo/environment/BUILD.gn @@ -0,0 +1,47 @@ +# 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. + +# GYP version: mojo.gyp:mojo_environment_chromium +static_library("chromium") { + output_name = "mojo_environment_chromium" + + sources = [ + "environment.cc", + # TODO(vtl): This is kind of ugly. (See TODO in logging.h.) + "../public/cpp/environment/logging.h", + "../public/cpp/environment/lib/logging.cc", + ] + + deps = [ + ":chromium_impl", + "//mojo/common", + ] + + forward_dependent_configs_from = [ + ":chromium_impl", + ] +} + +# GYP version: mojo.gyp:mojo_environment_chromium_impl +component("chromium_impl") { + output_name = "mojo_environment_impl" + visibility = "//mojo/*" + + sources = [ + "default_async_waiter_impl.cc", + "default_async_waiter_impl.h", + "default_logger_impl.cc", + "default_logger_impl.h", + ] + + defines = [ + "MOJO_ENVIRONMENT_IMPL_IMPLEMENTATION", + ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + "//mojo/common", + ] +} diff --git a/chromium/mojo/environment/default_async_waiter_impl.cc b/chromium/mojo/environment/default_async_waiter_impl.cc new file mode 100644 index 00000000000..ff7a524752e --- /dev/null +++ b/chromium/mojo/environment/default_async_waiter_impl.cc @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/environment/default_async_waiter_impl.h" + +#include "base/bind.h" +#include "mojo/common/handle_watcher.h" + +namespace mojo { +namespace internal { +namespace { + +void OnHandleReady(common::HandleWatcher* watcher, + MojoAsyncWaitCallback callback, + void* closure, + MojoResult result) { + delete watcher; + callback(closure, result); +} + +MojoAsyncWaitID AsyncWait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline, + MojoAsyncWaitCallback callback, + void* closure) { + // This instance will be deleted when done or cancelled. + common::HandleWatcher* watcher = new common::HandleWatcher(); + watcher->Start(Handle(handle), signals, deadline, + base::Bind(&OnHandleReady, watcher, callback, closure)); + return reinterpret_cast<MojoAsyncWaitID>(watcher); +} + +void CancelWait(MojoAsyncWaitID wait_id) { + delete reinterpret_cast<common::HandleWatcher*>(wait_id); +} + +const MojoAsyncWaiter kDefaultAsyncWaiter = { + AsyncWait, + CancelWait +}; + +} // namespace + +const MojoAsyncWaiter* GetDefaultAsyncWaiterImpl() { + return &kDefaultAsyncWaiter; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/environment/default_async_waiter_impl.h b/chromium/mojo/environment/default_async_waiter_impl.h new file mode 100644 index 00000000000..5140e35b4c2 --- /dev/null +++ b/chromium/mojo/environment/default_async_waiter_impl.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_ENVIRONMENT_DEFAULT_ASYNC_WAITER_IMPL_H_ +#define MOJO_ENVIRONMENT_DEFAULT_ASYNC_WAITER_IMPL_H_ + +#include "mojo/environment/mojo_environment_impl_export.h" +#include "mojo/public/c/environment/async_waiter.h" + +namespace mojo { +namespace internal { + +MOJO_ENVIRONMENT_IMPL_EXPORT const MojoAsyncWaiter* GetDefaultAsyncWaiterImpl(); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_ENVIRONMENT_DEFAULT_ASYNC_WAITER_IMPL_H_ diff --git a/chromium/mojo/environment/default_logger_impl.cc b/chromium/mojo/environment/default_logger_impl.cc new file mode 100644 index 00000000000..575feb39891 --- /dev/null +++ b/chromium/mojo/environment/default_logger_impl.cc @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/environment/default_logger_impl.h" + +#include "base/logging.h" +#include "base/macros.h" + +namespace mojo { +namespace internal { +namespace { + +// We rely on log levels being the same numerically: +COMPILE_ASSERT(logging::LOG_VERBOSE == MOJO_LOG_LEVEL_VERBOSE, + verbose_log_level_value_mismatch); +COMPILE_ASSERT(logging::LOG_INFO == MOJO_LOG_LEVEL_INFO, + info_log_level_value_mismatch); +COMPILE_ASSERT(logging::LOG_WARNING == MOJO_LOG_LEVEL_WARNING, + warning_log_level_value_mismatch); +COMPILE_ASSERT(logging::LOG_ERROR == MOJO_LOG_LEVEL_ERROR, + error_log_level_value_mismatch); +COMPILE_ASSERT(logging::LOG_FATAL == MOJO_LOG_LEVEL_FATAL, + fatal_log_level_value_mismatch); + +int MojoToChromiumLogLevel(MojoLogLevel log_level) { + // See the compile asserts above. + return static_cast<int>(log_level); +} + +MojoLogLevel ChromiumToMojoLogLevel(int chromium_log_level) { + // See the compile asserts above. + return static_cast<MojoLogLevel>(chromium_log_level); +} + +void LogMessage(MojoLogLevel log_level, const char* message) { + int chromium_log_level = MojoToChromiumLogLevel(log_level); + int chromium_min_log_level = logging::GetMinLogLevel(); + // "Fatal" errors aren't suppressable. + DCHECK_LE(chromium_min_log_level, logging::LOG_FATAL); + if (chromium_log_level < chromium_min_log_level) + return; + + // TODO(vtl): Possibly, we should try to pull out the file and line number + // from |message|. + logging::LogMessage(__FILE__, __LINE__, chromium_log_level).stream() + << message; +} + +MojoLogLevel GetMinimumLogLevel() { + return ChromiumToMojoLogLevel(logging::GetMinLogLevel()); +} + +void SetMinimumLogLevel(MojoLogLevel log_level) { + logging::SetMinLogLevel(MojoToChromiumLogLevel(log_level)); +} + +const MojoLogger kDefaultLogger = { + LogMessage, + GetMinimumLogLevel, + SetMinimumLogLevel +}; + +} // namespace + +const MojoLogger* GetDefaultLoggerImpl() { + return &kDefaultLogger; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/environment/default_logger_impl.h b/chromium/mojo/environment/default_logger_impl.h new file mode 100644 index 00000000000..c87a7276e43 --- /dev/null +++ b/chromium/mojo/environment/default_logger_impl.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_ENVIRONMENT_DEFAULT_LOGGER_IMPL_H_ +#define MOJO_ENVIRONMENT_DEFAULT_LOGGER_IMPL_H_ + +#include "mojo/environment/mojo_environment_impl_export.h" +#include "mojo/public/c/environment/logger.h" + +namespace mojo { +namespace internal { + +MOJO_ENVIRONMENT_IMPL_EXPORT const MojoLogger* GetDefaultLoggerImpl(); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_ENVIRONMENT_DEFAULT_LOGGER_IMPL_H_ diff --git a/chromium/mojo/environment/environment.cc b/chromium/mojo/environment/environment.cc new file mode 100644 index 00000000000..f8611139381 --- /dev/null +++ b/chromium/mojo/environment/environment.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/environment/environment.h" + +#include "mojo/environment/default_async_waiter_impl.h" +#include "mojo/environment/default_logger_impl.h" + +namespace mojo { + +// These methods are intentionally not implemented so that there is a link +// error if someone uses them in a Chromium-environment. +#if 0 +Environment::Environment() { +} + +Environment::Environment(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger) { +} + +Environment::~Environment() { +} +#endif + +// static +const MojoAsyncWaiter* Environment::GetDefaultAsyncWaiter() { + return internal::GetDefaultAsyncWaiterImpl(); +} + +// static +const MojoLogger* Environment::GetDefaultLogger() { + return internal::GetDefaultLoggerImpl(); +} + +} // namespace mojo diff --git a/chromium/mojo/environment/mojo_environment_impl_export.h b/chromium/mojo/environment/mojo_environment_impl_export.h new file mode 100644 index 00000000000..025542533c2 --- /dev/null +++ b/chromium/mojo/environment/mojo_environment_impl_export.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_ENVIRONMENT_MOJO_ENVIRONMENT_IMPL_EXPORT_H_ +#define MOJO_ENVIRONMENT_MOJO_ENVIRONMENT_IMPL_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_ENVIRONMENT_IMPL_IMPLEMENTATION) +#define MOJO_ENVIRONMENT_IMPL_EXPORT __declspec(dllexport) +#else +#define MOJO_ENVIRONMENT_IMPL_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_ENVIRONMENT_IMPL_IMPLEMENTATION) +#define MOJO_ENVIRONMENT_IMPL_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_ENVIRONMENT_IMPL_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_ENVIRONMENT_IMPL_EXPORT +#endif + +#endif // MOJO_ENVIRONMENT_MOJO_ENVIRONMENT_IMPL_EXPORT_H_ diff --git a/chromium/mojo/examples/aura_demo/DEPS b/chromium/mojo/examples/aura_demo/DEPS new file mode 100644 index 00000000000..5cbfbf175ca --- /dev/null +++ b/chromium/mojo/examples/aura_demo/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+cc", + "+skia/ext", + "+ui/aura", + "+ui/base/hit_test.h", + "+ui/compositor", + "+ui/events", + "+ui/gfx", +] diff --git a/chromium/mojo/examples/aura_demo/aura_demo.cc b/chromium/mojo/examples/aura_demo/aura_demo.cc new file mode 100644 index 00000000000..de005a77800 --- /dev/null +++ b/chromium/mojo/examples/aura_demo/aura_demo.cc @@ -0,0 +1,198 @@ +// 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 <stdio.h> +#include <string> + +#include "base/bind.h" +#include "mojo/aura/context_factory_mojo.h" +#include "mojo/aura/screen_mojo.h" +#include "mojo/aura/window_tree_host_mojo.h" +#include "mojo/aura/window_tree_host_mojo_delegate.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ui/aura/client/default_capture_client.h" +#include "ui/aura/client/window_tree_client.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/base/hit_test.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/codec/png_codec.h" + +namespace mojo { +namespace examples { + +// Trivial WindowDelegate implementation that draws a colored background. +class DemoWindowDelegate : public aura::WindowDelegate { + public: + explicit DemoWindowDelegate(SkColor color) : color_(color) {} + + // Overridden from WindowDelegate: + virtual gfx::Size GetMinimumSize() const OVERRIDE { + return gfx::Size(); + } + + virtual gfx::Size GetMaximumSize() const OVERRIDE { + return gfx::Size(); + } + + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE {} + virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE { + return gfx::kNullCursor; + } + virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE { + return HTCAPTION; + } + virtual bool ShouldDescendIntoChildForEventHandling( + aura::Window* child, + const gfx::Point& location) OVERRIDE { + return true; + } + virtual bool CanFocus() OVERRIDE { return true; } + virtual void OnCaptureLost() OVERRIDE {} + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { + canvas->DrawColor(color_, SkXfermode::kSrc_Mode); + } + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {} + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {} + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {} + virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {} + virtual bool HasHitTestMask() const OVERRIDE { return false; } + virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {} + + private: + const SkColor color_; + + DISALLOW_COPY_AND_ASSIGN(DemoWindowDelegate); +}; + +class DemoWindowTreeClient : public aura::client::WindowTreeClient { + public: + explicit DemoWindowTreeClient(aura::Window* window) : window_(window) { + aura::client::SetWindowTreeClient(window_, this); + } + + virtual ~DemoWindowTreeClient() { + aura::client::SetWindowTreeClient(window_, NULL); + } + + // Overridden from aura::client::WindowTreeClient: + virtual aura::Window* GetDefaultParent(aura::Window* context, + aura::Window* window, + const gfx::Rect& bounds) OVERRIDE { + if (!capture_client_) { + capture_client_.reset( + new aura::client::DefaultCaptureClient(window_->GetRootWindow())); + } + return window_; + } + + private: + aura::Window* window_; + scoped_ptr<aura::client::DefaultCaptureClient> capture_client_; + + DISALLOW_COPY_AND_ASSIGN(DemoWindowTreeClient); +}; + +class AuraDemo : public Application, + public WindowTreeHostMojoDelegate, + public view_manager::ViewManagerDelegate { + public: + AuraDemo() + : window1_(NULL), + window2_(NULL), + window21_(NULL), + view_(NULL) { + view_manager::ViewManager::Create(this, this); + } + virtual ~AuraDemo() {} + + private: + // Overridden from view_manager::ViewManagerDelegate: + virtual void OnRootAdded(view_manager::ViewManager* view_manager, + view_manager::Node* root) OVERRIDE { + // TODO(beng): this function could be called multiple times! + view_ = view_manager::View::Create(view_manager); + root->SetActiveView(view_); + + window_tree_host_.reset(new WindowTreeHostMojo(root, this)); + window_tree_host_->InitHost(); + + window_tree_client_.reset( + new DemoWindowTreeClient(window_tree_host_->window())); + + delegate1_.reset(new DemoWindowDelegate(SK_ColorBLUE)); + window1_ = new aura::Window(delegate1_.get()); + window1_->Init(aura::WINDOW_LAYER_TEXTURED); + window1_->SetBounds(gfx::Rect(100, 100, 400, 400)); + window1_->Show(); + window_tree_host_->window()->AddChild(window1_); + + delegate2_.reset(new DemoWindowDelegate(SK_ColorRED)); + window2_ = new aura::Window(delegate2_.get()); + window2_->Init(aura::WINDOW_LAYER_TEXTURED); + window2_->SetBounds(gfx::Rect(200, 200, 350, 350)); + window2_->Show(); + window_tree_host_->window()->AddChild(window2_); + + delegate21_.reset(new DemoWindowDelegate(SK_ColorGREEN)); + window21_ = new aura::Window(delegate21_.get()); + window21_->Init(aura::WINDOW_LAYER_TEXTURED); + window21_->SetBounds(gfx::Rect(10, 10, 50, 50)); + window21_->Show(); + window2_->AddChild(window21_); + + window_tree_host_->Show(); + } + + // WindowTreeHostMojoDelegate: + virtual void CompositorContentsChanged(const SkBitmap& bitmap) OVERRIDE { + view_->SetContents(bitmap); + } + + virtual void Initialize() OVERRIDE { + aura::Env::CreateInstance(true); + context_factory_.reset(new ContextFactoryMojo); + aura::Env::GetInstance()->set_context_factory(context_factory_.get()); + screen_.reset(ScreenMojo::Create()); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get()); + } + + scoped_ptr<DemoWindowTreeClient> window_tree_client_; + + scoped_ptr<ui::ContextFactory> context_factory_; + + scoped_ptr<ScreenMojo> screen_; + + scoped_ptr<DemoWindowDelegate> delegate1_; + scoped_ptr<DemoWindowDelegate> delegate2_; + scoped_ptr<DemoWindowDelegate> delegate21_; + + aura::Window* window1_; + aura::Window* window2_; + aura::Window* window21_; + + view_manager::View* view_; + + scoped_ptr<aura::WindowTreeHost> window_tree_host_; + + DISALLOW_COPY_AND_ASSIGN(AuraDemo); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::AuraDemo(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/aura_demo/view_manager_init.cc b/chromium/mojo/examples/aura_demo/view_manager_init.cc new file mode 100644 index 00000000000..7a558d3250a --- /dev/null +++ b/chromium/mojo/examples/aura_demo/view_manager_init.cc @@ -0,0 +1,45 @@ +// 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/basictypes.h" +#include "base/bind.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" + +namespace mojo { +namespace examples { + +// ViewManagerInit is responsible for establishing the initial connection to +// the view manager. When established it loads |mojo_aura_demo|. +class ViewManagerInit : public Application { + public: + ViewManagerInit() {} + virtual ~ViewManagerInit() {} + + virtual void Initialize() OVERRIDE { + ConnectTo("mojo:mojo_view_manager", &view_manager_init_); + view_manager_init_->EmbedRoot("mojo:mojo_aura_demo", + base::Bind(&ViewManagerInit::DidConnect, + base::Unretained(this))); + } + + private: + void DidConnect(bool result) { + DCHECK(result); + VLOG(1) << "ViewManagerInit::DidConnection result=" << result; + } + + view_manager::ViewManagerInitServicePtr view_manager_init_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerInit); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::ViewManagerInit(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/browser/DEPS b/chromium/mojo/examples/browser/DEPS new file mode 100644 index 00000000000..b65676e92cf --- /dev/null +++ b/chromium/mojo/examples/browser/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "+cc", + "+skia/ext", + "+ui/aura", + "+ui/base/hit_test.h", + "+ui/compositor", + "+ui/events", + "+ui/gfx", + "+ui/views", +] diff --git a/chromium/mojo/examples/browser/browser.cc b/chromium/mojo/examples/browser/browser.cc new file mode 100644 index 00000000000..d5b6caeeead --- /dev/null +++ b/chromium/mojo/examples/browser/browser.cc @@ -0,0 +1,128 @@ +// 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/basictypes.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" +#include "mojo/views/native_widget_view_manager.h" +#include "mojo/views/views_init.h" +#include "ui/events/event.h" +#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/controls/textfield/textfield_controller.h" +#include "ui/views/layout/layout_manager.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" +#include "url/gurl.h" + +namespace mojo { +namespace examples { + +class BrowserLayoutManager : public views::LayoutManager { + public: + BrowserLayoutManager() {} + virtual ~BrowserLayoutManager() {} + + private: + // Overridden from views::LayoutManager: + virtual void Layout(views::View* host) OVERRIDE { + // Browser view has one child, a text input field. + DCHECK_EQ(1, host->child_count()); + views::View* text_field = host->child_at(0); + gfx::Size ps = text_field->GetPreferredSize(); + text_field->SetBoundsRect(gfx::Rect(host->width(), ps.height())); + } + virtual gfx::Size GetPreferredSize(const views::View* host) const OVERRIDE { + return gfx::Size(); + } + + DISALLOW_COPY_AND_ASSIGN(BrowserLayoutManager); +}; + +// This is the basics of creating a views widget with a textfield. +// TODO: cleanup! +class Browser : public Application, + public view_manager::ViewManagerDelegate, + public views::TextfieldController { + public: + Browser() : view_manager_(NULL) {} + + virtual ~Browser() { + } + + private: + // Overridden from Application: + virtual void Initialize() MOJO_OVERRIDE { + views_init_.reset(new ViewsInit); + view_manager::ViewManager::Create(this, this); + ConnectTo("mojo:mojo_window_manager", &navigator_host_); + } + + void CreateWidget(view_manager::Node* node) { + views::Textfield* textfield = new views::Textfield; + textfield->set_controller(this); + + views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView; + widget_delegate->GetContentsView()->AddChildView(textfield); + widget_delegate->GetContentsView()->SetLayoutManager( + new BrowserLayoutManager); + + views::Widget* widget = new views::Widget; + views::Widget::InitParams params( + views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); + params.native_widget = new NativeWidgetViewManager(widget, node); + params.delegate = widget_delegate; + params.bounds = gfx::Rect(node->bounds().width(), node->bounds().height()); + widget->Init(params); + widget->Show(); + textfield->RequestFocus(); + } + + // view_manager::ViewManagerDelegate: + virtual void OnRootAdded(view_manager::ViewManager* view_manager, + view_manager::Node* root) OVERRIDE { + // TODO: deal with OnRootAdded() being invoked multiple times. + view_manager_ = view_manager; + root->SetActiveView(view_manager::View::Create(view_manager)); + root->SetFocus(); + CreateWidget(root); + } + + // views::TextfieldController: + virtual bool HandleKeyEvent(views::Textfield* sender, + const ui::KeyEvent& key_event) OVERRIDE { + if (key_event.key_code() == ui::VKEY_RETURN) { + GURL url(sender->text()); + printf("User entered this URL: %s\n", url.spec().c_str()); + navigation::NavigationDetailsPtr nav_details( + navigation::NavigationDetails::New()); + nav_details->url = url.spec(); + navigator_host_->RequestNavigate(view_manager_->GetRoots().front()->id(), + nav_details.Pass()); + } + return false; + } + + scoped_ptr<ViewsInit> views_init_; + + view_manager::ViewManager* view_manager_; + navigation::NavigatorHostPtr navigator_host_; + + DISALLOW_COPY_AND_ASSIGN(Browser); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::Browser; +} + +} // namespace mojo diff --git a/chromium/mojo/examples/compositor_app/DEPS b/chromium/mojo/examples/compositor_app/DEPS new file mode 100644 index 00000000000..8ea9d7b5532 --- /dev/null +++ b/chromium/mojo/examples/compositor_app/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+cc", + "+ui/gfx", + "+gpu/command_buffer/client", +] diff --git a/chromium/mojo/examples/compositor_app/compositor_app.cc b/chromium/mojo/examples/compositor_app/compositor_app.cc new file mode 100644 index 00000000000..31752bd4af3 --- /dev/null +++ b/chromium/mojo/examples/compositor_app/compositor_app.cc @@ -0,0 +1,69 @@ +// 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 <stdio.h> +#include <string> + +#include "base/macros.h" +#include "mojo/examples/compositor_app/compositor_host.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ui/gfx/rect.h" + +namespace mojo { +namespace examples { + +class SampleApp : public Application, public NativeViewportClient { + public: + SampleApp() {} + virtual ~SampleApp() {} + + virtual void Initialize() OVERRIDE { + ConnectTo("mojo:mojo_native_viewport_service", &viewport_); + viewport_.set_client(this); + + viewport_->Create(Rect::From(gfx::Rect(10, 10, 800, 600))); + viewport_->Show(); + + MessagePipe pipe; + viewport_->CreateGLES2Context( + MakeRequest<CommandBuffer>(pipe.handle0.Pass())); + host_.reset(new CompositorHost(pipe.handle1.Pass())); + } + + virtual void OnCreated() OVERRIDE { + } + + virtual void OnDestroyed() OVERRIDE { + base::MessageLoop::current()->Quit(); + } + + virtual void OnBoundsChanged(RectPtr bounds) OVERRIDE { + host_->SetSize(gfx::Size(bounds->width, bounds->height)); + } + + virtual void OnEvent(EventPtr event, + const mojo::Callback<void()>& callback) OVERRIDE { + callback.Run(); + } + + private: + mojo::GLES2Initializer gles2; + NativeViewportPtr viewport_; + scoped_ptr<CompositorHost> host_; + DISALLOW_COPY_AND_ASSIGN(SampleApp); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::SampleApp(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/compositor_app/compositor_host.cc b/chromium/mojo/examples/compositor_app/compositor_host.cc new file mode 100644 index 00000000000..31601456aa4 --- /dev/null +++ b/chromium/mojo/examples/compositor_app/compositor_host.cc @@ -0,0 +1,89 @@ +// 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 "mojo/examples/compositor_app/compositor_host.h" + +#include "cc/layers/layer.h" +#include "cc/layers/solid_color_layer.h" +#include "cc/output/context_provider.h" +#include "cc/output/output_surface.h" +#include "cc/trees/layer_tree_host.h" +#include "mojo/cc/context_provider_mojo.h" + +namespace mojo { +namespace examples { + +CompositorHost::CompositorHost(ScopedMessagePipeHandle command_buffer_handle) + : command_buffer_handle_(command_buffer_handle.Pass()), + compositor_thread_("compositor") { + DCHECK(command_buffer_handle_.is_valid()); + bool started = compositor_thread_.Start(); + DCHECK(started); + + cc::LayerTreeSettings settings; + tree_ = cc::LayerTreeHost::CreateThreaded( + this, NULL, settings, compositor_thread_.message_loop_proxy()); + SetupScene(); +} + +CompositorHost::~CompositorHost() {} + +void CompositorHost::SetSize(const gfx::Size& viewport_size) { + tree_->SetViewportSize(viewport_size); + tree_->SetLayerTreeHostClientReady(); +} + +void CompositorHost::SetupScene() { + scoped_refptr<cc::Layer> root_layer = cc::SolidColorLayer::Create(); + root_layer->SetBounds(gfx::Size(500, 500)); + root_layer->SetBackgroundColor(SK_ColorBLUE); + root_layer->SetIsDrawable(true); + tree_->SetRootLayer(root_layer); + + child_layer_ = cc::SolidColorLayer::Create(); + child_layer_->SetBounds(gfx::Size(100, 100)); + child_layer_->SetBackgroundColor(SK_ColorGREEN); + child_layer_->SetIsDrawable(true); + gfx::Transform child_transform; + child_transform.Translate(200, 200); + child_layer_->SetTransform(child_transform); + root_layer->AddChild(child_layer_); +} + +void CompositorHost::WillBeginMainFrame(int frame_id) {} +void CompositorHost::DidBeginMainFrame() {} + +void CompositorHost::Animate(base::TimeTicks frame_begin_time) { + // TODO(jamesr): Should use cc's animation system. + static const double kDegreesPerSecond = 70.0; + double time_in_seconds = (frame_begin_time - base::TimeTicks()).InSecondsF(); + double child_rotation_degrees = kDegreesPerSecond * time_in_seconds; + gfx::Transform child_transform; + child_transform.Translate(200, 200); + child_transform.Rotate(child_rotation_degrees); + child_layer_->SetTransform(child_transform); + tree_->SetNeedsAnimate(); +} + +void CompositorHost::Layout() {} +void CompositorHost::ApplyScrollAndScale(const gfx::Vector2d& scroll_delta, + float page_scale) {} + +scoped_ptr<cc::OutputSurface> CompositorHost::CreateOutputSurface( + bool fallback) { + return make_scoped_ptr( + new cc::OutputSurface( + new ContextProviderMojo(command_buffer_handle_.Pass()))); +} + +void CompositorHost::DidInitializeOutputSurface() { +} + +void CompositorHost::WillCommit() {} +void CompositorHost::DidCommit() {} +void CompositorHost::DidCommitAndDrawFrame() {} +void CompositorHost::DidCompleteSwapBuffers() {} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/compositor_app/compositor_host.h b/chromium/mojo/examples/compositor_app/compositor_host.h new file mode 100644 index 00000000000..ea49e280411 --- /dev/null +++ b/chromium/mojo/examples/compositor_app/compositor_host.h @@ -0,0 +1,58 @@ +// 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 MOJO_EXAMPLES_COMPOSITOR_APP_COMPOSITOR_HOST_H_ +#define MOJO_EXAMPLES_COMPOSITOR_APP_COMPOSITOR_HOST_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "cc/trees/layer_tree_host_client.h" +#include "mojo/public/cpp/system/core.h" +#include "ui/gfx/size.h" + +namespace cc { +class Layer; +class LayerTreeHost; +} // namespace cc + +namespace mojo { +namespace examples { +class GLES2ClientImpl; + +class CompositorHost : public cc::LayerTreeHostClient { + public: + explicit CompositorHost(ScopedMessagePipeHandle command_buffer_handle); + virtual ~CompositorHost(); + + void SetSize(const gfx::Size& viewport_size); + + // cc::LayerTreeHostClient implementation. + virtual void WillBeginMainFrame(int frame_id) OVERRIDE; + virtual void DidBeginMainFrame() OVERRIDE; + virtual void Animate(base::TimeTicks frame_begin_time) OVERRIDE; + virtual void Layout() OVERRIDE; + virtual void ApplyScrollAndScale(const gfx::Vector2d& scroll_delta, + float page_scale) OVERRIDE; + virtual scoped_ptr<cc::OutputSurface> CreateOutputSurface(bool fallback) + OVERRIDE; + virtual void DidInitializeOutputSurface() OVERRIDE; + virtual void WillCommit() OVERRIDE; + virtual void DidCommit() OVERRIDE; + virtual void DidCommitAndDrawFrame() OVERRIDE; + virtual void DidCompleteSwapBuffers() OVERRIDE; + + private: + void SetupScene(); + + ScopedMessagePipeHandle command_buffer_handle_; + scoped_ptr<cc::LayerTreeHost> tree_; + scoped_refptr<cc::Layer> child_layer_; + base::Thread compositor_thread_; +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_COMPOSITOR_APP_COMPOSITOR_HOST_H_ diff --git a/chromium/mojo/examples/dbus_echo/dbus_echo_app.cc b/chromium/mojo/examples/dbus_echo/dbus_echo_app.cc new file mode 100644 index 00000000000..5a9b80f0c15 --- /dev/null +++ b/chromium/mojo/examples/dbus_echo/dbus_echo_app.cc @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> +#include <string> + +#include "base/bind.h" +#include "base/logging.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/dbus_echo/echo.mojom.h" + +namespace mojo { +namespace examples { + +class DBusEchoApp : public Application { + public: + DBusEchoApp() {} + virtual ~DBusEchoApp() {} + + virtual void Initialize() MOJO_OVERRIDE { + ConnectTo("dbus:org.chromium.EchoService/org/chromium/MojoImpl", + &echo_service_); + + echo_service_->Echo( + String::From("who"), + base::Bind(&DBusEchoApp::OnEcho, base::Unretained(this))); + } + + private: + void OnEcho(String echoed) { + LOG(INFO) << "echo'd " << echoed; + } + + EchoServicePtr echo_service_; + + DISALLOW_COPY_AND_ASSIGN(DBusEchoApp); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::DBusEchoApp(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/demo_launcher/demo_launcher.cc b/chromium/mojo/examples/demo_launcher/demo_launcher.cc new file mode 100644 index 00000000000..141a49ada8e --- /dev/null +++ b/chromium/mojo/examples/demo_launcher/demo_launcher.cc @@ -0,0 +1,43 @@ +// 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/basictypes.h" +#include "base/bind.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" + +namespace mojo { +namespace examples { + +class DemoLauncher : public Application { + public: + DemoLauncher() {} + virtual ~DemoLauncher() {} + + private: + // Overridden from Application: + virtual void Initialize() MOJO_OVERRIDE { + ConnectTo<view_manager::ViewManagerInitService>("mojo:mojo_view_manager", + &view_manager_init_); + view_manager_init_->EmbedRoot("mojo:mojo_window_manager", + base::Bind(&DemoLauncher::OnConnect, + base::Unretained(this))); + } + + void OnConnect(bool success) {} + + view_manager::ViewManagerInitServicePtr view_manager_init_; + + DISALLOW_COPY_AND_ASSIGN(DemoLauncher); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::DemoLauncher; +} + +} // namespace mojo diff --git a/chromium/mojo/examples/embedded_app/DEPS b/chromium/mojo/examples/embedded_app/DEPS new file mode 100644 index 00000000000..fe1d98e366d --- /dev/null +++ b/chromium/mojo/examples/embedded_app/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/events", +] diff --git a/chromium/mojo/examples/embedded_app/embedded_app.cc b/chromium/mojo/examples/embedded_app/embedded_app.cc new file mode 100644 index 00000000000..840df0730fe --- /dev/null +++ b/chromium/mojo/examples/embedded_app/embedded_app.cc @@ -0,0 +1,155 @@ +// 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/basictypes.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "mojo/examples/window_manager/window_manager.mojom.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/node_observer.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" +#include "ui/events/event_constants.h" +#include "url/gurl.h" +#include "url/url_util.h" + +using mojo::view_manager::Node; +using mojo::view_manager::NodeObserver; +using mojo::view_manager::View; +using mojo::view_manager::ViewManager; +using mojo::view_manager::ViewManagerDelegate; +using mojo::view_manager::ViewObserver; + +namespace mojo { +namespace examples { + +class EmbeddedApp : public Application, + public ViewManagerDelegate, + public ViewObserver, + public NodeObserver { + public: + EmbeddedApp() : view_manager_(NULL) { + url::AddStandardScheme("mojo"); + } + virtual ~EmbeddedApp() {} + + void SetNodeColor(uint32 node_id, SkColor color) { + pending_node_colors_[node_id] = color; + ProcessPendingNodeColor(node_id); + } + + private: + class Navigator : public InterfaceImpl<navigation::Navigator> { + public: + explicit Navigator(EmbeddedApp* app) : app_(app) {} + private: + virtual void Navigate( + uint32 node_id, + navigation::NavigationDetailsPtr navigation_details, + navigation::ResponseDetailsPtr response_details) OVERRIDE { + GURL url(navigation_details->url.To<std::string>()); + if (!url.is_valid()) { + LOG(ERROR) << "URL is invalid."; + return; + } + // TODO(aa): Verify new URL is same origin as current origin. + SkColor color = 0x00; + if (!base::HexStringToUInt(url.path().substr(1), &color)) { + LOG(ERROR) << "Invalid URL, path not convertible to integer"; + return; + } + app_->SetNodeColor(node_id, color); + } + EmbeddedApp* app_; + DISALLOW_COPY_AND_ASSIGN(Navigator); + }; + + // Overridden from Application: + virtual void Initialize() MOJO_OVERRIDE { + ViewManager::Create(this, this); + // TODO(aa): Weird for embeddee to talk to embedder by URL. Seems like + // embedder should be able to specify the SP embeddee receives, then + // communication can be anonymous. + ConnectTo<IWindowManager>("mojo:mojo_window_manager", &window_manager_); + AddService<Navigator>(this); + } + + // Overridden from ViewManagerDelegate: + virtual void OnRootAdded(ViewManager* view_manager, Node* root) OVERRIDE { + View* view = View::Create(view_manager); + view->AddObserver(this); + root->SetActiveView(view); + root->AddObserver(this); + + roots_[root->id()] = root; + ProcessPendingNodeColor(root->id()); + } + + // Overridden from ViewObserver: + virtual void OnViewInputEvent(View* view, const EventPtr& event) OVERRIDE { + if (event->action == ui::ET_MOUSE_RELEASED) + window_manager_->CloseWindow(view->node()->id()); + } + + // Overridden from NodeObserver: + virtual void OnNodeActiveViewChange( + Node* node, + View* old_view, + View* new_view, + NodeObserver::DispositionChangePhase phase) OVERRIDE { + if (new_view == 0) + views_to_reap_[node] = old_view; + } + virtual void OnNodeDestroy( + Node* node, + NodeObserver::DispositionChangePhase phase) OVERRIDE { + if (phase != NodeObserver::DISPOSITION_CHANGED) + return; + DCHECK(roots_.find(node->id()) != roots_.end()); + roots_.erase(node->id()); + std::map<Node*, View*>::const_iterator it = views_to_reap_.find(node); + if (it != views_to_reap_.end()) + it->second->Destroy(); + } + + void ProcessPendingNodeColor(uint32 node_id) { + RootMap::iterator root = roots_.find(node_id); + if (root == roots_.end()) + return; + + PendingNodeColors::iterator color = pending_node_colors_.find(node_id); + if (color == pending_node_colors_.end()) + return; + + root->second->active_view()->SetColor(color->second); + pending_node_colors_.erase(color); + } + + view_manager::ViewManager* view_manager_; + IWindowManagerPtr window_manager_; + std::map<Node*, View*> views_to_reap_; + + typedef std::map<view_manager::Id, Node*> RootMap; + RootMap roots_; + + // We can receive navigations for nodes we don't have yet. + typedef std::map<uint32, SkColor> PendingNodeColors; + PendingNodeColors pending_node_colors_; + + DISALLOW_COPY_AND_ASSIGN(EmbeddedApp); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::EmbeddedApp; +} + +} // namespace mojo diff --git a/chromium/mojo/examples/html_viewer/DEPS b/chromium/mojo/examples/html_viewer/DEPS new file mode 100644 index 00000000000..83af040ba4a --- /dev/null +++ b/chromium/mojo/examples/html_viewer/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+skia", + "+net/base", + "+third_party/skia/include", + "+third_party/WebKit/public", +] diff --git a/chromium/mojo/examples/html_viewer/blink_platform_impl.cc b/chromium/mojo/examples/html_viewer/blink_platform_impl.cc new file mode 100644 index 00000000000..3128d43e753 --- /dev/null +++ b/chromium/mojo/examples/html_viewer/blink_platform_impl.cc @@ -0,0 +1,213 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/html_viewer/blink_platform_impl.h" + +#include <cmath> + +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/time/time.h" +#include "mojo/examples/html_viewer/webthread_impl.h" +#include "mojo/examples/html_viewer/weburlloader_impl.h" +#include "mojo/public/cpp/application/application.h" +#include "net/base/data_url.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "third_party/WebKit/public/platform/WebWaitableEvent.h" + +namespace mojo { +namespace examples { +namespace { + +// TODO(darin): Figure out what our UA should really be. +const char kUserAgentString[] = + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/35.0.1916.153 Safari/537.36"; + +class WebWaitableEventImpl : public blink::WebWaitableEvent { + public: + WebWaitableEventImpl() : impl_(new base::WaitableEvent(false, false)) {} + virtual ~WebWaitableEventImpl() {} + + virtual void wait() { impl_->Wait(); } + virtual void signal() { impl_->Signal(); } + + base::WaitableEvent* impl() { + return impl_.get(); + } + + private: + scoped_ptr<base::WaitableEvent> impl_; + DISALLOW_COPY_AND_ASSIGN(WebWaitableEventImpl); +}; + +} // namespace + +BlinkPlatformImpl::BlinkPlatformImpl(Application* app) + : main_loop_(base::MessageLoop::current()), + shared_timer_func_(NULL), + shared_timer_fire_time_(0.0), + shared_timer_fire_time_was_set_while_suspended_(false), + shared_timer_suspended_(0), + current_thread_slot_(&DestroyCurrentThread) { + app->ConnectTo("mojo:mojo_network_service", &network_service_); +} + +BlinkPlatformImpl::~BlinkPlatformImpl() { +} + +blink::WebMimeRegistry* BlinkPlatformImpl::mimeRegistry() { + return &mime_registry_; +} + +blink::WebThemeEngine* BlinkPlatformImpl::themeEngine() { + return &dummy_theme_engine_; +} + +blink::WebString BlinkPlatformImpl::defaultLocale() { + return blink::WebString::fromUTF8("en-US"); +} + +double BlinkPlatformImpl::currentTime() { + return base::Time::Now().ToDoubleT(); +} + +double BlinkPlatformImpl::monotonicallyIncreasingTime() { + return base::TimeTicks::Now().ToInternalValue() / + static_cast<double>(base::Time::kMicrosecondsPerSecond); +} + +void BlinkPlatformImpl::cryptographicallyRandomValues(unsigned char* buffer, + size_t length) { + base::RandBytes(buffer, length); +} + +void BlinkPlatformImpl::setSharedTimerFiredFunction(void (*func)()) { + shared_timer_func_ = func; +} + +void BlinkPlatformImpl::setSharedTimerFireInterval( + double interval_seconds) { + shared_timer_fire_time_ = interval_seconds + monotonicallyIncreasingTime(); + if (shared_timer_suspended_) { + shared_timer_fire_time_was_set_while_suspended_ = true; + return; + } + + // By converting between double and int64 representation, we run the risk + // of losing precision due to rounding errors. Performing computations in + // microseconds reduces this risk somewhat. But there still is the potential + // of us computing a fire time for the timer that is shorter than what we + // need. + // As the event loop will check event deadlines prior to actually firing + // them, there is a risk of needlessly rescheduling events and of + // needlessly looping if sleep times are too short even by small amounts. + // This results in measurable performance degradation unless we use ceil() to + // always round up the sleep times. + int64 interval = static_cast<int64>( + ceil(interval_seconds * base::Time::kMillisecondsPerSecond) + * base::Time::kMicrosecondsPerMillisecond); + + if (interval < 0) + interval = 0; + + shared_timer_.Stop(); + shared_timer_.Start(FROM_HERE, base::TimeDelta::FromMicroseconds(interval), + this, &BlinkPlatformImpl::DoTimeout); +} + +void BlinkPlatformImpl::stopSharedTimer() { + shared_timer_.Stop(); +} + +void BlinkPlatformImpl::callOnMainThread( + void (*func)(void*), void* context) { + main_loop_->PostTask(FROM_HERE, base::Bind(func, context)); +} + +const unsigned char* BlinkPlatformImpl::getTraceCategoryEnabledFlag( + const char* category_name) { + static const unsigned char buf[] = "*"; + return buf; +} + +blink::WebURLLoader* BlinkPlatformImpl::createURLLoader() { + return new WebURLLoaderImpl(network_service_.get()); +} + +blink::WebString BlinkPlatformImpl::userAgent() { + return blink::WebString::fromUTF8(kUserAgentString); +} + +blink::WebData BlinkPlatformImpl::parseDataURL( + const blink::WebURL& url, + blink::WebString& mimetype_out, + blink::WebString& charset_out) { + std::string mimetype, charset, data; + if (net::DataURL::Parse(url, &mimetype, &charset, &data) + && net::IsSupportedMimeType(mimetype)) { + mimetype_out = blink::WebString::fromUTF8(mimetype); + charset_out = blink::WebString::fromUTF8(charset); + return data; + } + return blink::WebData(); +} + +blink::WebURLError BlinkPlatformImpl::cancelledError(const blink::WebURL& url) + const { + blink::WebURLError error; + error.domain = blink::WebString::fromUTF8(net::kErrorDomain); + error.reason = net::ERR_ABORTED; + error.unreachableURL = url; + error.staleCopyInCache = false; + error.isCancellation = true; + return error; +} + +blink::WebThread* BlinkPlatformImpl::createThread(const char* name) { + return new WebThreadImpl(name); +} + +blink::WebThread* BlinkPlatformImpl::currentThread() { + WebThreadImplForMessageLoop* thread = + static_cast<WebThreadImplForMessageLoop*>(current_thread_slot_.Get()); + if (thread) + return (thread); + + scoped_refptr<base::MessageLoopProxy> message_loop = + base::MessageLoopProxy::current(); + if (!message_loop.get()) + return NULL; + + thread = new WebThreadImplForMessageLoop(message_loop.get()); + current_thread_slot_.Set(thread); + return thread; +} + +blink::WebWaitableEvent* BlinkPlatformImpl::createWaitableEvent() { + return new WebWaitableEventImpl(); +} + +blink::WebWaitableEvent* BlinkPlatformImpl::waitMultipleEvents( + const blink::WebVector<blink::WebWaitableEvent*>& web_events) { + std::vector<base::WaitableEvent*> events; + for (size_t i = 0; i < web_events.size(); ++i) + events.push_back(static_cast<WebWaitableEventImpl*>(web_events[i])->impl()); + size_t idx = base::WaitableEvent::WaitMany( + vector_as_array(&events), events.size()); + DCHECK_LT(idx, web_events.size()); + return web_events[idx]; +} + +// static +void BlinkPlatformImpl::DestroyCurrentThread(void* thread) { + WebThreadImplForMessageLoop* impl = + static_cast<WebThreadImplForMessageLoop*>(thread); + delete impl; +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/html_viewer/blink_platform_impl.h b/chromium/mojo/examples/html_viewer/blink_platform_impl.h new file mode 100644 index 00000000000..dfe7744fede --- /dev/null +++ b/chromium/mojo/examples/html_viewer/blink_platform_impl.h @@ -0,0 +1,78 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLE_HTML_VIEWER_BLINK_PLATFORM_IMPL_H_ +#define MOJO_EXAMPLE_HTML_VIEWER_BLINK_PLATFORM_IMPL_H_ + +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_local_storage.h" +#include "base/timer/timer.h" +#include "mojo/examples/html_viewer/webmimeregistry_impl.h" +#include "mojo/services/public/interfaces/network/network_service.mojom.h" +#include "third_party/WebKit/public/platform/Platform.h" +#include "third_party/WebKit/public/platform/WebThemeEngine.h" + +namespace mojo { +class Application; + +namespace examples { + +class BlinkPlatformImpl : public blink::Platform { + public: + explicit BlinkPlatformImpl(Application* app); + virtual ~BlinkPlatformImpl(); + + // blink::Platform methods: + virtual blink::WebMimeRegistry* mimeRegistry(); + virtual blink::WebThemeEngine* themeEngine(); + virtual blink::WebString defaultLocale(); + virtual double currentTime(); + virtual double monotonicallyIncreasingTime(); + virtual void cryptographicallyRandomValues( + unsigned char* buffer, size_t length); + virtual void setSharedTimerFiredFunction(void (*func)()); + virtual void setSharedTimerFireInterval(double interval_seconds); + virtual void stopSharedTimer(); + virtual void callOnMainThread(void (*func)(void*), void* context); + virtual blink::WebURLLoader* createURLLoader(); + virtual blink::WebString userAgent(); + virtual blink::WebData parseDataURL( + const blink::WebURL& url, blink::WebString& mime_type, + blink::WebString& charset); + virtual blink::WebURLError cancelledError(const blink::WebURL& url) const; + virtual blink::WebThread* createThread(const char* name); + virtual blink::WebThread* currentThread(); + virtual blink::WebWaitableEvent* createWaitableEvent(); + virtual blink::WebWaitableEvent* waitMultipleEvents( + const blink::WebVector<blink::WebWaitableEvent*>& events); + virtual const unsigned char* getTraceCategoryEnabledFlag( + const char* category_name); + + private: + void SuspendSharedTimer(); + void ResumeSharedTimer(); + + void DoTimeout() { + if (shared_timer_func_ && !shared_timer_suspended_) + shared_timer_func_(); + } + + static void DestroyCurrentThread(void*); + + NetworkServicePtr network_service_; + base::MessageLoop* main_loop_; + base::OneShotTimer<BlinkPlatformImpl> shared_timer_; + void (*shared_timer_func_)(); + double shared_timer_fire_time_; + bool shared_timer_fire_time_was_set_while_suspended_; + int shared_timer_suspended_; // counter + base::ThreadLocalStorage::Slot current_thread_slot_; + blink::WebThemeEngine dummy_theme_engine_; + WebMimeRegistryImpl mime_registry_; +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLE_HTML_VIEWER_BLINK_PLATFORM_IMPL_H_ diff --git a/chromium/mojo/examples/html_viewer/html_document_view.cc b/chromium/mojo/examples/html_viewer/html_document_view.cc new file mode 100644 index 00000000000..868230c2d26 --- /dev/null +++ b/chromium/mojo/examples/html_viewer/html_document_view.cc @@ -0,0 +1,140 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/html_viewer/html_document_view.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "skia/ext/refptr.h" +#include "third_party/WebKit/public/web/WebConsoleMessage.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebElement.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" +#include "third_party/WebKit/public/web/WebSettings.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkDevice.h" + +namespace mojo { +namespace examples { +namespace { + +blink::WebData CopyToWebData(DataPipeConsumerHandle handle) { + std::vector<char> data; + for (;;) { + char buf[4096]; + uint32_t num_bytes = sizeof(buf); + MojoResult result = ReadDataRaw( + handle, + buf, + &num_bytes, + MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_SHOULD_WAIT) { + Wait(handle, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + } else if (result == MOJO_RESULT_OK) { + data.insert(data.end(), buf, buf + num_bytes); + } else { + break; + } + } + return blink::WebData(data); +} + +void ConfigureSettings(blink::WebSettings* settings) { + settings->setAcceleratedCompositingEnabled(false); + settings->setLoadsImagesAutomatically(true); + settings->setJavaScriptEnabled(true); +} + +} // namespace + +HTMLDocumentView::HTMLDocumentView(view_manager::ViewManager* view_manager) + : view_manager_(view_manager), + view_(view_manager::View::Create(view_manager_)), + web_view_(NULL), + repaint_pending_(false), + weak_factory_(this) { +} + +HTMLDocumentView::~HTMLDocumentView() { + if (web_view_) + web_view_->close(); +} + +void HTMLDocumentView::AttachToNode(view_manager::Node* node) { + node->SetActiveView(view_); + view_->SetColor(SK_ColorCYAN); // Dummy background color. + + web_view_ = blink::WebView::create(this); + ConfigureSettings(web_view_->settings()); + web_view_->setMainFrame(blink::WebLocalFrame::create(this)); + + // TODO(darin): Track size of view_manager::Node. + web_view_->resize(gfx::Size(800, 600)); +} + +void HTMLDocumentView::Load(URLResponsePtr response, + ScopedDataPipeConsumerHandle response_body_stream) { + DCHECK(web_view_); + + // TODO(darin): A better solution would be to use loadRequest, but intercept + // the network request and connect it to the response we already have. + blink::WebData data = CopyToWebData(response_body_stream.get()); + web_view_->mainFrame()->loadHTMLString( + data, GURL(response->url), GURL(response->url)); +} + +void HTMLDocumentView::didInvalidateRect(const blink::WebRect& rect) { + if (!repaint_pending_) { + repaint_pending_ = true; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&HTMLDocumentView::Repaint, weak_factory_.GetWeakPtr())); + } +} + +bool HTMLDocumentView::allowsBrokenNullLayerTreeView() const { + // TODO(darin): Switch to using compositor bindings. + // + // NOTE: Note to Blink maintainers, feel free to just break this code if it + // is the last using compositor bindings and you want to delete the old path. + // + return true; +} + +void HTMLDocumentView::didAddMessageToConsole( + const blink::WebConsoleMessage& message, + const blink::WebString& source_name, + unsigned source_line, + const blink::WebString& stack_trace) { + printf("### console: %s\n", std::string(message.text.utf8()).c_str()); +} + +void HTMLDocumentView::Repaint() { + repaint_pending_ = false; + + web_view_->animate(0.0); + web_view_->layout(); + + int width = web_view_->size().width; + int height = web_view_->size().height; + + skia::RefPtr<SkCanvas> canvas = skia::AdoptRef(SkCanvas::NewRaster( + SkImageInfo::MakeN32(width, height, kOpaque_SkAlphaType))); + + web_view_->paint(canvas.get(), gfx::Rect(0, 0, width, height)); + + view_->SetContents(canvas->getDevice()->accessBitmap(false)); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/html_viewer/html_document_view.h b/chromium/mojo/examples/html_viewer/html_document_view.h new file mode 100644 index 00000000000..9ad76e42dc4 --- /dev/null +++ b/chromium/mojo/examples/html_viewer/html_document_view.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_HTML_VIEWER_HTML_DOCUMENT_VIEW_H_ +#define MOJO_EXAMPLES_HTML_VIEWER_HTML_DOCUMENT_VIEW_H_ + +#include "base/memory/weak_ptr.h" +#include "mojo/services/public/interfaces/network/url_loader.mojom.h" +#include "third_party/WebKit/public/web/WebFrameClient.h" +#include "third_party/WebKit/public/web/WebViewClient.h" + +namespace mojo { + +namespace view_manager { +class Node; +class ViewManager; +class View; +} + +namespace examples { + +// A view for a single HTML document. +class HTMLDocumentView : public blink::WebViewClient, + public blink::WebFrameClient { + public: + explicit HTMLDocumentView(view_manager::ViewManager* view_manager); + virtual ~HTMLDocumentView(); + + void AttachToNode(view_manager::Node* node); + + void Load(URLResponsePtr response, + ScopedDataPipeConsumerHandle response_body_stream); + + private: + // WebWidgetClient methods: + virtual void didInvalidateRect(const blink::WebRect& rect); + virtual bool allowsBrokenNullLayerTreeView() const; + + // WebFrameClient methods: + virtual void didAddMessageToConsole( + const blink::WebConsoleMessage& message, + const blink::WebString& source_name, + unsigned source_line, + const blink::WebString& stack_trace); + + void Repaint(); + + view_manager::ViewManager* view_manager_; + view_manager::View* view_; + blink::WebView* web_view_; + bool repaint_pending_; + + base::WeakPtrFactory<HTMLDocumentView> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(HTMLDocumentView); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_HTML_VIEWER_HTML_DOCUMENT_VIEW_H_ diff --git a/chromium/mojo/examples/html_viewer/html_viewer.cc b/chromium/mojo/examples/html_viewer/html_viewer.cc new file mode 100644 index 00000000000..05bd7b5da3e --- /dev/null +++ b/chromium/mojo/examples/html_viewer/html_viewer.cc @@ -0,0 +1,105 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/html_viewer/blink_platform_impl.h" +#include "mojo/examples/html_viewer/html_document_view.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" +#include "third_party/WebKit/public/web/WebKit.h" + +namespace mojo { +namespace examples { + +class HTMLViewer; + +class NavigatorImpl : public InterfaceImpl<navigation::Navigator> { + public: + explicit NavigatorImpl(HTMLViewer* viewer) : viewer_(viewer) {} + virtual ~NavigatorImpl() {} + + private: + // Overridden from navigation::Navigator: + virtual void Navigate( + uint32_t node_id, + navigation::NavigationDetailsPtr navigation_details, + navigation::ResponseDetailsPtr response_details) OVERRIDE; + + HTMLViewer* viewer_; + + DISALLOW_COPY_AND_ASSIGN(NavigatorImpl); +}; + +class HTMLViewer : public Application, + public view_manager::ViewManagerDelegate { + public: + HTMLViewer() : document_view_(NULL) { + } + virtual ~HTMLViewer() { + blink::shutdown(); + } + + void Load(URLResponsePtr response, + ScopedDataPipeConsumerHandle response_body_stream) { + // Need to wait for OnRootAdded. + response_ = response.Pass(); + response_body_stream_ = response_body_stream.Pass(); + MaybeLoad(); + } + + private: + // Overridden from Application: + virtual void Initialize() OVERRIDE { + blink_platform_impl_.reset(new BlinkPlatformImpl(this)); + blink::initialize(blink_platform_impl_.get()); + + AddService<NavigatorImpl>(this); + view_manager::ViewManager::Create(this, this); + } + + // Overridden from view_manager::ViewManagerDelegate: + virtual void OnRootAdded(view_manager::ViewManager* view_manager, + view_manager::Node* root) OVERRIDE { + document_view_ = new HTMLDocumentView(view_manager); + document_view_->AttachToNode(root); + MaybeLoad(); + } + + void MaybeLoad() { + if (document_view_ && response_.get()) + document_view_->Load(response_.Pass(), response_body_stream_.Pass()); + } + + scoped_ptr<BlinkPlatformImpl> blink_platform_impl_; + + // TODO(darin): Figure out proper ownership of this instance. + HTMLDocumentView* document_view_; + URLResponsePtr response_; + ScopedDataPipeConsumerHandle response_body_stream_; + + DISALLOW_COPY_AND_ASSIGN(HTMLViewer); +}; + +void NavigatorImpl::Navigate( + uint32_t node_id, + navigation::NavigationDetailsPtr navigation_details, + navigation::ResponseDetailsPtr response_details) { + printf("In HTMLViewer, rendering url: %s\n", + response_details->response->url.data()); + viewer_->Load(response_details->response.Pass(), + response_details->response_body_stream.Pass()); +} + +} + +// static +Application* Application::Create() { + return new examples::HTMLViewer; +} + +} diff --git a/chromium/mojo/examples/html_viewer/webmimeregistry_impl.cc b/chromium/mojo/examples/html_viewer/webmimeregistry_impl.cc new file mode 100644 index 00000000000..4f4bbedb606 --- /dev/null +++ b/chromium/mojo/examples/html_viewer/webmimeregistry_impl.cc @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/html_viewer/webmimeregistry_impl.h" + +#include "base/files/file_path.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/mime_util.h" +#include "third_party/WebKit/public/platform/WebString.h" + +namespace mojo { +namespace examples { +namespace { + +std::string ToASCIIOrEmpty(const blink::WebString& string) { + return base::IsStringASCII(string) ? base::UTF16ToASCII(string) + : std::string(); +} + +} // namespace + +blink::WebMimeRegistry::SupportsType WebMimeRegistryImpl::supportsMIMEType( + const blink::WebString& mime_type) { + return net::IsSupportedMimeType(ToASCIIOrEmpty(mime_type)) ? + blink::WebMimeRegistry::IsSupported : + blink::WebMimeRegistry::IsNotSupported; +} + +blink::WebMimeRegistry::SupportsType WebMimeRegistryImpl::supportsImageMIMEType( + const blink::WebString& mime_type) { + return net::IsSupportedImageMimeType(ToASCIIOrEmpty(mime_type)) ? + blink::WebMimeRegistry::IsSupported : + blink::WebMimeRegistry::IsNotSupported; +} + +blink::WebMimeRegistry::SupportsType + WebMimeRegistryImpl::supportsJavaScriptMIMEType( + const blink::WebString& mime_type) { + return net::IsSupportedJavascriptMimeType(ToASCIIOrEmpty(mime_type)) ? + blink::WebMimeRegistry::IsSupported : + blink::WebMimeRegistry::IsNotSupported; +} + +blink::WebMimeRegistry::SupportsType WebMimeRegistryImpl::supportsMediaMIMEType( + const blink::WebString& mime_type, + const blink::WebString& codecs, + const blink::WebString& key_system) { + NOTIMPLEMENTED(); + return IsNotSupported; +} + +bool WebMimeRegistryImpl::supportsMediaSourceMIMEType( + const blink::WebString& mime_type, + const blink::WebString& codecs) { + NOTIMPLEMENTED(); + return false; +} + +bool WebMimeRegistryImpl::supportsEncryptedMediaMIMEType( + const blink::WebString& key_system, + const blink::WebString& mime_type, + const blink::WebString& codecs) { + NOTIMPLEMENTED(); + return false; +} + +blink::WebMimeRegistry::SupportsType + WebMimeRegistryImpl::supportsNonImageMIMEType( + const blink::WebString& mime_type) { + return net::IsSupportedNonImageMimeType(ToASCIIOrEmpty(mime_type)) ? + blink::WebMimeRegistry::IsSupported : + blink::WebMimeRegistry::IsNotSupported; +} + +blink::WebString WebMimeRegistryImpl::mimeTypeForExtension( + const blink::WebString& file_extension) { + NOTIMPLEMENTED(); + return blink::WebString(); +} + +blink::WebString WebMimeRegistryImpl::wellKnownMimeTypeForExtension( + const blink::WebString& file_extension) { + NOTIMPLEMENTED(); + return blink::WebString(); +} + +blink::WebString WebMimeRegistryImpl::mimeTypeFromFile( + const blink::WebString& file_path) { + NOTIMPLEMENTED(); + return blink::WebString(); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/html_viewer/webmimeregistry_impl.h b/chromium/mojo/examples/html_viewer/webmimeregistry_impl.h new file mode 100644 index 00000000000..ec7b577961e --- /dev/null +++ b/chromium/mojo/examples/html_viewer/webmimeregistry_impl.h @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_HTML_VIEWER_WEBMIMEREGISTRY_IMPL_H_ +#define MOJO_EXAMPLES_HTML_VIEWER_WEBMIMEREGISTRY_IMPL_H_ + +#include "base/compiler_specific.h" +#include "third_party/WebKit/public/platform/WebMimeRegistry.h" + +namespace mojo { +namespace examples { + +class WebMimeRegistryImpl : public blink::WebMimeRegistry { + public: + WebMimeRegistryImpl() {} + virtual ~WebMimeRegistryImpl() {} + + // WebMimeRegistry methods: + virtual blink::WebMimeRegistry::SupportsType supportsMIMEType( + const blink::WebString& mime_type); + virtual blink::WebMimeRegistry::SupportsType supportsImageMIMEType( + const blink::WebString& mime_type); + virtual blink::WebMimeRegistry::SupportsType supportsJavaScriptMIMEType( + const blink::WebString& mime_type); + virtual blink::WebMimeRegistry::SupportsType supportsMediaMIMEType( + const blink::WebString& mime_type, + const blink::WebString& codecs, + const blink::WebString& key_system); + virtual bool supportsMediaSourceMIMEType( + const blink::WebString& mime_type, + const blink::WebString& codecs); + virtual bool supportsEncryptedMediaMIMEType( + const blink::WebString& key_system, + const blink::WebString& mime_type, + const blink::WebString& codecs); + virtual blink::WebMimeRegistry::SupportsType supportsNonImageMIMEType( + const blink::WebString& mime_type); + virtual blink::WebString mimeTypeForExtension( + const blink::WebString& extension); + virtual blink::WebString wellKnownMimeTypeForExtension( + const blink::WebString& extension); + virtual blink::WebString mimeTypeFromFile( + const blink::WebString& path); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_HTML_VIEWER_WEBMIMEREGISTRY_IMPL_H_ diff --git a/chromium/mojo/examples/html_viewer/webthread_impl.cc b/chromium/mojo/examples/html_viewer/webthread_impl.cc new file mode 100644 index 00000000000..1173a257ea2 --- /dev/null +++ b/chromium/mojo/examples/html_viewer/webthread_impl.cc @@ -0,0 +1,133 @@ +// 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. + +// An implementation of WebThread in terms of base::MessageLoop and +// base::Thread + +#include "mojo/examples/html_viewer/webthread_impl.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/pending_task.h" +#include "base/threading/platform_thread.h" + +namespace mojo { +namespace examples { + +WebThreadBase::WebThreadBase() {} +WebThreadBase::~WebThreadBase() {} + +class WebThreadBase::TaskObserverAdapter + : public base::MessageLoop::TaskObserver { + public: + TaskObserverAdapter(WebThread::TaskObserver* observer) + : observer_(observer) {} + + virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE { + observer_->willProcessTask(); + } + + virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE { + observer_->didProcessTask(); + } + +private: + WebThread::TaskObserver* observer_; +}; + +void WebThreadBase::addTaskObserver(TaskObserver* observer) { + CHECK(isCurrentThread()); + std::pair<TaskObserverMap::iterator, bool> result = task_observer_map_.insert( + std::make_pair(observer, static_cast<TaskObserverAdapter*>(NULL))); + if (result.second) + result.first->second = new TaskObserverAdapter(observer); + base::MessageLoop::current()->AddTaskObserver(result.first->second); +} + +void WebThreadBase::removeTaskObserver(TaskObserver* observer) { + CHECK(isCurrentThread()); + TaskObserverMap::iterator iter = task_observer_map_.find(observer); + if (iter == task_observer_map_.end()) + return; + base::MessageLoop::current()->RemoveTaskObserver(iter->second); + delete iter->second; + task_observer_map_.erase(iter); +} + +WebThreadImpl::WebThreadImpl(const char* name) + : thread_(new base::Thread(name)) { + thread_->Start(); +} + +void WebThreadImpl::postTask(Task* task) { + thread_->message_loop()->PostTask( + FROM_HERE, base::Bind(&blink::WebThread::Task::run, base::Owned(task))); +} + +void WebThreadImpl::postDelayedTask(Task* task, long long delay_ms) { + thread_->message_loop()->PostDelayedTask( + FROM_HERE, + base::Bind(&blink::WebThread::Task::run, base::Owned(task)), + base::TimeDelta::FromMilliseconds(delay_ms)); +} + +void WebThreadImpl::enterRunLoop() { + CHECK(isCurrentThread()); + CHECK(!thread_->message_loop()->is_running()); // We don't support nesting. + thread_->message_loop()->Run(); +} + +void WebThreadImpl::exitRunLoop() { + CHECK(isCurrentThread()); + CHECK(thread_->message_loop()->is_running()); + thread_->message_loop()->Quit(); +} + +bool WebThreadImpl::isCurrentThread() const { + return thread_->thread_id() == base::PlatformThread::CurrentId(); +} + +WebThreadImpl::~WebThreadImpl() { + thread_->Stop(); +} + +WebThreadImplForMessageLoop::WebThreadImplForMessageLoop( + base::MessageLoopProxy* message_loop) + : message_loop_(message_loop) {} + +void WebThreadImplForMessageLoop::postTask(Task* task) { + message_loop_->PostTask( + FROM_HERE, base::Bind(&blink::WebThread::Task::run, base::Owned(task))); +} + +void WebThreadImplForMessageLoop::postDelayedTask(Task* task, + long long delay_ms) { + message_loop_->PostDelayedTask( + FROM_HERE, + base::Bind(&blink::WebThread::Task::run, base::Owned(task)), + base::TimeDelta::FromMilliseconds(delay_ms)); +} + +void WebThreadImplForMessageLoop::enterRunLoop() { + CHECK(isCurrentThread()); + // We don't support nesting. + CHECK(!base::MessageLoop::current()->is_running()); + base::MessageLoop::current()->Run(); +} + +void WebThreadImplForMessageLoop::exitRunLoop() { + CHECK(isCurrentThread()); + CHECK(base::MessageLoop::current()->is_running()); + base::MessageLoop::current()->Quit(); +} + +bool WebThreadImplForMessageLoop::isCurrentThread() const { + return message_loop_->BelongsToCurrentThread(); +} + +WebThreadImplForMessageLoop::~WebThreadImplForMessageLoop() {} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/html_viewer/webthread_impl.h b/chromium/mojo/examples/html_viewer/webthread_impl.h new file mode 100644 index 00000000000..ea068ccba8a --- /dev/null +++ b/chromium/mojo/examples/html_viewer/webthread_impl.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_HTML_VIEWER_WEBTHREAD_IMPL_H_ +#define MOJO_EXAMPLES_HTML_VIEWER_WEBTHREAD_IMPL_H_ + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "third_party/WebKit/public/platform/WebThread.h" + +namespace mojo { +namespace examples { + +class WebThreadBase : public blink::WebThread { + public: + virtual ~WebThreadBase(); + + virtual void addTaskObserver(TaskObserver* observer); + virtual void removeTaskObserver(TaskObserver* observer); + + virtual bool isCurrentThread() const = 0; + + protected: + WebThreadBase(); + + private: + class TaskObserverAdapter; + + typedef std::map<TaskObserver*, TaskObserverAdapter*> TaskObserverMap; + TaskObserverMap task_observer_map_; +}; + +class WebThreadImpl : public WebThreadBase { + public: + explicit WebThreadImpl(const char* name); + virtual ~WebThreadImpl(); + + virtual void postTask(Task* task); + virtual void postDelayedTask(Task* task, long long delay_ms); + + virtual void enterRunLoop(); + virtual void exitRunLoop(); + + base::MessageLoop* message_loop() const { return thread_->message_loop(); } + + virtual bool isCurrentThread() const; + + private: + scoped_ptr<base::Thread> thread_; +}; + +class WebThreadImplForMessageLoop : public WebThreadBase { + public: + explicit WebThreadImplForMessageLoop( + base::MessageLoopProxy* message_loop); + virtual ~WebThreadImplForMessageLoop(); + + virtual void postTask(Task* task); + virtual void postDelayedTask(Task* task, long long delay_ms); + + virtual void enterRunLoop(); + virtual void exitRunLoop(); + + private: + virtual bool isCurrentThread() const; + scoped_refptr<base::MessageLoopProxy> message_loop_; +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_HTML_VIEWER_WEBTHREAD_IMPL_H_ diff --git a/chromium/mojo/examples/html_viewer/weburlloader_impl.cc b/chromium/mojo/examples/html_viewer/weburlloader_impl.cc new file mode 100644 index 00000000000..fa57e911db7 --- /dev/null +++ b/chromium/mojo/examples/html_viewer/weburlloader_impl.cc @@ -0,0 +1,139 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/html_viewer/weburlloader_impl.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "mojo/services/public/interfaces/network/network_service.mojom.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLLoaderClient.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" + +namespace mojo { +namespace examples { +namespace { + +blink::WebURLResponse ToWebURLResponse(const URLResponsePtr& url_response) { + blink::WebURLResponse result; + result.initialize(); + result.setURL(GURL(url_response->url)); + // TODO(darin): Copy other fields. + return result; +} + +} // namespace + +WebURLLoaderImpl::WebURLLoaderImpl(NetworkService* network_service) + : client_(NULL), + weak_factory_(this) { + network_service->CreateURLLoader(Get(&url_loader_)); + url_loader_.set_client(this); +} + +WebURLLoaderImpl::~WebURLLoaderImpl() { +} + +void WebURLLoaderImpl::loadSynchronously( + const blink::WebURLRequest& request, + blink::WebURLResponse& response, + blink::WebURLError& error, + blink::WebData& data) { + NOTIMPLEMENTED(); +} + +void WebURLLoaderImpl::loadAsynchronously(const blink::WebURLRequest& request, + blink::WebURLLoaderClient* client) { + client_ = client; + + URLRequestPtr url_request(URLRequest::New()); + url_request->url = request.url().spec(); + url_request->auto_follow_redirects = false; + // TODO(darin): Copy other fields. + + DataPipe pipe; + url_loader_->Start(url_request.Pass(), pipe.producer_handle.Pass()); + response_body_stream_ = pipe.consumer_handle.Pass(); +} + +void WebURLLoaderImpl::cancel() { + url_loader_.reset(); + response_body_stream_.reset(); + // TODO(darin): Need to asynchronously call didFail. +} + +void WebURLLoaderImpl::setDefersLoading(bool defers_loading) { + NOTIMPLEMENTED(); +} + +void WebURLLoaderImpl::OnReceivedRedirect(URLResponsePtr url_response, + const String& new_url, + const String& new_method) { + blink::WebURLRequest new_request; + new_request.initialize(); + new_request.setURL(GURL(new_url)); + + client_->willSendRequest(this, new_request, ToWebURLResponse(url_response)); + // TODO(darin): Check if new_request was rejected. + + url_loader_->FollowRedirect(); +} + +void WebURLLoaderImpl::OnReceivedResponse(URLResponsePtr url_response) { + client_->didReceiveResponse(this, ToWebURLResponse(url_response)); + + // Start streaming data + ReadMore(); +} + +void WebURLLoaderImpl::OnReceivedError(NetworkErrorPtr error) { + // TODO(darin): Construct a meaningful WebURLError. + client_->didFail(this, blink::WebURLError()); +} + +void WebURLLoaderImpl::OnReceivedEndOfResponseBody() { + // This is the signal that the response body was not truncated. +} + +void WebURLLoaderImpl::ReadMore() { + const void* buf; + uint32_t buf_size; + MojoResult rv = BeginReadDataRaw(response_body_stream_.get(), + &buf, + &buf_size, + MOJO_READ_DATA_FLAG_NONE); + if (rv == MOJO_RESULT_OK) { + client_->didReceiveData(this, static_cast<const char*>(buf), buf_size, -1); + EndReadDataRaw(response_body_stream_.get(), buf_size); + WaitToReadMore(); + } else if (rv == MOJO_RESULT_SHOULD_WAIT) { + WaitToReadMore(); + } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + // We reached end-of-file. + double finish_time = base::Time::Now().ToDoubleT(); + client_->didFinishLoading( + this, + finish_time, + blink::WebURLLoaderClient::kUnknownEncodedDataLength); + } else { + // TODO(darin): Oops! + } +} + +void WebURLLoaderImpl::WaitToReadMore() { + handle_watcher_.Start( + response_body_stream_.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + base::Bind(&WebURLLoaderImpl::OnResponseBodyStreamReady, + weak_factory_.GetWeakPtr())); +} + +void WebURLLoaderImpl::OnResponseBodyStreamReady(MojoResult result) { + ReadMore(); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/html_viewer/weburlloader_impl.h b/chromium/mojo/examples/html_viewer/weburlloader_impl.h new file mode 100644 index 00000000000..3d2a570aa3c --- /dev/null +++ b/chromium/mojo/examples/html_viewer/weburlloader_impl.h @@ -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. + +#ifndef MOJO_EXAMPLE_HTML_VIEWER_WEBURLLOADER_IMPL_H_ +#define MOJO_EXAMPLE_HTML_VIEWER_WEBURLLOADER_IMPL_H_ + +#include "base/memory/weak_ptr.h" +#include "mojo/common/handle_watcher.h" +#include "mojo/services/public/interfaces/network/url_loader.mojom.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" + +namespace mojo { +class NetworkService; + +namespace examples { + +class WebURLLoaderImpl : public blink::WebURLLoader, + public URLLoaderClient { + public: + explicit WebURLLoaderImpl(NetworkService* network_service); + + private: + virtual ~WebURLLoaderImpl(); + + // blink::WebURLLoader methods: + virtual void loadSynchronously( + const blink::WebURLRequest& request, blink::WebURLResponse& response, + blink::WebURLError& error, blink::WebData& data) OVERRIDE; + virtual void loadAsynchronously( + const blink::WebURLRequest&, blink::WebURLLoaderClient* client) OVERRIDE; + virtual void cancel() OVERRIDE; + virtual void setDefersLoading(bool defers_loading) OVERRIDE; + + // URLLoaderClient methods: + virtual void OnReceivedRedirect(URLResponsePtr response, + const String& new_url, + const String& new_method) OVERRIDE; + virtual void OnReceivedResponse(URLResponsePtr response) OVERRIDE; + virtual void OnReceivedError(NetworkErrorPtr error) OVERRIDE; + virtual void OnReceivedEndOfResponseBody() OVERRIDE; + + void ReadMore(); + void WaitToReadMore(); + void OnResponseBodyStreamReady(MojoResult result); + + URLLoaderPtr url_loader_; + blink::WebURLLoaderClient* client_; + ScopedDataPipeConsumerHandle response_body_stream_; + common::HandleWatcher handle_watcher_; + + base::WeakPtrFactory<WebURLLoaderImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WebURLLoaderImpl); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLE_HTML_VIEWER_WEBURLLOADER_IMPL_H_ diff --git a/chromium/mojo/examples/image_viewer/DEPS b/chromium/mojo/examples/image_viewer/DEPS new file mode 100644 index 00000000000..41134f52feb --- /dev/null +++ b/chromium/mojo/examples/image_viewer/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+third_party/skia/include", + "+ui/gfx", +] diff --git a/chromium/mojo/examples/image_viewer/image_viewer.cc b/chromium/mojo/examples/image_viewer/image_viewer.cc new file mode 100644 index 00000000000..4eb987c1ec5 --- /dev/null +++ b/chromium/mojo/examples/image_viewer/image_viewer.cc @@ -0,0 +1,138 @@ +// 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 <algorithm> + +#include "base/strings/string_tokenizer.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/png_codec.h" + +namespace mojo { +namespace examples { + +class ImageViewer; + +class NavigatorImpl : public InterfaceImpl<navigation::Navigator> { + public: + explicit NavigatorImpl(ImageViewer* viewer) : viewer_(viewer) {} + virtual ~NavigatorImpl() {} + + private: + // Overridden from navigation::Navigate: + virtual void Navigate( + uint32_t node_id, + navigation::NavigationDetailsPtr navigation_details, + navigation::ResponseDetailsPtr response_details) OVERRIDE { + int content_length = GetContentLength(response_details->response->headers); + unsigned char* data = new unsigned char[content_length]; + unsigned char* buf = data; + uint32_t bytes_remaining = content_length; + uint32_t num_bytes = bytes_remaining; + while (bytes_remaining > 0) { + MojoResult result = ReadDataRaw( + response_details->response_body_stream.get(), + buf, + &num_bytes, + MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_SHOULD_WAIT) { + Wait(response_details->response_body_stream.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + } else if (result == MOJO_RESULT_OK) { + buf += num_bytes; + num_bytes = bytes_remaining -= num_bytes; + } else { + break; + } + } + + SkBitmap bitmap; + gfx::PNGCodec::Decode(static_cast<const unsigned char*>(data), + content_length, &bitmap); + UpdateView(node_id, bitmap); + + delete[] data; + } + + void UpdateView(view_manager::Id node_id, const SkBitmap& bitmap); + + int GetContentLength(const Array<String>& headers) { + for (size_t i = 0; i < headers.size(); ++i) { + base::StringTokenizer t(headers[i], ": ;="); + while (t.GetNext()) { + if (!t.token_is_delim() && t.token() == "Content-Length") { + while (t.GetNext()) { + if (!t.token_is_delim()) + return atoi(t.token().c_str()); + } + } + } + } + return 0; + } + + ImageViewer* viewer_; + + DISALLOW_COPY_AND_ASSIGN(NavigatorImpl); +}; + +class ImageViewer : public Application, + public view_manager::ViewManagerDelegate { + public: + ImageViewer() : content_view_(NULL) {} + virtual ~ImageViewer() {} + + void UpdateView(view_manager::Id node_id, const SkBitmap& bitmap) { + bitmap_ = bitmap; + DrawBitmap(); + } + + private: + // Overridden from Application: + virtual void Initialize() OVERRIDE { + AddService<NavigatorImpl>(this); + view_manager::ViewManager::Create(this, this); + } + + // Overridden from view_manager::ViewManagerDelegate: + virtual void OnRootAdded(view_manager::ViewManager* view_manager, + view_manager::Node* root) OVERRIDE { + content_view_ = view_manager::View::Create(view_manager); + root->SetActiveView(content_view_); + content_view_->SetColor(SK_ColorRED); + if (!bitmap_.isNull()) + DrawBitmap(); + } + + void DrawBitmap() { + if (content_view_) + content_view_->SetContents(bitmap_); + } + + view_manager::View* content_view_; + SkBitmap bitmap_; + + DISALLOW_COPY_AND_ASSIGN(ImageViewer); +}; + +void NavigatorImpl::UpdateView(view_manager::Id node_id, + const SkBitmap& bitmap) { + viewer_->UpdateView(node_id, bitmap); +} + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::ImageViewer; +} + +} // namespace mojo diff --git a/chromium/mojo/examples/nesting_app/DEPS b/chromium/mojo/examples/nesting_app/DEPS new file mode 100644 index 00000000000..fe1d98e366d --- /dev/null +++ b/chromium/mojo/examples/nesting_app/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/events", +] diff --git a/chromium/mojo/examples/nesting_app/nesting_app.cc b/chromium/mojo/examples/nesting_app/nesting_app.cc new file mode 100644 index 00000000000..7eccc726f30 --- /dev/null +++ b/chromium/mojo/examples/nesting_app/nesting_app.cc @@ -0,0 +1,136 @@ +// 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/basictypes.h" +#include "base/bind.h" +#include "base/strings/stringprintf.h" +#include "mojo/examples/window_manager/window_manager.mojom.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/node_observer.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" +#include "ui/events/event_constants.h" +#include "url/gurl.h" + +using mojo::view_manager::Node; +using mojo::view_manager::NodeObserver; +using mojo::view_manager::View; +using mojo::view_manager::ViewManager; +using mojo::view_manager::ViewManagerDelegate; +using mojo::view_manager::ViewObserver; + +namespace mojo { +namespace examples { + +namespace { +const char kEmbeddedAppURL[] = "mojo:mojo_embedded_app"; +} + +// An app that embeds another app. +class NestingApp : public Application, + public ViewManagerDelegate, + public ViewObserver, + public NodeObserver { + public: + NestingApp() : nested_(NULL) {} + virtual ~NestingApp() {} + + private: + class Navigator : public InterfaceImpl<navigation::Navigator> { + public: + explicit Navigator(NestingApp* app) : app_(app) {} + private: + virtual void Navigate( + uint32 node_id, + navigation::NavigationDetailsPtr navigation_details, + navigation::ResponseDetailsPtr response_details) OVERRIDE { + GURL url(navigation_details->url.To<std::string>()); + if (!url.is_valid()) { + LOG(ERROR) << "URL is invalid."; + return; + } + app_->color_ = url.path().substr(1); + app_->NavigateChild(); + } + NestingApp* app_; + DISALLOW_COPY_AND_ASSIGN(Navigator); + }; + + // Overridden from Application: + virtual void Initialize() MOJO_OVERRIDE { + ViewManager::Create(this, this); + ConnectTo<IWindowManager>("mojo:mojo_window_manager", &window_manager_); + AddService<Navigator>(this); + } + + // Overridden from ViewManagerDelegate: + virtual void OnRootAdded(ViewManager* view_manager, Node* root) OVERRIDE { + root->AddObserver(this); + + View* view = View::Create(view_manager); + root->SetActiveView(view); + view->SetColor(SK_ColorCYAN); + view->AddObserver(this); + + nested_ = Node::Create(view_manager); + root->AddChild(nested_); + nested_->SetBounds(gfx::Rect(20, 20, 50, 50)); + nested_->Embed(kEmbeddedAppURL); + + if (!navigator_.get()) + ConnectTo(kEmbeddedAppURL, &navigator_); + + NavigateChild(); + } + + // Overridden from ViewObserver: + virtual void OnViewInputEvent(View* view, const EventPtr& event) OVERRIDE { + if (event->action == ui::ET_MOUSE_RELEASED) + window_manager_->CloseWindow(view->node()->id()); + } + + // Overridden from NodeObserver: + virtual void OnNodeDestroy( + Node* node, + NodeObserver::DispositionChangePhase phase) OVERRIDE { + if (phase != NodeObserver::DISPOSITION_CHANGED) + return; + // TODO(beng): reap views & child nodes. + nested_ = NULL; + } + + void NavigateChild() { + if (!color_.empty() && nested_) { + navigation::NavigationDetailsPtr details( + navigation::NavigationDetails::New()); + details->url = + base::StringPrintf("%s/%s", kEmbeddedAppURL, color_.c_str()); + navigation::ResponseDetailsPtr response_details( + navigation::ResponseDetails::New()); + navigator_->Navigate(nested_->id(), + details.Pass(), + response_details.Pass()); + } + } + + std::string color_; + Node* nested_; + navigation::NavigatorPtr navigator_; + IWindowManagerPtr window_manager_; + + DISALLOW_COPY_AND_ASSIGN(NestingApp); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::NestingApp; +} + +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/DEPS b/chromium/mojo/examples/pepper_container_app/DEPS new file mode 100644 index 00000000000..b1e8ef3dd81 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+base", + "+ppapi/c", + "+ppapi/shared_impl", + "+ppapi/thunk", +] diff --git a/chromium/mojo/examples/pepper_container_app/OWNERS b/chromium/mojo/examples/pepper_container_app/OWNERS new file mode 100644 index 00000000000..30159636d2f --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/OWNERS @@ -0,0 +1,6 @@ +bbudge@chromium.org +brettw@chromium.org +dmichael@chromium.org +raymes@chromium.org +teravest@chromium.org +yzshen@chromium.org diff --git a/chromium/mojo/examples/pepper_container_app/graphics_3d_resource.cc b/chromium/mojo/examples/pepper_container_app/graphics_3d_resource.cc new file mode 100644 index 00000000000..2ad7dcd4591 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/graphics_3d_resource.cc @@ -0,0 +1,160 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/pepper_container_app/graphics_3d_resource.h" + +#include "base/logging.h" +#include "mojo/examples/pepper_container_app/mojo_ppapi_globals.h" +#include "mojo/examples/pepper_container_app/plugin_instance.h" +#include "mojo/public/c/gles2/gles2.h" +#include "ppapi/c/pp_errors.h" + +namespace mojo { +namespace examples { + +namespace { + +gpu::CommandBuffer::State GetErrorState() { + gpu::CommandBuffer::State error_state; + error_state.error = gpu::error::kGenericError; + return error_state; +} + +} // namespace + +Graphics3DResource::Graphics3DResource(PP_Instance instance) + : Resource(ppapi::OBJECT_IS_IMPL, instance) { + ScopedMessagePipeHandle pipe = MojoPpapiGlobals::Get()->CreateGLES2Context(); + context_ = MojoGLES2CreateContext(pipe.release().value(), + &ContextLostThunk, + &DrawAnimationFrameThunk, + this); +} + +bool Graphics3DResource::IsBoundGraphics() const { + PluginInstance* plugin_instance = + MojoPpapiGlobals::Get()->GetInstance(pp_instance()); + return plugin_instance && plugin_instance->IsBoundGraphics(pp_resource()); +} + +void Graphics3DResource::BindGraphics() { + MojoGLES2MakeCurrent(context_); +} + +ppapi::thunk::PPB_Graphics3D_API* Graphics3DResource::AsPPB_Graphics3D_API() { + return this; +} + +int32_t Graphics3DResource::GetAttribs(int32_t attrib_list[]) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +int32_t Graphics3DResource::SetAttribs(const int32_t attrib_list[]) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +int32_t Graphics3DResource::GetError() { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +int32_t Graphics3DResource::ResizeBuffers(int32_t width, int32_t height) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +int32_t Graphics3DResource::SwapBuffers( + scoped_refptr<ppapi::TrackedCallback> callback) { + if (!IsBoundGraphics()) + return PP_ERROR_FAILED; + + MojoGLES2SwapBuffers(); + return PP_OK; +} + +int32_t Graphics3DResource::GetAttribMaxValue(int32_t attribute, + int32_t* value) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +PP_Bool Graphics3DResource::SetGetBuffer(int32_t shm_id) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +scoped_refptr<gpu::Buffer> Graphics3DResource::CreateTransferBuffer( + uint32_t size, + int32* id) { + NOTIMPLEMENTED(); + return NULL; +} + +PP_Bool Graphics3DResource::DestroyTransferBuffer(int32_t id) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +PP_Bool Graphics3DResource::Flush(int32_t put_offset) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +gpu::CommandBuffer::State Graphics3DResource::WaitForTokenInRange(int32_t start, + int32_t end) { + NOTIMPLEMENTED(); + return GetErrorState(); +} + +gpu::CommandBuffer::State Graphics3DResource::WaitForGetOffsetInRange( + int32_t start, int32_t end) { + NOTIMPLEMENTED(); + return GetErrorState(); +} + +void* Graphics3DResource::MapTexSubImage2DCHROMIUM(GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLsizei width, + GLsizei height, + GLenum format, + GLenum type, + GLenum access) { + NOTIMPLEMENTED(); + return NULL; +} + +void Graphics3DResource::UnmapTexSubImage2DCHROMIUM(const void* mem) { + NOTIMPLEMENTED(); +} + +uint32_t Graphics3DResource::InsertSyncPoint() { + NOTIMPLEMENTED(); + return 0; +} + +Graphics3DResource::~Graphics3DResource() { + MojoGLES2DestroyContext(context_); +} + +void Graphics3DResource::ContextLostThunk(void* closure) { + static_cast<Graphics3DResource*>(closure)->ContextLost(); +} + +void Graphics3DResource::DrawAnimationFrameThunk(void* closure) { + // TODO(yzshen): Use this notification to drive the SwapBuffers() callback. +} + +void Graphics3DResource::ContextLost() { + PluginInstance* plugin_instance = + MojoPpapiGlobals::Get()->GetInstance(pp_instance()); + if (plugin_instance) + plugin_instance->Graphics3DContextLost(); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/graphics_3d_resource.h b/chromium/mojo/examples/pepper_container_app/graphics_3d_resource.h new file mode 100644 index 00000000000..5dba92cc144 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/graphics_3d_resource.h @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_GRAPHICS_3D_RESOURCE_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_GRAPHICS_3D_RESOURCE_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/public/c/gles2/gles2_types.h" +#include "ppapi/shared_impl/resource.h" +#include "ppapi/shared_impl/tracked_callback.h" +#include "ppapi/thunk/ppb_graphics_3d_api.h" + +namespace mojo { +namespace examples { + +class Graphics3DResource : public ppapi::Resource, + public ppapi::thunk::PPB_Graphics3D_API { + public: + explicit Graphics3DResource(PP_Instance instance); + + bool IsBoundGraphics() const; + void BindGraphics(); + + // ppapi::Resource overrides. + virtual ppapi::thunk::PPB_Graphics3D_API* AsPPB_Graphics3D_API() OVERRIDE; + + // ppapi::thunk::PPB_Graphics3D_API implementation. + virtual int32_t GetAttribs(int32_t attrib_list[]) OVERRIDE; + virtual int32_t SetAttribs(const int32_t attrib_list[]) OVERRIDE; + virtual int32_t GetError() OVERRIDE; + virtual int32_t ResizeBuffers(int32_t width, int32_t height) OVERRIDE; + virtual int32_t SwapBuffers( + scoped_refptr<ppapi::TrackedCallback> callback) OVERRIDE; + virtual int32_t GetAttribMaxValue(int32_t attribute, int32_t* value) OVERRIDE; + virtual PP_Bool SetGetBuffer(int32_t shm_id) OVERRIDE; + virtual scoped_refptr<gpu::Buffer> CreateTransferBuffer(uint32_t size, + int32* id) OVERRIDE; + virtual PP_Bool DestroyTransferBuffer(int32_t id) OVERRIDE; + virtual PP_Bool Flush(int32_t put_offset) OVERRIDE; + virtual gpu::CommandBuffer::State WaitForTokenInRange(int32_t start, + int32_t end) OVERRIDE; + virtual gpu::CommandBuffer::State WaitForGetOffsetInRange( + int32_t start, int32_t end) OVERRIDE; + virtual void* MapTexSubImage2DCHROMIUM(GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLsizei width, + GLsizei height, + GLenum format, + GLenum type, + GLenum access) OVERRIDE; + virtual void UnmapTexSubImage2DCHROMIUM(const void* mem) OVERRIDE; + virtual uint32_t InsertSyncPoint() OVERRIDE; + + private: + virtual ~Graphics3DResource(); + + static void ContextLostThunk(void* closure); + static void DrawAnimationFrameThunk(void* closure); + void ContextLost(); + + MojoGLES2Context context_; + DISALLOW_COPY_AND_ASSIGN(Graphics3DResource); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_GRAPHICS_3D_RESOURCE_H_ diff --git a/chromium/mojo/examples/pepper_container_app/interface_list.cc b/chromium/mojo/examples/pepper_container_app/interface_list.cc new file mode 100644 index 00000000000..7f4d2ea613a --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/interface_list.cc @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/pepper_container_app/interface_list.h" + +#include "base/memory/singleton.h" +#include "mojo/examples/pepper_container_app/thunk.h" +#include "ppapi/c/ppb_core.h" +#include "ppapi/c/ppb_graphics_3d.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_opengles2.h" +#include "ppapi/c/ppb_view.h" +#include "ppapi/thunk/thunk.h" + +namespace mojo { +namespace examples { + +InterfaceList::InterfaceList() { + browser_interfaces_[PPB_CORE_INTERFACE_1_0] = GetPPB_Core_1_0_Thunk(); + browser_interfaces_[PPB_GRAPHICS_3D_INTERFACE_1_0] = + ppapi::thunk::GetPPB_Graphics3D_1_0_Thunk(); + browser_interfaces_[PPB_OPENGLES2_INTERFACE_1_0] = + GetPPB_OpenGLES2_Thunk(); + browser_interfaces_[PPB_INSTANCE_INTERFACE_1_0] = + ppapi::thunk::GetPPB_Instance_1_0_Thunk(); + browser_interfaces_[PPB_VIEW_INTERFACE_1_0] = + ppapi::thunk::GetPPB_View_1_0_Thunk(); + browser_interfaces_[PPB_VIEW_INTERFACE_1_1] = + ppapi::thunk::GetPPB_View_1_1_Thunk(); +} + +InterfaceList::~InterfaceList() {} + +// static +InterfaceList* InterfaceList::GetInstance() { + return Singleton<InterfaceList>::get(); +} + +const void* InterfaceList::GetBrowserInterface(const std::string& name) const { + NameToInterfaceMap::const_iterator iter = browser_interfaces_.find(name); + + if (iter == browser_interfaces_.end()) + return NULL; + + return iter->second; +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/interface_list.h b/chromium/mojo/examples/pepper_container_app/interface_list.h new file mode 100644 index 00000000000..5cff06c5d83 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/interface_list.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_INTERFACE_LIST_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_INTERFACE_LIST_H_ + +#include <map> +#include <string> + +#include "base/macros.h" + +namespace mojo { +namespace examples { + +// InterfaceList maintains the mapping from Pepper interface names to +// interface pointers. +class InterfaceList { + public: + InterfaceList(); + ~InterfaceList(); + + static InterfaceList* GetInstance(); + + const void* GetBrowserInterface(const std::string& name) const; + + private: + typedef std::map<std::string, const void*> NameToInterfaceMap; + NameToInterfaceMap browser_interfaces_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceList); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_INTERFACE_LIST_H_ diff --git a/chromium/mojo/examples/pepper_container_app/mojo_ppapi_globals.cc b/chromium/mojo/examples/pepper_container_app/mojo_ppapi_globals.cc new file mode 100644 index 00000000000..33f87176051 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/mojo_ppapi_globals.cc @@ -0,0 +1,184 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/pepper_container_app/mojo_ppapi_globals.h" + +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "mojo/examples/pepper_container_app/plugin_instance.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/shared_impl/ppb_message_loop_shared.h" + +namespace mojo { +namespace examples { + +namespace { + +const PP_Instance kInstanceId = 1; + +} // namespace + +// A non-abstract subclass of ppapi::MessageLoopShared that represents the +// message loop of the main thread. +// TODO(yzshen): Build a more general ppapi::MessageLoopShared subclass to fully +// support PPB_MessageLoop. +class MojoPpapiGlobals::MainThreadMessageLoopResource + : public ppapi::MessageLoopShared { + public: + explicit MainThreadMessageLoopResource( + base::MessageLoopProxy* main_thread_message_loop) + : MessageLoopShared(ForMainThread()), + main_thread_message_loop_(main_thread_message_loop) {} + + // ppapi::MessageLoopShared implementation. + virtual void PostClosure(const tracked_objects::Location& from_here, + const base::Closure& closure, + int64 delay_ms) OVERRIDE { + main_thread_message_loop_->PostDelayedTask( + from_here, closure, base::TimeDelta::FromMilliseconds(delay_ms)); + } + + virtual base::MessageLoopProxy* GetMessageLoopProxy() OVERRIDE { + return main_thread_message_loop_.get(); + } + + // ppapi::thunk::PPB_MessageLoop_API implementation. + virtual int32_t AttachToCurrentThread() OVERRIDE { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + virtual int32_t Run() OVERRIDE { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + virtual int32_t PostWork(PP_CompletionCallback callback, + int64_t delay_ms) OVERRIDE { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + virtual int32_t PostQuit(PP_Bool should_destroy) OVERRIDE { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; + } + + private: + virtual ~MainThreadMessageLoopResource() {} + + scoped_refptr<base::MessageLoopProxy> main_thread_message_loop_; + DISALLOW_COPY_AND_ASSIGN(MainThreadMessageLoopResource); +}; + +MojoPpapiGlobals::MojoPpapiGlobals(Delegate* delegate) + : delegate_(delegate), + plugin_instance_(NULL), + resource_tracker_(ppapi::ResourceTracker::THREAD_SAFE) {} + +MojoPpapiGlobals::~MojoPpapiGlobals() {} + +PP_Instance MojoPpapiGlobals::AddInstance(PluginInstance* instance) { + DCHECK(!plugin_instance_); + plugin_instance_ = instance; + resource_tracker_.DidCreateInstance(kInstanceId); + return kInstanceId; +} + +void MojoPpapiGlobals::InstanceDeleted(PP_Instance instance) { + DCHECK_EQ(instance, kInstanceId); + DCHECK(plugin_instance_); + resource_tracker_.DidDeleteInstance(instance); + plugin_instance_ = NULL; +} + +PluginInstance* MojoPpapiGlobals::GetInstance(PP_Instance instance) { + if (instance == kInstanceId) + return plugin_instance_; + return NULL; +} + +ScopedMessagePipeHandle MojoPpapiGlobals::CreateGLES2Context() { + return delegate_->CreateGLES2Context(); +} + +ppapi::ResourceTracker* MojoPpapiGlobals::GetResourceTracker() { + return &resource_tracker_; +} + +ppapi::VarTracker* MojoPpapiGlobals::GetVarTracker() { + NOTIMPLEMENTED(); + return NULL; +} + +ppapi::CallbackTracker* MojoPpapiGlobals::GetCallbackTrackerForInstance( + PP_Instance instance) { + if (instance == kInstanceId && plugin_instance_) + return plugin_instance_->plugin_module()->callback_tracker(); + return NULL; +} + +void MojoPpapiGlobals::LogWithSource(PP_Instance instance, + PP_LogLevel level, + const std::string& source, + const std::string& value) { + NOTIMPLEMENTED(); +} + +void MojoPpapiGlobals::BroadcastLogWithSource(PP_Module module, + PP_LogLevel level, + const std::string& source, + const std::string& value) { + NOTIMPLEMENTED(); +} + +ppapi::thunk::PPB_Instance_API* MojoPpapiGlobals::GetInstanceAPI( + PP_Instance instance) { + if (instance == kInstanceId && plugin_instance_) + return plugin_instance_; + return NULL; +} + +ppapi::thunk::ResourceCreationAPI* MojoPpapiGlobals::GetResourceCreationAPI( + PP_Instance instance) { + if (instance == kInstanceId && plugin_instance_) + return plugin_instance_->resource_creation(); + return NULL; +} + +PP_Module MojoPpapiGlobals::GetModuleForInstance(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +ppapi::MessageLoopShared* MojoPpapiGlobals::GetCurrentMessageLoop() { + if (base::MessageLoopProxy::current().get() == GetMainThreadMessageLoop()) { + if (!main_thread_message_loop_resource_) { + main_thread_message_loop_resource_ = new MainThreadMessageLoopResource( + GetMainThreadMessageLoop()); + } + return main_thread_message_loop_resource_.get(); + } + + NOTIMPLEMENTED(); + return NULL; +} + +base::TaskRunner* MojoPpapiGlobals::GetFileTaskRunner() { + NOTIMPLEMENTED(); + return NULL; +} + +std::string MojoPpapiGlobals::GetCmdLine() { + NOTIMPLEMENTED(); + return std::string(); +} + +void MojoPpapiGlobals::PreCacheFontForFlash(const void* logfontw) { + NOTIMPLEMENTED(); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/mojo_ppapi_globals.h b/chromium/mojo/examples/pepper_container_app/mojo_ppapi_globals.h new file mode 100644 index 00000000000..349f885b56c --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/mojo_ppapi_globals.h @@ -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. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_MOJO_PPAPI_GLOBALS_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_MOJO_PPAPI_GLOBALS_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/cpp/system/core.h" +#include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/resource_tracker.h" + +namespace base { +class MessageLoopProxy; +} + +namespace mojo { +namespace examples { + +class PluginInstance; + +class MojoPpapiGlobals : public ppapi::PpapiGlobals { + public: + class Delegate { + public: + virtual ~Delegate() {} + + virtual ScopedMessagePipeHandle CreateGLES2Context() = 0; + }; + + // |delegate| must live longer than this object. + explicit MojoPpapiGlobals(Delegate* delegate); + virtual ~MojoPpapiGlobals(); + + inline static MojoPpapiGlobals* Get() { + return static_cast<MojoPpapiGlobals*>(PpapiGlobals::Get()); + } + + PP_Instance AddInstance(PluginInstance* instance); + void InstanceDeleted(PP_Instance instance); + PluginInstance* GetInstance(PP_Instance instance); + + ScopedMessagePipeHandle CreateGLES2Context(); + + // ppapi::PpapiGlobals implementation. + virtual ppapi::ResourceTracker* GetResourceTracker() OVERRIDE; + virtual ppapi::VarTracker* GetVarTracker() OVERRIDE; + virtual ppapi::CallbackTracker* GetCallbackTrackerForInstance( + PP_Instance instance) OVERRIDE; + virtual void LogWithSource(PP_Instance instance, + PP_LogLevel level, + const std::string& source, + const std::string& value) OVERRIDE; + virtual void BroadcastLogWithSource(PP_Module module, + PP_LogLevel level, + const std::string& source, + const std::string& value) OVERRIDE; + virtual ppapi::thunk::PPB_Instance_API* GetInstanceAPI( + PP_Instance instance) OVERRIDE; + virtual ppapi::thunk::ResourceCreationAPI* GetResourceCreationAPI( + PP_Instance instance) OVERRIDE; + virtual PP_Module GetModuleForInstance(PP_Instance instance) OVERRIDE; + virtual ppapi::MessageLoopShared* GetCurrentMessageLoop() OVERRIDE; + virtual base::TaskRunner* GetFileTaskRunner() OVERRIDE; + virtual std::string GetCmdLine() OVERRIDE; + virtual void PreCacheFontForFlash(const void* logfontw) OVERRIDE; + + private: + class MainThreadMessageLoopResource; + + // Non-owning pointer. + Delegate* const delegate_; + + // Non-owning pointer. + PluginInstance* plugin_instance_; + + ppapi::ResourceTracker resource_tracker_; + + scoped_refptr<MainThreadMessageLoopResource> + main_thread_message_loop_resource_; + + DISALLOW_COPY_AND_ASSIGN(MojoPpapiGlobals); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_MOJO_PPAPI_GLOBALS_H_ diff --git a/chromium/mojo/examples/pepper_container_app/pepper_container_app.cc b/chromium/mojo/examples/pepper_container_app/pepper_container_app.cc new file mode 100644 index 00000000000..3c6cf76e4ec --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/pepper_container_app.cc @@ -0,0 +1,110 @@ +// 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 "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "build/build_config.h" +#include "mojo/examples/pepper_container_app/mojo_ppapi_globals.h" +#include "mojo/examples/pepper_container_app/plugin_instance.h" +#include "mojo/examples/pepper_container_app/plugin_module.h" +#include "mojo/examples/pepper_container_app/type_converters.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ppapi/c/pp_rect.h" +#include "ppapi/shared_impl/proxy_lock.h" + +namespace mojo { +namespace examples { + +class PepperContainerApp: public Application, + public NativeViewportClient, + public MojoPpapiGlobals::Delegate { + public: + explicit PepperContainerApp() + : Application(), + ppapi_globals_(this), + plugin_module_(new PluginModule) {} + + virtual ~PepperContainerApp() {} + + virtual void Initialize() MOJO_OVERRIDE { + ConnectTo("mojo:mojo_native_viewport_service", &viewport_); + viewport_.set_client(this); + + RectPtr rect(Rect::New()); + rect->x = 10; + rect->y = 10; + rect->width = 800; + rect->height = 600; + viewport_->Create(rect.Pass()); + viewport_->Show(); + } + + // NativeViewportClient implementation. + virtual void OnCreated() OVERRIDE { + ppapi::ProxyAutoLock lock; + + plugin_instance_ = plugin_module_->CreateInstance().Pass(); + if (!plugin_instance_->DidCreate()) + plugin_instance_.reset(); + } + + virtual void OnDestroyed() OVERRIDE { + ppapi::ProxyAutoLock lock; + + if (plugin_instance_) { + plugin_instance_->DidDestroy(); + plugin_instance_.reset(); + } + + base::MessageLoop::current()->Quit(); + } + + virtual void OnBoundsChanged(RectPtr bounds) OVERRIDE { + ppapi::ProxyAutoLock lock; + + if (plugin_instance_) + plugin_instance_->DidChangeView(bounds.To<PP_Rect>()); + } + + virtual void OnEvent(EventPtr event, + const mojo::Callback<void()>& callback) OVERRIDE { + if (!event->location.is_null()) { + ppapi::ProxyAutoLock lock; + + // TODO(yzshen): Handle events. + } + callback.Run(); + } + + // MojoPpapiGlobals::Delegate implementation. + virtual ScopedMessagePipeHandle CreateGLES2Context() OVERRIDE { + CommandBufferPtr command_buffer; + viewport_->CreateGLES2Context(Get(&command_buffer)); + return command_buffer.PassMessagePipe(); + } + + private: + MojoPpapiGlobals ppapi_globals_; + + NativeViewportPtr viewport_; + scoped_refptr<PluginModule> plugin_module_; + scoped_ptr<PluginInstance> plugin_instance_; + + DISALLOW_COPY_AND_ASSIGN(PepperContainerApp); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::PepperContainerApp(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/plugin_instance.cc b/chromium/mojo/examples/pepper_container_app/plugin_instance.cc new file mode 100644 index 00000000000..c0d2e312d64 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/plugin_instance.cc @@ -0,0 +1,416 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/pepper_container_app/plugin_instance.h" + +#include "base/logging.h" +#include "mojo/examples/pepper_container_app/graphics_3d_resource.h" +#include "mojo/examples/pepper_container_app/mojo_ppapi_globals.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppp_graphics_3d.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/shared_impl/ppb_view_shared.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/shared_impl/tracked_callback.h" +#include "ppapi/thunk/enter.h" +#include "ppapi/thunk/ppb_graphics_3d_api.h" + +namespace mojo { +namespace examples { + +PluginInstance::PluginInstance(scoped_refptr<PluginModule> plugin_module) + : pp_instance_(0), + plugin_module_(plugin_module) { + pp_instance_ = MojoPpapiGlobals::Get()->AddInstance(this); +} + +PluginInstance::~PluginInstance() { + MojoPpapiGlobals::Get()->InstanceDeleted(pp_instance_); +} + +bool PluginInstance::DidCreate() { + ppapi::ProxyAutoUnlock unlock; + const PPP_Instance_1_1* instance_interface = + static_cast<const PPP_Instance_1_1*>(plugin_module_->GetPluginInterface( + PPP_INSTANCE_INTERFACE_1_1)); + return !!instance_interface->DidCreate(pp_instance(), 0, NULL, NULL); +} + +void PluginInstance::DidDestroy() { + ppapi::ProxyAutoUnlock unlock; + const PPP_Instance_1_1* instance_interface = + static_cast<const PPP_Instance_1_1*>(plugin_module_->GetPluginInterface( + PPP_INSTANCE_INTERFACE_1_1)); + instance_interface->DidDestroy(pp_instance()); +} + +void PluginInstance::DidChangeView(const PP_Rect& bounds) { + ppapi::ViewData view_data; + view_data.rect = bounds; + view_data.is_fullscreen = false; + view_data.is_page_visible = true; + view_data.clip_rect = bounds; + view_data.device_scale = 1.0f; + view_data.css_scale = 1.0f; + + ppapi::ScopedPPResource resource(ppapi::ScopedPPResource::PassRef(), + (new ppapi::PPB_View_Shared( + ppapi::OBJECT_IS_IMPL, pp_instance(), view_data))->GetReference()); + { + ppapi::ProxyAutoUnlock unlock; + const PPP_Instance_1_1* instance_interface = + static_cast<const PPP_Instance_1_1*>(plugin_module_->GetPluginInterface( + PPP_INSTANCE_INTERFACE_1_1)); + instance_interface->DidChangeView(pp_instance(), resource); + } +} + +void PluginInstance::Graphics3DContextLost() { + ppapi::ProxyAutoUnlock unlock; + const PPP_Graphics3D_1_0* graphic_3d_interface = + static_cast<const PPP_Graphics3D_1_0*>(plugin_module_->GetPluginInterface( + PPP_GRAPHICS_3D_INTERFACE_1_0)); + // TODO(yzshen): Maybe we only need to notify for the bound graphics context? + graphic_3d_interface->Graphics3DContextLost(pp_instance()); +} + +bool PluginInstance::IsBoundGraphics(PP_Resource device) const { + return device != 0 && device == bound_graphics_.get(); +} + +PP_Bool PluginInstance::BindGraphics(PP_Instance instance, PP_Resource device) { + if (bound_graphics_.get() == device) + return PP_TRUE; + + ppapi::thunk::EnterResourceNoLock<ppapi::thunk::PPB_Graphics3D_API> + enter(device, false); + if (enter.failed()) + return PP_FALSE; + + bound_graphics_ = device; + static_cast<Graphics3DResource*>(enter.object())->BindGraphics(); + + return PP_TRUE; +} + +PP_Bool PluginInstance::IsFullFrame(PP_Instance instance) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +const ppapi::ViewData* PluginInstance::GetViewData(PP_Instance instance) { + NOTIMPLEMENTED(); + return NULL; +} + +PP_Bool PluginInstance::FlashIsFullscreen(PP_Instance instance) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +PP_Var PluginInstance::GetWindowObject(PP_Instance instance) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +PP_Var PluginInstance::GetOwnerElementObject(PP_Instance instance) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +PP_Var PluginInstance::ExecuteScript(PP_Instance instance, + PP_Var script, + PP_Var* exception) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +uint32_t PluginInstance::GetAudioHardwareOutputSampleRate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +uint32_t PluginInstance::GetAudioHardwareOutputBufferSize( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Var PluginInstance::GetDefaultCharSet(PP_Instance instance) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +void PluginInstance::Log(PP_Instance instance, + PP_LogLevel log_level, + PP_Var value) { + NOTIMPLEMENTED(); +} + +void PluginInstance::LogWithSource(PP_Instance instance, + PP_LogLevel log_level, + PP_Var source, + PP_Var value) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SetPluginToHandleFindRequests(PP_Instance instance) { + NOTIMPLEMENTED(); +} + +void PluginInstance::NumberOfFindResultsChanged(PP_Instance instance, + int32_t total, + PP_Bool final_result) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SelectedFindResultChanged(PP_Instance instance, + int32_t index) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SetTickmarks(PP_Instance instance, + const PP_Rect* tickmarks, + uint32_t count) { + NOTIMPLEMENTED(); +} + +PP_Bool PluginInstance::IsFullscreen(PP_Instance instance) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +PP_Bool PluginInstance::SetFullscreen(PP_Instance instance, + PP_Bool fullscreen) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +PP_Bool PluginInstance::GetScreenSize(PP_Instance instance, PP_Size* size) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +ppapi::Resource* PluginInstance::GetSingletonResource( + PP_Instance instance, + ppapi::SingletonResourceID id) { + NOTIMPLEMENTED(); + return NULL; +} + +int32_t PluginInstance::RequestInputEvents(PP_Instance instance, + uint32_t event_classes) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +int32_t PluginInstance::RequestFilteringInputEvents(PP_Instance instance, + uint32_t event_classes) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +void PluginInstance::ClearInputEventRequest(PP_Instance instance, + uint32_t event_classes) { + NOTIMPLEMENTED(); +} + +void PluginInstance::StartTrackingLatency(PP_Instance instance) { + NOTIMPLEMENTED(); +} + +void PluginInstance::PostMessage(PP_Instance instance, PP_Var message) { + NOTIMPLEMENTED(); +} + +int32_t PluginInstance::RegisterMessageHandler( + PP_Instance instance, + void* user_data, + const PPP_MessageHandler_0_1* handler, + PP_Resource message_loop) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +void PluginInstance::UnregisterMessageHandler(PP_Instance instance) { + NOTIMPLEMENTED(); +} + +PP_Bool PluginInstance::SetCursor(PP_Instance instance, + PP_MouseCursor_Type type, + PP_Resource image, + const PP_Point* hot_spot) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +int32_t PluginInstance::LockMouse( + PP_Instance instance, + scoped_refptr<ppapi::TrackedCallback> callback) { + NOTIMPLEMENTED(); + return PP_ERROR_FAILED; +} + +void PluginInstance::UnlockMouse(PP_Instance instance) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SetTextInputType(PP_Instance instance, + PP_TextInput_Type type) { + NOTIMPLEMENTED(); +} + +void PluginInstance::UpdateCaretPosition(PP_Instance instance, + const PP_Rect& caret, + const PP_Rect& bounding_box) { + NOTIMPLEMENTED(); +} + +void PluginInstance::CancelCompositionText(PP_Instance instance) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SelectionChanged(PP_Instance instance) { + NOTIMPLEMENTED(); +} + +void PluginInstance::UpdateSurroundingText(PP_Instance instance, + const char* text, + uint32_t caret, + uint32_t anchor) { + NOTIMPLEMENTED(); +} + +void PluginInstance::ZoomChanged(PP_Instance instance, double factor) { + NOTIMPLEMENTED(); +} + +void PluginInstance::ZoomLimitsChanged(PP_Instance instance, + double minimum_factor, + double maximum_factor) { + NOTIMPLEMENTED(); +} + +PP_Var PluginInstance::GetDocumentURL(PP_Instance instance, + PP_URLComponents_Dev* components) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +void PluginInstance::PromiseResolved(PP_Instance instance, uint32 promise_id) { + NOTIMPLEMENTED(); +} + +void PluginInstance::PromiseResolvedWithSession(PP_Instance instance, + uint32 promise_id, + PP_Var web_session_id_var) { + NOTIMPLEMENTED(); +} + +void PluginInstance::PromiseRejected(PP_Instance instance, + uint32 promise_id, + PP_CdmExceptionCode exception_code, + uint32 system_code, + PP_Var error_description_var) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SessionMessage(PP_Instance instance, + PP_Var web_session_id_var, + PP_Var message_var, + PP_Var destination_url_var) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SessionReady(PP_Instance instance, + PP_Var web_session_id_var) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SessionClosed(PP_Instance instance, + PP_Var web_session_id_var) { + NOTIMPLEMENTED(); +} + +void PluginInstance::SessionError(PP_Instance instance, + PP_Var web_session_id_var, + PP_CdmExceptionCode exception_code, + uint32 system_code, + PP_Var error_description_var) { + NOTIMPLEMENTED(); +} + +void PluginInstance::DeliverBlock(PP_Instance instance, + PP_Resource decrypted_block, + const PP_DecryptedBlockInfo* block_info) { + NOTIMPLEMENTED(); +} + +void PluginInstance::DecoderInitializeDone(PP_Instance instance, + PP_DecryptorStreamType decoder_type, + uint32_t request_id, + PP_Bool success) { + NOTIMPLEMENTED(); +} + +void PluginInstance::DecoderDeinitializeDone( + PP_Instance instance, + PP_DecryptorStreamType decoder_type, + uint32_t request_id) { + NOTIMPLEMENTED(); +} + +void PluginInstance::DecoderResetDone(PP_Instance instance, + PP_DecryptorStreamType decoder_type, + uint32_t request_id) { + NOTIMPLEMENTED(); +} + +void PluginInstance::DeliverFrame(PP_Instance instance, + PP_Resource decrypted_frame, + const PP_DecryptedFrameInfo* frame_info) { + NOTIMPLEMENTED(); +} + +void PluginInstance::DeliverSamples(PP_Instance instance, + PP_Resource audio_frames, + const PP_DecryptedSampleInfo* sample_info) { + NOTIMPLEMENTED(); +} + +PP_Var PluginInstance::ResolveRelativeToDocument( + PP_Instance instance, + PP_Var relative, + PP_URLComponents_Dev* components) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +PP_Bool PluginInstance::DocumentCanRequest(PP_Instance instance, PP_Var url) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +PP_Bool PluginInstance::DocumentCanAccessDocument(PP_Instance instance, + PP_Instance target) { + NOTIMPLEMENTED(); + return PP_FALSE; +} + +PP_Var PluginInstance::GetPluginInstanceURL(PP_Instance instance, + PP_URLComponents_Dev* components) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +PP_Var PluginInstance::GetPluginReferrerURL(PP_Instance instance, + PP_URLComponents_Dev* components) { + NOTIMPLEMENTED(); + return PP_MakeUndefined(); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/plugin_instance.h b/chromium/mojo/examples/pepper_container_app/plugin_instance.h new file mode 100644 index 00000000000..f76621a0eea --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/plugin_instance.h @@ -0,0 +1,185 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_PLUGIN_INSTANCE_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_PLUGIN_INSTANCE_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/examples/pepper_container_app/plugin_module.h" +#include "mojo/examples/pepper_container_app/resource_creation_impl.h" +#include "ppapi/c/pp_rect.h" +#include "ppapi/shared_impl/scoped_pp_resource.h" +#include "ppapi/thunk/ppb_instance_api.h" + +namespace mojo { +namespace examples { + +class PluginInstance : public ppapi::thunk::PPB_Instance_API { + public: + explicit PluginInstance(scoped_refptr<PluginModule> plugin_module); + virtual ~PluginInstance(); + + // Notifies the plugin that a new instance has been created. + bool DidCreate(); + // Notifies the plugin that the instance has been destroyed. + void DidDestroy(); + // Notifies the plugin that the position or size of the instance has changed. + void DidChangeView(const PP_Rect& bounds); + // Notifies the plugin that the Graphics 3D context has been invalidated. + void Graphics3DContextLost(); + + // Returns true if |device| has been bound as the current display surface. + bool IsBoundGraphics(PP_Resource device) const; + + PP_Instance pp_instance() const { return pp_instance_; } + ResourceCreationImpl* resource_creation() { return &resource_creation_; } + PluginModule* plugin_module() { return plugin_module_.get(); } + + // ppapi::thunk::PPB_Instance_API implementation. + virtual PP_Bool BindGraphics(PP_Instance instance, + PP_Resource device) OVERRIDE; + virtual PP_Bool IsFullFrame(PP_Instance instance) OVERRIDE; + virtual const ppapi::ViewData* GetViewData(PP_Instance instance) OVERRIDE; + virtual PP_Bool FlashIsFullscreen(PP_Instance instance) OVERRIDE; + virtual PP_Var GetWindowObject(PP_Instance instance) OVERRIDE; + virtual PP_Var GetOwnerElementObject(PP_Instance instance) OVERRIDE; + virtual PP_Var ExecuteScript(PP_Instance instance, + PP_Var script, + PP_Var* exception) OVERRIDE; + virtual uint32_t GetAudioHardwareOutputSampleRate( + PP_Instance instance) OVERRIDE; + virtual uint32_t GetAudioHardwareOutputBufferSize( + PP_Instance instance) OVERRIDE; + virtual PP_Var GetDefaultCharSet(PP_Instance instance) OVERRIDE; + virtual void Log(PP_Instance instance, + PP_LogLevel log_level, + PP_Var value) OVERRIDE; + virtual void LogWithSource(PP_Instance instance, + PP_LogLevel log_level, + PP_Var source, + PP_Var value) OVERRIDE; + virtual void SetPluginToHandleFindRequests(PP_Instance instance) OVERRIDE; + virtual void NumberOfFindResultsChanged(PP_Instance instance, + int32_t total, + PP_Bool final_result) OVERRIDE; + virtual void SelectedFindResultChanged(PP_Instance instance, + int32_t index) OVERRIDE; + virtual void SetTickmarks(PP_Instance instance, + const PP_Rect* tickmarks, + uint32_t count) OVERRIDE; + virtual PP_Bool IsFullscreen(PP_Instance instance) OVERRIDE; + virtual PP_Bool SetFullscreen(PP_Instance instance, + PP_Bool fullscreen) OVERRIDE; + virtual PP_Bool GetScreenSize(PP_Instance instance, PP_Size* size) OVERRIDE; + virtual ppapi::Resource* GetSingletonResource( + PP_Instance instance, ppapi::SingletonResourceID id) OVERRIDE; + virtual int32_t RequestInputEvents(PP_Instance instance, + uint32_t event_classes) OVERRIDE; + virtual int32_t RequestFilteringInputEvents(PP_Instance instance, + uint32_t event_classes) OVERRIDE; + virtual void ClearInputEventRequest(PP_Instance instance, + uint32_t event_classes) OVERRIDE; + virtual void StartTrackingLatency(PP_Instance instance) OVERRIDE; + virtual void PostMessage(PP_Instance instance, PP_Var message) OVERRIDE; + virtual int32_t RegisterMessageHandler(PP_Instance instance, + void* user_data, + const PPP_MessageHandler_0_1* handler, + PP_Resource message_loop) OVERRIDE; + virtual void UnregisterMessageHandler(PP_Instance instance) OVERRIDE; + virtual PP_Bool SetCursor(PP_Instance instance, + PP_MouseCursor_Type type, + PP_Resource image, + const PP_Point* hot_spot) OVERRIDE; + virtual int32_t LockMouse( + PP_Instance instance, + scoped_refptr<ppapi::TrackedCallback> callback) OVERRIDE; + virtual void UnlockMouse(PP_Instance instance) OVERRIDE; + virtual void SetTextInputType(PP_Instance instance, + PP_TextInput_Type type) OVERRIDE; + virtual void UpdateCaretPosition(PP_Instance instance, + const PP_Rect& caret, + const PP_Rect& bounding_box) OVERRIDE; + virtual void CancelCompositionText(PP_Instance instance) OVERRIDE; + virtual void SelectionChanged(PP_Instance instance) OVERRIDE; + virtual void UpdateSurroundingText(PP_Instance instance, + const char* text, + uint32_t caret, + uint32_t anchor) OVERRIDE; + virtual void ZoomChanged(PP_Instance instance, double factor) OVERRIDE; + virtual void ZoomLimitsChanged(PP_Instance instance, + double minimum_factor, + double maximum_factor) OVERRIDE; + virtual PP_Var GetDocumentURL(PP_Instance instance, + PP_URLComponents_Dev* components) OVERRIDE; + virtual void PromiseResolved(PP_Instance instance, + uint32 promise_id) OVERRIDE; + virtual void PromiseResolvedWithSession(PP_Instance instance, + uint32 promise_id, + PP_Var web_session_id_var) OVERRIDE; + virtual void PromiseRejected(PP_Instance instance, + uint32 promise_id, + PP_CdmExceptionCode exception_code, + uint32 system_code, + PP_Var error_description_var) OVERRIDE; + virtual void SessionMessage(PP_Instance instance, + PP_Var web_session_id_var, + PP_Var message_var, + PP_Var destination_url_var) OVERRIDE; + virtual void SessionReady(PP_Instance instance, + PP_Var web_session_id_var) OVERRIDE; + virtual void SessionClosed(PP_Instance instance, + PP_Var web_session_id_var) OVERRIDE; + virtual void SessionError(PP_Instance instance, + PP_Var web_session_id_var, + PP_CdmExceptionCode exception_code, + uint32 system_code, + PP_Var error_description_var) OVERRIDE; + virtual void DeliverBlock(PP_Instance instance, + PP_Resource decrypted_block, + const PP_DecryptedBlockInfo* block_info) OVERRIDE; + virtual void DecoderInitializeDone(PP_Instance instance, + PP_DecryptorStreamType decoder_type, + uint32_t request_id, + PP_Bool success) OVERRIDE; + virtual void DecoderDeinitializeDone(PP_Instance instance, + PP_DecryptorStreamType decoder_type, + uint32_t request_id) OVERRIDE; + virtual void DecoderResetDone(PP_Instance instance, + PP_DecryptorStreamType decoder_type, + uint32_t request_id) OVERRIDE; + virtual void DeliverFrame(PP_Instance instance, + PP_Resource decrypted_frame, + const PP_DecryptedFrameInfo* frame_info) OVERRIDE; + virtual void DeliverSamples( + PP_Instance instance, + PP_Resource audio_frames, + const PP_DecryptedSampleInfo* sample_info) OVERRIDE; + virtual PP_Var ResolveRelativeToDocument( + PP_Instance instance, + PP_Var relative, + PP_URLComponents_Dev* components) OVERRIDE; + virtual PP_Bool DocumentCanRequest(PP_Instance instance, PP_Var url) OVERRIDE; + virtual PP_Bool DocumentCanAccessDocument(PP_Instance instance, + PP_Instance target) OVERRIDE; + virtual PP_Var GetPluginInstanceURL( + PP_Instance instance, + PP_URLComponents_Dev* components) OVERRIDE; + virtual PP_Var GetPluginReferrerURL( + PP_Instance instance, + PP_URLComponents_Dev* components) OVERRIDE; + + private: + PP_Instance pp_instance_; + ResourceCreationImpl resource_creation_; + scoped_refptr<PluginModule> plugin_module_; + ppapi::ScopedPPResource bound_graphics_; + + DISALLOW_COPY_AND_ASSIGN(PluginInstance); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_PLUGIN_INSTANCE_H_ diff --git a/chromium/mojo/examples/pepper_container_app/plugin_module.cc b/chromium/mojo/examples/pepper_container_app/plugin_module.cc new file mode 100644 index 00000000000..d09d42d36d4 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/plugin_module.cc @@ -0,0 +1,110 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/pepper_container_app/plugin_module.h" + +#include <string> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "mojo/examples/pepper_container_app/interface_list.h" +#include "mojo/examples/pepper_container_app/plugin_instance.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/shared_impl/callback_tracker.h" + +namespace mojo { +namespace examples { + +namespace { + +const void* GetInterface(const char* name) { + const void* interface = + InterfaceList::GetInstance()->GetBrowserInterface(name); + + if (!interface) + LOG(WARNING) << "Interface requested " << name; + + return interface; +} + +} // namespace + +PluginModule::EntryPoints::EntryPoints() : get_interface(NULL), + initialize_module(NULL), + shutdown_module(NULL) {} + +PluginModule::PluginModule() : callback_tracker_(new ppapi::CallbackTracker) { + Initialize(); +} + +PluginModule::~PluginModule() { + callback_tracker_->AbortAll(); + + if (entry_points_.shutdown_module) + entry_points_.shutdown_module(); +} + +scoped_ptr<PluginInstance> PluginModule::CreateInstance() { + return make_scoped_ptr(new PluginInstance(this)); +} + +const void* PluginModule::GetPluginInterface(const char* name) const { + if (entry_points_.get_interface) + return entry_points_.get_interface(name); + return NULL; +} + +void PluginModule::Initialize() { + // Platform-specific filename. + // TODO(yzshen): Don't hard-code it. +#if defined(OS_WIN) + static const wchar_t plugin_name[] = L"ppapi_example_gles2_spinning_cube.dll"; +#elif defined(OS_MACOSX) + static const char plugin_name[] = "ppapi_example_gles2_spinning_cube.plugin"; +#elif defined(OS_POSIX) + static const char plugin_name[] = "libppapi_example_gles2_spinning_cube.so"; +#endif + + base::FilePath plugin_path(plugin_name); + + base::NativeLibraryLoadError error; + plugin_module_.Reset(base::LoadNativeLibrary(plugin_path, &error)); + + if (!plugin_module_.is_valid()) { + LOG(WARNING) << "Cannot load " << plugin_path.AsUTF8Unsafe() + << ". Error: " << error.ToString(); + return; + } + + entry_points_.get_interface = + reinterpret_cast<PP_GetInterface_Func>( + plugin_module_.GetFunctionPointer("PPP_GetInterface")); + if (!entry_points_.get_interface) { + LOG(WARNING) << "No PPP_GetInterface in plugin library"; + return; + } + + entry_points_.initialize_module = + reinterpret_cast<PP_InitializeModule_Func>( + plugin_module_.GetFunctionPointer("PPP_InitializeModule")); + if (!entry_points_.initialize_module) { + LOG(WARNING) << "No PPP_InitializeModule in plugin library"; + return; + } + + // It's okay for PPP_ShutdownModule to not be defined and |shutdown_module| to + // be NULL. + entry_points_.shutdown_module = + reinterpret_cast<PP_ShutdownModule_Func>( + plugin_module_.GetFunctionPointer("PPP_ShutdownModule")); + + int32_t result = entry_points_.initialize_module(pp_module(), + &GetInterface); + if (result != PP_OK) + LOG(WARNING) << "Initializing module failed with error " << result; +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/plugin_module.h b/chromium/mojo/examples/pepper_container_app/plugin_module.h new file mode 100644 index 00000000000..05aad3b1a65 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/plugin_module.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_PLUGIN_MODULE_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_PLUGIN_MODULE_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/native_library.h" +#include "base/scoped_native_library.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/ppp.h" + +namespace ppapi { +class CallbackTracker; +} + +namespace mojo { +namespace examples { + +class PluginInstance; + +class PluginModule : public base::RefCounted<PluginModule> { + public: + PluginModule(); + + scoped_ptr<PluginInstance> CreateInstance(); + + const void* GetPluginInterface(const char* name) const; + + PP_Module pp_module() const { return 1; } + ppapi::CallbackTracker* callback_tracker() { return callback_tracker_.get(); } + + private: + friend class base::RefCounted<PluginModule>; + + struct EntryPoints { + EntryPoints(); + + PP_GetInterface_Func get_interface; + PP_InitializeModule_Func initialize_module; + PP_ShutdownModule_Func shutdown_module; // Optional, may be NULL. + }; + + ~PluginModule(); + + void Initialize(); + + base::ScopedNativeLibrary plugin_module_; + EntryPoints entry_points_; + scoped_refptr<ppapi::CallbackTracker> callback_tracker_; + + DISALLOW_COPY_AND_ASSIGN(PluginModule); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_PLUGIN_MODULE_H_ diff --git a/chromium/mojo/examples/pepper_container_app/ppb_core_thunk.cc b/chromium/mojo/examples/pepper_container_app/ppb_core_thunk.cc new file mode 100644 index 00000000000..a76a9764785 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/ppb_core_thunk.cc @@ -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. + +#include "base/logging.h" +#include "mojo/examples/pepper_container_app/thunk.h" +#include "ppapi/c/ppb_core.h" +#include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/shared_impl/resource_tracker.h" + +namespace mojo { +namespace examples { + +namespace { + +void AddRefResource(PP_Resource resource) { + ppapi::ProxyAutoLock lock; + ppapi::PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(resource); +} + +void ReleaseResource(PP_Resource resource) { + ppapi::ProxyAutoLock lock; + ppapi::PpapiGlobals::Get()->GetResourceTracker()->ReleaseResource(resource); +} + +PP_Time GetTime() { + NOTIMPLEMENTED(); + return 0; +} + +PP_TimeTicks GetTimeTicks() { + NOTIMPLEMENTED(); + return 0; +} + +void CallOnMainThread(int32_t delay_in_milliseconds, + PP_CompletionCallback callback, + int32_t result) { + NOTIMPLEMENTED(); +} + +PP_Bool IsMainThread() { + NOTIMPLEMENTED(); + return PP_TRUE; +} + +} // namespace + +const PPB_Core_1_0 g_ppb_core_thunk_1_0 = { + &AddRefResource, + &ReleaseResource, + &GetTime, + &GetTimeTicks, + &CallOnMainThread, + &IsMainThread +}; + +const PPB_Core_1_0* GetPPB_Core_1_0_Thunk() { + return &g_ppb_core_thunk_1_0; +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/ppb_opengles2_thunk.cc b/chromium/mojo/examples/pepper_container_app/ppb_opengles2_thunk.cc new file mode 100644 index 00000000000..c5097bb0ddb --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/ppb_opengles2_thunk.cc @@ -0,0 +1,1454 @@ +// 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/logging.h" +#include "mojo/examples/pepper_container_app/graphics_3d_resource.h" +#include "mojo/examples/pepper_container_app/thunk.h" +#include "mojo/public/c/gles2/gles2.h" +#include "ppapi/thunk/enter.h" +#include "ppapi/thunk/ppb_graphics_3d_api.h" + +namespace mojo { +namespace examples { + +namespace { + +typedef ppapi::thunk::EnterResource<ppapi::thunk::PPB_Graphics3D_API> Enter3D; + +bool IsBoundGraphics(Enter3D* enter) { + return enter->succeeded() && + static_cast<Graphics3DResource*>(enter->object())->IsBoundGraphics(); +} + +void ActiveTexture(PP_Resource context_id, GLenum texture) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glActiveTexture(texture); + } +} + +void AttachShader(PP_Resource context_id, GLuint program, GLuint shader) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glAttachShader(program, shader); + } +} + +void BindAttribLocation(PP_Resource context_id, + GLuint program, + GLuint index, + const char* name) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBindAttribLocation(program, index, name); + } +} + +void BindBuffer(PP_Resource context_id, GLenum target, GLuint buffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBindBuffer(target, buffer); + } +} + +void BindFramebuffer(PP_Resource context_id, + GLenum target, + GLuint framebuffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBindFramebuffer(target, framebuffer); + } +} + +void BindRenderbuffer(PP_Resource context_id, + GLenum target, + GLuint renderbuffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBindRenderbuffer(target, renderbuffer); + } +} + +void BindTexture(PP_Resource context_id, GLenum target, GLuint texture) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBindTexture(target, texture); + } +} + +void BlendColor(PP_Resource context_id, + GLclampf red, + GLclampf green, + GLclampf blue, + GLclampf alpha) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBlendColor(red, green, blue, alpha); + } +} + +void BlendEquation(PP_Resource context_id, GLenum mode) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBlendEquation(mode); + } +} + +void BlendEquationSeparate(PP_Resource context_id, + GLenum modeRGB, + GLenum modeAlpha) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBlendEquationSeparate(modeRGB, modeAlpha); + } +} + +void BlendFunc(PP_Resource context_id, GLenum sfactor, GLenum dfactor) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBlendFunc(sfactor, dfactor); + } +} + +void BlendFuncSeparate(PP_Resource context_id, + GLenum srcRGB, + GLenum dstRGB, + GLenum srcAlpha, + GLenum dstAlpha) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); + } +} + +void BufferData(PP_Resource context_id, + GLenum target, + GLsizeiptr size, + const void* data, + GLenum usage) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBufferData(target, size, data, usage); + } +} + +void BufferSubData(PP_Resource context_id, + GLenum target, + GLintptr offset, + GLsizeiptr size, + const void* data) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glBufferSubData(target, offset, size, data); + } +} + +GLenum CheckFramebufferStatus(PP_Resource context_id, GLenum target) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glCheckFramebufferStatus(target); + } else { + return 0; + } +} + +void Clear(PP_Resource context_id, GLbitfield mask) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glClear(mask); + } +} + +void ClearColor(PP_Resource context_id, + GLclampf red, + GLclampf green, + GLclampf blue, + GLclampf alpha) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glClearColor(red, green, blue, alpha); + } +} + +void ClearDepthf(PP_Resource context_id, GLclampf depth) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glClearDepthf(depth); + } +} + +void ClearStencil(PP_Resource context_id, GLint s) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glClearStencil(s); + } +} + +void ColorMask(PP_Resource context_id, + GLboolean red, + GLboolean green, + GLboolean blue, + GLboolean alpha) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glColorMask(red, green, blue, alpha); + } +} + +void CompileShader(PP_Resource context_id, GLuint shader) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glCompileShader(shader); + } +} + +void CompressedTexImage2D(PP_Resource context_id, + GLenum target, + GLint level, + GLenum internalformat, + GLsizei width, + GLsizei height, + GLint border, + GLsizei imageSize, + const void* data) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glCompressedTexImage2D( + target, level, internalformat, width, height, border, imageSize, data); + } +} + +void CompressedTexSubImage2D(PP_Resource context_id, + GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLsizei width, + GLsizei height, + GLenum format, + GLsizei imageSize, + const void* data) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glCompressedTexSubImage2D(target, + level, + xoffset, + yoffset, + width, + height, + format, + imageSize, + data); + } +} + +void CopyTexImage2D(PP_Resource context_id, + GLenum target, + GLint level, + GLenum internalformat, + GLint x, + GLint y, + GLsizei width, + GLsizei height, + GLint border) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glCopyTexImage2D( + target, level, internalformat, x, y, width, height, border); + } +} + +void CopyTexSubImage2D(PP_Resource context_id, + GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLint x, + GLint y, + GLsizei width, + GLsizei height) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); + } +} + +GLuint CreateProgram(PP_Resource context_id) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glCreateProgram(); + } else { + return 0; + } +} + +GLuint CreateShader(PP_Resource context_id, GLenum type) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glCreateShader(type); + } else { + return 0; + } +} + +void CullFace(PP_Resource context_id, GLenum mode) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glCullFace(mode); + } +} + +void DeleteBuffers(PP_Resource context_id, GLsizei n, const GLuint* buffers) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDeleteBuffers(n, buffers); + } +} + +void DeleteFramebuffers(PP_Resource context_id, + GLsizei n, + const GLuint* framebuffers) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDeleteFramebuffers(n, framebuffers); + } +} + +void DeleteProgram(PP_Resource context_id, GLuint program) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDeleteProgram(program); + } +} + +void DeleteRenderbuffers(PP_Resource context_id, + GLsizei n, + const GLuint* renderbuffers) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDeleteRenderbuffers(n, renderbuffers); + } +} + +void DeleteShader(PP_Resource context_id, GLuint shader) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDeleteShader(shader); + } +} + +void DeleteTextures(PP_Resource context_id, GLsizei n, const GLuint* textures) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDeleteTextures(n, textures); + } +} + +void DepthFunc(PP_Resource context_id, GLenum func) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDepthFunc(func); + } +} + +void DepthMask(PP_Resource context_id, GLboolean flag) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDepthMask(flag); + } +} + +void DepthRangef(PP_Resource context_id, GLclampf zNear, GLclampf zFar) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDepthRangef(zNear, zFar); + } +} + +void DetachShader(PP_Resource context_id, GLuint program, GLuint shader) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDetachShader(program, shader); + } +} + +void Disable(PP_Resource context_id, GLenum cap) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDisable(cap); + } +} + +void DisableVertexAttribArray(PP_Resource context_id, GLuint index) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDisableVertexAttribArray(index); + } +} + +void DrawArrays(PP_Resource context_id, + GLenum mode, + GLint first, + GLsizei count) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDrawArrays(mode, first, count); + } +} + +void DrawElements(PP_Resource context_id, + GLenum mode, + GLsizei count, + GLenum type, + const void* indices) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glDrawElements(mode, count, type, indices); + } +} + +void Enable(PP_Resource context_id, GLenum cap) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glEnable(cap); + } +} + +void EnableVertexAttribArray(PP_Resource context_id, GLuint index) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glEnableVertexAttribArray(index); + } +} + +void Finish(PP_Resource context_id) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glFinish(); + } +} + +void Flush(PP_Resource context_id) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glFlush(); + } +} + +void FramebufferRenderbuffer(PP_Resource context_id, + GLenum target, + GLenum attachment, + GLenum renderbuffertarget, + GLuint renderbuffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glFramebufferRenderbuffer( + target, attachment, renderbuffertarget, renderbuffer); + } +} + +void FramebufferTexture2D(PP_Resource context_id, + GLenum target, + GLenum attachment, + GLenum textarget, + GLuint texture, + GLint level) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glFramebufferTexture2D(target, attachment, textarget, texture, level); + } +} + +void FrontFace(PP_Resource context_id, GLenum mode) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glFrontFace(mode); + } +} + +void GenBuffers(PP_Resource context_id, GLsizei n, GLuint* buffers) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGenBuffers(n, buffers); + } +} + +void GenerateMipmap(PP_Resource context_id, GLenum target) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGenerateMipmap(target); + } +} + +void GenFramebuffers(PP_Resource context_id, GLsizei n, GLuint* framebuffers) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGenFramebuffers(n, framebuffers); + } +} + +void GenRenderbuffers(PP_Resource context_id, + GLsizei n, + GLuint* renderbuffers) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGenRenderbuffers(n, renderbuffers); + } +} + +void GenTextures(PP_Resource context_id, GLsizei n, GLuint* textures) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGenTextures(n, textures); + } +} + +void GetActiveAttrib(PP_Resource context_id, + GLuint program, + GLuint index, + GLsizei bufsize, + GLsizei* length, + GLint* size, + GLenum* type, + char* name) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetActiveAttrib(program, index, bufsize, length, size, type, name); + } +} + +void GetActiveUniform(PP_Resource context_id, + GLuint program, + GLuint index, + GLsizei bufsize, + GLsizei* length, + GLint* size, + GLenum* type, + char* name) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetActiveUniform(program, index, bufsize, length, size, type, name); + } +} + +void GetAttachedShaders(PP_Resource context_id, + GLuint program, + GLsizei maxcount, + GLsizei* count, + GLuint* shaders) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetAttachedShaders(program, maxcount, count, shaders); + } +} + +GLint GetAttribLocation(PP_Resource context_id, + GLuint program, + const char* name) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glGetAttribLocation(program, name); + } else { + return -1; + } +} + +void GetBooleanv(PP_Resource context_id, GLenum pname, GLboolean* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetBooleanv(pname, params); + } +} + +void GetBufferParameteriv(PP_Resource context_id, + GLenum target, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetBufferParameteriv(target, pname, params); + } +} + +GLenum GetError(PP_Resource context_id) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glGetError(); + } else { + return 0; + } +} + +void GetFloatv(PP_Resource context_id, GLenum pname, GLfloat* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetFloatv(pname, params); + } +} + +void GetFramebufferAttachmentParameteriv(PP_Resource context_id, + GLenum target, + GLenum attachment, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetFramebufferAttachmentParameteriv(target, attachment, pname, params); + } +} + +void GetIntegerv(PP_Resource context_id, GLenum pname, GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetIntegerv(pname, params); + } +} + +void GetProgramiv(PP_Resource context_id, + GLuint program, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetProgramiv(program, pname, params); + } +} + +void GetProgramInfoLog(PP_Resource context_id, + GLuint program, + GLsizei bufsize, + GLsizei* length, + char* infolog) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetProgramInfoLog(program, bufsize, length, infolog); + } +} + +void GetRenderbufferParameteriv(PP_Resource context_id, + GLenum target, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetRenderbufferParameteriv(target, pname, params); + } +} + +void GetShaderiv(PP_Resource context_id, + GLuint shader, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetShaderiv(shader, pname, params); + } +} + +void GetShaderInfoLog(PP_Resource context_id, + GLuint shader, + GLsizei bufsize, + GLsizei* length, + char* infolog) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetShaderInfoLog(shader, bufsize, length, infolog); + } +} + +void GetShaderPrecisionFormat(PP_Resource context_id, + GLenum shadertype, + GLenum precisiontype, + GLint* range, + GLint* precision) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetShaderPrecisionFormat(shadertype, precisiontype, range, precision); + } +} + +void GetShaderSource(PP_Resource context_id, + GLuint shader, + GLsizei bufsize, + GLsizei* length, + char* source) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetShaderSource(shader, bufsize, length, source); + } +} + +const GLubyte* GetString(PP_Resource context_id, GLenum name) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glGetString(name); + } else { + return NULL; + } +} + +void GetTexParameterfv(PP_Resource context_id, + GLenum target, + GLenum pname, + GLfloat* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetTexParameterfv(target, pname, params); + } +} + +void GetTexParameteriv(PP_Resource context_id, + GLenum target, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetTexParameteriv(target, pname, params); + } +} + +void GetUniformfv(PP_Resource context_id, + GLuint program, + GLint location, + GLfloat* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetUniformfv(program, location, params); + } +} + +void GetUniformiv(PP_Resource context_id, + GLuint program, + GLint location, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetUniformiv(program, location, params); + } +} + +GLint GetUniformLocation(PP_Resource context_id, + GLuint program, + const char* name) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glGetUniformLocation(program, name); + } else { + return -1; + } +} + +void GetVertexAttribfv(PP_Resource context_id, + GLuint index, + GLenum pname, + GLfloat* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetVertexAttribfv(index, pname, params); + } +} + +void GetVertexAttribiv(PP_Resource context_id, + GLuint index, + GLenum pname, + GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetVertexAttribiv(index, pname, params); + } +} + +void GetVertexAttribPointerv(PP_Resource context_id, + GLuint index, + GLenum pname, + void** pointer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glGetVertexAttribPointerv(index, pname, pointer); + } +} + +void Hint(PP_Resource context_id, GLenum target, GLenum mode) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glHint(target, mode); + } +} + +GLboolean IsBuffer(PP_Resource context_id, GLuint buffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsBuffer(buffer); + } else { + return GL_FALSE; + } +} + +GLboolean IsEnabled(PP_Resource context_id, GLenum cap) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsEnabled(cap); + } else { + return GL_FALSE; + } +} + +GLboolean IsFramebuffer(PP_Resource context_id, GLuint framebuffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsFramebuffer(framebuffer); + } else { + return GL_FALSE; + } +} + +GLboolean IsProgram(PP_Resource context_id, GLuint program) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsProgram(program); + } else { + return GL_FALSE; + } +} + +GLboolean IsRenderbuffer(PP_Resource context_id, GLuint renderbuffer) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsRenderbuffer(renderbuffer); + } else { + return GL_FALSE; + } +} + +GLboolean IsShader(PP_Resource context_id, GLuint shader) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsShader(shader); + } else { + return GL_FALSE; + } +} + +GLboolean IsTexture(PP_Resource context_id, GLuint texture) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + return glIsTexture(texture); + } else { + return GL_FALSE; + } +} + +void LineWidth(PP_Resource context_id, GLfloat width) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glLineWidth(width); + } +} + +void LinkProgram(PP_Resource context_id, GLuint program) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glLinkProgram(program); + } +} + +void PixelStorei(PP_Resource context_id, GLenum pname, GLint param) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glPixelStorei(pname, param); + } +} + +void PolygonOffset(PP_Resource context_id, GLfloat factor, GLfloat units) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glPolygonOffset(factor, units); + } +} + +void ReadPixels(PP_Resource context_id, + GLint x, + GLint y, + GLsizei width, + GLsizei height, + GLenum format, + GLenum type, + void* pixels) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glReadPixels(x, y, width, height, format, type, pixels); + } +} + +void ReleaseShaderCompiler(PP_Resource context_id) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glReleaseShaderCompiler(); + } +} + +void RenderbufferStorage(PP_Resource context_id, + GLenum target, + GLenum internalformat, + GLsizei width, + GLsizei height) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glRenderbufferStorage(target, internalformat, width, height); + } +} + +void SampleCoverage(PP_Resource context_id, GLclampf value, GLboolean invert) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glSampleCoverage(value, invert); + } +} + +void Scissor(PP_Resource context_id, + GLint x, + GLint y, + GLsizei width, + GLsizei height) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glScissor(x, y, width, height); + } +} + +void ShaderBinary(PP_Resource context_id, + GLsizei n, + const GLuint* shaders, + GLenum binaryformat, + const void* binary, + GLsizei length) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glShaderBinary(n, shaders, binaryformat, binary, length); + } +} + +void ShaderSource(PP_Resource context_id, + GLuint shader, + GLsizei count, + const char** str, + const GLint* length) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glShaderSource(shader, count, str, length); + } +} + +void StencilFunc(PP_Resource context_id, GLenum func, GLint ref, GLuint mask) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glStencilFunc(func, ref, mask); + } +} + +void StencilFuncSeparate(PP_Resource context_id, + GLenum face, + GLenum func, + GLint ref, + GLuint mask) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glStencilFuncSeparate(face, func, ref, mask); + } +} + +void StencilMask(PP_Resource context_id, GLuint mask) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glStencilMask(mask); + } +} + +void StencilMaskSeparate(PP_Resource context_id, GLenum face, GLuint mask) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glStencilMaskSeparate(face, mask); + } +} + +void StencilOp(PP_Resource context_id, + GLenum fail, + GLenum zfail, + GLenum zpass) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glStencilOp(fail, zfail, zpass); + } +} + +void StencilOpSeparate(PP_Resource context_id, + GLenum face, + GLenum fail, + GLenum zfail, + GLenum zpass) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glStencilOpSeparate(face, fail, zfail, zpass); + } +} + +void TexImage2D(PP_Resource context_id, + GLenum target, + GLint level, + GLint internalformat, + GLsizei width, + GLsizei height, + GLint border, + GLenum format, + GLenum type, + const void* pixels) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glTexImage2D(target, + level, + internalformat, + width, + height, + border, + format, + type, + pixels); + } +} + +void TexParameterf(PP_Resource context_id, + GLenum target, + GLenum pname, + GLfloat param) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glTexParameterf(target, pname, param); + } +} + +void TexParameterfv(PP_Resource context_id, + GLenum target, + GLenum pname, + const GLfloat* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glTexParameterfv(target, pname, params); + } +} + +void TexParameteri(PP_Resource context_id, + GLenum target, + GLenum pname, + GLint param) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glTexParameteri(target, pname, param); + } +} + +void TexParameteriv(PP_Resource context_id, + GLenum target, + GLenum pname, + const GLint* params) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glTexParameteriv(target, pname, params); + } +} + +void TexSubImage2D(PP_Resource context_id, + GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLsizei width, + GLsizei height, + GLenum format, + GLenum type, + const void* pixels) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glTexSubImage2D( + target, level, xoffset, yoffset, width, height, format, type, pixels); + } +} + +void Uniform1f(PP_Resource context_id, GLint location, GLfloat x) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform1f(location, x); + } +} + +void Uniform1fv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLfloat* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform1fv(location, count, v); + } +} + +void Uniform1i(PP_Resource context_id, GLint location, GLint x) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform1i(location, x); + } +} + +void Uniform1iv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLint* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform1iv(location, count, v); + } +} + +void Uniform2f(PP_Resource context_id, GLint location, GLfloat x, GLfloat y) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform2f(location, x, y); + } +} + +void Uniform2fv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLfloat* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform2fv(location, count, v); + } +} + +void Uniform2i(PP_Resource context_id, GLint location, GLint x, GLint y) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform2i(location, x, y); + } +} + +void Uniform2iv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLint* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform2iv(location, count, v); + } +} + +void Uniform3f(PP_Resource context_id, + GLint location, + GLfloat x, + GLfloat y, + GLfloat z) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform3f(location, x, y, z); + } +} + +void Uniform3fv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLfloat* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform3fv(location, count, v); + } +} + +void Uniform3i(PP_Resource context_id, + GLint location, + GLint x, + GLint y, + GLint z) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform3i(location, x, y, z); + } +} + +void Uniform3iv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLint* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform3iv(location, count, v); + } +} + +void Uniform4f(PP_Resource context_id, + GLint location, + GLfloat x, + GLfloat y, + GLfloat z, + GLfloat w) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform4f(location, x, y, z, w); + } +} + +void Uniform4fv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLfloat* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform4fv(location, count, v); + } +} + +void Uniform4i(PP_Resource context_id, + GLint location, + GLint x, + GLint y, + GLint z, + GLint w) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform4i(location, x, y, z, w); + } +} + +void Uniform4iv(PP_Resource context_id, + GLint location, + GLsizei count, + const GLint* v) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniform4iv(location, count, v); + } +} + +void UniformMatrix2fv(PP_Resource context_id, + GLint location, + GLsizei count, + GLboolean transpose, + const GLfloat* value) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniformMatrix2fv(location, count, transpose, value); + } +} + +void UniformMatrix3fv(PP_Resource context_id, + GLint location, + GLsizei count, + GLboolean transpose, + const GLfloat* value) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniformMatrix3fv(location, count, transpose, value); + } +} + +void UniformMatrix4fv(PP_Resource context_id, + GLint location, + GLsizei count, + GLboolean transpose, + const GLfloat* value) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUniformMatrix4fv(location, count, transpose, value); + } +} + +void UseProgram(PP_Resource context_id, GLuint program) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glUseProgram(program); + } +} + +void ValidateProgram(PP_Resource context_id, GLuint program) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glValidateProgram(program); + } +} + +void VertexAttrib1f(PP_Resource context_id, GLuint indx, GLfloat x) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib1f(indx, x); + } +} + +void VertexAttrib1fv(PP_Resource context_id, + GLuint indx, + const GLfloat* values) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib1fv(indx, values); + } +} + +void VertexAttrib2f(PP_Resource context_id, GLuint indx, GLfloat x, GLfloat y) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib2f(indx, x, y); + } +} + +void VertexAttrib2fv(PP_Resource context_id, + GLuint indx, + const GLfloat* values) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib2fv(indx, values); + } +} + +void VertexAttrib3f(PP_Resource context_id, + GLuint indx, + GLfloat x, + GLfloat y, + GLfloat z) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib3f(indx, x, y, z); + } +} + +void VertexAttrib3fv(PP_Resource context_id, + GLuint indx, + const GLfloat* values) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib3fv(indx, values); + } +} + +void VertexAttrib4f(PP_Resource context_id, + GLuint indx, + GLfloat x, + GLfloat y, + GLfloat z, + GLfloat w) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib4f(indx, x, y, z, w); + } +} + +void VertexAttrib4fv(PP_Resource context_id, + GLuint indx, + const GLfloat* values) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttrib4fv(indx, values); + } +} + +void VertexAttribPointer(PP_Resource context_id, + GLuint indx, + GLint size, + GLenum type, + GLboolean normalized, + GLsizei stride, + const void* ptr) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glVertexAttribPointer(indx, size, type, normalized, stride, ptr); + } +} + +void Viewport(PP_Resource context_id, + GLint x, + GLint y, + GLsizei width, + GLsizei height) { + Enter3D enter(context_id, true); + if (IsBoundGraphics(&enter)) { + glViewport(x, y, width, height); + } +} + +} // namespace + +const PPB_OpenGLES2* GetPPB_OpenGLES2_Thunk() { + static const struct PPB_OpenGLES2 ppb_opengles2 = { + &ActiveTexture, &AttachShader, + &BindAttribLocation, &BindBuffer, + &BindFramebuffer, &BindRenderbuffer, + &BindTexture, &BlendColor, + &BlendEquation, &BlendEquationSeparate, + &BlendFunc, &BlendFuncSeparate, + &BufferData, &BufferSubData, + &CheckFramebufferStatus, &Clear, + &ClearColor, &ClearDepthf, + &ClearStencil, &ColorMask, + &CompileShader, &CompressedTexImage2D, + &CompressedTexSubImage2D, &CopyTexImage2D, + &CopyTexSubImage2D, &CreateProgram, + &CreateShader, &CullFace, + &DeleteBuffers, &DeleteFramebuffers, + &DeleteProgram, &DeleteRenderbuffers, + &DeleteShader, &DeleteTextures, + &DepthFunc, &DepthMask, + &DepthRangef, &DetachShader, + &Disable, &DisableVertexAttribArray, + &DrawArrays, &DrawElements, + &Enable, &EnableVertexAttribArray, + &Finish, &Flush, + &FramebufferRenderbuffer, &FramebufferTexture2D, + &FrontFace, &GenBuffers, + &GenerateMipmap, &GenFramebuffers, + &GenRenderbuffers, &GenTextures, + &GetActiveAttrib, &GetActiveUniform, + &GetAttachedShaders, &GetAttribLocation, + &GetBooleanv, &GetBufferParameteriv, + &GetError, &GetFloatv, + &GetFramebufferAttachmentParameteriv, &GetIntegerv, + &GetProgramiv, &GetProgramInfoLog, + &GetRenderbufferParameteriv, &GetShaderiv, + &GetShaderInfoLog, &GetShaderPrecisionFormat, + &GetShaderSource, &GetString, + &GetTexParameterfv, &GetTexParameteriv, + &GetUniformfv, &GetUniformiv, + &GetUniformLocation, &GetVertexAttribfv, + &GetVertexAttribiv, &GetVertexAttribPointerv, + &Hint, &IsBuffer, + &IsEnabled, &IsFramebuffer, + &IsProgram, &IsRenderbuffer, + &IsShader, &IsTexture, + &LineWidth, &LinkProgram, + &PixelStorei, &PolygonOffset, + &ReadPixels, &ReleaseShaderCompiler, + &RenderbufferStorage, &SampleCoverage, + &Scissor, &ShaderBinary, + &ShaderSource, &StencilFunc, + &StencilFuncSeparate, &StencilMask, + &StencilMaskSeparate, &StencilOp, + &StencilOpSeparate, &TexImage2D, + &TexParameterf, &TexParameterfv, + &TexParameteri, &TexParameteriv, + &TexSubImage2D, &Uniform1f, + &Uniform1fv, &Uniform1i, + &Uniform1iv, &Uniform2f, + &Uniform2fv, &Uniform2i, + &Uniform2iv, &Uniform3f, + &Uniform3fv, &Uniform3i, + &Uniform3iv, &Uniform4f, + &Uniform4fv, &Uniform4i, + &Uniform4iv, &UniformMatrix2fv, + &UniformMatrix3fv, &UniformMatrix4fv, + &UseProgram, &ValidateProgram, + &VertexAttrib1f, &VertexAttrib1fv, + &VertexAttrib2f, &VertexAttrib2fv, + &VertexAttrib3f, &VertexAttrib3fv, + &VertexAttrib4f, &VertexAttrib4fv, + &VertexAttribPointer, &Viewport}; + return &ppb_opengles2; +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/resource_creation_impl.cc b/chromium/mojo/examples/pepper_container_app/resource_creation_impl.cc new file mode 100644 index 00000000000..832602d9687 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/resource_creation_impl.cc @@ -0,0 +1,409 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/examples/pepper_container_app/resource_creation_impl.h" + +#include "base/logging.h" +#include "mojo/examples/pepper_container_app/graphics_3d_resource.h" + +namespace mojo { +namespace examples { + +ResourceCreationImpl::ResourceCreationImpl() {} + +ResourceCreationImpl::~ResourceCreationImpl() {} + +PP_Resource ResourceCreationImpl::CreateFileIO(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFileRef( + PP_Instance instance, + const ppapi::FileRefCreateInfo& create_info) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFileSystem( + PP_Instance instance, + PP_FileSystemType type) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateIMEInputEvent( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + struct PP_Var text, + uint32_t segment_number, + const uint32_t* segment_offsets, + int32_t target_segment, + uint32_t selection_start, + uint32_t selection_end) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateKeyboardInputEvent_1_0( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers, + uint32_t key_code, + struct PP_Var character_text) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateKeyboardInputEvent_1_2( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers, + uint32_t key_code, + struct PP_Var character_text, + struct PP_Var code) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateMouseInputEvent( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers, + PP_InputEvent_MouseButton mouse_button, + const PP_Point* mouse_position, + int32_t click_count, + const PP_Point* mouse_movement) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTouchInputEvent( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTrueTypeFont( + PP_Instance instance, + const PP_TrueTypeFontDesc_Dev* desc) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateURLLoader(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateURLRequestInfo( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateWheelInputEvent( + PP_Instance instance, + PP_TimeTicks time_stamp, + uint32_t modifiers, + const PP_FloatPoint* wheel_delta, + const PP_FloatPoint* wheel_ticks, + PP_Bool scroll_by_page) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateAudio1_0( + PP_Instance instance, + PP_Resource config_id, + PPB_Audio_Callback_1_0 audio_callback, + void* user_data) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateAudio( + PP_Instance instance, + PP_Resource config_id, + PPB_Audio_Callback audio_callback, + void* user_data) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateAudioTrusted(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateAudioConfig( + PP_Instance instance, + PP_AudioSampleRate sample_rate, + uint32_t sample_frame_count) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateCompositor(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFileChooser( + PP_Instance instance, + PP_FileChooserMode_Dev mode, + const PP_Var& accept_types) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateGraphics2D(PP_Instance instance, + const PP_Size* size, + PP_Bool is_always_opaque) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateGraphics3D( + PP_Instance instance, + PP_Resource share_context, + const int32_t* attrib_list) { + return (new Graphics3DResource(instance))->GetReference(); +} + +PP_Resource ResourceCreationImpl::CreateGraphics3DRaw( + PP_Instance instance, + PP_Resource share_context, + const int32_t* attrib_list) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateHostResolver(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateHostResolverPrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateImageData( + PP_Instance instance, + PP_ImageDataFormat format, + const PP_Size* size, + PP_Bool init_to_zero) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateImageDataSimple( + PP_Instance instance, + PP_ImageDataFormat format, + const PP_Size* size, + PP_Bool init_to_zero) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateMediaStreamVideoTrack( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateNetAddressFromIPv4Address( + PP_Instance instance, + const PP_NetAddress_IPv4* ipv4_addr) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateNetAddressFromIPv6Address( + PP_Instance instance, + const PP_NetAddress_IPv6* ipv6_addr) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateNetAddressFromNetAddressPrivate( + PP_Instance instance, + const PP_NetAddress_Private& private_addr) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateNetworkMonitor( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateOutputProtectionPrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreatePrinting(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTCPServerSocketPrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTCPSocket1_0( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTCPSocket( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTCPSocketPrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateUDPSocket(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateUDPSocketPrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateVideoDecoder(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateVideoDestination( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateVideoSource( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateWebSocket(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateX509CertificatePrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +#if !defined(OS_NACL) +PP_Resource ResourceCreationImpl::CreateAudioInput( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateBroker(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateBrowserFont( + PP_Instance instance, + const PP_BrowserFont_Trusted_Description* description) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateBuffer(PP_Instance instance, + uint32_t size) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFlashDRM(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFlashFontFile( + PP_Instance instance, + const PP_BrowserFont_Trusted_Description* description, + PP_PrivateFontCharset charset) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFlashMenu( + PP_Instance instance, + const PP_Flash_Menu* menu_data) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateFlashMessageLoop( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreatePlatformVerificationPrivate( + PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateScrollbar(PP_Instance instance, + PP_Bool vertical) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateTalk(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateVideoCapture(PP_Instance instance) { + NOTIMPLEMENTED(); + return 0; +} + +PP_Resource ResourceCreationImpl::CreateVideoDecoderDev( + PP_Instance instance, + PP_Resource context3d_id, + PP_VideoDecoder_Profile profile) { + NOTIMPLEMENTED(); + return 0; +} +#endif // !defined(OS_NACL) + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/pepper_container_app/resource_creation_impl.h b/chromium/mojo/examples/pepper_container_app/resource_creation_impl.h new file mode 100644 index 00000000000..14f4ca3f03c --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/resource_creation_impl.h @@ -0,0 +1,178 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_RESOURCE_CREATION_IMPL_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_RESOURCE_CREATION_IMPL_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "ppapi/thunk/resource_creation_api.h" + +namespace mojo { +namespace examples { + +class ResourceCreationImpl : public ppapi::thunk::ResourceCreationAPI { + public: + ResourceCreationImpl(); + virtual ~ResourceCreationImpl(); + + // ppapi::thunk::ResourceCreationAPI implementation. + virtual PP_Resource CreateFileIO(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateFileRef( + PP_Instance instance, + const ppapi::FileRefCreateInfo& create_info) OVERRIDE; + virtual PP_Resource CreateFileSystem(PP_Instance instance, + PP_FileSystemType type) OVERRIDE; + virtual PP_Resource CreateIMEInputEvent(PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + struct PP_Var text, + uint32_t segment_number, + const uint32_t* segment_offsets, + int32_t target_segment, + uint32_t selection_start, + uint32_t selection_end) OVERRIDE; + virtual PP_Resource CreateKeyboardInputEvent_1_0( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers, + uint32_t key_code, + PP_Var character_text) OVERRIDE; + virtual PP_Resource CreateKeyboardInputEvent_1_2( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers, + uint32_t key_code, + PP_Var character_text, + PP_Var code) OVERRIDE; + virtual PP_Resource CreateMouseInputEvent( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers, + PP_InputEvent_MouseButton mouse_button, + const PP_Point* mouse_position, + int32_t click_count, + const PP_Point* mouse_movement) OVERRIDE; + virtual PP_Resource CreateTouchInputEvent( + PP_Instance instance, + PP_InputEvent_Type type, + PP_TimeTicks time_stamp, + uint32_t modifiers) OVERRIDE; + virtual PP_Resource CreateTrueTypeFont( + PP_Instance instance, + const PP_TrueTypeFontDesc_Dev* desc) OVERRIDE; + virtual PP_Resource CreateURLLoader(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateURLRequestInfo( + PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateWheelInputEvent( + PP_Instance instance, + PP_TimeTicks time_stamp, + uint32_t modifiers, + const PP_FloatPoint* wheel_delta, + const PP_FloatPoint* wheel_ticks, + PP_Bool scroll_by_page) OVERRIDE; + virtual PP_Resource CreateAudio1_0(PP_Instance instance, + PP_Resource config_id, + PPB_Audio_Callback_1_0 audio_callback, + void* user_data) OVERRIDE; + virtual PP_Resource CreateAudio(PP_Instance instance, + PP_Resource config_id, + PPB_Audio_Callback audio_callback, + void* user_data) OVERRIDE; + virtual PP_Resource CreateAudioTrusted(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateAudioConfig(PP_Instance instance, + PP_AudioSampleRate sample_rate, + uint32_t sample_frame_count) OVERRIDE; + virtual PP_Resource CreateCompositor(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateFileChooser(PP_Instance instance, + PP_FileChooserMode_Dev mode, + const PP_Var& accept_types) OVERRIDE; + virtual PP_Resource CreateGraphics2D(PP_Instance pp_instance, + const PP_Size* size, + PP_Bool is_always_opaque) OVERRIDE; + virtual PP_Resource CreateGraphics3D(PP_Instance instance, + PP_Resource share_context, + const int32_t* attrib_list) OVERRIDE; + virtual PP_Resource CreateGraphics3DRaw( + PP_Instance instance, + PP_Resource share_context, + const int32_t* attrib_list) OVERRIDE; + virtual PP_Resource CreateHostResolver(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateHostResolverPrivate(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateImageData(PP_Instance instance, + PP_ImageDataFormat format, + const PP_Size* size, + PP_Bool init_to_zero) OVERRIDE; + virtual PP_Resource CreateImageDataSimple(PP_Instance instance, + PP_ImageDataFormat format, + const PP_Size* size, + PP_Bool init_to_zero) OVERRIDE; + virtual PP_Resource CreateMediaStreamVideoTrack( + PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateNetAddressFromIPv4Address( + PP_Instance instance, + const PP_NetAddress_IPv4* ipv4_addr) OVERRIDE; + virtual PP_Resource CreateNetAddressFromIPv6Address( + PP_Instance instance, + const PP_NetAddress_IPv6* ipv6_addr) OVERRIDE; + virtual PP_Resource CreateNetAddressFromNetAddressPrivate( + PP_Instance instance, + const PP_NetAddress_Private& private_addr) OVERRIDE; + virtual PP_Resource CreateNetworkMonitor(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateOutputProtectionPrivate( + PP_Instance instance) OVERRIDE; + virtual PP_Resource CreatePrinting(PP_Instance) OVERRIDE; + virtual PP_Resource CreateTCPServerSocketPrivate( + PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateTCPSocket1_0(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateTCPSocket(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateUDPSocket(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateVideoDecoder(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateVideoDestination(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateVideoSource(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateX509CertificatePrivate( + PP_Instance instance) OVERRIDE; +#if !defined(OS_NACL) + virtual PP_Resource CreateAudioInput(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateBroker(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateBrowserFont( + PP_Instance instance, + const PP_BrowserFont_Trusted_Description* description) OVERRIDE; + virtual PP_Resource CreateBuffer(PP_Instance instance, + uint32_t size) OVERRIDE; + virtual PP_Resource CreateFlashDRM(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateFlashFontFile( + PP_Instance instance, + const PP_BrowserFont_Trusted_Description* description, + PP_PrivateFontCharset charset) OVERRIDE; + virtual PP_Resource CreateFlashMenu(PP_Instance instance, + const PP_Flash_Menu* menu_data) OVERRIDE; + virtual PP_Resource CreateFlashMessageLoop(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreatePlatformVerificationPrivate( + PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateScrollbar(PP_Instance instance, + PP_Bool vertical) OVERRIDE; + virtual PP_Resource CreateTalk(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateVideoCapture(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateVideoDecoderDev( + PP_Instance instance, + PP_Resource context3d_id, + PP_VideoDecoder_Profile profile) OVERRIDE; +#endif // !defined(OS_NACL) + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceCreationImpl); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_RESOURCE_CREATION_IMPL_H_ diff --git a/chromium/mojo/examples/pepper_container_app/thunk.h b/chromium/mojo/examples/pepper_container_app/thunk.h new file mode 100644 index 00000000000..f3b3d548257 --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/thunk.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_THUNK_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_THUNK_H_ + +#include "ppapi/c/ppb_core.h" +#include "ppapi/c/ppb_opengles2.h" + +namespace mojo { +namespace examples { + +const PPB_Core_1_0* GetPPB_Core_1_0_Thunk(); +const PPB_OpenGLES2* GetPPB_OpenGLES2_Thunk(); + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_THUNK_H_ diff --git a/chromium/mojo/examples/pepper_container_app/type_converters.h b/chromium/mojo/examples/pepper_container_app/type_converters.h new file mode 100644 index 00000000000..0f02a0a521b --- /dev/null +++ b/chromium/mojo/examples/pepper_container_app/type_converters.h @@ -0,0 +1,74 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EXAMPLES_PEPPER_CONTAINER_APP_TYPE_CONVERTERS_H_ +#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_TYPE_CONVERTERS_H_ + +#include "mojo/public/cpp/bindings/type_converter.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ppapi/c/pp_point.h" +#include "ppapi/c/pp_rect.h" +#include "ppapi/c/pp_size.h" + +namespace mojo { + +template <> +class TypeConverter<PointPtr, PP_Point> { + public: + static PointPtr ConvertFrom(const PP_Point& input) { + PointPtr point(Point::New()); + point->x = input.x; + point->y = input.y; + return point.Pass(); + } + + static PP_Point ConvertTo(const PointPtr& input) { + if (!input) + return PP_MakePoint(0, 0); + return PP_MakePoint(static_cast<int32_t>(input->x), + static_cast<int32_t>(input->y)); + } +}; + +template <> +class TypeConverter<SizePtr, PP_Size> { + public: + static SizePtr ConvertFrom(const PP_Size& input) { + SizePtr size(Size::New()); + size->width = input.width; + size->height = input.height; + return size.Pass(); + } + + static PP_Size ConvertTo(const SizePtr& input) { + if (!input) + return PP_MakeSize(0, 0); + return PP_MakeSize(static_cast<int32_t>(input->width), + static_cast<int32_t>(input->height)); + } +}; + +template <> +class TypeConverter<RectPtr, PP_Rect> { + public: + static RectPtr ConvertFrom(const PP_Rect& input) { + RectPtr rect(Rect::New()); + rect->x = input.point.x; + rect->y = input.point.y; + rect->width = input.size.width; + rect->height = input.size.height; + return rect.Pass(); + } + + static PP_Rect ConvertTo(const RectPtr& input) { + if (!input) + return PP_MakeRectFromXYWH(0, 0, 0, 0); + return PP_MakeRectFromXYWH(input->x, input->y, + input->width, input->height); + } +}; + +} // namespace mojo + +#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_TYPE_CONVERTERS_H_ diff --git a/chromium/mojo/examples/sample_app/DEPS b/chromium/mojo/examples/sample_app/DEPS new file mode 100644 index 00000000000..49b9c281a7d --- /dev/null +++ b/chromium/mojo/examples/sample_app/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+ui/events/event_constants.h", # pending enum support in mojom + "+ui/gfx", # TODO(beng): trim the size of this dep. +] diff --git a/chromium/mojo/examples/sample_app/gles2_client_impl.cc b/chromium/mojo/examples/sample_app/gles2_client_impl.cc new file mode 100644 index 00000000000..eb571b49a77 --- /dev/null +++ b/chromium/mojo/examples/sample_app/gles2_client_impl.cc @@ -0,0 +1,134 @@ +// 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 "mojo/examples/sample_app/gles2_client_impl.h" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <math.h> +#include <stdlib.h> + +#include "mojo/public/c/gles2/gles2.h" +#include "ui/events/event_constants.h" + +namespace mojo { +namespace examples { +namespace { + +float CalculateDragDistance(const gfx::PointF& start, const Point& end) { + return hypot(start.x() - end.x, start.y() - end.y); +} + +float GetRandomColor() { + return static_cast<float>(rand()) / static_cast<float>(RAND_MAX); +} + +} + +GLES2ClientImpl::GLES2ClientImpl(CommandBufferPtr command_buffer) + : getting_animation_frames_(false) { + context_ = MojoGLES2CreateContext( + command_buffer.PassMessagePipe().release().value(), + &ContextLostThunk, + &DrawAnimationFrameThunk, + this); + MojoGLES2MakeCurrent(context_); +} + +GLES2ClientImpl::~GLES2ClientImpl() { + MojoGLES2DestroyContext(context_); +} + +void GLES2ClientImpl::SetSize(const Size& size) { + size_ = gfx::Size(size.width, size.height); + if (size_.IsEmpty()) + return; + cube_.Init(size_.width(), size_.height()); + RequestAnimationFrames(); +} + +void GLES2ClientImpl::HandleInputEvent(const Event& event) { + switch (event.action) { + case ui::ET_MOUSE_PRESSED: + case ui::ET_TOUCH_PRESSED: + if (event.flags & ui::EF_RIGHT_MOUSE_BUTTON) + break; + CancelAnimationFrames(); + capture_point_.SetPoint(event.location->x, event.location->y); + last_drag_point_ = capture_point_; + drag_start_time_ = GetTimeTicksNow(); + break; + case ui::ET_MOUSE_DRAGGED: + case ui::ET_TOUCH_MOVED: + if (event.flags & ui::EF_RIGHT_MOUSE_BUTTON) + break; + if (!getting_animation_frames_) { + int direction = event.location->y < last_drag_point_.y() || + event.location->x > last_drag_point_.x() ? 1 : -1; + cube_.set_direction(direction); + cube_.UpdateForDragDistance( + CalculateDragDistance(last_drag_point_, *event.location)); + cube_.Draw(); + MojoGLES2SwapBuffers(); + + last_drag_point_.SetPoint(event.location->x, event.location->y); + } + break; + case ui::ET_MOUSE_RELEASED: + case ui::ET_TOUCH_RELEASED: { + if (event.flags & ui::EF_RIGHT_MOUSE_BUTTON) { + cube_.set_color(GetRandomColor(), GetRandomColor(), GetRandomColor()); + break; + } + MojoTimeTicks offset = GetTimeTicksNow() - drag_start_time_; + float delta = static_cast<float>(offset) / 1000000.; + cube_.SetFlingMultiplier( + CalculateDragDistance(capture_point_, *event.location), + delta); + + capture_point_ = last_drag_point_ = gfx::PointF(); + RequestAnimationFrames(); + break; + } + default: + break; + } +} + +void GLES2ClientImpl::ContextLost() { + CancelAnimationFrames(); +} + +void GLES2ClientImpl::ContextLostThunk(void* closure) { + static_cast<GLES2ClientImpl*>(closure)->ContextLost(); +} + +void GLES2ClientImpl::DrawAnimationFrame() { + MojoTimeTicks now = GetTimeTicksNow(); + MojoTimeTicks offset = now - last_time_; + float delta = static_cast<float>(offset) / 1000000.; + last_time_ = now; + cube_.UpdateForTimeDelta(delta); + cube_.Draw(); + + MojoGLES2SwapBuffers(); +} + +void GLES2ClientImpl::DrawAnimationFrameThunk(void* closure) { + static_cast<GLES2ClientImpl*>(closure)->DrawAnimationFrame(); +} + +void GLES2ClientImpl::RequestAnimationFrames() { + getting_animation_frames_ = true; + MojoGLES2RequestAnimationFrames(context_); + last_time_ = GetTimeTicksNow(); +} + +void GLES2ClientImpl::CancelAnimationFrames() { + getting_animation_frames_ = false; + MojoGLES2CancelAnimationFrames(context_); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/sample_app/gles2_client_impl.h b/chromium/mojo/examples/sample_app/gles2_client_impl.h new file mode 100644 index 00000000000..6791504fbc1 --- /dev/null +++ b/chromium/mojo/examples/sample_app/gles2_client_impl.h @@ -0,0 +1,50 @@ +// 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 MOJO_EXAMPLES_SAMPLE_APP_GLES2_CLIENT_IMPL_H_ +#define MOJO_EXAMPLES_SAMPLE_APP_GLES2_CLIENT_IMPL_H_ + +#include "mojo/examples/sample_app/spinning_cube.h" +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ui/gfx/point_f.h" +#include "ui/gfx/size.h" + +namespace mojo { +namespace examples { + +class GLES2ClientImpl { + public: + explicit GLES2ClientImpl(CommandBufferPtr command_buffer); + virtual ~GLES2ClientImpl(); + + void SetSize(const Size& size); + void HandleInputEvent(const Event& event); + + private: + void ContextLost(); + static void ContextLostThunk(void* closure); + void DrawAnimationFrame(); + static void DrawAnimationFrameThunk(void* closure); + + void RequestAnimationFrames(); + void CancelAnimationFrames(); + + MojoTimeTicks last_time_; + gfx::Size size_; + SpinningCube cube_; + gfx::PointF capture_point_; + gfx::PointF last_drag_point_; + MojoTimeTicks drag_start_time_; + bool getting_animation_frames_; + + MojoGLES2Context context_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(GLES2ClientImpl); +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_SAMPLE_APP_GLES2_CLIENT_IMPL_H_ diff --git a/chromium/mojo/examples/sample_app/sample_app.cc b/chromium/mojo/examples/sample_app/sample_app.cc new file mode 100644 index 00000000000..b6a20b71709 --- /dev/null +++ b/chromium/mojo/examples/sample_app/sample_app.cc @@ -0,0 +1,84 @@ +// 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 <stdio.h> +#include <string> + +#include "mojo/examples/sample_app/gles2_client_impl.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" + +namespace mojo { +namespace examples { + +class SampleApp : public Application, public NativeViewportClient { + public: + SampleApp() {} + + virtual ~SampleApp() { + // TODO(darin): Fix shutdown so we don't need to leak this. + MOJO_ALLOW_UNUSED GLES2ClientImpl* leaked = gles2_client_.release(); + } + + virtual void Initialize() MOJO_OVERRIDE { + ConnectTo("mojo:mojo_native_viewport_service", &viewport_); + viewport_.set_client(this); + + RectPtr rect(Rect::New()); + rect->x = 10; + rect->y = 10; + rect->width = 800; + rect->height = 600; + viewport_->Create(rect.Pass()); + viewport_->Show(); + + CommandBufferPtr command_buffer; + viewport_->CreateGLES2Context(Get(&command_buffer)); + gles2_client_.reset(new GLES2ClientImpl(command_buffer.Pass())); + } + + virtual void OnCreated() MOJO_OVERRIDE { + } + + virtual void OnDestroyed() MOJO_OVERRIDE { + RunLoop::current()->Quit(); + } + + virtual void OnBoundsChanged(RectPtr bounds) MOJO_OVERRIDE { + assert(bounds); + SizePtr size(Size::New()); + size->width = bounds->width; + size->height = bounds->height; + gles2_client_->SetSize(*size); + } + + virtual void OnEvent(EventPtr event, + const Callback<void()>& callback) MOJO_OVERRIDE { + assert(event); + if (event->location) + gles2_client_->HandleInputEvent(*event); + callback.Run(); + } + + private: + mojo::GLES2Initializer gles2; + scoped_ptr<GLES2ClientImpl> gles2_client_; + NativeViewportPtr viewport_; + + DISALLOW_COPY_AND_ASSIGN(SampleApp); +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::SampleApp(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/sample_app/spinning_cube.cc b/chromium/mojo/examples/sample_app/spinning_cube.cc new file mode 100644 index 00000000000..024fdca93bf --- /dev/null +++ b/chromium/mojo/examples/sample_app/spinning_cube.cc @@ -0,0 +1,472 @@ +// 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. + +// This example program is based on Simple_VertexShader.c from: + +// +// Book: OpenGL(R) ES 2.0 Programming Guide +// Authors: Aaftab Munshi, Dan Ginsburg, Dave Shreiner +// ISBN-10: 0321502795 +// ISBN-13: 9780321502797 +// Publisher: Addison-Wesley Professional +// URLs: http://safari.informit.com/9780321563835 +// http://www.opengles-book.com +// + +#include "mojo/examples/sample_app/spinning_cube.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +namespace mojo { +namespace examples { + +namespace { + +const float kPi = 3.14159265359f; + +int GenerateCube(GLuint *vbo_vertices, + GLuint *vbo_indices) { + const int num_indices = 36; + + const GLfloat cube_vertices[] = { + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + }; + + const GLushort cube_indices[] = { + 0, 2, 1, + 0, 3, 2, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11, + 12, 15, 14, + 12, 14, 13, + 16, 17, 18, + 16, 18, 19, + 20, 23, 22, + 20, 22, 21 + }; + + if (vbo_vertices) { + glGenBuffers(1, vbo_vertices); + glBindBuffer(GL_ARRAY_BUFFER, *vbo_vertices); + glBufferData(GL_ARRAY_BUFFER, + sizeof(cube_vertices), + cube_vertices, + GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + if (vbo_indices) { + glGenBuffers(1, vbo_indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *vbo_indices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + sizeof(cube_indices), + cube_indices, + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + return num_indices; +} + +GLuint LoadShader(GLenum type, + const char* shader_source) { + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &shader_source, NULL); + glCompileShader(shader); + + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) { + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint LoadProgram(const char* vertext_shader_source, + const char* fragment_shader_source) { + GLuint vertex_shader = LoadShader(GL_VERTEX_SHADER, + vertext_shader_source); + if (!vertex_shader) + return 0; + + GLuint fragment_shader = LoadShader(GL_FRAGMENT_SHADER, + fragment_shader_source); + if (!fragment_shader) { + glDeleteShader(vertex_shader); + return 0; + } + + GLuint program_object = glCreateProgram(); + glAttachShader(program_object, vertex_shader); + glAttachShader(program_object, fragment_shader); + + glLinkProgram(program_object); + + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + GLint linked = 0; + glGetProgramiv(program_object, GL_LINK_STATUS, &linked); + + if (!linked) { + glDeleteProgram(program_object); + return 0; + } + + return program_object; +} + +class ESMatrix { + public: + GLfloat m[4][4]; + + ESMatrix() { + LoadZero(); + } + + void LoadZero() { + memset(this, 0x0, sizeof(ESMatrix)); + } + + void LoadIdentity() { + LoadZero(); + m[0][0] = 1.0f; + m[1][1] = 1.0f; + m[2][2] = 1.0f; + m[3][3] = 1.0f; + } + + void Multiply(ESMatrix* a, ESMatrix* b) { + ESMatrix result; + for (int i = 0; i < 4; ++i) { + result.m[i][0] = (a->m[i][0] * b->m[0][0]) + + (a->m[i][1] * b->m[1][0]) + + (a->m[i][2] * b->m[2][0]) + + (a->m[i][3] * b->m[3][0]); + + result.m[i][1] = (a->m[i][0] * b->m[0][1]) + + (a->m[i][1] * b->m[1][1]) + + (a->m[i][2] * b->m[2][1]) + + (a->m[i][3] * b->m[3][1]); + + result.m[i][2] = (a->m[i][0] * b->m[0][2]) + + (a->m[i][1] * b->m[1][2]) + + (a->m[i][2] * b->m[2][2]) + + (a->m[i][3] * b->m[3][2]); + + result.m[i][3] = (a->m[i][0] * b->m[0][3]) + + (a->m[i][1] * b->m[1][3]) + + (a->m[i][2] * b->m[2][3]) + + (a->m[i][3] * b->m[3][3]); + } + *this = result; + } + + void Frustum(float left, + float right, + float bottom, + float top, + float near_z, + float far_z) { + float delta_x = right - left; + float delta_y = top - bottom; + float delta_z = far_z - near_z; + + if ((near_z <= 0.0f) || + (far_z <= 0.0f) || + (delta_z <= 0.0f) || + (delta_y <= 0.0f) || + (delta_y <= 0.0f)) + return; + + ESMatrix frust; + frust.m[0][0] = 2.0f * near_z / delta_x; + frust.m[0][1] = frust.m[0][2] = frust.m[0][3] = 0.0f; + + frust.m[1][1] = 2.0f * near_z / delta_y; + frust.m[1][0] = frust.m[1][2] = frust.m[1][3] = 0.0f; + + frust.m[2][0] = (right + left) / delta_x; + frust.m[2][1] = (top + bottom) / delta_y; + frust.m[2][2] = -(near_z + far_z) / delta_z; + frust.m[2][3] = -1.0f; + + frust.m[3][2] = -2.0f * near_z * far_z / delta_z; + frust.m[3][0] = frust.m[3][1] = frust.m[3][3] = 0.0f; + + Multiply(&frust, this); + } + + void Perspective(float fov_y, float aspect, float near_z, float far_z) { + GLfloat frustum_h = tanf(fov_y / 360.0f * kPi) * near_z; + GLfloat frustum_w = frustum_h * aspect; + Frustum(-frustum_w, frustum_w, -frustum_h, frustum_h, near_z, far_z); + } + + void Translate(GLfloat tx, GLfloat ty, GLfloat tz) { + m[3][0] += m[0][0] * tx + m[1][0] * ty + m[2][0] * tz; + m[3][1] += m[0][1] * tx + m[1][1] * ty + m[2][1] * tz; + m[3][2] += m[0][2] * tx + m[1][2] * ty + m[2][2] * tz; + m[3][3] += m[0][3] * tx + m[1][3] * ty + m[2][3] * tz; + } + + void Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { + GLfloat mag = sqrtf(x * x + y * y + z * z); + + GLfloat sin_angle = sinf(angle * kPi / 180.0f); + GLfloat cos_angle = cosf(angle * kPi / 180.0f); + if (mag > 0.0f) { + GLfloat xx, yy, zz, xy, yz, zx, xs, ys, zs; + GLfloat one_minus_cos; + ESMatrix rotation; + + x /= mag; + y /= mag; + z /= mag; + + xx = x * x; + yy = y * y; + zz = z * z; + xy = x * y; + yz = y * z; + zx = z * x; + xs = x * sin_angle; + ys = y * sin_angle; + zs = z * sin_angle; + one_minus_cos = 1.0f - cos_angle; + + rotation.m[0][0] = (one_minus_cos * xx) + cos_angle; + rotation.m[0][1] = (one_minus_cos * xy) - zs; + rotation.m[0][2] = (one_minus_cos * zx) + ys; + rotation.m[0][3] = 0.0F; + + rotation.m[1][0] = (one_minus_cos * xy) + zs; + rotation.m[1][1] = (one_minus_cos * yy) + cos_angle; + rotation.m[1][2] = (one_minus_cos * yz) - xs; + rotation.m[1][3] = 0.0F; + + rotation.m[2][0] = (one_minus_cos * zx) - ys; + rotation.m[2][1] = (one_minus_cos * yz) + xs; + rotation.m[2][2] = (one_minus_cos * zz) + cos_angle; + rotation.m[2][3] = 0.0F; + + rotation.m[3][0] = 0.0F; + rotation.m[3][1] = 0.0F; + rotation.m[3][2] = 0.0F; + rotation.m[3][3] = 1.0F; + + Multiply(&rotation, this); + } + } +}; + +float RotationForTimeDelta(float delta_time) { + return delta_time * 40.0f; +} + +float RotationForDragDistance(float drag_distance) { + return drag_distance / 5; // Arbitrary damping. +} + +} // namespace + +class SpinningCube::GLState { + public: + GLState(); + + void OnGLContextLost(); + + GLfloat angle_; // Survives losing the GL context. + + GLuint program_object_; + GLint position_location_; + GLint mvp_location_; + GLint color_location_; + GLuint vbo_vertices_; + GLuint vbo_indices_; + int num_indices_; + ESMatrix mvp_matrix_; +}; + +SpinningCube::GLState::GLState() + : angle_(0) { + OnGLContextLost(); +} + +void SpinningCube::GLState::OnGLContextLost() { + program_object_ = 0; + position_location_ = 0; + mvp_location_ = 0; + color_location_ = 0; + vbo_vertices_ = 0; + vbo_indices_ = 0; + num_indices_ = 0; +} + +SpinningCube::SpinningCube() + : initialized_(false), + width_(0), + height_(0), + state_(new GLState()), + fling_multiplier_(1.0f), + direction_(1), + color_() { + state_->angle_ = 45.0f; + set_color(0.0, 1.0, 0.0); +} + +SpinningCube::~SpinningCube() { + if (!initialized_) + return; + if (state_->vbo_vertices_) + glDeleteBuffers(1, &state_->vbo_vertices_); + if (state_->vbo_indices_) + glDeleteBuffers(1, &state_->vbo_indices_); + if (state_->program_object_) + glDeleteProgram(state_->program_object_); +} + +void SpinningCube::Init(uint32_t width, uint32_t height) { + width_ = width; + height_ = height; + + const char vertext_shader_source[] = + "uniform mat4 u_mvpMatrix; \n" + "attribute vec4 a_position; \n" + "void main() \n" + "{ \n" + " gl_Position = u_mvpMatrix * a_position; \n" + "} \n"; + + const char fragment_shader_source[] = + "precision mediump float; \n" + "uniform vec4 u_color; \n" + "void main() \n" + "{ \n" + " gl_FragColor = u_color; \n" + "} \n"; + + state_->program_object_ = LoadProgram( + vertext_shader_source, fragment_shader_source); + state_->position_location_ = glGetAttribLocation( + state_->program_object_, "a_position"); + state_->mvp_location_ = glGetUniformLocation( + state_->program_object_, "u_mvpMatrix"); + state_->color_location_ = glGetUniformLocation( + state_->program_object_, "u_color"); + state_->num_indices_ = GenerateCube( + &state_->vbo_vertices_, &state_->vbo_indices_); + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + initialized_ = true; +} + +void SpinningCube::OnGLContextLost() { + initialized_ = false; + height_ = 0; + width_ = 0; + state_->OnGLContextLost(); +} + +void SpinningCube::SetFlingMultiplier(float drag_distance, + float drag_time) { + fling_multiplier_ = RotationForDragDistance(drag_distance) / + RotationForTimeDelta(drag_time); + +} + +void SpinningCube::UpdateForTimeDelta(float delta_time) { + state_->angle_ += RotationForTimeDelta(delta_time) * fling_multiplier_; + if (state_->angle_ >= 360.0f) + state_->angle_ -= 360.0f; + + // Arbitrary 50-step linear reduction in spin speed. + if (fling_multiplier_ > 1.0f) { + fling_multiplier_ = + std::max(1.0f, fling_multiplier_ - (fling_multiplier_ - 1.0f) / 50); + } + + Update(); +} + +void SpinningCube::UpdateForDragDistance(float distance) { + state_->angle_ += RotationForDragDistance(distance); + if (state_->angle_ >= 360.0f ) + state_->angle_ -= 360.0f; + + Update(); +} + +void SpinningCube::Draw() { + glViewport(0, 0, width_, height_); + glClear(GL_COLOR_BUFFER_BIT); + glUseProgram(state_->program_object_); + glBindBuffer(GL_ARRAY_BUFFER, state_->vbo_vertices_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state_->vbo_indices_); + glVertexAttribPointer(state_->position_location_, + 3, + GL_FLOAT, + GL_FALSE, 3 * sizeof(GLfloat), + 0); + glEnableVertexAttribArray(state_->position_location_); + glUniformMatrix4fv(state_->mvp_location_, + 1, + GL_FALSE, + (GLfloat*) &state_->mvp_matrix_.m[0][0]); + glUniform4f(state_->color_location_, color_[0], color_[1], color_[2], 1.0); + glDrawElements(GL_TRIANGLES, + state_->num_indices_, + GL_UNSIGNED_SHORT, + 0); +} + +void SpinningCube::Update() { + float aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_); + + ESMatrix perspective; + perspective.LoadIdentity(); + perspective.Perspective(60.0f, aspect, 1.0f, 20.0f ); + + ESMatrix modelview; + modelview.LoadIdentity(); + modelview.Translate(0.0, 0.0, -2.0); + modelview.Rotate(state_->angle_ * direction_, 1.0, 0.0, 1.0); + + state_->mvp_matrix_.Multiply(&modelview, &perspective); +} + +} // namespace examples +} // namespace mojo diff --git a/chromium/mojo/examples/sample_app/spinning_cube.h b/chromium/mojo/examples/sample_app/spinning_cube.h new file mode 100644 index 00000000000..9f7cdc427a6 --- /dev/null +++ b/chromium/mojo/examples/sample_app/spinning_cube.h @@ -0,0 +1,51 @@ +// 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 MOJO_EXAMPLES_SAMPLE_APP_SPINNING_CUBE_H_ +#define MOJO_EXAMPLES_SAMPLE_APP_SPINNING_CUBE_H_ + +#include <stdint.h> + +#include "base/memory/scoped_ptr.h" + +namespace mojo { +namespace examples { + +class SpinningCube { + public: + SpinningCube(); + ~SpinningCube(); + + void Init(uint32_t width, uint32_t height); + void set_direction(int direction) { direction_ = direction; } + void set_color(float r, float g, float b) { + color_[0] = r; + color_[1] = g; + color_[2] = b; + } + void SetFlingMultiplier(float drag_distance, float drag_time); + void UpdateForTimeDelta(float delta_time); + void UpdateForDragDistance(float distance); + void Draw(); + + void OnGLContextLost(); + + private: + class GLState; + + void Update(); + + bool initialized_; + uint32_t width_; + uint32_t height_; + scoped_ptr<GLState> state_; + float fling_multiplier_; + int direction_; + float color_[3]; +}; + +} // namespace examples +} // namespace mojo + +#endif // MOJO_EXAMPLES_SAMPLE_APP_SPINNING_CUBE_H_ diff --git a/chromium/mojo/examples/wget/wget.cc b/chromium/mojo/examples/wget/wget.cc new file mode 100644 index 00000000000..8a0ad5ecd9e --- /dev/null +++ b/chromium/mojo/examples/wget/wget.cc @@ -0,0 +1,116 @@ +// 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 <stdio.h> + +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/interfaces/network/network_service.mojom.h" +#include "mojo/services/public/interfaces/network/url_loader.mojom.h" + +namespace mojo { +namespace examples { + +class WGetApp : public Application, public URLLoaderClient { + public: + virtual void Initialize() MOJO_OVERRIDE { + ConnectTo("mojo:mojo_network_service", &network_service_); + Start(); + } + + private: + virtual void OnReceivedRedirect(URLResponsePtr response, + const String& new_url, + const String& new_method) MOJO_OVERRIDE { + PrintResponse(response); + } + + virtual void OnReceivedResponse(URLResponsePtr response) MOJO_OVERRIDE { + PrintResponse(response); + PrintResponseBody(); + Start(); + } + + virtual void OnReceivedError(NetworkErrorPtr error) MOJO_OVERRIDE { + printf("Got error: %d (%s)\n", + error->code, error->description.get().c_str()); + } + + virtual void OnReceivedEndOfResponseBody() MOJO_OVERRIDE { + // Ignored. + } + + void Start() { + std::string url = PromptForURL(); + printf("Loading: %s\n", url.c_str()); + + network_service_->CreateURLLoader(Get(&url_loader_)); + url_loader_.set_client(this); + + URLRequestPtr request(URLRequest::New()); + request->url = url; + request->method = "GET"; + request->auto_follow_redirects = true; + + DataPipe data_pipe; + response_body_stream_ = data_pipe.consumer_handle.Pass(); + + url_loader_->Start(request.Pass(), data_pipe.producer_handle.Pass()); + } + + std::string PromptForURL() { + printf("Enter URL> "); + char buf[1024]; + if (scanf("%1023s", buf) != 1) + buf[0] = '\0'; + return buf; + } + + void PrintResponse(const URLResponsePtr& response) { + printf(">>> Headers <<< \n"); + printf(" %s\n", response->status_line.get().c_str()); + if (response->headers) { + for (size_t i = 0; i < response->headers.size(); ++i) + printf(" %s\n", response->headers[i].get().c_str()); + } + } + + void PrintResponseBody() { + // Read response body in blocking fashion. + printf(">>> Body <<<\n"); + + for (;;) { + char buf[512]; + uint32_t num_bytes = sizeof(buf); + MojoResult result = ReadDataRaw( + response_body_stream_.get(), + buf, + &num_bytes, + MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_SHOULD_WAIT) { + Wait(response_body_stream_.get(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE); + } else if (result == MOJO_RESULT_OK) { + fwrite(buf, num_bytes, 1, stdout); + } else { + break; + } + } + + printf("\n>>> EOF <<<\n"); + } + + NetworkServicePtr network_service_; + URLLoaderPtr url_loader_; + ScopedDataPipeConsumerHandle response_body_stream_; +}; + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::WGetApp(); +} + +} // namespace mojo diff --git a/chromium/mojo/examples/window_manager/DEPS b/chromium/mojo/examples/window_manager/DEPS new file mode 100644 index 00000000000..fe1d98e366d --- /dev/null +++ b/chromium/mojo/examples/window_manager/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/events", +] diff --git a/chromium/mojo/examples/window_manager/window_manager.cc b/chromium/mojo/examples/window_manager/window_manager.cc new file mode 100644 index 00000000000..71e910b08ff --- /dev/null +++ b/chromium/mojo/examples/window_manager/window_manager.cc @@ -0,0 +1,226 @@ +// 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/basictypes.h" +#include "base/bind.h" +#include "base/strings/stringprintf.h" +#include "mojo/examples/window_manager/window_manager.mojom.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "mojo/services/public/interfaces/launcher/launcher.mojom.h" +#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" +#include "ui/events/event_constants.h" + +#if defined CreateWindow +#undef CreateWindow +#endif + +using mojo::view_manager::Id; +using mojo::view_manager::Node; +using mojo::view_manager::NodeObserver; +using mojo::view_manager::View; +using mojo::view_manager::ViewManager; +using mojo::view_manager::ViewManagerDelegate; +using mojo::view_manager::ViewObserver; + +namespace mojo { +namespace examples { + +class WindowManager; + +namespace { + +const SkColor kColors[] = { SK_ColorYELLOW, + SK_ColorRED, + SK_ColorGREEN, + SK_ColorMAGENTA }; + +} // namespace + +class WindowManagerConnection : public InterfaceImpl<IWindowManager> { + public: + explicit WindowManagerConnection(WindowManager* window_manager) + : window_manager_(window_manager) {} + virtual ~WindowManagerConnection() {} + + private: + // Overridden from IWindowManager: + virtual void CloseWindow(Id node_id) OVERRIDE; + + WindowManager* window_manager_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerConnection); +}; + +class NavigatorHost : public InterfaceImpl<navigation::NavigatorHost> { + public: + explicit NavigatorHost(WindowManager* window_manager) + : window_manager_(window_manager) { + } + virtual ~NavigatorHost() { + } + private: + virtual void RequestNavigate( + uint32 source_node_id, + navigation::NavigationDetailsPtr nav_details) OVERRIDE; + WindowManager* window_manager_; + DISALLOW_COPY_AND_ASSIGN(NavigatorHost); +}; + +class WindowManager : public Application, + public ViewObserver, + public ViewManagerDelegate, + public InterfaceImpl<launcher::LauncherClient> { + public: + WindowManager() : launcher_ui_(NULL), view_manager_(NULL) {} + virtual ~WindowManager() {} + + void CloseWindow(Id node_id) { + Node* node = view_manager_->GetNodeById(node_id); + DCHECK(node); + std::vector<Node*>::iterator iter = + std::find(windows_.begin(), windows_.end(), node); + DCHECK(iter != windows_.end()); + windows_.erase(iter); + node->Destroy(); + } + + void RequestNavigate( + uint32 source_node_id, + navigation::NavigationDetailsPtr nav_details) { + if (!launcher_.get()) { + ConnectTo("mojo:mojo_launcher", &launcher_); + launcher_.set_client(this); + } + launcher_->Launch(nav_details->url); + } + + private: + // Overridden from Application: + virtual void Initialize() MOJO_OVERRIDE { + AddService<WindowManagerConnection>(this); + AddService<NavigatorHost>(this); + ViewManager::Create(this, this); + } + + // Overridden from ViewObserver: + virtual void OnViewInputEvent(View* view, const EventPtr& event) OVERRIDE { + if (event->action == ui::ET_MOUSE_RELEASED) { + std::string app_url; + if (event->flags & ui::EF_LEFT_MOUSE_BUTTON) + app_url = "mojo://mojo_embedded_app"; + else if (event->flags & ui::EF_RIGHT_MOUSE_BUTTON) + app_url = "mojo://mojo_nesting_app"; + if (app_url.empty()) + return; + + Node* node = view_manager_->GetNodeById(parent_node_id_); + navigation::NavigationDetailsPtr nav_details( + navigation::NavigationDetails::New()); + size_t index = node->children().size() - 1; + nav_details->url = base::StringPrintf( + "%s/%x", app_url.c_str(), kColors[index % arraysize(kColors)]); + navigation::ResponseDetailsPtr response; + CreateWindow(app_url, nav_details.Pass(), response.Pass()); + } + } + + // Overridden from ViewManagerDelegate: + virtual void OnRootAdded(ViewManager* view_manager, Node* root) OVERRIDE { + DCHECK(!view_manager_); + view_manager_ = view_manager; + + Node* node = Node::Create(view_manager); + view_manager->GetRoots().front()->AddChild(node); + node->SetBounds(gfx::Rect(800, 600)); + parent_node_id_ = node->id(); + + View* view = View::Create(view_manager); + node->SetActiveView(view); + view->SetColor(SK_ColorBLUE); + view->AddObserver(this); + + CreateLauncherUI(); + } + + // Overridden from LauncherClient: + virtual void OnLaunch( + const mojo::String& requested_url, const mojo::String& handler_url, + navigation::ResponseDetailsPtr response) OVERRIDE { + navigation::NavigationDetailsPtr nav_details( + navigation::NavigationDetails::New()); + nav_details->url = requested_url; + CreateWindow(handler_url, nav_details.Pass(), response.Pass()); + } + + void CreateLauncherUI() { + navigation::NavigationDetailsPtr nav_details; + navigation::ResponseDetailsPtr response; + launcher_ui_ = CreateChild("mojo:mojo_browser", gfx::Rect(25, 25, 400, 25), + nav_details.Pass(), response.Pass()); + } + + void CreateWindow(const std::string& handler_url, + navigation::NavigationDetailsPtr nav_details, + navigation::ResponseDetailsPtr response) { + gfx::Rect bounds(25, 75, 400, 400); + if (!windows_.empty()) { + gfx::Point position = windows_.back()->bounds().origin(); + position.Offset(35, 35); + bounds.set_origin(position); + } + windows_.push_back(CreateChild(handler_url, bounds, nav_details.Pass(), + response.Pass())); + } + + Node* CreateChild(const std::string& url, + const gfx::Rect& bounds, + navigation::NavigationDetailsPtr nav_details, + navigation::ResponseDetailsPtr response) { + Node* node = view_manager_->GetNodeById(parent_node_id_); + Node* embedded = Node::Create(view_manager_); + node->AddChild(embedded); + embedded->SetBounds(bounds); + embedded->Embed(url); + + if (nav_details.get()) { + navigation::NavigatorPtr navigator; + ConnectTo(url, &navigator); + navigator->Navigate(embedded->id(), nav_details.Pass(), response.Pass()); + } + + return embedded; + } + + launcher::LauncherPtr launcher_; + Node* launcher_ui_; + std::vector<Node*> windows_; + ViewManager* view_manager_; + Id parent_node_id_; + + DISALLOW_COPY_AND_ASSIGN(WindowManager); +}; + +void WindowManagerConnection::CloseWindow(Id node_id) { + window_manager_->CloseWindow(node_id); +} + +void NavigatorHost::RequestNavigate( + uint32 source_node_id, + navigation::NavigationDetailsPtr nav_details) { + window_manager_->RequestNavigate(source_node_id, nav_details.Pass()); +} + +} // namespace examples + +// static +Application* Application::Create() { + return new examples::WindowManager; +} + +} // namespace mojo diff --git a/chromium/mojo/examples/window_manager/window_manager.mojom b/chromium/mojo/examples/window_manager/window_manager.mojom new file mode 100644 index 00000000000..213c5e5ceb5 --- /dev/null +++ b/chromium/mojo/examples/window_manager/window_manager.mojom @@ -0,0 +1,11 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +interface IWindowManager { + CloseWindow(uint32 node_id); +}; + +} diff --git a/chromium/mojo/gles2/DEPS b/chromium/mojo/gles2/DEPS new file mode 100644 index 00000000000..a0e373e85c1 --- /dev/null +++ b/chromium/mojo/gles2/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+gpu/command_buffer/client", + "+gpu/command_buffer/common", +] diff --git a/chromium/mojo/gles2/README.md b/chromium/mojo/gles2/README.md new file mode 100644 index 00000000000..94b3997a5cc --- /dev/null +++ b/chromium/mojo/gles2/README.md @@ -0,0 +1,5 @@ +Mojo GLES2 +========== + +We export this dynamically linked library via mojo/public/gles2 in order to +hide the gpu/command_buffer/client dependency from clients of the Mojo API. diff --git a/chromium/mojo/gles2/command_buffer_client_impl.cc b/chromium/mojo/gles2/command_buffer_client_impl.cc new file mode 100644 index 00000000000..78ebf13d998 --- /dev/null +++ b/chromium/mojo/gles2/command_buffer_client_impl.cc @@ -0,0 +1,269 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/gles2/command_buffer_client_impl.h" + +#include <limits> + +#include "base/logging.h" +#include "base/process/process_handle.h" +#include "mojo/public/cpp/bindings/sync_dispatcher.h" +#include "mojo/services/gles2/command_buffer_type_conversions.h" +#include "mojo/services/gles2/mojo_buffer_backing.h" + +namespace mojo { +namespace gles2 { + +namespace { + +bool CreateMapAndDupSharedBuffer(size_t size, + void** memory, + mojo::ScopedSharedBufferHandle* handle, + mojo::ScopedSharedBufferHandle* duped) { + MojoResult result = mojo::CreateSharedBuffer(NULL, size, handle); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(handle->is_valid()); + + result = mojo::DuplicateBuffer(handle->get(), NULL, duped); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(duped->is_valid()); + + result = mojo::MapBuffer( + handle->get(), 0, size, memory, MOJO_MAP_BUFFER_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(*memory); + + return true; +} +} + +CommandBufferDelegate::~CommandBufferDelegate() {} + +void CommandBufferDelegate::ContextLost() {} +void CommandBufferDelegate::DrawAnimationFrame() {} + +CommandBufferClientImpl::CommandBufferClientImpl( + CommandBufferDelegate* delegate, + const MojoAsyncWaiter* async_waiter, + ScopedMessagePipeHandle command_buffer_handle) + : delegate_(delegate), + shared_state_(NULL), + last_put_offset_(-1), + next_transfer_buffer_id_(0), + initialize_result_(false), + async_waiter_(async_waiter) { + command_buffer_.Bind(command_buffer_handle.Pass(), async_waiter); + command_buffer_.set_error_handler(this); + command_buffer_.set_client(this); +} + +CommandBufferClientImpl::~CommandBufferClientImpl() {} + +bool CommandBufferClientImpl::Initialize() { + const size_t kSharedStateSize = sizeof(gpu::CommandBufferSharedState); + void* memory = NULL; + mojo::ScopedSharedBufferHandle duped; + bool result = CreateMapAndDupSharedBuffer( + kSharedStateSize, &memory, &shared_state_handle_, &duped); + if (!result) + return false; + + shared_state_ = static_cast<gpu::CommandBufferSharedState*>(memory); + + shared_state()->Initialize(); + + // TODO(darin): We need better sugar for sync calls. + MessagePipe sync_pipe; + sync_dispatcher_.reset(new SyncDispatcher<CommandBufferSyncClient>( + sync_pipe.handle0.Pass(), this)); + CommandBufferSyncClientPtr sync_client = + MakeProxy<CommandBufferSyncClient>(sync_pipe.handle1.Pass(), + async_waiter_); + command_buffer_->Initialize(sync_client.Pass(), duped.Pass()); + // Wait for DidInitialize to come on the sync client pipe. + if (!sync_dispatcher_->WaitAndDispatchOneMessage()) { + VLOG(1) << "Channel encountered error while creating command buffer"; + return false; + } + return initialize_result_; +} + +gpu::CommandBuffer::State CommandBufferClientImpl::GetLastState() { + return last_state_; +} + +int32 CommandBufferClientImpl::GetLastToken() { + TryUpdateState(); + return last_state_.token; +} + +void CommandBufferClientImpl::Flush(int32 put_offset) { + if (last_put_offset_ == put_offset) + return; + + last_put_offset_ = put_offset; + command_buffer_->Flush(put_offset); +} + +void CommandBufferClientImpl::WaitForTokenInRange(int32 start, int32 end) { + TryUpdateState(); + while (!InRange(start, end, last_state_.token) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + TryUpdateState(); + } +} + +void CommandBufferClientImpl::WaitForGetOffsetInRange(int32 start, int32 end) { + TryUpdateState(); + while (!InRange(start, end, last_state_.get_offset) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + TryUpdateState(); + } +} + +void CommandBufferClientImpl::SetGetBuffer(int32 shm_id) { + command_buffer_->SetGetBuffer(shm_id); + last_put_offset_ = -1; +} + +scoped_refptr<gpu::Buffer> CommandBufferClientImpl::CreateTransferBuffer( + size_t size, + int32* id) { + if (size >= std::numeric_limits<uint32_t>::max()) + return NULL; + + void* memory = NULL; + mojo::ScopedSharedBufferHandle handle; + mojo::ScopedSharedBufferHandle duped; + if (!CreateMapAndDupSharedBuffer(size, &memory, &handle, &duped)) + return NULL; + + *id = ++next_transfer_buffer_id_; + + command_buffer_->RegisterTransferBuffer( + *id, duped.Pass(), static_cast<uint32_t>(size)); + + scoped_ptr<gpu::BufferBacking> backing( + new MojoBufferBacking(handle.Pass(), memory, size)); + scoped_refptr<gpu::Buffer> buffer(new gpu::Buffer(backing.Pass())); + return buffer; +} + +void CommandBufferClientImpl::DestroyTransferBuffer(int32 id) { + command_buffer_->DestroyTransferBuffer(id); +} + +gpu::Capabilities CommandBufferClientImpl::GetCapabilities() { + // TODO(piman) + NOTIMPLEMENTED(); + return gpu::Capabilities(); +} + +gfx::GpuMemoryBuffer* CommandBufferClientImpl::CreateGpuMemoryBuffer( + size_t width, + size_t height, + unsigned internalformat, + unsigned usage, + int32* id) { + // TODO(piman) + NOTIMPLEMENTED(); + return NULL; +} + +void CommandBufferClientImpl::DestroyGpuMemoryBuffer(int32 id) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +uint32 CommandBufferClientImpl::InsertSyncPoint() { + // TODO(piman) + NOTIMPLEMENTED(); + return 0; +} + +void CommandBufferClientImpl::SignalSyncPoint(uint32 sync_point, + const base::Closure& callback) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +void CommandBufferClientImpl::SignalQuery(uint32 query, + const base::Closure& callback) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +void CommandBufferClientImpl::SetSurfaceVisible(bool visible) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +void CommandBufferClientImpl::Echo(const base::Closure& callback) { + command_buffer_->Echo(callback); +} + +uint32 CommandBufferClientImpl::CreateStreamTexture(uint32 texture_id) { + // TODO(piman) + NOTIMPLEMENTED(); + return 0; +} + +void CommandBufferClientImpl::RequestAnimationFrames() { + command_buffer_->RequestAnimationFrames(); +} + +void CommandBufferClientImpl::CancelAnimationFrames() { + command_buffer_->CancelAnimationFrames(); +} + +void CommandBufferClientImpl::DidInitialize(bool success) { + initialize_result_ = success; +} + +void CommandBufferClientImpl::DidMakeProgress(CommandBufferStatePtr state) { + if (state->generation - last_state_.generation < 0x80000000U) + last_state_ = state.To<State>(); +} + +void CommandBufferClientImpl::DidDestroy() { + LostContext(gpu::error::kUnknown); +} + +void CommandBufferClientImpl::LostContext(int32_t lost_reason) { + last_state_.error = gpu::error::kLostContext; + last_state_.context_lost_reason = + static_cast<gpu::error::ContextLostReason>(lost_reason); + delegate_->ContextLost(); +} + +void CommandBufferClientImpl::OnConnectionError() { + LostContext(gpu::error::kUnknown); +} + +void CommandBufferClientImpl::TryUpdateState() { + if (last_state_.error == gpu::error::kNoError) + shared_state()->Read(&last_state_); +} + +void CommandBufferClientImpl::MakeProgressAndUpdateState() { + command_buffer_->MakeProgress(last_state_.get_offset); + if (!sync_dispatcher_->WaitAndDispatchOneMessage()) { + VLOG(1) << "Channel encountered error while waiting for command buffer"; + // TODO(piman): is it ok for this to re-enter? + DidDestroy(); + return; + } +} + +void CommandBufferClientImpl::DrawAnimationFrame() { + delegate_->DrawAnimationFrame(); +} + +} // namespace gles2 +} // namespace mojo diff --git a/chromium/mojo/gles2/command_buffer_client_impl.h b/chromium/mojo/gles2/command_buffer_client_impl.h new file mode 100644 index 00000000000..68312e08537 --- /dev/null +++ b/chromium/mojo/gles2/command_buffer_client_impl.h @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_ +#define MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_ + +#include <map> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/client/gpu_control.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/command_buffer/common/command_buffer_shared.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/services/gles2/command_buffer.mojom.h" + +namespace base { +class RunLoop; +} + +namespace mojo { +template <typename S> +class SyncDispatcher; + +namespace gles2 { +class CommandBufferClientImpl; + +class CommandBufferDelegate { + public: + virtual ~CommandBufferDelegate(); + virtual void ContextLost(); + virtual void DrawAnimationFrame(); +}; + +class CommandBufferClientImpl : public CommandBufferClient, + public CommandBufferSyncClient, + public ErrorHandler, + public gpu::CommandBuffer, + public gpu::GpuControl { + public: + explicit CommandBufferClientImpl( + CommandBufferDelegate* delegate, + const MojoAsyncWaiter* async_waiter, + ScopedMessagePipeHandle command_buffer_handle); + virtual ~CommandBufferClientImpl(); + + // CommandBuffer implementation: + virtual bool Initialize() OVERRIDE; + virtual State GetLastState() OVERRIDE; + virtual int32 GetLastToken() OVERRIDE; + virtual void Flush(int32 put_offset) OVERRIDE; + virtual void WaitForTokenInRange(int32 start, int32 end) OVERRIDE; + virtual void WaitForGetOffsetInRange(int32 start, int32 end) OVERRIDE; + virtual void SetGetBuffer(int32 shm_id) OVERRIDE; + virtual scoped_refptr<gpu::Buffer> CreateTransferBuffer(size_t size, + int32* id) OVERRIDE; + virtual void DestroyTransferBuffer(int32 id) OVERRIDE; + + // gpu::GpuControl implementation: + virtual gpu::Capabilities GetCapabilities() OVERRIDE; + virtual gfx::GpuMemoryBuffer* CreateGpuMemoryBuffer(size_t width, + size_t height, + unsigned internalformat, + unsigned usage, + int32* id) OVERRIDE; + virtual void DestroyGpuMemoryBuffer(int32 id) OVERRIDE; + virtual uint32 InsertSyncPoint() OVERRIDE; + virtual void SignalSyncPoint(uint32 sync_point, + const base::Closure& callback) OVERRIDE; + virtual void SignalQuery(uint32 query, + const base::Closure& callback) OVERRIDE; + virtual void SetSurfaceVisible(bool visible) OVERRIDE; + virtual void Echo(const base::Closure& callback) OVERRIDE; + virtual uint32 CreateStreamTexture(uint32 texture_id) OVERRIDE; + + void RequestAnimationFrames(); + void CancelAnimationFrames(); + + private: + // CommandBufferClient implementation: + virtual void DidInitialize(bool success) OVERRIDE; + virtual void DidMakeProgress(CommandBufferStatePtr state) OVERRIDE; + virtual void DidDestroy() OVERRIDE; + virtual void LostContext(int32_t lost_reason) OVERRIDE; + + // ErrorHandler implementation: + virtual void OnConnectionError() OVERRIDE; + + virtual void DrawAnimationFrame() OVERRIDE; + + void TryUpdateState(); + void MakeProgressAndUpdateState(); + + gpu::CommandBufferSharedState* shared_state() const { return shared_state_; } + + CommandBufferDelegate* delegate_; + CommandBufferPtr command_buffer_; + scoped_ptr<SyncDispatcher<CommandBufferSyncClient> > sync_dispatcher_; + + State last_state_; + mojo::ScopedSharedBufferHandle shared_state_handle_; + gpu::CommandBufferSharedState* shared_state_; + int32 last_put_offset_; + int32 next_transfer_buffer_id_; + + bool initialize_result_; + const MojoAsyncWaiter* async_waiter_; +}; + +} // gles2 +} // mojo + +#endif // MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_ diff --git a/chromium/mojo/gles2/gles2_context.cc b/chromium/mojo/gles2/gles2_context.cc new file mode 100644 index 00000000000..701b0fed13d --- /dev/null +++ b/chromium/mojo/gles2/gles2_context.cc @@ -0,0 +1,73 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/gles2/gles2_context.h" + +#include "gpu/command_buffer/client/gles2_cmd_helper.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "gpu/command_buffer/client/transfer_buffer.h" +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace gles2 { + +namespace { +const size_t kDefaultCommandBufferSize = 1024 * 1024; +const size_t kDefaultStartTransferBufferSize = 1 * 1024 * 1024; +const size_t kDefaultMinTransferBufferSize = 1 * 256 * 1024; +const size_t kDefaultMaxTransferBufferSize = 16 * 1024 * 1024; +} + +GLES2Context::GLES2Context(const MojoAsyncWaiter* async_waiter, + ScopedMessagePipeHandle command_buffer_handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure) + : command_buffer_(this, async_waiter, command_buffer_handle.Pass()), + lost_callback_(lost_callback), + animation_callback_(animation_callback), + closure_(closure) {} + +GLES2Context::~GLES2Context() {} + +bool GLES2Context::Initialize() { + if (!command_buffer_.Initialize()) + return false; + gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(&command_buffer_)); + if (!gles2_helper_->Initialize(kDefaultCommandBufferSize)) + return false; + gles2_helper_->SetAutomaticFlushes(false); + transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get())); + bool bind_generates_resource = true; + // TODO(piman): Some contexts (such as compositor) want this to be true, so + // this needs to be a public parameter. + bool lose_context_when_out_of_memory = false; + implementation_.reset( + new gpu::gles2::GLES2Implementation(gles2_helper_.get(), + NULL, + transfer_buffer_.get(), + bind_generates_resource, + lose_context_when_out_of_memory, + &command_buffer_)); + return implementation_->Initialize(kDefaultStartTransferBufferSize, + kDefaultMinTransferBufferSize, + kDefaultMaxTransferBufferSize, + gpu::gles2::GLES2Implementation::kNoLimit); +} + +void GLES2Context::RequestAnimationFrames() { + command_buffer_.RequestAnimationFrames(); +} + +void GLES2Context::CancelAnimationFrames() { + command_buffer_.CancelAnimationFrames(); +} + +void GLES2Context::ContextLost() { lost_callback_(closure_); } + +void GLES2Context::DrawAnimationFrame() { animation_callback_(closure_); } + +} // namespace gles2 +} // namespace mojo diff --git a/chromium/mojo/gles2/gles2_context.h b/chromium/mojo/gles2/gles2_context.h new file mode 100644 index 00000000000..bc73082a976 --- /dev/null +++ b/chromium/mojo/gles2/gles2_context.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_GLES2_GLES2_CONTEXT_H_ +#define MOJO_GLES2_GLES2_CONTEXT_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "mojo/gles2/command_buffer_client_impl.h" +#include "mojo/public/c/gles2/gles2.h" + +struct MojoGLES2ContextPrivate {}; + +namespace gpu { +class TransferBuffer; +namespace gles2 { +class GLES2CmdHelper; +class GLES2Implementation; +} +} + +namespace mojo { +namespace gles2 { + +class GLES2Context : public CommandBufferDelegate, + public MojoGLES2ContextPrivate { + public: + explicit GLES2Context(const MojoAsyncWaiter* async_waiter, + ScopedMessagePipeHandle command_buffer_handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure); + virtual ~GLES2Context(); + bool Initialize(); + + gpu::gles2::GLES2Interface* interface() const { + return implementation_.get(); + } + gpu::ContextSupport* context_support() const { return implementation_.get(); } + void RequestAnimationFrames(); + void CancelAnimationFrames(); + + private: + virtual void ContextLost() OVERRIDE; + virtual void DrawAnimationFrame() OVERRIDE; + + CommandBufferClientImpl command_buffer_; + scoped_ptr<gpu::gles2::GLES2CmdHelper> gles2_helper_; + scoped_ptr<gpu::TransferBuffer> transfer_buffer_; + scoped_ptr<gpu::gles2::GLES2Implementation> implementation_; + MojoGLES2ContextLost lost_callback_; + MojoGLES2DrawAnimationFrame animation_callback_; + void* closure_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(GLES2Context); +}; + +} // namespace gles2 +} // namespace mojo + +#endif // MOJO_GLES2_GLES2_CONTEXT_H_ diff --git a/chromium/mojo/gles2/gles2_impl_export.h b/chromium/mojo/gles2/gles2_impl_export.h new file mode 100644 index 00000000000..3299ced152e --- /dev/null +++ b/chromium/mojo/gles2/gles2_impl_export.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_GLES2_GLES2_IMPL_EXPORT_H_ +#define MOJO_GLES2_GLES2_IMPL_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_GLES2_IMPL_IMPLEMENTATION) +#define MOJO_GLES2_IMPL_EXPORT __declspec(dllexport) +#else +#define MOJO_GLES2_IMPL_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_GLES2_IMPL_IMPLEMENTATION) +#define MOJO_GLES2_IMPL_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_GLES2_IMPL_EXPORT +#endif + +#endif // defined(WIN32) + +#else // defined(COMPONENT_BUILD) +#define MOJO_GLES2_IMPL_EXPORT +#endif + +#endif // MOJO_GLES2_GLES2_IMPL_EXPORT_H_ diff --git a/chromium/mojo/gles2/gles2_support_impl.cc b/chromium/mojo/gles2/gles2_support_impl.cc new file mode 100644 index 00000000000..7fd86081dc2 --- /dev/null +++ b/chromium/mojo/gles2/gles2_support_impl.cc @@ -0,0 +1,116 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/gles2/gles2_support_impl.h" + +#include "base/lazy_instance.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "mojo/gles2/gles2_context.h" +#include "mojo/public/gles2/gles2_interface.h" +#include "mojo/public/gles2/gles2_private.h" + +namespace mojo { +namespace gles2 { + +namespace { + +class GLES2ImplForCommandBuffer : public GLES2Interface { + public: + GLES2ImplForCommandBuffer() : gpu_interface_(NULL) {} + + void set_gpu_interface(gpu::gles2::GLES2Interface* gpu_interface) { + gpu_interface_ = gpu_interface; + } + gpu::gles2::GLES2Interface* gpu_interface() const { return gpu_interface_; } + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + virtual ReturnType Function PARAMETERS OVERRIDE { \ + return gpu_interface_->Function ARGUMENTS; \ + } +#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h" +#undef VISIT_GL_CALL + + private: + gpu::gles2::GLES2Interface* gpu_interface_; + DISALLOW_COPY_AND_ASSIGN(GLES2ImplForCommandBuffer); +}; + +base::LazyInstance<GLES2ImplForCommandBuffer> g_gles2_interface = + LAZY_INSTANCE_INITIALIZER; + +} // anonymous namespace + +GLES2SupportImpl::GLES2SupportImpl() : async_waiter_(NULL) {} +GLES2SupportImpl::~GLES2SupportImpl() {} + +// static +void GLES2SupportImpl::Init() { GLES2Support::Init(new GLES2SupportImpl()); } + +void GLES2SupportImpl::Initialize(const MojoAsyncWaiter* async_waiter) { + DCHECK(!async_waiter_); + DCHECK(async_waiter); + async_waiter_ = async_waiter; +} + +void GLES2SupportImpl::Terminate() { + DCHECK(async_waiter_); + async_waiter_ = NULL; +} + +MojoGLES2Context GLES2SupportImpl::CreateContext( + MessagePipeHandle handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure) { + ScopedMessagePipeHandle scoped_handle(handle); + scoped_ptr<GLES2Context> client(new GLES2Context(async_waiter_, + scoped_handle.Pass(), + lost_callback, + animation_callback, + closure)); + if (!client->Initialize()) + client.reset(); + return client.release(); +} + +void GLES2SupportImpl::DestroyContext(MojoGLES2Context context) { + delete static_cast<GLES2Context*>(context); +} + +void GLES2SupportImpl::MakeCurrent(MojoGLES2Context context) { + gpu::gles2::GLES2Interface* interface = NULL; + if (context) { + GLES2Context* client = static_cast<GLES2Context*>(context); + interface = client->interface(); + DCHECK(interface); + } + g_gles2_interface.Get().set_gpu_interface(interface); +} + +void GLES2SupportImpl::SwapBuffers() { + g_gles2_interface.Get().gpu_interface()->SwapBuffers(); +} + +void GLES2SupportImpl::RequestAnimationFrames(MojoGLES2Context context) { + static_cast<GLES2Context*>(context)->RequestAnimationFrames(); +} + +void GLES2SupportImpl::CancelAnimationFrames(MojoGLES2Context context) { + static_cast<GLES2Context*>(context)->CancelAnimationFrames(); +} + +void* GLES2SupportImpl::GetGLES2Interface(MojoGLES2Context context) { + return static_cast<GLES2Context*>(context)->interface(); +} + +void* GLES2SupportImpl::GetContextSupport(MojoGLES2Context context) { + return static_cast<GLES2Context*>(context)->context_support(); +} + +GLES2Interface* GLES2SupportImpl::GetGLES2InterfaceForCurrentContext() { + return &g_gles2_interface.Get(); +} + +} // namespace gles2 +} // namespace mojo diff --git a/chromium/mojo/gles2/gles2_support_impl.h b/chromium/mojo/gles2/gles2_support_impl.h new file mode 100644 index 00000000000..3305bf6ed4e --- /dev/null +++ b/chromium/mojo/gles2/gles2_support_impl.h @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_GLES2_GLES2_SUPPORT_IMPL_H_ +#define MOJO_GLES2_GLES2_SUPPORT_IMPL_H_ + +#include "base/compiler_specific.h" +#include "mojo/gles2/gles2_impl_export.h" +#include "mojo/public/gles2/gles2_private.h" + +namespace mojo { +namespace gles2 { + +class MOJO_GLES2_IMPL_EXPORT GLES2SupportImpl : public GLES2Support { + public: + virtual ~GLES2SupportImpl(); + + static void Init(); + + virtual void Initialize(const MojoAsyncWaiter* async_waiter) OVERRIDE; + virtual void Terminate() OVERRIDE; + virtual MojoGLES2Context CreateContext( + MessagePipeHandle handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure) OVERRIDE; + virtual void DestroyContext(MojoGLES2Context context) OVERRIDE; + virtual void MakeCurrent(MojoGLES2Context context) OVERRIDE; + virtual void SwapBuffers() OVERRIDE; + virtual void RequestAnimationFrames(MojoGLES2Context context) OVERRIDE; + virtual void CancelAnimationFrames(MojoGLES2Context context) OVERRIDE; + virtual void* GetGLES2Interface(MojoGLES2Context context) OVERRIDE; + virtual void* GetContextSupport(MojoGLES2Context context) OVERRIDE; + virtual GLES2Interface* GetGLES2InterfaceForCurrentContext() OVERRIDE; + + private: + GLES2SupportImpl(); + + const MojoAsyncWaiter* async_waiter_; +}; + +} // namespace gles2 +} // namespace mojo + +#endif // MOJO_GLES2_GLES2_SUPPORT_IMPL_H_ diff --git a/chromium/mojo/mojo.gyp b/chromium/mojo/mojo.gyp index 9151d4259d9..9421a9a90b7 100644 --- a/chromium/mojo/mojo.gyp +++ b/chromium/mojo/mojo.gyp @@ -3,8 +3,19 @@ # found in the LICENSE file. { + 'target_defaults': { + 'conditions': [ + ['mojo_shell_debug_url != ""', { + 'defines': [ + 'MOJO_SHELL_DEBUG=1', + 'MOJO_SHELL_DEBUG_URL="<(mojo_shell_debug_url)"', + ], + }], + ], + }, 'variables': { 'chromium_code': 1, + 'mojo_shell_debug_url%': "", }, 'includes': [ 'mojo_apps.gypi', @@ -17,24 +28,86 @@ 'target_name': 'mojo', 'type': 'none', 'dependencies': [ - 'mojo_bindings', - 'mojo_bindings_unittests', + 'mojo_apps_js_unittests', + 'mojo_compositor_app', 'mojo_common_lib', 'mojo_common_unittests', - 'mojo_hello_world_service', + 'mojo_cpp_bindings', + 'mojo_geometry_lib', + 'mojo_html_viewer', + 'mojo_image_viewer', 'mojo_js', + 'mojo_js_bindings', 'mojo_js_unittests', - 'mojo_public_perftests', - 'mojo_public_test_support', - 'mojo_public_unittests', + 'mojo_launcher', + 'mojo_message_generator', + 'mojo_native_viewport_service', + 'mojo_network_service', + 'mojo_pepper_container_app', + 'mojo_public_test_utils', + 'mojo_public_bindings_unittests', + 'mojo_public_environment_unittests', + 'mojo_public_system_perftests', + 'mojo_public_system_unittests', + 'mojo_public_utility_unittests', 'mojo_sample_app', + 'mojo_service_manager', + 'mojo_service_manager_unittests', 'mojo_shell', 'mojo_shell_lib', + 'mojo_shell_tests', 'mojo_system', 'mojo_system_impl', 'mojo_system_unittests', + 'mojo_test_service', 'mojo_utility', - 'mojo_utility_unittests', + 'mojo_view_manager_lib', + 'mojo_view_manager_lib_unittests', + 'mojo_wget', + ], + 'conditions': [ + ['use_aura==1', { + 'dependencies': [ + 'mojo_aura_demo', + 'mojo_aura_demo_init', + 'mojo_browser', + 'mojo_demo_launcher', + 'mojo_embedded_app', + 'mojo_nesting_app', + 'mojo_window_manager', + 'mojo_view_manager', + 'mojo_view_manager_unittests', + ], + }], + ['OS == "android"', { + 'dependencies': [ + 'mojo_bindings_java', + 'mojo_public_java', + 'mojo_system_java', + 'libmojo_system_java', + 'mojo_test_apk', + ], + }], + ['OS == "linux"', { + 'dependencies': [ + 'mojo_dbus_echo', + 'mojo_dbus_echo_service', + ], + }], + ] + }, + { + 'target_name': 'mojo_external_service_bindings', + 'type': 'static_library', + 'sources': [ + 'shell/external_service.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', ], }, { @@ -44,8 +117,9 @@ '../base/base.gyp:base', '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', - 'mojo_system', 'mojo_system_impl', + 'mojo_test_support', + 'mojo_test_support_impl', ], 'sources': [ 'common/test/run_all_unittests.cc', @@ -56,29 +130,50 @@ 'type': 'static_library', 'dependencies': [ '../base/base.gyp:test_support_base', - 'mojo_system', 'mojo_system_impl', + 'mojo_test_support', + 'mojo_test_support_impl', ], 'sources': [ 'common/test/run_all_perftests.cc', ], }, { + # GN version: //mojo/system 'target_name': 'mojo_system_impl', 'type': '<(component)', 'dependencies': [ - 'mojo_system', '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', ], 'defines': [ 'MOJO_SYSTEM_IMPL_IMPLEMENTATION', + 'MOJO_SYSTEM_IMPLEMENTATION', + 'MOJO_USE_SYSTEM_IMPL', ], 'sources': [ + 'embedder/channel_init.cc', + 'embedder/channel_init.h', + 'embedder/embedder.cc', + 'embedder/embedder.h', + 'embedder/platform_channel_pair.cc', + 'embedder/platform_channel_pair.h', + 'embedder/platform_channel_pair_posix.cc', + 'embedder/platform_channel_pair_win.cc', + 'embedder/platform_channel_utils_posix.cc', + 'embedder/platform_channel_utils_posix.h', + 'embedder/platform_handle.cc', + 'embedder/platform_handle.h', + 'embedder/platform_handle_utils.h', + 'embedder/platform_handle_utils_posix.cc', + 'embedder/platform_handle_utils_win.cc', + 'embedder/platform_handle_vector.h', + 'embedder/scoped_platform_handle.h', 'system/channel.cc', 'system/channel.h', 'system/constants.h', - 'system/core_impl.cc', - 'system/core_impl.h', + 'system/core.cc', + 'system/core.h', 'system/data_pipe.cc', 'system/data_pipe.h', 'system/data_pipe_consumer_dispatcher.cc', @@ -87,58 +182,93 @@ 'system/data_pipe_producer_dispatcher.h', 'system/dispatcher.cc', 'system/dispatcher.h', + 'system/entrypoints.cc', + 'system/handle_signals_state.h', + 'system/handle_table.cc', + 'system/handle_table.h', 'system/local_data_pipe.cc', 'system/local_data_pipe.h', 'system/local_message_pipe_endpoint.cc', 'system/local_message_pipe_endpoint.h', + 'system/mapping_table.cc', + 'system/mapping_table.h', 'system/memory.cc', 'system/memory.h', 'system/message_in_transit.cc', 'system/message_in_transit.h', + 'system/message_in_transit_queue.cc', + 'system/message_in_transit_queue.h', 'system/message_pipe.cc', 'system/message_pipe.h', 'system/message_pipe_dispatcher.cc', 'system/message_pipe_dispatcher.h', 'system/message_pipe_endpoint.cc', 'system/message_pipe_endpoint.h', - 'system/platform_channel.cc', - 'system/platform_channel.h', - 'system/platform_channel_handle.cc', - 'system/platform_channel_handle.h', - 'system/platform_channel_posix.cc', + 'system/options_validation.h', + 'system/platform_handle_dispatcher.cc', + 'system/platform_handle_dispatcher.h', 'system/proxy_message_pipe_endpoint.cc', 'system/proxy_message_pipe_endpoint.h', + 'system/raw_channel.cc', 'system/raw_channel.h', 'system/raw_channel_posix.cc', 'system/raw_channel_win.cc', + 'system/raw_shared_buffer.cc', + 'system/raw_shared_buffer.h', + 'system/raw_shared_buffer_posix.cc', + 'system/raw_shared_buffer_win.cc', + 'system/shared_buffer_dispatcher.cc', + 'system/shared_buffer_dispatcher.h', 'system/simple_dispatcher.cc', 'system/simple_dispatcher.h', + 'system/transport_data.cc', + 'system/transport_data.h', 'system/waiter.cc', 'system/waiter.h', 'system/waiter_list.cc', 'system/waiter_list.h', + # Test-only code: + # TODO(vtl): It's a little unfortunate that these end up in the same + # component as non-test-only code. In the static build, this code should + # hopefully be dead-stripped. + 'embedder/test_embedder.cc', + 'embedder/test_embedder.h', ], + 'all_dependent_settings': { + # Ensures that dependent projects import the core functions on Windows. + 'defines': ['MOJO_USE_SYSTEM_IMPL'], + } }, { 'target_name': 'mojo_system_unittests', 'type': 'executable', 'dependencies': [ + '../base/base.gyp:base', '../base/base.gyp:run_all_unittests', '../testing/gtest.gyp:gtest', 'mojo_common_test_support', - 'mojo_system', 'mojo_system_impl', ], 'sources': [ - 'system/core_impl_unittest.cc', + 'embedder/embedder_unittest.cc', + 'embedder/platform_channel_pair_posix_unittest.cc', + 'system/channel_unittest.cc', + 'system/core_unittest.cc', 'system/core_test_base.cc', 'system/core_test_base.h', + 'system/data_pipe_unittest.cc', 'system/dispatcher_unittest.cc', + 'system/local_data_pipe_unittest.cc', + 'system/memory_unittest.cc', 'system/message_pipe_dispatcher_unittest.cc', 'system/message_pipe_unittest.cc', 'system/multiprocess_message_pipe_unittest.cc', - 'system/raw_channel_posix_unittest.cc', - 'system/remote_message_pipe_posix_unittest.cc', + 'system/options_validation_unittest.cc', + 'system/platform_handle_dispatcher_unittest.cc', + 'system/raw_channel_unittest.cc', + 'system/raw_shared_buffer_unittest.cc', + 'system/remote_message_pipe_unittest.cc', + 'system/shared_buffer_dispatcher_unittest.cc', 'system/simple_dispatcher_unittest.cc', 'system/test_utils.cc', 'system/test_utils.h', @@ -152,19 +282,43 @@ 'target_name': 'mojo_gles2_impl', 'type': '<(component)', 'dependencies': [ - '../gpu/gpu.gyp:gles2_c_lib', + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../gpu/gpu.gyp:command_buffer_client', + '../gpu/gpu.gyp:command_buffer_common', + '../gpu/gpu.gyp:gles2_cmd_helper', + '../gpu/gpu.gyp:gles2_implementation', 'mojo_gles2', + 'mojo_gles2_bindings', + 'mojo_environment_chromium', + 'mojo_system_impl', ], 'defines': [ 'MOJO_GLES2_IMPL_IMPLEMENTATION', ], 'sources': [ - 'gles2/export.h', - 'gles2/gles2_impl.cc', - 'gles2/gles2_impl.h', + 'gles2/command_buffer_client_impl.cc', + 'gles2/command_buffer_client_impl.h', + 'gles2/gles2_impl_export.h', + 'gles2/gles2_support_impl.cc', + 'gles2/gles2_support_impl.h', + 'gles2/gles2_context.cc', + 'gles2/gles2_context.h', + ], + }, + { + 'target_name': 'mojo_test_support_impl', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + ], + 'sources': [ + 'common/test/test_support_impl.cc', + 'common/test/test_support_impl.h', ], }, { + # GN version: //mojo/common 'target_name': 'mojo_common_lib', 'type': '<(component)', 'defines': [ @@ -173,26 +327,24 @@ 'dependencies': [ '../base/base.gyp:base', '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', - 'mojo_system', + 'mojo_system_impl', + ], + 'export_dependent_settings': [ + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + 'mojo_system_impl', ], 'sources': [ - 'common/bindings_support_impl.cc', - 'common/bindings_support_impl.h', 'common/common_type_converters.cc', 'common/common_type_converters.h', + 'common/data_pipe_utils.cc', + 'common/data_pipe_utils.h', 'common/handle_watcher.cc', 'common/handle_watcher.h', 'common/message_pump_mojo.cc', 'common/message_pump_mojo.h', 'common/message_pump_mojo_handler.h', - ], - 'conditions': [ - ['OS == "win"', { - # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. - 'msvs_disabled_warnings': [ - 4267, - ], - }], + 'common/time_helper.cc', + 'common/time_helper.h', ], }, { @@ -202,12 +354,14 @@ '../base/base.gyp:base', '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', - 'mojo_system', 'mojo_system_impl', ], 'sources': [ - 'common/test/multiprocess_test_base.cc', - 'common/test/multiprocess_test_base.h', + 'common/test/multiprocess_test_helper.cc', + 'common/test/multiprocess_test_helper.h', + 'common/test/test_utils.h', + 'common/test/test_utils_posix.cc', + 'common/test/test_utils_win.cc', ], }, { @@ -217,27 +371,108 @@ '../base/base.gyp:base', '../base/base.gyp:base_message_loop_tests', '../testing/gtest.gyp:gtest', - 'mojo_bindings', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', 'mojo_common_lib', 'mojo_common_test_support', - 'mojo_public_test_support', + 'mojo_public_test_utils', 'mojo_run_all_unittests', - 'mojo_system', - 'mojo_system_impl', ], 'sources': [ 'common/common_type_converters_unittest.cc', 'common/handle_watcher_unittest.cc', 'common/message_pump_mojo_unittest.cc', - 'common/test/multiprocess_test_base_unittest.cc', + 'common/test/multiprocess_test_helper_unittest.cc', ], - 'conditions': [ - ['OS == "win"', { - # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. - 'msvs_disabled_warnings': [ - 4267, - ], - }], + }, + { + # GN version: //mojo/environment:chromium + 'target_name': 'mojo_environment_chromium', + 'type': 'static_library', + 'dependencies': [ + 'mojo_common_lib', + 'mojo_environment_chromium_impl', + ], + 'sources': [ + 'environment/environment.cc', + # TODO(vtl): This is kind of ugly. (See TODO in logging.h.) + "public/cpp/environment/logging.h", + "public/cpp/environment/lib/logging.h", + ], + 'include_dirs': [ + '..', + ], + 'export_dependent_settings': [ + 'mojo_environment_chromium_impl', + ], + }, + { + # GN version: //mojo/environment:chromium_impl + 'target_name': 'mojo_environment_chromium_impl', + 'type': '<(component)', + 'defines': [ + 'MOJO_ENVIRONMENT_IMPL_IMPLEMENTATION', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + 'mojo_common_lib' + ], + 'sources': [ + 'environment/default_async_waiter_impl.cc', + 'environment/default_async_waiter_impl.h', + 'environment/default_logger_impl.cc', + 'environment/default_logger_impl.h', + ], + 'include_dirs': [ + '..', + ], + }, + { + # GN version: //mojo/service_manager + 'target_name': 'mojo_service_manager', + 'type': '<(component)', + 'defines': [ + 'MOJO_SERVICE_MANAGER_IMPLEMENTATION', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../net/net.gyp:net', + '../url/url.gyp:url_lib', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_service_provider_bindings', + 'mojo_system_impl', + ], + 'sources': [ + 'service_manager/background_service_loader.cc', + 'service_manager/background_service_loader.h', + 'service_manager/service_loader.h', + 'service_manager/service_manager.cc', + 'service_manager/service_manager.h', + 'service_manager/service_manager_export.h', + ], + 'export_dependent_settings': [ + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + 'mojo_service_provider_bindings', + ], + }, + { + 'target_name': 'mojo_spy', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_static', + '../net/net.gyp:http_server', + '../url/url.gyp:url_lib', + 'mojo_service_manager', + ], + 'sources': [ + 'spy/spy.cc', + 'spy/spy.h', + 'spy/websocket_server.cc', + 'spy/websocket_server.h', ], }, { @@ -245,43 +480,96 @@ 'type': 'static_library', 'dependencies': [ '../base/base.gyp:base', + '../base/base.gyp:base_static', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', '../net/net.gyp:net', '../url/url.gyp:url_lib', - 'mojo_bindings', + 'mojo_external_service_bindings', 'mojo_gles2_impl', 'mojo_native_viewport_service', - 'mojo_system', + 'mojo_network_bindings', + 'mojo_service_manager', + 'mojo_service_provider_bindings', + 'mojo_spy', 'mojo_system_impl', ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], 'sources': [ - 'shell/app_container.cc', - 'shell/app_container.h', + 'shell/app_child_process.cc', + 'shell/app_child_process.h', + 'shell/app_child_process.mojom', + 'shell/app_child_process_host.cc', + 'shell/app_child_process_host.h', + 'shell/child_process.cc', + 'shell/child_process.h', + 'shell/child_process_host.cc', + 'shell/child_process_host.h', 'shell/context.cc', 'shell/context.h', + 'shell/dbus_service_loader_linux.cc', + 'shell/dbus_service_loader_linux.h', + 'shell/dynamic_service_loader.cc', + 'shell/dynamic_service_loader.h', + 'shell/dynamic_service_runner.h', 'shell/init.cc', 'shell/init.h', - 'shell/loader.cc', - 'shell/loader.h', - 'shell/network_delegate.cc', - 'shell/network_delegate.h', + 'shell/in_process_dynamic_service_runner.cc', + 'shell/in_process_dynamic_service_runner.h', + 'shell/keep_alive.cc', + 'shell/keep_alive.h', + 'shell/mojo_url_resolver.cc', + 'shell/mojo_url_resolver.h', + 'shell/out_of_process_dynamic_service_runner.cc', + 'shell/out_of_process_dynamic_service_runner.h', 'shell/run.cc', 'shell/run.h', - 'shell/storage.cc', - 'shell/storage.h', 'shell/switches.cc', 'shell/switches.h', 'shell/task_runners.cc', 'shell/task_runners.h', - 'shell/url_request_context_getter.cc', - 'shell/url_request_context_getter.h', + 'shell/test_child_process.cc', + 'shell/test_child_process.h', + 'shell/view_manager_loader.cc', + 'shell/view_manager_loader.h', ], 'conditions': [ - ['OS == "win"', { - # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. - 'msvs_disabled_warnings': [ - 4267, + ['OS=="linux"', { + 'dependencies': [ + '../build/linux/system.gyp:dbus', + '../dbus/dbus.gyp:dbus', ], }], + ['use_aura==1', { + 'dependencies': [ + # These are only necessary as long as we hard code use of ViewManager. + '../skia/skia.gyp:skia', + 'mojo_gles2', + 'mojo_application', + 'mojo_view_manager', + 'mojo_view_manager_bindings', + ], + }, { # use_aura==0 + 'sources!': [ + 'shell/view_manager_loader.cc', + 'shell/view_manager_loader.h', + ], + }], + ], + }, + { + 'target_name': 'mojo_shell_test_support', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_static', + '../url/url.gyp:url_lib', + 'mojo_service_manager', + 'mojo_shell_lib', + 'mojo_system_impl', + ], + 'sources': [ + 'shell/shell_test_helper.cc', + 'shell/shell_test_helper.h', ], }, { @@ -292,65 +580,248 @@ '../ui/gl/gl.gyp:gl', '../url/url.gyp:url_lib', 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_service_manager', 'mojo_shell_lib', - 'mojo_system', 'mojo_system_impl', ], 'sources': [ 'shell/desktop/mojo_main.cc', ], + }, + { + 'target_name': 'mojo_shell_tests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + # TODO(vtl): We don't currently need this, but I imagine we will soon. + # '../ui/gl/gl.gyp:gl', + '../url/url.gyp:url_lib', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_service_manager', + 'mojo_shell_lib', + 'mojo_system_impl', + 'mojo_test_service_bindings', + ], + 'sources': [ + 'shell/child_process_host_unittest.cc', + 'shell/shell_test_base.cc', + 'shell/shell_test_base.h', + 'shell/shell_test_base_unittest.cc', + 'shell/shell_test_main.cc', + ], 'conditions': [ - ['OS == "win"', { - # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. - 'msvs_disabled_warnings': [ - 4267, + ['OS == "android"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', ], }], ], }, + { + 'target_name': 'mojo_service_manager_unittests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + '../url/url.gyp:url_lib', + 'mojo_common_lib', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_run_all_unittests', + 'mojo_service_manager', + 'mojo_application', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'sources': [ + 'service_manager/service_manager_unittest.cc', + 'service_manager/test.mojom', + ], + }, + { + 'target_name': 'mojo_js_bindings_lib', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../gin/gin.gyp:gin', + '../v8/tools/gyp/v8.gyp:v8', + 'mojo_common_lib', + ], + 'export_dependent_settings': [ + '../base/base.gyp:base', + '../gin/gin.gyp:gin', + 'mojo_common_lib', + ], + 'sources': [ + 'bindings/js/core.cc', + 'bindings/js/core.h', + 'bindings/js/handle.cc', + 'bindings/js/handle.h', + 'bindings/js/support.cc', + 'bindings/js/support.h', + 'bindings/js/waiting_callback.cc', + 'bindings/js/waiting_callback.h', + ], + }, + { + 'target_name': 'mojo_js_unittests', + 'type': 'executable', + 'dependencies': [ + '../gin/gin.gyp:gin_test', + 'mojo_common_test_support', + 'mojo_js_bindings_lib', + 'mojo_run_all_unittests', + 'mojo_public_test_interfaces', + ], + 'sources': [ + 'bindings/js/run_js_tests.cc', + ], + }, + { + 'target_name': 'mojo_message_generator', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + 'mojo_common_lib', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_system_impl', + ], + 'sources': [ + 'tools/message_generator.cc', + ], + }, + { + 'target_name': 'mojo_cc_support', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../cc/cc.gyp:cc', + '../skia/skia.gyp:skia', + '../gpu/gpu.gyp:gles2_implementation', + 'mojo_gles2', + ], + 'sources': [ + 'cc/context_provider_mojo.cc', + 'cc/context_provider_mojo.h', + ], + }, ], 'conditions': [ ['OS=="android"', { 'targets': [ { - 'target_name': 'mojo_native_viewport_java', + 'target_name': 'mojo_jni_headers', + 'type': 'none', + 'dependencies': [ + 'mojo_java_set_jni_headers', + ], + 'sources': [ + 'android/javatests/src/org/chromium/mojo/MojoTestCase.java', + 'android/system/src/org/chromium/mojo/system/impl/CoreImpl.java', + 'services/native_viewport/android/src/org/chromium/mojo/NativeViewportAndroid.java', + 'shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java', + ], + 'variables': { + 'jni_gen_package': 'mojo', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + { + 'target_name': 'mojo_system_java', 'type': 'none', 'dependencies': [ '../base/base.gyp:base_java', + 'mojo_public_java', ], 'variables': { - 'java_in_dir': '<(DEPTH)/mojo/services/native_viewport/android', + 'java_in_dir': '<(DEPTH)/mojo/android/system', }, 'includes': [ '../build/java.gypi' ], }, { - 'target_name': 'mojo_java_set_jni_headers', + 'target_name': 'libmojo_system_java', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_jni_headers', + 'mojo_service_provider_bindings', + 'mojo_shell_lib', + ], + 'sources': [ + 'android/system/core_impl.cc', + 'android/system/core_impl.h', + ], + }, + { + 'target_name': 'libmojo_java_unittest', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + 'libmojo_system_java', + 'mojo_jni_headers', + ], + 'defines': [ + 'UNIT_TEST' # As exported from testing/gtest.gyp:gtest. + ], + 'sources': [ + 'android/javatests/mojo_test_case.cc', + 'android/javatests/mojo_test_case.h', + 'android/javatests/init_library.cc', + ], + }, + { + 'target_name': 'mojo_test_apk', 'type': 'none', + 'dependencies': [ + 'mojo_bindings_java', + 'mojo_public_test_interfaces', + 'mojo_system_java', + '../base/base.gyp:base_java_test_support', + ], 'variables': { - 'jni_gen_package': 'mojo', - 'input_java_class': 'java/util/HashSet.class', + 'apk_name': 'MojoTest', + 'java_in_dir': '<(DEPTH)/mojo/android/javatests', + 'resource_dir': '<(DEPTH)/mojo/android/javatests/apk', + 'native_lib_target': 'libmojo_java_unittest', + 'is_test_apk': 1, + # Given that this apk tests itself, it needs to bring emma with it + # when instrumented. + 'conditions': [ + ['emma_coverage != 0', { + 'emma_instrument': 1, + }], + ], }, - 'includes': [ '../build/jar_file_jni_generator.gypi' ], + 'includes': [ '../build/java_apk.gypi' ], }, { - 'target_name': 'mojo_jni_headers', + 'target_name': 'mojo_native_viewport_java', 'type': 'none', 'dependencies': [ - 'mojo_java_set_jni_headers', + '../base/base.gyp:base_java', ], - 'direct_dependent_settings': { - 'include_dirs': [ - '<(SHARED_INTERMEDIATE_DIR)/mojo', - ], + 'variables': { + 'java_in_dir': '<(DEPTH)/mojo/services/native_viewport/android', }, - 'sources': [ - 'services/native_viewport/android/src/org/chromium/mojo/NativeViewportAndroid.java', - 'shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java', - ], + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'mojo_java_set_jni_headers', + 'type': 'none', 'variables': { - 'jni_gen_package': 'mojo' + 'jni_gen_package': 'mojo', + 'input_java_class': 'java/util/HashSet.class', }, - 'includes': [ '../build/jni_generator.gypi' ], + 'includes': [ '../build/jar_file_jni_generator.gypi' ], }, { 'target_name': 'libmojo_shell', @@ -359,9 +830,12 @@ '../base/base.gyp:base', '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', '../ui/gl/gl.gyp:gl', 'mojo_common_lib', + 'mojo_environment_chromium', 'mojo_jni_headers', + 'mojo_service_provider_bindings', 'mojo_shell_lib', ], 'sources': [ @@ -389,5 +863,98 @@ } ], }], + ['OS=="linux"', { + 'targets': [ + { + 'target_name': 'mojo_dbus_service', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../build/linux/system.gyp:dbus', + '../dbus/dbus.gyp:dbus', + 'mojo_common_lib', + 'mojo_external_service_bindings', + 'mojo_application', + 'mojo_system_impl', + ], + 'sources': [ + 'dbus/dbus_external_service.h', + 'dbus/dbus_external_service.cc', + ], + }, + ], + }], + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'mojo_js_unittests_run', + 'type': 'none', + 'dependencies': [ + 'mojo_js_unittests', + ], + 'includes': [ + '../build/isolate.gypi', + 'mojo_js_unittests.isolate', + ], + 'sources': [ + 'mojo_js_unittests.isolate', + ], + }, + ], + }], + ['use_aura==1', { + 'targets': [ + { + 'target_name': 'mojo_aura_support', + 'type': 'static_library', + 'dependencies': [ + '../cc/cc.gyp:cc', + '../ui/aura/aura.gyp:aura', + '../ui/compositor/compositor.gyp:compositor', + '../ui/events/events.gyp:events', + '../ui/events/events.gyp:events_base', + '../ui/gl/gl.gyp:gl', + '../webkit/common/gpu/webkit_gpu.gyp:webkit_gpu', + 'mojo_cc_support', + 'mojo_gles2', + 'mojo_native_viewport_bindings', + ], + 'sources': [ + 'aura/aura_init.cc', + 'aura/aura_init.h', + 'aura/context_factory_mojo.cc', + 'aura/context_factory_mojo.h', + 'aura/screen_mojo.cc', + 'aura/screen_mojo.h', + 'aura/window_tree_host_mojo.cc', + 'aura/window_tree_host_mojo.h', + 'aura/window_tree_host_mojo_delegate.h', + ], + }, + { + 'target_name': 'mojo_views_support', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../skia/skia.gyp:skia', + '../skia/skia.gyp:skia', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + '../ui/aura/aura.gyp:aura', + '../ui/base/ui_base.gyp:ui_base', + '../ui/views/views.gyp:views', + '../ui/wm/wm.gyp:wm', + 'mojo_aura_support', + ], + 'sources': [ + 'views/native_widget_view_manager.cc', + 'views/native_widget_view_manager.h', + 'views/views_init.cc', + 'views/views_init.h', + ], + }, + ], + }], ], } diff --git a/chromium/mojo/mojo_apps.gypi b/chromium/mojo/mojo_apps.gypi index fbcd134539b..5e1d2ca75e0 100644 --- a/chromium/mojo/mojo_apps.gypi +++ b/chromium/mojo/mojo_apps.gypi @@ -5,15 +5,15 @@ 'type': 'static_library', 'dependencies': [ '../base/base.gyp:base', - '../gpu/gpu.gyp:gles2_c_lib', '../gin/gin.gyp:gin', '../ui/gl/gl.gyp:gl', '../v8/tools/gyp/v8.gyp:v8', 'mojo_common_lib', + 'mojo_environment_chromium', 'mojo_gles2', 'mojo_gles2_bindings', + 'mojo_js_bindings_lib', 'mojo_native_viewport_bindings', - 'mojo_system', ], 'export_dependent_settings': [ '../base/base.gyp:base', @@ -22,40 +22,49 @@ 'mojo_gles2', 'mojo_gles2_bindings', 'mojo_native_viewport_bindings', - 'mojo_system', ], 'sources': [ 'apps/js/mojo_runner_delegate.cc', 'apps/js/mojo_runner_delegate.h', 'apps/js/bindings/threading.cc', 'apps/js/bindings/threading.h', - 'apps/js/bindings/core.cc', - 'apps/js/bindings/core.h', 'apps/js/bindings/gl/context.cc', 'apps/js/bindings/gl/context.h', 'apps/js/bindings/gl/module.cc', 'apps/js/bindings/gl/module.h', - 'apps/js/bindings/gl/opaque.cc', - 'apps/js/bindings/gl/opaque.h', - 'apps/js/bindings/handle.cc', - 'apps/js/bindings/handle.h', - 'apps/js/bindings/support.cc', - 'apps/js/bindings/support.h', - 'apps/js/bindings/waiting_callback.cc', - 'apps/js/bindings/waiting_callback.h', + 'apps/js/bindings/monotonic_clock.cc', + 'apps/js/bindings/monotonic_clock.h', ], }, { - 'target_name': 'mojo_js_unittests', + 'target_name': 'mojo_apps_js_bindings', + 'type': 'static_library', + 'sources': [ + 'apps/js/test/js_to_cpp.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + }, + { + 'target_name': 'mojo_apps_js_unittests', 'type': 'executable', 'dependencies': [ '../gin/gin.gyp:gin_test', + 'mojo_apps_js_bindings', + 'mojo_common_lib', + 'mojo_common_test_support', 'mojo_js_lib', 'mojo_run_all_unittests', - 'mojo_sample_service', + 'mojo_public_test_interfaces', ], 'sources': [ - 'apps/js/test/run_js_tests.cc', + 'apps/js/test/js_to_cpp_unittest.cc', + 'apps/js/test/run_apps_js_tests.cc', ], }, { @@ -63,10 +72,31 @@ 'type': 'shared_library', 'dependencies': [ 'mojo_js_lib', + 'mojo_system_impl', ], 'sources': [ 'apps/js/main.cc', ], }, ], + 'conditions': [ + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'mojo_apps_js_unittests_run', + 'type': 'none', + 'dependencies': [ + 'mojo_apps_js_unittests', + ], + 'includes': [ + '../build/isolate.gypi', + 'mojo_apps_js_unittests.isolate', + ], + 'sources': [ + 'mojo_apps_js_unittests.isolate', + ], + }, + ], + }], + ], } diff --git a/chromium/mojo/mojo_apps_js_unittests.isolate b/chromium/mojo/mojo_apps_js_unittests.isolate new file mode 100644 index 00000000000..4fdeb7296cb --- /dev/null +++ b/chromium/mojo/mojo_apps_js_unittests.isolate @@ -0,0 +1,54 @@ +# 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. +{ + 'includes': [ + '../third_party/icu/icu.isolate', + ], + 'conditions': [ + ['OS=="win" or OS=="mac" or OS=="linux"', { + 'variables': { + 'command': [ + '../testing/test_env.py', + '<(PRODUCT_DIR)/mojo_apps_js_unittests<(EXECUTABLE_SUFFIX)', + '--brave-new-test-launcher', + '--test-launcher-bot-mode', + ], + 'isolate_dependency_tracked': [ + '../gin/test/expect.js', + '../testing/test_env.py', + '<(PRODUCT_DIR)/mojo_apps_js_unittests<(EXECUTABLE_SUFFIX)', + ], + 'isolate_dependency_untracked': [ + '<(PRODUCT_DIR)/gen/mojo/public/interfaces/bindings/tests/', + 'apps/js/', + 'bindings/js/', + 'public/js/bindings/', + ], + }, + }], + ['OS=="win"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/mojo_system.dll', + '<(PRODUCT_DIR)/mojo_test_support.dll', + ], + }, + }], + ['OS=="linux"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/lib/libmojo_test_support.so', + ], + }, + }], + ['OS=="mac"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/libmojo_gles2.dylib', + '<(PRODUCT_DIR)/libmojo_test_support.dylib', + ], + }, + }], + ], +} diff --git a/chromium/mojo/mojo_examples.gypi b/chromium/mojo/mojo_examples.gypi index dbc46109b33..254d5faea3a 100644 --- a/chromium/mojo/mojo_examples.gypi +++ b/chromium/mojo/mojo_examples.gypi @@ -1,27 +1,32 @@ +# 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. + { 'targets': [ { 'target_name': 'mojo_sample_app', 'type': 'shared_library', 'dependencies': [ - '../base/base.gyp:base', - '../gpu/gpu.gyp:gles2_c_lib', - '../ui/gfx/gfx.gyp:gfx', - '../ui/gl/gl.gyp:gl', - 'mojo_common_lib', + # TODO(darin): we should not be linking against these libraries! + '../ui/events/events.gyp:events', + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_standalone', + 'mojo_geometry_bindings', 'mojo_gles2', - 'mojo_gles2_bindings', 'mojo_native_viewport_bindings', 'mojo_system', + 'mojo_utility', ], 'sources': [ 'examples/sample_app/gles2_client_impl.cc', 'examples/sample_app/gles2_client_impl.cc', - 'examples/sample_app/native_viewport_client_impl.cc', - 'examples/sample_app/native_viewport_client_impl.h', 'examples/sample_app/sample_app.cc', 'examples/sample_app/spinning_cube.cc', 'examples/sample_app/spinning_cube.h', + 'public/cpp/application/lib/mojo_main_standalone.cc', ], }, { @@ -32,31 +37,430 @@ 'includes': [ 'build/package_app.gypi' ], }, { - 'target_name': 'mojo_hello_world_bindings', - 'type': 'static_library', + 'target_name': 'mojo_compositor_app', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../cc/cc.gyp:cc', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_application', + 'mojo_cc_support', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_gles2', + 'mojo_native_viewport_bindings', + 'mojo_system_impl', + ], 'sources': [ - 'examples/hello_world_service/hello_world_service.mojom', + 'examples/compositor_app/compositor_app.cc', + 'examples/compositor_app/compositor_host.cc', + 'examples/compositor_app/compositor_host.h', + 'public/cpp/application/lib/mojo_main_chromium.cc', ], - 'includes': [ 'public/bindings/mojom_bindings_generator.gypi' ], - 'export_dependent_settings': [ - 'mojo_bindings', + }, + { + 'target_name': 'package_mojo_compositor_app', + 'variables': { + 'app_name': 'mojo_compositor_app', + }, + 'includes': [ 'build/package_app.gypi' ], + }, + { + 'target_name': 'mojo_wget', + 'type': 'shared_library', + 'dependencies': [ + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_standalone', + 'mojo_network_bindings', 'mojo_system', + 'mojo_utility', + ], + 'sources': [ + 'examples/wget/wget.cc', + 'public/cpp/application/lib/mojo_main_standalone.cc', + ], + }, + { + 'target_name': 'package_mojo_wget', + 'variables': { + 'app_name': 'mojo_wget', + }, + 'includes': [ 'build/package_app.gypi' ], + }, + { + 'target_name': 'mojo_html_viewer', + 'type': 'shared_library', + 'dependencies': [ + '../net/net.gyp:net', + '../skia/skia.gyp:skia', + '../third_party/WebKit/public/blink.gyp:blink', + '../url/url.gyp:url_lib', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_navigation_bindings', + 'mojo_network_bindings', + 'mojo_launcher_bindings', + 'mojo_system_impl', + 'mojo_utility', + 'mojo_view_manager_lib', + ], + 'include_dirs': [ + 'third_party/WebKit' + ], + 'sources': [ + 'examples/html_viewer/blink_platform_impl.cc', + 'examples/html_viewer/blink_platform_impl.h', + 'examples/html_viewer/html_viewer.cc', + 'examples/html_viewer/html_document_view.cc', + 'examples/html_viewer/html_document_view.h', + 'examples/html_viewer/webmimeregistry_impl.cc', + 'examples/html_viewer/webmimeregistry_impl.h', + 'examples/html_viewer/webthread_impl.cc', + 'examples/html_viewer/webthread_impl.h', + 'examples/html_viewer/weburlloader_impl.cc', + 'examples/html_viewer/weburlloader_impl.h', + 'public/cpp/application/lib/mojo_main_chromium.cc', ], }, { - 'target_name': 'mojo_hello_world_service', - 'type': 'static_library', + 'target_name': 'mojo_image_viewer', + 'type': 'shared_library', + 'dependencies': [ + '../skia/skia.gyp:skia', + '../ui/gfx/gfx.gyp:gfx', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_navigation_bindings', + 'mojo_network_bindings', + 'mojo_launcher_bindings', + 'mojo_system_impl', + 'mojo_utility', + 'mojo_view_manager_lib', + ], + 'sources': [ + 'examples/image_viewer/image_viewer.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_pepper_container_app', + 'type': 'shared_library', 'dependencies': [ '../base/base.gyp:base', - 'mojo_hello_world_bindings', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../gpu/gpu.gyp:command_buffer_common', + '../ppapi/ppapi.gyp:ppapi_c', + '../ppapi/ppapi_internal.gyp:ppapi_example_gles2_spinning_cube', + '../ui/events/events.gyp:events_base', + 'mojo_application', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_gles2', + 'mojo_native_viewport_bindings', + 'mojo_system_impl', ], - 'export_dependent_settings': [ - 'mojo_hello_world_bindings', + 'defines': [ + # We don't really want to export. We could change how + # ppapi_{shared,thunk}_export.h are defined to avoid this. + 'PPAPI_SHARED_IMPLEMENTATION', + 'PPAPI_THUNK_IMPLEMENTATION', ], 'sources': [ - 'examples/hello_world_service/hello_world_service_impl.cc', - 'examples/hello_world_service/hello_world_service_impl.h', + # Source files from ppapi/. + # An alternative is to depend on + # '../ppapi/ppapi_internal.gyp:ppapi_shared', but that target includes + # a lot of things that we don't need. + # TODO(yzshen): Consider extracting these files into a separate target + # which mojo_pepper_container_app and ppapi_shared both depend on. + '../ppapi/shared_impl/api_id.h', + '../ppapi/shared_impl/callback_tracker.cc', + '../ppapi/shared_impl/callback_tracker.h', + '../ppapi/shared_impl/host_resource.cc', + '../ppapi/shared_impl/host_resource.h', + '../ppapi/shared_impl/id_assignment.cc', + '../ppapi/shared_impl/id_assignment.h', + '../ppapi/shared_impl/ppapi_globals.cc', + '../ppapi/shared_impl/ppapi_globals.h', + '../ppapi/shared_impl/ppapi_shared_export.h', + '../ppapi/shared_impl/ppb_message_loop_shared.cc', + '../ppapi/shared_impl/ppb_message_loop_shared.h', + '../ppapi/shared_impl/ppb_view_shared.cc', + '../ppapi/shared_impl/ppb_view_shared.h', + '../ppapi/shared_impl/proxy_lock.cc', + '../ppapi/shared_impl/proxy_lock.h', + '../ppapi/shared_impl/resource.cc', + '../ppapi/shared_impl/resource.h', + '../ppapi/shared_impl/resource_tracker.cc', + '../ppapi/shared_impl/resource_tracker.h', + '../ppapi/shared_impl/scoped_pp_resource.cc', + '../ppapi/shared_impl/scoped_pp_resource.h', + '../ppapi/shared_impl/singleton_resource_id.h', + '../ppapi/shared_impl/tracked_callback.cc', + '../ppapi/shared_impl/tracked_callback.h', + '../ppapi/thunk/enter.cc', + '../ppapi/thunk/enter.h', + '../ppapi/thunk/interfaces_ppb_private.h', + '../ppapi/thunk/interfaces_ppb_private_flash.h', + '../ppapi/thunk/interfaces_ppb_private_no_permissions.h', + '../ppapi/thunk/interfaces_ppb_public_dev.h', + '../ppapi/thunk/interfaces_ppb_public_dev_channel.h', + '../ppapi/thunk/interfaces_ppb_public_stable.h', + '../ppapi/thunk/interfaces_preamble.h', + '../ppapi/thunk/ppapi_thunk_export.h', + '../ppapi/thunk/ppb_graphics_3d_api.h', + '../ppapi/thunk/ppb_graphics_3d_thunk.cc', + '../ppapi/thunk/ppb_instance_api.h', + '../ppapi/thunk/ppb_instance_thunk.cc', + '../ppapi/thunk/ppb_message_loop_api.h', + '../ppapi/thunk/ppb_view_api.h', + '../ppapi/thunk/ppb_view_thunk.cc', + '../ppapi/thunk/resource_creation_api.h', + '../ppapi/thunk/thunk.h', + + 'examples/pepper_container_app/graphics_3d_resource.cc', + 'examples/pepper_container_app/graphics_3d_resource.h', + 'examples/pepper_container_app/interface_list.cc', + 'examples/pepper_container_app/interface_list.h', + 'examples/pepper_container_app/mojo_ppapi_globals.cc', + 'examples/pepper_container_app/mojo_ppapi_globals.h', + 'examples/pepper_container_app/pepper_container_app.cc', + 'examples/pepper_container_app/plugin_instance.cc', + 'examples/pepper_container_app/plugin_instance.h', + 'examples/pepper_container_app/plugin_module.cc', + 'examples/pepper_container_app/plugin_module.h', + 'examples/pepper_container_app/ppb_core_thunk.cc', + 'examples/pepper_container_app/ppb_opengles2_thunk.cc', + 'examples/pepper_container_app/resource_creation_impl.cc', + 'examples/pepper_container_app/resource_creation_impl.h', + 'examples/pepper_container_app/thunk.h', + 'examples/pepper_container_app/type_converters.h', + 'public/cpp/application/lib/mojo_main_chromium.cc', ], }, ], + 'conditions': [ + ['use_aura==1', { + 'targets': [ + { + 'target_name': 'mojo_aura_demo', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../cc/cc.gyp:cc', + '../ui/aura/aura.gyp:aura', + '../ui/base/ui_base.gyp:ui_base', + '../ui/compositor/compositor.gyp:compositor', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_application', + 'mojo_aura_support', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_system_impl', + 'mojo_view_manager_lib', + ], + 'sources': [ + 'examples/aura_demo/aura_demo.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_aura_demo_init', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + 'mojo_application', + 'mojo_environment_chromium', + 'mojo_system_impl', + 'mojo_view_manager_bindings', + ], + 'sources': [ + 'examples/aura_demo/view_manager_init.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_browser', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../cc/cc.gyp:cc', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + '../ui/aura/aura.gyp:aura', + '../ui/base/ui_base.gyp:ui_base', + '../ui/compositor/compositor.gyp:compositor', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/resources/ui_resources.gyp:ui_resources', + '../ui/resources/ui_resources.gyp:ui_test_pak', + '../ui/views/views.gyp:views', + '../url/url.gyp:url_lib', + 'mojo_application', + 'mojo_aura_support', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_input_events_lib', + 'mojo_navigation_bindings', + 'mojo_system_impl', + 'mojo_views_support', + 'mojo_view_manager_bindings', + 'mojo_view_manager_lib', + ], + 'sources': [ + 'examples/browser/browser.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'package_mojo_aura_demo', + 'variables': { + 'app_name': 'mojo_aura_demo', + }, + 'includes': [ 'build/package_app.gypi' ], + }, + { + 'target_name': 'mojo_demo_launcher', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../skia/skia.gyp:skia', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_gles2', + 'mojo_view_manager_bindings', + 'mojo_system_impl', + 'mojo_utility', + ], + 'sources': [ + 'examples/demo_launcher/demo_launcher.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_window_manager_bindings', + 'type': 'static_library', + 'sources': [ + 'examples/window_manager/window_manager.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + }, + { + 'target_name': 'mojo_window_manager', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_gles2', + 'mojo_launcher_bindings', + 'mojo_navigation_bindings', + 'mojo_view_manager_lib', + 'mojo_window_manager_bindings', + 'mojo_system_impl', + 'mojo_utility', + ], + 'sources': [ + 'examples/window_manager/window_manager.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_embedded_app', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + '../url/url.gyp:url_lib', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_gles2', + 'mojo_navigation_bindings', + 'mojo_view_manager_lib', + 'mojo_window_manager_bindings', + 'mojo_system_impl', + 'mojo_utility', + ], + 'sources': [ + 'examples/embedded_app/embedded_app.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_nesting_app', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + '../url/url.gyp:url_lib', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_gles2', + 'mojo_navigation_bindings', + 'mojo_view_manager_lib', + 'mojo_window_manager_bindings', + 'mojo_system_impl', + 'mojo_utility', + ], + 'sources': [ + 'examples/nesting_app/nesting_app.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + ], + }], + ['OS=="linux"', { + 'targets': [ + { + 'target_name': 'mojo_dbus_echo', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_standalone', + 'mojo_echo_bindings', + 'mojo_system', + 'mojo_utility', + ], + 'sources': [ + 'examples/dbus_echo/dbus_echo_app.cc', + 'public/cpp/application/lib/mojo_main_standalone.cc', + ], + }, + ], + }], + ], } diff --git a/chromium/mojo/mojo_js_unittests.isolate b/chromium/mojo/mojo_js_unittests.isolate new file mode 100644 index 00000000000..6ceaab17342 --- /dev/null +++ b/chromium/mojo/mojo_js_unittests.isolate @@ -0,0 +1,52 @@ +# 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. +{ + 'includes': [ + '../third_party/icu/icu.isolate', + ], + 'conditions': [ + ['OS=="win" or OS=="mac" or OS=="linux"', { + 'variables': { + 'command': [ + '../testing/test_env.py', + '<(PRODUCT_DIR)/mojo_js_unittests<(EXECUTABLE_SUFFIX)', + '--brave-new-test-launcher', + '--test-launcher-bot-mode', + ], + 'isolate_dependency_tracked': [ + '../gin/test/expect.js', + '../testing/test_env.py', + '<(PRODUCT_DIR)/mojo_js_unittests<(EXECUTABLE_SUFFIX)', + ], + 'isolate_dependency_untracked': [ + '<(PRODUCT_DIR)/gen/mojo/public/interfaces/bindings/tests/', + 'bindings/js/', + 'public/js/bindings/', + ], + }, + }], + ['OS=="win"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/mojo_system.dll', + '<(PRODUCT_DIR)/mojo_test_support.dll', + ], + }, + }], + ['OS=="linux"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/lib/libmojo_test_support.so', + ], + }, + }], + ['OS=="mac"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/libmojo_test_support.dylib', + ], + }, + }], + ], +} diff --git a/chromium/mojo/mojo_public.gypi b/chromium/mojo/mojo_public.gypi index f3b1cd855b0..2690c7c773b 100644 --- a/chromium/mojo/mojo_public.gypi +++ b/chromium/mojo/mojo_public.gypi @@ -2,7 +2,7 @@ 'targets': [ { 'target_name': 'mojo_system', - 'type': 'shared_library', + 'type': 'static_library', 'defines': [ 'MOJO_SYSTEM_IMPLEMENTATION', ], @@ -14,13 +14,28 @@ '..', ], }, + 'all_dependent_settings': { + 'conditions': [ + # We need to be able to call the MojoSetSystemThunks() function in + # system_thunks.cc + ['OS=="android"', { + 'ldflags!': [ + '-Wl,--exclude-libs=ALL', + ], + }], + ], + }, 'sources': [ - 'public/system/core.h', - 'public/system/core_cpp.h', - 'public/system/core_private.cc', - 'public/system/core_private.h', - 'public/system/macros.h', - 'public/system/system_export.h', + 'public/c/system/buffer.h', + 'public/c/system/core.h', + 'public/c/system/data_pipe.h', + 'public/c/system/functions.h', + 'public/c/system/macros.h', + 'public/c/system/message_pipe.h', + 'public/c/system/system_export.h', + 'public/c/system/types.h', + 'public/platform/native/system_thunks.cc', + 'public/platform/native/system_thunks.h', ], }, { @@ -28,165 +43,400 @@ 'type': 'shared_library', 'defines': [ 'MOJO_GLES2_IMPLEMENTATION', + 'GLES2_USE_MOJO', ], 'include_dirs': [ '..', ], + 'dependencies': [ + '../third_party/khronos/khronos.gyp:khronos_headers' + ], 'direct_dependent_settings': { 'include_dirs': [ '..', ], + 'defines': [ + 'GLES2_USE_MOJO', + ], }, 'sources': [ - 'public/gles2/gles2.h', + 'public/c/gles2/gles2.h', + 'public/c/gles2/gles2_export.h', 'public/gles2/gles2_private.cc', 'public/gles2/gles2_private.h', ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + # Make it a run-path dependent library. + 'DYLIB_INSTALL_NAME_BASE': '@loader_path', + }, + }], + ], }, { - 'target_name': 'mojo_public_test_support', + 'target_name': 'mojo_test_support', + 'type': 'shared_library', + 'defines': [ + 'MOJO_TEST_SUPPORT_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + 'sources': [ + 'public/c/test_support/test_support.h', + 'public/c/test_support/test_support_export.h', + 'public/tests/test_support_private.cc', + 'public/tests/test_support_private.h', + ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + # Make it a run-path dependent library. + 'DYLIB_INSTALL_NAME_BASE': '@loader_path', + }, + }], + ], + }, + { + 'target_name': 'mojo_public_test_utils', 'type': 'static_library', 'dependencies': [ '../base/base.gyp:base', '../testing/gtest.gyp:gtest', - 'mojo_system', + 'mojo_test_support', + ], + 'sources': [ + 'public/cpp/test_support/lib/test_support.cc', + 'public/cpp/test_support/lib/test_utils.cc', + 'public/cpp/test_support/test_utils.h', + ], + }, + # TODO(vtl): Reorganize the mojo_public_*_unittests. + { + 'target_name': 'mojo_public_bindings_unittests', + 'type': 'executable', + 'dependencies': [ + '../testing/gtest.gyp:gtest', + 'mojo_cpp_bindings', + 'mojo_environment_standalone', + 'mojo_public_test_utils', + 'mojo_run_all_unittests', + 'mojo_public_test_interfaces', + 'mojo_utility', + ], + 'sources': [ + 'public/cpp/bindings/tests/array_unittest.cc', + 'public/cpp/bindings/tests/bounds_checker_unittest.cc', + 'public/cpp/bindings/tests/buffer_unittest.cc', + 'public/cpp/bindings/tests/connector_unittest.cc', + 'public/cpp/bindings/tests/handle_passing_unittest.cc', + 'public/cpp/bindings/tests/interface_ptr_unittest.cc', + 'public/cpp/bindings/tests/request_response_unittest.cc', + 'public/cpp/bindings/tests/router_unittest.cc', + 'public/cpp/bindings/tests/sample_service_unittest.cc', + 'public/cpp/bindings/tests/string_unittest.cc', + 'public/cpp/bindings/tests/struct_unittest.cc', + 'public/cpp/bindings/tests/type_conversion_unittest.cc', + 'public/cpp/bindings/tests/validation_test_input_parser.cc', + 'public/cpp/bindings/tests/validation_test_input_parser.h', + 'public/cpp/bindings/tests/validation_unittest.cc', + ], + }, + { + 'target_name': 'mojo_public_environment_unittests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + 'mojo_environment_standalone', + 'mojo_public_test_utils', + 'mojo_run_all_unittests', + 'mojo_utility', + ], + 'sources': [ + 'public/cpp/environment/tests/async_waiter_unittest.cc', + 'public/cpp/environment/tests/logger_unittest.cc', + 'public/cpp/environment/tests/logging_unittest.cc', + ], + }, + { + 'target_name': 'mojo_public_system_unittests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + 'mojo_cpp_bindings', + 'mojo_public_test_utils', + 'mojo_run_all_unittests', ], 'sources': [ - 'public/tests/simple_bindings_support.cc', - 'public/tests/simple_bindings_support.h', - 'public/tests/test_support.cc', - 'public/tests/test_support.h', + 'public/c/system/tests/core_unittest.cc', + 'public/c/system/tests/core_unittest_pure_c.c', + 'public/c/system/tests/macros_unittest.cc', + 'public/cpp/system/tests/core_unittest.cc', + 'public/cpp/system/tests/macros_unittest.cc', ], }, { - 'target_name': 'mojo_public_unittests', + 'target_name': 'mojo_public_utility_unittests', 'type': 'executable', 'dependencies': [ + '../base/base.gyp:base', '../testing/gtest.gyp:gtest', - 'mojo_bindings', - 'mojo_public_test_support', + 'mojo_cpp_bindings', + 'mojo_public_test_utils', 'mojo_run_all_unittests', - 'mojo_system', + 'mojo_utility', ], 'sources': [ - 'public/tests/bindings_array_unittest.cc', - 'public/tests/bindings_connector_unittest.cc', - 'public/tests/bindings_handle_passing_unittest.cc', - 'public/tests/bindings_remote_ptr_unittest.cc', - 'public/tests/bindings_type_conversion_unittest.cc', - 'public/tests/buffer_unittest.cc', - 'public/tests/math_calculator.mojom', - 'public/tests/sample_factory.mojom', - 'public/tests/system_core_cpp_unittest.cc', - 'public/tests/system_core_unittest.cc', - 'public/tests/test_structs.mojom', + 'public/cpp/utility/tests/mutex_unittest.cc', + 'public/cpp/utility/tests/run_loop_unittest.cc', + 'public/cpp/utility/tests/thread_unittest.cc', + ], + 'conditions': [ + # See crbug.com/342893: + ['OS=="win"', { + 'sources!': [ + 'public/cpp/utility/tests/mutex_unittest.cc', + 'public/cpp/utility/tests/thread_unittest.cc', + ], + }], ], - 'includes': [ 'public/bindings/mojom_bindings_generator.gypi' ], }, { - 'target_name': 'mojo_public_perftests', + 'target_name': 'mojo_public_system_perftests', 'type': 'executable', 'dependencies': [ '../base/base.gyp:base', '../testing/gtest.gyp:gtest', - 'mojo_public_test_support', + 'mojo_public_test_utils', 'mojo_run_all_perftests', - 'mojo_system', + 'mojo_utility', ], 'sources': [ - 'public/tests/system_core_perftest.cc', + 'public/c/system/tests/core_perftest.cc', ], }, { - 'target_name': 'mojo_bindings', + # GN version: //mojo/public/cpp/bindings + 'target_name': 'mojo_cpp_bindings', 'type': 'static_library', 'include_dirs': [ '..' ], 'sources': [ - 'public/bindings/lib/array.cc', - 'public/bindings/lib/array.h', - 'public/bindings/lib/array_internal.h', - 'public/bindings/lib/array_internal.cc', - 'public/bindings/lib/bindings.h', - 'public/bindings/lib/bindings_internal.h', - 'public/bindings/lib/bindings_serialization.cc', - 'public/bindings/lib/bindings_serialization.h', - 'public/bindings/lib/bindings_support.cc', - 'public/bindings/lib/bindings_support.h', - 'public/bindings/lib/buffer.cc', - 'public/bindings/lib/buffer.h', - 'public/bindings/lib/connector.cc', - 'public/bindings/lib/connector.h', - 'public/bindings/lib/message.cc', - 'public/bindings/lib/message.h', - 'public/bindings/lib/message_builder.cc', - 'public/bindings/lib/message_builder.h', - 'public/bindings/lib/message_queue.cc', - 'public/bindings/lib/message_queue.h', - ], - }, - { - 'target_name': 'mojo_sample_service', + 'public/cpp/bindings/array.h', + 'public/cpp/bindings/callback.h', + 'public/cpp/bindings/error_handler.h', + 'public/cpp/bindings/interface_impl.h', + 'public/cpp/bindings/interface_ptr.h', + 'public/cpp/bindings/interface_request.h', + 'public/cpp/bindings/message.h', + 'public/cpp/bindings/message_filter.h', + 'public/cpp/bindings/no_interface.h', + 'public/cpp/bindings/string.h', + 'public/cpp/bindings/sync_dispatcher.h', + 'public/cpp/bindings/type_converter.h', + 'public/cpp/bindings/lib/array_internal.h', + 'public/cpp/bindings/lib/array_internal.cc', + 'public/cpp/bindings/lib/array_serialization.h', + 'public/cpp/bindings/lib/bindings_internal.h', + 'public/cpp/bindings/lib/bindings_serialization.cc', + 'public/cpp/bindings/lib/bindings_serialization.h', + 'public/cpp/bindings/lib/bounds_checker.cc', + 'public/cpp/bindings/lib/bounds_checker.h', + 'public/cpp/bindings/lib/buffer.h', + 'public/cpp/bindings/lib/callback_internal.h', + 'public/cpp/bindings/lib/connector.cc', + 'public/cpp/bindings/lib/connector.h', + 'public/cpp/bindings/lib/filter_chain.cc', + 'public/cpp/bindings/lib/filter_chain.h', + 'public/cpp/bindings/lib/fixed_buffer.cc', + 'public/cpp/bindings/lib/fixed_buffer.h', + 'public/cpp/bindings/lib/interface_impl_internal.h', + 'public/cpp/bindings/lib/interface_ptr_internal.h', + 'public/cpp/bindings/lib/message.cc', + 'public/cpp/bindings/lib/message_builder.cc', + 'public/cpp/bindings/lib/message_builder.h', + 'public/cpp/bindings/lib/message_filter.cc', + 'public/cpp/bindings/lib/message_header_validator.cc', + 'public/cpp/bindings/lib/message_header_validator.h', + 'public/cpp/bindings/lib/message_internal.h', + 'public/cpp/bindings/lib/message_queue.cc', + 'public/cpp/bindings/lib/message_queue.h', + 'public/cpp/bindings/lib/no_interface.cc', + 'public/cpp/bindings/lib/router.cc', + 'public/cpp/bindings/lib/router.h', + 'public/cpp/bindings/lib/shared_data.h', + 'public/cpp/bindings/lib/shared_ptr.h', + 'public/cpp/bindings/lib/string_serialization.h', + 'public/cpp/bindings/lib/string_serialization.cc', + 'public/cpp/bindings/lib/sync_dispatcher.cc', + 'public/cpp/bindings/lib/validation_errors.cc', + 'public/cpp/bindings/lib/validation_errors.h', + ], + }, + { + # GN version: //mojo/public/js/bindings + 'target_name': 'mojo_js_bindings', 'type': 'static_library', + 'include_dirs': [ + '..' + ], 'sources': [ - 'public/bindings/sample/sample_service.mojom', + 'public/js/bindings/constants.cc', + 'public/js/bindings/constants.h', ], - 'includes': [ 'public/bindings/mojom_bindings_generator.gypi' ], + }, + { + 'target_name': 'mojo_public_test_interfaces', + 'type': 'static_library', + 'sources': [ + 'public/interfaces/bindings/tests/math_calculator.mojom', + 'public/interfaces/bindings/tests/sample_factory.mojom', + 'public/interfaces/bindings/tests/sample_import.mojom', + 'public/interfaces/bindings/tests/sample_import2.mojom', + 'public/interfaces/bindings/tests/sample_interfaces.mojom', + 'public/interfaces/bindings/tests/sample_service.mojom', + 'public/interfaces/bindings/tests/test_structs.mojom', + 'public/interfaces/bindings/tests/validation_test_interfaces.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], 'export_dependent_settings': [ - 'mojo_bindings', - 'mojo_system', + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', ], }, { - 'target_name': 'mojo_bindings_unittests', - 'type': 'executable', + 'target_name': 'mojo_environment_standalone', + 'type': 'static_library', 'sources': [ - 'public/bindings/sample/sample_service_unittests.cc', + 'public/c/environment/async_waiter.h', + 'public/c/environment/logger.h', + 'public/c/environment/logging.h', + 'public/cpp/environment/environment.h', + 'public/cpp/environment/lib/default_async_waiter.cc', + 'public/cpp/environment/lib/default_async_waiter.h', + 'public/cpp/environment/lib/default_logger.cc', + 'public/cpp/environment/lib/default_logger.h', + 'public/cpp/environment/lib/environment.cc', + 'public/cpp/environment/lib/logging.cc', ], - 'dependencies': [ - '../testing/gtest.gyp:gtest', - 'mojo_public_test_support', - 'mojo_run_all_unittests', - 'mojo_sample_service', + 'include_dirs': [ + '..', ], }, { 'target_name': 'mojo_utility', 'type': 'static_library', 'sources': [ - 'public/utility/bindings_support_impl.cc', - 'public/utility/bindings_support_impl.h', - 'public/utility/environment.cc', - 'public/utility/environment.h', - 'public/utility/run_loop.cc', - 'public/utility/run_loop.h', - 'public/utility/run_loop_handler.h', - 'public/utility/thread_local.h', - 'public/utility/thread_local_posix.cc', - 'public/utility/thread_local_win.cc', + 'public/cpp/utility/mutex.h', + 'public/cpp/utility/run_loop.h', + 'public/cpp/utility/run_loop_handler.h', + 'public/cpp/utility/thread.h', + 'public/cpp/utility/lib/mutex.cc', + 'public/cpp/utility/lib/run_loop.cc', + 'public/cpp/utility/lib/thread.cc', + 'public/cpp/utility/lib/thread_local.h', + 'public/cpp/utility/lib/thread_local_posix.cc', + 'public/cpp/utility/lib/thread_local_win.cc', + ], + 'conditions': [ + # See crbug.com/342893: + ['OS=="win"', { + 'sources!': [ + 'public/cpp/utility/mutex.h', + 'public/cpp/utility/thread.h', + 'public/cpp/utility/lib/mutex.cc', + 'public/cpp/utility/lib/thread.cc', + ], + }], ], 'include_dirs': [ '..', ], }, { - 'target_name': 'mojo_utility_unittests', - 'type': 'executable', + # GN version: //mojo/public/interfaces/interface_provider:interface_provider + 'target_name': 'mojo_interface_provider_bindings', + 'type': 'static_library', + 'sources': [ + 'public/interfaces/interface_provider/interface_provider.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], 'dependencies': [ - '../base/base.gyp:base', - '../testing/gtest.gyp:gtest', - 'mojo_bindings', - 'mojo_public_test_support', - 'mojo_run_all_unittests', - 'mojo_system', - 'mojo_utility', + 'mojo_cpp_bindings', ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + }, + { + # GN version: //mojo/public/interfaces/service_provider:service_provider + 'target_name': 'mojo_service_provider_bindings', + 'type': 'static_library', 'sources': [ - 'public/utility/bindings_support_impl_unittest.cc', - 'public/utility/run_loop_unittest.cc', - 'public/utility/thread_local_unittest.cc', + 'public/interfaces/service_provider/service_provider.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', ], }, + { + 'target_name': 'mojo_application', + 'type': 'static_library', + 'sources': [ + 'public/cpp/application/application.h', + 'public/cpp/application/connect.h', + 'public/cpp/application/lib/application.cc', + 'public/cpp/application/lib/service_connector.cc', + 'public/cpp/application/lib/service_connector.h', + 'public/cpp/application/lib/service_registry.cc', + 'public/cpp/application/lib/service_registry.h', + ], + 'dependencies': [ + 'mojo_service_provider_bindings', + ], + 'export_dependent_settings': [ + 'mojo_service_provider_bindings', + ], + }, + ], + 'conditions': [ + ['OS == "android"', { + 'targets': [ + { + 'target_name': 'mojo_public_java', + 'type': 'none', + 'variables': { + 'java_in_dir': 'public/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'mojo_bindings_java', + 'type': 'none', + 'variables': { + 'java_in_dir': 'bindings/java', + }, + 'dependencies': [ + 'mojo_public_java', + ], + 'includes': [ '../build/java.gypi' ], + }, + ], + }], ], } diff --git a/chromium/mojo/mojo_services.gypi b/chromium/mojo/mojo_services.gypi index 983d69f1ec4..341a418af87 100644 --- a/chromium/mojo/mojo_services.gypi +++ b/chromium/mojo/mojo_services.gypi @@ -1,15 +1,104 @@ { 'targets': [ { + 'target_name': 'mojo_echo_bindings', + 'type': 'static_library', + 'sources': [ + 'services/dbus_echo/echo.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + }, + { + 'target_name': 'mojo_input_events_lib', + 'type': '<(component)', + 'defines': [ + 'MOJO_INPUT_EVENTS_IMPLEMENTATION', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../ui/events/events.gyp:events', + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_environment_chromium', + 'mojo_input_events_bindings', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_system_impl', + ], + 'sources': [ + 'services/public/cpp/input_events/lib/input_events_type_converters.cc', + 'services/public/cpp/input_events/input_events_type_converters.h', + 'services/public/cpp/input_events/mojo_input_events_export.h', + ], + }, + { + 'target_name': 'mojo_input_events_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/input_events/input_events.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + 'mojo_geometry_bindings', + ], + }, + { + 'target_name': 'mojo_geometry_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/geometry/geometry.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + }, + { + 'target_name': 'mojo_geometry_lib', + 'type': '<(component)', + 'defines': [ + 'MOJO_GEOMETRY_IMPLEMENTATION', + ], + 'dependencies': [ + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_system_impl', + ], + 'sources': [ + 'services/public/cpp/geometry/lib/geometry_type_converters.cc', + 'services/public/cpp/geometry/geometry_type_converters.h', + ], + }, + { 'target_name': 'mojo_gles2_bindings', 'type': 'static_library', 'sources': [ - 'services/gles2/gles2.mojom', + 'services/gles2/command_buffer.mojom', + 'services/gles2/command_buffer_type_conversions.cc', + 'services/gles2/command_buffer_type_conversions.h', + 'services/gles2/mojo_buffer_backing.cc', + 'services/gles2/mojo_buffer_backing.h', ], - 'includes': [ 'public/bindings/mojom_bindings_generator.gypi' ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], 'export_dependent_settings': [ - 'mojo_bindings', - 'mojo_system', + 'mojo_cpp_bindings', + ], + 'dependencies': [ + '../gpu/gpu.gyp:command_buffer_common', + 'mojo_cpp_bindings', ], }, { @@ -18,8 +107,8 @@ 'dependencies': [ '../base/base.gyp:base', '../gpu/gpu.gyp:command_buffer_service', - '../gpu/gpu.gyp:gles2_implementation', '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', '../ui/gl/gl.gyp:gl', 'mojo_gles2_bindings', ], @@ -27,41 +116,55 @@ 'mojo_gles2_bindings', ], 'sources': [ - 'services/gles2/gles2_impl.cc', - 'services/gles2/gles2_impl.h', + 'services/gles2/command_buffer_impl.cc', + 'services/gles2/command_buffer_impl.h', ], }, { 'target_name': 'mojo_native_viewport_bindings', 'type': 'static_library', 'sources': [ - 'services/native_viewport/native_viewport.mojom', + 'services/public/interfaces/native_viewport/native_viewport.mojom', ], - 'includes': [ 'public/bindings/mojom_bindings_generator.gypi' ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], 'export_dependent_settings': [ - 'mojo_bindings', - 'mojo_system', + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_geometry_bindings', + 'mojo_gles2_bindings', + 'mojo_input_events_bindings', + 'mojo_cpp_bindings', ], }, { 'target_name': 'mojo_native_viewport_service', - 'type': 'static_library', + # This is linked directly into the embedder, so we make it a component. + 'type': '<(component)', 'dependencies': [ '../base/base.gyp:base', '../ui/events/events.gyp:events', '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_application', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', 'mojo_gles2_service', + 'mojo_input_events_lib', 'mojo_native_viewport_bindings', + 'mojo_system_impl', ], - 'export_dependent_settings': [ - 'mojo_native_viewport_bindings', + 'defines': [ + 'MOJO_NATIVE_VIEWPORT_IMPLEMENTATION', ], 'sources': [ 'services/native_viewport/native_viewport.h', 'services/native_viewport/native_viewport_android.cc', - 'services/native_viewport/native_viewport_impl.cc', - 'services/native_viewport/native_viewport_impl.h', 'services/native_viewport/native_viewport_mac.mm', + 'services/native_viewport/native_viewport_service.cc', + 'services/native_viewport/native_viewport_service.h', 'services/native_viewport/native_viewport_stub.cc', 'services/native_viewport/native_viewport_win.cc', 'services/native_viewport/native_viewport_x11.cc', @@ -79,5 +182,373 @@ }], ], }, + { + 'target_name': 'mojo_navigation_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/navigation/navigation.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + 'mojo_network_bindings', + ], + }, + { + 'target_name': 'mojo_network_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/network/network_error.mojom', + 'services/public/interfaces/network/network_service.mojom', + 'services/public/interfaces/network/url_loader.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + }, + { + 'target_name': 'mojo_network_service', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../net/net.gyp:net', + '../url/url.gyp:url_lib', + 'mojo_application', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_network_bindings', + 'mojo_system_impl', + ], + 'export_dependent_settings': [ + 'mojo_network_bindings', + ], + 'sources': [ + 'services/network/main.cc', + 'services/network/network_context.cc', + 'services/network/network_context.h', + 'services/network/network_service_impl.cc', + 'services/network/network_service_impl.h', + 'services/network/url_loader_impl.cc', + 'services/network/url_loader_impl.h', + ], + }, + { + 'target_name': 'mojo_view_manager_common', + 'type': 'static_library', + 'sources': [ + 'services/public/cpp/view_manager/types.h', + ], + }, + { + 'target_name': 'mojo_launcher_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/launcher/launcher.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + 'mojo_navigation_bindings', + ], + }, + { + 'target_name': 'mojo_launcher', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + '../url/url.gyp:url_lib', + 'mojo_application', + 'mojo_cpp_bindings', + 'mojo_environment_chromium', + 'mojo_launcher_bindings', + 'mojo_network_bindings', + 'mojo_system_impl', + 'mojo_utility', + ], + 'sources': [ + 'services/launcher/launcher.cc', + 'public/cpp/application/lib/mojo_main_chromium.cc', + ], + }, + { + 'target_name': 'mojo_view_manager_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/view_manager/view_manager.mojom', + 'services/public/interfaces/view_manager/view_manager_constants.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + 'mojo_geometry_bindings', + 'mojo_input_events_bindings', + ], + }, + { + 'target_name': 'mojo_view_manager_lib', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../skia/skia.gyp:skia', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + 'mojo_application', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_service_provider_bindings', + 'mojo_view_manager_bindings', + 'mojo_view_manager_common', + ], + 'sources': [ + 'services/public/cpp/view_manager/lib/node.cc', + 'services/public/cpp/view_manager/lib/node_observer.cc', + 'services/public/cpp/view_manager/lib/node_private.cc', + 'services/public/cpp/view_manager/lib/node_private.h', + 'services/public/cpp/view_manager/lib/view.cc', + 'services/public/cpp/view_manager/lib/view_private.cc', + 'services/public/cpp/view_manager/lib/view_private.h', + 'services/public/cpp/view_manager/lib/view_manager_client_impl.cc', + 'services/public/cpp/view_manager/lib/view_manager_client_impl.h', + 'services/public/cpp/view_manager/node.h', + 'services/public/cpp/view_manager/node_observer.h', + 'services/public/cpp/view_manager/view.h', + 'services/public/cpp/view_manager/view_manager.h', + 'services/public/cpp/view_manager/view_manager_delegate.h', + 'services/public/cpp/view_manager/view_observer.h', + ], + }, + { + 'target_name': 'mojo_view_manager_lib_unittests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_shell_test_support', + 'mojo_view_manager_bindings', + 'mojo_view_manager_lib', + ], + 'sources': [ + 'services/public/cpp/view_manager/tests/node_unittest.cc', + 'services/public/cpp/view_manager/tests/view_unittest.cc', + 'services/public/cpp/view_manager/tests/view_manager_unittest.cc', + ], + 'conditions': [ + ['use_aura==1', { + 'dependencies': [ + 'mojo_view_manager_run_unittests' + ], + }, { # use_aura==0 + 'dependencies': [ + 'mojo_run_all_unittests', + ], + }] + ], + }, + { + 'target_name': 'mojo_surfaces_bindings', + 'type': 'static_library', + 'sources': [ + 'services/public/interfaces/surfaces/surfaces.mojom', + 'services/public/interfaces/surfaces/surface_id.mojom', + 'services/public/interfaces/surfaces/quads.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + 'mojo_geometry_bindings', + ], + }, + { + 'target_name': 'mojo_test_service_bindings', + 'type': 'static_library', + 'sources': [ + 'services/test_service/test_service.mojom', + ], + 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ], + 'export_dependent_settings': [ + 'mojo_cpp_bindings', + ], + 'dependencies': [ + 'mojo_cpp_bindings', + ], + }, + { + 'target_name': 'mojo_test_service', + 'type': 'shared_library', + 'dependencies': [ + '../base/base.gyp:base', + 'mojo_application', + 'mojo_environment_standalone', + 'mojo_test_service_bindings', + 'mojo_system', + 'mojo_utility', + ], + 'sources': [ + 'public/cpp/application/lib/mojo_main_standalone.cc', + 'services/test_service/test_service_application.cc', + 'services/test_service/test_service_application.h', + 'services/test_service/test_service_impl.cc', + 'services/test_service/test_service_impl.h', + ], + }, + ], + 'conditions': [ + ['use_aura==1', { + 'targets': [ + { + 'target_name': 'mojo_view_manager', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + '../cc/cc.gyp:cc', + '../skia/skia.gyp:skia', + '../ui/aura/aura.gyp:aura', + '../ui/base/ui_base.gyp:ui_base', + '../ui/compositor/compositor.gyp:compositor', + '../ui/events/events.gyp:events', + '../ui/events/events.gyp:events_base', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + '../webkit/common/gpu/webkit_gpu.gyp:webkit_gpu', + 'mojo_application', + 'mojo_cc_support', + 'mojo_common_lib', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_gles2', + 'mojo_input_events_bindings', + 'mojo_input_events_lib', + 'mojo_native_viewport_bindings', + 'mojo_system_impl', + 'mojo_view_manager_bindings', + 'mojo_view_manager_common', + ], + 'sources': [ + 'public/cpp/application/lib/mojo_main_chromium.cc', + 'services/view_manager/ids.h', + 'services/view_manager/main.cc', + 'services/view_manager/node.cc', + 'services/view_manager/node.h', + 'services/view_manager/node_delegate.h', + 'services/view_manager/root_node_manager.cc', + 'services/view_manager/root_node_manager.h', + 'services/view_manager/root_view_manager.cc', + 'services/view_manager/root_view_manager.h', + 'services/view_manager/root_view_manager_delegate.h', + 'services/view_manager/screen_impl.cc', + 'services/view_manager/screen_impl.h', + 'services/view_manager/view.cc', + 'services/view_manager/view.h', + 'services/view_manager/view_manager_export.h', + 'services/view_manager/view_manager_init_service_impl.cc', + 'services/view_manager/view_manager_init_service_impl.h', + 'services/view_manager/view_manager_service_impl.cc', + 'services/view_manager/view_manager_service_impl.h', + 'services/view_manager/context_factory_impl.cc', + 'services/view_manager/context_factory_impl.h', + 'services/view_manager/window_tree_host_impl.cc', + 'services/view_manager/window_tree_host_impl.h', + ], + 'defines': [ + 'MOJO_VIEW_MANAGER_IMPLEMENTATION', + ], + }, + { + 'target_name': 'mojo_view_manager_run_unittests', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../ui/gl/gl.gyp:gl', + ], + 'sources': [ + 'services/public/cpp/view_manager/lib/view_manager_test_suite.cc', + 'services/public/cpp/view_manager/lib/view_manager_test_suite.h', + 'services/public/cpp/view_manager/lib/view_manager_unittests.cc', + ], + }, + { + 'target_name': 'mojo_view_manager_unittests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../skia/skia.gyp:skia', + '../testing/gtest.gyp:gtest', + '../ui/aura/aura.gyp:aura', + '../ui/gfx/gfx.gyp:gfx_geometry', + '../ui/gl/gl.gyp:gl', + 'mojo_application', + 'mojo_environment_chromium', + 'mojo_geometry_bindings', + 'mojo_geometry_lib', + 'mojo_input_events_bindings', + 'mojo_input_events_lib', + 'mojo_service_manager', + 'mojo_shell_test_support', + 'mojo_system_impl', + 'mojo_view_manager_bindings', + 'mojo_view_manager_common', + 'mojo_view_manager_run_unittests', + ], + 'sources': [ + 'services/view_manager/test_change_tracker.cc', + 'services/view_manager/test_change_tracker.h', + 'services/view_manager/view_manager_unittest.cc', + ], + }, + { + 'target_name': 'package_mojo_view_manager', + 'variables': { + 'app_name': 'mojo_view_manager', + }, + 'includes': [ 'build/package_app.gypi' ], + }, + ], + }], + ['OS=="linux"', { + 'targets': [ + { + 'target_name': 'mojo_dbus_echo_service', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + '../build/linux/system.gyp:dbus', + '../dbus/dbus.gyp:dbus', + 'mojo_application', + 'mojo_common_lib', + 'mojo_dbus_service', + 'mojo_echo_bindings', + 'mojo_environment_chromium', + 'mojo_system_impl', + ], + 'sources': [ + 'services/dbus_echo/dbus_echo_service.cc', + ], + }, + ], + }], ], } diff --git a/chromium/mojo/public/DEPS b/chromium/mojo/public/DEPS new file mode 100644 index 00000000000..0c679b9fd0f --- /dev/null +++ b/chromium/mojo/public/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "-base", + "-build", + "-mojo", + "+mojo/public", +] diff --git a/chromium/mojo/public/README.md b/chromium/mojo/public/README.md new file mode 100644 index 00000000000..a31a8a8245e --- /dev/null +++ b/chromium/mojo/public/README.md @@ -0,0 +1,43 @@ +Mojo Public API +=============== + +The Mojo Public API is a binary stable API to the Mojo system. + +It consists of support for a number of programming languages (with a directory +for each support language), some "build" tools and build-time requirements, and +interface definitions for Mojo services (specified using an IDL). + +Note that there are various subdirectories named tests/. These contain tests of +the code in the enclosing directory, and are not meant for use by Mojo +applications. + +C/CPP/JS +-------- + +The c/, cpp/, js/ subdirectories define the API for C, C++, and JavaScript, +respectively. + +The basic principle for these directories is that they consist of the source +files that one needs at build/deployment/run time (as appropriate for the +language), organized in a natural way for the particular language. + +Interfaces +---------- + +The interfaces/ subdirectory contains Mojo IDL (a.k.a. .mojom) descriptions of +standard Mojo services. + +Platform +-------- + +The platform/ subdirectory contains any build-time requirements (e.g., static +libraries) that may be needed to produce a Mojo application for certain +platforms, such as a native shared library or as a NaCl binary. + +Tools +----- + +The tools/ subdirectory contains tools that are useful/necessary at +build/deployment time. These tools may be needed (as a practical necessity) to +use the API in any given language, e.g., to generate bindings from Mojo IDL +files. diff --git a/chromium/mojo/public/bindings/mojom_bindings_generator.gypi b/chromium/mojo/public/bindings/mojom_bindings_generator.gypi deleted file mode 100644 index 45efe768d1b..00000000000 --- a/chromium/mojo/public/bindings/mojom_bindings_generator.gypi +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (c) 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. - -{ - 'variables': { - 'output_dir': '<(SHARED_INTERMEDIATE_DIR)/mojom', - }, - 'rules': [ - { - 'rule_name': 'Generate C++ source files from mojom files', - 'extension': 'mojom', - 'variables': { - 'mojom_bindings_generator': - '<(DEPTH)/mojo/public/bindings/mojom_bindings_generator.py', - }, - 'inputs': [ - '<(mojom_bindings_generator)', - '<(DEPTH)/mojo/public/bindings/parse/mojo_parser.py', - '<(DEPTH)/mojo/public/bindings/parse/mojo_translate.py', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/interface_declaration', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/interface_definition', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/interface_proxy_declaration', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/interface_stub_case', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/interface_stub_declaration', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/interface_stub_definition', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/module.cc-template', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/module.h-template', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/module_internal.h-template', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/params_definition', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/params_serialization', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/proxy_implementation', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_builder_definition', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_declaration', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_destructor', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_definition', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_serialization', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_serialization_definition', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/struct_serialization_traits', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/template_declaration', - '<(DEPTH)/mojo/public/bindings/generators/cpp_templates/wrapper_class_declaration', - '<(DEPTH)/mojo/public/bindings/generators/js_templates/module.js.tmpl', - '<(DEPTH)/mojo/public/bindings/generators/mojom.py', - '<(DEPTH)/mojo/public/bindings/generators/mojom_cpp_generator.py', - '<(DEPTH)/mojo/public/bindings/generators/mojom_data.py', - '<(DEPTH)/mojo/public/bindings/generators/mojom_generator.py', - '<(DEPTH)/mojo/public/bindings/generators/mojom_js_generator.py', - '<(DEPTH)/mojo/public/bindings/generators/mojom_pack.py', - '<(DEPTH)/mojo/public/bindings/generators/template_expander.py', - ], - 'outputs': [ - '<(output_dir)/<(RULE_INPUT_ROOT).cc', - '<(output_dir)/<(RULE_INPUT_ROOT).h', - '<(output_dir)/<(RULE_INPUT_ROOT).js', - '<(output_dir)/<(RULE_INPUT_ROOT)_internal.h', - ], - 'action': [ - 'python', '<@(mojom_bindings_generator)', - '<(RULE_INPUT_PATH)', - '-i', 'mojom', - '-o', '<(output_dir)', - ], - 'message': 'Generating C++ from mojom <(RULE_INPUT_PATH)', - 'process_outputs_as_sources': 1, - } - ], - 'dependencies': [ - 'mojo_bindings', - 'mojo_system', - ], - 'include_dirs': [ - '<(DEPTH)', - '<(SHARED_INTERMEDIATE_DIR)', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - '<(DEPTH)', - '<(SHARED_INTERMEDIATE_DIR)', - ], - }, - 'hard_dependency': 1, -} diff --git a/chromium/mojo/public/c/DEPS b/chromium/mojo/public/c/DEPS new file mode 100644 index 00000000000..52727706c38 --- /dev/null +++ b/chromium/mojo/public/c/DEPS @@ -0,0 +1,16 @@ +include_rules = [ + # Require explicit dependencies in each directory. + "-mojo/public", + # But everyone can depend on the C system headers. + "+mojo/public/c/system", +] + +specific_include_rules = { + r".*_(unit|perf)test\.cc": [ + "+testing", + # Our test harness is C++, so allow the use of C++: + "+mojo/public/cpp/system", + "+mojo/public/cpp/test_support", + "+mojo/public/cpp/utility", + ], +} diff --git a/chromium/mojo/public/c/README.md b/chromium/mojo/public/c/README.md new file mode 100644 index 00000000000..8e11545deb0 --- /dev/null +++ b/chromium/mojo/public/c/README.md @@ -0,0 +1,45 @@ +Mojo Public C API +================= + +This directory contains C language bindings for the Mojo Public API. + +Environment +----------- + +The environment/ subdirectory defines some common things that, while not part of +the system API, may be required for GLES2 (for example). These are things that a +Mojo application may be required to provide to the GLES2 (for example) library +in order to use it. (However, the Mojo application may implement these things as +it sees fit.) + +GLES2 +----- + +The gles2/ subdirectory defines the GLES2 C API that's available to Mojo +applications. To use GLES2, Mojo applications must link against a dynamic +library (the exact mechanism being platform-dependent) and use the header files +in this directory as well as the standard Khronos GLES2 header files. + +The reason for this, rather than providing GLES2 using the standard Mojo IPC +mechanism, is performance: The protocol (and transport mechanisms) used to +communicate with the Mojo GLES2 service is not stable nor "public" (mainly for +performance reasons), and using the dynamic library shields the application from +changes to the underlying system. + +System +------ + +The system/ subdirectory provides definitions of the basic low-level API used by +all Mojo applications (whether directly or indirectly). These consist primarily +of the IPC primitives used to communicate with Mojo services. + +Though the message protocol is stable, the implementation of the transport is +not, and access to the IPC mechanisms must be via the primitives defined in this +directory. + +Test Support +------------ + +This directory contains a C API for running tests. This API is only available +under special, specific test conditions. It is not meant for general use by Mojo +applications. diff --git a/chromium/mojo/public/c/environment/async_waiter.h b/chromium/mojo/public/c/environment/async_waiter.h new file mode 100644 index 00000000000..1eb06317d70 --- /dev/null +++ b/chromium/mojo/public/c/environment/async_waiter.h @@ -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. + +#ifndef MOJO_PUBLIC_C_ENVIRONMENT_ASYNC_WAITER_H_ +#define MOJO_PUBLIC_C_ENVIRONMENT_ASYNC_WAITER_H_ + +#include "mojo/public/c/system/types.h" + +typedef uintptr_t MojoAsyncWaitID; + +typedef void (*MojoAsyncWaitCallback)(void* closure, MojoResult result); + +struct MojoAsyncWaiter { + // Asynchronously call MojoWait on a background thread, and pass the result + // of MojoWait to the given MojoAsyncWaitCallback on the current thread. + // Returns a non-zero MojoAsyncWaitID that can be used with CancelWait to + // stop waiting. This identifier becomes invalid once the callback runs. + MojoAsyncWaitID (*AsyncWait)(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline, + MojoAsyncWaitCallback callback, + void* closure); + + // Cancel an existing call to AsyncWait with the given MojoAsyncWaitID. The + // corresponding MojoAsyncWaitCallback will not be called in this case. + void (*CancelWait)(MojoAsyncWaitID wait_id); +}; + +#endif // MOJO_PUBLIC_C_ENVIRONMENT_ASYNC_WAITER_H_ diff --git a/chromium/mojo/public/c/environment/logger.h b/chromium/mojo/public/c/environment/logger.h new file mode 100644 index 00000000000..5e9067fb509 --- /dev/null +++ b/chromium/mojo/public/c/environment/logger.h @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_ENVIRONMENT_LOGGER_H_ +#define MOJO_PUBLIC_C_ENVIRONMENT_LOGGER_H_ + +#include <stdint.h> + +// |MojoLogLevel|: Used to specify the type of log message. Values are ordered +// by severity (i.e., higher numerical values are more severe). + +typedef int32_t MojoLogLevel; + +#ifdef __cplusplus +const MojoLogLevel MOJO_LOG_LEVEL_VERBOSE = -1; +const MojoLogLevel MOJO_LOG_LEVEL_INFO = 0; +const MojoLogLevel MOJO_LOG_LEVEL_WARNING = 1; +const MojoLogLevel MOJO_LOG_LEVEL_ERROR = 2; +const MojoLogLevel MOJO_LOG_LEVEL_FATAL = 3; +#else +#define MOJO_LOG_LEVEL_VERBOSE ((MojoLogLevel) -1) +#define MOJO_LOG_LEVEL_INFO ((MojoLogLevel) 0) +#define MOJO_LOG_LEVEL_WARNING ((MojoLogLevel) 1) +#define MOJO_LOG_LEVEL_ERROR ((MojoLogLevel) 2) +#define MOJO_LOG_LEVEL_FATAL ((MojoLogLevel) 3) +#endif + +// Structure with basic logging functions (on top of which more friendly logging +// macros may be built). The functions are thread-safe, except for +// |SetMinimumLogLevel()| (see below). +struct MojoLogger { + // Logs |message| at level |log_level| if |log_level| is at least the current + // minimum log level. If |log_level| is |MOJO_LOG_LEVEL_FATAL| (or greater), + // aborts the application/process. + void (*LogMessage)(MojoLogLevel log_level, const char* message); + + // Gets the minimum log level (see above), which will always be at most + // |MOJO_LOG_LEVEL_FATAL|. (Though |LogMessage()| will automatically avoid + // logging messages below the minimum log level, this may be used to avoid + // extra work.) + MojoLogLevel (*GetMinimumLogLevel)(void); + + // Sets the minimum log level (see above) to the lesser of |minimum_log_level| + // and |MOJO_LOG_LEVEL_FATAL|. + // + // Warning: This function may not be thread-safe, and should not be called + // concurrently with other |MojoLogger| functions. (In some environments -- + // such as Chromium -- that share a logger across applications, this may mean + // that it is almost never safe to call this.) + void (*SetMinimumLogLevel)(MojoLogLevel minimum_log_level); +}; + +#endif // MOJO_PUBLIC_C_ENVIRONMENT_LOGGER_H_ diff --git a/chromium/mojo/public/c/gles2/DEPS b/chromium/mojo/public/c/gles2/DEPS new file mode 100644 index 00000000000..38874579404 --- /dev/null +++ b/chromium/mojo/public/c/gles2/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/public/c/environment", +] diff --git a/chromium/mojo/public/c/gles2/gles2.h b/chromium/mojo/public/c/gles2/gles2.h new file mode 100644 index 00000000000..44880afc1ed --- /dev/null +++ b/chromium/mojo/public/c/gles2/gles2.h @@ -0,0 +1,51 @@ +// 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 MOJO_PUBLIC_C_GLES2_GLES2_H_ +#define MOJO_PUBLIC_C_GLES2_GLES2_H_ + +// Note: This header should be compilable as C. + +#include <stdint.h> +#include <GLES2/gl2.h> + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/c/gles2/gles2_export.h" +#include "mojo/public/c/gles2/gles2_types.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MOJO_GLES2_EXPORT void MojoGLES2Initialize(const MojoAsyncWaiter* async_waiter); +MOJO_GLES2_EXPORT void MojoGLES2Terminate(void); +MOJO_GLES2_EXPORT MojoGLES2Context MojoGLES2CreateContext( + MojoHandle handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure); +MOJO_GLES2_EXPORT void MojoGLES2DestroyContext(MojoGLES2Context context); +MOJO_GLES2_EXPORT void MojoGLES2MakeCurrent(MojoGLES2Context context); +MOJO_GLES2_EXPORT void MojoGLES2SwapBuffers(void); +// TODO(piman): this doesn't belong here. +MOJO_GLES2_EXPORT void MojoGLES2RequestAnimationFrames( + MojoGLES2Context context); +MOJO_GLES2_EXPORT void MojoGLES2CancelAnimationFrames(MojoGLES2Context context); + +// TODO(piman): We shouldn't have to leak those 2 interfaces, especially in a +// type-unsafe way. +MOJO_GLES2_EXPORT void* MojoGLES2GetGLES2Interface(MojoGLES2Context context); +MOJO_GLES2_EXPORT void* MojoGLES2GetContextSupport(MojoGLES2Context context); + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + MOJO_GLES2_EXPORT ReturnType GL_APIENTRY gl##Function PARAMETERS; +#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h" +#undef VISIT_GL_CALL + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_GLES2_GLES2_H_ diff --git a/chromium/mojo/public/c/gles2/gles2_call_visitor_autogen.h b/chromium/mojo/public/c/gles2/gles2_call_visitor_autogen.h new file mode 100644 index 00000000000..72494c5416c --- /dev/null +++ b/chromium/mojo/public/c/gles2/gles2_call_visitor_autogen.h @@ -0,0 +1,544 @@ +// 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. + +// This file is auto-generated from +// gpu/command_buffer/build_gles2_cmd_buffer.py +// It's formatted by clang-format using chromium coding style: +// clang-format -i -style=chromium filename +// DO NOT EDIT! + +VISIT_GL_CALL(ActiveTexture, void, (GLenum texture), (texture)) +VISIT_GL_CALL(AttachShader, + void, + (GLuint program, GLuint shader), + (program, shader)) +VISIT_GL_CALL(BindAttribLocation, + void, + (GLuint program, GLuint index, const char* name), + (program, index, name)) +VISIT_GL_CALL(BindBuffer, + void, + (GLenum target, GLuint buffer), + (target, buffer)) +VISIT_GL_CALL(BindFramebuffer, + void, + (GLenum target, GLuint framebuffer), + (target, framebuffer)) +VISIT_GL_CALL(BindRenderbuffer, + void, + (GLenum target, GLuint renderbuffer), + (target, renderbuffer)) +VISIT_GL_CALL(BindTexture, + void, + (GLenum target, GLuint texture), + (target, texture)) +VISIT_GL_CALL(BlendColor, + void, + (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha), + (red, green, blue, alpha)) +VISIT_GL_CALL(BlendEquation, void, (GLenum mode), (mode)) +VISIT_GL_CALL(BlendEquationSeparate, + void, + (GLenum modeRGB, GLenum modeAlpha), + (modeRGB, modeAlpha)) +VISIT_GL_CALL(BlendFunc, + void, + (GLenum sfactor, GLenum dfactor), + (sfactor, dfactor)) +VISIT_GL_CALL(BlendFuncSeparate, + void, + (GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha), + (srcRGB, dstRGB, srcAlpha, dstAlpha)) +VISIT_GL_CALL(BufferData, + void, + (GLenum target, GLsizeiptr size, const void* data, GLenum usage), + (target, size, data, usage)) +VISIT_GL_CALL( + BufferSubData, + void, + (GLenum target, GLintptr offset, GLsizeiptr size, const void* data), + (target, offset, size, data)) +VISIT_GL_CALL(CheckFramebufferStatus, GLenum, (GLenum target), (target)) +VISIT_GL_CALL(Clear, void, (GLbitfield mask), (mask)) +VISIT_GL_CALL(ClearColor, + void, + (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha), + (red, green, blue, alpha)) +VISIT_GL_CALL(ClearDepthf, void, (GLclampf depth), (depth)) +VISIT_GL_CALL(ClearStencil, void, (GLint s), (s)) +VISIT_GL_CALL(ColorMask, + void, + (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha), + (red, green, blue, alpha)) +VISIT_GL_CALL(CompileShader, void, (GLuint shader), (shader)) +VISIT_GL_CALL( + CompressedTexImage2D, + void, + (GLenum target, + GLint level, + GLenum internalformat, + GLsizei width, + GLsizei height, + GLint border, + GLsizei imageSize, + const void* data), + (target, level, internalformat, width, height, border, imageSize, data)) +VISIT_GL_CALL( + CompressedTexSubImage2D, + void, + (GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLsizei width, + GLsizei height, + GLenum format, + GLsizei imageSize, + const void* data), + (target, level, xoffset, yoffset, width, height, format, imageSize, data)) +VISIT_GL_CALL(CopyTexImage2D, + void, + (GLenum target, + GLint level, + GLenum internalformat, + GLint x, + GLint y, + GLsizei width, + GLsizei height, + GLint border), + (target, level, internalformat, x, y, width, height, border)) +VISIT_GL_CALL(CopyTexSubImage2D, + void, + (GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLint x, + GLint y, + GLsizei width, + GLsizei height), + (target, level, xoffset, yoffset, x, y, width, height)) +VISIT_GL_CALL(CreateProgram, GLuint, (), ()) +VISIT_GL_CALL(CreateShader, GLuint, (GLenum type), (type)) +VISIT_GL_CALL(CullFace, void, (GLenum mode), (mode)) +VISIT_GL_CALL(DeleteBuffers, + void, + (GLsizei n, const GLuint* buffers), + (n, buffers)) +VISIT_GL_CALL(DeleteFramebuffers, + void, + (GLsizei n, const GLuint* framebuffers), + (n, framebuffers)) +VISIT_GL_CALL(DeleteProgram, void, (GLuint program), (program)) +VISIT_GL_CALL(DeleteRenderbuffers, + void, + (GLsizei n, const GLuint* renderbuffers), + (n, renderbuffers)) +VISIT_GL_CALL(DeleteShader, void, (GLuint shader), (shader)) +VISIT_GL_CALL(DeleteTextures, + void, + (GLsizei n, const GLuint* textures), + (n, textures)) +VISIT_GL_CALL(DepthFunc, void, (GLenum func), (func)) +VISIT_GL_CALL(DepthMask, void, (GLboolean flag), (flag)) +VISIT_GL_CALL(DepthRangef, void, (GLclampf zNear, GLclampf zFar), (zNear, zFar)) +VISIT_GL_CALL(DetachShader, + void, + (GLuint program, GLuint shader), + (program, shader)) +VISIT_GL_CALL(Disable, void, (GLenum cap), (cap)) +VISIT_GL_CALL(DisableVertexAttribArray, void, (GLuint index), (index)) +VISIT_GL_CALL(DrawArrays, + void, + (GLenum mode, GLint first, GLsizei count), + (mode, first, count)) +VISIT_GL_CALL(DrawElements, + void, + (GLenum mode, GLsizei count, GLenum type, const void* indices), + (mode, count, type, indices)) +VISIT_GL_CALL(Enable, void, (GLenum cap), (cap)) +VISIT_GL_CALL(EnableVertexAttribArray, void, (GLuint index), (index)) +VISIT_GL_CALL(Finish, void, (), ()) +VISIT_GL_CALL(Flush, void, (), ()) +VISIT_GL_CALL(FramebufferRenderbuffer, + void, + (GLenum target, + GLenum attachment, + GLenum renderbuffertarget, + GLuint renderbuffer), + (target, attachment, renderbuffertarget, renderbuffer)) +VISIT_GL_CALL(FramebufferTexture2D, + void, + (GLenum target, + GLenum attachment, + GLenum textarget, + GLuint texture, + GLint level), + (target, attachment, textarget, texture, level)) +VISIT_GL_CALL(FrontFace, void, (GLenum mode), (mode)) +VISIT_GL_CALL(GenBuffers, void, (GLsizei n, GLuint * buffers), (n, buffers)) +VISIT_GL_CALL(GenerateMipmap, void, (GLenum target), (target)) +VISIT_GL_CALL(GenFramebuffers, + void, + (GLsizei n, GLuint * framebuffers), + (n, framebuffers)) +VISIT_GL_CALL(GenRenderbuffers, + void, + (GLsizei n, GLuint * renderbuffers), + (n, renderbuffers)) +VISIT_GL_CALL(GenTextures, void, (GLsizei n, GLuint * textures), (n, textures)) +VISIT_GL_CALL(GetActiveAttrib, + void, + (GLuint program, + GLuint index, + GLsizei bufsize, + GLsizei * length, + GLint * size, + GLenum * type, + char* name), + (program, index, bufsize, length, size, type, name)) +VISIT_GL_CALL(GetActiveUniform, + void, + (GLuint program, + GLuint index, + GLsizei bufsize, + GLsizei * length, + GLint * size, + GLenum * type, + char* name), + (program, index, bufsize, length, size, type, name)) +VISIT_GL_CALL( + GetAttachedShaders, + void, + (GLuint program, GLsizei maxcount, GLsizei * count, GLuint * shaders), + (program, maxcount, count, shaders)) +VISIT_GL_CALL(GetAttribLocation, + GLint, + (GLuint program, const char* name), + (program, name)) +VISIT_GL_CALL(GetBooleanv, + void, + (GLenum pname, GLboolean * params), + (pname, params)) +VISIT_GL_CALL(GetBufferParameteriv, + void, + (GLenum target, GLenum pname, GLint * params), + (target, pname, params)) +VISIT_GL_CALL(GetError, GLenum, (), ()) +VISIT_GL_CALL(GetFloatv, + void, + (GLenum pname, GLfloat * params), + (pname, params)) +VISIT_GL_CALL(GetFramebufferAttachmentParameteriv, + void, + (GLenum target, GLenum attachment, GLenum pname, GLint * params), + (target, attachment, pname, params)) +VISIT_GL_CALL(GetIntegerv, + void, + (GLenum pname, GLint * params), + (pname, params)) +VISIT_GL_CALL(GetProgramiv, + void, + (GLuint program, GLenum pname, GLint * params), + (program, pname, params)) +VISIT_GL_CALL( + GetProgramInfoLog, + void, + (GLuint program, GLsizei bufsize, GLsizei * length, char* infolog), + (program, bufsize, length, infolog)) +VISIT_GL_CALL(GetRenderbufferParameteriv, + void, + (GLenum target, GLenum pname, GLint * params), + (target, pname, params)) +VISIT_GL_CALL(GetShaderiv, + void, + (GLuint shader, GLenum pname, GLint * params), + (shader, pname, params)) +VISIT_GL_CALL(GetShaderInfoLog, + void, + (GLuint shader, GLsizei bufsize, GLsizei * length, char* infolog), + (shader, bufsize, length, infolog)) +VISIT_GL_CALL( + GetShaderPrecisionFormat, + void, + (GLenum shadertype, GLenum precisiontype, GLint * range, GLint * precision), + (shadertype, precisiontype, range, precision)) +VISIT_GL_CALL(GetShaderSource, + void, + (GLuint shader, GLsizei bufsize, GLsizei * length, char* source), + (shader, bufsize, length, source)) +VISIT_GL_CALL(GetString, const GLubyte*, (GLenum name), (name)) +VISIT_GL_CALL(GetTexParameterfv, + void, + (GLenum target, GLenum pname, GLfloat * params), + (target, pname, params)) +VISIT_GL_CALL(GetTexParameteriv, + void, + (GLenum target, GLenum pname, GLint * params), + (target, pname, params)) +VISIT_GL_CALL(GetUniformfv, + void, + (GLuint program, GLint location, GLfloat * params), + (program, location, params)) +VISIT_GL_CALL(GetUniformiv, + void, + (GLuint program, GLint location, GLint * params), + (program, location, params)) +VISIT_GL_CALL(GetUniformLocation, + GLint, + (GLuint program, const char* name), + (program, name)) +VISIT_GL_CALL(GetVertexAttribfv, + void, + (GLuint index, GLenum pname, GLfloat * params), + (index, pname, params)) +VISIT_GL_CALL(GetVertexAttribiv, + void, + (GLuint index, GLenum pname, GLint * params), + (index, pname, params)) +VISIT_GL_CALL(GetVertexAttribPointerv, + void, + (GLuint index, GLenum pname, void** pointer), + (index, pname, pointer)) +VISIT_GL_CALL(Hint, void, (GLenum target, GLenum mode), (target, mode)) +VISIT_GL_CALL(IsBuffer, GLboolean, (GLuint buffer), (buffer)) +VISIT_GL_CALL(IsEnabled, GLboolean, (GLenum cap), (cap)) +VISIT_GL_CALL(IsFramebuffer, GLboolean, (GLuint framebuffer), (framebuffer)) +VISIT_GL_CALL(IsProgram, GLboolean, (GLuint program), (program)) +VISIT_GL_CALL(IsRenderbuffer, GLboolean, (GLuint renderbuffer), (renderbuffer)) +VISIT_GL_CALL(IsShader, GLboolean, (GLuint shader), (shader)) +VISIT_GL_CALL(IsTexture, GLboolean, (GLuint texture), (texture)) +VISIT_GL_CALL(LineWidth, void, (GLfloat width), (width)) +VISIT_GL_CALL(LinkProgram, void, (GLuint program), (program)) +VISIT_GL_CALL(PixelStorei, void, (GLenum pname, GLint param), (pname, param)) +VISIT_GL_CALL(PolygonOffset, + void, + (GLfloat factor, GLfloat units), + (factor, units)) +VISIT_GL_CALL(ReadPixels, + void, + (GLint x, + GLint y, + GLsizei width, + GLsizei height, + GLenum format, + GLenum type, + void* pixels), + (x, y, width, height, format, type, pixels)) +VISIT_GL_CALL(ReleaseShaderCompiler, void, (), ()) +VISIT_GL_CALL( + RenderbufferStorage, + void, + (GLenum target, GLenum internalformat, GLsizei width, GLsizei height), + (target, internalformat, width, height)) +VISIT_GL_CALL(SampleCoverage, + void, + (GLclampf value, GLboolean invert), + (value, invert)) +VISIT_GL_CALL(Scissor, + void, + (GLint x, GLint y, GLsizei width, GLsizei height), + (x, y, width, height)) +VISIT_GL_CALL(ShaderBinary, + void, + (GLsizei n, + const GLuint* shaders, + GLenum binaryformat, + const void* binary, + GLsizei length), + (n, shaders, binaryformat, binary, length)) +VISIT_GL_CALL(ShaderSource, + void, + (GLuint shader, + GLsizei count, + const GLchar* const* str, + const GLint* length), + (shader, count, str, length)) +VISIT_GL_CALL(StencilFunc, + void, + (GLenum func, GLint ref, GLuint mask), + (func, ref, mask)) +VISIT_GL_CALL(StencilFuncSeparate, + void, + (GLenum face, GLenum func, GLint ref, GLuint mask), + (face, func, ref, mask)) +VISIT_GL_CALL(StencilMask, void, (GLuint mask), (mask)) +VISIT_GL_CALL(StencilMaskSeparate, + void, + (GLenum face, GLuint mask), + (face, mask)) +VISIT_GL_CALL(StencilOp, + void, + (GLenum fail, GLenum zfail, GLenum zpass), + (fail, zfail, zpass)) +VISIT_GL_CALL(StencilOpSeparate, + void, + (GLenum face, GLenum fail, GLenum zfail, GLenum zpass), + (face, fail, zfail, zpass)) +VISIT_GL_CALL(TexImage2D, + void, + (GLenum target, + GLint level, + GLint internalformat, + GLsizei width, + GLsizei height, + GLint border, + GLenum format, + GLenum type, + const void* pixels), + (target, + level, + internalformat, + width, + height, + border, + format, + type, + pixels)) +VISIT_GL_CALL(TexParameterf, + void, + (GLenum target, GLenum pname, GLfloat param), + (target, pname, param)) +VISIT_GL_CALL(TexParameterfv, + void, + (GLenum target, GLenum pname, const GLfloat* params), + (target, pname, params)) +VISIT_GL_CALL(TexParameteri, + void, + (GLenum target, GLenum pname, GLint param), + (target, pname, param)) +VISIT_GL_CALL(TexParameteriv, + void, + (GLenum target, GLenum pname, const GLint* params), + (target, pname, params)) +VISIT_GL_CALL( + TexSubImage2D, + void, + (GLenum target, + GLint level, + GLint xoffset, + GLint yoffset, + GLsizei width, + GLsizei height, + GLenum format, + GLenum type, + const void* pixels), + (target, level, xoffset, yoffset, width, height, format, type, pixels)) +VISIT_GL_CALL(Uniform1f, void, (GLint location, GLfloat x), (location, x)) +VISIT_GL_CALL(Uniform1fv, + void, + (GLint location, GLsizei count, const GLfloat* v), + (location, count, v)) +VISIT_GL_CALL(Uniform1i, void, (GLint location, GLint x), (location, x)) +VISIT_GL_CALL(Uniform1iv, + void, + (GLint location, GLsizei count, const GLint* v), + (location, count, v)) +VISIT_GL_CALL(Uniform2f, + void, + (GLint location, GLfloat x, GLfloat y), + (location, x, y)) +VISIT_GL_CALL(Uniform2fv, + void, + (GLint location, GLsizei count, const GLfloat* v), + (location, count, v)) +VISIT_GL_CALL(Uniform2i, + void, + (GLint location, GLint x, GLint y), + (location, x, y)) +VISIT_GL_CALL(Uniform2iv, + void, + (GLint location, GLsizei count, const GLint* v), + (location, count, v)) +VISIT_GL_CALL(Uniform3f, + void, + (GLint location, GLfloat x, GLfloat y, GLfloat z), + (location, x, y, z)) +VISIT_GL_CALL(Uniform3fv, + void, + (GLint location, GLsizei count, const GLfloat* v), + (location, count, v)) +VISIT_GL_CALL(Uniform3i, + void, + (GLint location, GLint x, GLint y, GLint z), + (location, x, y, z)) +VISIT_GL_CALL(Uniform3iv, + void, + (GLint location, GLsizei count, const GLint* v), + (location, count, v)) +VISIT_GL_CALL(Uniform4f, + void, + (GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w), + (location, x, y, z, w)) +VISIT_GL_CALL(Uniform4fv, + void, + (GLint location, GLsizei count, const GLfloat* v), + (location, count, v)) +VISIT_GL_CALL(Uniform4i, + void, + (GLint location, GLint x, GLint y, GLint z, GLint w), + (location, x, y, z, w)) +VISIT_GL_CALL(Uniform4iv, + void, + (GLint location, GLsizei count, const GLint* v), + (location, count, v)) +VISIT_GL_CALL( + UniformMatrix2fv, + void, + (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value), + (location, count, transpose, value)) +VISIT_GL_CALL( + UniformMatrix3fv, + void, + (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value), + (location, count, transpose, value)) +VISIT_GL_CALL( + UniformMatrix4fv, + void, + (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value), + (location, count, transpose, value)) +VISIT_GL_CALL(UseProgram, void, (GLuint program), (program)) +VISIT_GL_CALL(ValidateProgram, void, (GLuint program), (program)) +VISIT_GL_CALL(VertexAttrib1f, void, (GLuint indx, GLfloat x), (indx, x)) +VISIT_GL_CALL(VertexAttrib1fv, + void, + (GLuint indx, const GLfloat* values), + (indx, values)) +VISIT_GL_CALL(VertexAttrib2f, + void, + (GLuint indx, GLfloat x, GLfloat y), + (indx, x, y)) +VISIT_GL_CALL(VertexAttrib2fv, + void, + (GLuint indx, const GLfloat* values), + (indx, values)) +VISIT_GL_CALL(VertexAttrib3f, + void, + (GLuint indx, GLfloat x, GLfloat y, GLfloat z), + (indx, x, y, z)) +VISIT_GL_CALL(VertexAttrib3fv, + void, + (GLuint indx, const GLfloat* values), + (indx, values)) +VISIT_GL_CALL(VertexAttrib4f, + void, + (GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w), + (indx, x, y, z, w)) +VISIT_GL_CALL(VertexAttrib4fv, + void, + (GLuint indx, const GLfloat* values), + (indx, values)) +VISIT_GL_CALL(VertexAttribPointer, + void, + (GLuint indx, + GLint size, + GLenum type, + GLboolean normalized, + GLsizei stride, + const void* ptr), + (indx, size, type, normalized, stride, ptr)) +VISIT_GL_CALL(Viewport, + void, + (GLint x, GLint y, GLsizei width, GLsizei height), + (x, y, width, height)) diff --git a/chromium/mojo/public/c/gles2/gles2_export.h b/chromium/mojo/public/c/gles2/gles2_export.h new file mode 100644 index 00000000000..4f8796da069 --- /dev/null +++ b/chromium/mojo/public/c/gles2/gles2_export.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_GLES2_GLES2_EXPORT_H_ +#define MOJO_PUBLIC_C_GLES2_GLES2_EXPORT_H_ + +#if defined(WIN32) + +#if defined(MOJO_GLES2_IMPLEMENTATION) +#define MOJO_GLES2_EXPORT __declspec(dllexport) +#else +#define MOJO_GLES2_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_GLES2_IMPLEMENTATION) +#define MOJO_GLES2_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_GLES2_EXPORT +#endif + +#endif // defined(WIN32) + +#endif // MOJO_PUBLIC_C_GLES2_GLES2_EXPORT_H_ diff --git a/chromium/mojo/public/c/gles2/gles2_types.h b/chromium/mojo/public/c/gles2/gles2_types.h new file mode 100644 index 00000000000..dd2a21314cc --- /dev/null +++ b/chromium/mojo/public/c/gles2/gles2_types.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_GLES2_GLES2_TYPES_H_ +#define MOJO_PUBLIC_C_GLES2_GLES2_TYPES_H_ + +// Note: This header should be compilable as C. + +#include <stdint.h> + +#include "mojo/public/c/gles2/gles2_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MojoGLES2ContextPrivate* MojoGLES2Context; +typedef void (*MojoGLES2ContextLost)(void* closure); +typedef void (*MojoGLES2DrawAnimationFrame)(void* closure); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_GLES2_GLES2_TYPES_H_ diff --git a/chromium/mojo/public/c/system/buffer.h b/chromium/mojo/public/c/system/buffer.h new file mode 100644 index 00000000000..86cf8223ee0 --- /dev/null +++ b/chromium/mojo/public/c/system/buffer.h @@ -0,0 +1,187 @@ +// 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. + +// This file contains types/constants and functions specific to buffers (and in +// particular shared buffers). +// TODO(vtl): Reorganize this file (etc.) to separate general buffer functions +// from (shared) buffer creation. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ +#define MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateSharedBufferOptions|: Used to specify creation parameters for a +// shared buffer to |MojoCreateSharedBuffer()|. +// |uint32_t struct_size|: Set to the size of the +// |MojoCreateSharedBufferOptions| struct. (Used to allow for future +// extensions.) +// |MojoCreateSharedBufferOptionsFlags flags|: Reserved for future use. +// |MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE|: No flags; default mode. +// +// TODO(vtl): Maybe add a flag to indicate whether the memory should be +// executable or not? +// TODO(vtl): Also a flag for discardable (ashmem-style) buffers. + +typedef uint32_t MojoCreateSharedBufferOptionsFlags; + +#ifdef __cplusplus +const MojoCreateSharedBufferOptionsFlags + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE \ + ((MojoCreateSharedBufferOptionsFlags) 0) +#endif + +MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int64_t) == 8, int64_t_has_weird_alignment); +struct MOJO_ALIGNAS(8) MojoCreateSharedBufferOptions { + uint32_t struct_size; + MojoCreateSharedBufferOptionsFlags flags; +}; +MOJO_COMPILE_ASSERT(sizeof(MojoCreateSharedBufferOptions) == 8, + MojoCreateSharedBufferOptions_has_wrong_size); + +// |MojoDuplicateBufferHandleOptions|: Used to specify parameters in duplicating +// access to a shared buffer to |MojoDuplicateBufferHandle()|. +// |uint32_t struct_size|: Set to the size of the +// |MojoDuplicateBufferHandleOptions| struct. (Used to allow for future +// extensions.) +// |MojoDuplicateBufferHandleOptionsFlags flags|: Reserved for future use. +// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default +// mode. +// +// TODO(vtl): Add flags to remove writability (and executability)? Also, COW? + +typedef uint32_t MojoDuplicateBufferHandleOptionsFlags; + +#ifdef __cplusplus +const MojoDuplicateBufferHandleOptionsFlags + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE \ + ((MojoDuplicateBufferHandleOptionsFlags) 0) +#endif + +struct MojoDuplicateBufferHandleOptions { + uint32_t struct_size; + MojoDuplicateBufferHandleOptionsFlags flags; +}; +MOJO_COMPILE_ASSERT(sizeof(MojoDuplicateBufferHandleOptions) == 8, + MojoDuplicateBufferHandleOptions_has_wrong_size); + +// |MojoMapBufferFlags|: Used to specify different modes to |MojoMapBuffer()|. +// |MOJO_MAP_BUFFER_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoMapBufferFlags; + +#ifdef __cplusplus +const MojoMapBufferFlags MOJO_MAP_BUFFER_FLAG_NONE = 0; +#else +#define MOJO_MAP_BUFFER_FLAG_NONE ((MojoMapBufferFlags) 0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a buffer of size |num_bytes| bytes that can be shared between +// applications (by duplicating the handle -- see |MojoDuplicateBufferHandle()| +// -- and passing it over a message pipe). To access the buffer, one must call +// |MojoMapBuffer()|. +// +// |options| may be set to null for a shared buffer with the default options. +// +// On success, |*shared_buffer_handle| will be set to the handle for the shared +// buffer. (On failure, it is not modified.) +// +// Note: While more than |num_bytes| bytes may apparently be +// available/visible/readable/writable, trying to use those extra bytes is +// undefined behavior. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |*options| is invalid or |shared_buffer_handle| looks invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached (e.g., if the requested size was too large, or if the +// maximum number of handles was exceeded). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, // Optional. + uint64_t num_bytes, // In. + MojoHandle* shared_buffer_handle); // Out. + +// Duplicates the handle |buffer_handle| to a buffer. This creates another +// handle (returned in |*new_buffer_handle| on success), which can then be sent +// to another application over a message pipe, while retaining access to the +// |buffer_handle| (and any mappings that it may have). +// +// |options| may be set to null to duplicate the buffer handle with the default +// options. +// +// On success, |*shared_buffer_handle| will be set to the handle for the new +// buffer handle. (On failure, it is not modified.) +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |buffer_handle| is not a valid buffer handle, |*options| is invalid, or +// |new_buffer_handle| looks invalid). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, // Optional. + MojoHandle* new_buffer_handle); // Out. + +// Maps the part (at offset |offset| of length |num_bytes|) of the buffer given +// by |buffer_handle| into memory, with options specified by |flags|. |offset + +// num_bytes| must be less than or equal to the size of the buffer. On success, +// |*buffer| points to memory with the requested part of the buffer. (On +// failure, it is not modified.) +// +// A single buffer handle may have multiple active mappings (possibly depending +// on the buffer type). The permissions (e.g., writable or executable) of the +// returned memory may depend on the properties of the buffer and properties +// attached to the buffer handle as well as |flags|. +// +// Note: Though data outside the specified range may apparently be +// available/visible/readable/writable, trying to use those extra bytes is +// undefined behavior. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |buffer_handle| is not a valid buffer handle, the range specified by +// |offset| and |num_bytes| is not valid, or |buffer| looks invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the mapping operation itself failed +// (e.g., due to not having appropriate address space available). +MOJO_SYSTEM_EXPORT MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, // Out. + MojoMapBufferFlags flags); + +// Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must +// have been the result of |MojoMapBuffer()| (not some pointer strictly inside +// the mapped memory), and the entire mapping will be removed (partial unmapping +// is not supported). A mapping may only be unmapped exactly once. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |buffer| is invalid (e.g., if it is not +// the result of |MojoMapBuffer()| or it has already been unmapped). +MOJO_SYSTEM_EXPORT MojoResult MojoUnmapBuffer(void* buffer); // In. + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ diff --git a/chromium/mojo/public/c/system/core.h b/chromium/mojo/public/c/system/core.h new file mode 100644 index 00000000000..5ea03fc5a5e --- /dev/null +++ b/chromium/mojo/public/c/system/core.h @@ -0,0 +1,20 @@ +// 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. + +// This is a catch-all header that includes everything. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_C_SYSTEM_CORE_H_ + +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#endif // MOJO_PUBLIC_C_SYSTEM_CORE_H_ diff --git a/chromium/mojo/public/c/system/data_pipe.h b/chromium/mojo/public/c/system/data_pipe.h new file mode 100644 index 00000000000..e28cf6eff11 --- /dev/null +++ b/chromium/mojo/public/c/system/data_pipe.h @@ -0,0 +1,367 @@ +// 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. + +// This file contains types/constants and functions specific to data pipes. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ +#define MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateDataPipeOptions|: Used to specify creation parameters for a data +// pipe to |MojoCreateDataPipe()|. +// |uint32_t struct_size|: Set to the size of the |MojoCreateDataPipeOptions| +// struct. (Used to allow for future extensions.) +// |MojoCreateDataPipeOptionsFlags flags|: Used to specify different modes of +// operation. +// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. +// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD|: May discard data for +// whatever reason; best-effort delivery. In particular, if the capacity +// is reached, old data may be discard to make room for new data. +// |uint32_t element_num_bytes|: The size of an element, in bytes. All +// transactions and buffers will consist of an integral number of +// elements. Must be nonzero. +// |uint32_t capacity_num_bytes|: The capacity of the data pipe, in number of +// bytes; must be a multiple of |element_num_bytes|. The data pipe will +// always be able to queue AT LEAST this much data. Set to zero to opt for +// a system-dependent automatically-calculated capacity (which will always +// be at least one element). + +typedef uint32_t MojoCreateDataPipeOptionsFlags; + +#ifdef __cplusplus +const MojoCreateDataPipeOptionsFlags + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE = 0; +const MojoCreateDataPipeOptionsFlags + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD = 1 << 0; +#else +#define MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE \ + ((MojoCreateDataPipeOptionsFlags) 0) +#define MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD \ + ((MojoCreateDataPipeOptionsFlags) 1 << 0) +#endif + +MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int64_t) == 8, int64_t_has_weird_alignment); +struct MOJO_ALIGNAS(8) MojoCreateDataPipeOptions { + uint32_t struct_size; + MojoCreateDataPipeOptionsFlags flags; + uint32_t element_num_bytes; + uint32_t capacity_num_bytes; +}; +MOJO_COMPILE_ASSERT(sizeof(MojoCreateDataPipeOptions) == 16, + MojoCreateDataPipeOptions_has_wrong_size); + +// |MojoWriteDataFlags|: Used to specify different modes to |MojoWriteData()| +// and |MojoBeginWriteData()|. +// |MOJO_WRITE_DATA_FLAG_NONE| - No flags; default mode. +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| - Write either all the elements +// requested or none of them. + +typedef uint32_t MojoWriteDataFlags; + +#ifdef __cplusplus +const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_NONE = 0; +const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_ALL_OR_NONE = 1 << 0; +#else +#define MOJO_WRITE_DATA_FLAG_NONE ((MojoWriteDataFlags) 0) +#define MOJO_WRITE_DATA_FLAG_ALL_OR_NONE ((MojoWriteDataFlags) 1 << 0) +#endif + +// |MojoReadDataFlags|: Used to specify different modes to |MojoReadData()| and +// |MojoBeginReadData()|. +// |MOJO_READ_DATA_FLAG_NONE| - No flags; default mode. +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| - Read (or discard) either the requested +// number of elements or none. +// |MOJO_READ_DATA_FLAG_DISCARD| - Discard (up to) the requested number of +// elements. +// |MOJO_READ_DATA_FLAG_QUERY| - Query the number of elements available to +// read. For use with |MojoReadData()| only. Mutually exclusive with +// |MOJO_READ_DATA_FLAG_DISCARD| and |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is +// ignored if this flag is set. + +typedef uint32_t MojoReadDataFlags; + +#ifdef __cplusplus +const MojoReadDataFlags MOJO_READ_DATA_FLAG_NONE = 0; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_ALL_OR_NONE = 1 << 0; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_QUERY = 1 << 2; +#else +#define MOJO_READ_DATA_FLAG_NONE ((MojoReadDataFlags) 0) +#define MOJO_READ_DATA_FLAG_ALL_OR_NONE ((MojoReadDataFlags) 1 << 0) +#define MOJO_READ_DATA_FLAG_DISCARD ((MojoReadDataFlags) 1 << 1) +#define MOJO_READ_DATA_FLAG_QUERY ((MojoReadDataFlags) 1 << 2) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a data pipe, which is a unidirectional communication channel for +// unframed data, with the given options. Data is unframed, but must come as +// (multiples of) discrete elements, of the size given in |options|. See +// |MojoCreateDataPipeOptions| for a description of the different options +// available for data pipes. +// +// |options| may be set to null for a data pipe with the default options (which +// will have an element size of one byte and have some system-dependent +// capacity). +// +// On success, |*data_pipe_producer_handle| will be set to the handle for the +// producer and |*data_pipe_consumer_handle| will be set to the handle for the +// consumer. (On failure, they are not modified.) +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |*options| is invalid or one of the handle pointers looks invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached (e.g., if the requested capacity was too large, or if the +// maximum number of handles was exceeded). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe( + const struct MojoCreateDataPipeOptions* options, // Optional. + MojoHandle* data_pipe_producer_handle, // Out. + MojoHandle* data_pipe_consumer_handle); // Out. + +// Writes the given data to the data pipe producer given by +// |data_pipe_producer_handle|. |elements| points to data of size |*num_bytes|; +// |*num_bytes| should be a multiple of the data pipe's element size. If +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data +// will be written or none is. +// +// On success, |*num_bytes| is set to the amount of data that was actually +// written. +// +// Note: If the data pipe has the "may discard" option flag (specified on +// creation), this will discard as much data as required to write the given +// data, starting with the earliest written data that has not been consumed. +// However, even with "may discard", if |*num_bytes| is greater than the data +// pipe's capacity (and |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is not set), this +// will write the maximum amount possible (namely, the data pipe's capacity) and +// set |*num_bytes| to that amount. It will *not* discard data from |elements|. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |data_pipe_producer_dispatcher| is not a handle to a data pipe +// producer, |elements| does not look like a valid pointer, or +// |*num_bytes| is not a multiple of the data pipe's element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been +// closed. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data +// (specified by |*num_bytes|) could not be written. +// |MOJO_RESULT_BUSY| if there is a two-phase write ongoing with +// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been +// called, but not yet the matching |MojoEndWriteData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the +// consumer is still open) and |flags| does *not* have +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. +// +// TODO(vtl): Should there be a way of querying how much data can be written? +MOJO_SYSTEM_EXPORT MojoResult MojoWriteData( + MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, // In/out. + MojoWriteDataFlags flags); + +// Begins a two-phase write to the data pipe producer given by +// |data_pipe_producer_handle|. On success, |*buffer| will be a pointer to which +// the caller can write |*buffer_num_bytes| bytes of data. If flags has +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set, then the output value +// |*buffer_num_bytes| will be at least as large as its input value, which must +// also be a multiple of the element size (if |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| +// is not set, the input value of |*buffer_num_bytes| is ignored). +// +// During a two-phase write, |data_pipe_producer_handle| is *not* writable. +// E.g., if another thread tries to write to it, it will get |MOJO_RESULT_BUSY|; +// that thread can then wait for |data_pipe_producer_handle| to become writable +// again. +// +// Once the caller has finished writing data to |*buffer|, it should call +// |MojoEndWriteData()| to specify the amount written and to complete the +// two-phase write. +// +// Note: If the data pipe has the "may discard" option flag (specified on +// creation) and |flags| has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set, this may +// discard some data. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |data_pipe_producer_handle| is not a handle to a data pipe producer, +// |buffer| or |buffer_num_bytes| does not look like a valid pointer, or +// flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and +// |*buffer_num_bytes| is not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been +// closed. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data +// (specified by |*buffer_num_bytes|) cannot be written contiguously at +// this time. (Note that there may be space available for the required +// amount of data, but the "next" write position may not be large enough.) +// |MOJO_RESULT_BUSY| if there is already a two-phase write ongoing with +// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been +// called, but not yet the matching |MojoEndWriteData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the +// consumer is still open). +MOJO_SYSTEM_EXPORT MojoResult MojoBeginWriteData( + MojoHandle data_pipe_producer_handle, + void** buffer, // Out. + uint32_t* buffer_num_bytes, // In/out. + MojoWriteDataFlags flags); + +// Ends a two-phase write to the data pipe producer given by +// |data_pipe_producer_handle| that was begun by a call to +// |MojoBeginWriteData()| on the same handle. |num_bytes_written| should +// indicate the amount of data actually written; it must be less than or equal +// to the value of |*buffer_num_bytes| output by |MojoBeginWriteData()| and must +// be a multiple of the element size. The buffer given by |*buffer| from +// |MojoBeginWriteData()| must have been filled with exactly |num_bytes_written| +// bytes of data. +// +// On failure, the two-phase write (if any) is ended (so the handle may become +// writable again, if there's space available) but no data written to |*buffer| +// is "put into" the data pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |data_pipe_producer_handle| is not a +// handle to a data pipe producer or |num_bytes_written| is invalid +// (greater than the maximum value provided by |MojoBeginWriteData()| or +// not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer is not in a +// two-phase write (e.g., |MojoBeginWriteData()| was not called or +// |MojoEndWriteData()| has already been called). +MOJO_SYSTEM_EXPORT MojoResult MojoEndWriteData( + MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written); + +// Reads data from the data pipe consumer given by |data_pipe_consumer_handle|. +// May also be used to discard data or query the amount of data available. +// +// If |flags| has neither |MOJO_READ_DATA_FLAG_DISCARD| nor +// |MOJO_READ_DATA_FLAG_QUERY| set, this tries to read up to |*num_bytes| (which +// must be a multiple of the data pipe's element size) bytes of data to +// |elements| and set |*num_bytes| to the amount actually read. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, it will either read exactly +// |*num_bytes| bytes of data or none. +// +// If flags has |MOJO_READ_DATA_FLAG_DISCARD| set, it discards up to +// |*num_bytes| (which again be a multiple of the element size) bytes of data, +// setting |*num_bytes| to the amount actually discarded. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE|, it will either discard exactly +// |*num_bytes| bytes of data or none. In this case, |MOJO_READ_DATA_FLAG_QUERY| +// must not be set, and |elements| is ignored (and should typically be set to +// null). +// +// If flags has |MOJO_READ_DATA_FLAG_QUERY| set, it queries the amount of data +// available, setting |*num_bytes| to the number of bytes available. In this +// case, |MOJO_READ_DATA_FLAG_DISCARD| must not be set, and +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is ignored, as are |elements| and the input +// value of |*num_bytes|. +// +// Returns: +// |MOJO_RESULT_OK| on success (see above for a description of the different +// operations). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is invalid, the combination of flags in +// |flags| is invalid, etc.). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been +// closed and data (or the required amount of data) was not available to +// be read or discarded. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// set and the required amount of data is not available to be read or +// discarded (and the producer is still open). +// |MOJO_RESULT_BUSY| if there is a two-phase read ongoing with +// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been +// called, but not yet the matching |MojoEndReadData()|). +// |MOJO_RESULT_SHOULD_WAIT| if there is no data to be read or discarded (and +// the producer is still open) and |flags| does *not* have +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set. +MOJO_SYSTEM_EXPORT MojoResult MojoReadData( + MojoHandle data_pipe_consumer_handle, + void* elements, // Out. + uint32_t* num_bytes, // In/out. + MojoReadDataFlags flags); + +// Begins a two-phase read from the data pipe consumer given by +// |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from +// which the caller can read |*buffer_num_bytes| bytes of data. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, then the output value +// |*buffer_num_bytes| will be at least as large as its input value, which must +// also be a multiple of the element size (if |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// is not set, the input value of |*buffer_num_bytes| is ignored). |flags| must +// not have |MOJO_READ_DATA_FLAG_DISCARD| or |MOJO_READ_DATA_FLAG_QUERY| set. +// +// During a two-phase read, |data_pipe_consumer_handle| is *not* readable. +// E.g., if another thread tries to read from it, it will get +// |MOJO_RESULT_BUSY|; that thread can then wait for |data_pipe_consumer_handle| +// to become readable again. +// +// Once the caller has finished reading data from |*buffer|, it should call +// |MojoEndReadData()| to specify the amount read and to complete the two-phase +// read. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |data_pipe_consumer_handle| is not a handle to a data pipe consumer, +// |buffer| or |buffer_num_bytes| does not look like a valid pointer, +// |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set and +// |*buffer_num_bytes| is not a multiple of the element size, or |flags| +// has invalid flags set). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been +// closed. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// set and the required amount of data (specified by |*buffer_num_bytes|) +// cannot be read from a contiguous buffer at this time. (Note that there +// may be the required amount of data, but it may not be contiguous.) +// |MOJO_RESULT_BUSY| if there is already a two-phase read ongoing with +// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been +// called, but not yet the matching |MojoEndReadData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be read (and the +// producer is still open). +MOJO_SYSTEM_EXPORT MojoResult MojoBeginReadData( + MojoHandle data_pipe_consumer_handle, + const void** buffer, // Out. + uint32_t* buffer_num_bytes, // In/out. + MojoReadDataFlags flags); + +// Ends a two-phase read from the data pipe consumer given by +// |data_pipe_consumer_handle| that was begun by a call to |MojoBeginReadData()| +// on the same handle. |num_bytes_read| should indicate the amount of data +// actually read; it must be less than or equal to the value of +// |*buffer_num_bytes| output by |MojoBeginReadData()| and must be a multiple of +// the element size. +// +// On failure, the two-phase read (if any) is ended (so the handle may become +// readable again) but no data is "removed" from the data pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |data_pipe_consumer_handle| is not a +// handle to a data pipe consumer or |num_bytes_written| is invalid +// (greater than the maximum value provided by |MojoBeginReadData()| or +// not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer is not in a +// two-phase read (e.g., |MojoBeginReadData()| was not called or +// |MojoEndReadData()| has already been called). +MOJO_SYSTEM_EXPORT MojoResult MojoEndReadData( + MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ diff --git a/chromium/mojo/public/c/system/functions.h b/chromium/mojo/public/c/system/functions.h new file mode 100644 index 00000000000..9d2ef6ca800 --- /dev/null +++ b/chromium/mojo/public/c/system/functions.h @@ -0,0 +1,87 @@ +// 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. + +// This file contains basic functions common to different Mojo system APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ +#define MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ + +// Note: This header should be compilable as C. + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: Pointer parameters that are labelled "optional" may be null (at least +// under some circumstances). Non-const pointer parameters are also labeled +// "in", "out", or "in/out", to indicate how they are used. (Note that how/if +// such a parameter is used may depend on other parameters or the requested +// operation's success/failure. E.g., a separate |flags| parameter may control +// whether a given "in/out" parameter is used for input, output, or both.) + +// Platform-dependent monotonically increasing tick count representing "right +// now." The resolution of this clock is ~1-15ms. Resolution varies depending +// on hardware/operating system configuration. +MOJO_SYSTEM_EXPORT MojoTimeTicks MojoGetTimeTicksNow(void); + +// Closes the given |handle|. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. +// +// Concurrent operations on |handle| may succeed (or fail as usual) if they +// happen before the close, be cancelled with result |MOJO_RESULT_CANCELLED| if +// they properly overlap (this is likely the case with |MojoWait()|, etc.), or +// fail with |MOJO_RESULT_INVALID_ARGUMENT| if they happen after. +MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle); + +// Waits on the given handle until a signal indicated by |signals| is satisfied +// or until |deadline| has passed. +// +// Returns: +// |MOJO_RESULT_OK| if some signal in |signals| was satisfied (or is already +// satisfied). +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle (e.g., if +// it has already been closed). +// |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline has passed without any of +// the signals being satisfied. +// |MOJO_RESULT_FAILED_PRECONDITION| if it is or becomes impossible that any +// signal in |signals| will ever be satisfied. +// +// If there are multiple waiters (on different threads, obviously) waiting on +// the same handle and signal, and that signal becomes is satisfied, all waiters +// will be awoken. +MOJO_SYSTEM_EXPORT MojoResult MojoWait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline); + +// Waits on |handles[0]|, ..., |handles[num_handles-1]| for at least one of them +// to satisfy a signal indicated by |signals[0]|, ..., |signals[num_handles-1]|, +// respectively, or until |deadline| has passed. +// +// Returns: +// The index |i| (from 0 to |num_handles-1|) if |handle[i]| satisfies a signal +// from |signals[i]|. +// |MOJO_RESULT_INVALID_ARGUMENT| if some |handle[i]| is not a valid handle +// (e.g., if it has already been closed). +// |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline has passed without any of +// handles satisfying any of its signals. +// |MOJO_RESULT_FAILED_PRECONDITION| if it is or becomes impossible that SOME +// |handle[i]| will ever satisfy any of the signals in |signals[i]|. +MOJO_SYSTEM_EXPORT MojoResult MojoWaitMany(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ diff --git a/chromium/mojo/public/c/system/macros.h b/chromium/mojo/public/c/system/macros.h new file mode 100644 index 00000000000..f1e3c7d6831 --- /dev/null +++ b/chromium/mojo/public/c/system/macros.h @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_SYSTEM_MACROS_H_ +#define MOJO_PUBLIC_C_SYSTEM_MACROS_H_ + +#include <stddef.h> + +// Annotate a variable indicating it's okay if it's unused. +// Use like: +// int x MOJO_ALLOW_UNUSED = ...; +#if defined(__GNUC__) +#define MOJO_ALLOW_UNUSED __attribute__((unused)) +#else +#define MOJO_ALLOW_UNUSED +#endif + +// Annotate a function indicating that the caller must examine the return value. +// Use like: +// int foo() MOJO_WARN_UNUSED_RESULT; +// Note that it can only be used on the prototype, and not the definition. +#if defined(__GNUC__) +#define MOJO_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define MOJO_WARN_UNUSED_RESULT +#endif + +// Assert things at compile time. (|msg| should be a valid identifier name.) +// This macro is currently C++-only, but we want to use it in the C core.h. +// Use like: +// MOJO_COMPILE_ASSERT(sizeof(Foo) == 12, Foo_has_invalid_size); +#if __cplusplus >= 201103L +#define MOJO_COMPILE_ASSERT(expr, msg) static_assert(expr, #msg) +#elif defined(__cplusplus) +namespace mojo { template <bool> struct CompileAssert {}; } +#define MOJO_COMPILE_ASSERT(expr, msg) \ + typedef ::mojo::CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] +#else +#define MOJO_COMPILE_ASSERT(expr, msg) +#endif + +// Like the C++11 |alignof| operator. +#if __cplusplus >= 201103L +#define MOJO_ALIGNOF(type) alignof(type) +#elif defined(__GNUC__) +#define MOJO_ALIGNOF(type) __alignof__(type) +#elif defined(_MSC_VER) +// The use of |sizeof| is to work around a bug in MSVC 2010 (see +// http://goo.gl/isH0C; supposedly fixed since then). +#define MOJO_ALIGNOF(type) (sizeof(type) - sizeof(type) + __alignof(type)) +#else +#error "Please define MOJO_ALIGNOF() for your compiler." +#endif + +// Specify the alignment of a |struct|, etc. +// Use like: +// struct MOJO_ALIGNAS(8) Foo { ... }; +// Unlike the C++11 |alignas()|, |alignment| must be an integer. It may not be a +// type, nor can it be an expression like |MOJO_ALIGNOF(type)| (due to the +// non-C++11 MSVS version). +#if __cplusplus >= 201103L +#define MOJO_ALIGNAS(alignment) alignas(alignment) +#elif defined(__GNUC__) +#define MOJO_ALIGNAS(alignment) __attribute__((aligned(alignment))) +#elif defined(_MSC_VER) +#define MOJO_ALIGNAS(alignment) __declspec(align(alignment)) +#else +#error "Please define MOJO_ALIGNAS() for your compiler." +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_MACROS_H_ diff --git a/chromium/mojo/public/c/system/message_pipe.h b/chromium/mojo/public/c/system/message_pipe.h new file mode 100644 index 00000000000..0d0b61b7c5b --- /dev/null +++ b/chromium/mojo/public/c/system/message_pipe.h @@ -0,0 +1,178 @@ +// 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. + +// This file contains types/constants and functions specific to message pipes. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateMessagePipeOptions|: Used to specify creation parameters for a +// message pipe to |MojoCreateMessagePipe()|. +// |uint32_t struct_size|: Set to the size of the +// |MojoCreateMessagePipeOptions| struct. (Used to allow for future +// extensions.) +// |MojoCreateMessagePipeOptionsFlags flags|: Reserved for future use. +// |MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. + +typedef uint32_t MojoCreateMessagePipeOptionsFlags; + +#ifdef __cplusplus +const MojoCreateMessagePipeOptionsFlags + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE \ + ((MojoCreateMessagePipeOptionsFlags) 0) +#endif + +MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int64_t) == 8, int64_t_has_weird_alignment); +struct MOJO_ALIGNAS(8) MojoCreateMessagePipeOptions { + uint32_t struct_size; + MojoCreateMessagePipeOptionsFlags flags; +}; +MOJO_COMPILE_ASSERT(sizeof(MojoCreateMessagePipeOptions) == 8, + MojoCreateMessagePipeOptions_has_wrong_size); + +// |MojoWriteMessageFlags|: Used to specify different modes to +// |MojoWriteMessage()|. +// |MOJO_WRITE_MESSAGE_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoWriteMessageFlags; + +#ifdef __cplusplus +const MojoWriteMessageFlags MOJO_WRITE_MESSAGE_FLAG_NONE = 0; +#else +#define MOJO_WRITE_MESSAGE_FLAG_NONE ((MojoWriteMessageFlags) 0) +#endif + +// |MojoReadMessageFlags|: Used to specify different modes to +// |MojoReadMessage()|. +// |MOJO_READ_MESSAGE_FLAG_NONE| - No flags; default mode. +// |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| - If the message is unable to be read +// for whatever reason (e.g., the caller-supplied buffer is too small), +// discard the message (i.e., simply dequeue it). + +typedef uint32_t MojoReadMessageFlags; + +#ifdef __cplusplus +const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_NONE = 0; +const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_MAY_DISCARD = 1 << 0; +#else +#define MOJO_READ_MESSAGE_FLAG_NONE ((MojoReadMessageFlags) 0) +#define MOJO_READ_MESSAGE_FLAG_MAY_DISCARD ((MojoReadMessageFlags) 1 << 0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a message pipe, which is a bidirectional communication channel for +// framed data (i.e., messages). Messages can contain plain data and/or Mojo +// handles. +// +// |options| may be set to null for a message pipe with the default options. +// +// On success, |*message_pipe_handle0| and |*message_pipe_handle1| are set to +// handles for the two endpoints (ports) for the message pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message_pipe_handle0| and/or +// |message_pipe_handle1| do not appear to be valid pointers. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached. +// +// TODO(vtl): Add an options struct pointer argument. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateMessagePipe( + const struct MojoCreateMessagePipeOptions* options, // Optional. + MojoHandle* message_pipe_handle0, // Out. + MojoHandle* message_pipe_handle1); // Out. + +// Writes a message to the message pipe endpoint given by |message_pipe_handle|, +// with message data specified by |bytes| of size |num_bytes| and attached +// handles specified by |handles| of count |num_handles|, and options specified +// by |flags|. If there is no message data, |bytes| may be null, in which case +// |num_bytes| must be zero. If there are no attached handles, |handles| may be +// null, in which case |num_handles| must be zero. +// +// If handles are attached, on success the handles will no longer be valid (the +// receiver will receive equivalent, but logically different, handles). Handles +// to be sent should not be in simultaneous use (e.g., on another thread). +// +// Returns: +// |MOJO_RESULT_OK| on success (i.e., the message was enqueued). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |message_pipe_handle| is not a valid handle, or some of the +// requirements above are not satisfied). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if some system limit has been reached, or +// the number of handles to send is too large (TODO(vtl): reconsider the +// latter case). +// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed. +// Note that closing an endpoint is not necessarily synchronous (e.g., +// across processes), so this function may be succeed even if the other +// endpoint has been closed (in which case the message would be dropped). +// |MOJO_RESULT_BUSY| if some handle to be sent is currently in use. +// +// TODO(vtl): Add a notion of capacity for message pipes, and return +// |MOJO_RESULT_SHOULD_WAIT| if the message pipe is full. +MOJO_SYSTEM_EXPORT MojoResult MojoWriteMessage( + MojoHandle message_pipe_handle, + const void* bytes, // Optional. + uint32_t num_bytes, + const MojoHandle* handles, // Optional. + uint32_t num_handles, + MojoWriteMessageFlags flags); + +// Reads a message from the message pipe endpoint given by +// |message_pipe_handle|; also usable to query the size of the next message or +// discard the next message. |bytes|/|*num_bytes| indicate the buffer/buffer +// size to receive the message data (if any) and |handles|/|*num_handles| +// indicate the buffer/maximum handle count to receive the attached handles (if +// any). +// +// |num_bytes| and |num_handles| are optional "in-out" parameters. If non-null, +// on return |*num_bytes| and |*num_handles| will usually indicate the number +// of bytes and number of attached handles in the "next" message, respectively, +// whether that message was read or not. (If null, the number of bytes/handles +// is treated as zero.) +// +// If |bytes| is null, then |*num_bytes| must be zero, and similarly for +// |handles| and |*num_handles|. +// +// Partial reads are NEVER done. Either a full read is done and |MOJO_RESULT_OK| +// returned, or the read is NOT done and |MOJO_RESULT_RESOURCE_EXHAUSTED| is +// returned (if |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| was set, the message is +// also discarded in this case). +// +// Returns: +// |MOJO_RESULT_OK| on success (i.e., a message was actually read). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid. +// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if one of the buffers to receive the +// message/attached handles (|bytes|/|*num_bytes| or +// |handles|/|*num_handles|) was too small. (TODO(vtl): Reconsider this +// error code; should distinguish this from the hitting-system-limits +// case.) +// |MOJO_RESULT_SHOULD_WAIT| if no message was available to be read. +MOJO_SYSTEM_EXPORT MojoResult MojoReadMessage( + MojoHandle message_pipe_handle, + void* bytes, // Optional out. + uint32_t* num_bytes, // Optional in/out. + MojoHandle* handles, // Optional out. + uint32_t* num_handles, // Optional in/out. + MojoReadMessageFlags flags); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ diff --git a/chromium/mojo/public/c/system/system_export.h b/chromium/mojo/public/c/system/system_export.h new file mode 100644 index 00000000000..bc3b459843f --- /dev/null +++ b/chromium/mojo/public/c/system/system_export.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ +#define MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ + +#if defined(COMPONENT_BUILD) && defined(MOJO_USE_SYSTEM_IMPL) +#if defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPLEMENTATION) +#define MOJO_SYSTEM_EXPORT __declspec(dllexport) +#else +#define MOJO_SYSTEM_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPLEMENTATION) +#define MOJO_SYSTEM_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SYSTEM_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) || !defined(MOJO_USE_SYSTEM_IMPL) + +#define MOJO_SYSTEM_EXPORT + +#endif // defined(COMPONENT_BUILD) && defined(MOJO_USE_SYSTEM_IMPL) + +#endif // MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ diff --git a/chromium/mojo/public/c/system/types.h b/chromium/mojo/public/c/system/types.h new file mode 100644 index 00000000000..e992d110e3a --- /dev/null +++ b/chromium/mojo/public/c/system/types.h @@ -0,0 +1,176 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains types and constants/macros common to different Mojo system +// APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_TYPES_H_ +#define MOJO_PUBLIC_C_SYSTEM_TYPES_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/macros.h" + +// TODO(vtl): Notes: Use of undefined flags will lead to undefined behavior +// (typically they'll be ignored), not necessarily an error. + +// |MojoTimeTicks|: Used to specify time ticks. Value is in microseconds. + +typedef int64_t MojoTimeTicks; + +// |MojoHandle|: Handles to Mojo objects. +// |MOJO_HANDLE_INVALID| - A value that is never a valid handle. + +typedef uint32_t MojoHandle; + +#ifdef __cplusplus +const MojoHandle MOJO_HANDLE_INVALID = 0; +#else +#define MOJO_HANDLE_INVALID ((MojoHandle) 0) +#endif + +// |MojoResult|: Result codes for Mojo operations. Non-negative values are +// success codes; negative values are error/failure codes. +// |MOJO_RESULT_OK| - Not an error; returned on success. Note that positive +// |MojoResult|s may also be used to indicate success. +// |MOJO_RESULT_CANCELLED| - Operation was cancelled, typically by the caller. +// |MOJO_RESULT_UNKNOWN| - Unknown error (e.g., if not enough information is +// available for a more specific error). +// |MOJO_RESULT_INVALID_ARGUMENT| - Caller specified an invalid argument. This +// differs from |MOJO_RESULT_FAILED_PRECONDITION| in that the former +// indicates arguments that are invalid regardless of the state of the +// system. +// |MOJO_RESULT_DEADLINE_EXCEEDED| - Deadline expired before the operation +// could complete. +// |MOJO_RESULT_NOT_FOUND| - Some requested entity was not found (i.e., does +// not exist). +// |MOJO_RESULT_ALREADY_EXISTS| - Some entity or condition that we attempted +// to create already exists. +// |MOJO_RESULT_PERMISSION_DENIED| - The caller does not have permission to +// for the operation (use |MOJO_RESULT_RESOURCE_EXHAUSTED| for rejections +// caused by exhausting some resource instead). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| - Some resource required for the call +// (possibly some quota) has been exhausted. +// |MOJO_RESULT_FAILED_PRECONDITION| - The system is not in a state required +// for the operation (use this if the caller must do something to rectify +// the state before retrying). +// |MOJO_RESULT_ABORTED| - The operation was aborted by the system, possibly +// due to a concurrency issue (use this if the caller may retry at a +// higher level). +// |MOJO_RESULT_OUT_OF_RANGE| - The operation was attempted past the valid +// range. Unlike |MOJO_RESULT_INVALID_ARGUMENT|, this indicates that the +// operation may be/become valid depending on the system state. (This +// error is similar to |MOJO_RESULT_FAILED_PRECONDITION|, but is more +// specific.) +// |MOJO_RESULT_UNIMPLEMENTED| - The operation is not implemented, supported, +// or enabled. +// |MOJO_RESULT_INTERNAL| - Internal error: this should never happen and +// indicates that some invariant expected by the system has been broken. +// |MOJO_RESULT_UNAVAILABLE| - The operation is (temporarily) currently +// unavailable. The caller may simply retry the operation (possibly with a +// backoff). +// |MOJO_RESULT_DATA_LOSS| - Unrecoverable data loss or corruption. +// |MOJO_RESULT_BUSY| - One of the resources involved is currently being used +// (possibly on another thread) in a way that prevents the current +// operation from proceeding, e.g., if the other operation may result in +// the resource being invalidated. +// |MOJO_RESULT_SHOULD_WAIT| - The request cannot currently be completed +// (e.g., if the data requested is not yet available). The caller should +// wait for it to be feasible using |MojoWait()| or |MojoWaitMany()|. +// +// Note that positive values are also available as success codes. +// +// The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from +// Google3's canonical error codes. +// +// TODO(vtl): Add a |MOJO_RESULT_UNSATISFIABLE|? + +typedef int32_t MojoResult; + +#ifdef __cplusplus +const MojoResult MOJO_RESULT_OK = 0; +const MojoResult MOJO_RESULT_CANCELLED = -1; +const MojoResult MOJO_RESULT_UNKNOWN = -2; +const MojoResult MOJO_RESULT_INVALID_ARGUMENT = -3; +const MojoResult MOJO_RESULT_DEADLINE_EXCEEDED = -4; +const MojoResult MOJO_RESULT_NOT_FOUND = -5; +const MojoResult MOJO_RESULT_ALREADY_EXISTS = -6; +const MojoResult MOJO_RESULT_PERMISSION_DENIED = -7; +const MojoResult MOJO_RESULT_RESOURCE_EXHAUSTED = -8; +const MojoResult MOJO_RESULT_FAILED_PRECONDITION = -9; +const MojoResult MOJO_RESULT_ABORTED = -10; +const MojoResult MOJO_RESULT_OUT_OF_RANGE = -11; +const MojoResult MOJO_RESULT_UNIMPLEMENTED = -12; +const MojoResult MOJO_RESULT_INTERNAL = -13; +const MojoResult MOJO_RESULT_UNAVAILABLE = -14; +const MojoResult MOJO_RESULT_DATA_LOSS = -15; +const MojoResult MOJO_RESULT_BUSY = -16; +const MojoResult MOJO_RESULT_SHOULD_WAIT = -17; +#else +#define MOJO_RESULT_OK ((MojoResult) 0) +#define MOJO_RESULT_CANCELLED ((MojoResult) -1) +#define MOJO_RESULT_UNKNOWN ((MojoResult) -2) +#define MOJO_RESULT_INVALID_ARGUMENT ((MojoResult) -3) +#define MOJO_RESULT_DEADLINE_EXCEEDED ((MojoResult) -4) +#define MOJO_RESULT_NOT_FOUND ((MojoResult) -5) +#define MOJO_RESULT_ALREADY_EXISTS ((MojoResult) -6) +#define MOJO_RESULT_PERMISSION_DENIED ((MojoResult) -7) +#define MOJO_RESULT_RESOURCE_EXHAUSTED ((MojoResult) -8) +#define MOJO_RESULT_FAILED_PRECONDITION ((MojoResult) -9) +#define MOJO_RESULT_ABORTED ((MojoResult) -10) +#define MOJO_RESULT_OUT_OF_RANGE ((MojoResult) -11) +#define MOJO_RESULT_UNIMPLEMENTED ((MojoResult) -12) +#define MOJO_RESULT_INTERNAL ((MojoResult) -13) +#define MOJO_RESULT_UNAVAILABLE ((MojoResult) -14) +#define MOJO_RESULT_DATA_LOSS ((MojoResult) -15) +#define MOJO_RESULT_BUSY ((MojoResult) -16) +#define MOJO_RESULT_SHOULD_WAIT ((MojoResult) -17) +#endif + +// |MojoDeadline|: Used to specify deadlines (timeouts), in microseconds (except +// for |MOJO_DEADLINE_INDEFINITE|). +// |MOJO_DEADLINE_INDEFINITE| - Used to indicate "forever". + +typedef uint64_t MojoDeadline; + +#ifdef __cplusplus +const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast<MojoDeadline>(-1); +#else +#define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) -1) +#endif + +// |MojoHandleSignals|: Used to specify signals that can be waited on for a +// handle (and which can be triggered), e.g., the ability to read or write to +// the handle. +// |MOJO_HANDLE_SIGNAL_NONE| - No flags. |MojoWait()|, etc. will return +// |MOJO_RESULT_FAILED_PRECONDITION| if you attempt to wait on this. +// |MOJO_HANDLE_SIGNAL_READABLE| - Can read (e.g., a message) from the handle. +// |MOJO_HANDLE_SIGNAL_WRITABLE| - Can write (e.g., a message) to the handle. + +typedef uint32_t MojoHandleSignals; + +#ifdef __cplusplus +const MojoHandleSignals MOJO_HANDLE_SIGNAL_NONE = 0; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_READABLE = 1 << 0; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_WRITABLE = 1 << 1; +#else +#define MOJO_HANDLE_SIGNAL_NONE ((MojoHandleSignals) 0) +#define MOJO_HANDLE_SIGNAL_READABLE ((MojoHandleSignals) 1 << 0) +#define MOJO_HANDLE_SIGNAL_WRITABLE ((MojoHandleSignals) 1 << 1) +#endif + +// TODO(vtl): Add out parameters with this to MojoWait/MojoWaitMany. +// Note: This struct is not extensible (and only has 32-bit quantities), so it's +// 32-bit-aligned. +MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int32_t) == 4, int32_t_has_weird_alignment); +struct MOJO_ALIGNAS(4) MojoHandleSignalsState { + MojoHandleSignals satisfied_signals; + MojoHandleSignals satisfiable_signals; +}; +MOJO_COMPILE_ASSERT(sizeof(MojoHandleSignalsState) == 8, + MojoHandleSignalsState_has_wrong_size); + +#endif // MOJO_PUBLIC_C_SYSTEM_TYPES_H_ diff --git a/chromium/mojo/public/c/test_support/test_support.h b/chromium/mojo/public/c/test_support/test_support.h new file mode 100644 index 00000000000..2b686b272eb --- /dev/null +++ b/chromium/mojo/public/c/test_support/test_support.h @@ -0,0 +1,48 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ +#define MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ + +// Note: This header should be compilable as C. + +#include <stdio.h> + +#include "mojo/public/c/test_support/test_support_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MOJO_TEST_SUPPORT_EXPORT void MojoTestSupportLogPerfResult( + const char* test_name, + double value, + const char* units); + +// Opens a "/"-delimited file path relative to the source root. +MOJO_TEST_SUPPORT_EXPORT FILE* MojoTestSupportOpenSourceRootRelativeFile( + const char* source_root_relative_path); + +// Enumerates a "/"-delimited directory path relative to the source root. +// Returns only regular files. The return value is a heap-allocated array of +// heap-allocated strings. Each must be free'd separately. +// +// The return value is built like so: +// +// char** rv = (char**) calloc(N + 1, sizeof(char*)); +// rv[0] = strdup("a"); +// rv[1] = strdup("b"); +// rv[2] = strdup("c"); +// ... +// rv[N] = NULL; +// +MOJO_TEST_SUPPORT_EXPORT +char** MojoTestSupportEnumerateSourceRootRelativeDirectory( + const char* source_root_relative_path); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ diff --git a/chromium/mojo/public/c/test_support/test_support_export.h b/chromium/mojo/public/c/test_support/test_support_export.h new file mode 100644 index 00000000000..e22a9e30144 --- /dev/null +++ b/chromium/mojo/public/c/test_support/test_support_export.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_EXPORT_H_ +#define MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_EXPORT_H_ + +#if defined(WIN32) + +#if defined(MOJO_TEST_SUPPORT_IMPLEMENTATION) +#define MOJO_TEST_SUPPORT_EXPORT __declspec(dllexport) +#else +#define MOJO_TEST_SUPPORT_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_TEST_SUPPORT_IMPLEMENTATION) +#define MOJO_TEST_SUPPORT_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_TEST_SUPPORT_EXPORT +#endif + +#endif // defined(WIN32) + +#endif // MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_EXPORT_H_ diff --git a/chromium/mojo/public/cpp/DEPS b/chromium/mojo/public/cpp/DEPS new file mode 100644 index 00000000000..74acd7c7ed3 --- /dev/null +++ b/chromium/mojo/public/cpp/DEPS @@ -0,0 +1,18 @@ +include_rules = [ + # Require explicit dependencies in each directory. + "-mojo/public", + # But everyone can depend on the C and C++ system headers. + "+mojo/public/c/system", + "+mojo/public/cpp/system", + # Ditto for the C environment headers (but not the C++ environment, since it + # has dependencies of its own). + "+mojo/public/c/environment", +] + +specific_include_rules = { + r".*_(unit|perf)test\.cc": [ + "+testing", + "+mojo/public/cpp/test_support", + "+mojo/public/cpp/utility", + ], +} diff --git a/chromium/mojo/public/cpp/README.md b/chromium/mojo/public/cpp/README.md new file mode 100644 index 00000000000..8f03d984b9a --- /dev/null +++ b/chromium/mojo/public/cpp/README.md @@ -0,0 +1,71 @@ +Mojo Public C++ API +=================== + +This directory contains C++ language bindings for the Mojo Public API. + +A number of subdirectories provide wrappers for the lower-level C APIs (in +subdirectories of the same name, under mojo/public/c/). Typically, these +wrappers provide increased convenience and/or type-safety. + +Other subdirectories provide support (static) libraries of various sorts. In +this case, the organization is to have the public interface for the library in +defined in header files in the subdirectory itself and the implementation of the +library at a lower level, under a lib (sub)subdirectory. A developer should be +able to substitute their own implementation of any such support library, and +expect other support libraries, which may depend on that library, to work +properly. + +Bindings +-------- + +The bindings/ subdirectory contains a support (static) library needed by the +code generated by the bindings generator tool (in mojo/public/tools/bindings/), +which translates Mojo IDL (.mojom) files into idiomatic C++ (among other +languages). + +This library depends on the Environment library. + +Environment +----------- + +The environment/ subdirectory contains a support (static) library that +represents shared state needed to support the Bindings and GLES2 libraries. + +This library depends on the Utility library. + + +GLES2 +----- + +The gles2/ subdirectory contains C++ wrappers (and some additional helpers) of +the API defined in mojo/public/c/gles2/ (which provides access to GLES2). + +These wrappers depend on the Environment library. + +Shell +----- + +The shell/ subdirectory contains a support (static) library that aids in writing +Mojo applications and interacting with the Shell service. + +System +------ + +The system/ subdirectory contains C++ wrappers (and some additional helpers) of +the API defined in mojo/public/c/system/, which defines the basic, "core" API, +especially used to communicate with Mojo services. + +Test Support +------------ + +The test_support/ subdirectory contains C++ wrappers of the test-only API +defined in mojo/public/c/test_support/. It is not meant for general use by Mojo +applications. + +Utility +------- + +The utility/ subdirectory contains a support (static) library that provides +various basic functionality. Most notably, it provides an implementation of a +RunLoop based on MojoWaitMany() that applications may use as the basis for +asynchronous message processing. diff --git a/chromium/mojo/public/cpp/application/DEPS b/chromium/mojo/public/cpp/application/DEPS new file mode 100644 index 00000000000..e808b79707e --- /dev/null +++ b/chromium/mojo/public/cpp/application/DEPS @@ -0,0 +1,14 @@ +include_rules = [ + "+mojo/public/cpp/bindings", + "+mojo/public/interfaces/service_provider", +] + +specific_include_rules = { + "mojo_main_chromium.cc": [ + "+base", + "+mojo/public/cpp" + ], + "mojo_main_standalone.cc": [ + "+mojo/public/cpp" + ], +} diff --git a/chromium/mojo/public/cpp/application/application.h b/chromium/mojo/public/cpp/application/application.h new file mode 100644 index 00000000000..da7a0ed7c61 --- /dev/null +++ b/chromium/mojo/public/cpp/application/application.h @@ -0,0 +1,121 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_APPLICATION_APPLICATION_H_ +#define MOJO_PUBLIC_APPLICATION_APPLICATION_H_ +#include <vector> + +#include "mojo/public/cpp/application/connect.h" +#include "mojo/public/cpp/application/lib/service_connector.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" + +#if defined(WIN32) +#if !defined(CDECL) +#define CDECL __cdecl +#endif +#define APPLICATION_EXPORT __declspec(dllexport) +#else +#define CDECL +#define APPLICATION_EXPORT __attribute__((visibility("default"))) +#endif + +// DSOs can either implement MojoMain directly or include +// mojo_main_{standalone|chromium}.cc in their project and implement +// Application::Create(); +// TODO(davemoore): Establish this as part of our SDK for third party mojo +// application writers. +extern "C" APPLICATION_EXPORT MojoResult CDECL MojoMain( + MojoHandle service_provider_handle); + +namespace mojo { + +// Utility class for creating ServiceProviders that vend service instances. +// To use define a class that implements your specific server api, e.g. FooImpl +// to implement a service named Foo. +// That class must subclass an InterfaceImpl specialization. +// +// If there is context that is to be shared amongst all instances, define a +// constructor with that class as its only argument, otherwise define an empty +// constructor. +// +// class FooImpl : public InterfaceImpl<Foo> { +// public: +// FooImpl() {} +// }; +// +// or +// +// class BarImpl : public InterfaceImpl<Bar> { +// public: +// // context will remain valid for the lifetime of BarImpl. +// BarImpl(BarContext* context) : context_(context) {} +// private: +// BarContext* context; +// }; +// +// Create an Application instance that collects any service implementations. +// +// Application app(service_provider_handle); +// app.AddService<FooImpl>(); +// +// BarContext context; +// app.AddService<BarImpl>(&context); +// +// +class Application { + public: + Application(); + explicit Application(ScopedMessagePipeHandle service_provider_handle); + explicit Application(MojoHandle service_provider_handle); + virtual ~Application(); + + // Override this method to control what urls are allowed to connect to a + // service. + virtual bool AllowIncomingConnection(const mojo::String& service_name, + const mojo::String& requestor_url); + + template <typename Impl, typename Context> + void AddService(Context* context) { + service_registry_.AddServiceConnector( + new internal::ServiceConnector<Impl, Context>(Impl::Name_, context)); + } + + template <typename Impl> + void AddService() { + service_registry_.AddServiceConnector( + new internal::ServiceConnector<Impl, void>(Impl::Name_, NULL)); + } + + template <typename Interface> + void ConnectTo(const std::string& url, InterfacePtr<Interface>* ptr) { + mojo::ConnectToService(service_provider(), url, ptr); + } + + ServiceProvider* service_provider() { + return service_registry_.remote_service_provider(); + } + + void BindServiceProvider(ScopedMessagePipeHandle service_provider_handle); + + protected: + // Override this to do any necessary initialization. There's no need to call + // Application's implementation. + // The service_provider will be bound to its pipe before this is called. + virtual void Initialize(); + + private: + friend MojoResult (::MojoMain)(MojoHandle); + + // Implement this method to create the specific subclass of Application. + static Application* Create(); + + internal::ServiceRegistry service_registry_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Application); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_APPLICATION_APPLICATION_H_ diff --git a/chromium/mojo/public/cpp/application/connect.h b/chromium/mojo/public/cpp/application/connect.h new file mode 100644 index 00000000000..e4ba641e2ed --- /dev/null +++ b/chromium/mojo/public/cpp/application/connect.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_APPLICATION_CONNECT_H_ +#define MOJO_PUBLIC_CPP_APPLICATION_CONNECT_H_ + +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" + +namespace mojo { + +template <typename Interface> +inline void ConnectToService(ServiceProvider* service_provider, + const std::string& url, + InterfacePtr<Interface>* ptr) { + MessagePipe pipe; + ptr->Bind(pipe.handle0.Pass()); + service_provider->ConnectToService( + url, Interface::Name_, pipe.handle1.Pass(), std::string()); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_APPLICATION_CONNECT_H_ diff --git a/chromium/mojo/public/cpp/application/lib/application.cc b/chromium/mojo/public/cpp/application/lib/application.cc new file mode 100644 index 00000000000..78f5a8bbd0e --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/application.cc @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/application/application.h" + +namespace mojo { + +Application::Application() : service_registry_(this) {} + +Application::Application(ScopedMessagePipeHandle service_provider_handle) + : service_registry_(this, service_provider_handle.Pass()) {} + +Application::Application(MojoHandle service_provider_handle) + : service_registry_( + this, + mojo::MakeScopedHandle( + MessagePipeHandle(service_provider_handle)).Pass()) {} + +Application::~Application() {} + +bool Application::AllowIncomingConnection(const mojo::String& service_name, + const mojo::String& requestor_url) { + return true; +} + +void Application::BindServiceProvider( + ScopedMessagePipeHandle service_provider_handle) { + service_registry_.BindRemoteServiceProvider(service_provider_handle.Pass()); +} + +void Application::Initialize() {} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/application/lib/mojo_main_chromium.cc b/chromium/mojo/public/cpp/application/lib/mojo_main_chromium.cc new file mode 100644 index 00000000000..cda7cd02d18 --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/mojo_main_chromium.cc @@ -0,0 +1,23 @@ +// 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/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/application/application.h" + +extern "C" APPLICATION_EXPORT MojoResult CDECL MojoMain( + MojoHandle service_provider_handle) { + base::CommandLine::Init(0, NULL); + base::AtExitManager at_exit; + base::MessageLoop loop; + + scoped_ptr<mojo::Application> app(mojo::Application::Create()); + app->BindServiceProvider( + mojo::MakeScopedHandle(mojo::MessagePipeHandle(service_provider_handle))); + app->Initialize(); + loop.Run(); + + return MOJO_RESULT_OK; +} diff --git a/chromium/mojo/public/cpp/application/lib/mojo_main_standalone.cc b/chromium/mojo/public/cpp/application/lib/mojo_main_standalone.cc new file mode 100644 index 00000000000..05825aa3319 --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/mojo_main_standalone.cc @@ -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. + +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/utility/run_loop.h" + +extern "C" APPLICATION_EXPORT MojoResult CDECL MojoMain( + MojoHandle service_provider_handle) { + mojo::Environment env; + mojo::RunLoop loop; + + mojo::Application* app = mojo::Application::Create(); + app->BindServiceProvider( + mojo::MakeScopedHandle(mojo::MessagePipeHandle(service_provider_handle))); + app->Initialize(); + loop.Run(); + delete app; + + return MOJO_RESULT_OK; +} diff --git a/chromium/mojo/public/cpp/application/lib/service_connector.cc b/chromium/mojo/public/cpp/application/lib/service_connector.cc new file mode 100644 index 00000000000..5cc4421b7aa --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/service_connector.cc @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/application/lib/service_connector.h" + +namespace mojo { +namespace internal { + +ServiceConnectorBase::ServiceConnectorBase(const std::string& name) + : name_(name), + registry_(NULL) { +} + +ServiceConnectorBase::~ServiceConnectorBase() {} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/application/lib/service_connector.h b/chromium/mojo/public/cpp/application/lib/service_connector.h new file mode 100644 index 00000000000..30786adb55d --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/service_connector.h @@ -0,0 +1,133 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_CONNECTOR_H_ +#define MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_CONNECTOR_H_ + +#include <assert.h> + +#include <vector> + +#include "mojo/public/cpp/application/lib/service_registry.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" + +namespace mojo { +namespace internal { + +template <class ServiceImpl, typename Context> +class ServiceConnector; + +// Specialization of ServiceConnection. +// ServiceImpl: Subclass of InterfaceImpl<...>. +// Context: Type of shared context. +template <class ServiceImpl, typename Context> +class ServiceConnection : public ServiceImpl { + public: + ServiceConnection() : ServiceImpl() {} + ServiceConnection(Context* context) : ServiceImpl(context) {} + + virtual void OnConnectionError() MOJO_OVERRIDE { + service_connector_->RemoveConnection(static_cast<ServiceImpl*>(this)); + ServiceImpl::OnConnectionError(); + } + +private: + friend class ServiceConnector<ServiceImpl, Context>; + + // Called shortly after this class is instantiated. + void set_service_connector( + ServiceConnector<ServiceImpl, Context>* connector) { + service_connector_ = connector; + } + + ServiceConnector<ServiceImpl, Context>* service_connector_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceConnection); +}; + +template <typename ServiceImpl, typename Context> +struct ServiceConstructor { + static ServiceConnection<ServiceImpl, Context>* New(Context* context) { + return new ServiceConnection<ServiceImpl, Context>(context); + } +}; + +template <typename ServiceImpl> +struct ServiceConstructor<ServiceImpl, void> { + public: + static ServiceConnection<ServiceImpl, void>* New(void* context) { + return new ServiceConnection<ServiceImpl, void>(); + } +}; + +class ServiceConnectorBase { + public: + ServiceConnectorBase(const std::string& name); + virtual ~ServiceConnectorBase(); + virtual void ConnectToService(const std::string& url, + const std::string& name, + ScopedMessagePipeHandle client_handle) = 0; + std::string name() const { return name_; } + void set_registry(ServiceRegistry* registry) { registry_ = registry; } + + protected: + std::string name_; + ServiceRegistry* registry_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceConnectorBase); +}; + +template <class ServiceImpl, typename Context=void> +class ServiceConnector : public internal::ServiceConnectorBase { + public: + ServiceConnector(const std::string& name, Context* context = NULL) + : ServiceConnectorBase(name), context_(context) {} + + virtual ~ServiceConnector() { + ConnectionList doomed; + doomed.swap(connections_); + for (typename ConnectionList::iterator it = doomed.begin(); + it != doomed.end(); ++it) { + delete *it; + } + assert(connections_.empty()); // No one should have added more! + } + + virtual void ConnectToService(const std::string& url, + const std::string& name, + ScopedMessagePipeHandle handle) MOJO_OVERRIDE { + ServiceConnection<ServiceImpl, Context>* impl = + ServiceConstructor<ServiceImpl, Context>::New(context_); + impl->set_service_connector(this); + BindToPipe(impl, handle.Pass()); + + connections_.push_back(impl); + } + + void RemoveConnection(ServiceImpl* impl) { + // Called from ~ServiceImpl, in response to a connection error. + for (typename ConnectionList::iterator it = connections_.begin(); + it != connections_.end(); ++it) { + if (*it == impl) { + delete impl; + connections_.erase(it); + return; + } + } + } + + Context* context() const { return context_; } + + private: + typedef std::vector<ServiceImpl*> ConnectionList; + ConnectionList connections_; + Context* context_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceConnector); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_CONNECTOR_H_ diff --git a/chromium/mojo/public/cpp/application/lib/service_registry.cc b/chromium/mojo/public/cpp/application/lib/service_registry.cc new file mode 100644 index 00000000000..bc901dfb924 --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/service_registry.cc @@ -0,0 +1,78 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/application/lib/service_registry.h" + +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/application/lib/service_connector.h" + +namespace mojo { +namespace internal { + +ServiceRegistry::ServiceRegistry(Application* application) + : application_(application) { +} + +ServiceRegistry::ServiceRegistry( + Application* application, + ScopedMessagePipeHandle service_provider_handle) + : application_(application) { + remote_service_provider_.Bind(service_provider_handle.Pass()); + remote_service_provider_.set_client(this); +} + +ServiceRegistry::~ServiceRegistry() { + for (NameToServiceConnectorMap::iterator i = + name_to_service_connector_.begin(); + i != name_to_service_connector_.end(); ++i) { + delete i->second; + } + name_to_service_connector_.clear(); +} + +void ServiceRegistry::AddServiceConnector( + ServiceConnectorBase* service_connector) { + name_to_service_connector_[service_connector->name()] = service_connector; + service_connector->set_registry(this); +} + +void ServiceRegistry::RemoveServiceConnector( + ServiceConnectorBase* service_connector) { + NameToServiceConnectorMap::iterator it = + name_to_service_connector_.find(service_connector->name()); + assert(it != name_to_service_connector_.end()); + delete it->second; + name_to_service_connector_.erase(it); + if (name_to_service_connector_.empty()) + remote_service_provider_.reset(); +} + +void ServiceRegistry::BindRemoteServiceProvider( + ScopedMessagePipeHandle service_provider_handle) { + remote_service_provider_.Bind(service_provider_handle.Pass()); + remote_service_provider_.set_client(this); +} + +void ServiceRegistry::ConnectToService(const mojo::String& service_url, + const mojo::String& service_name, + ScopedMessagePipeHandle client_handle, + const mojo::String& requestor_url) { + if (name_to_service_connector_.find(service_name) == + name_to_service_connector_.end() || + !application_->AllowIncomingConnection(service_name, requestor_url)) { + client_handle.reset(); + return; + } + + internal::ServiceConnectorBase* service_connector = + name_to_service_connector_[service_name]; + assert(service_connector); + // requestor_url is ignored because the service_connector stores the url + // of the requestor safely. + return service_connector->ConnectToService( + service_url, service_name, client_handle.Pass()); +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/application/lib/service_registry.h b/chromium/mojo/public/cpp/application/lib/service_registry.h new file mode 100644 index 00000000000..478cd055ef2 --- /dev/null +++ b/chromium/mojo/public/cpp/application/lib/service_registry.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_REGISTRY_H_ +#define MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_REGISTRY_H_ + +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" + +namespace mojo { + +class Application; + +namespace internal { + +class ServiceConnectorBase; + +class ServiceRegistry : public ServiceProvider { + public: + ServiceRegistry(Application* application); + ServiceRegistry(Application* application, + ScopedMessagePipeHandle service_provider_handle); + virtual ~ServiceRegistry(); + + void AddServiceConnector(ServiceConnectorBase* service_connector); + void RemoveServiceConnector(ServiceConnectorBase* service_connector); + + ServiceProvider* remote_service_provider() { + return remote_service_provider_.get(); + } + + void BindRemoteServiceProvider( + ScopedMessagePipeHandle service_provider_handle); + + // ServiceProvider method. + virtual void ConnectToService(const mojo::String& service_url, + const mojo::String& service_name, + ScopedMessagePipeHandle client_handle, + const mojo::String& requestor_url) + MOJO_OVERRIDE; + + private: + Application* application_; + typedef std::map<std::string, ServiceConnectorBase*> + NameToServiceConnectorMap; + NameToServiceConnectorMap name_to_service_connector_; + ServiceProviderPtr remote_service_provider_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceRegistry); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_REGISTRY_H_ diff --git a/chromium/mojo/public/cpp/bindings/BUILD.gn b/chromium/mojo/public/cpp/bindings/BUILD.gn new file mode 100644 index 00000000000..2cc35aa52ff --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/BUILD.gn @@ -0,0 +1,53 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("bindings") { + sources = [ + "array.h", + "callback.h", + "error_handler.h", + "interface_ptr.h", + "message.h", + "message_filter.h", + "no_interface.h", + "string.h", + "struct_ptr.h", + "sync_dispatcher.h", + "type_converter.h", + "lib/array_internal.cc", + "lib/array_internal.h", + "lib/array_serialization.h", + "lib/bindings_internal.h", + "lib/bindings_serialization.cc", + "lib/bindings_serialization.h", + "lib/bounds_checker.cc", + "lib/bounds_checker.h", + "lib/buffer.h", + "lib/callback_internal.h", + "lib/connector.cc", + "lib/connector.h", + "lib/filter_chain.cc", + "lib/filter_chain.h", + "lib/fixed_buffer.cc", + "lib/fixed_buffer.h", + "lib/message.cc", + "lib/message_builder.cc", + "lib/message_builder.h", + "lib/message_filter.cc", + "lib/message_internal.h", + "lib/message_queue.cc", + "lib/message_queue.h", + "lib/no_interface.cc", + "lib/router.cc", + "lib/router.h", + "lib/shared_data.h", + "lib/shared_ptr.h", + "lib/string_serialization.cc", + "lib/string_serialization.h", + "lib/sync_dispatcher.cc", + "lib/template_util.h", + "lib/validation_errors.cc", + "lib/validation_errors.h", + ] +} diff --git a/chromium/mojo/public/cpp/bindings/DEPS b/chromium/mojo/public/cpp/bindings/DEPS new file mode 100644 index 00000000000..2a0496e9d9c --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/public/cpp/environment", +] diff --git a/chromium/mojo/public/cpp/bindings/array.h b/chromium/mojo/public/cpp/bindings/array.h new file mode 100644 index 00000000000..daf7125bb3c --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/array.h @@ -0,0 +1,143 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_ + +#include <string.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +namespace mojo { + +// Provides read-only access to array data. +template <typename T> +class Array { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(Array, RValue) + public: + typedef internal::ArrayTraits<T, internal::IsMoveOnlyType<T>::value> + Traits; + typedef typename Traits::ConstRefType ConstRefType; + typedef typename Traits::RefType RefType; + typedef typename Traits::StorageType StorageType; + typedef typename Traits::ForwardType ForwardType; + + typedef internal::Array_Data<typename internal::WrapperTraits<T>::DataType> + Data_; + + Array() : is_null_(true) {} + explicit Array(size_t size) : vec_(size), is_null_(false) { + Traits::Initialize(&vec_); + } + ~Array() { Traits::Finalize(&vec_); } + + Array(RValue other) : is_null_(true) { Take(other.object); } + Array& operator=(RValue other) { + Take(other.object); + return *this; + } + + static Array New(size_t size) { + return Array(size).Pass(); + } + + template <typename U> + static Array From(const U& other) { + return TypeConverter<Array, U>::ConvertFrom(other); + } + + template <typename U> + U To() const { + return TypeConverter<Array, U>::ConvertTo(*this); + } + + void reset() { + if (!vec_.empty()) { + Traits::Finalize(&vec_); + vec_.clear(); + } + is_null_ = true; + } + + bool is_null() const { return is_null_; } + + size_t size() const { return vec_.size(); } + + ConstRefType at(size_t offset) const { return Traits::at(&vec_, offset); } + ConstRefType operator[](size_t offset) const { return at(offset); } + + RefType at(size_t offset) { return Traits::at(&vec_, offset); } + RefType operator[](size_t offset) { return at(offset); } + + void push_back(ForwardType value) { + is_null_ = false; + Traits::PushBack(&vec_, value); + } + + void resize(size_t size) { + is_null_ = false; + Traits::Resize(&vec_, size); + } + + const std::vector<StorageType>& storage() const { + return vec_; + } + operator const std::vector<StorageType>&() const { + return vec_; + } + + void Swap(Array* other) { + std::swap(is_null_, other->is_null_); + vec_.swap(other->vec_); + } + void Swap(std::vector<StorageType>* other) { + is_null_ = false; + vec_.swap(*other); + } + + private: + typedef std::vector<StorageType> Array::*Testable; + + public: + operator Testable() const { return is_null_ ? 0 : &Array::vec_; } + + private: + void Take(Array* other) { + reset(); + Swap(other); + } + + std::vector<StorageType> vec_; + bool is_null_; +}; + +template <typename T, typename E> +class TypeConverter<Array<T>, std::vector<E> > { + public: + static Array<T> ConvertFrom(const std::vector<E>& input) { + Array<T> result(input.size()); + for (size_t i = 0; i < input.size(); ++i) + result[i] = TypeConverter<T, E>::ConvertFrom(input[i]); + return result.Pass(); + } + static std::vector<E> ConvertTo(const Array<T>& input) { + std::vector<E> result; + if (!input.is_null()) { + result.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + result[i] = TypeConverter<T, E>::ConvertTo(input[i]); + } + return result; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_ diff --git a/chromium/mojo/public/cpp/bindings/callback.h b/chromium/mojo/public/cpp/bindings/callback.h new file mode 100644 index 00000000000..d8ff4718232 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/callback.h @@ -0,0 +1,462 @@ +// This file was GENERATED by command: +// pump.py callback.h.pump +// DO NOT EDIT BY HAND!!! + + + +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ + +#include "mojo/public/cpp/bindings/lib/callback_internal.h" +#include "mojo/public/cpp/bindings/lib/shared_ptr.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template <typename Sig> +class Callback; + +template <> +class Callback<void()> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run() const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run() const { + if (sink_.get()) + sink_->Run(); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run() const MOJO_OVERRIDE { + sink.Run(); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1> +class Callback<void(A1)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1, typename A2> +class Callback<void(A1, A2)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1), + internal::Forward(a2)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1), + internal::Forward(a2)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1, typename A2, typename A3> +class Callback<void(A1, A2, A3)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1, typename A2, typename A3, typename A4> +class Callback<void(A1, A2, A3, A4)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1, typename A2, typename A3, typename A4, typename A5> +class Callback<void(A1, A2, A3, A4, A5)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4), + internal::Forward(a5)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4), + internal::Forward(a5)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1, typename A2, typename A3, typename A4, typename A5, + typename A6> +class Callback<void(A1, A2, A3, A4, A5, A6)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5, + typename internal::Callback_ParamTraits<A6>::ForwardType a6) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5, + typename internal::Callback_ParamTraits<A6>::ForwardType a6) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4), + internal::Forward(a5), + internal::Forward(a6)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5, + typename internal::Callback_ParamTraits<A6>::ForwardType a6) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4), + internal::Forward(a5), + internal::Forward(a6)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +template <typename A1, typename A2, typename A3, typename A4, typename A5, + typename A6, typename A7> +class Callback<void(A1, A2, A3, A4, A5, A6, A7)> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5, + typename internal::Callback_ParamTraits<A6>::ForwardType a6, + typename internal::Callback_ParamTraits<A7>::ForwardType a7) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5, + typename internal::Callback_ParamTraits<A6>::ForwardType a6, + typename internal::Callback_ParamTraits<A7>::ForwardType a7) const { + if (sink_.get()) + sink_->Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4), + internal::Forward(a5), + internal::Forward(a6), + internal::Forward(a7)); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + typename internal::Callback_ParamTraits<A1>::ForwardType a1, + typename internal::Callback_ParamTraits<A2>::ForwardType a2, + typename internal::Callback_ParamTraits<A3>::ForwardType a3, + typename internal::Callback_ParamTraits<A4>::ForwardType a4, + typename internal::Callback_ParamTraits<A5>::ForwardType a5, + typename internal::Callback_ParamTraits<A6>::ForwardType a6, + typename internal::Callback_ParamTraits<A7>::ForwardType a7) const + MOJO_OVERRIDE { + sink.Run( + internal::Forward(a1), + internal::Forward(a2), + internal::Forward(a3), + internal::Forward(a4), + internal::Forward(a5), + internal::Forward(a6), + internal::Forward(a7)); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ diff --git a/chromium/mojo/public/cpp/bindings/callback.h.pump b/chromium/mojo/public/cpp/bindings/callback.h.pump new file mode 100644 index 00000000000..a0f23d5f77e --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/callback.h.pump @@ -0,0 +1,80 @@ +$$ This is a pump file for generating file templates. Pump is a python +$$ script that is part of the Google Test suite of utilities. Description +$$ can be found here: +$$ +$$ http://code.google.com/p/googletest/wiki/PumpManual +$$ + +$var MAX_ARITY = 7 + +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ + +#include "mojo/public/cpp/bindings/lib/callback_internal.h" +#include "mojo/public/cpp/bindings/lib/shared_ptr.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template <typename Sig> +class Callback; + +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +template <$for ARG , [[typename A$(ARG)]]> +class Callback<void($for ARG , [[A$(ARG)]])> { + public: + struct Runnable { + virtual ~Runnable() {} + virtual void Run( + $for ARG , + [[typename internal::Callback_ParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const = 0; + }; + + Callback() {} + + // The Callback assumes ownership of |runnable|. + explicit Callback(Runnable* runnable) : sink_(runnable) {} + + // Any class that is copy-constructable and has a compatible Run method may + // be adapted to a Callback using this constructor. + template <typename Sink> + Callback(const Sink& sink) : sink_(new Adapter<Sink>(sink)) {} + + void Run( + $for ARG , + [[typename internal::Callback_ParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const { + if (sink_.get()) + sink_->Run( + $for ARG , + [[internal::Forward(a$(ARG))]]); + } + + private: + template <typename Sink> + struct Adapter : public Runnable { + explicit Adapter(const Sink& sink) : sink(sink) {} + virtual void Run( + $for ARG , + [[typename internal::Callback_ParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const MOJO_OVERRIDE { + sink.Run( + $for ARG , + [[internal::Forward(a$(ARG))]]); + } + Sink sink; + }; + + internal::SharedPtr<Runnable> sink_; +}; + +]] $$ for ARITY + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_ diff --git a/chromium/mojo/public/cpp/bindings/error_handler.h b/chromium/mojo/public/cpp/bindings/error_handler.h new file mode 100644 index 00000000000..8ce1af27264 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/error_handler.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ERROR_HANDLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ERROR_HANDLER_H_ + +namespace mojo { + +// This interface is used to report connection errors. +class ErrorHandler { + public: + virtual ~ErrorHandler() {} + virtual void OnConnectionError() = 0; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ERROR_HANDLER_H_ diff --git a/chromium/mojo/public/cpp/bindings/interface_impl.h b/chromium/mojo/public/cpp/bindings/interface_impl.h new file mode 100644 index 00000000000..8ba5e7ffa4a --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/interface_impl.h @@ -0,0 +1,109 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_IMPL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_IMPL_H_ + +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/interface_impl_internal.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// InterfaceImpl<..> is designed to be the base class of an interface +// implementation. It may be bound to a pipe or a proxy, see BindToPipe and +// BindToProxy. +template <typename Interface> +class InterfaceImpl : public internal::InterfaceImplBase<Interface> { + public: + typedef typename Interface::Client Client; + + InterfaceImpl() : internal_state_(this) {} + virtual ~InterfaceImpl() {} + + // Returns a proxy to the client interface. This is null upon construction, + // and becomes non-null after OnClientConnected. NOTE: It remains non-null + // until this instance is deleted. + Client* client() { return internal_state_.client(); } + + // Called when the client has connected to this instance. + virtual void OnConnectionEstablished() {} + + // Called when the client is no longer connected to this instance. NOTE: The + // client() method continues to return a non-null pointer after this method + // is called. After this method is called, any method calls made on client() + // will be silently ignored. + virtual void OnConnectionError() {} + + // DO NOT USE. Exposed only for internal use and for testing. + internal::InterfaceImplState<Interface>* internal_state() { + return &internal_state_; + } + + private: + internal::InterfaceImplState<Interface> internal_state_; + MOJO_DISALLOW_COPY_AND_ASSIGN(InterfaceImpl); +}; + +// Takes an instance of an InterfaceImpl<..> subclass and binds it to the given +// MessagePipe. The instance is returned for convenience in member initializer +// lists, etc. If the pipe is closed, the instance's OnConnectionError method +// will be called. +// +// The instance is also bound to the current thread. Its methods will only be +// called on the current thread, and if the current thread exits, then it will +// also be deleted, and along with it, its end point of the pipe will be closed. +// +// Before returning, the instance's OnConnectionEstablished method is called. +template <typename Impl> +Impl* BindToPipe( + Impl* instance, + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + instance->internal_state()->Bind(handle.Pass(), waiter); + return instance; +} + +// Takes an instance of an InterfaceImpl<..> subclass and binds it to the given +// InterfacePtr<..>. The instance is returned for convenience in member +// initializer lists, etc. If the pipe is closed, the instance's +// OnConnectionError method will be called. +// +// The instance is also bound to the current thread. Its methods will only be +// called on the current thread, and if the current thread exits, then it will +// also be deleted, and along with it, its end point of the pipe will be closed. +// +// Before returning, the instance's OnConnectionEstablished method is called. +template <typename Impl, typename Interface> +Impl* BindToProxy( + Impl* instance, + InterfacePtr<Interface>* ptr, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + instance->internal_state()->BindProxy(ptr, waiter); + return instance; +} + +// Takes an instance of an InterfaceImpl<..> subclass and binds it to the given +// InterfaceRequest<..>. The instance is returned for convenience in member +// initializer lists, etc. If the pipe is closed, the instance's +// OnConnectionError method will be called. +// +// The instance is also bound to the current thread. Its methods will only be +// called on the current thread, and if the current thread exits, then it will +// also be deleted, and along with it, its end point of the pipe will be closed. +// +// Before returning, the instance will receive a SetClient call, providing it +// with a proxy to the client on the other end of the pipe. +template <typename Impl, typename Interface> +Impl* BindToRequest( + Impl* instance, + InterfaceRequest<Interface>* request, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + return BindToPipe(instance, request->PassMessagePipe(), waiter); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_IMPL_H_ diff --git a/chromium/mojo/public/cpp/bindings/interface_ptr.h b/chromium/mojo/public/cpp/bindings/interface_ptr.h new file mode 100644 index 00000000000..726896d9cce --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/interface_ptr.h @@ -0,0 +1,124 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ + +#include <assert.h> + +#include <algorithm> + +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/public/cpp/bindings/lib/interface_ptr_internal.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +class ErrorHandler; + +// InterfacePtr represents a proxy to a remote instance of an interface. +template <typename Interface> +class InterfacePtr { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(InterfacePtr, RValue) + public: + InterfacePtr() {} + + InterfacePtr(RValue other) { + internal_state_.Swap(&other.object->internal_state_); + } + InterfacePtr& operator=(RValue other) { + reset(); + internal_state_.Swap(&other.object->internal_state_); + return *this; + } + + ~InterfacePtr() {} + + Interface* get() const { + return internal_state_.instance(); + } + Interface* operator->() const { return get(); } + Interface& operator*() const { return *get(); } + + void reset() { + State doomed; + internal_state_.Swap(&doomed); + } + + // This method configures the InterfacePtr<..> to be a proxy to a remote + // object on the other end of the given pipe. + // + // The proxy is bound to the current thread, which means its methods may + // only be called on the current thread. + // + // To move a bound InterfacePtr<..> to another thread, call + // ResetAndReturnMessagePipe. Then create a new InterfacePtr<..> on another + // thread, and bind the new InterfacePtr<..> to the message pipe on that + // thread. + void Bind( + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + reset(); + internal_state_.ConfigureProxy(handle.Pass(), waiter); + } + + // The client interface may only be set after this InterfacePtr<..> is bound. + void set_client(typename Interface::Client* client) { + internal_state_.set_client(client); + } + + // This method may be called to query if the underlying pipe has encountered + // an error. If true, this means method calls made on this interface will be + // dropped (and may have already been dropped) on the floor. + bool encountered_error() const { + assert(internal_state_.router()); + return internal_state_.router()->encountered_error(); + } + + // This method may be called to register an ErrorHandler to observe a + // connection error on the underlying pipe. The callback runs asynchronously + // from the current message loop. + void set_error_handler(ErrorHandler* error_handler) { + assert(internal_state_.router()); + internal_state_.router()->set_error_handler(error_handler); + } + + // Returns the underlying message pipe handle (if any) and resets the + // InterfacePtr<..> to its uninitialized state. This method is helpful if you + // need to move a proxy to another thread. See related notes for Bind. + ScopedMessagePipeHandle PassMessagePipe() { + State state; + internal_state_.Swap(&state); + return state.router() ? + state.router()->PassMessagePipe() : ScopedMessagePipeHandle(); + } + + // DO NOT USE. Exposed only for internal use and for testing. + internal::InterfacePtrState<Interface>* internal_state() { + return &internal_state_; + } + + private: + typedef internal::InterfacePtrState<Interface> State; + State internal_state_; +}; + +// Takes a handle to the proxy end-point of a pipe. On the other end is +// presumed to be an interface implementation of type |Interface|. Returns a +// generated proxy to that interface, which may be used on the current thread. +// It is valid to call set_client on the returned InterfacePtr<..> to set an +// instance of Interface::Client. +template <typename Interface> +InterfacePtr<Interface> MakeProxy( + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + InterfacePtr<Interface> ptr; + if (handle.is_valid()) + ptr.Bind(handle.Pass(), waiter); + return ptr.Pass(); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ diff --git a/chromium/mojo/public/cpp/bindings/interface_request.h b/chromium/mojo/public/cpp/bindings/interface_request.h new file mode 100644 index 00000000000..6b7d3033c9d --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/interface_request.h @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ + +#include "mojo/public/cpp/bindings/interface_ptr.h" + +namespace mojo { + +// Used in methods that return instances of remote objects. +template <typename Interface> +class InterfaceRequest { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(InterfaceRequest, RValue) + public: + InterfaceRequest() {} + + InterfaceRequest(RValue other) { + handle_ = other.object->handle_.Pass(); + } + InterfaceRequest& operator=(RValue other) { + handle_ = other.object->handle_.Pass(); + return *this; + } + + // Returns true if the request has yet to be completed. + bool is_pending() const { return handle_.is_valid(); } + + void Bind(ScopedMessagePipeHandle handle) { + handle_ = handle.Pass(); + } + + ScopedMessagePipeHandle PassMessagePipe() { + return handle_.Pass(); + } + + private: + ScopedMessagePipeHandle handle_; +}; + +template <typename Interface> +InterfaceRequest<Interface> MakeRequest(ScopedMessagePipeHandle handle) { + InterfaceRequest<Interface> request; + request.Bind(handle.Pass()); + return request.Pass(); +} + +// Used to construct a request that synchronously binds an InterfacePtr<..>, +// making it immediately usable upon return. The resulting request object may +// then be later bound to an InterfaceImpl<..> via BindToRequest. +// +// Given the following interface: +// +// interface Foo { +// CreateBar(Bar& bar); +// } +// +// The caller of CreateBar would have code similar to the following: +// +// InterfacePtr<Foo> foo = ...; +// InterfacePtr<Bar> bar; +// foo->CreateBar(Get(&bar)); +// +// Upon return from CreateBar, |bar| is ready to have methods called on it. +// +template <typename Interface> +InterfaceRequest<Interface> Get(InterfacePtr<Interface>* ptr) { + MessagePipe pipe; + ptr->Bind(pipe.handle0.Pass()); + return MakeRequest<Interface>(pipe.handle1.Pass()); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/DEPS b/chromium/mojo/public/cpp/bindings/lib/DEPS new file mode 100644 index 00000000000..b809b58d337 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+mojo/public/cpp/bindings", + "+mojo/public/cpp/environment", + "+mojo/public/cpp/system", +] diff --git a/chromium/mojo/public/cpp/bindings/lib/TODO b/chromium/mojo/public/cpp/bindings/lib/TODO new file mode 100644 index 00000000000..21bcb6fe7a2 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/TODO @@ -0,0 +1,6 @@ +TODOs: + - Ensure validation checks are solid + - Add tests of validation logic + - Optimize Buffer classes? + - Add compile-time asserts to verify object packing and padding. + - Investigate making arrays of objects not be arrays of pointers. diff --git a/chromium/mojo/public/cpp/bindings/lib/array_internal.cc b/chromium/mojo/public/cpp/bindings/lib/array_internal.cc new file mode 100644 index 00000000000..3f7f1ce0b16 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/array_internal.cc @@ -0,0 +1,70 @@ +// 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 "mojo/public/cpp/bindings/lib/array_internal.h" + +namespace mojo { +namespace internal { + +ArrayDataTraits<bool>::BitRef::~BitRef() { +} + +ArrayDataTraits<bool>::BitRef::BitRef(uint8_t* storage, uint8_t mask) + : storage_(storage), + mask_(mask) { +} + +ArrayDataTraits<bool>::BitRef& +ArrayDataTraits<bool>::BitRef::operator=(bool value) { + if (value) { + *storage_ |= mask_; + } else { + *storage_ &= ~mask_; + } + return *this; +} + +ArrayDataTraits<bool>::BitRef& +ArrayDataTraits<bool>::BitRef::operator=(const BitRef& value) { + return (*this) = static_cast<bool>(value); +} + +ArrayDataTraits<bool>::BitRef::operator bool() const { + return (*storage_ & mask_) != 0; +} + +// static +void ArraySerializationHelper<Handle, true>::EncodePointersAndHandles( + const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + EncodeHandle(&elements[i], handles); +} + +// static +void ArraySerializationHelper<Handle, true>::DecodePointersAndHandles( + const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + DecodeHandle(&elements[i], handles); +} + +// static +bool ArraySerializationHelper<Handle, true>::ValidateElements( + const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!bounds_checker->ClaimHandle(elements[i])) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_HANDLE); + return false; + } + } + return true; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/array_internal.h b/chromium/mojo/public/cpp/bindings/lib/array_internal.h new file mode 100644 index 00000000000..8ea00991251 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/array_internal.h @@ -0,0 +1,384 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ + +#include <new> +#include <vector> + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +namespace mojo { +template <typename T> class Array; +class String; + +namespace internal { + +template <typename T> +struct ArrayDataTraits { + typedef T StorageType; + typedef T& Ref; + typedef T const& ConstRef; + + static size_t GetStorageSize(size_t num_elements) { + return sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset]; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset]; + } +}; + +template <typename P> +struct ArrayDataTraits<P*> { + typedef StructPointer<P> StorageType; + typedef P*& Ref; + typedef P* const& ConstRef; + + static size_t GetStorageSize(size_t num_elements) { + return sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset].ptr; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset].ptr; + } +}; + +template <typename T> +struct ArrayDataTraits<Array_Data<T>*> { + typedef ArrayPointer<T> StorageType; + typedef Array_Data<T>*& Ref; + typedef Array_Data<T>* const& ConstRef; + + static size_t GetStorageSize(size_t num_elements) { + return sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset].ptr; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset].ptr; + } +}; + +// Specialization of Arrays for bools, optimized for space. It has the +// following differences from a generalized Array: +// * Each element takes up a single bit of memory. +// * Accessing a non-const single element uses a helper class |BitRef|, which +// emulates a reference to a bool. +template <> +struct ArrayDataTraits<bool> { + // Helper class to emulate a reference to a bool, used for direct element + // access. + class BitRef { + public: + ~BitRef(); + BitRef& operator=(bool value); + BitRef& operator=(const BitRef& value); + operator bool() const; + private: + friend struct ArrayDataTraits<bool>; + BitRef(uint8_t* storage, uint8_t mask); + BitRef(); + uint8_t* storage_; + uint8_t mask_; + }; + + typedef uint8_t StorageType; + typedef BitRef Ref; + typedef bool ConstRef; + + static size_t GetStorageSize(size_t num_elements) { + return ((num_elements + 7) / 8); + } + static BitRef ToRef(StorageType* storage, size_t offset) { + return BitRef(&storage[offset / 8], 1 << (offset % 8)); + } + static bool ToConstRef(const StorageType* storage, size_t offset) { + return (storage[offset / 8] & (1 << (offset % 8))) != 0; + } +}; + +// What follows is code to support the serialization of Array_Data<T>. There +// are two interesting cases: arrays of primitives and arrays of objects. +// Arrays of objects are represented as arrays of pointers to objects. + +template <typename T, bool kIsHandle> struct ArraySerializationHelper; + +template <typename T> +struct ArraySerializationHelper<T, false> { + typedef typename ArrayDataTraits<T>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + } + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + } + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker) { + return true; + } +}; + +template <> +struct ArraySerializationHelper<Handle, true> { + typedef ArrayDataTraits<Handle>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles); + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles); + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker); +}; + +template <typename H> +struct ArraySerializationHelper<H, true> { + typedef typename ArrayDataTraits<H>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + ArraySerializationHelper<Handle, true>::EncodePointersAndHandles( + header, elements, handles); + } + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + ArraySerializationHelper<Handle, true>::DecodePointersAndHandles( + header, elements, handles); + } + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker) { + return ArraySerializationHelper<Handle, true>::ValidateElements( + header, elements, bounds_checker); + } +}; + +template <typename P> +struct ArraySerializationHelper<P*, false> { + typedef typename ArrayDataTraits<P*>::StorageType ElementType; + + static void EncodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + Encode(&elements[i], handles); + } + + static void DecodePointersAndHandles(const ArrayHeader* header, + ElementType* elements, + std::vector<Handle>* handles) { + for (uint32_t i = 0; i < header->num_elements; ++i) + Decode(&elements[i], handles); + } + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + BoundsChecker* bounds_checker) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!ValidateEncodedPointer(&elements[i].offset)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_POINTER); + return false; + } + if (!P::Validate(DecodePointerRaw(&elements[i].offset), bounds_checker)) + return false; + } + return true; + } +}; + +template <typename T> +class Array_Data { + public: + typedef ArrayDataTraits<T> Traits; + typedef typename Traits::StorageType StorageType; + typedef typename Traits::Ref Ref; + typedef typename Traits::ConstRef ConstRef; + typedef ArraySerializationHelper<T, IsHandle<T>::value> Helper; + + static Array_Data<T>* New(size_t num_elements, Buffer* buf) { + size_t num_bytes = sizeof(Array_Data<T>) + + Traits::GetStorageSize(num_elements); + return new (buf->Allocate(num_bytes)) Array_Data<T>(num_bytes, + num_elements); + } + + static bool Validate(const void* data, BoundsChecker* bounds_checker) { + if (!data) + return true; + if (!IsAligned(data)) { + ReportValidationError(VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!bounds_checker->IsValidRange(data, sizeof(ArrayHeader))) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + const ArrayHeader* header = static_cast<const ArrayHeader*>(data); + if (header->num_bytes < (sizeof(Array_Data<T>) + + Traits::GetStorageSize(header->num_elements))) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + return false; + } + if (!bounds_checker->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const Array_Data<T>* object = static_cast<const Array_Data<T>*>(data); + return Helper::ValidateElements(&object->header_, object->storage(), + bounds_checker); + } + + size_t size() const { return header_.num_elements; } + + Ref at(size_t offset) { + assert(offset < static_cast<size_t>(header_.num_elements)); + return Traits::ToRef(storage(), offset); + } + + ConstRef at(size_t offset) const { + assert(offset < static_cast<size_t>(header_.num_elements)); + return Traits::ToConstRef(storage(), offset); + } + + StorageType* storage() { + return reinterpret_cast<StorageType*>( + reinterpret_cast<char*>(this) + sizeof(*this)); + } + + const StorageType* storage() const { + return reinterpret_cast<const StorageType*>( + reinterpret_cast<const char*>(this) + sizeof(*this)); + } + + void EncodePointersAndHandles(std::vector<Handle>* handles) { + Helper::EncodePointersAndHandles(&header_, storage(), handles); + } + + void DecodePointersAndHandles(std::vector<Handle>* handles) { + Helper::DecodePointersAndHandles(&header_, storage(), handles); + } + + private: + Array_Data(size_t num_bytes, size_t num_elements) { + header_.num_bytes = static_cast<uint32_t>(num_bytes); + header_.num_elements = static_cast<uint32_t>(num_elements); + } + ~Array_Data() {} + + internal::ArrayHeader header_; + + // Elements of type internal::ArrayDataTraits<T>::StorageType follow. +}; +MOJO_COMPILE_ASSERT(sizeof(Array_Data<char>) == 8, bad_sizeof_Array_Data); + +// UTF-8 encoded +typedef Array_Data<char> String_Data; + +template <typename T, bool kIsMoveOnlyType> struct ArrayTraits {}; + +template <typename T> struct ArrayTraits<T, false> { + typedef T StorageType; + typedef typename std::vector<T>::reference RefType; + typedef typename std::vector<T>::const_reference ConstRefType; + typedef ConstRefType ForwardType; + static inline void Initialize(std::vector<T>* vec) { + } + static inline void Finalize(std::vector<T>* vec) { + } + static inline ConstRefType at(const std::vector<T>* vec, size_t offset) { + return vec->at(offset); + } + static inline RefType at(std::vector<T>* vec, size_t offset) { + return vec->at(offset); + } + static inline void Resize(std::vector<T>* vec, size_t size) { + vec->resize(size); + } + static inline void PushBack(std::vector<T>* vec, ForwardType value) { + vec->push_back(value); + } +}; + +template <typename T> struct ArrayTraits<T, true> { + struct StorageType { + char buf[sizeof(T) + (8 - (sizeof(T) % 8)) % 8]; // Make 8-byte aligned. + }; + typedef T& RefType; + typedef const T& ConstRefType; + typedef T ForwardType; + static inline void Initialize(std::vector<StorageType>* vec) { + for (size_t i = 0; i < vec->size(); ++i) + new (vec->at(i).buf) T(); + } + static inline void Finalize(std::vector<StorageType>* vec) { + for (size_t i = 0; i < vec->size(); ++i) + reinterpret_cast<T*>(vec->at(i).buf)->~T(); + } + static inline ConstRefType at(const std::vector<StorageType>* vec, + size_t offset) { + return *reinterpret_cast<const T*>(vec->at(offset).buf); + } + static inline RefType at(std::vector<StorageType>* vec, size_t offset) { + return *reinterpret_cast<T*>(vec->at(offset).buf); + } + static inline void Resize(std::vector<StorageType>* vec, size_t size) { + size_t old_size = vec->size(); + for (size_t i = size; i < old_size; i++) + reinterpret_cast<T*>(vec->at(i).buf)->~T(); + ResizeStorage(vec, size); + for (size_t i = old_size; i < vec->size(); i++) + new (vec->at(i).buf) T(); + } + static inline void PushBack(std::vector<StorageType>* vec, RefType value) { + size_t old_size = vec->size(); + ResizeStorage(vec, old_size + 1); + new (vec->at(old_size).buf) T(value.Pass()); + } + static inline void ResizeStorage(std::vector<StorageType>* vec, size_t size) { + if (size <= vec->capacity()) { + vec->resize(size); + return; + } + std::vector<StorageType> new_storage(size); + for (size_t i = 0; i < vec->size(); i++) + new (new_storage.at(i).buf) T(at(vec, i).Pass()); + vec->swap(new_storage); + Finalize(&new_storage); + } +}; + +template <> struct WrapperTraits<String, false> { + typedef String_Data* DataType; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/array_serialization.h b/chromium/mojo/public/cpp/bindings/lib/array_serialization.h new file mode 100644 index 00000000000..83403d52502 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/array_serialization.h @@ -0,0 +1,179 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ + +#include <string> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/string_serialization.h" + +namespace mojo { + +template <typename E> +inline size_t GetSerializedSize_(const Array<E>& input); + +template <typename E, typename F> +inline void Serialize_(Array<E> input, internal::Buffer* buf, + internal::Array_Data<F>** output); +template <typename E, typename F> +inline void Deserialize_(internal::Array_Data<F>* data, Array<E>* output); + +namespace internal { + +template <typename E, typename F, bool move_only = IsMoveOnlyType<E>::value> +struct ArraySerializer; + +template <typename E, typename F> struct ArraySerializer<E, F, false> { + MOJO_COMPILE_ASSERT(sizeof(E) == sizeof(F), wrong_array_serializer); + static size_t GetSerializedSize(const Array<E>& input) { + return sizeof(Array_Data<F>) + Align(input.size() * sizeof(E)); + } + static void SerializeElements( + Array<E> input, Buffer* buf, Array_Data<F>* output) { + memcpy(output->storage(), &input.storage()[0], input.size() * sizeof(E)); + } + static void DeserializeElements( + Array_Data<F>* input, Array<E>* output) { + std::vector<E> result(input->size()); + memcpy(&result[0], input->storage(), input->size() * sizeof(E)); + output->Swap(&result); + } +}; + +template <> struct ArraySerializer<bool, bool, false> { + static size_t GetSerializedSize(const Array<bool>& input) { + return sizeof(Array_Data<bool>) + Align((input.size() + 7) / 8); + } + static void SerializeElements( + Array<bool> input, Buffer* buf, Array_Data<bool>* output) { + // TODO(darin): Can this be a memcpy somehow instead of a bit-by-bit copy? + for (size_t i = 0; i < input.size(); ++i) + output->at(i) = input[i]; + } + static void DeserializeElements( + Array_Data<bool>* input, Array<bool>* output) { + Array<bool> result(input->size()); + // TODO(darin): Can this be a memcpy somehow instead of a bit-by-bit copy? + for (size_t i = 0; i < input->size(); ++i) + result.at(i) = input->at(i); + output->Swap(&result); + } +}; + +template <typename H> struct ArraySerializer<ScopedHandleBase<H>, H, true> { + static size_t GetSerializedSize(const Array<ScopedHandleBase<H> >& input) { + return sizeof(Array_Data<H>) + Align(input.size() * sizeof(H)); + } + static void SerializeElements( + Array<ScopedHandleBase<H> > input, + Buffer* buf, + Array_Data<H>* output) { + for (size_t i = 0; i < input.size(); ++i) + output->at(i) = input[i].release(); // Transfer ownership of the handle. + } + static void DeserializeElements( + Array_Data<H>* input, Array<ScopedHandleBase<H> >* output) { + Array<ScopedHandleBase<H> > result(input->size()); + for (size_t i = 0; i < input->size(); ++i) + result.at(i) = MakeScopedHandle(FetchAndReset(&input->at(i))); + output->Swap(&result); + } +}; + +template <typename S> struct ArraySerializer<S, typename S::Data_*, true> { + static size_t GetSerializedSize(const Array<S>& input) { + size_t size = sizeof(Array_Data<typename S::Data_*>) + + input.size() * sizeof(internal::StructPointer<typename S::Data_>); + for (size_t i = 0; i < input.size(); ++i) + size += GetSerializedSize_(input[i]); + return size; + } + static void SerializeElements( + Array<S> input, + Buffer* buf, + Array_Data<typename S::Data_*>* output) { + for (size_t i = 0; i < input.size(); ++i) { + typename S::Data_* element; + Serialize_(input[i].Pass(), buf, &element); + output->at(i) = element; + } + } + static void DeserializeElements( + Array_Data<typename S::Data_*>* input, Array<S>* output) { + Array<S> result(input->size()); + for (size_t i = 0; i < input->size(); ++i) { + S element; + Deserialize_(input->at(i), &element); + result[i] = element.Pass(); + } + output->Swap(&result); + } +}; + +template <> struct ArraySerializer<String, String_Data*, false> { + static size_t GetSerializedSize(const Array<String>& input) { + size_t size = sizeof(Array_Data<String_Data*>) + + input.size() * sizeof(internal::StringPointer); + for (size_t i = 0; i < input.size(); ++i) + size += GetSerializedSize_(input[i]); + return size; + } + static void SerializeElements( + Array<String> input, + Buffer* buf, + Array_Data<String_Data*>* output) { + for (size_t i = 0; i < input.size(); ++i) { + String_Data* element; + Serialize_(input[i], buf, &element); + output->at(i) = element; + } + } + static void DeserializeElements( + Array_Data<String_Data*>* input, Array<String>* output) { + Array<String> result(input->size()); + for (size_t i = 0; i < input->size(); ++i) + Deserialize_(input->at(i), &result[i]); + output->Swap(&result); + } +}; + +} // namespace internal + +template <typename E> +inline size_t GetSerializedSize_(const Array<E>& input) { + if (!input) + return 0; + typedef typename internal::WrapperTraits<E>::DataType F; + return internal::ArraySerializer<E, F>::GetSerializedSize(input); +} + +template <typename E, typename F> +inline void Serialize_(Array<E> input, internal::Buffer* buf, + internal::Array_Data<F>** output) { + if (input) { + internal::Array_Data<F>* result = + internal::Array_Data<F>::New(input.size(), buf); + internal::ArraySerializer<E, F>::SerializeElements( + internal::Forward(input), buf, result); + *output = result; + } else { + *output = NULL; + } +} + +template <typename E, typename F> +inline void Deserialize_(internal::Array_Data<F>* input, + Array<E>* output) { + if (input) { + internal::ArraySerializer<E, F>::DeserializeElements(input, output); + } else { + output->reset(); + } +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/bindings_internal.h b/chromium/mojo/public/cpp/bindings/lib/bindings_internal.h new file mode 100644 index 00000000000..6e87cbd1fb5 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/bindings_internal.h @@ -0,0 +1,86 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +class String; + +namespace internal { +template <typename T> class Array_Data; + +#pragma pack(push, 1) + +struct StructHeader { + uint32_t num_bytes; + uint32_t num_fields; +}; +MOJO_COMPILE_ASSERT(sizeof(StructHeader) == 8, bad_sizeof_StructHeader); + +struct ArrayHeader { + uint32_t num_bytes; + uint32_t num_elements; +}; +MOJO_COMPILE_ASSERT(sizeof(ArrayHeader) == 8, bad_sizeof_ArrayHeader); + +template <typename T> +union StructPointer { + uint64_t offset; + T* ptr; +}; +MOJO_COMPILE_ASSERT(sizeof(StructPointer<char>) == 8, bad_sizeof_StructPointer); + +template <typename T> +union ArrayPointer { + uint64_t offset; + Array_Data<T>* ptr; +}; +MOJO_COMPILE_ASSERT(sizeof(ArrayPointer<char>) == 8, bad_sizeof_ArrayPointer); + +union StringPointer { + uint64_t offset; + Array_Data<char>* ptr; +}; +MOJO_COMPILE_ASSERT(sizeof(StringPointer) == 8, bad_sizeof_StringPointer); + +#pragma pack(pop) + +template <typename T> +void ResetIfNonNull(T* ptr) { + if (ptr) + *ptr = T(); +} + +template <typename T> +T FetchAndReset(T* ptr) { + T temp = *ptr; + *ptr = T(); + return temp; +} + +template <typename H> struct IsHandle { + static const bool value = IsBaseOf<Handle, H>::value; +}; + +template <typename T, bool move_only = IsMoveOnlyType<T>::value> +struct WrapperTraits; + +template <typename T> struct WrapperTraits<T, false> { + typedef T DataType; +}; +template <typename H> struct WrapperTraits<ScopedHandleBase<H>, true> { + typedef H DataType; +}; +template <typename S> struct WrapperTraits<S, true> { + typedef typename S::Data_* DataType; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/bindings_serialization.cc b/chromium/mojo/public/cpp/bindings/lib/bindings_serialization.cc new file mode 100644 index 00000000000..d5f77853684 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/bindings_serialization.cc @@ -0,0 +1,118 @@ +// 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 "mojo/public/cpp/bindings/lib/bindings_serialization.h" + +#include <assert.h> + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +namespace mojo { +namespace internal { + +namespace { + +const size_t kAlignment = 8; + +template<typename T> +T AlignImpl(T t) { + return t + (kAlignment - (t % kAlignment)) % kAlignment; +} + +} // namespace + +size_t Align(size_t size) { + return AlignImpl(size); +} + +char* AlignPointer(char* ptr) { + return reinterpret_cast<char*>(AlignImpl(reinterpret_cast<uintptr_t>(ptr))); +} + +bool IsAligned(const void* ptr) { + return !(reinterpret_cast<uintptr_t>(ptr) % kAlignment); +} + +void EncodePointer(const void* ptr, uint64_t* offset) { + if (!ptr) { + *offset = 0; + return; + } + + const char* p_obj = reinterpret_cast<const char*>(ptr); + const char* p_slot = reinterpret_cast<const char*>(offset); + assert(p_obj > p_slot); + + *offset = static_cast<uint64_t>(p_obj - p_slot); +} + +const void* DecodePointerRaw(const uint64_t* offset) { + if (!*offset) + return NULL; + return reinterpret_cast<const char*>(offset) + *offset; +} + +bool ValidateEncodedPointer(const uint64_t* offset) { + // Cast to uintptr_t so overflow behavior is well defined. + return reinterpret_cast<uintptr_t>(offset) + *offset >= + reinterpret_cast<uintptr_t>(offset); +} + +void EncodeHandle(Handle* handle, std::vector<Handle>* handles) { + if (handle->is_valid()) { + handles->push_back(*handle); + handle->set_value(static_cast<MojoHandle>(handles->size() - 1)); + } else { + handle->set_value(kEncodedInvalidHandleValue); + } +} + +void DecodeHandle(Handle* handle, std::vector<Handle>* handles) { + if (handle->value() == kEncodedInvalidHandleValue) { + *handle = Handle(); + return; + } + assert(handle->value() < handles->size()); + // Just leave holes in the vector so we don't screw up other indices. + *handle = FetchAndReset(&handles->at(handle->value())); +} + +bool ValidateStructHeader(const void* data, + uint32_t min_num_bytes, + uint32_t min_num_fields, + BoundsChecker* bounds_checker) { + assert(min_num_bytes >= sizeof(StructHeader)); + + if (!IsAligned(data)) { + ReportValidationError(VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!bounds_checker->IsValidRange(data, sizeof(StructHeader))) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const StructHeader* header = static_cast<const StructHeader*>(data); + + // TODO(yzshen): Currently our binding code cannot handle structs of smaller + // size or with fewer fields than the version that it sees. That needs to be + // changed in order to provide backward compatibility. + if (header->num_bytes < min_num_bytes || + header->num_fields < min_num_fields) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + + if (!bounds_checker->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + return true; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/bindings_serialization.h b/chromium/mojo/public/cpp/bindings/lib/bindings_serialization.h new file mode 100644 index 00000000000..6bebf90bf95 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/bindings_serialization.h @@ -0,0 +1,83 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_H_ + +#include <vector> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +class BoundsChecker; + +// Please note that this is a different value than |mojo::kInvalidHandleValue|, +// which is the "decoded" invalid handle. +const MojoHandle kEncodedInvalidHandleValue = static_cast<MojoHandle>(-1); + +size_t Align(size_t size); +char* AlignPointer(char* ptr); + +bool IsAligned(const void* ptr); + +// Pointers are encoded as relative offsets. The offsets are relative to the +// address of where the offset value is stored, such that the pointer may be +// recovered with the expression: +// +// ptr = reinterpret_cast<char*>(offset) + *offset +// +// A null pointer is encoded as an offset value of 0. +// +void EncodePointer(const void* ptr, uint64_t* offset); +// Note: This function doesn't validate the encoded pointer value. +const void* DecodePointerRaw(const uint64_t* offset); + +// Note: This function doesn't validate the encoded pointer value. +template <typename T> +inline void DecodePointer(const uint64_t* offset, T** ptr) { + *ptr = reinterpret_cast<T*>(const_cast<void*>(DecodePointerRaw(offset))); +} + +// Checks whether decoding the pointer will overflow and produce a pointer +// smaller than |offset|. +bool ValidateEncodedPointer(const uint64_t* offset); + +// Handles are encoded as indices into a vector of handles. These functions +// manipulate the value of |handle|, mapping it to and from an index. +void EncodeHandle(Handle* handle, std::vector<Handle>* handles); +// Note: This function doesn't validate the encoded handle value. +void DecodeHandle(Handle* handle, std::vector<Handle>* handles); + +// The following 2 functions are used to encode/decode all objects (structs and +// arrays) in a consistent manner. + +template <typename T> +inline void Encode(T* obj, std::vector<Handle>* handles) { + if (obj->ptr) + obj->ptr->EncodePointersAndHandles(handles); + EncodePointer(obj->ptr, &obj->offset); +} + +// Note: This function doesn't validate the encoded pointer and handle values. +template <typename T> +inline void Decode(T* obj, std::vector<Handle>* handles) { + DecodePointer(&obj->offset, &obj->ptr); + if (obj->ptr) + obj->ptr->DecodePointersAndHandles(handles); +} + +// If returns true, this function also claims the memory range of the size +// specified in the struct header, starting from |data|. +// Note: |min_num_bytes| must be no less than sizeof(StructHeader). +bool ValidateStructHeader(const void* data, + uint32_t min_num_bytes, + uint32_t min_num_fields, + BoundsChecker* bounds_checker); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/bounds_checker.cc b/chromium/mojo/public/cpp/bindings/lib/bounds_checker.cc new file mode 100644 index 00000000000..cf1f3238e03 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/bounds_checker.cc @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" + +#include <assert.h> + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +BoundsChecker::BoundsChecker(const void* data, uint32_t data_num_bytes, + size_t num_handles) + : data_begin_(reinterpret_cast<uintptr_t>(data)), + data_end_(data_begin_ + data_num_bytes), + handle_begin_(0), + handle_end_(static_cast<uint32_t>(num_handles)) { + if (data_end_ < data_begin_) { + // The calculation of |data_end_| overflowed. + // It shouldn't happen but if it does, set the range to empty so + // IsValidRange() and ClaimMemory() always fail. + assert(false); // Not reached. + data_end_ = data_begin_; + } + if (handle_end_ < num_handles) { + // Assigning |num_handles| to |handle_end_| overflowed. + // It shouldn't happen but if it does, set the handle index range to empty. + assert(false); // Not reached. + handle_end_ = 0; + } +} + +BoundsChecker::~BoundsChecker() { +} + +bool BoundsChecker::ClaimMemory(const void* position, uint32_t num_bytes) { + uintptr_t begin = reinterpret_cast<uintptr_t>(position); + uintptr_t end = begin + num_bytes; + + if (!InternalIsValidRange(begin, end)) + return false; + + data_begin_ = end; + return true; +} + +bool BoundsChecker::ClaimHandle(const Handle& encoded_handle) { + uint32_t index = encoded_handle.value(); + if (index == kEncodedInvalidHandleValue) + return true; + + if (index < handle_begin_ || index >= handle_end_) + return false; + + // |index| + 1 shouldn't overflow, because |index| is not the max value of + // uint32_t (it is less than |handle_end_|). + handle_begin_ = index + 1; + return true; +} + +bool BoundsChecker::IsValidRange(const void* position, + uint32_t num_bytes) const { + uintptr_t begin = reinterpret_cast<uintptr_t>(position); + uintptr_t end = begin + num_bytes; + + return InternalIsValidRange(begin, end); +} + +bool BoundsChecker::InternalIsValidRange(uintptr_t begin, uintptr_t end) const { + return end > begin && begin >= data_begin_ && end <= data_end_; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/bounds_checker.h b/chromium/mojo/public/cpp/bindings/lib/bounds_checker.h new file mode 100644 index 00000000000..6c472309dde --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/bounds_checker.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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BOUNDS_CHECKER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BOUNDS_CHECKER_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +class Handle; + +namespace internal { + +// BoundsChecker is used to validate object sizes, pointers and handle indices +// for payload of incoming messages. +class BoundsChecker { + public: + // [data, data + data_num_bytes) specifies the initial valid memory range. + // [0, num_handles) specifies the initial valid range of handle indices. + BoundsChecker(const void* data, uint32_t data_num_bytes, + size_t num_handles); + + ~BoundsChecker(); + + // Claims the specified memory range. + // The method succeeds if the range is valid to claim. (Please see + // the comments for IsValidRange().) + // On success, the valid memory range is shrinked to begin right after the end + // of the claimed range. + bool ClaimMemory(const void* position, uint32_t num_bytes); + + // Claims the specified encoded handle (which is basically a handle index). + // The method succeeds if: + // - |encoded_handle|'s value is |kEncodedInvalidHandleValue|. + // - the handle is contained inside the valid range of handle indices. In this + // case, the valid range is shinked to begin right after the claimed handle. + bool ClaimHandle(const Handle& encoded_handle); + + // Returns true if the specified range is not empty, and the range is + // contained inside the valid memory range. + bool IsValidRange(const void* position, uint32_t num_bytes) const; + + private: + bool InternalIsValidRange(uintptr_t begin, uintptr_t end) const; + + // [data_begin_, data_end_) is the valid memory range. + uintptr_t data_begin_; + uintptr_t data_end_; + + // [handle_begin_, handle_end_) is the valid handle index range. + uint32_t handle_begin_; + uint32_t handle_end_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChecker); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BOUNDS_CHECKER_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/buffer.h b/chromium/mojo/public/cpp/bindings/lib/buffer.h new file mode 100644 index 00000000000..c3b570e7767 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/buffer.h @@ -0,0 +1,24 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ + +#include <stddef.h> + +namespace mojo { +namespace internal { + +// Buffer provides a way to allocate memory. Allocations are 8-byte aligned and +// zero-initialized. Allocations remain valid for the lifetime of the Buffer. +class Buffer { + public: + virtual ~Buffer() {} + virtual void* Allocate(size_t num_bytes) = 0; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/callback_internal.h b/chromium/mojo/public/cpp/bindings/lib/callback_internal.h new file mode 100644 index 00000000000..f76ebef59e6 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/callback_internal.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CALLBACK_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CALLBACK_INTERNAL_H_ + +namespace mojo { +class String; + +namespace internal { + +template <typename T> +struct Callback_ParamTraits { + typedef T ForwardType; +}; + +template <> +struct Callback_ParamTraits<String> { + typedef const String& ForwardType; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CALLBACK_INTERNAL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/connector.cc b/chromium/mojo/public/cpp/bindings/lib/connector.cc new file mode 100644 index 00000000000..aa2c4d12334 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/connector.cc @@ -0,0 +1,157 @@ +// 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 "mojo/public/cpp/bindings/lib/connector.h" + +#include <assert.h> +#include <stdlib.h> + +#include "mojo/public/cpp/bindings/error_handler.h" + +namespace mojo { +namespace internal { + +// ---------------------------------------------------------------------------- + +Connector::Connector(ScopedMessagePipeHandle message_pipe, + const MojoAsyncWaiter* waiter) + : error_handler_(NULL), + waiter_(waiter), + message_pipe_(message_pipe.Pass()), + incoming_receiver_(NULL), + async_wait_id_(0), + error_(false), + drop_writes_(false), + enforce_errors_from_incoming_receiver_(true), + destroyed_flag_(NULL) { + // Even though we don't have an incoming receiver, we still want to monitor + // the message pipe to know if is closed or encounters an error. + WaitToReadMore(); +} + +Connector::~Connector() { + if (destroyed_flag_) + *destroyed_flag_ = true; + + if (async_wait_id_) + waiter_->CancelWait(async_wait_id_); +} + +void Connector::CloseMessagePipe() { + Close(message_pipe_.Pass()); +} + +ScopedMessagePipeHandle Connector::PassMessagePipe() { + if (async_wait_id_) { + waiter_->CancelWait(async_wait_id_); + async_wait_id_ = 0; + } + return message_pipe_.Pass(); +} + +bool Connector::Accept(Message* message) { + assert(message_pipe_.is_valid()); + + if (error_) + return false; + + if (drop_writes_) + return true; + + MojoResult rv = WriteMessageRaw( + message_pipe_.get(), + message->data(), + message->data_num_bytes(), + message->mutable_handles()->empty() ? NULL : + reinterpret_cast<const MojoHandle*>( + &message->mutable_handles()->front()), + static_cast<uint32_t>(message->mutable_handles()->size()), + MOJO_WRITE_MESSAGE_FLAG_NONE); + + switch (rv) { + case MOJO_RESULT_OK: + // The handles were successfully transferred, so we don't need the message + // to track their lifetime any longer. + message->mutable_handles()->clear(); + break; + case MOJO_RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any backlog + // of incoming messages before regarding the message pipe as closed. + drop_writes_ = true; + break; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; +} + +// static +void Connector::CallOnHandleReady(void* closure, MojoResult result) { + Connector* self = static_cast<Connector*>(closure); + self->OnHandleReady(result); +} + +void Connector::OnHandleReady(MojoResult result) { + assert(async_wait_id_ != 0); + async_wait_id_ = 0; + + if (result == MOJO_RESULT_OK) { + // Return immediately if |this| was destroyed. Do not touch any members! + if (!ReadMore()) + return; + } else { + error_ = true; + } + + if (error_ && error_handler_) + error_handler_->OnConnectionError(); +} + +void Connector::WaitToReadMore() { + async_wait_id_ = waiter_->AsyncWait(message_pipe_.get().value(), + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + &Connector::CallOnHandleReady, + this); +} + +bool Connector::ReadMore() { + while (true) { + bool receiver_result = false; + + // Detect if |this| was destroyed during message dispatch. Allow for the + // possibility of re-entering ReadMore() through message dispatch. + bool was_destroyed_during_dispatch = false; + bool* previous_destroyed_flag = destroyed_flag_; + destroyed_flag_ = &was_destroyed_during_dispatch; + + MojoResult rv = ReadAndDispatchMessage( + message_pipe_.get(), incoming_receiver_, &receiver_result); + + if (was_destroyed_during_dispatch) { + if (previous_destroyed_flag) + *previous_destroyed_flag = true; // Propagate flag. + return false; + } + destroyed_flag_ = previous_destroyed_flag; + + if (rv == MOJO_RESULT_SHOULD_WAIT) { + WaitToReadMore(); + break; + } + if (rv != MOJO_RESULT_OK || + (enforce_errors_from_incoming_receiver_ && !receiver_result)) { + error_ = true; + break; + } + } + return true; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/connector.h b/chromium/mojo/public/cpp/bindings/lib/connector.h new file mode 100644 index 00000000000..30cd65f4c42 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/connector.h @@ -0,0 +1,100 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_CONNECTOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONNECTOR_H_ + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/bindings/lib/message_queue.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +class ErrorHandler; + +namespace internal { + +// The Connector class is responsible for performing read/write operations on a +// MessagePipe. It writes messages it receives through the MessageReceiver +// interface that it subclasses, and it forwards messages it reads through the +// MessageReceiver interface assigned as its incoming receiver. +// +// NOTE: MessagePipe I/O is non-blocking. +// +class Connector : public MessageReceiver { + public: + // The Connector takes ownership of |message_pipe|. + explicit Connector( + ScopedMessagePipeHandle message_pipe, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()); + virtual ~Connector(); + + // Sets the receiver to handle messages read from the message pipe. The + // Connector will read messages from the pipe regardless of whether or not an + // incoming receiver has been set. + void set_incoming_receiver(MessageReceiver* receiver) { + incoming_receiver_ = receiver; + } + + // Errors from incoming receivers will force the connector into an error + // state, where no more messages will be processed. This method is used + // during testing to prevent that from happening. + void set_enforce_errors_from_incoming_receiver(bool enforce) { + enforce_errors_from_incoming_receiver_ = enforce; + } + + // Sets the error handler to receive notifications when an error is + // encountered while reading from the pipe or waiting to read from the pipe. + void set_error_handler(ErrorHandler* error_handler) { + error_handler_ = error_handler; + } + + // Returns true if an error was encountered while reading from the pipe or + // waiting to read from the pipe. + bool encountered_error() const { return error_; } + + // Closes the pipe, triggering the error state. Connector is put into a + // quiescent state. + void CloseMessagePipe(); + + // Releases the pipe, not triggering the error state. Connector is put into + // a quiescent state. + ScopedMessagePipeHandle PassMessagePipe(); + + // MessageReceiver implementation: + virtual bool Accept(Message* message) MOJO_OVERRIDE; + + private: + static void CallOnHandleReady(void* closure, MojoResult result); + void OnHandleReady(MojoResult result); + + void WaitToReadMore(); + + // Returns false if |this| was destroyed during message dispatch. + MOJO_WARN_UNUSED_RESULT bool ReadMore(); + + ErrorHandler* error_handler_; + const MojoAsyncWaiter* waiter_; + + ScopedMessagePipeHandle message_pipe_; + MessageReceiver* incoming_receiver_; + + MojoAsyncWaitID async_wait_id_; + bool error_; + bool drop_writes_; + bool enforce_errors_from_incoming_receiver_; + + // If non-null, this will be set to true when the Connector is destroyed. We + // use this flag to allow for the Connector to be destroyed as a side-effect + // of dispatching an incoming message. + bool* destroyed_flag_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Connector); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONNECTOR_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/filter_chain.cc b/chromium/mojo/public/cpp/bindings/lib/filter_chain.cc new file mode 100644 index 00000000000..efd4ba19875 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/filter_chain.cc @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/filter_chain.h" + +#include <assert.h> + +#include <algorithm> + +namespace mojo { +namespace internal { + +FilterChain::FilterChain(MessageReceiver* sink) : sink_(sink) { +} + +FilterChain::FilterChain(RValue other) : sink_(other.object->sink_) { + other.object->sink_ = NULL; + filters_.swap(other.object->filters_); +} + +FilterChain& FilterChain::operator=(RValue other) { + std::swap(sink_, other.object->sink_); + filters_.swap(other.object->filters_); + return *this; +} + +FilterChain::~FilterChain() { + for (std::vector<MessageFilter*>::iterator iter = filters_.begin(); + iter != filters_.end(); + ++iter) { + delete *iter; + } +} + +void FilterChain::SetSink(MessageReceiver* sink) { + assert(!sink_); + sink_ = sink; + if (!filters_.empty()) + filters_.back()->set_sink(sink); +} + +MessageReceiver* FilterChain::GetHead() { + assert(sink_); + return filters_.empty() ? sink_ : filters_.front(); +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/filter_chain.h b/chromium/mojo/public/cpp/bindings/lib/filter_chain.h new file mode 100644 index 00000000000..8680f41aaee --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/filter_chain.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_FILTER_CHAIN_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FILTER_CHAIN_H_ + +#include <vector> + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_filter.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +class FilterChain { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(FilterChain, RValue) + + public: + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + explicit FilterChain(MessageReceiver* sink = NULL); + + // Move-only constructor and operator=. + FilterChain(RValue other); + FilterChain& operator=(RValue other); + + ~FilterChain(); + + template <typename FilterType> + inline void Append(); + + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + void SetSink(MessageReceiver* sink); + + // Returns a receiver to accept messages. Messages flow through all filters in + // the same order as they were appended to the chain. If all filters allow a + // message to pass, it will be forwarded to |sink_|. + // The returned value is invalidated when this object goes away. + MessageReceiver* GetHead(); + + private: + // Owned by this object. + std::vector<MessageFilter*> filters_; + + MessageReceiver* sink_; +}; + +template <typename FilterType> +inline void FilterChain::Append() { + FilterType* filter = new FilterType(sink_); + if (!filters_.empty()) + filters_.back()->set_sink(filter); + filters_.push_back(filter); +} + +template <> +inline void FilterChain::Append<PassThroughFilter>() { +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FILTER_CHAIN_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/chromium/mojo/public/cpp/bindings/lib/fixed_buffer.cc new file mode 100644 index 00000000000..5226231725c --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/fixed_buffer.cc @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <algorithm> + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" + +namespace mojo { +namespace internal { + +FixedBuffer::FixedBuffer(size_t size) + : ptr_(NULL), + cursor_(0), + size_(internal::Align(size)) { + // calloc() required to zero memory and thus avoid info leaks. + ptr_ = static_cast<char*>(calloc(size_, 1)); +} + +FixedBuffer::~FixedBuffer() { + free(ptr_); +} + +void* FixedBuffer::Allocate(size_t delta) { + delta = internal::Align(delta); + + if (delta == 0 || delta > size_ - cursor_) { + assert(false); + return NULL; + } + + char* result = ptr_ + cursor_; + cursor_ += delta; + + return result; +} + +void* FixedBuffer::Leak() { + char* ptr = ptr_; + ptr_ = NULL; + cursor_ = 0; + size_ = 0; + return ptr; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/fixed_buffer.h b/chromium/mojo/public/cpp/bindings/lib/fixed_buffer.h new file mode 100644 index 00000000000..a3515a2ae86 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/fixed_buffer.h @@ -0,0 +1,67 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ + +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// FixedBuffer provides a simple way to allocate objects within a fixed chunk +// of memory. Objects are allocated by calling the |Allocate| method, which +// extends the buffer accordingly. Objects allocated in this way are not freed +// explicitly. Instead, they remain valid so long as the FixedBuffer remains +// valid. The Leak method may be used to steal the underlying memory from the +// FixedBuffer. +// +// Typical usage: +// +// { +// FixedBuffer buf(8 + 8); +// +// int* a = static_cast<int*>(buf->Allocate(sizeof(int))); +// *a = 2; +// +// double* b = static_cast<double*>(buf->Allocate(sizeof(double))); +// *b = 3.14f; +// +// void* data = buf.Leak(); +// Process(data); +// +// free(data); +// } +// +class FixedBuffer : public Buffer { + public: + explicit FixedBuffer(size_t size); + virtual ~FixedBuffer(); + + // Grows the buffer by |num_bytes| and returns a pointer to the start of the + // addition. The resulting address is 8-byte aligned, and the content of the + // memory is zero-filled. + virtual void* Allocate(size_t num_bytes) MOJO_OVERRIDE; + + size_t size() const { return size_; } + + // Returns the internal memory owned by the Buffer to the caller. The Buffer + // relinquishes its pointer, effectively resetting the state of the Buffer + // and leaving the caller responsible for freeing the returned memory address + // when no longer needed. + void* Leak(); + + private: + char* ptr_; + size_t cursor_; + size_t size_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(FixedBuffer); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/interface_impl_internal.h b/chromium/mojo/public/cpp/bindings/lib/interface_impl_internal.h new file mode 100644 index 00000000000..f2cc4f0c5b6 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/interface_impl_internal.h @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_IMPL_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_IMPL_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +template <typename Interface> +class InterfaceImplBase : public Interface { + public: + virtual ~InterfaceImplBase() {} + virtual void OnConnectionEstablished() = 0; + virtual void OnConnectionError() = 0; +}; + +template <typename Interface> +class InterfaceImplState : public ErrorHandler { + public: + typedef typename Interface::Client Client; + + explicit InterfaceImplState(InterfaceImplBase<Interface>* instance) + : router_(NULL), + proxy_(NULL) { + assert(instance); + stub_.set_sink(instance); + } + + virtual ~InterfaceImplState() { + delete proxy_; + if (router_) { + router_->set_error_handler(NULL); + delete router_; + } + } + + void BindProxy( + InterfacePtr<Interface>* ptr, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + MessagePipe pipe; + ptr->Bind(pipe.handle0.Pass(), waiter); + Bind(pipe.handle1.Pass(), waiter); + } + + void Bind(ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter) { + assert(!router_); + + FilterChain filters; + filters.Append<MessageHeaderValidator>(); + filters.Append<typename Interface::RequestValidator_>(); + filters.Append<typename Interface::Client::ResponseValidator_>(); + + router_ = new Router(handle.Pass(), filters.Pass(), waiter); + router_->set_incoming_receiver(&stub_); + router_->set_error_handler(this); + + proxy_ = new typename Client::Proxy_(router_); + + instance()->OnConnectionEstablished(); + } + + Router* router() { return router_; } + Client* client() { return proxy_; } + + private: + InterfaceImplBase<Interface>* instance() { + return static_cast<InterfaceImplBase<Interface>*>(stub_.sink()); + } + + virtual void OnConnectionError() MOJO_OVERRIDE { + instance()->OnConnectionError(); + } + + Router* router_; + typename Client::Proxy_* proxy_; + typename Interface::Stub_ stub_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(InterfaceImplState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_IMPL_INTERNAL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_internal.h b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_internal.h new file mode 100644 index 00000000000..a318d6185c5 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_internal.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_ + +#include <assert.h> +#include <stdio.h> + +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { +namespace internal { + +template <typename Interface> +class InterfacePtrState { + public: + InterfacePtrState() : proxy_(NULL), router_(NULL) {} + + ~InterfacePtrState() { + // Destruction order matters here. We delete |proxy_| first, even though + // |router_| may have a reference to it, so that |~Interface| may have a + // shot at generating new outbound messages (ie, invoking client methods). + delete proxy_; + delete router_; + } + + Interface* instance() const { return proxy_; } + + Router* router() const { return router_; } + + void Swap(InterfacePtrState* other) { + std::swap(other->proxy_, proxy_); + std::swap(other->router_, router_); + } + + void ConfigureProxy( + ScopedMessagePipeHandle handle, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) { + assert(!proxy_); + assert(!router_); + + FilterChain filters; + filters.Append<MessageHeaderValidator>(); + filters.Append<typename Interface::Client::RequestValidator_>(); + filters.Append<typename Interface::ResponseValidator_>(); + + router_ = new Router(handle.Pass(), filters.Pass(), waiter); + ProxyWithStub* proxy = new ProxyWithStub(router_); + router_->set_incoming_receiver(&proxy->stub); + + proxy_ = proxy; + } + + void set_client(typename Interface::Client* client) { + assert(proxy_); + proxy_->stub.set_sink(client); + } + + private: + class ProxyWithStub : public Interface::Proxy_ { + public: + explicit ProxyWithStub(MessageReceiverWithResponder* receiver) + : Interface::Proxy_(receiver) { + } + typename Interface::Client::Stub_ stub; + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(ProxyWithStub); + }; + + ProxyWithStub* proxy_; + Router* router_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(InterfacePtrState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/message.cc b/chromium/mojo/public/cpp/bindings/lib/message.cc new file mode 100644 index 00000000000..78017c00793 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message.cc @@ -0,0 +1,81 @@ +// 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 "mojo/public/cpp/bindings/message.h" + +#include <assert.h> +#include <stdlib.h> + +#include <algorithm> + +namespace mojo { + +Message::Message() + : data_num_bytes_(0), + data_(NULL) { +} + +Message::~Message() { + free(data_); + + for (std::vector<Handle>::iterator it = handles_.begin(); + it != handles_.end(); ++it) { + if (it->is_valid()) + CloseRaw(*it); + } +} + +void Message::AllocUninitializedData(uint32_t num_bytes) { + assert(!data_); + data_num_bytes_ = num_bytes; + data_ = static_cast<internal::MessageData*>(malloc(num_bytes)); +} + +void Message::AdoptData(uint32_t num_bytes, internal::MessageData* data) { + assert(!data_); + data_num_bytes_ = num_bytes; + data_ = data; +} + +void Message::Swap(Message* other) { + std::swap(data_num_bytes_, other->data_num_bytes_); + std::swap(data_, other->data_); + std::swap(handles_, other->handles_); +} + +MojoResult ReadAndDispatchMessage(MessagePipeHandle handle, + MessageReceiver* receiver, + bool* receiver_result) { + MojoResult rv; + + uint32_t num_bytes = 0, num_handles = 0; + rv = ReadMessageRaw(handle, + NULL, + &num_bytes, + NULL, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv != MOJO_RESULT_RESOURCE_EXHAUSTED) + return rv; + + Message message; + message.AllocUninitializedData(num_bytes); + message.mutable_handles()->resize(num_handles); + + rv = ReadMessageRaw(handle, + message.mutable_data(), + &num_bytes, + message.mutable_handles()->empty() + ? NULL + : reinterpret_cast<MojoHandle*>( + &message.mutable_handles()->front()), + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (receiver && rv == MOJO_RESULT_OK) + *receiver_result = receiver->Accept(&message); + + return rv; +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/message_builder.cc b/chromium/mojo/public/cpp/bindings/lib/message_builder.cc new file mode 100644 index 00000000000..c7466444d04 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_builder.cc @@ -0,0 +1,52 @@ +// 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 "mojo/public/cpp/bindings/lib/message_builder.h" + +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +template <typename Header> +void Allocate(Buffer* buf, Header** header) { + *header = static_cast<Header*>(buf->Allocate(sizeof(Header))); + (*header)->num_bytes = sizeof(Header); +} + +MessageBuilder::MessageBuilder(uint32_t name, size_t payload_size) + : buf_(sizeof(MessageHeader) + payload_size) { + MessageHeader* header; + Allocate(&buf_, &header); + header->num_fields = 2; + header->name = name; +} + +MessageBuilder::~MessageBuilder() { +} + +void MessageBuilder::Finish(Message* message) { + uint32_t num_bytes = static_cast<uint32_t>(buf_.size()); + message->AdoptData(num_bytes, static_cast<MessageData*>(buf_.Leak())); +} + +MessageBuilder::MessageBuilder(size_t size) + : buf_(size) { +} + +MessageWithRequestIDBuilder::MessageWithRequestIDBuilder(uint32_t name, + size_t payload_size, + uint32_t flags, + uint64_t request_id) + : MessageBuilder(sizeof(MessageHeaderWithRequestID) + payload_size) { + MessageHeaderWithRequestID* header; + Allocate(&buf_, &header); + header->num_fields = 3; + header->name = name; + header->flags = flags; + header->request_id = request_id; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/message_builder.h b/chromium/mojo/public/cpp/bindings/lib/message_builder.h new file mode 100644 index 00000000000..b4988ff9cb9 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_builder.h @@ -0,0 +1,63 @@ +// 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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" + +namespace mojo { +class Message; + +namespace internal { + +class MessageBuilder { + public: + MessageBuilder(uint32_t name, size_t payload_size); + ~MessageBuilder(); + + Buffer* buffer() { return &buf_; } + + // Call Finish when done making allocations in |buffer()|. Upon return, + // |message| will contain the message data, and |buffer()| will no longer be + // valid to reference. + void Finish(Message* message); + + protected: + explicit MessageBuilder(size_t size); + FixedBuffer buf_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(MessageBuilder); +}; + +class MessageWithRequestIDBuilder : public MessageBuilder { + public: + MessageWithRequestIDBuilder(uint32_t name, size_t payload_size, + uint32_t flags, uint64_t request_id); +}; + +class RequestMessageBuilder : public MessageWithRequestIDBuilder { + public: + RequestMessageBuilder(uint32_t name, size_t payload_size) + : MessageWithRequestIDBuilder(name, payload_size, kMessageExpectsResponse, + 0) { + } +}; + +class ResponseMessageBuilder : public MessageWithRequestIDBuilder { + public: + ResponseMessageBuilder(uint32_t name, size_t payload_size, + uint64_t request_id) + : MessageWithRequestIDBuilder(name, payload_size, kMessageIsResponse, + request_id) { + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/message_filter.cc b/chromium/mojo/public/cpp/bindings/lib/message_filter.cc new file mode 100644 index 00000000000..b09f40d8c5e --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_filter.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/message_filter.h" + +namespace mojo { + +MessageFilter::MessageFilter(MessageReceiver* sink) : sink_(sink) { +} + +MessageFilter::~MessageFilter() { +} + +PassThroughFilter::PassThroughFilter(MessageReceiver* sink) + : MessageFilter(sink) { +} + +bool PassThroughFilter::Accept(Message* message) { + return sink_->Accept(message); +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc b/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc new file mode 100644 index 00000000000..a55917afde5 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_header_validator.cc @@ -0,0 +1,81 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" + +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +namespace mojo { +namespace internal { +namespace { + +bool IsValidMessageHeader(const MessageHeader* header) { + // NOTE: Our goal is to preserve support for future extension of the message + // header. If we encounter fields we do not understand, we must ignore them. + + // Extra validation of the struct header: + if (header->num_fields == 2) { + if (header->num_bytes != sizeof(MessageHeader)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } else if (header->num_fields == 3) { + if (header->num_bytes != sizeof(MessageHeaderWithRequestID)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } else if (header->num_fields > 3) { + if (header->num_bytes < sizeof(MessageHeaderWithRequestID)) { + ReportValidationError(VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } + + // Validate flags (allow unknown bits): + + // These flags require a RequestID. + if (header->num_fields < 3 && + ((header->flags & kMessageExpectsResponse) || + (header->flags & kMessageIsResponse))) { + ReportValidationError(VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID); + return false; + } + + // These flags are mutually exclusive. + if ((header->flags & kMessageExpectsResponse) && + (header->flags & kMessageIsResponse)) { + ReportValidationError( + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION); + return false; + } + + return true; +} + +} // namespace + +MessageHeaderValidator::MessageHeaderValidator(MessageReceiver* sink) + : MessageFilter(sink) { +} + +bool MessageHeaderValidator::Accept(Message* message) { + // Pass 0 as number of handles because we don't expect any in the header, even + // if |message| contains handles. + BoundsChecker bounds_checker(message->data(), message->data_num_bytes(), 0); + + if (!ValidateStructHeader(message->data(), sizeof(MessageHeader), 2, + &bounds_checker)) { + return false; + } + + if (!IsValidMessageHeader(message->header())) + return false; + + return sink_->Accept(message); +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/message_header_validator.h b/chromium/mojo/public/cpp/bindings/lib/message_header_validator.h new file mode 100644 index 00000000000..c3f43a07fbc --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_header_validator.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_ + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_filter.h" + +namespace mojo { +namespace internal { + +class MessageHeaderValidator : public MessageFilter { + public: + explicit MessageHeaderValidator(MessageReceiver* sink = NULL); + + virtual bool Accept(Message* message) MOJO_OVERRIDE; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/message_internal.h b/chromium/mojo/public/cpp/bindings/lib/message_internal.h new file mode 100644 index 00000000000..3c67902b7a8 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_internal.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +namespace mojo { +namespace internal { + +#pragma pack(push, 1) + +enum { + kMessageExpectsResponse = 1 << 0, + kMessageIsResponse = 1 << 1 +}; + +struct MessageHeader : internal::StructHeader { + uint32_t name; + uint32_t flags; +}; +MOJO_COMPILE_ASSERT(sizeof(MessageHeader) == 16, bad_sizeof_MessageHeader); + +struct MessageHeaderWithRequestID : MessageHeader { + uint64_t request_id; +}; +MOJO_COMPILE_ASSERT(sizeof(MessageHeaderWithRequestID) == 24, + bad_sizeof_MessageHeaderWithRequestID); + +struct MessageData { + MessageHeader header; +}; + +MOJO_COMPILE_ASSERT(sizeof(MessageData) == sizeof(MessageHeader), + bad_sizeof_MessageData); + +#pragma pack(pop) + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/message_queue.cc b/chromium/mojo/public/cpp/bindings/lib/message_queue.cc new file mode 100644 index 00000000000..1982ccb14c2 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_queue.cc @@ -0,0 +1,50 @@ +// 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 "mojo/public/cpp/bindings/lib/message_queue.h" + +#include <assert.h> +#include <stddef.h> + +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +MessageQueue::MessageQueue() { +} + +MessageQueue::~MessageQueue() { + while (!queue_.empty()) + Pop(); +} + +bool MessageQueue::IsEmpty() const { + return queue_.empty(); +} + +Message* MessageQueue::Peek() { + assert(!queue_.empty()); + return queue_.front(); +} + +void MessageQueue::Push(Message* message) { + queue_.push(new Message()); + queue_.back()->Swap(message); +} + +void MessageQueue::Pop(Message* message) { + assert(!queue_.empty()); + queue_.front()->Swap(message); + Pop(); +} + +void MessageQueue::Pop() { + assert(!queue_.empty()); + delete queue_.front(); + queue_.pop(); +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/message_queue.h b/chromium/mojo/public/cpp/bindings/lib/message_queue.h new file mode 100644 index 00000000000..4e46b54a250 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/message_queue.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_QUEUE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_QUEUE_H_ + +#include <queue> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +class Message; + +namespace internal { + +// A queue for Message objects. +class MessageQueue { + public: + MessageQueue(); + ~MessageQueue(); + + bool IsEmpty() const; + Message* Peek(); + + // This method transfers ownership of |message->data| and |message->handles| + // to the message queue, resetting |message| in the process. + void Push(Message* message); + + // Removes the next message from the queue, transferring ownership of its + // data and handles to the given |message|. + void Pop(Message* message); + + // Removes the next message from the queue, discarding its data and handles. + // This is meant to be used in conjunction with |Peek|. + void Pop(); + + private: + std::queue<Message*> queue_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_QUEUE_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/no_interface.cc b/chromium/mojo/public/cpp/bindings/lib/no_interface.cc new file mode 100644 index 00000000000..9e0945c83b3 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/no_interface.cc @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/no_interface.h" + +namespace mojo { + +const char* NoInterface::Name_ = "mojo::NoInterface"; + +bool NoInterfaceStub::Accept(Message* message) { + return false; +} + +bool NoInterfaceStub::AcceptWithResponder(Message* message, + MessageReceiver* responder) { + return false; +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/router.cc b/chromium/mojo/public/cpp/bindings/lib/router.cc new file mode 100644 index 00000000000..a8222203c47 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/router.cc @@ -0,0 +1,140 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/router.h" + +namespace mojo { +namespace internal { + +// ---------------------------------------------------------------------------- + +class ResponderThunk : public MessageReceiver { + public: + explicit ResponderThunk(const SharedData<Router*>& router) + : router_(router) { + } + virtual ~ResponderThunk() { + } + + // MessageReceiver implementation: + virtual bool Accept(Message* message) MOJO_OVERRIDE { + assert(message->has_flag(kMessageIsResponse)); + + bool result = false; + + Router* router = router_.value(); + if (router) + result = router->Accept(message); + + return result; + } + + private: + SharedData<Router*> router_; +}; + +// ---------------------------------------------------------------------------- + +Router::HandleIncomingMessageThunk::HandleIncomingMessageThunk(Router* router) + : router_(router) { +} + +Router::HandleIncomingMessageThunk::~HandleIncomingMessageThunk() { +} + +bool Router::HandleIncomingMessageThunk::Accept(Message* message) { + return router_->HandleIncomingMessage(message); +} + +// ---------------------------------------------------------------------------- + +Router::Router(ScopedMessagePipeHandle message_pipe, + FilterChain filters, + const MojoAsyncWaiter* waiter) + : thunk_(this), + filters_(filters.Pass()), + connector_(message_pipe.Pass(), waiter), + weak_self_(this), + incoming_receiver_(NULL), + next_request_id_(0), + testing_mode_(false) { + filters_.SetSink(&thunk_); + connector_.set_incoming_receiver(filters_.GetHead()); +} + +Router::~Router() { + weak_self_.set_value(NULL); + + for (ResponderMap::const_iterator i = responders_.begin(); + i != responders_.end(); ++i) { + delete i->second; + } +} + +bool Router::Accept(Message* message) { + assert(!message->has_flag(kMessageExpectsResponse)); + return connector_.Accept(message); +} + +bool Router::AcceptWithResponder(Message* message, + MessageReceiver* responder) { + assert(message->has_flag(kMessageExpectsResponse)); + + // Reserve 0 in case we want it to convey special meaning in the future. + uint64_t request_id = next_request_id_++; + if (request_id == 0) + request_id = next_request_id_++; + + message->set_request_id(request_id); + if (!connector_.Accept(message)) + return false; + + // We assume ownership of |responder|. + responders_[request_id] = responder; + return true; +} + +void Router::EnableTestingMode() { + testing_mode_ = true; + connector_.set_enforce_errors_from_incoming_receiver(false); +} + +bool Router::HandleIncomingMessage(Message* message) { + if (message->has_flag(kMessageExpectsResponse)) { + if (incoming_receiver_) { + MessageReceiver* responder = new ResponderThunk(weak_self_); + bool ok = incoming_receiver_->AcceptWithResponder(message, responder); + if (!ok) + delete responder; + return ok; + } + + // If we receive a request expecting a response when the client is not + // listening, then we have no choice but to tear down the pipe. + connector_.CloseMessagePipe(); + } else if (message->has_flag(kMessageIsResponse)) { + uint64_t request_id = message->request_id(); + ResponderMap::iterator it = responders_.find(request_id); + if (it == responders_.end()) { + assert(testing_mode_); + return false; + } + MessageReceiver* responder = it->second; + responders_.erase(it); + bool ok = responder->Accept(message); + delete responder; + return ok; + } else { + if (incoming_receiver_) + return incoming_receiver_->Accept(message); + // OK to drop message on the floor. + } + + return false; +} + +// ---------------------------------------------------------------------------- + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/router.h b/chromium/mojo/public/cpp/bindings/lib/router.h new file mode 100644 index 00000000000..76bf1471e2e --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/router.h @@ -0,0 +1,91 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ROUTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ROUTER_H_ + +#include <map> + +#include "mojo/public/cpp/bindings/lib/connector.h" +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/shared_data.h" +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { +namespace internal { + +class Router : public MessageReceiverWithResponder { + public: + Router(ScopedMessagePipeHandle message_pipe, + FilterChain filters, + const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()); + virtual ~Router(); + + // Sets the receiver to handle messages read from the message pipe that do + // not have the kMessageIsResponse flag set. + void set_incoming_receiver(MessageReceiverWithResponder* receiver) { + incoming_receiver_ = receiver; + } + + // Sets the error handler to receive notifications when an error is + // encountered while reading from the pipe or waiting to read from the pipe. + void set_error_handler(ErrorHandler* error_handler) { + connector_.set_error_handler(error_handler); + } + + // Returns true if an error was encountered while reading from the pipe or + // waiting to read from the pipe. + bool encountered_error() const { return connector_.encountered_error(); } + + void CloseMessagePipe() { + connector_.CloseMessagePipe(); + } + + ScopedMessagePipeHandle PassMessagePipe() { + return connector_.PassMessagePipe(); + } + + // MessageReceiver implementation: + virtual bool Accept(Message* message) MOJO_OVERRIDE; + virtual bool AcceptWithResponder(Message* message, MessageReceiver* responder) + MOJO_OVERRIDE; + + // Sets this object to testing mode. + // In testing mode: + // - the object is more tolerant of unrecognized response messages; + // - the connector continues working after seeing errors from its incoming + // receiver. + void EnableTestingMode(); + + private: + typedef std::map<uint64_t, MessageReceiver*> ResponderMap; + + class HandleIncomingMessageThunk : public MessageReceiver { + public: + HandleIncomingMessageThunk(Router* router); + virtual ~HandleIncomingMessageThunk(); + + // MessageReceiver implementation: + virtual bool Accept(Message* message) MOJO_OVERRIDE; + + private: + Router* router_; + }; + + bool HandleIncomingMessage(Message* message); + + HandleIncomingMessageThunk thunk_; + FilterChain filters_; + Connector connector_; + SharedData<Router*> weak_self_; + MessageReceiverWithResponder* incoming_receiver_; + ResponderMap responders_; + uint64_t next_request_id_; + bool testing_mode_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ROUTER_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/shared_data.h b/chromium/mojo/public/cpp/bindings/lib/shared_data.h new file mode 100644 index 00000000000..c7bd54f8d7c --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/shared_data.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_DATA_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_DATA_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// Used to allocate an instance of T that can be shared via reference counting. +template <typename T> +class SharedData { + public: + ~SharedData() { + holder_->Release(); + } + + SharedData() : holder_(new Holder()) { + } + + explicit SharedData(const T& value) : holder_(new Holder(value)) { + } + + SharedData(const SharedData<T>& other) : holder_(other.holder_) { + holder_->Retain(); + } + + SharedData<T>& operator=(const SharedData<T>& other) { + if (other.holder_ == holder_) + return *this; + holder_->Release(); + holder_ = other.holder_; + holder_->Retain(); + return *this; + } + + void reset() { + holder_->Release(); + holder_ = new Holder(); + } + + void reset(const T& value) { + holder_->Release(); + holder_ = new Holder(value); + } + + void set_value(const T& value) { + holder_->value = value; + } + T* mutable_value() { + return &holder_->value; + } + const T& value() const { + return holder_->value; + } + + private: + class Holder { + public: + Holder() : value(), ref_count_(1) { + } + Holder(const T& value) : value(value), ref_count_(1) { + } + + void Retain() { ++ref_count_; } + void Release() { if (--ref_count_ == 0) delete this; } + + T value; + + private: + int ref_count_; + MOJO_DISALLOW_COPY_AND_ASSIGN(Holder); + }; + + Holder* holder_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_DATA_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/shared_ptr.h b/chromium/mojo/public/cpp/bindings/lib/shared_ptr.h new file mode 100644 index 00000000000..6ae53a5681a --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/shared_ptr.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_PTR_H_ + +#include "mojo/public/cpp/bindings/lib/shared_data.h" + +namespace mojo { +namespace internal { + +// Used to manage a heap-allocated instance of P that can be shared via +// reference counting. When the last reference is dropped, the instance is +// deleted. +template <typename P> +class SharedPtr { + public: + SharedPtr() {} + + explicit SharedPtr(P* ptr) { + impl_.mutable_value()->ptr = ptr; + } + + // Default copy-constructor and assignment operator are OK. + + P* get() { + return impl_.value().ptr; + } + const P* get() const { + return impl_.value().ptr; + } + + P* operator->() { return get(); } + const P* operator->() const { return get(); } + + private: + class Impl { + public: + ~Impl() { + if (ptr) + delete ptr; + } + + Impl() : ptr(NULL) { + } + + Impl(P* ptr) : ptr(ptr) { + } + + P* ptr; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(Impl); + }; + + SharedData<Impl> impl_; +}; + +} // namespace mojo +} // namespace internal + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SHARED_PTR_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/string_serialization.cc b/chromium/mojo/public/cpp/bindings/lib/string_serialization.cc new file mode 100644 index 00000000000..0044b1b9cff --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/string_serialization.cc @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/string_serialization.h" + +#include <string.h> + +namespace mojo { + +size_t GetSerializedSize_(const String& input) { + if (!input) + return 0; + return internal::Align(sizeof(internal::String_Data) + input.size()); +} + +void Serialize_(const String& input, internal::Buffer* buf, + internal::String_Data** output) { + if (input) { + internal::String_Data* result = + internal::String_Data::New(input.size(), buf); + memcpy(result->storage(), input.data(), input.size()); + *output = result; + } else { + *output = NULL; + } +} + +void Deserialize_(internal::String_Data* input, String* output) { + if (input) { + String result(input->storage(), input->size()); + result.Swap(output); + } else { + output->reset(); + } +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/string_serialization.h b/chromium/mojo/public/cpp/bindings/lib/string_serialization.h new file mode 100644 index 00000000000..bad2a0c2766 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/string_serialization.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/string.h" + +namespace mojo { + +size_t GetSerializedSize_(const String& input); +void Serialize_(const String& input, internal::Buffer* buffer, + internal::String_Data** output); +void Deserialize_(internal::String_Data* input, String* output); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/sync_dispatcher.cc b/chromium/mojo/public/cpp/bindings/lib/sync_dispatcher.cc new file mode 100644 index 00000000000..2cde97f6ee4 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/sync_dispatcher.cc @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/sync_dispatcher.h" + +#include <stdlib.h> + +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +bool WaitForMessageAndDispatch(MessagePipeHandle handle, + MessageReceiver* receiver) { + while (true) { + bool result; + MojoResult rv = ReadAndDispatchMessage(handle, receiver, &result); + if (rv == MOJO_RESULT_OK) + return result; + if (rv == MOJO_RESULT_SHOULD_WAIT) + rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE); + if (rv != MOJO_RESULT_OK) + return false; + } +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/template_util.h b/chromium/mojo/public/cpp/bindings/lib/template_util.h new file mode 100644 index 00000000000..599126691ed --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/template_util.h @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ + +namespace mojo { +namespace internal { + +template<class T, T v> +struct IntegralConstant { + static const T value = v; +}; + +template <class T, T v> const T IntegralConstant<T, v>::value; + +typedef IntegralConstant<bool, true> TrueType; +typedef IntegralConstant<bool, false> FalseType; + +template <class T> struct IsConst : FalseType {}; +template <class T> struct IsConst<const T> : TrueType {}; + +template<bool B, typename T = void> +struct EnableIf {}; + +template<typename T> +struct EnableIf<true, T> { typedef T type; }; + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// A helper template to determine if given type is non-const move-only-type, +// i.e. if a value of the given type should be passed via .Pass() in a +// destructive way. +template <typename T> struct IsMoveOnlyType { + template <typename U> + static YesType Test(const typename U::MoveOnlyTypeForCPP03*); + + template <typename U> + static NoType Test(...); + + static const bool value = sizeof(Test<T>(0)) == sizeof(YesType) && + !IsConst<T>::value; +}; + +template <typename T> +typename EnableIf<!IsMoveOnlyType<T>::value, T>::type& Forward(T& t) { + return t; +} + +template <typename T> +typename EnableIf<IsMoveOnlyType<T>::value, T>::type Forward(T& t) { + return t.Pass(); +} + +// This goop is a trick used to implement a template that can be used to +// determine if a given class is the base class of another given class. +template<typename, typename> struct IsSame { + static bool const value = false; +}; +template<typename A> struct IsSame<A, A> { + static bool const value = true; +}; +template<typename Base, typename Derived> struct IsBaseOf { + private: + // This class doesn't work correctly with forward declarations. + // Because sizeof cannot be applied to incomplete types, this line prevents us + // from passing in forward declarations. + typedef char (*EnsureTypesAreComplete)[sizeof(Base) + sizeof(Derived)]; + + static Derived* CreateDerived(); + static char (&Check(Base*))[1]; + static char (&Check(...))[2]; + + public: + static bool const value = sizeof Check(CreateDerived()) == 1 && + !IsSame<Base const, void const>::value; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ diff --git a/chromium/mojo/public/cpp/bindings/lib/validation_errors.cc b/chromium/mojo/public/cpp/bindings/lib/validation_errors.cc new file mode 100644 index 00000000000..ed5904d3963 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/validation_errors.cc @@ -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. + +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +#include <assert.h> +#include <stdio.h> + +namespace mojo { +namespace internal { +namespace { + +ValidationErrorObserverForTesting* g_validation_error_observer = NULL; + +} // namespace + +const char* ValidationErrorToString(ValidationError error) { + switch (error) { + case VALIDATION_ERROR_NONE: + return "VALIDATION_ERROR_NONE"; + case VALIDATION_ERROR_MISALIGNED_OBJECT: + return "VALIDATION_ERROR_MISALIGNED_OBJECT"; + case VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE: + return "VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE"; + case VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER: + return "VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER"; + case VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER: + return "VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER"; + case VALIDATION_ERROR_ILLEGAL_HANDLE: + return "VALIDATION_ERROR_ILLEGAL_HANDLE"; + case VALIDATION_ERROR_ILLEGAL_POINTER: + return "VALIDATION_ERROR_ILLEGAL_POINTER"; + case VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION: + return "VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION"; + case VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID: + return "VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID"; + } + + return "Unknown error"; +} + +void ReportValidationError(ValidationError error) { + if (g_validation_error_observer) { + g_validation_error_observer->set_last_error(error); + } else { + // TODO(yzshen): Consider adding better logging support. + fprintf(stderr, "Invalid message: %s\n", ValidationErrorToString(error)); + } +} + +ValidationErrorObserverForTesting::ValidationErrorObserverForTesting() + : last_error_(VALIDATION_ERROR_NONE) { + assert(!g_validation_error_observer); + g_validation_error_observer = this; +} + +ValidationErrorObserverForTesting::~ValidationErrorObserverForTesting() { + assert(g_validation_error_observer == this); + g_validation_error_observer = NULL; +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/bindings/lib/validation_errors.h b/chromium/mojo/public/cpp/bindings/lib/validation_errors.h new file mode 100644 index 00000000000..9b443c371e5 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/lib/validation_errors.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +enum ValidationError { + // There is no validation error. + VALIDATION_ERROR_NONE, + // An object (struct or array) is not 8-byte aligned. + VALIDATION_ERROR_MISALIGNED_OBJECT, + // An object is not contained inside the message data, or it overlaps other + // objects. + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE, + // A struct header doesn't make sense, for example: + // - |num_bytes| is smaller than the size of the oldest version that we + // support. + // - |num_fields| is smaller than the field number of the oldest version that + // we support. + // - |num_bytes| and |num_fields| don't match. + VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER, + // An array header doesn't make sense, for example: + // - |num_bytes| is smaller than the size of the header plus the size required + // to store |num_elements| elements. + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + // An encoded handle is illegal. + VALIDATION_ERROR_ILLEGAL_HANDLE, + // An encoded pointer is illegal. + VALIDATION_ERROR_ILLEGAL_POINTER, + // |flags| in the message header is an invalid flag combination. + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION, + // |flags| in the message header indicates that a request ID is required but + // there isn't one. + VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID, +}; + +const char* ValidationErrorToString(ValidationError error); + +void ReportValidationError(ValidationError error); + +// Only used by validation tests and when there is only one thread doing message +// validation. +class ValidationErrorObserverForTesting { + public: + ValidationErrorObserverForTesting(); + ~ValidationErrorObserverForTesting(); + + ValidationError last_error() const { return last_error_; } + void set_last_error(ValidationError error) { last_error_ = error; } + + private: + ValidationError last_error_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ValidationErrorObserverForTesting); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ diff --git a/chromium/mojo/public/cpp/bindings/message.h b/chromium/mojo/public/cpp/bindings/message.h new file mode 100644 index 00000000000..beb1cd9036f --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/message.h @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ + +#include <assert.h> + +#include <vector> + +#include "mojo/public/cpp/bindings/lib/message_internal.h" + +namespace mojo { + +// Message is a holder for the data and handles to be sent over a MessagePipe. +// Message owns its data and handles, but a consumer of Message is free to +// mutate the data and handles. The message's data is comprised of a header +// followed by payload. +class Message { + public: + Message(); + ~Message(); + + // These may only be called on a newly created Message object. + void AllocUninitializedData(uint32_t num_bytes); + void AdoptData(uint32_t num_bytes, internal::MessageData* data); + + // Swaps data and handles between this Message and another. + void Swap(Message* other); + + uint32_t data_num_bytes() const { return data_num_bytes_; } + + // Access the raw bytes of the message. + const uint8_t* data() const { return + reinterpret_cast<const uint8_t*>(data_); + } + uint8_t* mutable_data() { return reinterpret_cast<uint8_t*>(data_); } + + // Access the header. + const internal::MessageHeader* header() const { return &data_->header; } + + uint32_t name() const { return data_->header.name; } + bool has_flag(uint32_t flag) const { return !!(data_->header.flags & flag); } + + // Access the request_id field (if present). + bool has_request_id() const { return data_->header.num_fields >= 3; } + uint64_t request_id() const { + assert(has_request_id()); + return static_cast<const internal::MessageHeaderWithRequestID*>( + &data_->header)->request_id; + } + void set_request_id(uint64_t request_id) { + assert(has_request_id()); + static_cast<internal::MessageHeaderWithRequestID*>(&data_->header)-> + request_id = request_id; + } + + // Access the payload. + const uint8_t* payload() const { + return reinterpret_cast<const uint8_t*>(data_) + data_->header.num_bytes; + } + uint8_t* mutable_payload() { + return reinterpret_cast<uint8_t*>(data_) + data_->header.num_bytes; + } + uint32_t payload_num_bytes() const { + assert(data_num_bytes_ >= data_->header.num_bytes); + return data_num_bytes_ - data_->header.num_bytes; + } + + // Access the handles. + const std::vector<Handle>* handles() const { return &handles_; } + std::vector<Handle>* mutable_handles() { return &handles_; } + + private: + uint32_t data_num_bytes_; + internal::MessageData* data_; // Heap-allocated using malloc. + std::vector<Handle> handles_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Message); +}; + +class MessageReceiver { + public: + virtual ~MessageReceiver() {} + + // The receiver may mutate the given message. Returns true if the message + // was accepted and false otherwise, indicating that the message was invalid + // or malformed. + virtual bool Accept(Message* message) MOJO_WARN_UNUSED_RESULT = 0; +}; + +class MessageReceiverWithResponder : public MessageReceiver { + public: + virtual ~MessageReceiverWithResponder() {} + + // A variant on Accept that registers a MessageReceiver (known as the + // responder) to handle the response message generated from the given + // message. The responder's Accept method may be called during + // AcceptWithResponder or some time after its return. + // + // NOTE: Upon returning true, AcceptWithResponder assumes ownership of + // |responder| and will delete it after calling |responder->Accept| or upon + // its own destruction. + // + virtual bool AcceptWithResponder( + Message* message, MessageReceiver* responder) MOJO_WARN_UNUSED_RESULT = 0; +}; + +// Read a single message from the pipe and dispatch to the given receiver. The +// receiver may be null, in which case the message is simply discarded. +// Returns MOJO_RESULT_SHOULD_WAIT if the caller should wait on the handle to +// become readable. Returns MOJO_RESULT_OK if a message was dispatched and +// otherwise returns an error code if something went wrong. +// +// NOTE: The message hasn't been validated and may be malformed! +MojoResult ReadAndDispatchMessage(MessagePipeHandle handle, + MessageReceiver* receiver, + bool* receiver_result); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ diff --git a/chromium/mojo/public/cpp/bindings/message_filter.h b/chromium/mojo/public/cpp/bindings/message_filter.h new file mode 100644 index 00000000000..6d716733ecc --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/message_filter.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_FILTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_FILTER_H_ + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// This class is the base class for message filters. Subclasses should +// implement the pure virtual method Accept() inherited from MessageReceiver to +// process messages and/or forward them to |sink_|. +class MessageFilter : public MessageReceiver { + public: + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + explicit MessageFilter(MessageReceiver* sink = NULL); + virtual ~MessageFilter(); + + void set_sink(MessageReceiver* sink) { sink_ = sink; } + + protected: + MessageReceiver* sink_; +}; + +// A trivial filter that simply forwards every message it receives to |sink_|. +class PassThroughFilter : public MessageFilter { + public: + explicit PassThroughFilter(MessageReceiver* sink = NULL); + + virtual bool Accept(Message* message) MOJO_OVERRIDE; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_FILTER_H_ diff --git a/chromium/mojo/public/cpp/bindings/no_interface.h b/chromium/mojo/public/cpp/bindings/no_interface.h new file mode 100644 index 00000000000..83b0eff5000 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/no_interface.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_H_ + +#include <assert.h> + +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_filter.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// NoInterface is for use in cases when a non-existent or empty interface is +// needed (e.g., when the Mojom "Peer" attribute is not present). + +class NoInterfaceProxy; +class NoInterfaceStub; + +class NoInterface { + public: + static const char* Name_; + typedef NoInterfaceProxy Proxy_; + typedef NoInterfaceStub Stub_; + typedef PassThroughFilter RequestValidator_; + typedef PassThroughFilter ResponseValidator_; + typedef NoInterface Client; + virtual ~NoInterface() {} +}; + +class NoInterfaceProxy : public NoInterface { + public: + explicit NoInterfaceProxy(MessageReceiver* receiver) {} +}; + +class NoInterfaceStub : public MessageReceiverWithResponder { + public: + NoInterfaceStub() {} + void set_sink(NoInterface* sink) {} + NoInterface* sink() { return NULL; } + virtual bool Accept(Message* message) MOJO_OVERRIDE; + virtual bool AcceptWithResponder(Message* message, MessageReceiver* responder) + MOJO_OVERRIDE; +}; + + +// AnyInterface is for use in cases where any interface would do (e.g., see the +// Shell::Connect method). + +typedef NoInterface AnyInterface; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_H_ diff --git a/chromium/mojo/public/cpp/bindings/string.h b/chromium/mojo/public/cpp/bindings/string.h new file mode 100644 index 00000000000..a2427844975 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/string.h @@ -0,0 +1,154 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_H_ + +#include <assert.h> + +#include <string> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/type_converter.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +class String { + public: + typedef internal::String_Data Data_; + + String() : is_null_(true) {} + String(const std::string& str) : value_(str), is_null_(false) {} + String(const char* chars) : is_null_(!chars) { + if (chars) + value_ = chars; + } + String(const char* chars, size_t num_chars) + : value_(chars, num_chars), + is_null_(false) { + } + template <size_t N> + String(const char chars[N]) : value_(chars, N-1), is_null_(false) {} + + template <typename U> + static String From(const U& other) { + return TypeConverter<String, U>::ConvertFrom(other); + } + + template <typename U> + U To() const { + return TypeConverter<String, U>::ConvertTo(*this); + } + + String& operator=(const std::string& str) { + value_ = str; + is_null_ = false; + return *this; + } + String& operator=(const char* chars) { + is_null_ = !chars; + if (chars) { + value_ = chars; + } else { + value_.clear(); + } + return *this; + } + + void reset() { + value_.clear(); + is_null_ = true; + } + + bool is_null() const { return is_null_; } + + size_t size() const { return value_.size(); } + + const char* data() const { return value_.data(); } + + const char& at(size_t offset) const { return value_.at(offset); } + const char& operator[](size_t offset) const { return value_[offset]; } + + const std::string& get() const { return value_; } + operator const std::string&() const { return value_; } + + void Swap(String* other) { + std::swap(is_null_, other->is_null_); + value_.swap(other->value_); + } + + void Swap(std::string* other) { + is_null_ = false; + value_.swap(*other); + } + + private: + typedef std::string String::*Testable; + + public: + operator Testable() const { return is_null_ ? 0 : &String::value_; } + + private: + std::string value_; + bool is_null_; +}; + +inline bool operator==(const String& a, const String& b) { + return a.is_null() == b.is_null() && a.get() == b.get(); +} +inline bool operator==(const char* a, const String& b) { + return !b.is_null() && a == b.get(); +} +inline bool operator==(const String& a, const char* b) { + return !a.is_null() && a.get() == b; +} +inline bool operator!=(const String& a, const String& b) { return !(a == b); } +inline bool operator!=(const char* a, const String& b) { return !(a == b); } +inline bool operator!=(const String& a, const char* b) { return !(a == b); } + +// TODO(darin): Add similar variants of operator<,<=,>,>= + +template <> +class TypeConverter<String, std::string> { + public: + static String ConvertFrom(const std::string& input) { + return String(input); + } + static std::string ConvertTo(const String& input) { + return input; + } +}; + +template <size_t N> +class TypeConverter<String, char[N]> { + public: + static String ConvertFrom(const char input[N]) { + assert(input); + return String(input, N-1); + } +}; + +// Appease MSVC. +template <size_t N> +class TypeConverter<String, const char[N]> { + public: + static String ConvertFrom(const char input[N]) { + assert(input); + return String(input, N-1); + } +}; + +template <> +class TypeConverter<String, const char*> { + public: + // |input| may be null, in which case a null String will be returned. + static String ConvertFrom(const char* input) { + return String(input); + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_H_ diff --git a/chromium/mojo/public/cpp/bindings/struct_ptr.h b/chromium/mojo/public/cpp/bindings/struct_ptr.h new file mode 100644 index 00000000000..a4e55e758f4 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/struct_ptr.h @@ -0,0 +1,154 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ + +#include <assert.h> + +#include <new> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +template <typename Struct> +class StructHelper { + public: + template <typename Ptr> + static void Initialize(Ptr* ptr) { ptr->Initialize(); } +}; + +} // namespace internal + +template <typename Struct> +class StructPtr { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(StructPtr, RValue); + public: + typedef typename Struct::Data_ Data_; + + StructPtr() : ptr_(NULL) {} + ~StructPtr() { + delete ptr_; + } + + StructPtr(RValue other) : ptr_(NULL) { Take(other.object); } + StructPtr& operator=(RValue other) { + Take(other.object); + return *this; + } + + template <typename U> + U To() const { + return TypeConverter<StructPtr, U>::ConvertTo(*this); + } + + void reset() { + if (ptr_) { + delete ptr_; + ptr_ = NULL; + } + } + + bool is_null() const { return ptr_ == NULL; } + + Struct& operator*() const { + assert(ptr_); + return *ptr_; + } + Struct* operator->() const { + assert(ptr_); + return ptr_; + } + Struct* get() const { return ptr_; } + + void Swap(StructPtr* other) { + std::swap(ptr_, other->ptr_); + } + + private: + typedef Struct* StructPtr::*Testable; + + public: + operator Testable() const { return ptr_ ? &StructPtr::ptr_ : 0; } + + private: + friend class internal::StructHelper<Struct>; + void Initialize() { assert(!ptr_); ptr_ = new Struct(); } + + void Take(StructPtr* other) { + reset(); + Swap(other); + } + + Struct* ptr_; +}; + +// Designed to be used when Struct is small and copyable. +template <typename Struct> +class InlinedStructPtr { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(InlinedStructPtr, RValue); + public: + typedef typename Struct::Data_ Data_; + + InlinedStructPtr() : is_null_(true) {} + ~InlinedStructPtr() {} + + InlinedStructPtr(RValue other) : is_null_(true) { Take(other.object); } + InlinedStructPtr& operator=(RValue other) { + Take(other.object); + return *this; + } + + template <typename U> + U To() const { + return TypeConverter<InlinedStructPtr, U>::ConvertTo(*this); + } + + void reset() { + is_null_ = true; + value_.~Struct(); + new (&value_) Struct(); + } + + bool is_null() const { return is_null_; } + + Struct& operator*() const { + assert(!is_null_); + return value_; + } + Struct* operator->() const { + assert(!is_null_); + return &value_; + } + Struct* get() const { return &value_; } + + void Swap(InlinedStructPtr* other) { + std::swap(value_, other->value_); + std::swap(is_null_, other->is_null_); + } + + private: + typedef Struct InlinedStructPtr::*Testable; + + public: + operator Testable() const { return is_null_ ? 0 : &InlinedStructPtr::value_; } + + private: + friend class internal::StructHelper<Struct>; + void Initialize() { is_null_ = false; } + + void Take(InlinedStructPtr* other) { + reset(); + Swap(other); + } + + mutable Struct value_; + bool is_null_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ diff --git a/chromium/mojo/public/cpp/bindings/sync_dispatcher.h b/chromium/mojo/public/cpp/bindings/sync_dispatcher.h new file mode 100644 index 00000000000..9f825bf8a6a --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/sync_dispatcher.h @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_DISPATCHER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_DISPATCHER_H_ + +#include "mojo/public/cpp/bindings/lib/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/message_header_validator.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class MessageReceiver; + +// Waits for one message to arrive on the message pipe, and dispatch it to the +// receiver. Returns true on success, false on failure. +// +// NOTE: The message hasn't been validated and may be malformed! +bool WaitForMessageAndDispatch(MessagePipeHandle handle, + mojo::MessageReceiver* receiver); + +template<typename Interface> class SyncDispatcher { + public: + SyncDispatcher(ScopedMessagePipeHandle message_pipe, Interface* sink) + : message_pipe_(message_pipe.Pass()) { + stub_.set_sink(sink); + + filters_.Append<internal::MessageHeaderValidator>(); + filters_.Append<typename Interface::RequestValidator_>(); + filters_.SetSink(&stub_); + } + + bool WaitAndDispatchOneMessage() { + return WaitForMessageAndDispatch(message_pipe_.get(), + filters_.GetHead()); + } + + private: + ScopedMessagePipeHandle message_pipe_; + typename Interface::Stub_ stub_; + internal::FilterChain filters_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_DISPATCHER_H_ diff --git a/chromium/mojo/public/cpp/bindings/type_converter.h b/chromium/mojo/public/cpp/bindings/type_converter.h new file mode 100644 index 00000000000..0ac5f6d4760 --- /dev/null +++ b/chromium/mojo/public/cpp/bindings/type_converter.h @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ + +namespace mojo { + +// Specialize the following class: +// template <typename T, typename U> class TypeConverter; +// to perform type conversion for Mojom-defined structs and arrays. Here, T is +// the Mojom-defined struct or array, and U is some other non-Mojom +// struct or array type. +// +// Specializations should implement the following interface: +// namespace mojo { +// template <> +// class TypeConverter<T, U> { +// public: +// static T ConvertFrom(const U& input); +// static U ConvertTo(const T& input); +// }; +// } +// +// EXAMPLE: +// +// Suppose you have the following Mojom-defined struct: +// +// module geometry { +// struct Point { +// int32 x; +// int32 y; +// }; +// } +// +// Now, imagine you wanted to write a TypeConverter specialization for +// gfx::Point. It might look like this: +// +// namespace mojo { +// template <> +// class TypeConverter<geometry::PointPtr, gfx::Point> { +// public: +// static geometry::PointPtr ConvertFrom(const gfx::Point& input) { +// geometry::PointPtr result; +// result->x = input.x(); +// result->y = input.y(); +// return result.Pass(); +// } +// static gfx::Point ConvertTo(const geometry::PointPtr& input) { +// return input ? gfx::Point(input->x, input->y) : gfx::Point(); +// } +// }; +// } +// +// With the above TypeConverter defined, it is possible to write code like this: +// +// void AcceptPoint(const geometry::PointPtr& input) { +// // With an explicit cast using the .To<> method. +// gfx::Point pt = input.To<gfx::Point>(); +// +// // With an explicit cast using the static From() method. +// geometry::PointPtr output = geometry::Point::From(pt); +// } +// +template <typename T, typename U> class TypeConverter; + +// The following specialization is useful when you are converting between +// Array<POD> and std::vector<POD>. +template <typename T> class TypeConverter<T, T> { + public: + static T ConvertFrom(const T& obj) { + return obj; + } + static T ConvertTo(const T& obj) { + return obj; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ diff --git a/chromium/mojo/public/cpp/environment/environment.h b/chromium/mojo/public/cpp/environment/environment.h new file mode 100644 index 00000000000..48f4c26eb72 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/environment.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_ENVIRONMENT_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_ENVIRONMENT_H_ + +#include "mojo/public/cpp/system/macros.h" + +struct MojoAsyncWaiter; +struct MojoLogger; + +namespace mojo { + +// Other parts of the Mojo C++ APIs use the *static* methods of this class. +// +// The "standalone" implementation of this class requires that this class (in +// the lib/ subdirectory) be instantiated (and remain so) while using the Mojo +// C++ APIs. I.e., the static methods depend on things set up by the constructor +// and torn down by the destructor. +// +// Other implementations may not have this requirement. +class Environment { + public: + Environment(); + // This constructor allows the standard implementations to be overridden (set + // a parameter to null to get the standard implementation). + Environment(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger); + ~Environment(); + + static const MojoAsyncWaiter* GetDefaultAsyncWaiter(); + static const MojoLogger* GetDefaultLogger(); + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(Environment); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_ENVIRONMENT_H_ diff --git a/chromium/mojo/public/cpp/environment/lib/DEPS b/chromium/mojo/public/cpp/environment/lib/DEPS new file mode 100644 index 00000000000..1889e1fb75f --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+mojo/public/cpp/environment", + "+mojo/public/cpp/utility", +] diff --git a/chromium/mojo/public/cpp/environment/lib/default_async_waiter.cc b/chromium/mojo/public/cpp/environment/lib/default_async_waiter.cc new file mode 100644 index 00000000000..257f1c8d797 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/default_async_waiter.cc @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/environment/lib/default_async_waiter.h" + +#include <assert.h> + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "mojo/public/cpp/utility/run_loop_handler.h" + +namespace mojo { + +namespace { + +// RunLoopHandler implementation used for a request to AsyncWait(). There are +// two ways RunLoopHandlerImpl is deleted: +// . when the handle is ready (or errored). +// . when CancelWait() is invoked. +class RunLoopHandlerImpl : public RunLoopHandler { + public: + RunLoopHandlerImpl(const Handle& handle, + MojoAsyncWaitCallback callback, + void* closure) + : handle_(handle), + callback_(callback), + closure_(closure) { + } + + virtual ~RunLoopHandlerImpl() { + RunLoop::current()->RemoveHandler(handle_); + } + + // RunLoopHandler: + virtual void OnHandleReady(const Handle& handle) MOJO_OVERRIDE { + NotifyCallback(MOJO_RESULT_OK); + } + + virtual void OnHandleError(const Handle& handle, + MojoResult result) MOJO_OVERRIDE { + NotifyCallback(result); + } + + private: + void NotifyCallback(MojoResult result) { + // Delete this to unregister the handle. That way if the callback + // reregisters everything is ok. + MojoAsyncWaitCallback callback = callback_; + void* closure = closure_; + delete this; + + callback(closure, result); + } + + const Handle handle_; + MojoAsyncWaitCallback callback_; + void* closure_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoopHandlerImpl); +}; + +MojoAsyncWaitID AsyncWait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline, + MojoAsyncWaitCallback callback, + void* closure) { + RunLoop* run_loop = RunLoop::current(); + assert(run_loop); + + // |run_loop_handler| is destroyed either when the handle is ready or if + // CancelWait is invoked. + RunLoopHandlerImpl* run_loop_handler = + new RunLoopHandlerImpl(Handle(handle), callback, closure); + run_loop->AddHandler(run_loop_handler, Handle(handle), signals, deadline); + return reinterpret_cast<MojoAsyncWaitID>(run_loop_handler); +} + +void CancelWait(MojoAsyncWaitID wait_id) { + delete reinterpret_cast<RunLoopHandlerImpl*>(wait_id); +} + +} // namespace + +namespace internal { + +const MojoAsyncWaiter kDefaultAsyncWaiter = { + AsyncWait, + CancelWait +}; + +} // namespace internal + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/environment/lib/default_async_waiter.h b/chromium/mojo/public/cpp/environment/lib/default_async_waiter.h new file mode 100644 index 00000000000..49ce233490c --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/default_async_waiter.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_ASYNC_WAITER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_ASYNC_WAITER_H_ + +struct MojoAsyncWaiter; + +namespace mojo { +namespace internal { + +extern const MojoAsyncWaiter kDefaultAsyncWaiter; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_ASYNC_WAITER_H_ diff --git a/chromium/mojo/public/cpp/environment/lib/default_logger.cc b/chromium/mojo/public/cpp/environment/lib/default_logger.cc new file mode 100644 index 00000000000..af4a62866b0 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/default_logger.cc @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/environment/lib/default_logger.h" + +#include <stdio.h> +#include <stdlib.h> // For |abort()|. + +#include <algorithm> + +#include "mojo/public/c/environment/logger.h" + +namespace mojo { + +namespace { + +MojoLogLevel g_minimum_log_level = MOJO_LOG_LEVEL_INFO; + +const char* GetLogLevelString(MojoLogLevel log_level) { + if (log_level <= MOJO_LOG_LEVEL_VERBOSE-3) + return "VERBOSE4+"; + switch (log_level) { + case MOJO_LOG_LEVEL_VERBOSE-2: + return "VERBOSE3"; + case MOJO_LOG_LEVEL_VERBOSE-1: + return "VERBOSE2"; + case MOJO_LOG_LEVEL_VERBOSE: + return "VERBOSE1"; + case MOJO_LOG_LEVEL_INFO: + return "INFO"; + case MOJO_LOG_LEVEL_WARNING: + return "WARNING"; + case MOJO_LOG_LEVEL_ERROR: + return "ERROR"; + } + // Consider everything higher to be fatal. + return "FATAL"; +} + +void LogMessage(MojoLogLevel log_level, const char* message) { + if (log_level < g_minimum_log_level) + return; + + // TODO(vtl): Add timestamp also? + fprintf(stderr, "%s: %s\n", GetLogLevelString(log_level), message); + if (log_level >= MOJO_LOG_LEVEL_FATAL) + abort(); +} + +MojoLogLevel GetMinimumLogLevel() { + return g_minimum_log_level; +} + +void SetMinimumLogLevel(MojoLogLevel minimum_log_level) { + g_minimum_log_level = std::min(minimum_log_level, MOJO_LOG_LEVEL_FATAL); +} + +} // namespace + +namespace internal { + +const MojoLogger kDefaultLogger = { + LogMessage, + GetMinimumLogLevel, + SetMinimumLogLevel +}; + +} // namespace internal + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/environment/lib/default_logger.h b/chromium/mojo/public/cpp/environment/lib/default_logger.h new file mode 100644 index 00000000000..4db32336811 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/default_logger.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_LOGGER_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_LOGGER_H_ + +struct MojoLogger; + +namespace mojo { +namespace internal { + +extern const MojoLogger kDefaultLogger; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LIB_DEFAULT_LOGGER_H_ diff --git a/chromium/mojo/public/cpp/environment/lib/environment.cc b/chromium/mojo/public/cpp/environment/lib/environment.cc new file mode 100644 index 00000000000..58e270e9ae4 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/environment.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 "mojo/public/cpp/environment/environment.h" + +#include <stddef.h> + +#include "mojo/public/c/environment/logger.h" +#include "mojo/public/cpp/environment/lib/default_async_waiter.h" +#include "mojo/public/cpp/environment/lib/default_logger.h" +#include "mojo/public/cpp/utility/run_loop.h" + +namespace mojo { + +namespace { + +const MojoAsyncWaiter* g_default_async_waiter = NULL; +const MojoLogger* g_default_logger = NULL; + +void Init(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger) { + g_default_async_waiter = + default_async_waiter ? default_async_waiter : + &internal::kDefaultAsyncWaiter; + g_default_logger = default_logger ? default_logger : + &internal::kDefaultLogger; + + RunLoop::SetUp(); +} + +} // namespace + +Environment::Environment() { + Init(NULL, NULL); +} + +Environment::Environment(const MojoAsyncWaiter* default_async_waiter, + const MojoLogger* default_logger) { + Init(default_async_waiter, default_logger); +} + +Environment::~Environment() { + RunLoop::TearDown(); + + // TODO(vtl): Maybe we should allow nesting, and restore previous default + // async waiters and loggers? +} + +// static +const MojoAsyncWaiter* Environment::GetDefaultAsyncWaiter() { + return g_default_async_waiter; +} + +// static +const MojoLogger* Environment::GetDefaultLogger() { + return g_default_logger; +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/environment/lib/logging.cc b/chromium/mojo/public/cpp/environment/lib/logging.cc new file mode 100644 index 00000000000..990626df736 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/lib/logging.cc @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/environment/logging.h" + +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { +namespace internal { + +namespace { + +// Gets a pointer to the filename portion of |s|. Assumes that the filename +// follows the last slash or backslash in |s|, or is |s| if no slash or +// backslash is present. +// +// E.g., a pointer to "foo.cc" is returned for the following inputs: "foo.cc", +// "./foo.cc", ".\foo.cc", "/absolute/path/to/foo.cc", +// "relative/path/to/foo.cc", "C:\absolute\path\to\foo.cc", etc. +const char* GetFilename(const char* s) { + const char* rv = s; + while (*s) { + if (*s == '/' || *s == '\\') + rv = s + 1; + s++; + } + return rv; +} + +} // namespace + +LogMessage::LogMessage(const char* file, int line, MojoLogLevel log_level) + : log_level_(log_level) { + // Note: Don't include the log level in the message, since that's passed on. + stream_ << GetFilename(file) << '(' << line << "): "; +} + +LogMessage::~LogMessage() { + Environment::GetDefaultLogger()->LogMessage(log_level_, + stream_.str().c_str()); +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/environment/logging.h b/chromium/mojo/public/cpp/environment/logging.h new file mode 100644 index 00000000000..a3e2cef4293 --- /dev/null +++ b/chromium/mojo/public/cpp/environment/logging.h @@ -0,0 +1,87 @@ +// 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. + +// Logging macros, similar to Chromium's base/logging.h, except with |MOJO_| +// prefixes and missing some features (notably |CHECK_EQ()|, etc.). + +// TODO(vtl): It's weird that this is in the environment directory, since its +// implementation (in environment/lib) is meant to be used by any implementation +// of the environment. + +#ifndef MOJO_PUBLIC_CPP_ENVIRONMENT_LOGGING_H_ +#define MOJO_PUBLIC_CPP_ENVIRONMENT_LOGGING_H_ + +#include <sstream> + +#include "mojo/public/c/environment/logger.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/public/cpp/system/macros.h" + +#define MOJO_LOG_STREAM(level) \ + ::mojo::internal::LogMessage(__FILE__, __LINE__, \ + MOJO_LOG_LEVEL_ ## level).stream() + +#define MOJO_LAZY_LOG_STREAM(level, condition) \ + !(condition) ? \ + (void) 0 : \ + ::mojo::internal::VoidifyOstream() & MOJO_LOG_STREAM(level) + +#define MOJO_SHOULD_LOG(level) \ + (MOJO_LOG_LEVEL_ ## level >= \ + ::mojo::Environment::GetDefaultLogger()->GetMinimumLogLevel()) + +#define MOJO_LOG(level) \ + MOJO_LAZY_LOG_STREAM(level, MOJO_SHOULD_LOG(level)) + +#define MOJO_LOG_IF(level, condition) \ + MOJO_LAZY_LOG_STREAM(level, MOJO_SHOULD_LOG(level) && (condition)) + +#define MOJO_CHECK(condition) \ + MOJO_LAZY_LOG_STREAM(FATAL, !(condition)) \ + << "Check failed: " #condition ". " + +// Note: For non-debug builds, |MOJO_DLOG_IF()| *eliminates* (i.e., doesn't +// compile) the condition, whereas |MOJO_DCHECK()| "neuters" the condition +// (i.e., compiles, but doesn't evaluate). +#ifdef NDEBUG + +#define MOJO_DLOG(level) MOJO_LAZY_LOG_STREAM(level, false) +#define MOJO_DLOG_IF(level, condition) MOJO_LAZY_LOG_STREAM(level, false) +#define MOJO_DCHECK(condition) MOJO_LAZY_LOG_STREAM(FATAL, false && (condition)) + +#else + +#define MOJO_DLOG(level) MOJO_LOG(level) +#define MOJO_DLOG_IF(level, condition) MOJO_LOG_IF(level, condition) +#define MOJO_DCHECK(condition) MOJO_CHECK(condition) + +#endif // NDEBUG + +namespace mojo { +namespace internal { + +class LogMessage { + public: + LogMessage(const char* file, int line, MojoLogLevel log_level); + ~LogMessage(); + + std::ostream& stream() { return stream_; } + + private: + const MojoLogLevel log_level_; + std::ostringstream stream_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Used to ignore a stream. +struct VoidifyOstream { + // Use & since it has precedence lower than << but higher than ?:. + void operator&(std::ostream&) {} +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_ENVIRONMENT_LOGGING_H_ diff --git a/chromium/mojo/public/cpp/gles2/DEPS b/chromium/mojo/public/cpp/gles2/DEPS new file mode 100644 index 00000000000..3c48be99eb8 --- /dev/null +++ b/chromium/mojo/public/cpp/gles2/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+mojo/public/c/gles2", + "+mojo/public/cpp/environment", +] diff --git a/chromium/mojo/public/cpp/gles2/gles2.h b/chromium/mojo/public/cpp/gles2/gles2.h new file mode 100644 index 00000000000..a408fc701eb --- /dev/null +++ b/chromium/mojo/public/cpp/gles2/gles2.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_GLES2_GLES2_H_ +#define MOJO_PUBLIC_CPP_GLES2_GLES2_H_ + +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { + +class GLES2Initializer { + public: + explicit GLES2Initializer(const MojoAsyncWaiter* async_waiter = + Environment::GetDefaultAsyncWaiter()) { + MojoGLES2Initialize(async_waiter); + } + ~GLES2Initializer() { MojoGLES2Terminate(); } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_GLES2_GLES2_H_ diff --git a/chromium/mojo/public/cpp/system/core.h b/chromium/mojo/public/cpp/system/core.h new file mode 100644 index 00000000000..d73bb296514 --- /dev/null +++ b/chromium/mojo/public/cpp/system/core.h @@ -0,0 +1,555 @@ +// 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 MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ + +#include <assert.h> +#include <stddef.h> + +#include <limits> + +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// OVERVIEW +// +// |Handle| and |...Handle|: +// +// |Handle| is a simple, copyable wrapper for the C type |MojoHandle| (which is +// just an integer). Its purpose is to increase type-safety, not provide +// lifetime management. For the same purpose, we have trivial *subclasses* of +// |Handle|, e.g., |MessagePipeHandle| and |DataPipeProducerHandle|. |Handle| +// and its subclasses impose *no* extra overhead over using |MojoHandle|s +// directly. +// +// Note that though we provide constructors for |Handle|/|...Handle| from a +// |MojoHandle|, we do not provide, e.g., a constructor for |MessagePipeHandle| +// from a |Handle|. This is for type safety: If we did, you'd then be able to +// construct a |MessagePipeHandle| from, e.g., a |DataPipeProducerHandle| (since +// it's a |Handle|). +// +// |ScopedHandleBase| and |Scoped...Handle|: +// +// |ScopedHandleBase<HandleType>| is a templated scoped wrapper, for the handle +// types above (in the same sense that a C++11 |unique_ptr<T>| is a scoped +// wrapper for a |T*|). It provides lifetime management, closing its owned +// handle on destruction. It also provides (emulated) move semantics, again +// along the lines of C++11's |unique_ptr| (and exactly like Chromium's +// |scoped_ptr|). +// +// |ScopedHandle| is just (a typedef of) a |ScopedHandleBase<Handle>|. +// Similarly, |ScopedMessagePipeHandle| is just a +// |ScopedHandleBase<MessagePipeHandle>|. Etc. Note that a +// |ScopedMessagePipeHandle| is *not* a (subclass of) |ScopedHandle|. +// +// Wrapper functions: +// +// We provide simple wrappers for the |Mojo...()| functions (in +// mojo/public/c/system/core.h -- see that file for details on individual +// functions). +// +// The general guideline is functions that imply ownership transfer of a handle +// should take (or produce) an appropriate |Scoped...Handle|, while those that +// don't take a |...Handle|. For example, |CreateMessagePipe()| has two +// |ScopedMessagePipe| "out" parameters, whereas |Wait()| and |WaitMany()| take +// |Handle| parameters. Some, have both: e.g., |DuplicatedBuffer()| takes a +// suitable (unscoped) handle (e.g., |SharedBufferHandle|) "in" parameter and +// produces a suitable scoped handle (e.g., |ScopedSharedBufferHandle| a.k.a. +// |ScopedHandleBase<SharedBufferHandle>|) as an "out" parameter. +// +// An exception are some of the |...Raw()| functions. E.g., |CloseRaw()| takes a +// |Handle|, leaving the user to discard the handle. +// +// More significantly, |WriteMessageRaw()| exposes the full API complexity of +// |MojoWriteMessage()| (but doesn't require any extra overhead). It takes a raw +// array of |Handle|s as input, and takes ownership of them (i.e., invalidates +// them) on *success* (but not on failure). There are a number of reasons for +// this. First, C++03 |std::vector|s cannot contain the move-only +// |Scoped...Handle|s. Second, |std::vector|s impose extra overhead +// (necessitating heap-allocation of the buffer). Third, |std::vector|s wouldn't +// provide the desired level of flexibility/safety: a vector of handles would +// have to be all of the same type (probably |Handle|/|ScopedHandle|). Fourth, +// it's expected to not be used directly, but instead be used by generated +// bindings. +// +// Other |...Raw()| functions expose similar rough edges, e.g., dealing with raw +// pointers (and lengths) instead of taking |std::vector|s or similar. + +// Standalone functions -------------------------------------------------------- + +inline MojoTimeTicks GetTimeTicksNow() { + return MojoGetTimeTicksNow(); +} + +// ScopedHandleBase ------------------------------------------------------------ + +// Scoper for the actual handle types defined further below. It's move-only, +// like the C++11 |unique_ptr|. +template <class HandleType> +class ScopedHandleBase { + MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(ScopedHandleBase, RValue) + + public: + ScopedHandleBase() {} + explicit ScopedHandleBase(HandleType handle) : handle_(handle) {} + ~ScopedHandleBase() { CloseIfNecessary(); } + + template <class CompatibleHandleType> + explicit ScopedHandleBase(ScopedHandleBase<CompatibleHandleType> other) + : handle_(other.release()) { + } + + // Move-only constructor and operator=. + ScopedHandleBase(RValue other) : handle_(other.object->release()) {} + ScopedHandleBase& operator=(RValue other) { + if (other.object != this) { + CloseIfNecessary(); + handle_ = other.object->release(); + } + return *this; + } + + const HandleType& get() const { return handle_; } + + template <typename PassedHandleType> + static ScopedHandleBase<HandleType> From( + ScopedHandleBase<PassedHandleType> other) { + MOJO_COMPILE_ASSERT( + sizeof(static_cast<PassedHandleType*>(static_cast<HandleType*>(0))), + HandleType_is_not_a_subtype_of_PassedHandleType); + return ScopedHandleBase<HandleType>( + static_cast<HandleType>(other.release().value())); + } + + void swap(ScopedHandleBase& other) { + handle_.swap(other.handle_); + } + + HandleType release() MOJO_WARN_UNUSED_RESULT { + HandleType rv; + rv.swap(handle_); + return rv; + } + + void reset(HandleType handle = HandleType()) { + CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { + return handle_.is_valid(); + } + + private: + void CloseIfNecessary() { + if (!handle_.is_valid()) + return; + MojoResult result MOJO_ALLOW_UNUSED = MojoClose(handle_.value()); + assert(result == MOJO_RESULT_OK); + } + + HandleType handle_; +}; + +template <typename HandleType> +inline ScopedHandleBase<HandleType> MakeScopedHandle(HandleType handle) { + return ScopedHandleBase<HandleType>(handle); +} + +// Handle ---------------------------------------------------------------------- + +const MojoHandle kInvalidHandleValue = MOJO_HANDLE_INVALID; + +// Wrapper base class for |MojoHandle|. +class Handle { + public: + Handle() : value_(kInvalidHandleValue) {} + explicit Handle(MojoHandle value) : value_(value) {} + ~Handle() {} + + void swap(Handle& other) { + MojoHandle temp = value_; + value_ = other.value_; + other.value_ = temp; + } + + bool is_valid() const { + return value_ != kInvalidHandleValue; + } + + MojoHandle value() const { return value_; } + MojoHandle* mutable_value() { return &value_; } + void set_value(MojoHandle value) { value_ = value; } + + private: + MojoHandle value_; + + // Copying and assignment allowed. +}; + +// Should have zero overhead. +MOJO_COMPILE_ASSERT(sizeof(Handle) == sizeof(MojoHandle), + bad_size_for_cpp_Handle); + +// The scoper should also impose no more overhead. +typedef ScopedHandleBase<Handle> ScopedHandle; +MOJO_COMPILE_ASSERT(sizeof(ScopedHandle) == sizeof(Handle), + bad_size_for_cpp_ScopedHandle); + +inline MojoResult Wait(const Handle& handle, + MojoHandleSignals signals, + MojoDeadline deadline) { + return MojoWait(handle.value(), signals, deadline); +} + +// |HandleVectorType| and |FlagsVectorType| should be similar enough to +// |std::vector<Handle>| and |std::vector<MojoHandleSignals>|, respectively: +// - They should have a (const) |size()| method that returns an unsigned type. +// - They must provide contiguous storage, with access via (const) reference to +// that storage provided by a (const) |operator[]()| (by reference). +template <class HandleVectorType, class FlagsVectorType> +inline MojoResult WaitMany(const HandleVectorType& handles, + const FlagsVectorType& signals, + MojoDeadline deadline) { + if (signals.size() != handles.size()) + return MOJO_RESULT_INVALID_ARGUMENT; + if (handles.size() > std::numeric_limits<uint32_t>::max()) + return MOJO_RESULT_OUT_OF_RANGE; + + if (handles.size() == 0) + return MojoWaitMany(NULL, NULL, 0, deadline); + + const Handle& first_handle = handles[0]; + const MojoHandleSignals& first_signals = signals[0]; + return MojoWaitMany( + reinterpret_cast<const MojoHandle*>(&first_handle), + reinterpret_cast<const MojoHandleSignals*>(&first_signals), + static_cast<uint32_t>(handles.size()), + deadline); +} + +// |Close()| takes ownership of the handle, since it'll invalidate it. +// Note: There's nothing to do, since the argument will be destroyed when it +// goes out of scope. +template <class HandleType> +inline void Close(ScopedHandleBase<HandleType> /*handle*/) {} + +// Most users should typically use |Close()| (above) instead. +inline MojoResult CloseRaw(Handle handle) { + return MojoClose(handle.value()); +} + +// Strict weak ordering, so that |Handle|s can be used as keys in |std::map|s, +// etc. +inline bool operator<(const Handle& a, const Handle& b) { + return a.value() < b.value(); +} + +// MessagePipeHandle ----------------------------------------------------------- + +class MessagePipeHandle : public Handle { + public: + MessagePipeHandle() {} + explicit MessagePipeHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +MOJO_COMPILE_ASSERT(sizeof(MessagePipeHandle) == sizeof(Handle), + bad_size_for_cpp_MessagePipeHandle); + +typedef ScopedHandleBase<MessagePipeHandle> ScopedMessagePipeHandle; +MOJO_COMPILE_ASSERT(sizeof(ScopedMessagePipeHandle) == + sizeof(MessagePipeHandle), + bad_size_for_cpp_ScopedMessagePipeHandle); + +inline MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options, + ScopedMessagePipeHandle* message_pipe0, + ScopedMessagePipeHandle* message_pipe1) { + assert(message_pipe0); + assert(message_pipe1); + MessagePipeHandle handle0; + MessagePipeHandle handle1; + MojoResult rv = MojoCreateMessagePipe(options, + handle0.mutable_value(), + handle1.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + message_pipe0->reset(handle0); + message_pipe1->reset(handle1); + return rv; +} + +// These "raw" versions fully expose the underlying API, but don't help with +// ownership of handles (especially when writing messages). +// TODO(vtl): Write "baked" versions. +inline MojoResult WriteMessageRaw(MessagePipeHandle message_pipe, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return MojoWriteMessage(message_pipe.value(), bytes, num_bytes, handles, + num_handles, flags); +} + +inline MojoResult ReadMessageRaw(MessagePipeHandle message_pipe, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return MojoReadMessage(message_pipe.value(), bytes, num_bytes, handles, + num_handles, flags); +} + +// A wrapper class that automatically creates a message pipe and owns both +// handles. +class MessagePipe { + public: + MessagePipe(); + explicit MessagePipe(const MojoCreateMessagePipeOptions& options); + ~MessagePipe(); + + ScopedMessagePipeHandle handle0; + ScopedMessagePipeHandle handle1; +}; + +inline MessagePipe::MessagePipe() { + MojoResult result MOJO_ALLOW_UNUSED = + CreateMessagePipe(NULL, &handle0, &handle1); + assert(result == MOJO_RESULT_OK); +} + +inline MessagePipe::MessagePipe(const MojoCreateMessagePipeOptions& options) { + MojoResult result MOJO_ALLOW_UNUSED = + CreateMessagePipe(&options, &handle0, &handle1); + assert(result == MOJO_RESULT_OK); +} + +inline MessagePipe::~MessagePipe() { +} + +// DataPipeProducerHandle and DataPipeConsumerHandle --------------------------- + +class DataPipeProducerHandle : public Handle { + public: + DataPipeProducerHandle() {} + explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +MOJO_COMPILE_ASSERT(sizeof(DataPipeProducerHandle) == sizeof(Handle), + bad_size_for_cpp_DataPipeProducerHandle); + +typedef ScopedHandleBase<DataPipeProducerHandle> ScopedDataPipeProducerHandle; +MOJO_COMPILE_ASSERT(sizeof(ScopedDataPipeProducerHandle) == + sizeof(DataPipeProducerHandle), + bad_size_for_cpp_ScopedDataPipeProducerHandle); + +class DataPipeConsumerHandle : public Handle { + public: + DataPipeConsumerHandle() {} + explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +MOJO_COMPILE_ASSERT(sizeof(DataPipeConsumerHandle) == sizeof(Handle), + bad_size_for_cpp_DataPipeConsumerHandle); + +typedef ScopedHandleBase<DataPipeConsumerHandle> ScopedDataPipeConsumerHandle; +MOJO_COMPILE_ASSERT(sizeof(ScopedDataPipeConsumerHandle) == + sizeof(DataPipeConsumerHandle), + bad_size_for_cpp_ScopedDataPipeConsumerHandle); + +inline MojoResult CreateDataPipe( + const MojoCreateDataPipeOptions* options, + ScopedDataPipeProducerHandle* data_pipe_producer, + ScopedDataPipeConsumerHandle* data_pipe_consumer) { + assert(data_pipe_producer); + assert(data_pipe_consumer); + DataPipeProducerHandle producer_handle; + DataPipeConsumerHandle consumer_handle; + MojoResult rv = MojoCreateDataPipe(options, producer_handle.mutable_value(), + consumer_handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + data_pipe_producer->reset(producer_handle); + data_pipe_consumer->reset(consumer_handle); + return rv; +} + +inline MojoResult WriteDataRaw(DataPipeProducerHandle data_pipe_producer, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + return MojoWriteData(data_pipe_producer.value(), elements, num_bytes, flags); +} + +inline MojoResult BeginWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + return MojoBeginWriteData(data_pipe_producer.value(), buffer, + buffer_num_bytes, flags); +} + +inline MojoResult EndWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + uint32_t num_bytes_written) { + return MojoEndWriteData(data_pipe_producer.value(), num_bytes_written); +} + +inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags); +} + +inline MojoResult BeginReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + return MojoBeginReadData(data_pipe_consumer.value(), buffer, buffer_num_bytes, + flags); +} + +inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + uint32_t num_bytes_read) { + return MojoEndReadData(data_pipe_consumer.value(), num_bytes_read); +} + +// A wrapper class that automatically creates a data pipe and owns both handles. +// TODO(vtl): Make an even more friendly version? (Maybe templatized for a +// particular type instead of some "element"? Maybe functions that take +// vectors?) +class DataPipe { + public: + DataPipe(); + explicit DataPipe(const MojoCreateDataPipeOptions& options); + ~DataPipe(); + + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; +}; + +inline DataPipe::DataPipe() { + MojoResult result MOJO_ALLOW_UNUSED = + CreateDataPipe(NULL, &producer_handle, &consumer_handle); + assert(result == MOJO_RESULT_OK); +} + +inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) { + MojoResult result MOJO_ALLOW_UNUSED = + CreateDataPipe(&options, &producer_handle, &consumer_handle); + assert(result == MOJO_RESULT_OK); +} + +inline DataPipe::~DataPipe() { +} + +// SharedBufferHandle ---------------------------------------------------------- + +class SharedBufferHandle : public Handle { + public: + SharedBufferHandle() {} + explicit SharedBufferHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +MOJO_COMPILE_ASSERT(sizeof(SharedBufferHandle) == sizeof(Handle), + bad_size_for_cpp_SharedBufferHandle); + +typedef ScopedHandleBase<SharedBufferHandle> ScopedSharedBufferHandle; +MOJO_COMPILE_ASSERT(sizeof(ScopedSharedBufferHandle) == + sizeof(SharedBufferHandle), + bad_size_for_cpp_ScopedSharedBufferHandle); + +inline MojoResult CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + ScopedSharedBufferHandle* shared_buffer) { + assert(shared_buffer); + SharedBufferHandle handle; + MojoResult rv = MojoCreateSharedBuffer(options, num_bytes, + handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + shared_buffer->reset(handle); + return rv; +} + +// TODO(vtl): This (and also the functions below) are templatized to allow for +// future/other buffer types. A bit "safer" would be to overload this function +// manually. (The template enforces that the in and out handles to be of the +// same type.) +template <class BufferHandleType> +inline MojoResult DuplicateBuffer( + BufferHandleType buffer, + const MojoDuplicateBufferHandleOptions* options, + ScopedHandleBase<BufferHandleType>* new_buffer) { + assert(new_buffer); + BufferHandleType handle; + MojoResult rv = MojoDuplicateBufferHandle( + buffer.value(), options, handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + new_buffer->reset(handle); + return rv; +} + +template <class BufferHandleType> +inline MojoResult MapBuffer(BufferHandleType buffer, + uint64_t offset, + uint64_t num_bytes, + void** pointer, + MojoMapBufferFlags flags) { + assert(buffer.is_valid()); + return MojoMapBuffer(buffer.value(), offset, num_bytes, pointer, flags); +} + +inline MojoResult UnmapBuffer(void* pointer) { + assert(pointer); + return MojoUnmapBuffer(pointer); +} + +// A wrapper class that automatically creates a shared buffer and owns the +// handle. +class SharedBuffer { + public: + explicit SharedBuffer(uint64_t num_bytes); + SharedBuffer(uint64_t num_bytes, + const MojoCreateSharedBufferOptions& options); + ~SharedBuffer(); + + ScopedSharedBufferHandle handle; +}; + +inline SharedBuffer::SharedBuffer(uint64_t num_bytes) { + MojoResult result MOJO_ALLOW_UNUSED = + CreateSharedBuffer(NULL, num_bytes, &handle); + assert(result == MOJO_RESULT_OK); +} + +inline SharedBuffer::SharedBuffer( + uint64_t num_bytes, + const MojoCreateSharedBufferOptions& options) { + MojoResult result MOJO_ALLOW_UNUSED = + CreateSharedBuffer(&options, num_bytes, &handle); + assert(result == MOJO_RESULT_OK); +} + +inline SharedBuffer::~SharedBuffer() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ diff --git a/chromium/mojo/public/cpp/system/macros.h b/chromium/mojo/public/cpp/system/macros.h new file mode 100644 index 00000000000..ac4f3dbc84c --- /dev/null +++ b/chromium/mojo/public/cpp/system/macros.h @@ -0,0 +1,53 @@ +// 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 MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_ + +#include "mojo/public/c/system/macros.h" + +// Annotate a virtual method indicating it must be overriding a virtual method +// in the parent class. Use like: +// virtual void foo() OVERRIDE; +#if defined(_MSC_VER) || defined(__clang__) +#define MOJO_OVERRIDE override +#else +#define MOJO_OVERRIDE +#endif + +// A macro to disallow the copy constructor and operator= functions. +// This should be used in the private: declarations for a class. +#define MOJO_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// Used to calculate the number of elements in an array. +// (See |arraysize()| in Chromium's base/basictypes.h for more details.) +namespace mojo { +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +#if !defined(_MSC_VER) +template <typename T, size_t N> +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif +} // namespace mojo +#define MOJO_ARRAYSIZE(array) (sizeof(::mojo::ArraySizeHelper(array))) + +// Used to make a type move-only in C++03. See Chromium's base/move.h for more +// details. +#define MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + explicit rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + typedef void MoveOnlyTypeForCPP03; \ + private: + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_ diff --git a/chromium/mojo/public/cpp/test_support/DEPS b/chromium/mojo/public/cpp/test_support/DEPS new file mode 100644 index 00000000000..6dc53942e16 --- /dev/null +++ b/chromium/mojo/public/cpp/test_support/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/public/c/test_support", +] diff --git a/chromium/mojo/public/cpp/test_support/lib/test_support.cc b/chromium/mojo/public/cpp/test_support/lib/test_support.cc new file mode 100644 index 00000000000..55a3dcc7024 --- /dev/null +++ b/chromium/mojo/public/cpp/test_support/lib/test_support.cc @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/test_support/test_support.h" + +#include <stdlib.h> + +namespace mojo { +namespace test { + +std::vector<std::string> EnumerateSourceRootRelativeDirectory( + const std::string& relative_path) { + char** names = MojoTestSupportEnumerateSourceRootRelativeDirectory( + relative_path.c_str()); + std::vector<std::string> results; + for (char** ptr = names; *ptr != NULL; ++ptr) { + results.push_back(*ptr); + free(*ptr); + } + free(names); + return results; +} + +} // namespace test +} // namespace mojo diff --git a/chromium/mojo/public/cpp/test_support/lib/test_utils.cc b/chromium/mojo/public/cpp/test_support/lib/test_utils.cc new file mode 100644 index 00000000000..b47ea209020 --- /dev/null +++ b/chromium/mojo/public/cpp/test_support/lib/test_utils.cc @@ -0,0 +1,91 @@ +// 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 "mojo/public/cpp/test_support/test_utils.h" + +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_support.h" + +namespace mojo { +namespace test { + +bool WriteTextMessage(const MessagePipeHandle& handle, + const std::string& text) { + MojoResult rv = WriteMessageRaw(handle, + text.data(), + static_cast<uint32_t>(text.size()), + NULL, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + return rv == MOJO_RESULT_OK; +} + +bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text) { + MojoResult rv; + bool did_wait = false; + + uint32_t num_bytes = 0, num_handles = 0; + for (;;) { + rv = ReadMessageRaw(handle, + NULL, + &num_bytes, + NULL, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv == MOJO_RESULT_SHOULD_WAIT) { + if (did_wait) { + assert(false); // Looping endlessly!? + return false; + } + rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE); + if (rv != MOJO_RESULT_OK) + return false; + did_wait = true; + } else { + assert(!num_handles); + break; + } + } + + text->resize(num_bytes); + rv = ReadMessageRaw(handle, + &text->at(0), + &num_bytes, + NULL, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + return rv == MOJO_RESULT_OK; +} + +bool DiscardMessage(const MessagePipeHandle& handle) { + MojoResult rv = ReadMessageRaw(handle, NULL, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + return rv == MOJO_RESULT_OK; +} + +void IterateAndReportPerf(const char* test_name, + PerfTestSingleIteration single_iteration, + void* closure) { + // TODO(vtl): These should be specifiable using command-line flags. + static const size_t kGranularity = 100; + static const MojoTimeTicks kPerftestTimeMicroseconds = 3 * 1000000; + + const MojoTimeTicks start_time = GetTimeTicksNow(); + MojoTimeTicks end_time; + size_t iterations = 0; + do { + for (size_t i = 0; i < kGranularity; i++) + (*single_iteration)(closure); + iterations += kGranularity; + + end_time = GetTimeTicksNow(); + } while (end_time - start_time < kPerftestTimeMicroseconds); + + MojoTestSupportLogPerfResult(test_name, + 1000000.0 * iterations / (end_time - start_time), + "iterations/second"); +} + +} // namespace test +} // namespace mojo diff --git a/chromium/mojo/public/cpp/test_support/test_support.h b/chromium/mojo/public/cpp/test_support/test_support.h new file mode 100644 index 00000000000..eb4d4be7116 --- /dev/null +++ b/chromium/mojo/public/cpp/test_support/test_support.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ +#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ + +#include <string> +#include <vector> + +#include "mojo/public/c/test_support/test_support.h" + +namespace mojo { +namespace test { + +inline void LogPerfResult(const char* test_name, + double value, + const char* units) { + MojoTestSupportLogPerfResult(test_name, value, units); +} + +// Opens text file relative to the source root for reading. +inline FILE* OpenSourceRootRelativeFile(const std::string& relative_path) { + return MojoTestSupportOpenSourceRootRelativeFile(relative_path.c_str()); +} + +// Returns the list of regular files in a directory relative to the source root. +std::vector<std::string> EnumerateSourceRootRelativeDirectory( + const std::string& relative_path); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ diff --git a/chromium/mojo/public/cpp/test_support/test_utils.h b/chromium/mojo/public/cpp/test_support/test_utils.h new file mode 100644 index 00000000000..43a3ea99821 --- /dev/null +++ b/chromium/mojo/public/cpp/test_support/test_utils.h @@ -0,0 +1,39 @@ +// 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 MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ +#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ + +#include <string> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace test { + +// Writes a message to |handle| with message data |text|. Returns true on +// success. +bool WriteTextMessage(const MessagePipeHandle& handle, const std::string& text); + +// Reads a message from |handle|, putting its contents into |*text|. Returns +// true on success. (This blocks if necessary and will call |MojoReadMessage()| +// multiple times, e.g., to query the size of the message.) +bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text); + +// Discards a message from |handle|. Returns true on success. (This does not +// block. It will fail if no message is available to discard.) +bool DiscardMessage(const MessagePipeHandle& handle); + +// Run |single_iteration| an appropriate number of times and report its +// performance appropriately. (This actually runs |single_iteration| for a fixed +// amount of time and reports the number of iterations per unit time.) +typedef void (*PerfTestSingleIteration)(void* closure); +void IterateAndReportPerf(const char* test_name, + PerfTestSingleIteration single_iteration, + void* closure); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ diff --git a/chromium/mojo/public/cpp/utility/lib/mutex.cc b/chromium/mojo/public/cpp/utility/lib/mutex.cc new file mode 100644 index 00000000000..23370e1c023 --- /dev/null +++ b/chromium/mojo/public/cpp/utility/lib/mutex.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/utility/mutex.h" + +#include <assert.h> +#include <errno.h> + +namespace mojo { + +// Release builds have inlined (non-error-checking) definitions in the header. +#if !defined(NDEBUG) +Mutex::Mutex() { + pthread_mutexattr_t mutexattr; + int rv = pthread_mutexattr_init(&mutexattr); + assert(rv == 0); + rv = pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK); + assert(rv == 0); + rv = pthread_mutex_init(&mutex_, &mutexattr); + assert(rv == 0); + rv = pthread_mutexattr_destroy(&mutexattr); + assert(rv == 0); +} + +Mutex::~Mutex() { + int rv = pthread_mutex_destroy(&mutex_); + assert(rv == 0); +} + +void Mutex::Lock() { + int rv = pthread_mutex_lock(&mutex_); + assert(rv == 0); +} + +void Mutex::Unlock() { + int rv = pthread_mutex_unlock(&mutex_); + assert(rv == 0); +} + +bool Mutex::TryLock() { + int rv = pthread_mutex_trylock(&mutex_); + assert(rv == 0 || rv == EBUSY); + return rv == 0; +} + +void Mutex::AssertHeld() { + assert(pthread_mutex_lock(&mutex_) == EDEADLK); +} +#endif // !defined(NDEBUG) + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/utility/lib/run_loop.cc b/chromium/mojo/public/cpp/utility/lib/run_loop.cc new file mode 100644 index 00000000000..f523eaeaead --- /dev/null +++ b/chromium/mojo/public/cpp/utility/lib/run_loop.cc @@ -0,0 +1,221 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/utility/run_loop.h" + +#include <assert.h> + +#include <algorithm> +#include <vector> + +#include "mojo/public/cpp/utility/lib/thread_local.h" +#include "mojo/public/cpp/utility/run_loop_handler.h" + +namespace mojo { +namespace { + +internal::ThreadLocalPointer<RunLoop> current_run_loop; + +const MojoTimeTicks kInvalidTimeTicks = static_cast<MojoTimeTicks>(0); + +} // namespace + +// State needed for one iteration of WaitMany(). +struct RunLoop::WaitState { + WaitState() : deadline(MOJO_DEADLINE_INDEFINITE) {} + + std::vector<Handle> handles; + std::vector<MojoHandleSignals> handle_signals; + MojoDeadline deadline; +}; + +struct RunLoop::RunState { + RunState() : should_quit(false) {} + + bool should_quit; +}; + +RunLoop::RunLoop() : run_state_(NULL), next_handler_id_(0) { + assert(!current()); + current_run_loop.Set(this); +} + +RunLoop::~RunLoop() { + assert(current() == this); + current_run_loop.Set(NULL); +} + +// static +void RunLoop::SetUp() { + current_run_loop.Allocate(); +} + +// static +void RunLoop::TearDown() { + assert(!current()); + current_run_loop.Free(); +} + +// static +RunLoop* RunLoop::current() { + return current_run_loop.Get(); +} + +void RunLoop::AddHandler(RunLoopHandler* handler, + const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline) { + assert(current() == this); + assert(handler); + assert(handle.is_valid()); + // Assume it's an error if someone tries to reregister an existing handle. + assert(0u == handler_data_.count(handle)); + HandlerData handler_data; + handler_data.handler = handler; + handler_data.handle_signals = handle_signals; + handler_data.deadline = (deadline == MOJO_DEADLINE_INDEFINITE) ? + kInvalidTimeTicks : + GetTimeTicksNow() + static_cast<MojoTimeTicks>(deadline); + handler_data.id = next_handler_id_++; + handler_data_[handle] = handler_data; +} + +void RunLoop::RemoveHandler(const Handle& handle) { + assert(current() == this); + handler_data_.erase(handle); +} + +bool RunLoop::HasHandler(const Handle& handle) const { + return handler_data_.find(handle) != handler_data_.end(); +} + +void RunLoop::Run() { + assert(current() == this); + RunState* old_state = run_state_; + RunState run_state; + run_state_ = &run_state; + while (!run_state.should_quit) + Wait(false); + run_state_ = old_state; +} + +void RunLoop::RunUntilIdle() { + assert(current() == this); + RunState* old_state = run_state_; + RunState run_state; + run_state_ = &run_state; + while (!run_state.should_quit) { + if (!Wait(true)) + break; + } + run_state_ = old_state; +} + +void RunLoop::Quit() { + assert(current() == this); + if (run_state_) + run_state_->should_quit = true; +} + +bool RunLoop::Wait(bool non_blocking) { + const WaitState wait_state = GetWaitState(non_blocking); + if (wait_state.handles.empty()) { + Quit(); + return false; + } + + const MojoResult result = WaitMany(wait_state.handles, + wait_state.handle_signals, + wait_state.deadline); + if (result >= 0) { + const size_t index = static_cast<size_t>(result); + assert(handler_data_.find(wait_state.handles[index]) != + handler_data_.end()); + handler_data_[wait_state.handles[index]].handler->OnHandleReady( + wait_state.handles[index]); + return true; + } + + switch (result) { + case MOJO_RESULT_INVALID_ARGUMENT: + case MOJO_RESULT_FAILED_PRECONDITION: + return RemoveFirstInvalidHandle(wait_state); + case MOJO_RESULT_DEADLINE_EXCEEDED: + return NotifyDeadlineExceeded(); + } + + assert(false); + return false; +} + +bool RunLoop::NotifyDeadlineExceeded() { + bool notified = false; + + // Make a copy in case someone tries to add/remove new handlers as part of + // notifying. + const HandleToHandlerData cloned_handlers(handler_data_); + const MojoTimeTicks now(GetTimeTicksNow()); + for (HandleToHandlerData::const_iterator i = cloned_handlers.begin(); + i != cloned_handlers.end(); ++i) { + // Since we're iterating over a clone of the handlers, verify the handler is + // still valid before notifying. + if (i->second.deadline != kInvalidTimeTicks && + i->second.deadline < now && + handler_data_.find(i->first) != handler_data_.end() && + handler_data_[i->first].id == i->second.id) { + handler_data_.erase(i->first); + i->second.handler->OnHandleError(i->first, MOJO_RESULT_DEADLINE_EXCEEDED); + notified = true; + } + } + + return notified; +} + +bool RunLoop::RemoveFirstInvalidHandle(const WaitState& wait_state) { + for (size_t i = 0; i < wait_state.handles.size(); ++i) { + const MojoResult result = + mojo::Wait(wait_state.handles[i], wait_state.handle_signals[i], + static_cast<MojoDeadline>(0)); + if (result == MOJO_RESULT_INVALID_ARGUMENT || + result == MOJO_RESULT_FAILED_PRECONDITION) { + // Remove the handle first, this way if OnHandleError() tries to remove + // the handle our iterator isn't invalidated. + assert(handler_data_.find(wait_state.handles[i]) != handler_data_.end()); + RunLoopHandler* handler = + handler_data_[wait_state.handles[i]].handler; + handler_data_.erase(wait_state.handles[i]); + handler->OnHandleError(wait_state.handles[i], result); + return true; + } + assert(MOJO_RESULT_DEADLINE_EXCEEDED == result); + } + return false; +} + +RunLoop::WaitState RunLoop::GetWaitState(bool non_blocking) const { + WaitState wait_state; + MojoTimeTicks min_time = kInvalidTimeTicks; + for (HandleToHandlerData::const_iterator i = handler_data_.begin(); + i != handler_data_.end(); ++i) { + wait_state.handles.push_back(i->first); + wait_state.handle_signals.push_back(i->second.handle_signals); + if (!non_blocking && i->second.deadline != kInvalidTimeTicks && + (min_time == kInvalidTimeTicks || i->second.deadline < min_time)) { + min_time = i->second.deadline; + } + } + if (non_blocking) { + wait_state.deadline = static_cast<MojoDeadline>(0); + } else if (min_time != kInvalidTimeTicks) { + const MojoTimeTicks now = GetTimeTicksNow(); + if (min_time < now) + wait_state.deadline = static_cast<MojoDeadline>(0); + else + wait_state.deadline = static_cast<MojoDeadline>(min_time - now); + } + return wait_state; +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/utility/lib/thread.cc b/chromium/mojo/public/cpp/utility/lib/thread.cc new file mode 100644 index 00000000000..da33497df42 --- /dev/null +++ b/chromium/mojo/public/cpp/utility/lib/thread.cc @@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/utility/thread.h" + +#include <assert.h> + +namespace mojo { + +Thread::Thread() + : options_(), + thread_(), + started_(false), + joined_(false) { +} + +Thread::Thread(const Options& options) + : options_(options), + thread_(), + started_(false), + joined_(false) { +} + +Thread::~Thread() { + // If it was started, it must have been joined. + assert(!started_ || joined_); +} + +void Thread::Start() { + assert(!started_); + assert(!joined_); + + pthread_attr_t attr; + int rv MOJO_ALLOW_UNUSED = pthread_attr_init(&attr); + assert(rv == 0); + + // Non-default stack size? + if (options_.stack_size() != 0) { + rv = pthread_attr_setstacksize(&attr, options_.stack_size()); + assert(rv == 0); + } + + started_ = true; + rv = pthread_create(&thread_, &attr, &ThreadRunTrampoline, this); + assert(rv == 0); + + rv = pthread_attr_destroy(&attr); + assert(rv == 0); +} + +void Thread::Join() { + // Must have been started but not yet joined. + assert(started_); + assert(!joined_); + + joined_ = true; + int rv MOJO_ALLOW_UNUSED = pthread_join(thread_, NULL); + assert(rv == 0); +} + +// static +void* Thread::ThreadRunTrampoline(void* arg) { + Thread* self = static_cast<Thread*>(arg); + self->Run(); + return NULL; +} + +} // namespace mojo diff --git a/chromium/mojo/public/cpp/utility/lib/thread_local.h b/chromium/mojo/public/cpp/utility/lib/thread_local.h new file mode 100644 index 00000000000..4c3625d00d8 --- /dev/null +++ b/chromium/mojo/public/cpp/utility/lib/thread_local.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_ +#define MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_ + +#ifndef _WIN32 +#include <pthread.h> +#endif + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace internal { + +// Helper functions that abstract the cross-platform APIs. +struct ThreadLocalPlatform { +#ifdef _WIN32 + typedef unsigned long SlotType; +#else + typedef pthread_key_t SlotType; +#endif + + static void AllocateSlot(SlotType* slot); + static void FreeSlot(SlotType slot); + static void* GetValueFromSlot(SlotType slot); + static void SetValueInSlot(SlotType slot, void* value); +}; + +// This class is intended to be statically allocated. +template <typename P> +class ThreadLocalPointer { + public: + ThreadLocalPointer() : slot_() { + } + + void Allocate() { + ThreadLocalPlatform::AllocateSlot(&slot_); + } + + void Free() { + ThreadLocalPlatform::FreeSlot(slot_); + } + + P* Get() { + return static_cast<P*>(ThreadLocalPlatform::GetValueFromSlot(slot_)); + } + + void Set(P* value) { + ThreadLocalPlatform::SetValueInSlot(slot_, value); + } + + private: + ThreadLocalPlatform::SlotType slot_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_ diff --git a/chromium/mojo/public/cpp/utility/lib/thread_local_posix.cc b/chromium/mojo/public/cpp/utility/lib/thread_local_posix.cc new file mode 100644 index 00000000000..b33dfc61297 --- /dev/null +++ b/chromium/mojo/public/cpp/utility/lib/thread_local_posix.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/utility/lib/thread_local.h" + +#include <assert.h> + +namespace mojo { +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + if (pthread_key_create(slot, NULL) != 0) { + assert(false); + } +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType slot) { + if (pthread_key_delete(slot) != 0) { + assert(false); + } +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { + return pthread_getspecific(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { + if (pthread_setspecific(slot, value) != 0) { + assert(false); + } +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/utility/lib/thread_local_win.cc b/chromium/mojo/public/cpp/utility/lib/thread_local_win.cc new file mode 100644 index 00000000000..98841f7273a --- /dev/null +++ b/chromium/mojo/public/cpp/utility/lib/thread_local_win.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/utility/lib/thread_local.h" + +#include <assert.h> +#include <windows.h> + +namespace mojo { +namespace internal { + +// static +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + *slot = TlsAlloc(); + assert(*slot != TLS_OUT_OF_INDEXES); +} + +// static +void ThreadLocalPlatform::FreeSlot(SlotType slot) { + if (!TlsFree(slot)) { + assert(false); + } +} + +// static +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { + return TlsGetValue(slot); +} + +// static +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { + if (!TlsSetValue(slot, value)) { + assert(false); + } +} + +} // namespace internal +} // namespace mojo diff --git a/chromium/mojo/public/cpp/utility/mutex.h b/chromium/mojo/public/cpp/utility/mutex.h new file mode 100644 index 00000000000..35611c2ba9a --- /dev/null +++ b/chromium/mojo/public/cpp/utility/mutex.h @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_ +#define MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_ + +#ifdef _WIN32 +#error "Not implemented: See crbug.com/342893." +#endif + +#include <pthread.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +#ifdef NDEBUG +// Note: Make a C++ constant for |PTHREAD_MUTEX_INITIALIZER|. (We can't directly +// use the C macro in an initializer list, since it might expand to |{ ... }|.) +namespace internal { +const pthread_mutex_t kPthreadMutexInitializer = PTHREAD_MUTEX_INITIALIZER; +} +#endif + +class Mutex { + public: +#ifdef NDEBUG + Mutex() : mutex_(internal::kPthreadMutexInitializer) {} + ~Mutex() { pthread_mutex_destroy(&mutex_); } + + void Lock() { pthread_mutex_lock(&mutex_); } + void Unlock() { pthread_mutex_unlock(&mutex_); } + bool TryLock() { return pthread_mutex_trylock(&mutex_) == 0; } + + void AssertHeld() {} +#else + Mutex(); + ~Mutex(); + + void Lock(); + void Unlock(); + bool TryLock(); + + void AssertHeld(); +#endif + + private: + pthread_mutex_t mutex_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Mutex); +}; + +class MutexLock { + public: + explicit MutexLock(Mutex* mutex) : mutex_(mutex) { mutex_->Lock(); } + ~MutexLock() { mutex_->Unlock(); } + + private: + Mutex* const mutex_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(MutexLock); +}; + +// Catch bug where variable name is omitted (e.g., |MutexLock (&mu)|). +#define MutexLock(x) MOJO_COMPILE_ASSERT(0, mutex_lock_missing_variable_name); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_ diff --git a/chromium/mojo/public/cpp/utility/run_loop.h b/chromium/mojo/public/cpp/utility/run_loop.h new file mode 100644 index 00000000000..74f870abc57 --- /dev/null +++ b/chromium/mojo/public/cpp/utility/run_loop.h @@ -0,0 +1,108 @@ +// 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 MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_ +#define MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_ + +#include <map> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class RunLoopHandler; + +class RunLoop { + public: + RunLoop(); + ~RunLoop(); + + // Sets up state needed for RunLoop. This must be invoked before creating a + // RunLoop. + static void SetUp(); + + // Cleans state created by Setup(). + static void TearDown(); + + // Returns the RunLoop for the current thread. Returns NULL if not yet + // created. + static RunLoop* current(); + + // Registers a RunLoopHandler for the specified handle. Only one handler can + // be registered for a specified handle. + void AddHandler(RunLoopHandler* handler, + const Handle& handle, + MojoHandleSignals handle_signals, + MojoDeadline deadline); + void RemoveHandler(const Handle& handle); + bool HasHandler(const Handle& handle) const; + + // Runs the loop servicing handles as they are ready. This returns when Quit() + // is invoked, or there no more handles. + void Run(); + + // Runs the loop servicing any handles that are ready. Does not wait for + // handles to become ready before returning. Returns early if Quit() is + // invoked. + void RunUntilIdle(); + + void Quit(); + + private: + struct RunState; + struct WaitState; + + // Contains the data needed to track a request to AddHandler(). + struct HandlerData { + HandlerData() + : handler(NULL), + handle_signals(MOJO_HANDLE_SIGNAL_NONE), + deadline(0), + id(0) {} + + RunLoopHandler* handler; + MojoHandleSignals handle_signals; + MojoTimeTicks deadline; + // See description of |RunLoop::next_handler_id_| for details. + int id; + }; + + typedef std::map<Handle, HandlerData> HandleToHandlerData; + + // Waits for a handle to be ready. Returns after servicing at least one + // handle (or there are no more handles) unless |non_blocking| is true, + // in which case it will also return if servicing at least one handle + // would require blocking. Returns true if a RunLoopHandler was notified. + bool Wait(bool non_blocking); + + // Notifies any handlers whose deadline has expired. Returns true if a + // RunLoopHandler was notified. + bool NotifyDeadlineExceeded(); + + // Removes the first invalid handle. This is called if MojoWaitMany() finds an + // invalid handle. Returns true if a RunLoopHandler was notified. + bool RemoveFirstInvalidHandle(const WaitState& wait_state); + + // Returns the state needed to pass to WaitMany(). + WaitState GetWaitState(bool non_blocking) const; + + HandleToHandlerData handler_data_; + + // If non-NULL we're running (inside Run()). Member references a value on the + // stack. + RunState* run_state_; + + // An ever increasing value assigned to each HandlerData::id. Used to detect + // uniqueness while notifying. That is, while notifying expired timers we copy + // |handler_data_| and only notify handlers whose id match. If the id does not + // match it means the handler was removed then added so that we shouldn't + // notify it. + int next_handler_id_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoop); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_ diff --git a/chromium/mojo/public/cpp/utility/run_loop_handler.h b/chromium/mojo/public/cpp/utility/run_loop_handler.h new file mode 100644 index 00000000000..69838d5e57c --- /dev/null +++ b/chromium/mojo/public/cpp/utility/run_loop_handler.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 MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_ +#define MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_ + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// Used by RunLoop to notify when a handle is either ready or has become +// invalid. +class RunLoopHandler { + public: + virtual void OnHandleReady(const Handle& handle) = 0; + virtual void OnHandleError(const Handle& handle, MojoResult result) = 0; + + protected: + virtual ~RunLoopHandler() {} +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_ diff --git a/chromium/mojo/public/cpp/utility/thread.h b/chromium/mojo/public/cpp/utility/thread.h new file mode 100644 index 00000000000..b7d10ee7037 --- /dev/null +++ b/chromium/mojo/public/cpp/utility/thread.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_UTILITY_THREAD_H_ +#define MOJO_PUBLIC_CPP_UTILITY_THREAD_H_ + +#ifdef _WIN32 +#error "Not implemented: See crbug.com/342893." +#endif + +#include <pthread.h> +#include <stddef.h> + +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { + +// This class is thread-friendly, not thread-safe (e.g., you mustn't call +// |Join()| from multiple threads and/or simultaneously try to destroy the +// object). +class Thread { + public: + // TODO(vtl): Support non-joinable? priority? + class Options { + public: + Options() : stack_size_(0) {} + + // A stack size of 0 means the default. + size_t stack_size() const { return stack_size_; } + void set_stack_size(size_t stack_size) { stack_size_ = stack_size; } + + private: + size_t stack_size_; + + // Copy and assign allowed. + }; + + // TODO(vtl): Add name or name prefix? + Thread(); + explicit Thread(const Options& options); + virtual ~Thread(); + + void Start(); + void Join(); + + virtual void Run() = 0; + + private: + static void* ThreadRunTrampoline(void* arg); + + const Options options_; + pthread_t thread_; + bool started_; + bool joined_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(Thread); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_UTILITY_THREAD_H_ diff --git a/chromium/mojo/public/gles2/gles2_interface.h b/chromium/mojo/public/gles2/gles2_interface.h new file mode 100644 index 00000000000..19b958ec112 --- /dev/null +++ b/chromium/mojo/public/gles2/gles2_interface.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_GLES2_GLES2_INTERFACE_H_ +#define MOJO_PUBLIC_GLES2_GLES2_INTERFACE_H_ + +#include <GLES2/gl2.h> + +namespace mojo { + +class GLES2Interface { + public: + virtual ~GLES2Interface() {} +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + virtual ReturnType Function PARAMETERS = 0; +#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h" +#undef VISIT_GL_CALL +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_GLES2_GLES2_INTERFACE_H_ diff --git a/chromium/mojo/public/gles2/gles2_private.cc b/chromium/mojo/public/gles2/gles2_private.cc new file mode 100644 index 00000000000..52b47d36c3f --- /dev/null +++ b/chromium/mojo/public/gles2/gles2_private.cc @@ -0,0 +1,92 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/gles2/gles2_private.h" + +#include <assert.h> +#include <stddef.h> + +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/public/gles2/gles2_interface.h" + +static mojo::GLES2Support* g_gles2_support = NULL; +static mojo::GLES2Interface* g_gles2_interface = NULL; + +extern "C" { + +void MojoGLES2Initialize(const MojoAsyncWaiter* async_waiter) { + assert(g_gles2_support); + return g_gles2_support->Initialize(async_waiter); +} + +void MojoGLES2Terminate() { + assert(g_gles2_support); + return g_gles2_support->Terminate(); +} + +MojoGLES2Context MojoGLES2CreateContext( + MojoHandle handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure) { + return g_gles2_support->CreateContext(mojo::MessagePipeHandle(handle), + lost_callback, + animation_callback, + closure); +} + +void MojoGLES2DestroyContext(MojoGLES2Context context) { + return g_gles2_support->DestroyContext(context); +} + +void MojoGLES2MakeCurrent(MojoGLES2Context context) { + assert(g_gles2_support); + g_gles2_support->MakeCurrent(context); + g_gles2_interface = g_gles2_support->GetGLES2InterfaceForCurrentContext(); +} + +void MojoGLES2SwapBuffers() { + assert(g_gles2_support); + return g_gles2_support->SwapBuffers(); +} + +void MojoGLES2RequestAnimationFrames(MojoGLES2Context context) { + assert(g_gles2_support); + return g_gles2_support->RequestAnimationFrames(context); +} + +void MojoGLES2CancelAnimationFrames(MojoGLES2Context context) { + assert(g_gles2_support); + return g_gles2_support->CancelAnimationFrames(context); +} + +void* MojoGLES2GetGLES2Interface(MojoGLES2Context context) { + assert(g_gles2_support); + return g_gles2_support->GetGLES2Interface(context); +} + +void* MojoGLES2GetContextSupport(MojoGLES2Context context) { + assert(g_gles2_support); + return g_gles2_support->GetContextSupport(context); +} + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + ReturnType gl##Function PARAMETERS { \ + return g_gles2_interface->Function ARGUMENTS; \ + } +#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h" +#undef VISIT_GL_CALL + +} // extern "C" + +namespace mojo { + +GLES2Support::~GLES2Support() {} + +void GLES2Support::Init(GLES2Support* gles2_support) { + assert(!g_gles2_support); + g_gles2_support = gles2_support; +} + +} // namespace mojo diff --git a/chromium/mojo/public/gles2/gles2_private.h b/chromium/mojo/public/gles2/gles2_private.h new file mode 100644 index 00000000000..f0a336d60e6 --- /dev/null +++ b/chromium/mojo/public/gles2/gles2_private.h @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_GLES2_GLES2_PRIVATE_H_ +#define MOJO_PUBLIC_GLES2_GLES2_PRIVATE_H_ + +#include <stdint.h> + +#include "mojo/public/c/environment/async_waiter.h" +#include "mojo/public/c/gles2/gles2_export.h" +#include "mojo/public/c/gles2/gles2_types.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +class GLES2Interface; + +// Implementors of the GLES2 APIs can use this interface to install their +// implementation into the mojo_gles2 dynamic library. Mojo clients should not +// call these functions directly. +class MOJO_GLES2_EXPORT GLES2Support { + public: + virtual ~GLES2Support(); + + static void Init(GLES2Support* gles2_support); + + virtual void Initialize(const MojoAsyncWaiter* async_waiter) = 0; + virtual void Terminate() = 0; + virtual MojoGLES2Context CreateContext( + MessagePipeHandle handle, + MojoGLES2ContextLost lost_callback, + MojoGLES2DrawAnimationFrame animation_callback, + void* closure) = 0; + virtual void DestroyContext(MojoGLES2Context context) = 0; + virtual void MakeCurrent(MojoGLES2Context context) = 0; + virtual void SwapBuffers() = 0; + virtual void RequestAnimationFrames(MojoGLES2Context context) = 0; + virtual void CancelAnimationFrames(MojoGLES2Context context) = 0; + virtual void* GetGLES2Interface(MojoGLES2Context context) = 0; + virtual void* GetContextSupport(MojoGLES2Context context) = 0; + virtual GLES2Interface* GetGLES2InterfaceForCurrentContext() = 0; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_GLES2_GLES2_PRIVATE_H_ diff --git a/chromium/mojo/public/interfaces/interface_provider/BUILD.gn b/chromium/mojo/public/interfaces/interface_provider/BUILD.gn new file mode 100644 index 00000000000..55d5b0a37da --- /dev/null +++ b/chromium/mojo/public/interfaces/interface_provider/BUILD.gn @@ -0,0 +1,11 @@ +# 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") + +mojom("interface_provider") { + sources = [ + "interface_provider.mojom", + ] +} diff --git a/chromium/mojo/public/interfaces/interface_provider/interface_provider.mojom b/chromium/mojo/public/interfaces/interface_provider/interface_provider.mojom new file mode 100644 index 00000000000..91e32813da6 --- /dev/null +++ b/chromium/mojo/public/interfaces/interface_provider/interface_provider.mojom @@ -0,0 +1,12 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +[Client=IInterfaceProvider] +interface IInterfaceProvider { + GetInterface(string name, handle<message_pipe> client_handle); +}; + +} diff --git a/chromium/mojo/public/interfaces/service_provider/BUILD.gn b/chromium/mojo/public/interfaces/service_provider/BUILD.gn new file mode 100644 index 00000000000..990d3e9b626 --- /dev/null +++ b/chromium/mojo/public/interfaces/service_provider/BUILD.gn @@ -0,0 +1,11 @@ +# 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") + +mojom("service_provider") { + sources = [ + "service_provider.mojom", + ] +} diff --git a/chromium/mojo/public/interfaces/service_provider/service_provider.mojom b/chromium/mojo/public/interfaces/service_provider/service_provider.mojom new file mode 100644 index 00000000000..190c85c2d78 --- /dev/null +++ b/chromium/mojo/public/interfaces/service_provider/service_provider.mojom @@ -0,0 +1,19 @@ +// 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. + +module mojo { + +[Client=ServiceProvider] +interface ServiceProvider { + // Loads url. mojo:{service} will result in the user of the value of the + // --origin flag to the shell being used. + ConnectToService(string service_url, + string service_name, + handle<message_pipe> client_handle, + // ignored for client making request, filled in by system for + // implementor. + string requestor_url); +}; + +} diff --git a/chromium/mojo/public/interfaces/shell/BUILD.gn b/chromium/mojo/public/interfaces/shell/BUILD.gn new file mode 100644 index 00000000000..c11ae70a311 --- /dev/null +++ b/chromium/mojo/public/interfaces/shell/BUILD.gn @@ -0,0 +1,11 @@ +# 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") + +mojom("shell") { + sources = [ + "shell.mojom", + ] +} diff --git a/chromium/mojo/public/js/bindings/BUILD.gn b/chromium/mojo/public/js/bindings/BUILD.gn new file mode 100644 index 00000000000..0f305e924ec --- /dev/null +++ b/chromium/mojo/public/js/bindings/BUILD.gn @@ -0,0 +1,10 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("bindings") { + sources = [ + "constants.cc", + "constants.h", + ] +} diff --git a/chromium/mojo/public/js/bindings/codec.js b/chromium/mojo/public/js/bindings/codec.js new file mode 100644 index 00000000000..84c86c12991 --- /dev/null +++ b/chromium/mojo/public/js/bindings/codec.js @@ -0,0 +1,739 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/bindings/codec", [ + "mojo/public/js/bindings/unicode" +], function(unicode) { + + var kErrorUnsigned = "Passing negative value to unsigned"; + + // Memory ------------------------------------------------------------------- + + var kAlignment = 8; + var kHighWordMultiplier = 0x100000000; + var kHostIsLittleEndian = (function () { + var endianArrayBuffer = new ArrayBuffer(2); + var endianUint8Array = new Uint8Array(endianArrayBuffer); + var endianUint16Array = new Uint16Array(endianArrayBuffer); + endianUint16Array[0] = 1; + return endianUint8Array[0] == 1; + })(); + + function align(size) { + return size + (kAlignment - (size % kAlignment)) % kAlignment; + } + + function getInt64(dataView, byteOffset, value) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = dataView.getUint32(byteOffset, kHostIsLittleEndian); + hi = dataView.getInt32(byteOffset + 4, kHostIsLittleEndian); + } else { + hi = dataView.getInt32(byteOffset, kHostIsLittleEndian); + lo = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + function getUint64(dataView, byteOffset, value) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = dataView.getUint32(byteOffset, kHostIsLittleEndian); + hi = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian); + } else { + hi = dataView.getUint32(byteOffset, kHostIsLittleEndian); + lo = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + function setInt64(dataView, byteOffset, value) { + var hi = Math.floor(value / kHighWordMultiplier); + if (kHostIsLittleEndian) { + dataView.setInt32(byteOffset, value, kHostIsLittleEndian); + dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian); + } else { + dataView.setInt32(byteOffset, hi, kHostIsLittleEndian); + dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian); + } + } + + function setUint64(dataView, byteOffset, value) { + var hi = (value / kHighWordMultiplier) | 0; + if (kHostIsLittleEndian) { + dataView.setInt32(byteOffset, value, kHostIsLittleEndian); + dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian); + } else { + dataView.setInt32(byteOffset, hi, kHostIsLittleEndian); + dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian); + } + } + + function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) { + (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer)); + } + + // Buffer ------------------------------------------------------------------- + + function Buffer(sizeOrArrayBuffer) { + if (sizeOrArrayBuffer instanceof ArrayBuffer) { + this.arrayBuffer = sizeOrArrayBuffer; + } else { + this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer); + }; + + this.dataView = new DataView(this.arrayBuffer); + this.next = 0; + } + + Buffer.prototype.alloc = function(size) { + var pointer = this.next; + this.next += size; + if (this.next > this.arrayBuffer.byteLength) { + var newSize = (1.5 * (this.arrayBuffer.byteLength + size)) | 0; + this.grow(newSize); + } + return pointer; + }; + + Buffer.prototype.grow = function(size) { + var newArrayBuffer = new ArrayBuffer(size); + copyArrayBuffer(newArrayBuffer, this.arrayBuffer); + this.arrayBuffer = newArrayBuffer; + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.trim = function() { + this.arrayBuffer = this.arrayBuffer.slice(0, this.next); + this.dataView = new DataView(this.arrayBuffer); + }; + + // Constants ---------------------------------------------------------------- + + var kArrayHeaderSize = 8; + var kStructHeaderSize = 8; + var kMessageHeaderSize = 16; + var kMessageWithRequestIDHeaderSize = 24; + + // Decoder ------------------------------------------------------------------ + + function Decoder(buffer, handles, base) { + this.buffer = buffer; + this.handles = handles; + this.base = base; + this.next = base; + } + + Decoder.prototype.skip = function(offset) { + this.next += offset; + }; + + Decoder.prototype.readInt8 = function() { + var result = this.buffer.dataView.getInt8(this.next, kHostIsLittleEndian); + this.next += 1; + return result; + }; + + Decoder.prototype.readUint8 = function() { + var result = this.buffer.dataView.getUint8(this.next, kHostIsLittleEndian); + this.next += 1; + return result; + }; + + Decoder.prototype.readInt16 = function() { + var result = this.buffer.dataView.getInt16(this.next, kHostIsLittleEndian); + this.next += 2; + return result; + }; + + Decoder.prototype.readUint16 = function() { + var result = this.buffer.dataView.getUint16(this.next, kHostIsLittleEndian); + this.next += 2; + return result; + }; + + Decoder.prototype.readInt32 = function() { + var result = this.buffer.dataView.getInt32(this.next, kHostIsLittleEndian); + this.next += 4; + return result; + }; + + Decoder.prototype.readUint32 = function() { + var result = this.buffer.dataView.getUint32(this.next, kHostIsLittleEndian); + this.next += 4; + return result; + }; + + Decoder.prototype.readInt64 = function() { + var result = getInt64(this.buffer.dataView, this.next, kHostIsLittleEndian); + this.next += 8; + return result; + }; + + Decoder.prototype.readUint64 = function() { + var result = getUint64( + this.buffer.dataView, this.next, kHostIsLittleEndian); + this.next += 8; + return result; + }; + + Decoder.prototype.readFloat = function() { + var result = this.buffer.dataView.getFloat32( + this.next, kHostIsLittleEndian); + this.next += 4; + return result; + }; + + Decoder.prototype.readDouble = function() { + var result = this.buffer.dataView.getFloat64( + this.next, kHostIsLittleEndian); + this.next += 8; + return result; + }; + + Decoder.prototype.decodePointer = function() { + // TODO(abarth): To correctly decode a pointer, we need to know the real + // base address of the array buffer. + var offsetPointer = this.next; + var offset = this.readUint64(); + if (!offset) + return 0; + return offsetPointer + offset; + }; + + Decoder.prototype.decodeAndCreateDecoder = function(pointer) { + return new Decoder(this.buffer, this.handles, pointer); + }; + + Decoder.prototype.decodeHandle = function() { + return this.handles[this.readUint32()]; + }; + + Decoder.prototype.decodeString = function() { + var numberOfBytes = this.readUint32(); + var numberOfElements = this.readUint32(); + var base = this.next; + this.next += numberOfElements; + return unicode.decodeUtf8String( + new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements)); + }; + + Decoder.prototype.decodeArray = function(cls) { + var numberOfBytes = this.readUint32(); + var numberOfElements = this.readUint32(); + var val = new Array(numberOfElements); + for (var i = 0; i < numberOfElements; ++i) { + val[i] = cls.decode(this); + } + return val; + }; + + Decoder.prototype.decodeStruct = function(cls) { + return cls.decode(this); + }; + + Decoder.prototype.decodeStructPointer = function(cls) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return cls.decode(this.decodeAndCreateDecoder(pointer)); + }; + + Decoder.prototype.decodeArrayPointer = function(cls) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return this.decodeAndCreateDecoder(pointer).decodeArray(cls); + }; + + Decoder.prototype.decodeStringPointer = function() { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return this.decodeAndCreateDecoder(pointer).decodeString(); + }; + + // Encoder ------------------------------------------------------------------ + + function Encoder(buffer, handles, base) { + this.buffer = buffer; + this.handles = handles; + this.base = base; + this.next = base; + } + + Encoder.prototype.skip = function(offset) { + this.next += offset; + }; + + Encoder.prototype.writeInt8 = function(val) { + // NOTE: Endianness doesn't come into play for single bytes. + this.buffer.dataView.setInt8(this.next, val); + this.next += 1; + }; + + Encoder.prototype.writeUint8 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + // NOTE: Endianness doesn't come into play for single bytes. + this.buffer.dataView.setUint8(this.next, val); + this.next += 1; + }; + + Encoder.prototype.writeInt16 = function(val) { + this.buffer.dataView.setInt16(this.next, val, kHostIsLittleEndian); + this.next += 2; + }; + + Encoder.prototype.writeUint16 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.dataView.setUint16(this.next, val, kHostIsLittleEndian); + this.next += 2; + }; + + Encoder.prototype.writeInt32 = function(val) { + this.buffer.dataView.setInt32(this.next, val, kHostIsLittleEndian); + this.next += 4; + }; + + Encoder.prototype.writeUint32 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.dataView.setUint32(this.next, val, kHostIsLittleEndian); + this.next += 4; + }; + + Encoder.prototype.writeInt64 = function(val) { + setInt64(this.buffer.dataView, this.next, val); + this.next += 8; + }; + + Encoder.prototype.writeUint64 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + setUint64(this.buffer.dataView, this.next, val); + this.next += 8; + }; + + Encoder.prototype.writeFloat = function(val) { + this.buffer.dataView.setFloat32(this.next, val, kHostIsLittleEndian); + this.next += 4; + }; + + Encoder.prototype.writeDouble = function(val) { + this.buffer.dataView.setFloat64(this.next, val, kHostIsLittleEndian); + this.next += 8; + }; + + Encoder.prototype.encodePointer = function(pointer) { + if (!pointer) + return this.writeUint64(0); + // TODO(abarth): To correctly encode a pointer, we need to know the real + // base address of the array buffer. + var offset = pointer - this.next; + this.writeUint64(offset); + }; + + Encoder.prototype.createAndEncodeEncoder = function(size) { + var pointer = this.buffer.alloc(align(size)); + this.encodePointer(pointer); + return new Encoder(this.buffer, this.handles, pointer); + }; + + Encoder.prototype.encodeHandle = function(handle) { + this.handles.push(handle); + this.writeUint32(this.handles.length - 1); + }; + + Encoder.prototype.encodeString = function(val) { + var base = this.next + kArrayHeaderSize; + var numberOfElements = unicode.encodeUtf8String( + val, new Uint8Array(this.buffer.arrayBuffer, base)); + var numberOfBytes = kArrayHeaderSize + numberOfElements; + this.writeUint32(numberOfBytes); + this.writeUint32(numberOfElements); + this.next += numberOfElements; + }; + + Encoder.prototype.encodeArray = function(cls, val) { + var numberOfElements = val.length; + var numberOfBytes = kArrayHeaderSize + cls.encodedSize * numberOfElements; + this.writeUint32(numberOfBytes); + this.writeUint32(numberOfElements); + for (var i = 0; i < numberOfElements; ++i) { + cls.encode(this, val[i]); + } + }; + + Encoder.prototype.encodeStruct = function(cls, val) { + return cls.encode(this, val); + }; + + Encoder.prototype.encodeStructPointer = function(cls, val) { + if (!val) { + this.encodePointer(val); + return; + } + var encoder = this.createAndEncodeEncoder(cls.encodedSize); + cls.encode(encoder, val); + }; + + Encoder.prototype.encodeArrayPointer = function(cls, val) { + if (!val) { + this.encodePointer(val); + return; + } + var encodedSize = kArrayHeaderSize + cls.encodedSize * val.length; + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeArray(cls, val); + }; + + Encoder.prototype.encodeStringPointer = function(val) { + if (!val) { + this.encodePointer(val); + return; + } + var encodedSize = kArrayHeaderSize + unicode.utf8Length(val); + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeString(val); + }; + + // Message ------------------------------------------------------------------ + + var kMessageExpectsResponse = 1 << 0; + var kMessageIsResponse = 1 << 1; + + // Skip over num_bytes, num_fields, and message_name. + var kFlagsOffset = 4 + 4 + 4; + + // Skip over num_bytes, num_fields, message_name, and flags. + var kRequestIDOffset = 4 + 4 + 4 + 4; + + function Message(buffer, handles) { + this.buffer = buffer; + this.handles = handles; + } + + Message.prototype.setRequestID = function(requestID) { + // TODO(darin): Verify that space was reserved for this field! + setUint64(this.buffer.dataView, kRequestIDOffset, requestID); + }; + + Message.prototype.getFlags = function() { + return this.buffer.dataView.getUint32(kFlagsOffset, kHostIsLittleEndian); + }; + + // MessageBuilder ----------------------------------------------------------- + + function MessageBuilder(messageName, payloadSize) { + // Currently, we don't compute the payload size correctly ahead of time. + // Instead, we resize the buffer at the end. + var numberOfBytes = kMessageHeaderSize + payloadSize; + this.buffer = new Buffer(numberOfBytes); + this.handles = []; + var encoder = this.createEncoder(kMessageHeaderSize); + encoder.writeUint32(kMessageHeaderSize); + encoder.writeUint32(2); // num_fields. + encoder.writeUint32(messageName); + encoder.writeUint32(0); // flags. + } + + MessageBuilder.prototype.createEncoder = function(size) { + var pointer = this.buffer.alloc(size); + return new Encoder(this.buffer, this.handles, pointer); + }; + + MessageBuilder.prototype.encodeStruct = function(cls, val) { + cls.encode(this.createEncoder(cls.encodedSize), val); + }; + + MessageBuilder.prototype.finish = function() { + // TODO(abarth): Rather than resizing the buffer at the end, we could + // compute the size we need ahead of time, like we do in C++. + this.buffer.trim(); + var message = new Message(this.buffer, this.handles); + this.buffer = null; + this.handles = null; + this.encoder = null; + return message; + }; + + // MessageWithRequestIDBuilder ----------------------------------------------- + + function MessageWithRequestIDBuilder(messageName, payloadSize, flags, + requestID) { + // Currently, we don't compute the payload size correctly ahead of time. + // Instead, we resize the buffer at the end. + var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize; + this.buffer = new Buffer(numberOfBytes); + this.handles = []; + var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize); + encoder.writeUint32(kMessageWithRequestIDHeaderSize); + encoder.writeUint32(3); // num_fields. + encoder.writeUint32(messageName); + encoder.writeUint32(flags); + encoder.writeUint64(requestID); + } + + MessageWithRequestIDBuilder.prototype = + Object.create(MessageBuilder.prototype); + + MessageWithRequestIDBuilder.prototype.constructor = + MessageWithRequestIDBuilder; + + // MessageReader ------------------------------------------------------------ + + function MessageReader(message) { + this.decoder = new Decoder(message.buffer, message.handles, 0); + var messageHeaderSize = this.decoder.readUint32(); + this.payloadSize = + message.buffer.arrayBuffer.byteLength - messageHeaderSize; + var numFields = this.decoder.readUint32(); + this.messageName = this.decoder.readUint32(); + this.flags = this.decoder.readUint32(); + if (numFields >= 3) + this.requestID = this.decoder.readUint64(); + this.decoder.skip(messageHeaderSize - this.decoder.next); + } + + MessageReader.prototype.decodeStruct = function(cls) { + return cls.decode(this.decoder); + }; + + // Built-in types ----------------------------------------------------------- + + function Int8() { + } + + Int8.encodedSize = 1; + + Int8.decode = function(decoder) { + return decoder.readInt8(); + }; + + Int8.encode = function(encoder, val) { + encoder.writeInt8(val); + }; + + Uint8.encode = function(encoder, val) { + encoder.writeUint8(val); + }; + + function Uint8() { + } + + Uint8.encodedSize = 1; + + Uint8.decode = function(decoder) { + return decoder.readUint8(); + }; + + Uint8.encode = function(encoder, val) { + encoder.writeUint8(val); + }; + + function Int16() { + } + + Int16.encodedSize = 2; + + Int16.decode = function(decoder) { + return decoder.readInt16(); + }; + + Int16.encode = function(encoder, val) { + encoder.writeInt16(val); + }; + + function Uint16() { + } + + Uint16.encodedSize = 2; + + Uint16.decode = function(decoder) { + return decoder.readUint16(); + }; + + Uint16.encode = function(encoder, val) { + encoder.writeUint16(val); + }; + + function Int32() { + } + + Int32.encodedSize = 4; + + Int32.decode = function(decoder) { + return decoder.readInt32(); + }; + + Int32.encode = function(encoder, val) { + encoder.writeInt32(val); + }; + + function Uint32() { + } + + Uint32.encodedSize = 4; + + Uint32.decode = function(decoder) { + return decoder.readUint32(); + }; + + Uint32.encode = function(encoder, val) { + encoder.writeUint32(val); + }; + + function Int64() { + } + + Int64.encodedSize = 8; + + Int64.decode = function(decoder) { + return decoder.readInt64(); + }; + + Int64.encode = function(encoder, val) { + encoder.writeInt64(val); + }; + + function Uint64() { + } + + Uint64.encodedSize = 8; + + Uint64.decode = function(decoder) { + return decoder.readUint64(); + }; + + Uint64.encode = function(encoder, val) { + encoder.writeUint64(val); + }; + + function String() { + }; + + String.encodedSize = 8; + + String.decode = function(decoder) { + return decoder.decodeStringPointer(); + }; + + String.encode = function(encoder, val) { + encoder.encodeStringPointer(val); + }; + + + function Float() { + } + + Float.encodedSize = 4; + + Float.decode = function(decoder) { + return decoder.readFloat(); + }; + + Float.encode = function(encoder, val) { + encoder.writeFloat(val); + }; + + function Double() { + } + + Double.encodedSize = 8; + + Double.decode = function(decoder) { + return decoder.readDouble(); + }; + + Double.encode = function(encoder, val) { + encoder.writeDouble(val); + }; + + function PointerTo(cls) { + this.cls = cls; + } + + PointerTo.prototype.encodedSize = 8; + + PointerTo.prototype.decode = function(decoder) { + var pointer = decoder.decodePointer(); + if (!pointer) { + return null; + } + return this.cls.decode(decoder.decodeAndCreateDecoder(pointer)); + }; + + PointerTo.prototype.encode = function(encoder, val) { + if (!val) { + encoder.encodePointer(val); + return; + } + var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize); + this.cls.encode(objectEncoder, val); + }; + + function ArrayOf(cls) { + this.cls = cls; + } + + ArrayOf.prototype.encodedSize = 8; + + ArrayOf.prototype.decode = function(decoder) { + return decoder.decodeArrayPointer(this.cls); + }; + + ArrayOf.prototype.encode = function(encoder, val) { + encoder.encodeArrayPointer(this.cls, val); + }; + + function Handle() { + } + + Handle.encodedSize = 4; + + Handle.decode = function(decoder) { + return decoder.decodeHandle(); + }; + + Handle.encode = function(encoder, val) { + encoder.encodeHandle(val); + }; + + var exports = {}; + exports.align = align; + exports.Buffer = Buffer; + exports.Message = Message; + exports.MessageBuilder = MessageBuilder; + exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder; + exports.MessageReader = MessageReader; + exports.kArrayHeaderSize = kArrayHeaderSize; + exports.kStructHeaderSize = kStructHeaderSize; + exports.kMessageHeaderSize = kMessageHeaderSize; + exports.kMessageExpectsResponse = kMessageExpectsResponse; + exports.kMessageIsResponse = kMessageIsResponse; + exports.Int8 = Int8; + exports.Uint8 = Uint8; + exports.Int16 = Int16; + exports.Uint16 = Uint16; + exports.Int32 = Int32; + exports.Uint32 = Uint32; + exports.Int64 = Int64; + exports.Uint64 = Uint64; + exports.Float = Float; + exports.Double = Double; + exports.String = String; + exports.PointerTo = PointerTo; + exports.ArrayOf = ArrayOf; + exports.Handle = Handle; + return exports; +}); diff --git a/chromium/mojo/public/js/bindings/connection.js b/chromium/mojo/public/js/bindings/connection.js new file mode 100644 index 00000000000..ebf60adb7f8 --- /dev/null +++ b/chromium/mojo/public/js/bindings/connection.js @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/bindings/connection", [ + "mojo/public/js/bindings/router", +], function(router) { + + function Connection(handle, localFactory, remoteFactory) { + this.router_ = new router.Router(handle); + this.remote = new remoteFactory(this.router_); + this.local = new localFactory(this.remote); + this.router_.setIncomingReceiver(this.local); + } + + Connection.prototype.close = function() { + this.router_.close(); + this.router_ = null; + this.local = null; + this.remote = null; + }; + + Connection.prototype.encounteredError = function() { + return this.router_.encounteredError(); + }; + + var exports = {}; + exports.Connection = Connection; + return exports; +}); diff --git a/chromium/mojo/public/js/bindings/connector.js b/chromium/mojo/public/js/bindings/connector.js new file mode 100644 index 00000000000..51022b0e45f --- /dev/null +++ b/chromium/mojo/public/js/bindings/connector.js @@ -0,0 +1,109 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/bindings/connector", [ + "mojo/public/js/bindings/codec", + "mojo/public/js/bindings/core", + "mojo/public/js/bindings/support", +], function(codec, core, support) { + + function Connector(handle) { + this.handle_ = handle; + this.dropWrites_ = false; + this.error_ = false; + this.incomingReceiver_ = null; + this.readWaitCookie_ = null; + this.errorHandler_ = null; + + this.waitToReadMore_(); + } + + Connector.prototype.close = function() { + if (this.readWaitCookie_) { + support.cancelWait(this.readWaitCookie_); + this.readWaitCookie_ = null; + } + if (this.handle_ != null) { + core.close(this.handle_); + this.handle_ = null; + } + }; + + Connector.prototype.accept = function(message) { + if (this.error_) + return false; + + if (this.dropWrites_) + return true; + + var result = core.writeMessage(this.handle_, + new Uint8Array(message.buffer.arrayBuffer), + message.handles, + core.WRITE_MESSAGE_FLAG_NONE); + switch (result) { + case core.RESULT_OK: + // The handles were successfully transferred, so we don't own them + // anymore. + message.handles = []; + break; + case core.RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any + // backlog of incoming messages before regarding the message pipe as + // closed. + this.dropWrites_ = true; + break; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; + }; + + Connector.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + Connector.prototype.setErrorHandler = function(handler) { + this.errorHandler_ = handler; + }; + + Connector.prototype.encounteredError = function() { + return this.error_; + }; + + Connector.prototype.waitToReadMore_ = function() { + this.readWaitCookie_ = support.asyncWait(this.handle_, + core.HANDLE_SIGNAL_READABLE, + this.readMore_.bind(this)); + }; + + Connector.prototype.readMore_ = function(result) { + for (;;) { + var read = core.readMessage(this.handle_, + core.READ_MESSAGE_FLAG_NONE); + if (read.result == core.RESULT_SHOULD_WAIT) { + this.waitToReadMore_(); + return; + } + if (read.result != core.RESULT_OK) { + this.error_ = true; + if (this.errorHandler_) + this.errorHandler_.onError(read.result); + return; + } + var buffer = new codec.Buffer(read.buffer); + var message = new codec.Message(buffer, read.handles); + if (this.incomingReceiver_) { + this.incomingReceiver_.accept(message); + } + } + }; + + var exports = {}; + exports.Connector = Connector; + return exports; +}); diff --git a/chromium/mojo/public/js/bindings/constants.cc b/chromium/mojo/public/js/bindings/constants.cc new file mode 100644 index 00000000000..239b67d4139 --- /dev/null +++ b/chromium/mojo/public/js/bindings/constants.cc @@ -0,0 +1,15 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/js/bindings/constants.h" + +namespace mojo { + +const char kCodecModuleName[] = "mojo/public/js/bindings/codec"; +const char kConnectionModuleName[] = "mojo/public/js/bindings/connection"; +const char kConnectorModuleName[] = "mojo/public/js/bindings/connector"; +const char kUnicodeModuleName[] = "mojo/public/js/bindings/unicode"; +const char kRouterModuleName[] = "mojo/public/js/bindings/router"; + +} // namespace mojo diff --git a/chromium/mojo/public/js/bindings/constants.h b/chromium/mojo/public/js/bindings/constants.h new file mode 100644 index 00000000000..50e5d7c0248 --- /dev/null +++ b/chromium/mojo/public/js/bindings/constants.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_ +#define MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_ + +namespace mojo { + +// JavaScript module names: +extern const char kCodecModuleName[]; +extern const char kConnectionModuleName[]; +extern const char kConnectorModuleName[]; +extern const char kUnicodeModuleName[]; +extern const char kRouterModuleName[]; + +} // namespace mojo + +#endif // MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_ diff --git a/chromium/mojo/public/js/bindings/core.js b/chromium/mojo/public/js/bindings/core.js new file mode 100644 index 00000000000..0aff5310833 --- /dev/null +++ b/chromium/mojo/public/js/bindings/core.js @@ -0,0 +1,215 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Module "mojo/public/js/bindings/core" +// +// Note: This file is for documentation purposes only. The code here is not +// actually executed. The real module is implemented natively in Mojo. +// +// This module provides the JavaScript bindings for mojo/public/c/system/core.h. +// Refer to that file for more detailed documentation for equivalent methods. + +while (1); + +/** + * MojoHandle: An opaque handles to a Mojo object (e.g. a message pipe). + */ +var kInvalidHandle; + +/** + * MojoResult {number}: Result codes for Mojo operations. + * See core.h for more information. + */ +var RESULT_OK; +var RESULT_CANCELLED; +var RESULT_UNKNOWN; +var RESULT_INVALID_ARGUMENT; +var RESULT_DEADLINE_EXCEEDED; +var RESULT_NOT_FOUND; +var RESULT_ALREADY_EXISTS; +var RESULT_PERMISSION_DENIED; +var RESULT_RESOURCE_EXHAUSTED; +var RESULT_FAILED_PRECONDITION; +var RESULT_ABORTED; +var RESULT_OUT_OF_RANGE; +var RESULT_UNIMPLEMENTED; +var RESULT_INTERNAL; +var RESULT_UNAVAILABLE; +var RESULT_DATA_LOSS; +var RESULT_BUSY; +var RESULT_SHOULD_WAIT; + +/** + * MojoDeadline {number}: Used to specify deadlines (timeouts), in microseconds. + * See core.h for more information. + */ +var DEADLINE_INDEFINITE; + +/** + * MojoHandleSignals: Used to specify signals that can be waited on for a handle + *(and which can be triggered), e.g., the ability to read or write to + * the handle. + * See core.h for more information. + */ +var HANDLE_SIGNAL_NONE; +var HANDLE_SIGNAL_READABLE; +var HANDLE_SIGNAL_WRITABLE; + +/* + * MojoWriteMessageFlags: Used to specify different modes to |writeMessage()|. + * See core.h for more information. + */ +var WRITE_MESSAGE_FLAG_NONE; + +/** + * MojoReadMessageFlags: Used to specify different modes to |readMessage()|. + * See core.h for more information. + */ +var READ_MESSAGE_FLAG_NONE; +var READ_MESSAGE_FLAG_MAY_DISCARD; + +/** + * MojoCreateDataPipeOptions: Used to specify creation parameters for a data + * pipe to |createDataPipe()|. + * See core.h for more information. + */ +dictionary MojoCreateDataPipeOptions { + MojoCreateDataPipeOptionsFlags flags; // See below. + int32 elementNumBytes; // The size of an element, in bytes. + int32 capacityNumBytes; // The capacity of the data pipe, in bytes. +}; + +// MojoCreateDataPipeOptionsFlags +var CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; +var CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD; + +/* + * MojoWriteDataFlags: Used to specify different modes to |writeData()|. + * See core.h for more information. + */ +var WRITE_DATA_FLAG_NONE; +var WRITE_DATA_FLAG_ALL_OR_NONE; + +/** + * MojoReadDataFlags: Used to specify different modes to |readData()|. + * See core.h for more information. + */ +var READ_DATA_FLAG_NONE; +var READ_DATA_FLAG_ALL_OR_NONE; +var READ_DATA_FLAG_DISCARD; +var READ_DATA_FLAG_QUERY; + +/** + * Closes the given |handle|. See MojoClose for more info. + * @param {MojoHandle} Handle to close. + * @return {MojoResult} Result code. + */ +function close(handle) { [native code] } + +/** + * Waits on the given handle until a signal indicated by |signals| is + * satisfied or until |deadline| is passed. See MojoWait for more information. + * + * @param {MojoHandle} handle Handle to wait on. + * @param {MojoHandleSignals} signals Specifies the condition to wait for. + * @param {MojoDeadline} deadline Stops waiting if this is reached. + * @return {MojoResult} Result code. + */ +function wait(handle, signals, deadline) { [native code] } + +/** + * Waits on |handles[0]|, ..., |handles[handles.length-1]| for at least one of + * them to satisfy the state indicated by |flags[0]|, ..., + * |flags[handles.length-1]|, respectively, or until |deadline| has passed. + * See MojoWaitMany for more information. + * + * @param {Array.MojoHandle} handles Handles to wait on. + * @param {Array.MojoHandleSignals} signals Specifies the condition to wait for, + * for each corresponding handle. Must be the same length as |handles|. + * @param {MojoDeadline} deadline Stops waiting if this is reached. + * @return {MojoResult} Result code. + */ +function waitMany(handles, signals, deadline) { [native code] } + +/** + * Creates a message pipe. This function always succeeds. + * See MojoCreateMessagePipe for more information on message pipes. + * + * @return {MessagePipe} An object of the form { + * handle0, + * handle1, + * } + * where |handle0| and |handle1| are MojoHandles to each end of the channel. + */ +function createMessagePipe() { [native code] } + +/** + * Writes a message to the message pipe endpoint given by |handle|. See + * MojoWriteMessage for more information, including return codes. + * + * @param {MojoHandle} handle The endpoint to write to. + * @param {ArrayBufferView} buffer The message data. May be empty. + * @param {Array.MojoHandle} handlesArray Any handles to attach. Handles are + * transferred on success and will no longer be valid. May be empty. + * @param {MojoWriteMessageFlags} flags Flags. + * @return {MojoResult} Result code. + */ +function writeMessage(handle, buffer, handlesArray, flags) { [native code] } + +/** + * Reads a message from the message pipe endpoint given by |handle|. See + * MojoReadMessage for more information, including return codes. + * + * @param {MojoHandle} handle The endpoint to read from. + * @param {MojoReadMessageFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * buffer, // An ArrayBufferView of the message data (only on success). + * handles // An array of MojoHandles transferred, if any. + * } + */ +function readMessage(handle, flags) { [native code] } + +/** + * Creates a data pipe, which is a unidirectional communication channel for + * unframed data, with the given options. See MojoCreateDataPipe for more + * more information, including return codes. + * + * @param {MojoCreateDataPipeOptions} optionsDict Options to control the data + * pipe parameters. May be null. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * producerHandle, // MojoHandle to use with writeData (only on success). + * consumerHandle, // MojoHandle to use with readData (only on success). + * } + */ +function createDataPipe(optionsDict) { [native code] } + +/** + * Writes the given data to the data pipe producer given by |handle|. See + * MojoWriteData for more information, including return codes. + * + * @param {MojoHandle} handle A producerHandle returned by createDataPipe. + * @param {ArrayBufferView} buffer The data to write. + * @param {MojoWriteDataFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * numBytes, // The number of bytes written. + * } + */ +function writeData(handle, buffer, flags) { [native code] } + +/** + * Reads data from the data pipe consumer given by |handle|. May also + * be used to discard data. See MojoReadData for more information, including + * return codes. + * + * @param {MojoHandle} handle A consumerHandle returned by createDataPipe. + * @param {MojoReadDataFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * buffer, // An ArrayBufferView of the data read (only on success). + * } + */ +function readData(handle, flags) { [native code] } diff --git a/chromium/mojo/public/js/bindings/router.js b/chromium/mojo/public/js/bindings/router.js new file mode 100644 index 00000000000..2718e8b0b78 --- /dev/null +++ b/chromium/mojo/public/js/bindings/router.js @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/bindings/router", [ + "mojo/public/js/bindings/codec", + "mojo/public/js/bindings/connector", +], function(codec, connector) { + + function Router(handle) { + this.connector_ = new connector.Connector(handle); + this.incomingReceiver_ = null; + this.nextRequestID_ = 0; + this.responders_ = {}; + + this.connector_.setIncomingReceiver({ + accept: this.handleIncomingMessage_.bind(this), + }); + this.connector_.setErrorHandler({ + onError: this.handleConnectionError_.bind(this), + }); + } + + Router.prototype.close = function() { + this.responders_ = {}; // Drop any responders. + this.connector_.close(); + }; + + Router.prototype.accept = function(message) { + this.connector_.accept(message); + }; + + Router.prototype.reject = function(message) { + // TODO(mpcomplete): no way to trasmit errors over a Connection. + }; + + Router.prototype.acceptWithResponder = function(message, responder) { + // Reserve 0 in case we want it to convey special meaning in the future. + var requestID = this.nextRequestID_++; + if (requestID == 0) + requestID = this.nextRequestID_++; + + message.setRequestID(requestID); + var result = this.connector_.accept(message); + + this.responders_[requestID] = responder; + + // TODO(mpcomplete): accept should return a Promise too, maybe? + if (result) + return Promise.resolve(); + return Promise.reject(Error("Connection error")); + }; + + Router.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + Router.prototype.encounteredError = function() { + return this.connector_.encounteredError(); + }; + + Router.prototype.handleIncomingMessage_ = function(message) { + var flags = message.getFlags(); + if (flags & codec.kMessageExpectsResponse) { + if (this.incomingReceiver_) { + this.incomingReceiver_.acceptWithResponder(message, this); + } else { + // If we receive a request expecting a response when the client is not + // listening, then we have no choice but to tear down the pipe. + this.close(); + } + } else if (flags & codec.kMessageIsResponse) { + var reader = new codec.MessageReader(message); + var requestID = reader.requestID; + var responder = this.responders_[requestID]; + delete this.responders_[requestID]; + responder.accept(message); + } else { + if (this.incomingReceiver_) + this.incomingReceiver_.accept(message); + } + }; + + Router.prototype.handleConnectionError_ = function(result) { + for (var each in this.responders_) + this.responders_[each].reject(result); + this.close(); + }; + + var exports = {}; + exports.Router = Router; + return exports; +}); diff --git a/chromium/mojo/public/js/bindings/support.js b/chromium/mojo/public/js/bindings/support.js new file mode 100644 index 00000000000..58df6bd4d02 --- /dev/null +++ b/chromium/mojo/public/js/bindings/support.js @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Module "mojo/public/js/bindings/support" +// +// Note: This file is for documentation purposes only. The code here is not +// actually executed. The real module is implemented natively in Mojo. + +while (1); + +/* + * Waits on the given handle until the state indicated by |signals| is + * satisfied. + * + * @param {MojoHandle} handle The handle to wait on. + * @param {MojoHandleSignals} signals Specifies the condition to wait for. + * @param {function (mojoResult)} callback Called with the result the wait is + * complete. See MojoWait for possible result codes. + * + * @return {MojoWaitId} A waitId that can be passed to cancelWait to cancel the + * wait. + */ +function asyncWait(handle, signals, callback) { [native code] } + +/* + * Cancels the asyncWait operation specified by the given |waitId|. + * @param {MojoWaitId} waitId The waitId returned by asyncWait. + */ +function cancelWait(waitId) { [native code] } diff --git a/chromium/mojo/public/js/bindings/unicode.js b/chromium/mojo/public/js/bindings/unicode.js new file mode 100644 index 00000000000..ba0f00f95ba --- /dev/null +++ b/chromium/mojo/public/js/bindings/unicode.js @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Defines functions for translating between JavaScript strings and UTF8 strings + * stored in ArrayBuffers. There is much room for optimization in this code if + * it proves necessary. + */ +define("mojo/public/js/bindings/unicode", function() { + /** + * Decodes the UTF8 string from the given buffer. + * @param {ArrayBufferView} buffer The buffer containing UTF8 string data. + * @return {string} The corresponding JavaScript string. + */ + function decodeUtf8String(buffer) { + return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer))); + } + + /** + * Encodes the given JavaScript string into UTF8. + * @param {string} str The string to encode. + * @param {ArrayBufferView} outputBuffer The buffer to contain the result. + * Should be pre-allocated to hold enough space. Use |utf8Length| to determine + * how much space is required. + * @return {number} The number of bytes written to |outputBuffer|. + */ + function encodeUtf8String(str, outputBuffer) { + var utf8String = unescape(encodeURIComponent(str)); + if (outputBuffer.length < utf8String.length) + throw new Error("Buffer too small for encodeUtf8String"); + for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++) + outputBuffer[i] = utf8String.charCodeAt(i); + return i; + } + + /** + * Returns the number of bytes that a UTF8 encoding of the JavaScript string + * |str| would occupy. + */ + function utf8Length(str) { + var utf8String = unescape(encodeURIComponent(str)); + return utf8String.length; + } + + var exports = {}; + exports.decodeUtf8String = decodeUtf8String; + exports.encodeUtf8String = encodeUtf8String; + exports.utf8Length = utf8Length; + return exports; +}); diff --git a/chromium/mojo/public/platform/native/system_thunks.cc b/chromium/mojo/public/platform/native/system_thunks.cc new file mode 100644 index 00000000000..6fdc4f4530f --- /dev/null +++ b/chromium/mojo/public/platform/native/system_thunks.cc @@ -0,0 +1,170 @@ +// 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 "mojo/public/platform/native/system_thunks.h" + +#include <assert.h> + +extern "C" { + +static MojoSystemThunks g_thunks = {0}; + +MojoTimeTicks MojoGetTimeTicksNow() { + assert(g_thunks.GetTimeTicksNow); + return g_thunks.GetTimeTicksNow(); +} + +MojoResult MojoClose(MojoHandle handle) { + assert(g_thunks.Close); + return g_thunks.Close(handle); +} + +MojoResult MojoWait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline) { + assert(g_thunks.Wait); + return g_thunks.Wait(handle, signals, deadline); +} + +MojoResult MojoWaitMany(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline) { + assert(g_thunks.WaitMany); + return g_thunks.WaitMany(handles, signals, num_handles, deadline); +} + +MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + assert(g_thunks.CreateMessagePipe); + return g_thunks.CreateMessagePipe(options, message_pipe_handle0, + message_pipe_handle1); +} + +MojoResult MojoWriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + assert(g_thunks.WriteMessage); + return g_thunks.WriteMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + assert(g_thunks.ReadMessage); + return g_thunks.ReadMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + assert(g_thunks.CreateDataPipe); + return g_thunks.CreateDataPipe(options, data_pipe_producer_handle, + data_pipe_consumer_handle); +} + +MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags) { + assert(g_thunks.WriteData); + return g_thunks.WriteData(data_pipe_producer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags) { + assert(g_thunks.BeginWriteData); + return g_thunks.BeginWriteData(data_pipe_producer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written) { + assert(g_thunks.EndWriteData); + return g_thunks.EndWriteData(data_pipe_producer_handle, num_elements_written); +} + +MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags) { + assert(g_thunks.ReadData); + return g_thunks.ReadData(data_pipe_consumer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags) { + assert(g_thunks.BeginReadData); + return g_thunks.BeginReadData(data_pipe_consumer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read) { + assert(g_thunks.EndReadData); + return g_thunks.EndReadData(data_pipe_consumer_handle, num_elements_read); +} + +MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + assert(g_thunks.CreateSharedBuffer); + return g_thunks.CreateSharedBuffer(options, num_bytes, shared_buffer_handle); +} + +MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + assert(g_thunks.DuplicateBufferHandle); + return g_thunks.DuplicateBufferHandle(buffer_handle, options, + new_buffer_handle); +} + +MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + assert(g_thunks.MapBuffer); + return g_thunks.MapBuffer(buffer_handle, offset, num_bytes, buffer, flags); +} + +MojoResult MojoUnmapBuffer(void* buffer) { + assert(g_thunks.UnmapBuffer); + return g_thunks.UnmapBuffer(buffer); +} + +// Call this function by looking +// Always export this api. +#if defined(WIN32) +#define THUNK_EXPORT __declspec(dllexport) +#else +#define THUNK_EXPORT __attribute__((visibility("default"))) +#endif + +extern "C" THUNK_EXPORT size_t MojoSetSystemThunks( + const MojoSystemThunks* system_thunks) { + if (system_thunks->size >= sizeof(g_thunks)) + g_thunks = *system_thunks; + return sizeof(g_thunks); +} + +} // extern "C" diff --git a/chromium/mojo/public/platform/native/system_thunks.h b/chromium/mojo/public/platform/native/system_thunks.h new file mode 100644 index 00000000000..de82eba1bfc --- /dev/null +++ b/chromium/mojo/public/platform/native/system_thunks.h @@ -0,0 +1,137 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_PLATFORM_NATIVE_SYSTEM_THUNKS_H_ +#define MOJO_PUBLIC_PLATFORM_NATIVE_SYSTEM_THUNKS_H_ + +#include <stddef.h> + +#include "mojo/public/c/system/core.h" + +// The embedder needs to bind the basic Mojo Core functions of a DSO to those of +// the embedder when loading a DSO that is dependent on mojo_system. +// The typical usage would look like: +// base::ScopedNativeLibrary app_library( +// base::LoadNativeLibrary(app_path_, &error)); +// typedef MojoResult (*MojoSetSystemThunksFn)(MojoSystemThunks*); +// MojoSetSystemThunksFn mojo_set_system_thunks_fn = +// reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer( +// "MojoSetSystemThunks")); +// MojoSystemThunks system_thunks = MojoMakeSystemThunks(); +// size_t expected_size = mojo_set_system_thunks_fn(&system_thunks); +// if (expected_size > sizeof(MojoSystemThunks)) { +// LOG(ERROR) +// << "Invalid DSO. Expected MojoSystemThunks size: " +// << expected_size; +// break; +// } + +// Structure used to bind the basic Mojo Core functions of a DSO to those of +// the embedder. +// This is the ABI between the embedder and the DSO. It can only have new +// functions added to the end. No other changes are supported. +#pragma pack(push, 8) +struct MojoSystemThunks { + size_t size; // Should be set to sizeof(MojoSystemThunks). + MojoTimeTicks (*GetTimeTicksNow)(); + MojoResult (*Close)(MojoHandle handle); + MojoResult (*Wait)(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline); + MojoResult (*WaitMany)(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline); + MojoResult (*CreateMessagePipe)(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1); + MojoResult (*WriteMessage)(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags); + MojoResult (*ReadMessage)(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult (*CreateDataPipe)(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle); + MojoResult (*WriteData)(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags); + MojoResult (*BeginWriteData)(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags); + MojoResult (*EndWriteData)(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written); + MojoResult (*ReadData)(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags); + MojoResult (*BeginReadData)(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags); + MojoResult (*EndReadData)(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read); + MojoResult (*CreateSharedBuffer)( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle); + MojoResult (*DuplicateBufferHandle)( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle); + MojoResult (*MapBuffer)(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags); + MojoResult (*UnmapBuffer)(void* buffer); +}; +#pragma pack(pop) + +// Intended to be called from the embedder. Returns a |MojoCore| initialized +// to contain pointers to each of the embedder's MojoCore functions. +inline MojoSystemThunks MojoMakeSystemThunks() { + MojoSystemThunks system_thunks = { + sizeof(MojoSystemThunks), + MojoGetTimeTicksNow, + MojoClose, + MojoWait, + MojoWaitMany, + MojoCreateMessagePipe, + MojoWriteMessage, + MojoReadMessage, + MojoCreateDataPipe, + MojoWriteData, + MojoBeginWriteData, + MojoEndWriteData, + MojoReadData, + MojoBeginReadData, + MojoEndReadData, + MojoCreateSharedBuffer, + MojoDuplicateBufferHandle, + MojoMapBuffer, + MojoUnmapBuffer + }; + return system_thunks; +} + +// Use this type for the function found by dynamically discovering it in +// a DSO linked with mojo_system. For example: +// MojoSetSystemThunksFn mojo_set_system_thunks_fn = +// reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer( +// "MojoSetSystemThunks")); +// The expected size of |system_thunks} is returned. +// The contents of |system_thunks| are copied. +typedef size_t (*MojoSetSystemThunksFn)(const MojoSystemThunks* system_thunks); + +#endif // MOJO_PUBLIC_PLATFORM_NATIVE_SYSTEM_THUNKS_H_ diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_declaration.tmpl new file mode 100644 index 00000000000..bc2a097b098 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/enum_declaration.tmpl @@ -0,0 +1,9 @@ +enum {{enum.name}} { +{%- for field in enum.fields %} +{%- if field.value %} + {{field.name}} = {{field.value|expression_to_text}}, +{%- else %} + {{field.name}}, +{%- endif %} +{%- endfor %} +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl new file mode 100644 index 00000000000..30d20d8993e --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl @@ -0,0 +1,49 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +class {{interface.name}}Proxy; +class {{interface.name}}Stub; + +class {{interface.name}}RequestValidator; +{%- if interface|has_callbacks %} +class {{interface.name}}ResponseValidator; +{%- endif %} +{% if interface.client %} +class {{interface.client}}; +{% endif %} + +class {{interface.name}} { + public: + static const char* Name_; + + typedef {{interface.name}}Proxy Proxy_; + typedef {{interface.name}}Stub Stub_; + + typedef {{interface.name}}RequestValidator RequestValidator_; +{%- if interface|has_callbacks %} + typedef {{interface.name}}ResponseValidator ResponseValidator_; +{%- else %} + typedef mojo::PassThroughFilter ResponseValidator_; +{%- endif %} +{% if interface.client %} + typedef {{interface.client}} Client; +{% else %} + typedef mojo::NoInterface Client; +{% endif %} + +{#--- Constants #} +{%- for constant in interface.constants %} + static const {{constant.kind|cpp_pod_type}} {{constant.name}}; +{%- endfor %} + +{#--- Enums #} +{%- for enum in interface.enums %} +{% macro enum_def() %}{% include "enum_declaration.tmpl" %}{% endmacro %} + {{enum_def()|indent(2)}} +{%- endfor %} + +{#--- Methods #} + virtual ~{{interface.name}}() {} + +{%- for method in interface.methods %} + virtual void {{method.name}}({{interface_macros.declare_request_params("", method)}}) = 0; +{%- endfor %} +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl new file mode 100644 index 00000000000..9b6a3249e55 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl @@ -0,0 +1,315 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +{%- set class_name = interface.name %} +{%- set proxy_name = interface.name ~ "Proxy" %} +{%- set namespace_as_string = "%s"|format(namespace|replace(".","::")) %} + +{%- macro alloc_params(parameters) %} +{%- for param in parameters %} +{%- if param.kind|is_object_kind %} +{{param.kind|cpp_result_type}} p{{loop.index}}; +Deserialize_(params->{{param.name}}.ptr, &p{{loop.index}}); +{% endif -%} +{%- endfor %} +{%- endmacro %} + +{%- macro pass_params(parameters) %} +{%- for param in parameters %} +{%- if param.kind|is_string_kind -%} +p{{loop.index}} +{%- elif param.kind|is_object_kind -%} +p{{loop.index}}.Pass() +{%- elif param.kind|is_interface_kind -%} +mojo::MakeProxy<{{param.kind|get_name_for_kind}}>(mojo::MakeScopedHandle(mojo::internal::FetchAndReset(¶ms->{{param.name}}))) +{%- elif param.kind|is_interface_request_kind -%} +mojo::MakeRequest<{{param.kind.kind|get_name_for_kind}}>(mojo::MakeScopedHandle(mojo::internal::FetchAndReset(¶ms->{{param.name}}))) +{%- elif param.kind|is_handle_kind -%} +mojo::MakeScopedHandle(mojo::internal::FetchAndReset(¶ms->{{param.name}})) +{%- elif param.kind|is_enum_kind -%} +static_cast<{{param.kind|cpp_wrapper_type}}>(params->{{param.name}}) +{%- else -%} +params->{{param.name}} +{%- endif -%} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro compute_payload_size(params_name, parameters) -%} + size_t payload_size = + mojo::internal::Align(sizeof({{params_name}})); +{#--- Computes #} +{%- for param in parameters %} +{%- if param.kind|is_object_kind %} + payload_size += GetSerializedSize_(in_{{param.name}}); +{%- endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro build_message(params_name, parameters) -%} + {{params_name}}* params = + {{params_name}}::New(builder.buffer()); +{#--- Sets #} +{% for param in parameters %} +{%- if param.kind|is_object_kind %} + Serialize_(mojo::internal::Forward(in_{{param.name}}), builder.buffer(), ¶ms->{{param.name}}.ptr); +{%- elif param.kind|is_interface_kind %} + if (!in_{{param.name}}.get()) { + params->{{param.name}} = mojo::MessagePipeHandle(); + } else { + // Delegate handle. + params->{{param.name}} = in_{{param.name}}.PassMessagePipe().release(); + } +{%- elif param.kind|is_interface_request_kind %} + // Delegate handle. + params->{{param.name}} = in_{{param.name}}.PassMessagePipe().release(); +{%- elif param.kind|is_handle_kind %} + params->{{param.name}} = in_{{param.name}}.release(); +{%- else %} + params->{{param.name}} = in_{{param.name}}; +{%- endif %} +{%- endfor %} + mojo::Message message; + params->EncodePointersAndHandles(message.mutable_handles()); + builder.Finish(&message); +{%- endmacro %} + +{#--- Begin #} +const char* {{class_name}}::Name_ = "{{namespace_as_string}}::{{class_name}}"; +{#--- Constants #} +{% for constant in interface.constants %} +const {{constant.kind|cpp_pod_type}} {{interface.name}}::{{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +{#--- ForwardToCallback definition #} +{%- for method in interface.methods -%} +{%- if method.response_parameters != None %} +class {{class_name}}_{{method.name}}_ForwardToCallback + : public mojo::MessageReceiver { + public: + {{class_name}}_{{method.name}}_ForwardToCallback( + const {{interface_macros.declare_callback(method)}}& callback) + : callback_(callback) { + } + virtual bool Accept(mojo::Message* message) MOJO_OVERRIDE; + private: + {{interface_macros.declare_callback(method)}} callback_; + MOJO_DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ForwardToCallback); +}; +bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept( + mojo::Message* message) { + internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>( + message->mutable_payload()); + + params->DecodePointersAndHandles(message->mutable_handles()); + {{alloc_params(method.response_parameters)|indent(2)}} + callback_.Run({{pass_params(method.response_parameters)}}); + return true; +} +{%- endif %} +{%- endfor %} + +{{proxy_name}}::{{proxy_name}}(mojo::MessageReceiverWithResponder* receiver) + : receiver_(receiver) { +} + +{#--- Proxy definitions #} + +{%- for method in interface.methods %} +{%- set message_name = + "internal::k%s_%s_Name"|format(interface.name, method.name) %} +{%- set params_name = + "internal::%s_%s_Params_Data"|format(interface.name, method.name) %} +void {{proxy_name}}::{{method.name}}( + {{interface_macros.declare_request_params("in_", method)}}) { + {{compute_payload_size(params_name, method.parameters)}} + +{%- if method.response_parameters != None %} + mojo::internal::RequestMessageBuilder builder({{message_name}}, payload_size); +{%- else %} + mojo::internal::MessageBuilder builder({{message_name}}, payload_size); +{%- endif %} + + {{build_message(params_name, method.parameters)}} + +{%- if method.response_parameters != None %} + mojo::MessageReceiver* responder = + new {{class_name}}_{{method.name}}_ForwardToCallback(callback); + if (!receiver_->AcceptWithResponder(&message, responder)) + delete responder; +{%- else %} + bool ok MOJO_ALLOW_UNUSED = receiver_->Accept(&message); + // This return value may be ignored as !ok implies the Connector has + // encountered an error, which will be visible through other means. +{%- endif %} +} +{%- endfor %} + +{#--- ProxyToResponder definition #} +{%- for method in interface.methods -%} +{%- if method.response_parameters != None %} +{%- set message_name = + "internal::k%s_%s_Name"|format(interface.name, method.name) %} +{%- set params_name = + "internal::%s_%s_ResponseParams_Data"|format(interface.name, method.name) %} +class {{class_name}}_{{method.name}}_ProxyToResponder + : public {{interface_macros.declare_callback(method)}}::Runnable { + public: + virtual ~{{class_name}}_{{method.name}}_ProxyToResponder() { + delete responder_; + } + + {{class_name}}_{{method.name}}_ProxyToResponder( + uint64_t request_id, + mojo::MessageReceiver* responder) + : request_id_(request_id), + responder_(responder) { + } + + virtual void Run({{interface_macros.declare_params("in_", method.response_parameters)}}) const; + + private: + uint64_t request_id_; + mutable mojo::MessageReceiver* responder_; + MOJO_DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder); +}; +void {{class_name}}_{{method.name}}_ProxyToResponder::Run( + {{interface_macros.declare_params("in_", method.response_parameters)}}) const { + {{compute_payload_size(params_name, method.response_parameters)}} + mojo::internal::ResponseMessageBuilder builder( + {{message_name}}, payload_size, request_id_); + {{build_message(params_name, method.response_parameters)}} + bool ok MOJO_ALLOW_UNUSED = responder_->Accept(&message); + // TODO(darin): !ok returned here indicates a malformed message, and that may + // be good reason to close the connection. However, we don't have a way to do + // that from here. We should add a way. + delete responder_; + responder_ = NULL; +} +{%- endif -%} +{%- endfor %} + +{{class_name}}Stub::{{class_name}}Stub() + : sink_(NULL) { +} + +{#--- Stub definition #} + +bool {{class_name}}Stub::Accept(mojo::Message* message) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters == None %} + internal::{{class_name}}_{{method.name}}_Params_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>( + message->mutable_payload()); + + params->DecodePointersAndHandles(message->mutable_handles()); + {{alloc_params(method.parameters)|indent(6)}} + sink_->{{method.name}}({{pass_params(method.parameters)}}); + return true; +{%- else %} + break; +{%- endif %} + } +{%- endfor %} + } +{%- endif %} + return false; +} + +bool {{class_name}}Stub::AcceptWithResponder( + mojo::Message* message, mojo::MessageReceiver* responder) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters != None %} + internal::{{class_name}}_{{method.name}}_Params_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>( + message->mutable_payload()); + + params->DecodePointersAndHandles(message->mutable_handles()); + {{interface_macros.declare_callback(method)}}::Runnable* runnable = + new {{class_name}}_{{method.name}}_ProxyToResponder( + message->request_id(), responder); + {{interface_macros.declare_callback(method)}} callback(runnable); + {{alloc_params(method.parameters)|indent(6)}} + sink_->{{method.name}}( +{%- if method.parameters -%}{{pass_params(method.parameters)}}, {% endif -%}callback); + return true; +{%- else %} + break; +{%- endif %} + } +{%- endfor %} + } +{%- endif %} + return false; +} + +{#--- Request validator definitions #} + +{{class_name}}RequestValidator::{{class_name}}RequestValidator( + mojo::MessageReceiver* sink) : MessageFilter(sink) { +} + +bool {{class_name}}RequestValidator::Accept(mojo::Message* message) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters != None %} + if (!message->has_flag(mojo::internal::kMessageExpectsResponse)) + break; +{%- else %} + if (message->has_flag(mojo::internal::kMessageExpectsResponse) || + message->has_flag(mojo::internal::kMessageIsResponse)) { + break; + } +{%- endif %} + mojo::internal::BoundsChecker bounds_checker( + message->payload(), message->payload_num_bytes(), + message->handles()->size()); + if (!internal::{{class_name}}_{{method.name}}_Params_Data::Validate( + message->payload(), &bounds_checker)) { + return false; + } + break; + } +{%- endfor %} + } +{%- endif %} + + return sink_->Accept(message); +} + +{#--- Response validator definitions #} +{% if interface|has_callbacks %} +{{class_name}}ResponseValidator::{{class_name}}ResponseValidator( + mojo::MessageReceiver* sink) : MessageFilter(sink) { +} + +bool {{class_name}}ResponseValidator::Accept(mojo::Message* message) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods if method.response_parameters != None %} + case internal::k{{class_name}}_{{method.name}}_Name: { + if (!message->has_flag(mojo::internal::kMessageIsResponse)) + break; + mojo::internal::BoundsChecker bounds_checker( + message->payload(), message->payload_num_bytes(), + message->handles()->size()); + if (!internal::{{class_name}}_{{method.name}}_ResponseParams_Data::Validate( + message->payload(), &bounds_checker)) { + return false; + } + break; + } +{%- endfor %} + } +{%- endif %} + + return sink_->Accept(message); +} +{%- endif -%} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl new file mode 100644 index 00000000000..fbefce2d397 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl @@ -0,0 +1,23 @@ +{%- macro declare_params(prefix, parameters) %} +{%- for param in parameters -%} +{{param.kind|cpp_const_wrapper_type}} {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro declare_callback(method) -%} +mojo::Callback<void( +{%- for param in method.response_parameters -%} +{{param.kind|cpp_result_type}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +)> +{%- endmacro -%} + +{%- macro declare_request_params(prefix, method) -%} +{{declare_params(prefix, method.parameters)}} +{%- if method.response_parameters != None -%} +{%- if method.parameters %}, {% endif %} +const {{declare_callback(method)}}& callback +{%- endif -%} +{%- endmacro -%} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl new file mode 100644 index 00000000000..9451118a438 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl @@ -0,0 +1,14 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +class {{interface.name}}Proxy : public {{interface.name}} { + public: + explicit {{interface.name}}Proxy(mojo::MessageReceiverWithResponder* receiver); + +{%- for method in interface.methods %} + virtual void {{method.name}}( + {{interface_macros.declare_request_params("", method)}} + ) MOJO_OVERRIDE; +{%- endfor %} + + private: + mojo::MessageReceiverWithResponder* receiver_; +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl new file mode 100644 index 00000000000..63c60ee2db8 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl @@ -0,0 +1,6 @@ +class {{interface.name}}RequestValidator : public mojo::MessageFilter { + public: + explicit {{interface.name}}RequestValidator(mojo::MessageReceiver* sink = NULL); + + virtual bool Accept(mojo::Message* message) MOJO_OVERRIDE; +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl new file mode 100644 index 00000000000..0719060c155 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl @@ -0,0 +1,6 @@ +class {{interface.name}}ResponseValidator : public mojo::MessageFilter { + public: + explicit {{interface.name}}ResponseValidator(mojo::MessageReceiver* sink = NULL); + + virtual bool Accept(mojo::Message* message) MOJO_OVERRIDE; +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl new file mode 100644 index 00000000000..25b28ec777a --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl @@ -0,0 +1,14 @@ +class {{interface.name}}Stub : public mojo::MessageReceiverWithResponder { + public: + {{interface.name}}Stub(); + void set_sink({{interface.name}}* sink) { sink_ = sink; } + {{interface.name}}* sink() { return sink_; } + + virtual bool Accept(mojo::Message* message) MOJO_OVERRIDE; + virtual bool AcceptWithResponder(mojo::Message* message, + mojo::MessageReceiver* responder) + MOJO_OVERRIDE; + + private: + {{interface.name}}* sink_; +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-internal.h.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-internal.h.tmpl new file mode 100644 index 00000000000..f0cf33b560b --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module-internal.h.tmpl @@ -0,0 +1,49 @@ +// 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. + +{%- set header_guard = "%s_INTERNAL_H_"| + format(module.path|upper|replace("/","_")|replace(".","_")) %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" + +{%- for import in imports %} +#include "{{import.module.path}}-internal.h" +{%- endfor %} + +namespace mojo { +namespace internal { +class BoundsChecker; +} +} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} + +{#--- Wrapper forward declarations #} +{% for struct in structs %} +class {{struct.name}}; +{%- endfor %} + +namespace internal { + +#pragma pack(push, 1) + +{#--- Class declarations #} +{% for struct in structs %} +{% include "struct_declaration.tmpl" %} +{%- endfor %} + +#pragma pack(pop) + +} // namespace internal +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#endif // {{header_guard}} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl new file mode 100644 index 00000000000..8cf8e99713c --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl @@ -0,0 +1,86 @@ +// 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. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif + +#include "{{module.path}}.h" + +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/bindings_serialization.h" +#include "mojo/public/cpp/bindings/lib/bounds_checker.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/string_serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} + +{#--- Constants #} +{% for constant in module.constants %} +const {{constant.kind|cpp_pod_type}} {{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +namespace internal { +namespace { + +#pragma pack(push, 1) + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %} +const uint32_t {{method_name}} = {{method.ordinal}}; +{% set struct = method|struct_from_method %} +{%- include "params_definition.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method|response_struct_from_method %} +{%- include "params_definition.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +#pragma pack(pop) + +} // namespace + +{#--- Struct definitions #} +{% for struct in structs %} +{%- include "struct_definition.tmpl" %} +{%- endfor %} + +} // namespace internal + +{#--- Struct Constants #} +{%- for struct in structs %} +{% for constant in struct.constants %} +const {{constant.kind|cpp_pod_type}} {{struct.name}}::{{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} +{%- endfor %} + +{#--- Struct builder definitions #} +{%- for struct in structs %} +{%- include "wrapper_class_definition.tmpl" %} +{%- endfor %} + +{#--- Interface definitions #} +{%- for interface in interfaces %} +{%- include "interface_definition.tmpl" %} +{%- endfor %} + +{#--- Struct Serialization Helpers #} +{%- for struct in structs %} +{%- include "struct_serialization_definition.tmpl" %} +{%- endfor %} + +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl new file mode 100644 index 00000000000..4e21d4774bd --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl @@ -0,0 +1,108 @@ +// 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. + +{%- set header_guard = "%s_H_"| + format(module.path|upper|replace("/","_")|replace(".","_")) %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_impl.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/message_filter.h" +#include "mojo/public/cpp/bindings/no_interface.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" +#include "{{module.path}}-internal.h" +{%- for import in imports %} +#include "{{import.module.path}}.h" +{%- endfor %} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} + +{#--- Constants #} +{% for constant in module.constants %} +extern const {{constant.kind|cpp_pod_type}} {{constant.name}}; +{%- endfor %} + +{#--- Enums #} +{% for enum in enums %} +{% include "enum_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Forward Declarations -#} +{% for interface in interfaces %} +class {{interface.name}}; +typedef mojo::InterfacePtr<{{interface.name}}> {{interface.name}}Ptr; +{% endfor %} + +{#--- Struct Forward Declarations -#} +{% for struct in structs %} +class {{struct.name}}; +{% if struct|should_inline %} +typedef mojo::InlinedStructPtr<{{struct.name}}> {{struct.name}}Ptr; +{% else %} +typedef mojo::StructPtr<{{struct.name}}> {{struct.name}}Ptr; +{% endif %} +{% endfor %} + +{#--- NOTE: Non-inlined structs may have pointers to inlined structs, so we #} +{#--- need to fully define inlined structs ahead of the others. #} + +{#--- Inlined structs #} +{% for struct in structs %} +{% if struct|should_inline %} +{% include "wrapper_class_declaration.tmpl" %} +{% endif %} +{%- endfor %} + +{#--- Non-inlined structs #} +{% for struct in structs %} +{% if not struct|should_inline %} +{% include "wrapper_class_declaration.tmpl" %} +{% endif %} +{%- endfor %} + +{#--- Interfaces -#} +{% for interface in interfaces %} +{% include "interface_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Proxies -#} +{% for interface in interfaces %} +{% include "interface_proxy_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Stubs -#} +{% for interface in interfaces %} +{% include "interface_stub_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Request Validators -#} +{% for interface in interfaces %} +{% include "interface_request_validator_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Response Validators -#} +{% for interface in interfaces if interface|has_callbacks %} +{% include "interface_response_validator_declaration.tmpl" %} +{%- endfor %} + +{#--- Struct Serialization Helpers -#} +{% if structs %} +{% for struct in structs %} +{% include "struct_serialization_declaration.tmpl" %} +{%- endfor %} +{%- endif %} + +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#endif // {{header_guard}} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/params_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/params_definition.tmpl new file mode 100644 index 00000000000..0b1104719bb --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/params_definition.tmpl @@ -0,0 +1,33 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set class_name = struct.name ~ "_Data" %} +class {{class_name}} { + public: + static {{class_name}}* New(mojo::internal::Buffer* buf) { + return new (buf->Allocate(sizeof({{class_name}}))) + {{class_name}}(); + } + + static bool Validate(const void* data, + mojo::internal::BoundsChecker* bounds_checker) { + {{ struct_macros.validate(struct, class_name)|indent(4) }} + } + + mojo::internal::StructHeader header_; +{{struct_macros.fields(struct)}} + + void EncodePointersAndHandles(std::vector<mojo::Handle>* handles) { + {{ struct_macros.encodes(struct)|indent(4) }} + } + + void DecodePointersAndHandles(std::vector<mojo::Handle>* handles) { + {{ struct_macros.decodes(struct)|indent(4) }} + } + + private: + {{class_name}}() { + header_.num_bytes = sizeof(*this); + header_.num_fields = {{struct.packed.packed_fields|length}}; + } +}; +MOJO_COMPILE_ASSERT(sizeof({{class_name}}) == {{struct.packed|struct_size}}, + bad_sizeof_{{class_name}}); diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl new file mode 100644 index 00000000000..60a6a9e6d3e --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl @@ -0,0 +1,22 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set class_name = struct.name ~ "_Data" -%} + +class {{class_name}} { + public: + static {{class_name}}* New(mojo::internal::Buffer* buf); + + static bool Validate(const void* data, + mojo::internal::BoundsChecker* bounds_checker); + + mojo::internal::StructHeader header_; +{{struct_macros.fields(struct)}} + + void EncodePointersAndHandles(std::vector<mojo::Handle>* handles); + void DecodePointersAndHandles(std::vector<mojo::Handle>* handles); + + private: + {{class_name}}(); + ~{{class_name}}(); // NOT IMPLEMENTED +}; +MOJO_COMPILE_ASSERT(sizeof({{class_name}}) == {{struct.packed|struct_size}}, + bad_sizeof_{{class_name}}); diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl new file mode 100644 index 00000000000..461f158602b --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl @@ -0,0 +1,28 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set class_name = struct.name ~ "_Data" %} + +// static +{{class_name}}* {{class_name}}::New(mojo::internal::Buffer* buf) { + return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}(); +} + +// static +bool {{class_name}}::Validate(const void* data, + mojo::internal::BoundsChecker* bounds_checker) { + {{ struct_macros.validate(struct, class_name)|indent(2) }} +} + +{{class_name}}::{{class_name}}() { + header_.num_bytes = sizeof(*this); + header_.num_fields = {{struct.packed.packed_fields|length}}; +} + +void {{class_name}}::EncodePointersAndHandles( + std::vector<mojo::Handle>* handles) { + {{ struct_macros.encodes(struct)|indent(2) }} +} + +void {{class_name}}::DecodePointersAndHandles( + std::vector<mojo::Handle>* handles) { + {{ struct_macros.decodes(struct)|indent(2) }} +} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl new file mode 100644 index 00000000000..bd05a06de72 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl @@ -0,0 +1,91 @@ +{%- macro validate(struct, class_name) %} + if (!data) + return true; + + if (!ValidateStructHeader( + data, sizeof({{class_name}}), + {{struct.packed.packed_fields|length}}, bounds_checker)) { + return false; + } + + const {{class_name}}* MOJO_ALLOW_UNUSED object = + static_cast<const {{class_name}}*>(data); + +{%- for packed_field in struct.packed.packed_fields %} +{%- set name = packed_field.field.name %} +{%- if packed_field.field.kind|is_object_kind %} +{%- set wrapper_type = packed_field.field.kind|cpp_wrapper_type %} + if (!mojo::internal::ValidateEncodedPointer(&object->{{name}}.offset)) { + ReportValidationError(mojo::internal::VALIDATION_ERROR_ILLEGAL_POINTER); + return false; + } + if (!{{wrapper_type}}::Data_::Validate( + mojo::internal::DecodePointerRaw(&object->{{name}}.offset), + bounds_checker)) { + return false; + } +{%- elif packed_field.field.kind|is_handle_kind %} + if (!bounds_checker->ClaimHandle(object->{{name}})) { + ReportValidationError(mojo::internal::VALIDATION_ERROR_ILLEGAL_HANDLE); + return false; + } +{%- endif %} +{%- endfor %} + + return true; +{%- endmacro %} + +{%- macro field_line(field) %} +{%- set type = field.kind|cpp_field_type %} +{%- set name = field.name -%} +{%- if field.kind.spec == 'b' -%} + uint8_t {{name}} : 1; +{%- elif field.kind|is_enum_kind -%} + int32_t {{name}}; +{%- else -%} + {{type}} {{name}}; +{%- endif %} +{%- endmacro %} + +{%- macro fields(struct) %} +{%- for packed_field in struct.packed.packed_fields %} + {{field_line(packed_field.field)}} +{%- if not loop.last %} +{%- set next_pf = struct.packed.packed_fields[loop.index0 + 1] %} +{%- set pad = next_pf.offset - (packed_field.offset + packed_field.size) %} +{%- if pad > 0 %} + uint8_t pad{{loop.index0}}_[{{pad}}]; +{%- endif %} +{%- endif %} +{%- endfor -%} + +{%- set num_fields = struct.packed.packed_fields|length %} +{%- if num_fields > 0 %} +{%- set last_field = struct.packed.packed_fields[num_fields - 1] %} +{%- set offset = last_field.offset + last_field.size %} +{%- set pad = offset|get_pad(8) -%} +{%- if pad > 0 %} + uint8_t padfinal_[{{pad}}]; +{%- endif %} +{%- endif %} +{%- endmacro %} + +{%- macro encodes(struct) -%} +{%- for pf in struct.packed.packed_fields %} +{%- if pf.field.kind|is_object_kind %} +mojo::internal::Encode(&{{pf.field.name}}, handles); +{%- elif pf.field.kind|is_handle_kind %} +mojo::internal::EncodeHandle(&{{pf.field.name}}, handles); +{%- endif %} +{%- endfor %} +{%- endmacro -%} + +{%- macro decodes(struct) -%} +{%- for pf in struct.packed.packed_fields %} +{%- if pf.field.kind|is_object_kind %} +mojo::internal::Decode(&{{pf.field.name}}, handles); +{%- elif pf.field.kind|is_handle_kind %} +mojo::internal::DecodeHandle(&{{pf.field.name}}, handles); +{%- endif %} +{%- endfor %} +{%- endmacro -%} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl new file mode 100644 index 00000000000..604be86c7e2 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl @@ -0,0 +1,5 @@ +size_t GetSerializedSize_(const {{struct.name}}Ptr& input); +void Serialize_({{struct.name}}Ptr input, mojo::internal::Buffer* buffer, + internal::{{struct.name}}_Data** output); +void Deserialize_(internal::{{struct.name}}_Data* input, + {{struct.name}}Ptr* output); diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl new file mode 100644 index 00000000000..aec2afa93e5 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl @@ -0,0 +1,51 @@ +size_t GetSerializedSize_(const {{struct.name}}Ptr& input) { + if (!input) + return 0; + size_t size = sizeof(internal::{{struct.name}}_Data); +{%- for pf in struct.packed.packed_fields if pf.field.kind|is_object_kind %} + size += GetSerializedSize_(input->{{pf.field.name}}); +{%- endfor %} + return size; +} + +void Serialize_({{struct.name}}Ptr input, mojo::internal::Buffer* buf, + internal::{{struct.name}}_Data** output) { + if (input) { + internal::{{struct.name}}_Data* result = + internal::{{struct.name}}_Data::New(buf); +{%- for pf in struct.packed.packed_fields %} +{%- if pf.field.kind|is_object_kind %} + Serialize_(mojo::internal::Forward(input->{{pf.field.name}}), buf, &result->{{pf.field.name}}.ptr); +{%- elif pf.field.kind|is_handle_kind %} + result->{{pf.field.name}} = input->{{pf.field.name}}.release(); +{%- else %} + result->{{pf.field.name}} = input->{{pf.field.name}}; +{%- endif %} +{%- endfor %} + *output = result; + } else { + *output = NULL; + } +} + +void Deserialize_(internal::{{struct.name}}_Data* input, + {{struct.name}}Ptr* output) { + if (input) { + {{struct.name}}Ptr result({{struct.name}}::New()); +{%- for pf in struct.packed.packed_fields %} +{%- if pf.field.kind|is_object_kind %} + Deserialize_(input->{{pf.field.name}}.ptr, &result->{{pf.field.name}}); +{%- elif pf.field.kind|is_handle_kind %} + result->{{pf.field.name}}.reset(mojo::internal::FetchAndReset(&input->{{pf.field.name}})); +{%- elif pf.field.kind|is_enum_kind %} + result->{{pf.field.name}} = static_cast<{{pf.field.kind|cpp_wrapper_type}}>( + input->{{pf.field.name}}); +{%- else %} + result->{{pf.field.name}} = input->{{pf.field.name}}; +{%- endif %} +{%- endfor %} + *output = result.Pass(); + } else { + output->reset(); + } +} diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl new file mode 100644 index 00000000000..0e01976ce61 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl @@ -0,0 +1,31 @@ + +class {{struct.name}} { + public: + typedef internal::{{struct.name}}_Data Data_; + +{#--- Constants #} +{%- for constant in struct.constants %} + static const {{constant.kind|cpp_pod_type}} {{constant.name}}; +{%- endfor %} +{#--- Enums #} +{%- for enum in struct.enums -%} +{% macro enum_def() %}{% include "enum_declaration.tmpl" %}{% endmacro %} + {{enum_def()|indent(2)}} +{%- endfor %} + static {{struct.name}}Ptr New(); + + template <typename U> + static {{struct.name}}Ptr From(const U& u) { + return mojo::TypeConverter<{{struct.name}}Ptr, U>::ConvertFrom(u); + } + + {{struct.name}}(); + ~{{struct.name}}(); + +{#--- Getters #} +{% for field in struct.fields %} +{%- set type = field.kind|cpp_wrapper_type %} +{%- set name = field.name %} + {{type}} {{name}}; +{%- endfor %} +}; diff --git a/chromium/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl new file mode 100644 index 00000000000..29bdd8eeb95 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl @@ -0,0 +1,15 @@ +// static +{{struct.name}}Ptr {{struct.name}}::New() { + {{struct.name}}Ptr rv; + mojo::internal::StructHelper<{{struct.name}}>::Initialize(&rv); + return rv.Pass(); +} + +{{struct.name}}::{{struct.name}}() +{%-for field in struct.fields %} + {% if loop.first %}:{% else %} {% endif %} {{field.name}}({{field|default_value}}){% if not loop.last %},{% endif %} +{%- endfor %} { +} + +{{struct.name}}::~{{struct.name}}() { +} diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl new file mode 100644 index 00000000000..f69f657d314 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl @@ -0,0 +1,5 @@ +{% from "java_macros.tmpl" import build_default %} + +{% macro constant_def(constant) %} +public static final {{constant.kind|java_type}} {{constant|name}} = {{build_default(module, constant.kind, constant.value)|indent(4)}}; +{% endmacro %} diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl new file mode 100644 index 00000000000..0a4e29956b6 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl @@ -0,0 +1,12 @@ +{% from "constant_definition.tmpl" import constant_def %} +{% include "header.java.tmpl" %} + +public final class {{main_entity}} { +{% for constant in constants %} + + {{constant_def(constant)|indent(4)}} +{% endfor %} + + private {{main_entity}}() {} + +} diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl new file mode 100644 index 00000000000..7096a18747f --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl @@ -0,0 +1,4 @@ +{% from "enum_definition.tmpl" import enum_def %} +{% include "header.java.tmpl" %} + +{{enum_def(enum, true)}} diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl new file mode 100644 index 00000000000..d72bd7ff4a7 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl @@ -0,0 +1,21 @@ +{%- macro enum_value(enum, field, index) -%} +{%- if field.value -%} +(int) ({{field.value|expression_to_text}}) +{%- elif index == 0 -%} +0 +{%- else -%} +{{enum.fields[index - 1].name}} + 1 +{%- endif -%} +{%- endmacro -%} + +{%- macro enum_def(enum, top_level) -%} +public {{ 'static ' if not top_level }}final class {{enum|name}} { + +{% for field in enum.fields %} + public static final int {{field.name}} = {{enum_value(enum, field, loop.index0)}}; +{% endfor %} + + private {{enum|name}}() {} + +} +{%- endmacro -%} diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl new file mode 100644 index 00000000000..ec6a88b1474 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl @@ -0,0 +1,11 @@ +// 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. + +// This file is autogenerated by: +// mojo/public/tools/bindings/mojom_bindings_generator.py +// For: +// {{module.path}} +// + +package {{package}}; diff --git a/chromium/mojo/public/tools/bindings/generators/java_templates/java_macros.tmpl b/chromium/mojo/public/tools/bindings/generators/java_templates/java_macros.tmpl new file mode 100644 index 00000000000..d7339f5a420 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/java_templates/java_macros.tmpl @@ -0,0 +1,3 @@ +{% macro build_default(module, kind, value) %} +({{kind|java_type}}) {{value|expression_to_text}} +{% endmacro %} diff --git a/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl new file mode 100644 index 00000000000..795116d8dd9 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl @@ -0,0 +1,14 @@ +{%- macro enum_def(enum_name, enum, module) -%} + {{enum_name}} = {}; + +{%- set prev_enum = 0 %} +{%- for field in enum.fields %} +{%- if field.value %} + {{enum_name}}.{{field.name}} = {{field.value|expression_to_text}}; +{%- elif loop.first %} + {{enum_name}}.{{field.name}} = 0; +{%- else %} + {{enum_name}}.{{field.name}} = {{enum_name}}.{{enum.fields[loop.index0 - 1].name}} + 1; +{%- endif %} +{%- endfor %} +{%- endmacro %} diff --git a/chromium/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl new file mode 100644 index 00000000000..d46f8eeac9c --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl @@ -0,0 +1,122 @@ +{%- set namespace_as_string = namespace|replace(".","::") %} +{%- for method in interface.methods %} + var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}}; +{%- endfor %} + + function {{interface.name}}Proxy(receiver) { + this.receiver_ = receiver; + } + + {{interface.name}}Proxy.NAME_ = '{{namespace_as_string}}::{{interface.name}}'; + +{%- for method in interface.methods %} + {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function( +{%- for parameter in method.parameters -%} +{{parameter.name}}{% if not loop.last %}, {% endif %} +{%- endfor -%} +) { + var params = new {{interface.name}}_{{method.name}}_Params(); +{%- for parameter in method.parameters %} + params.{{parameter.name}} = {{parameter.name}}; +{%- endfor %} + +{%- if method.response_parameters == None %} + var builder = new codec.MessageBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_Params.encodedSize)); + builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params); + var message = builder.finish(); + this.receiver_.accept(message); +{%- else %} + return new Promise(function(resolve, reject) { + var builder = new codec.MessageWithRequestIDBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_Params.encodedSize), + codec.kMessageExpectsResponse, 0); + builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params); + var message = builder.finish(); + this.receiver_.acceptWithResponder(message, { + accept: function(message) { + var reader = new codec.MessageReader(message); + var responseParams = + reader.decodeStruct({{interface.name}}_{{method.name}}_ResponseParams); + resolve(responseParams); + }, + reject: function(result) { + reject(Error("Connection error: " + result)); + }, + }).catch(reject); + }.bind(this)); +{%- endif %} + }; +{%- endfor %} + + function {{interface.name}}Stub() { + } + + {{interface.name}}Stub.NAME_ = '{{namespace_as_string}}::{{interface.name}}'; + + {{interface.name}}Stub.prototype.accept = function(message) { + var reader = new codec.MessageReader(message); + switch (reader.messageName) { +{%- for method in interface.methods %} +{%- if method.response_parameters == None %} + case k{{interface.name}}_{{method.name}}_Name: + var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); + this.{{method.name|stylize_method}}( +{%- for parameter in method.parameters -%} +params.{{parameter.name}}{% if not loop.last %}, {% endif %} +{%- endfor %}); + return true; +{%- endif %} +{%- endfor %} + default: + return false; + } + }; + + {{interface.name}}Stub.prototype.acceptWithResponder = + function(message, responder) { + var reader = new codec.MessageReader(message); + switch (reader.messageName) { +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} + case k{{interface.name}}_{{method.name}}_Name: + var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); + return this.{{method.name|stylize_method}}( +{%- for parameter in method.parameters -%} +params.{{parameter.name}}{% if not loop.last %}, {% endif -%} +{%- endfor %}).then(function(response) { + var responseParams = + new {{interface.name}}_{{method.name}}_ResponseParams(); +{%- for parameter in method.response_parameters %} + responseParams.{{parameter.name}} = response.{{parameter.name}}; +{%- endfor %} + var builder = new codec.MessageWithRequestIDBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize), + codec.kMessageIsResponse, reader.requestID); + builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams, + responseParams); + var message = builder.finish(); + responder.accept(message); + }); +{%- endif %} +{%- endfor %} + default: + return Promise.reject(Error("Unhandled message: " + reader.messageName)); + } + }; + +{#--- Enums #} +{% from "enum_definition.tmpl" import enum_def -%} +{% for enum in interface.enums %} + {{enum_def("%sProxy.%s"|format(interface.name, enum.name), enum, module)}} + {{interface.name}}Stub.{{enum.name}} = {{interface.name}}Proxy.{{enum.name}}; +{%- endfor %} + +{#--- Constants. #} +{% for constant in interface.constants %} + {{interface.name}}Proxy.{{constant.name}} = {{constant.value|expression_to_text}}; + {{interface.name}}Stub.{{constant.name}} = {{interface.name}}Proxy.{{constant.name}}; +{% endfor %} diff --git a/chromium/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl b/chromium/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl new file mode 100644 index 00000000000..db570cd79df --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl @@ -0,0 +1,52 @@ +// 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("{{module.path}}", [ + "mojo/public/js/bindings/codec", +{%- for import in imports %} + "{{import.module.path}}", +{%- endfor %} + ], function(codec +{%- for import in imports -%} + , {{import.unique_name}} +{%- endfor -%} +) { + +{#--- Constants #} +{% for constant in module.constants %} + var {{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +{#--- Enums #} +{%- from "enum_definition.tmpl" import enum_def %} +{%- for enum in enums %} + var {{ enum_def(enum.name, enum, module) }} +{%- endfor %} + +{#--- Struct definitions #} +{% for struct in structs %} +{%- include "struct_definition.tmpl" %} +{%- endfor %} + +{#--- Interface definitions #} +{%- for interface in interfaces %} +{%- include "interface_definition.tmpl" %} +{%- endfor %} + + var exports = {}; +{% for constant in module.constants %} + exports.{{constant.name}} = {{constant.name}}; +{%- endfor %} +{%- for enum in enums %} + exports.{{enum.name}} = {{enum.name}}; +{%- endfor %} +{%- for struct in structs if struct.exported %} + exports.{{struct.name}} = {{struct.name}}; +{%- endfor %} +{%- for interface in interfaces %} + exports.{{interface.name}}Proxy = {{interface.name}}Proxy; + exports.{{interface.name}}Stub = {{interface.name}}Stub; +{%- endfor %} + return exports; +}); diff --git a/chromium/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/chromium/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl new file mode 100644 index 00000000000..ae1ccb079af --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl @@ -0,0 +1,72 @@ +{#--- Begin #} + function {{struct.name}}() { + this.initDefaults_(); + } + +{#--- Enums #} +{%- from "enum_definition.tmpl" import enum_def %} +{% for enum in struct.enums %} + {{enum_def("%s.%s"|format(struct.name, enum.name), enum, module)}} +{% endfor %} + +{#--- Constants #} +{% for constant in struct.constants %} + {{struct.name}}.{{constant.name}} = {{constant.value|expression_to_text}}; +{% endfor %} + +{#--- Set up defaults #} + {{struct.name}}.prototype.initDefaults_ = function() { +{%- for packed_field in struct.packed.packed_fields %} + this.{{packed_field.field.name}} = {{packed_field.field|default_value}}; +{%- endfor %} + }; + +{#--- Encoding and decoding #} + + {{struct.name}}.encodedSize = codec.kStructHeaderSize + {{struct.packed|payload_size}}; + + {{struct.name}}.decode = function(decoder) { + var packed; + var val = new {{struct.name}}(); + var numberOfBytes = decoder.readUint32(); + var numberOfFields = decoder.readUint32(); +{%- for byte in struct.bytes %} +{%- if byte.packed_fields|length > 1 %} + packed = decoder.readUint8(); +{%- for packed_field in byte.packed_fields %} + val.{{packed_field.field.name}} = (packed >> {{packed_field.bit}}) & 1 ? true : false; +{%- endfor %} +{%- else %} +{%- for packed_field in byte.packed_fields %} + val.{{packed_field.field.name}} = decoder.{{packed_field.field.kind|decode_snippet}}; +{%- endfor %} +{%- endif %} +{%- if byte.is_padding %} + decoder.skip(1); +{%- endif %} +{%- endfor %} + return val; + }; + + {{struct.name}}.encode = function(encoder, val) { + var packed; + encoder.writeUint32({{struct.name}}.encodedSize); + encoder.writeUint32({{struct.packed.packed_fields|length}}); + +{%- for byte in struct.bytes %} +{%- if byte.packed_fields|length > 1 %} + packed = 0; +{%- for packed_field in byte.packed_fields %} + packed |= (val.{{packed_field.field.name}} & 1) << {{packed_field.bit}} +{%- endfor %} + encoder.writeUint8(packed); +{%- else %} +{%- for packed_field in byte.packed_fields %} + encoder.{{packed_field.field.kind|encode_snippet}}val.{{packed_field.field.name}}); +{%- endfor %} +{%- endif %} +{%- if byte.is_padding %} + encoder.skip(1); +{%- endif %} +{%- endfor %} + }; diff --git a/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py new file mode 100644 index 00000000000..812dd47c609 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojom_cpp_generator.py @@ -0,0 +1,286 @@ +# 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. + +"""Generates C++ source files from a mojom.Module.""" + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +from mojom.generate.template_expander import UseJinja + + +_kind_to_cpp_type = { + mojom.BOOL: "bool", + mojom.INT8: "int8_t", + mojom.UINT8: "uint8_t", + mojom.INT16: "int16_t", + mojom.UINT16: "uint16_t", + mojom.INT32: "int32_t", + mojom.UINT32: "uint32_t", + mojom.FLOAT: "float", + mojom.HANDLE: "mojo::Handle", + mojom.DCPIPE: "mojo::DataPipeConsumerHandle", + mojom.DPPIPE: "mojo::DataPipeProducerHandle", + mojom.MSGPIPE: "mojo::MessagePipeHandle", + mojom.SHAREDBUFFER: "mojo::SharedBufferHandle", + mojom.INT64: "int64_t", + mojom.UINT64: "uint64_t", + mojom.DOUBLE: "double", +} + +def DefaultValue(field): + if field.default: + if isinstance(field.kind, mojom.Struct): + assert field.default == "default" + return "%s::New()" % GetNameForKind(field.kind) + return ExpressionToText(field.default) + return "" + +def NamespaceToArray(namespace): + return namespace.split('.') if namespace else [] + +def GetNameForKind(kind, internal = False): + parts = [] + if kind.imported_from: + parts.extend(NamespaceToArray(kind.imported_from["namespace"])) + if internal: + parts.append("internal") + if kind.parent_kind: + parts.append(kind.parent_kind.name) + parts.append(kind.name) + return "::".join(parts) + +def GetCppType(kind): + if isinstance(kind, mojom.Struct): + return "%s_Data*" % GetNameForKind(kind, internal=True) + if isinstance(kind, mojom.Array): + return "mojo::internal::Array_Data<%s>*" % GetCppType(kind.kind) + if isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest): + return "mojo::MessagePipeHandle" + if isinstance(kind, mojom.Enum): + return "int32_t" + if kind.spec == 's': + return "mojo::internal::String_Data*" + return _kind_to_cpp_type[kind] + +def GetCppPodType(kind): + if kind.spec == 's': + return "char*" + return _kind_to_cpp_type[kind] + +def GetCppArrayArgWrapperType(kind): + if isinstance(kind, mojom.Enum): + return GetNameForKind(kind) + if isinstance(kind, mojom.Struct): + return "%sPtr" % GetNameForKind(kind) + if isinstance(kind, mojom.Array): + return "mojo::Array<%s> " % GetCppArrayArgWrapperType(kind.kind) + if isinstance(kind, mojom.Interface): + raise Exception("Arrays of interfaces not yet supported!") + if isinstance(kind, mojom.InterfaceRequest): + raise Exception("Arrays of interface requests not yet supported!") + if kind.spec == 's': + return "mojo::String" + if kind.spec == 'h': + return "mojo::ScopedHandle" + if kind.spec == 'h:d:c': + return "mojo::ScopedDataPipeConsumerHandle" + if kind.spec == 'h:d:p': + return "mojo::ScopedDataPipeProducerHandle" + if kind.spec == 'h:m': + return "mojo::ScopedMessagePipeHandle" + if kind.spec == 'h:s': + return "mojo::ScopedSharedBufferHandle" + return _kind_to_cpp_type[kind] + +def GetCppResultWrapperType(kind): + if isinstance(kind, mojom.Enum): + return GetNameForKind(kind) + if isinstance(kind, mojom.Struct): + return "%sPtr" % GetNameForKind(kind) + if isinstance(kind, mojom.Array): + return "mojo::Array<%s>" % GetCppArrayArgWrapperType(kind.kind) + if isinstance(kind, mojom.Interface): + return "%sPtr" % GetNameForKind(kind) + if isinstance(kind, mojom.InterfaceRequest): + return "mojo::InterfaceRequest<%s>" % GetNameForKind(kind.kind) + if kind.spec == 's': + return "mojo::String" + if kind.spec == 'h': + return "mojo::ScopedHandle" + if kind.spec == 'h:d:c': + return "mojo::ScopedDataPipeConsumerHandle" + if kind.spec == 'h:d:p': + return "mojo::ScopedDataPipeProducerHandle" + if kind.spec == 'h:m': + return "mojo::ScopedMessagePipeHandle" + if kind.spec == 'h:s': + return "mojo::ScopedSharedBufferHandle" + return _kind_to_cpp_type[kind] + +def GetCppWrapperType(kind): + if isinstance(kind, mojom.Enum): + return GetNameForKind(kind) + if isinstance(kind, mojom.Struct): + return "%sPtr" % GetNameForKind(kind) + if isinstance(kind, mojom.Array): + return "mojo::Array<%s>" % GetCppArrayArgWrapperType(kind.kind) + if isinstance(kind, mojom.Interface): + return "mojo::ScopedMessagePipeHandle" + if isinstance(kind, mojom.InterfaceRequest): + raise Exception("InterfaceRequest fields not supported!") + if kind.spec == 's': + return "mojo::String" + if kind.spec == 'h': + return "mojo::ScopedHandle" + if kind.spec == 'h:d:c': + return "mojo::ScopedDataPipeConsumerHandle" + if kind.spec == 'h:d:p': + return "mojo::ScopedDataPipeProducerHandle" + if kind.spec == 'h:m': + return "mojo::ScopedMessagePipeHandle" + if kind.spec == 'h:s': + return "mojo::ScopedSharedBufferHandle" + return _kind_to_cpp_type[kind] + +def GetCppConstWrapperType(kind): + if isinstance(kind, mojom.Struct): + return "%sPtr" % GetNameForKind(kind) + if isinstance(kind, mojom.Array): + return "mojo::Array<%s>" % GetCppArrayArgWrapperType(kind.kind) + if isinstance(kind, mojom.Interface): + return "%sPtr" % GetNameForKind(kind) + if isinstance(kind, mojom.InterfaceRequest): + return "mojo::InterfaceRequest<%s>" % GetNameForKind(kind.kind) + if isinstance(kind, mojom.Enum): + return GetNameForKind(kind) + if kind.spec == 's': + return "const mojo::String&" + if kind.spec == 'h': + return "mojo::ScopedHandle" + if kind.spec == 'h:d:c': + return "mojo::ScopedDataPipeConsumerHandle" + if kind.spec == 'h:d:p': + return "mojo::ScopedDataPipeProducerHandle" + if kind.spec == 'h:m': + return "mojo::ScopedMessagePipeHandle" + if kind.spec == 'h:s': + return "mojo::ScopedSharedBufferHandle" + if not kind in _kind_to_cpp_type: + print "missing:", kind.spec + return _kind_to_cpp_type[kind] + +def GetCppFieldType(kind): + if isinstance(kind, mojom.Struct): + return ("mojo::internal::StructPointer<%s_Data>" % + GetNameForKind(kind, internal=True)) + if isinstance(kind, mojom.Array): + return "mojo::internal::ArrayPointer<%s>" % GetCppType(kind.kind) + if isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest): + return "mojo::MessagePipeHandle" + if isinstance(kind, mojom.Enum): + return GetNameForKind(kind) + if kind.spec == 's': + return "mojo::internal::StringPointer" + return _kind_to_cpp_type[kind] + +def IsStructWithHandles(struct): + for pf in struct.packed.packed_fields: + if generator.IsHandleKind(pf.field.kind): + return True + return False + +def TranslateConstants(token): + if isinstance(token, (mojom.NamedValue, mojom.EnumValue)): + # Both variable and enum constants are constructed like: + # Namespace::Struct::CONSTANT_NAME + name = [] + if token.imported_from: + name.extend(NamespaceToArray(token.namespace)) + if token.parent_kind: + name.append(token.parent_kind.name) + name.append(token.name) + return "::".join(name) + return token + +def ExpressionToText(value): + return TranslateConstants(value) + +def HasCallbacks(interface): + for method in interface.methods: + if method.response_parameters != None: + return True + return False + +def ShouldInlineStruct(struct): + # TODO(darin): Base this on the size of the wrapper class. + if len(struct.fields) > 4: + return False + for field in struct.fields: + if generator.IsHandleKind(field.kind) or generator.IsObjectKind(field.kind): + return False + return True + +_HEADER_SIZE = 8 + +class Generator(generator.Generator): + + cpp_filters = { + "cpp_const_wrapper_type": GetCppConstWrapperType, + "cpp_field_type": GetCppFieldType, + "cpp_pod_type": GetCppPodType, + "cpp_result_type": GetCppResultWrapperType, + "cpp_type": GetCppType, + "cpp_wrapper_type": GetCppWrapperType, + "default_value": DefaultValue, + "expression_to_text": ExpressionToText, + "get_name_for_kind": GetNameForKind, + "get_pad": pack.GetPad, + "has_callbacks": HasCallbacks, + "should_inline": ShouldInlineStruct, + "is_enum_kind": generator.IsEnumKind, + "is_move_only_kind": generator.IsMoveOnlyKind, + "is_handle_kind": generator.IsHandleKind, + "is_interface_kind": generator.IsInterfaceKind, + "is_interface_request_kind": generator.IsInterfaceRequestKind, + "is_object_kind": generator.IsObjectKind, + "is_string_kind": generator.IsStringKind, + "is_struct_with_handles": IsStructWithHandles, + "struct_size": lambda ps: ps.GetTotalSize() + _HEADER_SIZE, + "struct_from_method": generator.GetStructFromMethod, + "response_struct_from_method": generator.GetResponseStructFromMethod, + "stylize_method": generator.StudlyCapsToCamel, + } + + def GetJinjaExports(self): + return { + "module": self.module, + "namespace": self.module.namespace, + "namespaces_as_array": NamespaceToArray(self.module.namespace), + "imports": self.module.imports, + "kinds": self.module.kinds, + "enums": self.module.enums, + "structs": self.GetStructs(), + "interfaces": self.module.interfaces, + } + + @UseJinja("cpp_templates/module.h.tmpl", filters=cpp_filters) + def GenerateModuleHeader(self): + return self.GetJinjaExports() + + @UseJinja("cpp_templates/module-internal.h.tmpl", filters=cpp_filters) + def GenerateModuleInternalHeader(self): + return self.GetJinjaExports() + + @UseJinja("cpp_templates/module.cc.tmpl", filters=cpp_filters) + def GenerateModuleSource(self): + return self.GetJinjaExports() + + def GenerateFiles(self, args): + self.Write(self.GenerateModuleHeader(), "%s.h" % self.module.name) + self.Write(self.GenerateModuleInternalHeader(), + "%s-internal.h" % self.module.name) + self.Write(self.GenerateModuleSource(), "%s.cc" % self.module.name) diff --git a/chromium/mojo/public/tools/bindings/generators/mojom_java_generator.py b/chromium/mojo/public/tools/bindings/generators/mojom_java_generator.py new file mode 100644 index 00000000000..ca43b845630 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojom_java_generator.py @@ -0,0 +1,187 @@ +# 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. + +"""Generates java source files from a mojom.Module.""" + +import argparse +import os +import re + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +from mojom.generate.template_expander import UseJinja + + +GENERATOR_PREFIX = 'java' + +_spec_to_java_type = { + 'b': 'boolean', + 'd': 'double', + 'f': 'float', + 'h:d:c': 'org.chromium.mojo.system.DataPipe.ConsumerHandle', + 'h:d:p': 'org.chromium.mojo.system.DataPipe.ProducerHandle', + 'h:m': 'org.chromium.mojo.system.MessagePipeHandle', + 'h': 'org.chromium.mojo.system.UntypedHandle', + 'h:s': 'org.chromium.mojo.system.SharedBufferHandle', + 'i16': 'short', + 'i32': 'int', + 'i64': 'long', + 'i8': 'byte', + 's': 'String', + 'u16': 'short', + 'u32': 'int', + 'u64': 'long', + 'u8': 'byte', +} + + +def NameToComponent(name): + # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar -> + # HTTP_Entry2_FooBar) + name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name) + # insert '_' between non upper and start of upper blocks (e.g., + # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar) + name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name) + return [x.lower() for x in name.split('_')] + +def CapitalizeFirst(string): + return string[0].upper() + string[1:] + +def UpperCamelCase(name): + return ''.join([CapitalizeFirst(x) for x in NameToComponent(name)]) + +def CamelCase(name): + uccc = UpperCamelCase(name) + return uccc[0].lower() + uccc[1:] + +def ConstantStyle(name): + components = NameToComponent(name) + if components[0] == 'k': + components = components[1:] + return '_'.join([x.upper() for x in components]) + +def GetNameForElement(element): + if (isinstance(element, mojom.Enum) or + isinstance(element, mojom.Interface) or + isinstance(element, mojom.Struct)): + return UpperCamelCase(element.name) + if (isinstance(element, mojom.Method) or + isinstance(element, mojom.Parameter) or + isinstance(element, mojom.Field)): + return CamelCase(element.name) + if isinstance(element, mojom.EnumValue): + return (UpperCamelCase(element.enum_name) + '.' + + ConstantStyle(element.name)) + if (isinstance(element, mojom.NamedValue) or + isinstance(element, mojom.Constant)): + return ConstantStyle(element.name) + raise Exception("Unexpected element: " % element) + +def ParseStringAttribute(attribute): + assert isinstance(attribute, basestring) + return attribute + +def GetPackage(module): + if 'JavaPackage' in module.attributes: + return ParseStringAttribute(module.attributes['JavaPackage']) + # Default package. + return "org.chromium.mojom." + module.namespace + +def GetNameForKind(kind): + def _GetNameHierachy(kind): + hierachy = [] + if kind.parent_kind: + hierachy = _GetNameHierachy(kind.parent_kind) + hierachy.append(kind.name) + return hierachy + + elements = [GetPackage(kind.module)] + elements += _GetNameHierachy(kind) + return '.'.join(elements) + +def GetJavaType(kind): + if isinstance(kind, (mojom.Struct, mojom.Interface)): + return GetNameForKind(kind) + if isinstance(kind, mojom.Array): + return "%s[]" % GetJavaType(kind.kind) + if isinstance(kind, mojom.Enum): + return "int" + return _spec_to_java_type[kind.spec] + +def ExpressionToText(token): + def _TranslateNamedValue(named_value): + entity_name = GetNameForElement(named_value) + if named_value.parent_kind: + return GetJavaType(named_value.parent_kind) + '.' + entity_name + # Handle the case where named_value is a module level constant: + if not isinstance(named_value, mojom.EnumValue): + entity_name = (GetConstantsMainEntityName(named_value.module) + '.' + + entity_name) + return GetPackage(named_value.module) + '.' + entity_name + + if isinstance(token, mojom.NamedValue): + return _TranslateNamedValue(token) + # Add Long suffix to all number literals. + if re.match('^[0-9]+$', token): + return token + 'L' + return token + +def GetConstantsMainEntityName(module): + if 'JavaConstantsClassName' in module.attributes: + return ParseStringAttribute(module.attributes['JavaConstantsClassName']) + # This constructs the name of the embedding classes for module level constants + # by extracting the mojom's filename and prepending it to Constants. + return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) + + 'Constants') + +class Generator(generator.Generator): + + java_filters = { + "expression_to_text": ExpressionToText, + "java_type": GetJavaType, + "name": GetNameForElement, + } + + def GetJinjaExports(self): + return { + "module": self.module, + "package": GetPackage(self.module), + } + + @UseJinja("java_templates/enum.java.tmpl", filters=java_filters, + lstrip_blocks=True, trim_blocks=True) + def GenerateEnumSource(self, enum): + exports = self.GetJinjaExports() + exports.update({"enum": enum}) + return exports + + @UseJinja("java_templates/constants.java.tmpl", filters=java_filters, + lstrip_blocks=True, trim_blocks=True) + def GenerateConstantsSource(self, module): + exports = self.GetJinjaExports() + exports.update({"main_entity": GetConstantsMainEntityName(module), + "constants": module.constants}) + return exports + + def GenerateFiles(self, unparsed_args): + parser = argparse.ArgumentParser() + parser.add_argument("--java_output_directory", dest="java_output_directory") + args = parser.parse_args(unparsed_args) + if self.output_dir and args.java_output_directory: + self.output_dir = os.path.join(args.java_output_directory, + GetPackage(self.module).replace('.', '/')) + if not os.path.exists(self.output_dir): + try: + os.makedirs(self.output_dir) + except: + # Ignore errors on directory creation. + pass + + for enum in self.module.enums: + self.Write(self.GenerateEnumSource(enum), + "%s.java" % GetNameForElement(enum)) + + if self.module.constants: + self.Write(self.GenerateConstantsSource(self.module), + "%s.java" % GetConstantsMainEntityName(self.module)) diff --git a/chromium/mojo/public/tools/bindings/generators/mojom_js_generator.py b/chromium/mojo/public/tools/bindings/generators/mojom_js_generator.py new file mode 100644 index 00000000000..1fa351037c6 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/mojom_js_generator.py @@ -0,0 +1,186 @@ +# 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. + +"""Generates JavaScript source files from a mojom.Module.""" + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +from mojom.generate.template_expander import UseJinja + +_kind_to_javascript_default_value = { + mojom.BOOL: "false", + mojom.INT8: "0", + mojom.UINT8: "0", + mojom.INT16: "0", + mojom.UINT16: "0", + mojom.INT32: "0", + mojom.UINT32: "0", + mojom.FLOAT: "0", + mojom.HANDLE: "null", + mojom.DCPIPE: "null", + mojom.DPPIPE: "null", + mojom.MSGPIPE: "null", + mojom.SHAREDBUFFER: "null", + mojom.INT64: "0", + mojom.UINT64: "0", + mojom.DOUBLE: "0", + mojom.STRING: '""', +} + + +def JavaScriptDefaultValue(field): + if field.default: + if isinstance(field.kind, mojom.Struct): + assert field.default == "default" + return "new %s()" % JavascriptType(field.kind) + return ExpressionToText(field.default) + if field.kind in mojom.PRIMITIVES: + return _kind_to_javascript_default_value[field.kind] + if isinstance(field.kind, mojom.Struct): + return "null" + if isinstance(field.kind, mojom.Array): + return "[]" + if isinstance(field.kind, mojom.Interface) or \ + isinstance(field.kind, mojom.InterfaceRequest): + return _kind_to_javascript_default_value[mojom.MSGPIPE] + if isinstance(field.kind, mojom.Enum): + return "0" + + +def JavaScriptPayloadSize(packed): + packed_fields = packed.packed_fields + if not packed_fields: + return 0 + last_field = packed_fields[-1] + offset = last_field.offset + last_field.size + pad = pack.GetPad(offset, 8) + return offset + pad + + +_kind_to_codec_type = { + mojom.BOOL: "codec.Uint8", + mojom.INT8: "codec.Int8", + mojom.UINT8: "codec.Uint8", + mojom.INT16: "codec.Int16", + mojom.UINT16: "codec.Uint16", + mojom.INT32: "codec.Int32", + mojom.UINT32: "codec.Uint32", + mojom.FLOAT: "codec.Float", + mojom.HANDLE: "codec.Handle", + mojom.DCPIPE: "codec.Handle", + mojom.DPPIPE: "codec.Handle", + mojom.MSGPIPE: "codec.Handle", + mojom.SHAREDBUFFER: "codec.Handle", + mojom.INT64: "codec.Int64", + mojom.UINT64: "codec.Uint64", + mojom.DOUBLE: "codec.Double", + mojom.STRING: "codec.String", +} + + +def CodecType(kind): + if kind in mojom.PRIMITIVES: + return _kind_to_codec_type[kind] + if isinstance(kind, mojom.Struct): + return "new codec.PointerTo(%s)" % CodecType(kind.name) + if isinstance(kind, mojom.Array): + return "new codec.ArrayOf(%s)" % CodecType(kind.kind) + if isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest): + return CodecType(mojom.MSGPIPE) + if isinstance(kind, mojom.Enum): + return _kind_to_codec_type[mojom.INT32] + return kind + + +def JavaScriptDecodeSnippet(kind): + if kind in mojom.PRIMITIVES: + return "decodeStruct(%s)" % CodecType(kind) + if isinstance(kind, mojom.Struct): + return "decodeStructPointer(%s)" % CodecType(kind.name) + if isinstance(kind, mojom.Array): + return "decodeArrayPointer(%s)" % CodecType(kind.kind) + if isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest): + return JavaScriptDecodeSnippet(mojom.MSGPIPE) + if isinstance(kind, mojom.Enum): + return JavaScriptDecodeSnippet(mojom.INT32) + + +def JavaScriptEncodeSnippet(kind): + if kind in mojom.PRIMITIVES: + return "encodeStruct(%s, " % CodecType(kind) + if isinstance(kind, mojom.Struct): + return "encodeStructPointer(%s, " % CodecType(kind.name) + if isinstance(kind, mojom.Array): + return "encodeArrayPointer(%s, " % CodecType(kind.kind) + if isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest): + return JavaScriptEncodeSnippet(mojom.MSGPIPE) + if isinstance(kind, mojom.Enum): + return JavaScriptEncodeSnippet(mojom.INT32) + + +def TranslateConstants(token): + if isinstance(token, (mojom.EnumValue, mojom.NamedValue)): + # Both variable and enum constants are constructed like: + # NamespaceUid.Struct[.Enum].CONSTANT_NAME + name = [] + if token.imported_from: + name.append(token.imported_from["unique_name"]) + if token.parent_kind: + name.append(token.parent_kind.name) + if isinstance(token, mojom.EnumValue): + name.append(token.enum_name) + name.append(token.name) + return ".".join(name) + return token + + +def ExpressionToText(value): + return TranslateConstants(value) + + +def JavascriptType(kind): + if kind.imported_from: + return kind.imported_from["unique_name"] + "." + kind.name + return kind.name + + +class Generator(generator.Generator): + + js_filters = { + "default_value": JavaScriptDefaultValue, + "payload_size": JavaScriptPayloadSize, + "decode_snippet": JavaScriptDecodeSnippet, + "encode_snippet": JavaScriptEncodeSnippet, + "expression_to_text": ExpressionToText, + "js_type": JavascriptType, + "stylize_method": generator.StudlyCapsToCamel, + } + + @UseJinja("js_templates/module.js.tmpl", filters=js_filters) + def GenerateJsModule(self): + return { + "namespace": self.module.namespace, + "imports": self.GetImports(), + "kinds": self.module.kinds, + "enums": self.module.enums, + "module": self.module, + "structs": self.GetStructs() + self.GetStructsFromMethods(), + "interfaces": self.module.interfaces, + } + + def GenerateFiles(self, args): + self.Write(self.GenerateJsModule(), "%s.js" % self.module.name) + + def GetImports(self): + # Since each import is assigned a variable in JS, they need to have unique + # names. + counter = 1 + for each in self.module.imports: + each["unique_name"] = "import" + str(counter) + counter += 1 + return self.module.imports diff --git a/chromium/mojo/public/tools/bindings/generators/run_cpp_generator.py b/chromium/mojo/public/tools/bindings/generators/run_cpp_generator.py new file mode 100755 index 00000000000..4c6f5974069 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/generators/run_cpp_generator.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# 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. + +import ast +import os +import sys + +script_dir = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, os.path.join(script_dir, os.pardir, "pylib")) + +from mojom.generate.data +import mojom_cpp_generator + +def ReadDict(file): + with open(file, 'r') as f: + s = f.read() + dict = ast.literal_eval(s) + return dict + +dict = ReadDict(sys.argv[1]) +module = mojom.generate.data.ModuleFromData(dict) +dir = None +if len(sys.argv) > 2: + dir = sys.argv[2] +cpp = mojom_cpp_generator.Generator(module, ".", dir) +cpp.GenerateFiles([]) diff --git a/chromium/mojo/public/tools/bindings/mojom.gni b/chromium/mojo/public/tools/bindings/mojom.gni new file mode 100644 index 00000000000..3376abfb6d0 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/mojom.gni @@ -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. + +# Generate C++ and JavaScript source files from mojom files. +template("mojom") { + assert(defined(invoker.sources), + "\"sources\" must be defined for the $target_name template.") + + generator_root = "//mojo/public/tools/bindings" + generator_script = "$generator_root/mojom_bindings_generator.py" + generator_sources = [ + generator_script, + "$generator_root/generators/cpp_templates/enum_declaration.tmpl", + "$generator_root/generators/cpp_templates/interface_declaration.tmpl", + "$generator_root/generators/cpp_templates/interface_definition.tmpl", + "$generator_root/generators/cpp_templates/interface_macros.tmpl", + "$generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl", + "$generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl", + "$generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl", + "$generator_root/generators/cpp_templates/interface_stub_declaration.tmpl", + "$generator_root/generators/cpp_templates/module.cc.tmpl", + "$generator_root/generators/cpp_templates/module.h.tmpl", + "$generator_root/generators/cpp_templates/module-internal.h.tmpl", + "$generator_root/generators/cpp_templates/params_definition.tmpl", + "$generator_root/generators/cpp_templates/struct_declaration.tmpl", + "$generator_root/generators/cpp_templates/struct_definition.tmpl", + "$generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl", + "$generator_root/generators/cpp_templates/struct_serialization_definition.tmpl", + "$generator_root/generators/cpp_templates/struct_macros.tmpl", + "$generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl", + "$generator_root/generators/cpp_templates/wrapper_class_definition.tmpl", + "$generator_root/generators/js_templates/enum_definition.tmpl", + "$generator_root/generators/js_templates/interface_definition.tmpl", + "$generator_root/generators/js_templates/module.js.tmpl", + "$generator_root/generators/js_templates/struct_definition.tmpl", + "$generator_root/generators/mojom_cpp_generator.py", + "$generator_root/generators/mojom_js_generator.py", + "$generator_root/pylib/mojom/__init__.py", + "$generator_root/pylib/mojom/error.py", + "$generator_root/pylib/mojom/generate/__init__.py", + "$generator_root/pylib/mojom/generate/data.py", + "$generator_root/pylib/mojom/generate/generator.py", + "$generator_root/pylib/mojom/generate/module.py", + "$generator_root/pylib/mojom/generate/pack.py", + "$generator_root/pylib/mojom/generate/template_expander.py", + "$generator_root/pylib/mojom/parse/__init__.py", + "$generator_root/pylib/mojom/parse/ast.py", + "$generator_root/pylib/mojom/parse/lexer.py", + "$generator_root/pylib/mojom/parse/parser.py", + "$generator_root/pylib/mojom/parse/translate.py", + ] + generator_cpp_outputs = [ + "$target_gen_dir/{{source_name_part}}.mojom.cc", + "$target_gen_dir/{{source_name_part}}.mojom.h", + "$target_gen_dir/{{source_name_part}}.mojom-internal.h", + ] + generator_js_outputs = [ + "$target_gen_dir/{{source_name_part}}.mojom.js", + ] + + target_visibility = ":$target_name" + + generator_target_name = target_name + "_generator" + action_foreach(generator_target_name) { + visibility = target_visibility + script = generator_script + source_prereqs = generator_sources + sources = invoker.sources + outputs = generator_cpp_outputs + generator_js_outputs + args = [ + "{{source}}", + "--use_chromium_bundled_pylibs", + "-d", rebase_path("//", root_build_dir), + "-o", rebase_path(target_gen_dir, root_build_dir), + ] + } + + source_set(target_name) { + if (defined(invoker.visibility)) { + visibility = invoker.visibility + } + sources = process_file_template(invoker.sources, generator_cpp_outputs) + data = process_file_template(invoker.sources, generator_js_outputs) + deps = [ + ":$generator_target_name", + "//mojo/public/cpp/bindings", + ] + } +} diff --git a/chromium/mojo/public/tools/bindings/mojom_bindings_generator.gypi b/chromium/mojo/public/tools/bindings/mojom_bindings_generator.gypi new file mode 100644 index 00000000000..1341685a2ee --- /dev/null +++ b/chromium/mojo/public/tools/bindings/mojom_bindings_generator.gypi @@ -0,0 +1,99 @@ +# 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. + +{ + 'rules': [ + { + 'rule_name': 'Generate C++, JS and Java source files from mojom files', + 'extension': 'mojom', + 'variables': { + 'mojom_base_output_dir': + '<!(python <(DEPTH)/build/inverse_depth.py <(DEPTH))', + 'mojom_bindings_generator': + '<(DEPTH)/mojo/public/tools/bindings/mojom_bindings_generator.py', + 'java_out_dir': '<(PRODUCT_DIR)/java_mojo/<(_target_name)/src', + }, + 'inputs': [ + '<(mojom_bindings_generator)', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/enum_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/module-internal.h.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/params_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/java_macros.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl', + '<(DEPTH)/mojo/public/tools/bindings/generators/mojom_cpp_generator.py', + '<(DEPTH)/mojo/public/tools/bindings/generators/mojom_java_generator.py', + '<(DEPTH)/mojo/public/tools/bindings/generators/mojom_js_generator.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/__init__.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/error.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/generate/data.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/generate/generator.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/generate/module.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/generate/pack.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/parse/ast.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/parse/parser.py', + '<(DEPTH)/mojo/public/tools/bindings/pylib/mojom/parse/translate.py', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/<(mojom_base_output_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).mojom.cc', + '<(SHARED_INTERMEDIATE_DIR)/<(mojom_base_output_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).mojom.h', + '<(SHARED_INTERMEDIATE_DIR)/<(mojom_base_output_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).mojom.js', + '<(SHARED_INTERMEDIATE_DIR)/<(mojom_base_output_dir)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).mojom-internal.h', + ], + 'action': [ + 'python', '<@(mojom_bindings_generator)', + './<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).mojom', + '--use_chromium_bundled_pylibs', + '-d', '<(DEPTH)', + '-o', '<(SHARED_INTERMEDIATE_DIR)/<(mojom_base_output_dir)/<(RULE_INPUT_DIRNAME)', + '--java_output_directory=<(java_out_dir)', + ], + 'message': 'Generating Mojo bindings from <(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).mojom', + 'process_outputs_as_sources': 1, + } + ], + 'include_dirs': [ + '<(DEPTH)', + '<(SHARED_INTERMEDIATE_DIR)', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(DEPTH)', + '<(SHARED_INTERMEDIATE_DIR)', + ], + 'variables': { + 'generated_src_dirs': [ + '<(PRODUCT_DIR)/java_mojo/<(_target_name)/src', + ], + }, + }, + 'hard_dependency': 1, +} diff --git a/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py b/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py new file mode 100755 index 00000000000..93325767b2a --- /dev/null +++ b/chromium/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# 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. + +"""The frontend for the Mojo bindings system.""" + + +import argparse +import imp +import os +import pprint +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +# Manually check for the command-line flag. (This isn't quite right, since it +# ignores, e.g., "--", but it's close enough.) +if "--use_chromium_bundled_pylibs" in sys.argv[1:]: + sys.path.insert(0, os.path.join(_GetDirAbove("mojo"), "third_party")) + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + "pylib")) + +from mojom.error import Error +from mojom.generate.data import OrderedModuleFromData +from mojom.parse.parser import Parse +from mojom.parse.translate import Translate + + +def LoadGenerators(generators_string): + if not generators_string: + return [] # No generators. + + script_dir = os.path.dirname(os.path.abspath(__file__)) + generators = [] + for generator_name in [s.strip() for s in generators_string.split(",")]: + # "Built-in" generators: + if generator_name.lower() == "c++": + generator_name = os.path.join(script_dir, "generators", + "mojom_cpp_generator.py") + elif generator_name.lower() == "javascript": + generator_name = os.path.join(script_dir, "generators", + "mojom_js_generator.py") + elif generator_name.lower() == "java": + generator_name = os.path.join(script_dir, "generators", + "mojom_java_generator.py") + # Specified generator python module: + elif generator_name.endswith(".py"): + pass + else: + print "Unknown generator name %s" % generator_name + sys.exit(1) + generator_module = imp.load_source(os.path.basename(generator_name)[:-3], + generator_name) + generators.append(generator_module) + return generators + + +def MakeImportStackMessage(imported_filename_stack): + """Make a (human-readable) message listing a chain of imports. (Returned + string begins with a newline (if nonempty) and does not end with one.)""" + return ''.join( + reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \ + zip(imported_filename_stack[1:], imported_filename_stack)])) + + +# Disable check for dangerous default arguments (they're "private" keyword +# arguments; note that we want |_processed_files| to memoize across invocations +# of |ProcessFile()|): +# pylint: disable=W0102 +def ProcessFile(args, remaining_args, generator_modules, filename, + _processed_files={}, _imported_filename_stack=None): + # Memoized results. + if filename in _processed_files: + return _processed_files[filename] + + if _imported_filename_stack is None: + _imported_filename_stack = [] + + # Ensure we only visit each file once. + if filename in _imported_filename_stack: + print "%s: Error: Circular dependency" % filename + \ + MakeImportStackMessage(_imported_filename_stack + [filename]) + sys.exit(1) + + try: + with open(filename) as f: + source = f.read() + except IOError as e: + print "%s: Error: %s" % (e.filename, e.strerror) + \ + MakeImportStackMessage(_imported_filename_stack + [filename]) + sys.exit(1) + + try: + tree = Parse(source, filename) + except Error as e: + print str(e) + MakeImportStackMessage(_imported_filename_stack + [filename]) + sys.exit(1) + + dirname, name = os.path.split(filename) + mojom = Translate(tree, name) + if args.debug_print_intermediate: + pprint.PrettyPrinter().pprint(mojom) + + # Process all our imports first and collect the module object for each. + # We use these to generate proper type info. + for import_data in mojom['imports']: + import_filename = os.path.join(dirname, import_data['filename']) + import_data['module'] = ProcessFile( + args, remaining_args, generator_modules, import_filename, + _processed_files=_processed_files, + _imported_filename_stack=_imported_filename_stack + [filename]) + + module = OrderedModuleFromData(mojom) + + # Set the path as relative to the source root. + module.path = os.path.relpath(os.path.abspath(filename), + os.path.abspath(args.depth)) + + # Normalize to unix-style path here to keep the generators simpler. + module.path = module.path.replace('\\', '/') + + for generator_module in generator_modules: + generator = generator_module.Generator(module, args.output_dir) + filtered_args = [] + if hasattr(generator_module, 'GENERATOR_PREFIX'): + prefix = '--' + generator_module.GENERATOR_PREFIX + '_' + filtered_args = [arg for arg in remaining_args if arg.startswith(prefix)] + generator.GenerateFiles(filtered_args) + + # Save result. + _processed_files[filename] = module + return module +# pylint: enable=W0102 + + +def main(): + parser = argparse.ArgumentParser( + description="Generate bindings from mojom files.") + parser.add_argument("filename", nargs="+", + help="mojom input file") + parser.add_argument("-d", "--depth", dest="depth", default=".", + help="depth from source root") + parser.add_argument("-o", "--output_dir", dest="output_dir", default=".", + help="output directory for generated files") + parser.add_argument("-g", "--generators", dest="generators_string", + metavar="GENERATORS", default="c++,javascript,java", + help="comma-separated list of generators") + parser.add_argument("--debug_print_intermediate", action="store_true", + help="print the intermediate representation") + parser.add_argument("--use_chromium_bundled_pylibs", action="store_true", + help="use Python modules bundled in the Chromium source") + (args, remaining_args) = parser.parse_known_args() + + generator_modules = LoadGenerators(args.generators_string) + + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + + for filename in args.filename: + ProcessFile(args, remaining_args, generator_modules, filename) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py new file mode 100644 index 00000000000..de388561cb5 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -0,0 +1,23 @@ +# 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 unittest + +from mojom_bindings_generator import MakeImportStackMessage + + +class MojoBindingsGeneratorTest(unittest.TestCase): + """Tests mojo_bindings_generator.""" + + def testMakeImportStackMessage(self): + """Tests MakeImportStackMessage().""" + self.assertEquals(MakeImportStackMessage(["x"]), "") + self.assertEquals(MakeImportStackMessage(["x", "y"]), + "\n y was imported by x") + self.assertEquals(MakeImportStackMessage(["x", "y", "z"]), + "\n z was imported by y\n y was imported by x") + + +if __name__ == "__main__": + unittest.main() diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/__init__.py b/chromium/mojo/public/tools/bindings/pylib/mojom/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/__init__.py diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/error.py b/chromium/mojo/public/tools/bindings/pylib/mojom/error.py new file mode 100644 index 00000000000..99522b9507f --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/error.py @@ -0,0 +1,27 @@ +# 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. + +class Error(Exception): + """Base class for Mojo IDL bindings parser/generator errors.""" + + def __init__(self, filename, message, lineno=None, addenda=None, **kwargs): + """|filename| is the (primary) file which caused the error, |message| is the + error message, |lineno| is the 1-based line number (or |None| if not + applicable/available), and |addenda| is a list of additional lines to append + to the final error message.""" + Exception.__init__(self, **kwargs) + self.filename = filename + self.message = message + self.lineno = lineno + self.addenda = addenda + + def __str__(self): + if self.lineno: + s = "%s:%d: Error: %s" % (self.filename, self.lineno, self.message) + else: + s = "%s: Error: %s" % (self.filename, self.message) + return "\n".join([s] + self.addenda) if self.addenda else s + + def __repr__(self): + return str(self) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/data.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/data.py new file mode 100644 index 00000000000..608603b8584 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/data.py @@ -0,0 +1,348 @@ +# 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. + +# TODO(vtl): "data" is a pretty vague name. Rename it? + +import copy + +import module as mojom + +# This module provides a mechanism to turn mojom Modules to dictionaries and +# back again. This can be used to persist a mojom Module created progromatically +# or to read a dictionary from code or a file. +# Example: +# test_dict = { +# 'name': 'test', +# 'namespace': 'testspace', +# 'structs': [{ +# 'name': 'teststruct', +# 'fields': [ +# {'name': 'testfield1', 'kind': 'i32'}, +# {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}], +# 'interfaces': [{ +# 'name': 'Server', +# 'methods': [{ +# 'name': 'Foo', +# 'parameters': [{ +# 'name': 'foo', 'kind': 'i32'}, +# {'name': 'bar', 'kind': 'a:x:teststruct'}], +# 'ordinal': 42}]}] +# } +# test_module = data.ModuleFromData(test_dict) + +# Used to create a subclass of str that supports sorting by index, to make +# pretty printing maintain the order. +def istr(index, string): + class IndexedString(str): + def __lt__(self, other): + return self.__index__ < other.__index__ + + istr = IndexedString(string) + istr.__index__ = index + return istr + +def LookupKind(kinds, spec, scope): + """Tries to find which Kind a spec refers to, given the scope in which its + referenced. Starts checking from the narrowest scope to most general. For + example, given a struct field like + Foo.Bar x; + Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner + type 'Bar' in the struct 'Foo' in the current namespace. + + |scope| is a tuple that looks like (namespace, struct/interface), referring + to the location where the type is referenced.""" + if spec.startswith('x:'): + name = spec[2:] + for i in xrange(len(scope), -1, -1): + test_spec = 'x:' + if i > 0: + test_spec += '.'.join(scope[:i]) + '.' + test_spec += name + kind = kinds.get(test_spec) + if kind: + return kind + + return kinds.get(spec) + +def LookupValue(values, name, scope): + """Like LookupKind, but for constant values.""" + for i in xrange(len(scope), -1, -1): + if i > 0: + test_spec = '.'.join(scope[:i]) + '.' + test_spec += name + value = values.get(test_spec) + if value: + return value + + return values.get(name) + +def FixupExpression(module, value, scope): + """Translates an IDENTIFIER into a structured Value object.""" + if isinstance(value, tuple) and value[0] == 'IDENTIFIER': + result = LookupValue(module.values, value[1], scope) + if result: + return result + return value + +def KindToData(kind): + return kind.spec + +def KindFromData(kinds, data, scope): + kind = LookupKind(kinds, data, scope) + if kind: + return kind + if data.startswith('a:'): + kind = mojom.Array() + kind.kind = KindFromData(kinds, data[2:], scope) + elif data.startswith('r:'): + kind = mojom.InterfaceRequest() + kind.kind = KindFromData(kinds, data[2:], scope) + else: + kind = mojom.Kind() + kind.spec = data + kinds[data] = kind + return kind + +def KindFromImport(original_kind, imported_from): + """Used with 'import module' - clones the kind imported from the given + module's namespace. Only used with Structs, Interfaces and Enums.""" + kind = copy.deepcopy(original_kind) + kind.imported_from = imported_from + return kind + +def ImportFromData(module, data): + import_module = data['module'] + + import_item = {} + import_item['module_name'] = import_module.name + import_item['namespace'] = import_module.namespace + import_item['module'] = import_module + + # Copy the struct kinds from our imports into the current module. + for kind in import_module.kinds.itervalues(): + if (isinstance(kind, (mojom.Struct, mojom.Enum, mojom.Interface)) and + kind.imported_from is None): + kind = KindFromImport(kind, import_item) + module.kinds[kind.spec] = kind + # Ditto for values. + for value in import_module.values.itervalues(): + if value.imported_from is None: + value = copy.deepcopy(value) + value.imported_from = import_item + module.values[value.GetSpec()] = value + + return import_item + +def StructToData(struct): + return { + istr(0, 'name'): struct.name, + istr(1, 'fields'): map(FieldToData, struct.fields) + } + +def StructFromData(module, data): + struct = mojom.Struct(module=module) + struct.name = data['name'] + struct.attributes = data['attributes'] + struct.spec = 'x:' + module.namespace + '.' + struct.name + module.kinds[struct.spec] = struct + struct.enums = map(lambda enum: + EnumFromData(module, enum, struct), data['enums']) + struct.constants = map(lambda constant: + ConstantFromData(module, constant, struct), data['constants']) + # Stash fields data here temporarily. + struct.fields_data = data['fields'] + return struct + +def FieldToData(field): + data = { + istr(0, 'name'): field.name, + istr(1, 'kind'): KindToData(field.kind) + } + if field.ordinal != None: + data[istr(2, 'ordinal')] = field.ordinal + if field.default != None: + data[istr(3, 'default')] = field.default + return data + +def FieldFromData(module, data, struct): + field = mojom.Field() + field.name = data['name'] + field.kind = KindFromData( + module.kinds, data['kind'], (module.namespace, struct.name)) + field.ordinal = data.get('ordinal') + field.default = FixupExpression( + module, data.get('default'), (module.namespace, struct.name)) + return field + +def ParameterToData(parameter): + data = { + istr(0, 'name'): parameter.name, + istr(1, 'kind'): parameter.kind.spec + } + if parameter.ordinal != None: + data[istr(2, 'ordinal')] = parameter.ordinal + if parameter.default != None: + data[istr(3, 'default')] = parameter.default + return data + +def ParameterFromData(module, data, interface): + parameter = mojom.Parameter() + parameter.name = data['name'] + parameter.kind = KindFromData( + module.kinds, data['kind'], (module.namespace, interface.name)) + parameter.ordinal = data.get('ordinal') + parameter.default = data.get('default') + return parameter + +def MethodToData(method): + data = { + istr(0, 'name'): method.name, + istr(1, 'parameters'): map(ParameterToData, method.parameters) + } + if method.ordinal != None: + data[istr(2, 'ordinal')] = method.ordinal + if method.response_parameters != None: + data[istr(3, 'response_parameters')] = map( + ParameterToData, method.response_parameters) + return data + +def MethodFromData(module, data, interface): + method = mojom.Method(interface, data['name'], ordinal=data.get('ordinal')) + method.default = data.get('default') + method.parameters = map(lambda parameter: + ParameterFromData(module, parameter, interface), data['parameters']) + if data.has_key('response_parameters'): + method.response_parameters = map( + lambda parameter: ParameterFromData(module, parameter, interface), + data['response_parameters']) + return method + +def InterfaceToData(interface): + return { + istr(0, 'name'): interface.name, + istr(1, 'client'): interface.client, + istr(2, 'methods'): map(MethodToData, interface.methods) + } + +def InterfaceFromData(module, data): + interface = mojom.Interface(module=module) + interface.name = data['name'] + interface.spec = 'x:' + module.namespace + '.' + interface.name + interface.client = data['client'] if data.has_key('client') else None + module.kinds[interface.spec] = interface + interface.enums = map(lambda enum: + EnumFromData(module, enum, interface), data['enums']) + interface.constants = map(lambda constant: + ConstantFromData(module, constant, interface), data['constants']) + # Stash methods data here temporarily. + interface.methods_data = data['methods'] + return interface + +def EnumFieldFromData(module, enum, data, parent_kind): + field = mojom.EnumField() + field.name = data['name'] + # TODO(mpcomplete): FixupExpression should be done in the second pass, + # so constants and enums can refer to each other. + # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or + # vice versa? + if parent_kind: + field.value = FixupExpression( + module, data['value'], (module.namespace, parent_kind.name)) + else: + field.value = FixupExpression( + module, data['value'], (module.namespace, )) + value = mojom.EnumValue(module, enum, field) + module.values[value.GetSpec()] = value + return field + +def EnumFromData(module, data, parent_kind): + enum = mojom.Enum(module=module) + enum.name = data['name'] + name = enum.name + if parent_kind: + name = parent_kind.name + '.' + name + enum.spec = 'x:%s.%s' % (module.namespace, name) + enum.parent_kind = parent_kind + + enum.fields = map( + lambda field: EnumFieldFromData(module, enum, field, parent_kind), + data['fields']) + module.kinds[enum.spec] = enum + return enum + +def ConstantFromData(module, data, parent_kind): + constant = mojom.Constant() + constant.name = data['name'] + if parent_kind: + scope = (module.namespace, parent_kind.name) + else: + scope = (module.namespace, ) + # TODO(mpcomplete): maybe we should only support POD kinds. + constant.kind = KindFromData(module.kinds, data['kind'], scope) + constant.value = FixupExpression(module, data.get('value'), scope) + + value = mojom.NamedValue(module, parent_kind, constant.name) + module.values[value.GetSpec()] = value + return constant + +def ModuleToData(module): + return { + istr(0, 'name'): module.name, + istr(1, 'namespace'): module.namespace, + istr(2, 'structs'): map(StructToData, module.structs), + istr(3, 'interfaces'): map(InterfaceToData, module.interfaces) + } + +def ModuleFromData(data): + module = mojom.Module() + module.kinds = {} + for kind in mojom.PRIMITIVES: + module.kinds[kind.spec] = kind + + module.values = {} + + module.name = data['name'] + module.namespace = data['namespace'] + module.attributes = data['attributes'] + # Imports must come first, because they add to module.kinds which is used + # by by the others. + module.imports = map( + lambda import_data: ImportFromData(module, import_data), + data['imports']) + + # First pass collects kinds. + module.enums = map( + lambda enum: EnumFromData(module, enum, None), data['enums']) + module.structs = map( + lambda struct: StructFromData(module, struct), data['structs']) + module.interfaces = map( + lambda interface: InterfaceFromData(module, interface), + data['interfaces']) + module.constants = map( + lambda constant: ConstantFromData(module, constant, None), + data['constants']) + + # Second pass expands fields and methods. This allows fields and parameters + # to refer to kinds defined anywhere in the mojom. + for struct in module.structs: + struct.fields = map(lambda field: + FieldFromData(module, field, struct), struct.fields_data) + del struct.fields_data + for interface in module.interfaces: + interface.methods = map(lambda method: + MethodFromData(module, method, interface), interface.methods_data) + del interface.methods_data + + return module + +def OrderedModuleFromData(data): + module = ModuleFromData(data) + next_interface_ordinal = 0 + for interface in module.interfaces: + next_ordinal = 0 + for method in interface.methods: + if method.ordinal is None: + method.ordinal = next_ordinal + next_ordinal = method.ordinal + 1 + return module diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/data_tests.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/data_tests.py new file mode 100644 index 00000000000..096554c61a9 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/data_tests.py @@ -0,0 +1,86 @@ +# 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. + +import sys + +import data +import test_support + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest + + +def DeepEquals(d1, d2): + if d1 == d2: + return True + if d2.__class__ != d2.__class__: + return False + if isinstance(d1, dict): + if set(d1.keys()) != set(d2.keys()): + return False + for key in d1.keys(): + if not DeepEquals(d1[key], d2[key]): + return False + return True + if isinstance(d1, (list, tuple)): + if len(d1) != len(d2): + return False + for i in range(len(d1)): + if not DeepEquals(d1[i], d2[i]): + return False + return True + return False + + +test_dict = { + 'name': 'test', + 'namespace': 'testspace', + 'structs': [{ + 'name': 'teststruct', + 'fields': [ + {'name': 'testfield1', 'kind': 'i32'}, + {'name': 'testfield2', 'kind': 'a:i32', 'ordinal': 42}]}], + 'interfaces': [{ + 'name': 'Server', + 'client': None, + 'methods': [{ + 'name': 'Foo', + 'parameters': [ + {'name': 'foo', 'kind': 'i32'}, + {'name': 'bar', 'kind': 'a:x:teststruct'}], + 'ordinal': 42}]}] +} + + +def TestRead(): + module = data.ModuleFromData(test_dict) + return test_support.TestTestModule(module) + + +def TestWrite(): + module = test_support.BuildTestModule() + d = data.ModuleToData(module) + return EXPECT_TRUE(DeepEquals(test_dict, d)) + + +def TestWriteRead(): + module1 = test_support.BuildTestModule() + + dict1 = data.ModuleToData(module1) + module2 = data.ModuleFromData(dict1) + return EXPECT_TRUE(test_support.ModulesAreEqual(module1, module2)) + + +def Main(args): + errors = 0 + errors += RunTest(TestWriteRead) + errors += RunTest(TestRead) + errors += RunTest(TestWrite) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/generator.py new file mode 100644 index 00000000000..707028568c3 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/generator.py @@ -0,0 +1,90 @@ +# 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. + +"""Code shared by the various language-specific code generators.""" + +from functools import partial +import os.path + +import module as mojom +import pack + +def GetStructFromMethod(method): + """Converts a method's parameters into the fields of a struct.""" + params_class = "%s_%s_Params" % (method.interface.name, method.name) + struct = mojom.Struct(params_class, module=method.interface.module) + for param in method.parameters: + struct.AddField(param.name, param.kind, param.ordinal) + struct.packed = pack.PackedStruct(struct) + return struct + +def GetResponseStructFromMethod(method): + """Converts a method's response_parameters into the fields of a struct.""" + params_class = "%s_%s_ResponseParams" % (method.interface.name, method.name) + struct = mojom.Struct(params_class, module=method.interface.module) + for param in method.response_parameters: + struct.AddField(param.name, param.kind, param.ordinal) + struct.packed = pack.PackedStruct(struct) + return struct + +def GetDataHeader(exported, struct): + struct.packed = pack.PackedStruct(struct) + struct.bytes = pack.GetByteLayout(struct.packed) + struct.exported = exported + return struct + +def IsStringKind(kind): + return kind.spec == 's' + +def IsEnumKind(kind): + return isinstance(kind, mojom.Enum) + +def IsObjectKind(kind): + return isinstance(kind, (mojom.Struct, mojom.Array)) or IsStringKind(kind) + +def IsHandleKind(kind): + return kind.spec.startswith('h') or \ + isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest) + +def IsInterfaceKind(kind): + return isinstance(kind, mojom.Interface) + +def IsInterfaceRequestKind(kind): + return isinstance(kind, mojom.InterfaceRequest) + +def IsMoveOnlyKind(kind): + return IsObjectKind(kind) or IsHandleKind(kind) + +def StudlyCapsToCamel(studly): + return studly[0].lower() + studly[1:] + +class Generator(object): + # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all + # files to stdout. + def __init__(self, module, output_dir=None): + self.module = module + self.output_dir = output_dir + + def GetStructsFromMethods(self): + result = [] + for interface in self.module.interfaces: + for method in interface.methods: + result.append(GetStructFromMethod(method)) + if method.response_parameters != None: + result.append(GetResponseStructFromMethod(method)) + return map(partial(GetDataHeader, False), result) + + def GetStructs(self): + return map(partial(GetDataHeader, True), self.module.structs) + + def Write(self, contents, filename): + if self.output_dir is None: + print contents + return + with open(os.path.join(self.output_dir, filename), "w+") as f: + f.write(contents) + + def GenerateFiles(self, args): + raise NotImplementedError("Subclasses must override/implement this method") diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/module.py new file mode 100644 index 00000000000..345f432030f --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/module.py @@ -0,0 +1,217 @@ +# 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. + +# This module's classes provide an interface to mojo modules. Modules are +# collections of interfaces and structs to be used by mojo ipc clients and +# servers. +# +# A simple interface would be created this way: +# module = mojom.generate.module.Module('Foo') +# interface = module.AddInterface('Bar') +# method = interface.AddMethod('Tat', 0) +# method.AddParameter('baz', 0, mojom.INT32) +# + +class Kind(object): + def __init__(self, spec=None): + self.spec = spec + self.parent_kind = None + +# Initialize the set of primitive types. These can be accessed by clients. +BOOL = Kind('b') +INT8 = Kind('i8') +INT16 = Kind('i16') +INT32 = Kind('i32') +INT64 = Kind('i64') +UINT8 = Kind('u8') +UINT16 = Kind('u16') +UINT32 = Kind('u32') +UINT64 = Kind('u64') +FLOAT = Kind('f') +DOUBLE = Kind('d') +STRING = Kind('s') +HANDLE = Kind('h') +DCPIPE = Kind('h:d:c') +DPPIPE = Kind('h:d:p') +MSGPIPE = Kind('h:m') +SHAREDBUFFER = Kind('h:s') + + +# Collection of all Primitive types +PRIMITIVES = ( + BOOL, + INT8, + INT16, + INT32, + INT64, + UINT8, + UINT16, + UINT32, + UINT64, + FLOAT, + DOUBLE, + STRING, + HANDLE, + DCPIPE, + DPPIPE, + MSGPIPE, + SHAREDBUFFER +) + + +class NamedValue(object): + def __init__(self, module, parent_kind, name): + self.module = module + self.namespace = module.namespace + self.parent_kind = parent_kind + self.name = name + self.imported_from = None + + def GetSpec(self): + return (self.namespace + '.' + + (self.parent_kind and (self.parent_kind.name + '.') or "") + + self.name) + + +class EnumValue(NamedValue): + def __init__(self, module, enum, field): + NamedValue.__init__(self, module, enum.parent_kind, field.name) + self.enum_name = enum.name + + +class Constant(object): + def __init__(self, name=None, kind=None, value=None): + self.name = name + self.kind = kind + self.value = value + + +class Field(object): + def __init__(self, name=None, kind=None, ordinal=None, default=None): + self.name = name + self.kind = kind + self.ordinal = ordinal + self.default = default + + +class Struct(Kind): + def __init__(self, name=None, module=None): + self.name = name + self.module = module + self.imported_from = None + if name != None: + spec = 'x:' + name + else: + spec = None + Kind.__init__(self, spec) + self.fields = [] + + def AddField(self, name, kind, ordinal=None, default=None): + field = Field(name, kind, ordinal, default) + self.fields.append(field) + return field + + +class Array(Kind): + def __init__(self, kind=None): + self.kind = kind + if kind != None: + Kind.__init__(self, 'a:' + kind.spec) + else: + Kind.__init__(self) + + +class InterfaceRequest(Kind): + def __init__(self, kind=None): + self.kind = kind + if kind != None: + Kind.__init__(self, 'r:' + kind.spec) + else: + Kind.__init__(self) + + +class Parameter(object): + def __init__(self, name=None, kind=None, ordinal=None, default=None): + self.name = name + self.ordinal = ordinal + self.kind = kind + self.default = default + + +class Method(object): + def __init__(self, interface, name, ordinal=None): + self.interface = interface + self.name = name + self.ordinal = ordinal + self.parameters = [] + self.response_parameters = None + + def AddParameter(self, name, kind, ordinal=None, default=None): + parameter = Parameter(name, kind, ordinal, default) + self.parameters.append(parameter) + return parameter + + def AddResponseParameter(self, name, kind, ordinal=None, default=None): + if self.response_parameters == None: + self.response_parameters = [] + parameter = Parameter(name, kind, ordinal, default) + self.response_parameters.append(parameter) + return parameter + + +class Interface(Kind): + def __init__(self, name=None, client=None, module=None): + self.module = module + self.name = name + self.imported_from = None + if name != None: + spec = 'x:' + name + else: + spec = None + Kind.__init__(self, spec) + self.client = client + self.methods = [] + + def AddMethod(self, name, ordinal=None): + method = Method(self, name, ordinal=ordinal) + self.methods.append(method) + return method + + +class EnumField(object): + def __init__(self, name=None, value=None): + self.name = name + self.value = value + + +class Enum(Kind): + def __init__(self, name=None, module=None): + self.module = module + self.name = name + self.imported_from = None + if name != None: + spec = 'x:' + name + else: + spec = None + Kind.__init__(self, spec) + self.fields = [] + + +class Module(object): + def __init__(self, name=None, namespace=None): + self.name = name + self.path = name + self.namespace = namespace + self.structs = [] + self.interfaces = [] + + def AddInterface(self, name): + interface=Interface(name, module=self); + self.interfaces.append(interface) + return interface + + def AddStruct(self, name): + struct=Struct(name, module=self) + self.structs.append(struct) + return struct diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py new file mode 100644 index 00000000000..a887686e1b5 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py @@ -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. + +import sys + +import test_support + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest +ModulesAreEqual = test_support.ModulesAreEqual +BuildTestModule = test_support.BuildTestModule +TestTestModule = test_support.TestTestModule + + +def BuildAndTestModule(): + return TestTestModule(BuildTestModule()) + + +def TestModulesEqual(): + return EXPECT_TRUE(ModulesAreEqual(BuildTestModule(), BuildTestModule())) + + +def Main(args): + errors = 0 + errors += RunTest(BuildAndTestModule) + errors += RunTest(TestModulesEqual) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/pack.py new file mode 100644 index 00000000000..21d2c184180 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/pack.py @@ -0,0 +1,150 @@ +# 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. + +import module as mojom + +# This module provides a mechanism for determining the packed order and offsets +# of a mojom.Struct. +# +# ps = pack.PackedStruct(struct) +# ps.packed_fields will access a list of PackedField objects, each of which +# will have an offset, a size and a bit (for mojom.BOOLs). + +class PackedField(object): + kind_to_size = { + mojom.BOOL: 1, + mojom.INT8: 1, + mojom.UINT8: 1, + mojom.INT16: 2, + mojom.UINT16: 2, + mojom.INT32: 4, + mojom.UINT32: 4, + mojom.FLOAT: 4, + mojom.HANDLE: 4, + mojom.MSGPIPE: 4, + mojom.SHAREDBUFFER: 4, + mojom.DCPIPE: 4, + mojom.DPPIPE: 4, + mojom.INT64: 8, + mojom.UINT64: 8, + mojom.DOUBLE: 8, + mojom.STRING: 8 + } + + @classmethod + def GetSizeForKind(cls, kind): + if isinstance(kind, mojom.Array) or isinstance(kind, mojom.Struct): + return 8 + if isinstance(kind, mojom.Interface) or \ + isinstance(kind, mojom.InterfaceRequest): + kind = mojom.MSGPIPE + if isinstance(kind, mojom.Enum): + # TODO(mpcomplete): what about big enums? + return cls.kind_to_size[mojom.INT32] + if not kind in cls.kind_to_size: + raise Exception("Invalid kind: %s" % kind.spec) + return cls.kind_to_size[kind] + + def __init__(self, field, ordinal): + self.field = field + self.ordinal = ordinal + self.size = self.GetSizeForKind(field.kind) + self.offset = None + self.bit = None + + +# Returns the pad necessary to reserve space for alignment of |size|. +def GetPad(offset, size): + return (size - (offset % size)) % size + + +# Returns a 2-tuple of the field offset and bit (for BOOLs) +def GetFieldOffset(field, last_field): + if field.field.kind == mojom.BOOL and \ + last_field.field.kind == mojom.BOOL and \ + last_field.bit < 7: + return (last_field.offset, last_field.bit + 1) + + offset = last_field.offset + last_field.size + pad = GetPad(offset, field.size) + return (offset + pad, 0) + + +class PackedStruct(object): + def __init__(self, struct): + self.struct = struct + self.packed_fields = [] + + # No fields. + if (len(struct.fields) == 0): + return + + # Start by sorting by ordinal. + src_fields = [] + ordinal = 0 + for field in struct.fields: + if field.ordinal is not None: + ordinal = field.ordinal + src_fields.append(PackedField(field, ordinal)) + ordinal += 1 + src_fields.sort(key=lambda field: field.ordinal) + + src_field = src_fields[0] + src_field.offset = 0 + src_field.bit = 0 + # dst_fields will contain each of the fields, in increasing offset order. + dst_fields = self.packed_fields + dst_fields.append(src_field) + + # Then find first slot that each field will fit. + for src_field in src_fields[1:]: + last_field = dst_fields[0] + for i in xrange(1, len(dst_fields)): + next_field = dst_fields[i] + offset, bit = GetFieldOffset(src_field, last_field) + if offset + src_field.size <= next_field.offset: + # Found hole. + src_field.offset = offset + src_field.bit = bit + dst_fields.insert(i, src_field) + break + last_field = next_field + if src_field.offset is None: + # Add to end + src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field) + dst_fields.append(src_field) + + def GetTotalSize(self): + if not self.packed_fields: + return 0 + last_field = self.packed_fields[-1] + offset = last_field.offset + last_field.size + pad = GetPad(offset, 8) + return offset + pad + + +class ByteInfo(object): + def __init__(self): + self.is_padding = False + self.packed_fields = [] + + +def GetByteLayout(packed_struct): + bytes = [ByteInfo() for i in xrange(packed_struct.GetTotalSize())] + + limit_of_previous_field = 0 + for packed_field in packed_struct.packed_fields: + for i in xrange(limit_of_previous_field, packed_field.offset): + bytes[i].is_padding = True + bytes[packed_field.offset].packed_fields.append(packed_field) + limit_of_previous_field = packed_field.offset + packed_field.size + + for i in xrange(limit_of_previous_field, len(bytes)): + bytes[i].is_padding = True + + for byte in bytes: + # A given byte cannot both be padding and have a fields packed into it. + assert not (byte.is_padding and byte.packed_fields) + + return bytes diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py new file mode 100644 index 00000000000..4e61004e5f4 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py @@ -0,0 +1,176 @@ +# 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. + +import sys + +import module as mojom +import pack +import test_support + + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest + + +def TestOrdinalOrder(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT32, 2) + struct.AddField('testfield2', mojom.INT32, 1) + ps = pack.PackedStruct(struct) + + errors += EXPECT_EQ(2, len(ps.packed_fields)) + errors += EXPECT_EQ('testfield2', ps.packed_fields[0].field.name) + errors += EXPECT_EQ('testfield1', ps.packed_fields[1].field.name) + + return errors + +def TestZeroFields(): + errors = 0 + struct = mojom.Struct('test') + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(0, len(ps.packed_fields)) + return errors + + +def TestOneField(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT8) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(1, len(ps.packed_fields)) + return errors + +# Pass three tuples. +# |kinds| is a sequence of mojom.Kinds that specify the fields that are to +# be created. +# |fields| is the expected order of the resulting fields, with the integer +# "1" first. +# |offsets| is the expected order of offsets, with the integer "0" first. +def TestSequence(kinds, fields, offsets): + errors = 0 + struct = mojom.Struct('test') + index = 1 + for kind in kinds: + struct.AddField("%d" % index, kind) + index += 1 + ps = pack.PackedStruct(struct) + num_fields = len(ps.packed_fields) + errors += EXPECT_EQ(len(kinds), num_fields) + for i in xrange(num_fields): + EXPECT_EQ("%d" % fields[i], ps.packed_fields[i].field.name) + EXPECT_EQ(offsets[i], ps.packed_fields[i].offset) + + return errors + + +def TestPaddingPackedInOrder(): + return TestSequence( + (mojom.INT8, mojom.UINT8, mojom.INT32), + (1, 2, 3), + (0, 1, 4)) + + +def TestPaddingPackedOutOfOrder(): + return TestSequence( + (mojom.INT8, mojom.INT32, mojom.UINT8), + (1, 3, 2), + (0, 1, 4)) + + +def TestPaddingPackedOverflow(): + kinds = (mojom.INT8, mojom.INT32, mojom.INT16, mojom.INT8, mojom.INT8) + # 2 bytes should be packed together first, followed by short, then by int. + fields = (1, 4, 3, 2, 5) + offsets = (0, 1, 2, 4, 8) + return TestSequence(kinds, fields, offsets) + + +def TestAllTypes(): + struct = mojom.Struct('test') + array = mojom.Array() + return TestSequence( + (mojom.BOOL, mojom.INT8, mojom.STRING, mojom.UINT8, + mojom.INT16, mojom.DOUBLE, mojom.UINT16, + mojom.INT32, mojom.UINT32, mojom.INT64, + mojom.FLOAT, mojom.STRING, mojom.HANDLE, + mojom.UINT64, mojom.Struct('test'), mojom.Array()), + (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17), + (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80)) + + +def TestPaddingPackedOutOfOrderByOrdinal(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT8) + struct.AddField('testfield3', mojom.UINT8, 3) + struct.AddField('testfield2', mojom.INT32, 2) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(3, len(ps.packed_fields)) + + # Second byte should be packed in behind first, altering order. + errors += EXPECT_EQ('testfield1', ps.packed_fields[0].field.name) + errors += EXPECT_EQ('testfield3', ps.packed_fields[1].field.name) + errors += EXPECT_EQ('testfield2', ps.packed_fields[2].field.name) + + # Second byte should be packed with first. + errors += EXPECT_EQ(0, ps.packed_fields[0].offset) + errors += EXPECT_EQ(1, ps.packed_fields[1].offset) + errors += EXPECT_EQ(4, ps.packed_fields[2].offset) + + return errors + + +def TestBools(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('bit0', mojom.BOOL) + struct.AddField('bit1', mojom.BOOL) + struct.AddField('int', mojom.INT32) + struct.AddField('bit2', mojom.BOOL) + struct.AddField('bit3', mojom.BOOL) + struct.AddField('bit4', mojom.BOOL) + struct.AddField('bit5', mojom.BOOL) + struct.AddField('bit6', mojom.BOOL) + struct.AddField('bit7', mojom.BOOL) + struct.AddField('bit8', mojom.BOOL) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(10, len(ps.packed_fields)) + + # First 8 bits packed together. + for i in xrange(8): + pf = ps.packed_fields[i] + errors += EXPECT_EQ(0, pf.offset) + errors += EXPECT_EQ("bit%d" % i, pf.field.name) + errors += EXPECT_EQ(i, pf.bit) + + # Ninth bit goes into second byte. + errors += EXPECT_EQ("bit8", ps.packed_fields[8].field.name) + errors += EXPECT_EQ(1, ps.packed_fields[8].offset) + errors += EXPECT_EQ(0, ps.packed_fields[8].bit) + + # int comes last. + errors += EXPECT_EQ("int", ps.packed_fields[9].field.name) + errors += EXPECT_EQ(4, ps.packed_fields[9].offset) + + return errors + + +def Main(args): + errors = 0 + errors += RunTest(TestZeroFields) + errors += RunTest(TestOneField) + errors += RunTest(TestPaddingPackedInOrder) + errors += RunTest(TestPaddingPackedOutOfOrder) + errors += RunTest(TestPaddingPackedOverflow) + errors += RunTest(TestAllTypes) + errors += RunTest(TestPaddingPackedOutOfOrderByOrdinal) + errors += RunTest(TestBools) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py new file mode 100755 index 00000000000..41f11a2b71e --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# 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. + +""" Test runner for Mojom """ + +import subprocess +import sys + +def TestMojom(testname, args): + print '\nRunning unit tests for %s.' % testname + try: + args = [sys.executable, testname] + args + subprocess.check_call(args, stdout=sys.stdout) + print 'Succeeded' + return 0 + except subprocess.CalledProcessError as err: + print 'Failed with %s.' % str(err) + return 1 + + +def main(args): + errors = 0 + errors += TestMojom('data_tests.py', ['--test']) + errors += TestMojom('module_tests.py', ['--test']) + errors += TestMojom('pack_tests.py', ['--test']) + + if errors: + print '\nFailed tests.' + return min(errors, 127) # Make sure the return value doesn't "wrap". + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py new file mode 100644 index 00000000000..86ea0eac0b4 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py @@ -0,0 +1,54 @@ +# 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. + +# Based on: +# http://src.chromium.org/viewvc/blink/trunk/Source/build/scripts/template_expander.py + +import imp +import inspect +import os.path +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("jinja2") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +import jinja2 + + +def ApplyTemplate(base_dir, path_to_template, params, filters=None, **kwargs): + template_directory, template_name = os.path.split(path_to_template) + path_to_templates = os.path.join(base_dir, template_directory) + loader = jinja2.FileSystemLoader([path_to_templates]) + jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True, + **kwargs) + if filters: + jinja_env.filters.update(filters) + template = jinja_env.get_template(template_name) + return template.render(params) + + +def UseJinja(path_to_template, **kwargs): + # Get the directory of our caller's file. + base_dir = os.path.dirname(inspect.getfile(sys._getframe(1))) + def RealDecorator(generator): + def GeneratorInternal(*args, **kwargs2): + parameters = generator(*args, **kwargs2) + return ApplyTemplate(base_dir, path_to_template, parameters, **kwargs) + GeneratorInternal.func_name = generator.func_name + return GeneratorInternal + return RealDecorator diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py new file mode 100644 index 00000000000..eb394619d2b --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py @@ -0,0 +1,193 @@ +# 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. + +import sys +import traceback + +import module as mojom + +# Support for writing mojom test cases. +# RunTest(fn) will execute fn, catching any exceptions. fn should return +# the number of errors that are encountered. +# +# EXPECT_EQ(a, b) and EXPECT_TRUE(b) will print error information if the +# expectations are not true and return a non zero value. This allows test cases +# to be written like this +# +# def Foo(): +# errors = 0 +# errors += EXPECT_EQ('test', test()) +# ... +# return errors +# +# RunTest(foo) + +def FieldsAreEqual(field1, field2): + if field1 == field2: + return True + return field1.name == field2.name and \ + KindsAreEqual(field1.kind, field2.kind) and \ + field1.ordinal == field2.ordinal and \ + field1.default == field2.default + + +def KindsAreEqual(kind1, kind2): + if kind1 == kind2: + return True + if kind1.__class__ != kind2.__class__ or kind1.spec != kind2.spec: + return False + if kind1.__class__ == mojom.Kind: + return kind1.spec == kind2.spec + if kind1.__class__ == mojom.Struct: + if kind1.name != kind2.name or \ + kind1.spec != kind2.spec or \ + len(kind1.fields) != len(kind2.fields): + return False + for i in range(len(kind1.fields)): + if not FieldsAreEqual(kind1.fields[i], kind2.fields[i]): + return False + return True + if kind1.__class__ == mojom.Array: + return KindsAreEqual(kind1.kind, kind2.kind) + print 'Unknown Kind class: ', kind1.__class__.__name__ + return False + + +def ParametersAreEqual(parameter1, parameter2): + if parameter1 == parameter2: + return True + return parameter1.name == parameter2.name and \ + parameter1.ordinal == parameter2.ordinal and \ + parameter1.default == parameter2.default and \ + KindsAreEqual(parameter1.kind, parameter2.kind) + + +def MethodsAreEqual(method1, method2): + if method1 == method2: + return True + if method1.name != method2.name or \ + method1.ordinal != method2.ordinal or \ + len(method1.parameters) != len(method2.parameters): + return False + for i in range(len(method1.parameters)): + if not ParametersAreEqual(method1.parameters[i], method2.parameters[i]): + return False + return True + + +def InterfacesAreEqual(interface1, interface2): + if interface1 == interface2: + return True + if interface1.name != interface2.name or \ + len(interface1.methods) != len(interface2.methods): + return False + for i in range(len(interface1.methods)): + if not MethodsAreEqual(interface1.methods[i], interface2.methods[i]): + return False + return True + + +def ModulesAreEqual(module1, module2): + if module1 == module2: + return True + if module1.name != module2.name or \ + module1.namespace != module2.namespace or \ + len(module1.structs) != len(module2.structs) or \ + len(module1.interfaces) != len(module2.interfaces): + return False + for i in range(len(module1.structs)): + if not KindsAreEqual(module1.structs[i], module2.structs[i]): + return False + for i in range(len(module1.interfaces)): + if not InterfacesAreEqual(module1.interfaces[i], module2.interfaces[i]): + return False + return True + + +# Builds and returns a Module suitable for testing/ +def BuildTestModule(): + module = mojom.Module('test', 'testspace') + struct = module.AddStruct('teststruct') + struct.AddField('testfield1', mojom.INT32) + struct.AddField('testfield2', mojom.Array(mojom.INT32), 42) + + interface = module.AddInterface('Server') + method = interface.AddMethod('Foo', 42) + method.AddParameter('foo', mojom.INT32) + method.AddParameter('bar', mojom.Array(struct)) + + return module + + +# Tests if |module| is as built by BuildTestModule(). Returns the number of +# errors +def TestTestModule(module): + errors = 0 + + errors += EXPECT_EQ('test', module.name) + errors += EXPECT_EQ('testspace', module.namespace) + errors += EXPECT_EQ(1, len(module.structs)) + errors += EXPECT_EQ('teststruct', module.structs[0].name) + errors += EXPECT_EQ(2, len(module.structs[0].fields)) + errors += EXPECT_EQ('testfield1', module.structs[0].fields[0].name) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[0].kind) + errors += EXPECT_EQ('testfield2', module.structs[0].fields[1].name) + errors += EXPECT_EQ(mojom.Array, module.structs[0].fields[1].kind.__class__) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[1].kind.kind) + + errors += EXPECT_EQ(1, len(module.interfaces)) + errors += EXPECT_EQ('Server', module.interfaces[0].name) + errors += EXPECT_EQ(1, len(module.interfaces[0].methods)) + errors += EXPECT_EQ('Foo', module.interfaces[0].methods[0].name) + errors += EXPECT_EQ(2, len(module.interfaces[0].methods[0].parameters)) + errors += EXPECT_EQ('foo', module.interfaces[0].methods[0].parameters[0].name) + errors += EXPECT_EQ(mojom.INT32, + module.interfaces[0].methods[0].parameters[0].kind) + errors += EXPECT_EQ('bar', module.interfaces[0].methods[0].parameters[1].name) + errors += EXPECT_EQ( + mojom.Array, + module.interfaces[0].methods[0].parameters[1].kind.__class__) + errors += EXPECT_EQ( + module.structs[0], + module.interfaces[0].methods[0].parameters[1].kind.kind) + return errors + + +def PrintFailure(string): + stack = traceback.extract_stack() + frame = stack[len(stack)-3] + sys.stderr.write("ERROR at %s:%d, %s\n" % (frame[0], frame[1], string)) + print "Traceback:" + for line in traceback.format_list(stack[:len(stack)-2]): + sys.stderr.write(line) + + +def EXPECT_EQ(a, b): + if a != b: + PrintFailure("%s != %s" % (a, b)) + return 1 + return 0 + + +def EXPECT_TRUE(a): + if not a: + PrintFailure('Expecting True') + return 1 + return 0 + + +def RunTest(fn): + sys.stdout.write('Running %s...' % fn.__name__) + try: + errors = fn() + except: + traceback.print_exc(sys.stderr) + errors = 1 + if errors == 0: + sys.stdout.write('OK\n') + elif errors == 1: + sys.stdout.write('1 ERROR\n') + else: + sys.stdout.write('%d ERRORS\n' % errors) + return errors diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/parse/ast.py b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/ast.py new file mode 100644 index 00000000000..bd398c18ecb --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/ast.py @@ -0,0 +1,25 @@ +# 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. + +"""Node objects for the AST for a Mojo IDL file.""" + +# Note: For convenience of testing, you probably want to define __eq__() methods +# for all node types; it's okay to be slightly lax (e.g., not compare filename +# and lineno). + + +class BaseNode(object): + def __init__(self, filename=None, lineno=None): + self.filename = filename + self.lineno = lineno + + +class Ordinal(BaseNode): + """Represents an ordinal value labeling, e.g., a struct field.""" + def __init__(self, value, **kwargs): + BaseNode.__init__(self, **kwargs) + self.value = value + + def __eq__(self, other): + return self.value == other.value diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py new file mode 100644 index 00000000000..34419acd53c --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py @@ -0,0 +1,277 @@ +# 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 imp +import os.path +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply.lex import TOKEN + +from ..error import Error + + +# Disable lint check for exceptions deriving from Exception: +# pylint: disable=W0710 +class LexError(Error): + """Class for errors from the lexer.""" + + def __init__(self, filename, message, lineno): + Error.__init__(self, filename, message, lineno=lineno) + + +# We have methods which look like they could be functions: +# pylint: disable=R0201 +class Lexer(object): + + def __init__(self, filename): + self.filename = filename + + ######################-- PRIVATE --###################### + + ## + ## Internal auxiliary methods + ## + def _error(self, msg, token): + raise LexError(self.filename, msg, token.lineno) + + ## + ## Reserved keywords + ## + keywords = ( + 'HANDLE', + + 'IMPORT', + 'MODULE', + 'STRUCT', + 'INTERFACE', + 'ENUM', + 'CONST', + 'TRUE', + 'FALSE', + 'DEFAULT', + ) + + keyword_map = {} + for keyword in keywords: + keyword_map[keyword.lower()] = keyword + + ## + ## All the tokens recognized by the lexer + ## + tokens = keywords + ( + # Identifiers + 'NAME', + + # Constants + 'ORDINAL', + 'INT_CONST_DEC', 'INT_CONST_HEX', + 'FLOAT_CONST', + 'CHAR_CONST', + + # String literals + 'STRING_LITERAL', + + # Operators + 'MINUS', + 'PLUS', + 'AMP', + + # Assignment + 'EQUALS', + + # Request / response + 'RESPONSE', + + # Delimiters + 'LPAREN', 'RPAREN', # ( ) + 'LBRACKET', 'RBRACKET', # [ ] + 'LBRACE', 'RBRACE', # { } + 'LANGLE', 'RANGLE', # < > + 'SEMI', # ; + 'COMMA', 'DOT' # , . + ) + + ## + ## Regexes for use in tokens + ## + + # valid C identifiers (K&R2: A.2.3) + identifier = r'[a-zA-Z_][0-9a-zA-Z_]*' + + hex_prefix = '0[xX]' + hex_digits = '[0-9a-fA-F]+' + + # integer constants (K&R2: A.2.5.1) + decimal_constant = '0|([1-9][0-9]*)' + hex_constant = hex_prefix+hex_digits + # Don't allow octal constants (even invalid octal). + octal_constant_disallowed = '0[0-9]+' + + # character constants (K&R2: A.2.5.2) + # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line + # directives with Windows paths as filenames (..\..\dir\file) + # For the same reason, decimal_escape allows all digit sequences. We want to + # parse all correct code, even if it means to sometimes parse incorrect + # code. + # + simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])""" + decimal_escape = r"""(\d+)""" + hex_escape = r"""(x[0-9a-fA-F]+)""" + bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])""" + + escape_sequence = \ + r"""(\\("""+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))' + cconst_char = r"""([^'\\\n]|"""+escape_sequence+')' + char_const = "'"+cconst_char+"'" + unmatched_quote = "('"+cconst_char+"*\\n)|('"+cconst_char+"*$)" + bad_char_const = \ + r"""('"""+cconst_char+"""[^'\n]+')|('')|('"""+ \ + bad_escape+r"""[^'\n]*')""" + + # string literals (K&R2: A.2.6) + string_char = r"""([^"\\\n]|"""+escape_sequence+')' + string_literal = '"'+string_char+'*"' + bad_string_literal = '"'+string_char+'*'+bad_escape+string_char+'*"' + + # floating constants (K&R2: A.2.5.3) + exponent_part = r"""([eE][-+]?[0-9]+)""" + fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)""" + floating_constant = \ + '(((('+fractional_constant+')'+ \ + exponent_part+'?)|([0-9]+'+exponent_part+')))' + + # Ordinals + ordinal = r'@[0-9]+' + missing_ordinal_value = r'@' + # Don't allow ordinal values in octal (even invalid octal, like 09) or + # hexadecimal. + octal_or_hex_ordinal_disallowed = r'@((0[0-9]+)|('+hex_prefix+hex_digits+'))' + + ## + ## Rules for the normal state + ## + t_ignore = ' \t\r' + + # Newlines + def t_NEWLINE(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + # Operators + t_MINUS = r'-' + t_PLUS = r'\+' + t_AMP = r'&' + + # = + t_EQUALS = r'=' + + # => + t_RESPONSE = r'=>' + + # Delimiters + t_LPAREN = r'\(' + t_RPAREN = r'\)' + t_LBRACKET = r'\[' + t_RBRACKET = r'\]' + t_LBRACE = r'\{' + t_RBRACE = r'\}' + t_LANGLE = r'<' + t_RANGLE = r'>' + t_COMMA = r',' + t_DOT = r'\.' + t_SEMI = r';' + + t_STRING_LITERAL = string_literal + + # The following floating and integer constants are defined as + # functions to impose a strict order (otherwise, decimal + # is placed before the others because its regex is longer, + # and this is bad) + # + @TOKEN(floating_constant) + def t_FLOAT_CONST(self, t): + return t + + @TOKEN(hex_constant) + def t_INT_CONST_HEX(self, t): + return t + + @TOKEN(octal_constant_disallowed) + def t_OCTAL_CONSTANT_DISALLOWED(self, t): + msg = "Octal values not allowed" + self._error(msg, t) + + @TOKEN(decimal_constant) + def t_INT_CONST_DEC(self, t): + return t + + # Must come before bad_char_const, to prevent it from + # catching valid char constants as invalid + # + @TOKEN(char_const) + def t_CHAR_CONST(self, t): + return t + + @TOKEN(unmatched_quote) + def t_UNMATCHED_QUOTE(self, t): + msg = "Unmatched '" + self._error(msg, t) + + @TOKEN(bad_char_const) + def t_BAD_CHAR_CONST(self, t): + msg = "Invalid char constant %s" % t.value + self._error(msg, t) + + # unmatched string literals are caught by the preprocessor + + @TOKEN(bad_string_literal) + def t_BAD_STRING_LITERAL(self, t): + msg = "String contains invalid escape code" + self._error(msg, t) + + # Handle ordinal-related tokens in the right order: + @TOKEN(octal_or_hex_ordinal_disallowed) + def t_OCTAL_OR_HEX_ORDINAL_DISALLOWED(self, t): + msg = "Octal and hexadecimal ordinal values not allowed" + self._error(msg, t) + + @TOKEN(ordinal) + def t_ORDINAL(self, t): + return t + + @TOKEN(missing_ordinal_value) + def t_BAD_ORDINAL(self, t): + msg = "Missing ordinal value" + self._error(msg, t) + + @TOKEN(identifier) + def t_NAME(self, t): + t.type = self.keyword_map.get(t.value, "NAME") + return t + + # Ignore C and C++ style comments + def t_COMMENT(self, t): + r'(/\*(.|\n)*?\*/)|(//.*(\n[ \t]*//.*)*)' + t.lexer.lineno += t.value.count("\n") + + def t_error(self, t): + msg = "Illegal character %s" % repr(t.value[0]) + self._error(msg, t) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/parser.py new file mode 100644 index 00000000000..da441e3806c --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/parser.py @@ -0,0 +1,323 @@ +# 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. + +"""Generates a syntax tree from a Mojo IDL file.""" + +import imp +import os.path +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply import lex +from ply import yacc + +from ..error import Error +import ast +from lexer import Lexer + + +_MAX_ORDINAL_VALUE = 0xffffffff + + +def _ListFromConcat(*items): + """Generate list by concatenating inputs (note: only concatenates lists, not + tuples or other iterables).""" + itemsout = [] + for item in items: + if item is None: + continue + if type(item) is not type([]): + itemsout.append(item) + else: + itemsout.extend(item) + return itemsout + + +# Disable lint check for exceptions deriving from Exception: +# pylint: disable=W0710 +class ParseError(Error): + """Class for errors from the parser.""" + + def __init__(self, filename, message, lineno=None, snippet=None): + Error.__init__(self, filename, message, lineno=lineno, + addenda=([snippet] if snippet else None)) + + +# We have methods which look like they could be functions: +# pylint: disable=R0201 +class Parser(object): + + def __init__(self, lexer, source, filename): + self.tokens = lexer.tokens + self.source = source + self.filename = filename + + def p_root(self, p): + """root : import root + | module + | definitions""" + if len(p) > 2: + p[0] = _ListFromConcat(p[1], p[2]) + else: + # Generator expects a module. If one wasn't specified insert one with an + # empty name. + if p[1][0] != 'MODULE': + p[0] = [('MODULE', '', None, p[1])] + else: + p[0] = [p[1]] + + def p_import(self, p): + """import : IMPORT STRING_LITERAL""" + # 'eval' the literal to strip the quotes. + p[0] = ('IMPORT', eval(p[2])) + + def p_module(self, p): + """module : attribute_section MODULE identifier LBRACE definitions RBRACE""" + p[0] = ('MODULE', p[3], p[1], p[5]) + + def p_definitions(self, p): + """definitions : definition definitions + | """ + if len(p) > 1: + p[0] = _ListFromConcat(p[1], p[2]) + + def p_definition(self, p): + """definition : struct + | interface + | enum + | const""" + p[0] = p[1] + + def p_attribute_section(self, p): + """attribute_section : LBRACKET attributes RBRACKET + | """ + if len(p) > 3: + p[0] = p[2] + + def p_attributes(self, p): + """attributes : attribute + | attribute COMMA attributes + | """ + if len(p) == 2: + p[0] = _ListFromConcat(p[1]) + elif len(p) > 3: + p[0] = _ListFromConcat(p[1], p[3]) + + def p_attribute(self, p): + """attribute : NAME EQUALS evaled_literal + | NAME EQUALS NAME""" + p[0] = ('ATTRIBUTE', p[1], p[3]) + + def p_evaled_literal(self, p): + """evaled_literal : literal""" + # 'eval' the literal to strip the quotes. + p[0] = eval(p[1]) + + def p_struct(self, p): + """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI""" + p[0] = ('STRUCT', p[3], p[1], p[5]) + + def p_struct_body(self, p): + """struct_body : field struct_body + | enum struct_body + | const struct_body + | """ + if len(p) > 1: + p[0] = _ListFromConcat(p[1], p[2]) + + def p_field(self, p): + """field : typename NAME ordinal default SEMI""" + p[0] = ('FIELD', p[1], p[2], p[3], p[4]) + + def p_default(self, p): + """default : EQUALS constant + | """ + if len(p) > 2: + p[0] = p[2] + + def p_interface(self, p): + """interface : attribute_section INTERFACE NAME LBRACE interface_body \ + RBRACE SEMI""" + p[0] = ('INTERFACE', p[3], p[1], p[5]) + + def p_interface_body(self, p): + """interface_body : method interface_body + | enum interface_body + | const interface_body + | """ + if len(p) > 1: + p[0] = _ListFromConcat(p[1], p[2]) + + def p_response(self, p): + """response : RESPONSE LPAREN parameters RPAREN + | """ + if len(p) > 3: + p[0] = p[3] + + def p_method(self, p): + """method : NAME ordinal LPAREN parameters RPAREN response SEMI""" + p[0] = ('METHOD', p[1], p[4], p[2], p[6]) + + def p_parameters(self, p): + """parameters : parameter + | parameter COMMA parameters + | """ + if len(p) == 1: + p[0] = [] + elif len(p) == 2: + p[0] = _ListFromConcat(p[1]) + elif len(p) > 3: + p[0] = _ListFromConcat(p[1], p[3]) + + def p_parameter(self, p): + """parameter : typename NAME ordinal""" + p[0] = ('PARAM', p[1], p[2], p[3]) + + def p_typename(self, p): + """typename : basictypename + | array + | interfacerequest""" + p[0] = p[1] + + def p_basictypename(self, p): + """basictypename : identifier + | handletype""" + p[0] = p[1] + + def p_handletype(self, p): + """handletype : HANDLE + | HANDLE LANGLE NAME RANGLE""" + if len(p) == 2: + p[0] = p[1] + else: + if p[3] not in ('data_pipe_consumer', + 'data_pipe_producer', + 'message_pipe', + 'shared_buffer'): + # Note: We don't enable tracking of line numbers for everything, so we + # can't use |p.lineno(3)|. + raise ParseError(self.filename, "Invalid handle type %r:" % p[3], + lineno=p.lineno(1), + snippet=self._GetSnippet(p.lineno(1))) + p[0] = "handle<" + p[3] + ">" + + def p_array(self, p): + """array : typename LBRACKET RBRACKET""" + p[0] = p[1] + "[]" + + def p_interfacerequest(self, p): + """interfacerequest : identifier AMP""" + p[0] = p[1] + "&" + + def p_ordinal(self, p): + """ordinal : ORDINAL + | """ + if len(p) > 1: + value = int(p[1][1:]) + if value > _MAX_ORDINAL_VALUE: + raise ParseError(self.filename, "Ordinal value %d too large:" % value, + lineno=p.lineno(1), + snippet=self._GetSnippet(p.lineno(1))) + p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1)) + else: + p[0] = ast.Ordinal(None) + + def p_enum(self, p): + """enum : ENUM NAME LBRACE enum_fields RBRACE SEMI""" + p[0] = ('ENUM', p[2], p[4]) + + def p_enum_fields(self, p): + """enum_fields : enum_field + | enum_field COMMA enum_fields + | """ + if len(p) == 2: + p[0] = _ListFromConcat(p[1]) + elif len(p) > 3: + p[0] = _ListFromConcat(p[1], p[3]) + + def p_enum_field(self, p): + """enum_field : NAME + | NAME EQUALS constant""" + if len(p) == 2: + p[0] = ('ENUM_FIELD', p[1], None) + else: + p[0] = ('ENUM_FIELD', p[1], p[3]) + + def p_const(self, p): + """const : CONST typename NAME EQUALS constant SEMI""" + p[0] = ('CONST', p[2], p[3], p[5]) + + def p_constant(self, p): + """constant : literal + | identifier_wrapped""" + p[0] = p[1] + + def p_identifier_wrapped(self, p): + """identifier_wrapped : identifier""" + p[0] = ('IDENTIFIER', p[1]) + + def p_identifier(self, p): + """identifier : NAME + | NAME DOT identifier""" + p[0] = ''.join(p[1:]) + + def p_literal(self, p): + """literal : number + | CHAR_CONST + | TRUE + | FALSE + | DEFAULT + | STRING_LITERAL""" + p[0] = p[1] + + def p_number(self, p): + """number : digits + | PLUS digits + | MINUS digits""" + p[0] = ''.join(p[1:]) + + def p_digits(self, p): + """digits : INT_CONST_DEC + | INT_CONST_HEX + | FLOAT_CONST""" + p[0] = p[1] + + def p_error(self, e): + if e is None: + # Unexpected EOF. + # TODO(vtl): Can we figure out what's missing? + raise ParseError(self.filename, "Unexpected end of file") + + raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno, + snippet=self._GetSnippet(e.lineno)) + + def _GetSnippet(self, lineno): + return self.source.split('\n')[lineno - 1] + + +def Parse(source, filename): + lexer = Lexer(filename) + parser = Parser(lexer, source, filename) + + lex.lex(object=lexer) + yacc.yacc(module=parser, debug=0, write_tables=0) + + tree = yacc.parse(source) + return tree diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom/parse/translate.py b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/translate.py new file mode 100644 index 00000000000..8407a088c6b --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom/parse/translate.py @@ -0,0 +1,144 @@ +# 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. + +"""Translates parse tree to Mojom IR.""" + + +import ast + + +def _MapTree(func, tree, name): + if not tree: + return [] + return [func(subtree) for subtree in tree if subtree[0] == name] + +def _MapKind(kind): + map_to_kind = { 'bool': 'b', + 'int8': 'i8', + 'int16': 'i16', + 'int32': 'i32', + 'int64': 'i64', + 'uint8': 'u8', + 'uint16': 'u16', + 'uint32': 'u32', + 'uint64': 'u64', + 'float': 'f', + 'double': 'd', + 'string': 's', + 'handle': 'h', + 'handle<data_pipe_consumer>': 'h:d:c', + 'handle<data_pipe_producer>': 'h:d:p', + 'handle<message_pipe>': 'h:m', + 'handle<shared_buffer>': 'h:s'} + if kind.endswith('[]'): + return 'a:' + _MapKind(kind[0:len(kind)-2]) + if kind.endswith('&'): + return 'r:' + _MapKind(kind[0:len(kind)-1]) + if kind in map_to_kind: + return map_to_kind[kind] + return 'x:' + kind + +def _MapAttributes(attributes): + if not attributes: + return {} + return dict([(attribute[1], attribute[2]) + for attribute in attributes if attribute[0] == 'ATTRIBUTE']) + +def _GetAttribute(attributes, name): + out = None + if attributes: + for attribute in attributes: + if attribute[0] == 'ATTRIBUTE' and attribute[1] == name: + out = attribute[2] + return out + +def _MapField(tree): + assert type(tree[3]) is ast.Ordinal + return {'name': tree[2], + 'kind': _MapKind(tree[1]), + 'ordinal': tree[3].value, + 'default': tree[4]} + +def _MapParameter(tree): + assert type(tree[3]) is ast.Ordinal + return {'name': tree[2], + 'kind': _MapKind(tree[1]), + 'ordinal': tree[3].value} + +def _MapMethod(tree): + assert type(tree[3]) is ast.Ordinal + method = {'name': tree[1], + 'parameters': _MapTree(_MapParameter, tree[2], 'PARAM'), + 'ordinal': tree[3].value} + if tree[4] != None: + method['response_parameters'] = _MapTree(_MapParameter, tree[4], 'PARAM') + return method + +def _MapEnumField(tree): + return {'name': tree[1], + 'value': tree[2]} + +def _MapStruct(tree): + struct = {} + struct['name'] = tree[1] + struct['attributes'] = _MapAttributes(tree[2]) + struct['fields'] = _MapTree(_MapField, tree[3], 'FIELD') + struct['enums'] = _MapTree(_MapEnum, tree[3], 'ENUM') + struct['constants'] = _MapTree(_MapConstant, tree[3], 'CONST') + return struct + +def _MapInterface(tree): + interface = {} + interface['name'] = tree[1] + interface['client'] = _GetAttribute(tree[2], 'Client') + interface['methods'] = _MapTree(_MapMethod, tree[3], 'METHOD') + interface['enums'] = _MapTree(_MapEnum, tree[3], 'ENUM') + interface['constants'] = _MapTree(_MapConstant, tree[3], 'CONST') + return interface + +def _MapEnum(tree): + enum = {} + enum['name'] = tree[1] + enum['fields'] = _MapTree(_MapEnumField, tree[2], 'ENUM_FIELD') + return enum + +def _MapConstant(tree): + constant = {} + constant['name'] = tree[2] + constant['kind'] = _MapKind(tree[1]) + constant['value'] = tree[3] + return constant + +def _MapModule(tree, name): + mojom = {} + mojom['name'] = name + mojom['namespace'] = tree[1] + mojom['attributes'] = _MapAttributes(tree[2]) + mojom['structs'] = _MapTree(_MapStruct, tree[3], 'STRUCT') + mojom['interfaces'] = _MapTree(_MapInterface, tree[3], 'INTERFACE') + mojom['enums'] = _MapTree(_MapEnum, tree[3], 'ENUM') + mojom['constants'] = _MapTree(_MapConstant, tree[3], 'CONST') + return mojom + +def _MapImport(tree): + import_item = {} + import_item['filename'] = tree[1] + return import_item + + +class _MojomBuilder(object): + def __init__(self): + self.mojom = {} + + def Build(self, tree, name): + modules = [_MapModule(item, name) for item in tree if item[0] == 'MODULE'] + if len(modules) != 1: + raise Exception('A mojom file must contain exactly 1 module.') + self.mojom = modules[0] + self.mojom['imports'] = _MapTree(_MapImport, tree, 'IMPORT') + return self.mojom + + +def Translate(tree, name): + return _MojomBuilder().Build(tree, name) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py new file mode 100644 index 00000000000..28c936d20b8 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py @@ -0,0 +1,187 @@ +# 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 imp +import os.path +import sys +import unittest + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply import lex + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.lexer + + +# This (monkey-patching LexToken to make comparison value-based) is evil, but +# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing +# for object identity.) +def _LexTokenEq(self, other): + return self.type == other.type and self.value == other.value and \ + self.lineno == other.lineno and self.lexpos == other.lexpos +setattr(lex.LexToken, '__eq__', _LexTokenEq) + + +def _MakeLexToken(token_type, value, lineno=1, lexpos=0): + """Makes a LexToken with the given parameters. (Note that lineno is 1-based, + but lexpos is 0-based.)""" + rv = lex.LexToken() + rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos + return rv + + +def _MakeLexTokenForKeyword(keyword, **kwargs): + """Makes a LexToken for the given keyword.""" + return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs) + + +class LexerTest(unittest.TestCase): + """Tests |mojom.parse.lexer.Lexer|.""" + + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + # Clone all lexer instances from this one, since making a lexer is slow. + self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer("my_file.mojom")) + + def testValidKeywords(self): + """Tests valid keywords.""" + self.assertEquals(self._SingleTokenForInput("handle"), + _MakeLexTokenForKeyword("handle")) + self.assertEquals(self._SingleTokenForInput("import"), + _MakeLexTokenForKeyword("import")) + self.assertEquals(self._SingleTokenForInput("module"), + _MakeLexTokenForKeyword("module")) + self.assertEquals(self._SingleTokenForInput("struct"), + _MakeLexTokenForKeyword("struct")) + self.assertEquals(self._SingleTokenForInput("interface"), + _MakeLexTokenForKeyword("interface")) + self.assertEquals(self._SingleTokenForInput("enum"), + _MakeLexTokenForKeyword("enum")) + self.assertEquals(self._SingleTokenForInput("const"), + _MakeLexTokenForKeyword("const")) + self.assertEquals(self._SingleTokenForInput("true"), + _MakeLexTokenForKeyword("true")) + self.assertEquals(self._SingleTokenForInput("false"), + _MakeLexTokenForKeyword("false")) + self.assertEquals(self._SingleTokenForInput("default"), + _MakeLexTokenForKeyword("default")) + + def testValidIdentifiers(self): + """Tests identifiers.""" + self.assertEquals(self._SingleTokenForInput("abcd"), + _MakeLexToken("NAME", "abcd")) + self.assertEquals(self._SingleTokenForInput("AbC_d012_"), + _MakeLexToken("NAME", "AbC_d012_")) + self.assertEquals(self._SingleTokenForInput("_0123"), + _MakeLexToken("NAME", "_0123")) + + def testInvalidIdentifiers(self): + with self.assertRaisesRegexp( + mojom.parse.lexer.LexError, + r"^my_file\.mojom:1: Error: Illegal character '\$'$"): + self._TokensForInput("$abc") + with self.assertRaisesRegexp( + mojom.parse.lexer.LexError, + r"^my_file\.mojom:1: Error: Illegal character '\$'$"): + self._TokensForInput("a$bc") + + def testDecimalIntegerConstants(self): + self.assertEquals(self._SingleTokenForInput("0"), + _MakeLexToken("INT_CONST_DEC", "0")) + self.assertEquals(self._SingleTokenForInput("1"), + _MakeLexToken("INT_CONST_DEC", "1")) + self.assertEquals(self._SingleTokenForInput("123"), + _MakeLexToken("INT_CONST_DEC", "123")) + self.assertEquals(self._SingleTokenForInput("10"), + _MakeLexToken("INT_CONST_DEC", "10")) + + def testValidTokens(self): + """Tests valid tokens (which aren't tested elsewhere).""" + # Keywords tested in |testValidKeywords|. + # NAME tested in |testValidIdentifiers|. + self.assertEquals(self._SingleTokenForInput("@123"), + _MakeLexToken("ORDINAL", "@123")) + self.assertEquals(self._SingleTokenForInput("456"), + _MakeLexToken("INT_CONST_DEC", "456")) + self.assertEquals(self._SingleTokenForInput("0x01aB2eF3"), + _MakeLexToken("INT_CONST_HEX", "0x01aB2eF3")) + self.assertEquals(self._SingleTokenForInput("123.456"), + _MakeLexToken("FLOAT_CONST", "123.456")) + self.assertEquals(self._SingleTokenForInput("'x'"), + _MakeLexToken("CHAR_CONST", "'x'")) + self.assertEquals(self._SingleTokenForInput("\"hello\""), + _MakeLexToken("STRING_LITERAL", "\"hello\"")) + self.assertEquals(self._SingleTokenForInput("+"), + _MakeLexToken("PLUS", "+")) + self.assertEquals(self._SingleTokenForInput("-"), + _MakeLexToken("MINUS", "-")) + self.assertEquals(self._SingleTokenForInput("&"), + _MakeLexToken("AMP", "&")) + self.assertEquals(self._SingleTokenForInput("="), + _MakeLexToken("EQUALS", "=")) + self.assertEquals(self._SingleTokenForInput("=>"), + _MakeLexToken("RESPONSE", "=>")) + self.assertEquals(self._SingleTokenForInput("("), + _MakeLexToken("LPAREN", "(")) + self.assertEquals(self._SingleTokenForInput(")"), + _MakeLexToken("RPAREN", ")")) + self.assertEquals(self._SingleTokenForInput("["), + _MakeLexToken("LBRACKET", "[")) + self.assertEquals(self._SingleTokenForInput("]"), + _MakeLexToken("RBRACKET", "]")) + self.assertEquals(self._SingleTokenForInput("{"), + _MakeLexToken("LBRACE", "{")) + self.assertEquals(self._SingleTokenForInput("}"), + _MakeLexToken("RBRACE", "}")) + self.assertEquals(self._SingleTokenForInput("<"), + _MakeLexToken("LANGLE", "<")) + self.assertEquals(self._SingleTokenForInput(">"), + _MakeLexToken("RANGLE", ">")) + self.assertEquals(self._SingleTokenForInput(";"), + _MakeLexToken("SEMI", ";")) + self.assertEquals(self._SingleTokenForInput(","), + _MakeLexToken("COMMA", ",")) + self.assertEquals(self._SingleTokenForInput("."), + _MakeLexToken("DOT", ".")) + + def _TokensForInput(self, input_string): + """Gets a list of tokens for the given input string.""" + lexer = self._zygote_lexer.clone() + lexer.input(input_string) + rv = [] + while True: + tok = lexer.token() + if not tok: + return rv + rv.append(tok) + + def _SingleTokenForInput(self, input_string): + """Gets the single token for the given input string. (Raises an exception if + the input string does not result in exactly one token.)""" + toks = self._TokensForInput(input_string) + assert len(toks) == 1 + return toks[0] + + +if __name__ == "__main__": + unittest.main() diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py new file mode 100644 index 00000000000..024b433410b --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py @@ -0,0 +1,493 @@ +# 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 imp +import os.path +import sys +import unittest + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.ast as ast +import mojom.parse.lexer as lexer +import mojom.parse.parser as parser + + +class ParserTest(unittest.TestCase): + """Tests |parser.Parse()|.""" + + def testTrivialValidSource(self): + """Tests a trivial, but valid, .mojom source.""" + source = """\ +// This is a comment. + +module my_module { +} +""" + self.assertEquals(parser.Parse(source, "my_file.mojom"), + [("MODULE", "my_module", None, None)]) + + def testSourceWithCrLfs(self): + """Tests a .mojom source with CR-LFs instead of LFs.""" + source = "// This is a comment.\r\n\r\nmodule my_module {\r\n}\r\n" + self.assertEquals(parser.Parse(source, "my_file.mojom"), + [("MODULE", "my_module", None, None)]) + + def testUnexpectedEOF(self): + """Tests a "truncated" .mojom source.""" + source = """\ +// This is a comment. + +module my_module { +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom: Error: Unexpected end of file$"): + parser.Parse(source, "my_file.mojom") + + def testCommentLineNumbers(self): + """Tests that line numbers are correctly tracked when comments are + present.""" + source1 = """\ +// Isolated C++-style comments. + +// Foo. +asdf1 +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\nasdf1$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ +// Consecutive C++-style comments. +// Foo. + // Bar. + +struct Yada { // Baz. +// Quux. + int32 x; +}; + +asdf2 +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\nasdf2$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ +/* Single-line C-style comments. */ +/* Foobar. */ + +/* Baz. */ +asdf3 +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\nasdf3$"): + parser.Parse(source3, "my_file.mojom") + + source4 = """\ +/* Multi-line C-style comments. +*/ +/* +Foo. +Bar. +*/ + +/* Baz + Quux. */ +asdf4 +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\nasdf4$"): + parser.Parse(source4, "my_file.mojom") + + + def testSimpleStruct(self): + """Tests a simple .mojom source that just defines a struct.""" + source = """\ +module my_module { + +struct MyStruct { + int32 a; + double b; +}; + +} // module my_module +""" + expected = \ +[('MODULE', + 'my_module', + None, + [('STRUCT', + 'MyStruct', + None, + [('FIELD', 'int32', 'a', ast.Ordinal(None), None), + ('FIELD', 'double', 'b', ast.Ordinal(None), None)])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testSimpleStructWithoutModule(self): + """Tests a simple struct without an enclosing module.""" + source = """\ +struct MyStruct { + int32 a; + double b; +}; +""" + expected = \ +[('MODULE', + '', + None, + [('STRUCT', + 'MyStruct', + None, + [('FIELD', 'int32', 'a', ast.Ordinal(None), None), + ('FIELD', 'double', 'b', ast.Ordinal(None), None)])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testMissingModuleName(self): + """Tests an (invalid) .mojom with a missing module name.""" + source1 = """\ +// Missing module name. +module { +struct MyStruct { + int32 a; +}; +} +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '{':\nmodule {$"): + parser.Parse(source1, "my_file.mojom") + + # Another similar case, but make sure that line-number tracking/reporting + # is correct. + source2 = """\ +module +// This line intentionally left unblank. + +{ +} +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected '{':\n{$"): + parser.Parse(source2, "my_file.mojom") + + def testEnumInitializers(self): + """Tests an enum with simple initialized values.""" + source = """\ +module my_module { + +enum MyEnum { + MY_ENUM_NEG1 = -1, + MY_ENUM_ZERO = 0, + MY_ENUM_1 = +1, + MY_ENUM_2, +}; + +} // my_module +""" + expected = \ +[('MODULE', + 'my_module', + None, + [('ENUM', + 'MyEnum', + [('ENUM_FIELD', 'MY_ENUM_NEG1', '-1'), + ('ENUM_FIELD', 'MY_ENUM_ZERO', '0'), + ('ENUM_FIELD', 'MY_ENUM_1', '+1'), + ('ENUM_FIELD', 'MY_ENUM_2', None)])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testConst(self): + """Tests some constants and struct memebers initialized with them.""" + source = """\ +module my_module { + +struct MyStruct { + const int8 kNumber = -1; + int8 number@0 = kNumber; +}; + +} // my_module +""" + expected = \ +[('MODULE', + 'my_module', + None, + [('STRUCT', + 'MyStruct', None, + [('CONST', 'int8', 'kNumber', '-1'), + ('FIELD', 'int8', 'number', + ast.Ordinal(0), ('IDENTIFIER', 'kNumber'))])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testNoConditionals(self): + """Tests that ?: is not allowed.""" + source = """\ +module my_module { + +enum MyEnum { + MY_ENUM_1 = 1 ? 2 : 3 +}; + +} // my_module +""" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: Illegal character '\?'$"): + parser.Parse(source, "my_file.mojom") + + def testSimpleOrdinals(self): + """Tests that (valid) ordinal values are scanned correctly.""" + source = """\ +module my_module { + +// This isn't actually valid .mojom, but the problem (missing ordinals) should +// be handled at a different level. +struct MyStruct { + int32 a0@0; + int32 a1@1; + int32 a2@2; + int32 a9@9; + int32 a10 @10; + int32 a11 @11; + int32 a29 @29; + int32 a1234567890 @1234567890; +}; + +} // module my_module +""" + expected = \ +[('MODULE', + 'my_module', + None, + [('STRUCT', + 'MyStruct', + None, + [('FIELD', 'int32', 'a0', ast.Ordinal(0), None), + ('FIELD', 'int32', 'a1', ast.Ordinal(1), None), + ('FIELD', 'int32', 'a2', ast.Ordinal(2), None), + ('FIELD', 'int32', 'a9', ast.Ordinal(9), None), + ('FIELD', 'int32', 'a10', ast.Ordinal(10), None), + ('FIELD', 'int32', 'a11', ast.Ordinal(11), None), + ('FIELD', 'int32', 'a29', ast.Ordinal(29), None), + ('FIELD', 'int32', 'a1234567890', ast.Ordinal(1234567890), None)])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidOrdinals(self): + """Tests that (lexically) invalid ordinals are correctly detected.""" + source1 = """\ +module my_module { + +struct MyStruct { + int32 a_missing@; +}; + +} // module my_module +""" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: Missing ordinal value$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ +module my_module { + +struct MyStruct { + int32 a_octal@01; +}; + +} // module my_module +""" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ +module my_module { struct MyStruct { int32 a_invalid_octal@08; }; } +""" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source3, "my_file.mojom") + + source4 = """\ +module my_module { struct MyStruct { int32 a_hex@0x1aB9; }; } +""" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source4, "my_file.mojom") + + source5 = """\ +module my_module { struct MyStruct { int32 a_hex@0X0; }; } +""" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source5, "my_file.mojom") + + source6 = """\ +struct MyStruct { + int32 a_too_big@999999999999; +}; +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: " + r"Ordinal value 999999999999 too large:\n" + r" int32 a_too_big@999999999999;$"): + parser.Parse(source6, "my_file.mojom") + + def testNestedNamespace(self): + """Tests that "nested" namespaces work.""" + source = """\ +module my.mod { + +struct MyStruct { + int32 a; +}; + +} // module my.mod +""" + expected = \ +[('MODULE', + 'my.mod', + None, + [('STRUCT', + 'MyStruct', + None, + [('FIELD', 'int32', 'a', ast.Ordinal(None), None)])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidHandleTypes(self): + """Tests (valid) handle types.""" + source = """\ +struct MyStruct { + handle a; + handle<data_pipe_consumer> b; + handle <data_pipe_producer> c; + handle < message_pipe > d; + handle + < shared_buffer + > e; +}; +""" + expected = \ +[('MODULE', + '', + None, + [('STRUCT', + 'MyStruct', + None, + [('FIELD', 'handle', 'a', ast.Ordinal(None), None), + ('FIELD', 'handle<data_pipe_consumer>', 'b', ast.Ordinal(None), None), + ('FIELD', 'handle<data_pipe_producer>', 'c', ast.Ordinal(None), None), + ('FIELD', 'handle<message_pipe>', 'd', ast.Ordinal(None), None), + ('FIELD', 'handle<shared_buffer>', 'e', ast.Ordinal(None), None)])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidHandleType(self): + """Tests an invalid (unknown) handle type.""" + source = """\ +struct MyStruct { + handle<wtf_is_this> foo; +}; +""" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: " + r"Invalid handle type 'wtf_is_this':\n" + r" handle<wtf_is_this> foo;$"): + parser.Parse(source, "my_file.mojom") + + def testValidDefaultValues(self): + """Tests default values that are valid (to the parser).""" + source = """\ +struct MyStruct { + int16 a0 = 0; + uint16 a1 = 0x0; + uint16 a2 = 0x00; + uint16 a3 = 0x01; + uint16 a4 = 0xcd; + int32 a5 = 12345; + int64 a6 = -12345; + int64 a7 = +12345; + uint32 a8 = 0x12cd3; + uint32 a9 = -0x12cD3; + uint32 a10 = +0x12CD3; + bool a11 = true; + bool a12 = false; + float a13 = 1.2345; + float a14 = -1.2345; + float a15 = +1.2345; + float a16 = 123.; + float a17 = .123; + double a18 = 1.23E10; + double a19 = 1.E-10; + double a20 = .5E+10; + double a21 = -1.23E10; + double a22 = +.123E10; +}; +""" + expected = \ +[('MODULE', + '', + None, + [('STRUCT', + 'MyStruct', + None, + [('FIELD', 'int16', 'a0', ast.Ordinal(None), '0'), + ('FIELD', 'uint16', 'a1', ast.Ordinal(None), '0x0'), + ('FIELD', 'uint16', 'a2', ast.Ordinal(None), '0x00'), + ('FIELD', 'uint16', 'a3', ast.Ordinal(None), '0x01'), + ('FIELD', 'uint16', 'a4', ast.Ordinal(None), '0xcd'), + ('FIELD', 'int32', 'a5' , ast.Ordinal(None), '12345'), + ('FIELD', 'int64', 'a6', ast.Ordinal(None), '-12345'), + ('FIELD', 'int64', 'a7', ast.Ordinal(None), '+12345'), + ('FIELD', 'uint32', 'a8', ast.Ordinal(None), '0x12cd3'), + ('FIELD', 'uint32', 'a9', ast.Ordinal(None), '-0x12cD3'), + ('FIELD', 'uint32', 'a10', ast.Ordinal(None), '+0x12CD3'), + ('FIELD', 'bool', 'a11', ast.Ordinal(None), 'true'), + ('FIELD', 'bool', 'a12', ast.Ordinal(None), 'false'), + ('FIELD', 'float', 'a13', ast.Ordinal(None), '1.2345'), + ('FIELD', 'float', 'a14', ast.Ordinal(None), '-1.2345'), + ('FIELD', 'float', 'a15', ast.Ordinal(None), '+1.2345'), + ('FIELD', 'float', 'a16', ast.Ordinal(None), '123.'), + ('FIELD', 'float', 'a17', ast.Ordinal(None), '.123'), + ('FIELD', 'double', 'a18', ast.Ordinal(None), '1.23E10'), + ('FIELD', 'double', 'a19', ast.Ordinal(None), '1.E-10'), + ('FIELD', 'double', 'a20', ast.Ordinal(None), '.5E+10'), + ('FIELD', 'double', 'a21', ast.Ordinal(None), '-1.23E10'), + ('FIELD', 'double', 'a22', ast.Ordinal(None), '+.123E10')])])] + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py new file mode 100755 index 00000000000..edca7e11ff1 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# 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. + +"""Simple testing utility to just run the mojom parser.""" + + +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.path.pardir, os.path.pardir)) + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +from mojom.parse.parser import Parse, ParseError + + +def main(argv): + if len(argv) < 2: + print "usage: %s filename" % argv[0] + return 0 + + for filename in argv[1:]: + with open(filename) as f: + print "%s:" % filename + try: + print Parse(f.read(), filename) + except ParseError, e: + print e + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py new file mode 100755 index 00000000000..833af6292a7 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# 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. + +"""Simple testing utility to just run the mojom translate stage.""" + + +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.path.pardir, os.path.pardir)) + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +from mojom.parse.parser import Parse +from mojom.parse.translate import Translate + + +def main(argv): + if len(argv) < 2: + print "usage: %s filename" % sys.argv[0] + return 1 + + for filename in argv[1:]: + with open(filename) as f: + print "%s:" % filename + print Translate(Parse(f.read(), filename), + os.path.splitext(os.path.basename(filename))[0]) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py new file mode 100644 index 00000000000..00524a2413a --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py @@ -0,0 +1,32 @@ +# 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. + +from fnmatch import filter +from os import walk +from os.path import join +import sys + + +def FindFiles(top, pattern, **kwargs): + """Finds files under |top| matching the glob pattern |pattern|, returning a + list of paths.""" + matches = [] + for dirpath, _, filenames in walk(top, **kwargs): + for filename in filter(filenames, pattern): + matches.append(join(dirpath, filename)) + return matches + + +def main(argv): + if len(argv) != 3: + print "usage: %s path pattern" % argv[0] + return 1 + + for filename in FindFiles(argv[1], argv[2]): + print filename + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py new file mode 100644 index 00000000000..20ef4619699 --- /dev/null +++ b/chromium/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py @@ -0,0 +1,47 @@ +# 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 os.path +from subprocess import check_call +import sys + + +def RunBindingsGenerator(out_dir, root_dir, mojom_file, extra_flags=None): + out_dir = os.path.abspath(out_dir) + root_dir = os.path.abspath(root_dir) + mojom_file = os.path.abspath(mojom_file) + + # The mojom file should be under the root directory somewhere. + assert mojom_file.startswith(root_dir) + mojom_reldir = os.path.dirname(os.path.relpath(mojom_file, root_dir)) + + # TODO(vtl): Abstract out the "main" functions, so that we can just import + # the bindings generator (which would be more portable and easier to use in + # tests). + this_dir = os.path.dirname(os.path.abspath(__file__)) + # We're in src/mojo/public/tools/bindings/pylib/mojom_tests/support; + # mojom_bindings_generator.py is in .../bindings. + bindings_generator = os.path.join(this_dir, os.pardir, os.pardir, os.pardir, + "mojom_bindings_generator.py") + + args = ["python", bindings_generator, + "-o", os.path.join(out_dir, mojom_reldir)] + if extra_flags: + args.extend(extra_flags) + args.append(mojom_file) + + check_call(args) + + +def main(argv): + if len(argv) < 4: + print "usage: %s out_dir root_dir mojom_file [extra_flags]" % argv[0] + return 1 + + RunBindingsGenerator(argv[1], argv[2], argv[3], extra_flags=argv[4:]) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/mojo/service_manager/BUILD.gn b/chromium/mojo/service_manager/BUILD.gn new file mode 100644 index 00000000000..584b4f50f97 --- /dev/null +++ b/chromium/mojo/service_manager/BUILD.gn @@ -0,0 +1,33 @@ +# 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. + +# GYP version: mojo.gyp:mojo_service_manager +component("service_manager") { + output_name = "mojo_service_manager" + sources = [ + "background_service_loader.cc", + "background_service_loader.h", + "service_loader.h", + "service_manager.cc", + "service_manager.h", + "service_manager_export.h", + ] + + defines = [ "MOJO_SERVICE_MANAGER_IMPLEMENTATION" ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + "//net", + "//url", + "//mojo/common", + "//mojo/environment:chromium", + "//mojo/public/interfaces/service_provider:service_provider", + "//mojo/system", + ] + + forward_dependent_configs_from = [ + "//mojo/public/interfaces/service_provider:service_provider", + ] +} diff --git a/chromium/mojo/service_manager/background_service_loader.cc b/chromium/mojo/service_manager/background_service_loader.cc new file mode 100644 index 00000000000..96a2c430a89 --- /dev/null +++ b/chromium/mojo/service_manager/background_service_loader.cc @@ -0,0 +1,104 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/service_manager/background_service_loader.h" + +#include "base/bind.h" +#include "mojo/service_manager/service_manager.h" + +namespace mojo { + +class BackgroundServiceLoader::BackgroundLoader { + public: + explicit BackgroundLoader(ServiceLoader* loader) : loader_(loader) {} + ~BackgroundLoader() {} + + void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_provider_handle) { + loader_->LoadService(manager, url, service_provider_handle.Pass()); + } + + void OnServiceError(ServiceManager* manager, const GURL& url) { + loader_->OnServiceError(manager, url); + } + + private: + base::MessageLoop::Type message_loop_type_; + ServiceLoader* loader_; // Owned by BackgroundServiceLoader + + DISALLOW_COPY_AND_ASSIGN(BackgroundLoader); +}; + +BackgroundServiceLoader::BackgroundServiceLoader( + scoped_ptr<ServiceLoader> real_loader, + const char* thread_name, + base::MessageLoop::Type message_loop_type) + : loader_(real_loader.Pass()), + thread_(thread_name), + message_loop_type_(message_loop_type), + background_loader_(NULL) { +} + +BackgroundServiceLoader::~BackgroundServiceLoader() { + if (thread_.IsRunning()) { + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundServiceLoader::ShutdownOnBackgroundThread, + base::Unretained(this))); + } + thread_.Stop(); +} + +void BackgroundServiceLoader::LoadService( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) { + const int kDefaultStackSize = 0; + if (!thread_.IsRunning()) + thread_.StartWithOptions( + base::Thread::Options(message_loop_type_, kDefaultStackSize)); + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundServiceLoader::LoadServiceOnBackgroundThread, + base::Unretained(this), manager, url, + base::Owned( + new ScopedMessagePipeHandle(service_handle.Pass())))); +} + +void BackgroundServiceLoader::OnServiceError(ServiceManager* manager, + const GURL& url) { + if (!thread_.IsRunning()) + thread_.Start(); + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&BackgroundServiceLoader::OnServiceErrorOnBackgroundThread, + base::Unretained(this), manager, url)); +} + +void BackgroundServiceLoader::LoadServiceOnBackgroundThread( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle* service_provider_handle) { + if (!background_loader_) + background_loader_ = new BackgroundLoader(loader_.get()); + background_loader_->LoadService( + manager, url, service_provider_handle->Pass()); +} + +void BackgroundServiceLoader::OnServiceErrorOnBackgroundThread( + ServiceManager* manager, + const GURL& url) { + if (!background_loader_) + background_loader_ = new BackgroundLoader(loader_.get()); + background_loader_->OnServiceError(manager, url); +} + +void BackgroundServiceLoader::ShutdownOnBackgroundThread() { + delete background_loader_; + // Destroy |loader_| on the thread it's actually used on. + loader_.reset(); +} + +} // namespace mojo diff --git a/chromium/mojo/service_manager/background_service_loader.h b/chromium/mojo/service_manager/background_service_loader.h new file mode 100644 index 00000000000..59dbc0f86db --- /dev/null +++ b/chromium/mojo/service_manager/background_service_loader.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICE_MANAGER_BACKGROUND_SERVICE_LOADER_H_ +#define MOJO_SERVICE_MANAGER_BACKGROUND_SERVICE_LOADER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "mojo/service_manager/service_loader.h" + +namespace mojo { + +class ServiceManager; + +// ServiceLoader implementation that creates a background thread and issues load +// requests there. +class MOJO_SERVICE_MANAGER_EXPORT BackgroundServiceLoader + : public ServiceLoader { + public: + BackgroundServiceLoader(scoped_ptr<ServiceLoader> real_loader, + const char* thread_name, + base::MessageLoop::Type message_loop_type); + virtual ~BackgroundServiceLoader(); + + // ServiceLoader overrides: + virtual void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) OVERRIDE; + virtual void OnServiceError(ServiceManager* manager, + const GURL& url) OVERRIDE; + + private: + class BackgroundLoader; + + // These functions are exected on the background thread. They call through + // to |background_loader_| to do the actual loading. + // TODO: having this code take a |manager| is fragile (as ServiceManager isn't + // thread safe). + void LoadServiceOnBackgroundThread( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle* service_provider_handle); + void OnServiceErrorOnBackgroundThread(ServiceManager* manager, + const GURL& url); + void ShutdownOnBackgroundThread(); + + scoped_ptr<ServiceLoader> loader_; + base::Thread thread_; + base::MessageLoop::Type message_loop_type_; + + // Lives on |thread_|. Trivial interface that calls through to |loader_|. + BackgroundLoader* background_loader_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundServiceLoader); +}; + +} // namespace mojo + +#endif // MOJO_SERVICE_MANAGER_BACKGROUND_SERVICE_LOADER_H_ diff --git a/chromium/mojo/service_manager/service_loader.h b/chromium/mojo/service_manager/service_loader.h new file mode 100644 index 00000000000..32c15cccd8d --- /dev/null +++ b/chromium/mojo/service_manager/service_loader.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICE_MANAGER_SERVICE_LOADER_H_ +#define MOJO_SERVICE_MANAGER_SERVICE_LOADER_H_ + +#include "mojo/public/cpp/system/core.h" +#include "mojo/service_manager/service_manager_export.h" +#include "url/gurl.h" + +namespace mojo { + +class ServiceManager; + +// Interface to allowing default loading behavior to be overridden for a +// specific url. +class MOJO_SERVICE_MANAGER_EXPORT ServiceLoader { + public: + virtual ~ServiceLoader() {} + virtual void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) = 0; + virtual void OnServiceError(ServiceManager* manager, const GURL& url) = 0; + + protected: + ServiceLoader() {} +}; + +} // namespace mojo + +#endif // MOJO_SERVICE_MANAGER_SERVICE_LOADER_H_ diff --git a/chromium/mojo/service_manager/service_manager.cc b/chromium/mojo/service_manager/service_manager.cc new file mode 100644 index 00000000000..060ca4ff10f --- /dev/null +++ b/chromium/mojo/service_manager/service_manager.cc @@ -0,0 +1,206 @@ +// 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 <stdio.h> + +#include "mojo/service_manager/service_manager.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "mojo/service_manager/service_loader.h" + +namespace mojo { + +namespace { +// Used by TestAPI. +bool has_created_instance = false; +} + +class ServiceManager::ServiceFactory : public InterfaceImpl<ServiceProvider> { + public: + ServiceFactory(ServiceManager* manager, const GURL& url) + : manager_(manager), + url_(url) { + } + + virtual ~ServiceFactory() { + } + + void ConnectToClient(const std::string& service_name, + ScopedMessagePipeHandle handle, + const GURL& requestor_url) { + if (handle.is_valid()) { + client()->ConnectToService( + url_.spec(), service_name, handle.Pass(), requestor_url.spec()); + } + } + + // ServiceProvider implementation: + virtual void ConnectToService(const String& service_url, + const String& service_name, + ScopedMessagePipeHandle client_pipe, + const String& requestor_url) OVERRIDE { + // Ignore provided requestor_url and use url from connection. + manager_->ConnectToService( + GURL(service_url), service_name, client_pipe.Pass(), url_); + } + + const GURL& url() const { return url_; } + + private: + virtual void OnConnectionError() OVERRIDE { + manager_->OnServiceFactoryError(this); + } + + ServiceManager* const manager_; + const GURL url_; + + DISALLOW_COPY_AND_ASSIGN(ServiceFactory); +}; + +class ServiceManager::TestAPI::TestServiceProviderConnection + : public InterfaceImpl<ServiceProvider> { + public: + explicit TestServiceProviderConnection(ServiceManager* manager) + : manager_(manager) {} + virtual ~TestServiceProviderConnection() {} + + virtual void OnConnectionError() OVERRIDE { + // TODO(darin): How should we handle this error? + } + + // ServiceProvider: + virtual void ConnectToService(const String& service_url, + const String& service_name, + ScopedMessagePipeHandle client_pipe, + const String& requestor_url) OVERRIDE { + manager_->ConnectToService(GURL(service_url), + service_name, + client_pipe.Pass(), + GURL(requestor_url)); + } + + private: + ServiceManager* manager_; + + DISALLOW_COPY_AND_ASSIGN(TestServiceProviderConnection); +}; + +// static +ServiceManager::TestAPI::TestAPI(ServiceManager* manager) : manager_(manager) { +} + +ServiceManager::TestAPI::~TestAPI() { +} + +bool ServiceManager::TestAPI::HasCreatedInstance() { + return has_created_instance; +} + +ScopedMessagePipeHandle ServiceManager::TestAPI::GetServiceProviderHandle() { + MessagePipe pipe; + service_provider_.reset( + BindToPipe(new TestServiceProviderConnection(manager_), + pipe.handle0.Pass())); + return pipe.handle1.Pass(); +} + +bool ServiceManager::TestAPI::HasFactoryForURL(const GURL& url) const { + return manager_->url_to_service_factory_.find(url) != + manager_->url_to_service_factory_.end(); +} + +ServiceManager::ServiceManager() + : interceptor_(NULL) { +} + +ServiceManager::~ServiceManager() { + STLDeleteValues(&url_to_service_factory_); + STLDeleteValues(&url_to_loader_); + STLDeleteValues(&scheme_to_loader_); +} + +// static +ServiceManager* ServiceManager::GetInstance() { + static base::LazyInstance<ServiceManager> instance = + LAZY_INSTANCE_INITIALIZER; + has_created_instance = true; + return &instance.Get(); +} + +void ServiceManager::ConnectToService(const GURL& url, + const std::string& name, + ScopedMessagePipeHandle client_handle, + const GURL& requestor_url) { + URLToServiceFactoryMap::const_iterator service_it = + url_to_service_factory_.find(url); + ServiceFactory* service_factory; + if (service_it != url_to_service_factory_.end()) { + service_factory = service_it->second; + } else { + MessagePipe pipe; + GetLoaderForURL(url)->LoadService(this, url, pipe.handle0.Pass()); + + service_factory = + BindToPipe(new ServiceFactory(this, url), pipe.handle1.Pass()); + + url_to_service_factory_[url] = service_factory; + } + if (interceptor_) { + service_factory->ConnectToClient( + name, + interceptor_->OnConnectToClient(url, client_handle.Pass()), + requestor_url); + } else { + service_factory->ConnectToClient(name, client_handle.Pass(), requestor_url); + } +} + +void ServiceManager::SetLoaderForURL(scoped_ptr<ServiceLoader> loader, + const GURL& url) { + URLToLoaderMap::iterator it = url_to_loader_.find(url); + if (it != url_to_loader_.end()) + delete it->second; + url_to_loader_[url] = loader.release(); +} + +void ServiceManager::SetLoaderForScheme(scoped_ptr<ServiceLoader> loader, + const std::string& scheme) { + SchemeToLoaderMap::iterator it = scheme_to_loader_.find(scheme); + if (it != scheme_to_loader_.end()) + delete it->second; + scheme_to_loader_[scheme] = loader.release(); +} + +void ServiceManager::SetInterceptor(Interceptor* interceptor) { + interceptor_ = interceptor; +} + +ServiceLoader* ServiceManager::GetLoaderForURL(const GURL& url) { + URLToLoaderMap::const_iterator url_it = url_to_loader_.find(url); + if (url_it != url_to_loader_.end()) + return url_it->second; + SchemeToLoaderMap::const_iterator scheme_it = + scheme_to_loader_.find(url.scheme()); + if (scheme_it != scheme_to_loader_.end()) + return scheme_it->second; + DCHECK(default_loader_); + return default_loader_.get(); +} + +void ServiceManager::OnServiceFactoryError(ServiceFactory* service_factory) { + // Called from ~ServiceFactory, so we do not need to call Destroy here. + const GURL url = service_factory->url(); + URLToServiceFactoryMap::iterator it = url_to_service_factory_.find(url); + DCHECK(it != url_to_service_factory_.end()); + delete it->second; + url_to_service_factory_.erase(it); + ServiceLoader* loader = GetLoaderForURL(url); + if (loader) + loader->OnServiceError(this, url); +} + +} // namespace mojo diff --git a/chromium/mojo/service_manager/service_manager.h b/chromium/mojo/service_manager/service_manager.h new file mode 100644 index 00000000000..170e3b7e311 --- /dev/null +++ b/chromium/mojo/service_manager/service_manager.h @@ -0,0 +1,117 @@ +// 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 MOJO_SERVICE_MANAGER_SERVICE_MANAGER_H_ +#define MOJO_SERVICE_MANAGER_SERVICE_MANAGER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/service_manager/service_manager_export.h" +#include "url/gurl.h" + +namespace mojo { + +class MOJO_SERVICE_MANAGER_EXPORT ServiceManager { + public: + // API for testing. + class MOJO_SERVICE_MANAGER_EXPORT TestAPI { + public: + explicit TestAPI(ServiceManager* manager); + ~TestAPI(); + + // Returns a handle to a unique ServiceProvider instance. + ScopedMessagePipeHandle GetServiceProviderHandle(); + + // Returns true if the shared instance has been created. + static bool HasCreatedInstance(); + // Returns true if there is a ServiceFactory for this URL. + bool HasFactoryForURL(const GURL& url) const; + + private: + class TestServiceProviderConnection; + + ServiceManager* manager_; + scoped_ptr<TestServiceProviderConnection> service_provider_; + + DISALLOW_COPY_AND_ASSIGN(TestAPI); + }; + + // Interface class for debugging only. + class Interceptor { + public: + virtual ~Interceptor() {} + // Called when ServiceManager::Connect is called. + virtual ScopedMessagePipeHandle OnConnectToClient( + const GURL& url, ScopedMessagePipeHandle handle) = 0; + }; + + ServiceManager(); + ~ServiceManager(); + + // Returns a shared instance, creating it if necessary. + static ServiceManager* GetInstance(); + + // Loads a service if necessary and establishes a new client connection. + void ConnectToService(const GURL& service_url, + const std::string& service_name, + ScopedMessagePipeHandle client_handle, + const GURL& requestor_url); + + template <typename Interface> + void ConnectTo(const GURL& service_url, + InterfacePtr<Interface>* ptr, + const GURL& requestor_url) { + MessagePipe pipe; + ptr->Bind(pipe.handle0.Pass()); + ConnectToService(service_url, + Interface::Name_, + pipe.handle1.Pass(), + requestor_url); + } + + // Sets the default Loader to be used if not overridden by SetLoaderForURL() + // or SetLoaderForScheme(). + void set_default_loader(scoped_ptr<ServiceLoader> loader) { + default_loader_ = loader.Pass(); + } + // Sets a Loader to be used for a specific url. + void SetLoaderForURL(scoped_ptr<ServiceLoader> loader, const GURL& url); + // Sets a Loader to be used for a specific url scheme. + void SetLoaderForScheme(scoped_ptr<ServiceLoader> loader, + const std::string& scheme); + // Allows to interpose a debugger to service connections. + void SetInterceptor(Interceptor* interceptor); + + private: + class ServiceFactory; + typedef std::map<std::string, ServiceLoader*> SchemeToLoaderMap; + typedef std::map<GURL, ServiceLoader*> URLToLoaderMap; + typedef std::map<GURL, ServiceFactory*> URLToServiceFactoryMap; + + // Returns the Loader to use for a url (using default if not overridden.) + // The preference is to use a loader that's been specified for an url first, + // then one that's been specified for a scheme, then the default. + ServiceLoader* GetLoaderForURL(const GURL& url); + + // Removes a ServiceFactory when it encounters an error. + void OnServiceFactoryError(ServiceFactory* service_factory); + + // Loader management. + URLToLoaderMap url_to_loader_; + SchemeToLoaderMap scheme_to_loader_; + scoped_ptr<ServiceLoader> default_loader_; + Interceptor* interceptor_; + + URLToServiceFactoryMap url_to_service_factory_; + DISALLOW_COPY_AND_ASSIGN(ServiceManager); +}; + +} // namespace mojo + +#endif // MOJO_SERVICE_MANAGER_SERVICE_MANAGER_H_ diff --git a/chromium/mojo/service_manager/service_manager_export.h b/chromium/mojo/service_manager/service_manager_export.h new file mode 100644 index 00000000000..251aad95d52 --- /dev/null +++ b/chromium/mojo/service_manager/service_manager_export.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICE_MANAGER_SERVICE_MANAGER_EXPORT_H_ +#define MOJO_SERVICE_MANAGER_SERVICE_MANAGER_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_SERVICE_MANAGER_IMPLEMENTATION) +#define MOJO_SERVICE_MANAGER_EXPORT __declspec(dllexport) +#else +#define MOJO_SERVICE_MANAGER_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_SERVICE_MANAGER_IMPLEMENTATION) +#define MOJO_SERVICE_MANAGER_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SERVICE_MANAGER_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_SERVICE_MANAGER_EXPORT +#endif + +#endif // MOJO_SERVICE_MANAGER_SERVICE_MANAGER_EXPORT_H_ diff --git a/chromium/mojo/service_manager/service_manager_unittest.cc b/chromium/mojo/service_manager/service_manager_unittest.cc new file mode 100644 index 00000000000..a2122017c27 --- /dev/null +++ b/chromium/mojo/service_manager/service_manager_unittest.cc @@ -0,0 +1,406 @@ +// 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/at_exit.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/service_manager/service_manager.h" +#include "mojo/service_manager/test.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const char kTestURLString[] = "test:testService"; +const char kTestAURLString[] = "test:TestA"; +const char kTestBURLString[] = "test:TestB"; + +struct TestContext { + TestContext() : num_impls(0), num_loader_deletes(0) {} + std::string last_test_string; + int num_impls; + int num_loader_deletes; +}; + +class QuitMessageLoopErrorHandler : public ErrorHandler { + public: + QuitMessageLoopErrorHandler() {} + virtual ~QuitMessageLoopErrorHandler() {} + + // |ErrorHandler| implementation: + virtual void OnConnectionError() OVERRIDE { + base::MessageLoop::current()->QuitWhenIdle(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler); +}; + +class TestServiceImpl : public InterfaceImpl<TestService> { + public: + explicit TestServiceImpl(TestContext* context) : context_(context) { + ++context_->num_impls; + } + + virtual ~TestServiceImpl() { + --context_->num_impls; + } + + virtual void OnConnectionError() OVERRIDE { + base::MessageLoop::current()->Quit(); + } + + // TestService implementation: + virtual void Test(const String& test_string) OVERRIDE { + context_->last_test_string = test_string; + client()->AckTest(); + } + + private: + TestContext* context_; +}; + +class TestClientImpl : public TestClient { + public: + explicit TestClientImpl(TestServicePtr service) + : service_(service.Pass()), + quit_after_ack_(false) { + service_.set_client(this); + } + + virtual ~TestClientImpl() {} + + virtual void AckTest() OVERRIDE { + if (quit_after_ack_) + base::MessageLoop::current()->Quit(); + } + + void Test(std::string test_string) { + quit_after_ack_ = true; + service_->Test(test_string); + } + + private: + TestServicePtr service_; + bool quit_after_ack_; + DISALLOW_COPY_AND_ASSIGN(TestClientImpl); +}; + +class TestServiceLoader : public ServiceLoader { + public: + TestServiceLoader() + : context_(NULL), + num_loads_(0) { + } + + virtual ~TestServiceLoader() { + if (context_) + ++context_->num_loader_deletes; + test_app_.reset(NULL); + } + + void set_context(TestContext* context) { context_ = context; } + int num_loads() const { return num_loads_; } + + private: + virtual void LoadService( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_provider_handle) OVERRIDE { + ++num_loads_; + test_app_.reset(new Application(service_provider_handle.Pass())); + test_app_->AddService<TestServiceImpl>(context_); + } + + virtual void OnServiceError(ServiceManager* manager, + const GURL& url) OVERRIDE { + } + + scoped_ptr<Application> test_app_; + TestContext* context_; + int num_loads_; + DISALLOW_COPY_AND_ASSIGN(TestServiceLoader); +}; + +// Used to test that the requestor url will be correctly passed. +class TestAImpl : public InterfaceImpl<TestA> { + public: + explicit TestAImpl(Application* app) : app_(app) {} + + virtual void LoadB() OVERRIDE { + TestBPtr b; + app_->ConnectTo(kTestBURLString, &b); + b->Test(); + } + + private: + Application* app_; +}; + +class TestBImpl : public InterfaceImpl<TestB> { + public: + virtual void Test() OVERRIDE { + base::MessageLoop::current()->Quit(); + } +}; + +class TestApp : public Application, public ServiceLoader { + public: + explicit TestApp(std::string requestor_url) + : requestor_url_(requestor_url), + num_connects_(0) { + } + + int num_connects() const { return num_connects_; } + + private: + virtual void LoadService( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_provider_handle) OVERRIDE { + BindServiceProvider(service_provider_handle.Pass()); + } + + virtual bool AllowIncomingConnection(const mojo::String& service_name, + const mojo::String& requestor_url) + MOJO_OVERRIDE { + if (requestor_url_.empty() || requestor_url_ == requestor_url) { + ++num_connects_; + return true; + } else { + base::MessageLoop::current()->Quit(); + return false; + } + } + + virtual void OnServiceError(ServiceManager* manager, + const GURL& url) OVERRIDE {} + std::string requestor_url_; + int num_connects_; +}; + +class TestServiceInterceptor : public ServiceManager::Interceptor { + public: + TestServiceInterceptor() : call_count_(0) {} + + virtual ScopedMessagePipeHandle OnConnectToClient( + const GURL& url, ScopedMessagePipeHandle handle) OVERRIDE { + ++call_count_; + url_ = url; + return handle.Pass(); + } + + std::string url_spec() const { + if (!url_.is_valid()) + return "invalid url"; + return url_.spec(); + } + + int call_count() const { + return call_count_; + } + + private: + int call_count_; + GURL url_; + DISALLOW_COPY_AND_ASSIGN(TestServiceInterceptor); +}; + +} // namespace + +class ServiceManagerTest : public testing::Test { + public: + ServiceManagerTest() {} + + virtual ~ServiceManagerTest() {} + + virtual void SetUp() OVERRIDE { + GURL test_url(kTestURLString); + service_manager_.reset(new ServiceManager); + + MessagePipe pipe; + TestServicePtr service_proxy = MakeProxy<TestService>(pipe.handle0.Pass()); + test_client_.reset(new TestClientImpl(service_proxy.Pass())); + + TestServiceLoader* default_loader = new TestServiceLoader; + default_loader->set_context(&context_); + service_manager_->set_default_loader( + scoped_ptr<ServiceLoader>(default_loader)); + + service_manager_->ConnectToService( + test_url, TestService::Name_, pipe.handle1.Pass(), GURL()); + } + + virtual void TearDown() OVERRIDE { + test_client_.reset(NULL); + service_manager_.reset(NULL); + } + + bool HasFactoryForTestURL() { + ServiceManager::TestAPI manager_test_api(service_manager_.get()); + return manager_test_api.HasFactoryForURL(GURL(kTestURLString)); + } + + protected: + base::ShadowingAtExitManager at_exit_; + base::MessageLoop loop_; + TestContext context_; + scoped_ptr<TestClientImpl> test_client_; + scoped_ptr<ServiceManager> service_manager_; + DISALLOW_COPY_AND_ASSIGN(ServiceManagerTest); +}; + +TEST_F(ServiceManagerTest, Basic) { + test_client_->Test("test"); + loop_.Run(); + EXPECT_EQ(std::string("test"), context_.last_test_string); +} + +TEST_F(ServiceManagerTest, ClientError) { + test_client_->Test("test"); + EXPECT_TRUE(HasFactoryForTestURL()); + loop_.Run(); + EXPECT_EQ(1, context_.num_impls); + test_client_.reset(NULL); + loop_.Run(); + EXPECT_EQ(0, context_.num_impls); + EXPECT_TRUE(HasFactoryForTestURL()); +} + +TEST_F(ServiceManagerTest, Deletes) { + { + ServiceManager sm; + TestServiceLoader* default_loader = new TestServiceLoader; + default_loader->set_context(&context_); + TestServiceLoader* url_loader1 = new TestServiceLoader; + TestServiceLoader* url_loader2 = new TestServiceLoader; + url_loader1->set_context(&context_); + url_loader2->set_context(&context_); + TestServiceLoader* scheme_loader1 = new TestServiceLoader; + TestServiceLoader* scheme_loader2 = new TestServiceLoader; + scheme_loader1->set_context(&context_); + scheme_loader2->set_context(&context_); + sm.set_default_loader(scoped_ptr<ServiceLoader>(default_loader)); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(url_loader1), + GURL("test:test1")); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(url_loader2), + GURL("test:test1")); + sm.SetLoaderForScheme(scoped_ptr<ServiceLoader>(scheme_loader1), "test"); + sm.SetLoaderForScheme(scoped_ptr<ServiceLoader>(scheme_loader2), "test"); + } + EXPECT_EQ(5, context_.num_loader_deletes); +} + +// Confirm that both urls and schemes can have their loaders explicitly set. +TEST_F(ServiceManagerTest, SetLoaders) { + ServiceManager sm; + TestServiceLoader* default_loader = new TestServiceLoader; + TestServiceLoader* url_loader = new TestServiceLoader; + TestServiceLoader* scheme_loader = new TestServiceLoader; + sm.set_default_loader(scoped_ptr<ServiceLoader>(default_loader)); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(url_loader), GURL("test:test1")); + sm.SetLoaderForScheme(scoped_ptr<ServiceLoader>(scheme_loader), "test"); + + // test::test1 should go to url_loader. + TestServicePtr test_service; + sm.ConnectTo(GURL("test:test1"), &test_service, GURL()); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(0, scheme_loader->num_loads()); + EXPECT_EQ(0, default_loader->num_loads()); + + // test::test2 should go to scheme loader. + sm.ConnectTo(GURL("test:test2"), &test_service, GURL()); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(1, scheme_loader->num_loads()); + EXPECT_EQ(0, default_loader->num_loads()); + + // http::test1 should go to default loader. + sm.ConnectTo(GURL("http:test1"), &test_service, GURL()); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(1, scheme_loader->num_loads()); + EXPECT_EQ(1, default_loader->num_loads()); +} + +// Confirm that the url of a service is correctly passed to another service that +// it loads. +TEST_F(ServiceManagerTest, ALoadB) { + ServiceManager sm; + + // Any url can load a. + TestApp* a_app = new TestApp(std::string()); + a_app->AddService<TestAImpl>(a_app); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(a_app), GURL(kTestAURLString)); + + // Only a can load b. + TestApp* b_app = new TestApp(kTestAURLString); + b_app->AddService<TestBImpl>(); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(b_app), GURL(kTestBURLString)); + + TestAPtr a; + sm.ConnectTo(GURL(kTestAURLString), &a, GURL()); + a->LoadB(); + loop_.Run(); + EXPECT_EQ(1, b_app->num_connects()); +} + +// Confirm that the url of a service is correctly passed to another service that +// it loads, and that it can be rejected. +TEST_F(ServiceManagerTest, ANoLoadB) { + ServiceManager sm; + + // Any url can load a. + TestApp* a_app = new TestApp(std::string()); + a_app->AddService<TestAImpl>(a_app); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(a_app), GURL(kTestAURLString)); + + // Only c can load b, so this will fail. + TestApp* b_app = new TestApp("test:TestC"); + b_app->AddService<TestBImpl>(); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(b_app), GURL(kTestBURLString)); + + TestAPtr a; + sm.ConnectTo(GURL(kTestAURLString), &a, GURL()); + a->LoadB(); + loop_.Run(); + EXPECT_EQ(0, b_app->num_connects()); +} + +TEST_F(ServiceManagerTest, NoServiceNoLoad) { + ServiceManager sm; + + TestApp* b_app = new TestApp(std::string()); + b_app->AddService<TestBImpl>(); + sm.SetLoaderForURL(scoped_ptr<ServiceLoader>(b_app), GURL(kTestBURLString)); + + // There is no TestA service implementation registered with ServiceManager, + // so this cannot succeed (but also shouldn't crash). + TestAPtr a; + sm.ConnectTo(GURL(kTestBURLString), &a, GURL()); + QuitMessageLoopErrorHandler quitter; + a.set_error_handler(&quitter); + a->LoadB(); + + loop_.Run(); + EXPECT_TRUE(a.encountered_error()); + EXPECT_EQ(0, b_app->num_connects()); +} + +TEST_F(ServiceManagerTest, Interceptor) { + ServiceManager sm; + TestServiceInterceptor interceptor; + TestServiceLoader* default_loader = new TestServiceLoader; + sm.set_default_loader(scoped_ptr<ServiceLoader>(default_loader)); + sm.SetInterceptor(&interceptor); + + std::string url("test:test3"); + TestServicePtr test_service; + sm.ConnectTo(GURL(url), &test_service, GURL()); + EXPECT_EQ(1, interceptor.call_count()); + EXPECT_EQ(url, interceptor.url_spec()); + EXPECT_EQ(1, default_loader->num_loads()); +} + +} // namespace mojo diff --git a/chromium/mojo/service_manager/test.mojom b/chromium/mojo/service_manager/test.mojom new file mode 100644 index 00000000000..47921cda9af --- /dev/null +++ b/chromium/mojo/service_manager/test.mojom @@ -0,0 +1,24 @@ +// 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. + +module mojo { + +[Client=TestClient] +interface TestService { + Test(string test_string); +}; + +interface TestClient { + AckTest(); +}; + +interface TestA { + LoadB(); +}; + +interface TestB { + Test(); +}; + +} diff --git a/chromium/mojo/services/DEPS b/chromium/mojo/services/DEPS new file mode 100644 index 00000000000..2bbe62df877 --- /dev/null +++ b/chromium/mojo/services/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "-mojo", + "+mojo/common", + "+mojo/public", + "+jni", + + # TODO(abarth) Instead of having the services depend on the shell, we + # probably should create a layer below the services. + "+mojo/shell", +] diff --git a/chromium/mojo/services/dbus_echo/DEPS b/chromium/mojo/services/dbus_echo/DEPS new file mode 100644 index 00000000000..35a7b6c826f --- /dev/null +++ b/chromium/mojo/services/dbus_echo/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+base", + "+mojo/dbus", + "+mojo/embedder", +] diff --git a/chromium/mojo/services/dbus_echo/dbus_echo_service.cc b/chromium/mojo/services/dbus_echo/dbus_echo_service.cc new file mode 100644 index 00000000000..ae8029f435b --- /dev/null +++ b/chromium/mojo/services/dbus_echo/dbus_echo_service.cc @@ -0,0 +1,58 @@ +// 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/at_exit.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/dbus/dbus_external_service.h" +#include "mojo/embedder/channel_init.h" +#include "mojo/embedder/embedder.h" +#include "mojo/public/cpp/environment/environment.h" +#include "mojo/services/dbus_echo/echo.mojom.h" + +namespace { +class EchoServiceImpl : public mojo::InterfaceImpl<mojo::EchoService> { + public: + EchoServiceImpl() {} + virtual ~EchoServiceImpl() {} + + protected: + virtual void Echo( + const mojo::String& in_to_echo, + const mojo::Callback<void(mojo::String)>& callback) OVERRIDE { + DVLOG(1) << "Asked to echo " << in_to_echo; + callback.Run(in_to_echo); + } +}; + +const char kServiceName[] = "org.chromium.EchoService"; +} // anonymous namespace + +int main(int argc, char** argv) { + base::AtExitManager exit_manager; + base::CommandLine::Init(argc, argv); + + logging::LoggingSettings settings; + settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; + logging::InitLogging(settings); + logging::SetLogItems(false, // Process ID + false, // Thread ID + false, // Timestamp + false); // Tick count + + mojo::embedder::Init(); + + base::MessageLoopForIO message_loop; + base::RunLoop run_loop; + + mojo::DBusExternalService<EchoServiceImpl> echo_service(kServiceName); + echo_service.Start(); + + run_loop.Run(); + return 0; +} diff --git a/chromium/mojo/services/dbus_echo/echo.mojom b/chromium/mojo/services/dbus_echo/echo.mojom new file mode 100644 index 00000000000..937737c223c --- /dev/null +++ b/chromium/mojo/services/dbus_echo/echo.mojom @@ -0,0 +1,11 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +interface EchoService { + Echo(string to_echo) => (string echoed); +}; + +} diff --git a/chromium/mojo/services/gles2/DEPS b/chromium/mojo/services/gles2/DEPS new file mode 100644 index 00000000000..acd992d92a2 --- /dev/null +++ b/chromium/mojo/services/gles2/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+gpu/command_buffer", + "+ui/gfx", + "+ui/gl", +] diff --git a/chromium/mojo/services/gles2/command_buffer.mojom b/chromium/mojo/services/gles2/command_buffer.mojom new file mode 100644 index 00000000000..1f91362eb4d --- /dev/null +++ b/chromium/mojo/services/gles2/command_buffer.mojom @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +struct CommandBufferState { + int32 num_entries; + int32 get_offset; + int32 put_offset; + int32 token; + int32 error; // TODO(piman): enum + int32 context_lost_reason; // TODO(piman): enum + uint32 generation; +}; + +interface CommandBufferSyncClient { + DidInitialize(bool success); + DidMakeProgress(CommandBufferState state); +}; + +[Client=CommandBufferClient] +interface CommandBuffer { + Initialize(CommandBufferSyncClient sync_client, + handle<shared_buffer> shared_state); + SetGetBuffer(int32 buffer); + Flush(int32 put_offset); + MakeProgress(int32 last_get_offset); + RegisterTransferBuffer( + int32 id, handle<shared_buffer> transfer_buffer, uint32 size); + DestroyTransferBuffer(int32 id); + Echo() => (); + + // TODO(piman): move to somewhere else (native_viewport?). + RequestAnimationFrames(); + CancelAnimationFrames(); + + // TODO(piman): sync points +}; + +interface CommandBufferClient { + DidDestroy(); + LostContext(int32 lost_reason); // TODO(piman): enum + + // TODO(piman): move to somewhere else (native_viewport?). + DrawAnimationFrame(); +}; + +} diff --git a/chromium/mojo/services/gles2/command_buffer_impl.cc b/chromium/mojo/services/gles2/command_buffer_impl.cc new file mode 100644 index 00000000000..090b78c3707 --- /dev/null +++ b/chromium/mojo/services/gles2/command_buffer_impl.cc @@ -0,0 +1,198 @@ +// 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 "mojo/services/gles2/command_buffer_impl.h" + +#include "base/bind.h" +#include "base/memory/shared_memory.h" + +#include "gpu/command_buffer/common/constants.h" +#include "gpu/command_buffer/service/command_buffer_service.h" +#include "gpu/command_buffer/service/context_group.h" +#include "gpu/command_buffer/service/gles2_cmd_decoder.h" +#include "gpu/command_buffer/service/gpu_control_service.h" +#include "gpu/command_buffer/service/gpu_scheduler.h" +#include "gpu/command_buffer/service/image_manager.h" +#include "gpu/command_buffer/service/mailbox_manager.h" +#include "gpu/command_buffer/service/memory_tracking.h" +#include "mojo/services/gles2/command_buffer_type_conversions.h" +#include "mojo/services/gles2/mojo_buffer_backing.h" +#include "ui/gl/gl_context.h" +#include "ui/gl/gl_surface.h" + +namespace mojo { +namespace services { + +namespace { + +class MemoryTrackerStub : public gpu::gles2::MemoryTracker { + public: + MemoryTrackerStub() {} + + virtual void TrackMemoryAllocatedChange(size_t old_size, + size_t new_size, + gpu::gles2::MemoryTracker::Pool pool) + OVERRIDE {} + + virtual bool EnsureGPUMemoryAvailable(size_t size_needed) OVERRIDE { + return true; + }; + + private: + virtual ~MemoryTrackerStub() {} + + DISALLOW_COPY_AND_ASSIGN(MemoryTrackerStub); +}; + +} // anonymous namespace + +CommandBufferImpl::CommandBufferImpl(gfx::AcceleratedWidget widget, + const gfx::Size& size) + : widget_(widget), size_(size) {} + +CommandBufferImpl::~CommandBufferImpl() { + client()->DidDestroy(); + if (decoder_.get()) { + bool have_context = decoder_->MakeCurrent(); + decoder_->Destroy(have_context); + } +} + +void CommandBufferImpl::OnConnectionError() { + // TODO(darin): How should we handle this error? +} + +void CommandBufferImpl::Initialize( + CommandBufferSyncClientPtr sync_client, + mojo::ScopedSharedBufferHandle shared_state) { + sync_client_ = sync_client.Pass(); + sync_client_->DidInitialize(DoInitialize(shared_state.Pass())); +} + +bool CommandBufferImpl::DoInitialize( + mojo::ScopedSharedBufferHandle shared_state) { + // TODO(piman): offscreen surface. + scoped_refptr<gfx::GLSurface> surface = + gfx::GLSurface::CreateViewGLSurface(widget_); + if (!surface.get()) + return false; + + // TODO(piman): context sharing, virtual contexts, gpu preference. + scoped_refptr<gfx::GLContext> context = gfx::GLContext::CreateGLContext( + NULL, surface.get(), gfx::PreferIntegratedGpu); + if (!context.get()) + return false; + + if (!context->MakeCurrent(surface.get())) + return false; + + // TODO(piman): ShaderTranslatorCache is currently per-ContextGroup but + // only needs to be per-thread. + scoped_refptr<gpu::gles2::ContextGroup> context_group = + new gpu::gles2::ContextGroup(NULL, + NULL, + new MemoryTrackerStub, + new gpu::gles2::ShaderTranslatorCache, + NULL, + true); + + command_buffer_.reset( + new gpu::CommandBufferService(context_group->transfer_buffer_manager())); + bool result = command_buffer_->Initialize(); + DCHECK(result); + + decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group.get())); + scheduler_.reset(new gpu::GpuScheduler( + command_buffer_.get(), decoder_.get(), decoder_.get())); + decoder_->set_engine(scheduler_.get()); + + gpu::gles2::DisallowedFeatures disallowed_features; + + // TODO(piman): attributes. + std::vector<int32> attrib_vector; + if (!decoder_->Initialize(surface, + context, + false /* offscreen */, + size_, + disallowed_features, + attrib_vector)) + return false; + + gpu_control_.reset( + new gpu::GpuControlService(context_group->image_manager(), NULL)); + + command_buffer_->SetPutOffsetChangeCallback(base::Bind( + &gpu::GpuScheduler::PutChanged, base::Unretained(scheduler_.get()))); + command_buffer_->SetGetBufferChangeCallback(base::Bind( + &gpu::GpuScheduler::SetGetBuffer, base::Unretained(scheduler_.get()))); + command_buffer_->SetParseErrorCallback( + base::Bind(&CommandBufferImpl::OnParseError, base::Unretained(this))); + + // TODO(piman): other callbacks + + const size_t kSize = sizeof(gpu::CommandBufferSharedState); + scoped_ptr<gpu::BufferBacking> backing( + gles2::MojoBufferBacking::Create(shared_state.Pass(), kSize)); + if (!backing.get()) + return false; + + command_buffer_->SetSharedStateBuffer(backing.Pass()); + return true; +} + +void CommandBufferImpl::SetGetBuffer(int32_t buffer) { + command_buffer_->SetGetBuffer(buffer); +} + +void CommandBufferImpl::Flush(int32_t put_offset) { + command_buffer_->Flush(put_offset); +} + +void CommandBufferImpl::MakeProgress(int32_t last_get_offset) { + // TODO(piman): handle out-of-order. + sync_client_->DidMakeProgress( + CommandBufferState::From(command_buffer_->GetLastState())); +} + +void CommandBufferImpl::RegisterTransferBuffer( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + // Take ownership of the memory and map it into this process. + // This validates the size. + scoped_ptr<gpu::BufferBacking> backing( + gles2::MojoBufferBacking::Create(transfer_buffer.Pass(), size)); + if (!backing.get()) { + DVLOG(0) << "Failed to map shared memory."; + return; + } + command_buffer_->RegisterTransferBuffer(id, backing.Pass()); +} + +void CommandBufferImpl::DestroyTransferBuffer(int32_t id) { + command_buffer_->DestroyTransferBuffer(id); +} + +void CommandBufferImpl::Echo(const Callback<void()>& callback) { + callback.Run(); +} + +void CommandBufferImpl::RequestAnimationFrames() { + timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(16), + this, + &CommandBufferImpl::DrawAnimationFrame); +} + +void CommandBufferImpl::CancelAnimationFrames() { timer_.Stop(); } + +void CommandBufferImpl::OnParseError() { + gpu::CommandBuffer::State state = command_buffer_->GetLastState(); + client()->LostContext(state.context_lost_reason); +} + +void CommandBufferImpl::DrawAnimationFrame() { client()->DrawAnimationFrame(); } + +} // namespace services +} // namespace mojo diff --git a/chromium/mojo/services/gles2/command_buffer_impl.h b/chromium/mojo/services/gles2/command_buffer_impl.h new file mode 100644 index 00000000000..5d1c7bdd205 --- /dev/null +++ b/chromium/mojo/services/gles2/command_buffer_impl.h @@ -0,0 +1,72 @@ +// 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 MOJO_SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_ +#define MOJO_SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/timer/timer.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/services/gles2/command_buffer.mojom.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/size.h" + +namespace gpu { +class CommandBufferService; +class GpuScheduler; +class GpuControlService; +namespace gles2 { +class GLES2Decoder; +} +} + +namespace mojo { +namespace services { + +class CommandBufferImpl : public InterfaceImpl<CommandBuffer> { + public: + CommandBufferImpl(gfx::AcceleratedWidget widget, + const gfx::Size& size); + virtual ~CommandBufferImpl(); + + virtual void OnConnectionError() OVERRIDE; + virtual void Initialize(CommandBufferSyncClientPtr sync_client, + mojo::ScopedSharedBufferHandle shared_state) OVERRIDE; + virtual void SetGetBuffer(int32_t buffer) OVERRIDE; + virtual void Flush(int32_t put_offset) OVERRIDE; + virtual void MakeProgress(int32_t last_get_offset) OVERRIDE; + virtual void RegisterTransferBuffer( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) OVERRIDE; + virtual void DestroyTransferBuffer(int32_t id) OVERRIDE; + virtual void Echo(const Callback<void()>& callback) OVERRIDE; + + virtual void RequestAnimationFrames() OVERRIDE; + virtual void CancelAnimationFrames() OVERRIDE; + + private: + bool DoInitialize(mojo::ScopedSharedBufferHandle shared_state); + + void OnParseError(); + + void DrawAnimationFrame(); + + CommandBufferSyncClientPtr sync_client_; + + gfx::AcceleratedWidget widget_; + gfx::Size size_; + scoped_ptr<gpu::CommandBufferService> command_buffer_; + scoped_ptr<gpu::gles2::GLES2Decoder> decoder_; + scoped_ptr<gpu::GpuScheduler> scheduler_; + scoped_ptr<gpu::GpuControlService> gpu_control_; + base::RepeatingTimer<CommandBufferImpl> timer_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferImpl); +}; + +} // namespace services +} // namespace mojo + +#endif // MOJO_SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_ diff --git a/chromium/mojo/services/gles2/command_buffer_type_conversions.cc b/chromium/mojo/services/gles2/command_buffer_type_conversions.cc new file mode 100644 index 00000000000..3952bef3179 --- /dev/null +++ b/chromium/mojo/services/gles2/command_buffer_type_conversions.cc @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/gles2/command_buffer_type_conversions.h" + +#include "mojo/services/gles2/command_buffer.mojom.h" + +namespace mojo { + +CommandBufferStatePtr +TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State>::ConvertFrom( + const gpu::CommandBuffer::State& input) { + CommandBufferStatePtr result(CommandBufferState::New()); + result->num_entries = input.num_entries; + result->get_offset = input.get_offset; + result->put_offset = input.put_offset; + result->token = input.token; + result->error = input.error; + result->context_lost_reason = input.context_lost_reason; + result->generation = input.generation; + return result.Pass(); +} + +gpu::CommandBuffer::State +TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State>::ConvertTo( + const CommandBufferStatePtr& input) { + gpu::CommandBuffer::State state; + state.num_entries = input->num_entries; + state.get_offset = input->get_offset; + state.put_offset = input->put_offset; + state.token = input->token; + state.error = static_cast<gpu::error::Error>(input->error); + state.context_lost_reason = + static_cast<gpu::error::ContextLostReason>(input->context_lost_reason); + state.generation = input->generation; + return state; +} + +} // namespace mojo diff --git a/chromium/mojo/services/gles2/command_buffer_type_conversions.h b/chromium/mojo/services/gles2/command_buffer_type_conversions.h new file mode 100644 index 00000000000..b2334d008ac --- /dev/null +++ b/chromium/mojo/services/gles2/command_buffer_type_conversions.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_ +#define MOJO_SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_ + +#include "gpu/command_buffer/common/command_buffer.h" +#include "mojo/public/cpp/bindings/type_converter.h" +#include "mojo/services/gles2/command_buffer.mojom.h" + +namespace mojo { + +class CommandBufferState; + +template <> +class TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State> { + public: + static CommandBufferStatePtr ConvertFrom( + const gpu::CommandBuffer::State& input); + static gpu::CommandBuffer::State ConvertTo( + const CommandBufferStatePtr& input); +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_ diff --git a/chromium/mojo/services/gles2/mojo_buffer_backing.cc b/chromium/mojo/services/gles2/mojo_buffer_backing.cc new file mode 100644 index 00000000000..3c908787ff5 --- /dev/null +++ b/chromium/mojo/services/gles2/mojo_buffer_backing.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/gles2/mojo_buffer_backing.h" + +#include "base/logging.h" + +namespace mojo { +namespace gles2 { + +MojoBufferBacking::MojoBufferBacking(mojo::ScopedSharedBufferHandle handle, + void* memory, + size_t size) + : handle_(handle.Pass()), memory_(memory), size_(size) {} + +MojoBufferBacking::~MojoBufferBacking() { mojo::UnmapBuffer(memory_); } + +// static +scoped_ptr<gpu::BufferBacking> MojoBufferBacking::Create( + mojo::ScopedSharedBufferHandle handle, + size_t size) { + void* memory = NULL; + MojoResult result = mojo::MapBuffer( + handle.get(), 0, size, &memory, MOJO_MAP_BUFFER_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return scoped_ptr<BufferBacking>(); + DCHECK(memory); + return scoped_ptr<BufferBacking>( + new MojoBufferBacking(handle.Pass(), memory, size)); +} +void* MojoBufferBacking::GetMemory() const { return memory_; } +size_t MojoBufferBacking::GetSize() const { return size_; } + +} // namespace gles2 +} // namespace mojo diff --git a/chromium/mojo/services/gles2/mojo_buffer_backing.h b/chromium/mojo/services/gles2/mojo_buffer_backing.h new file mode 100644 index 00000000000..4048b6cbff2 --- /dev/null +++ b/chromium/mojo/services/gles2/mojo_buffer_backing.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_GLES2_MOJO_BUFFER_BACKING_H_ +#define MOJO_SERVICES_GLES2_MOJO_BUFFER_BACKING_H_ + +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/common/buffer.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace gles2 { + +class MojoBufferBacking : public gpu::BufferBacking { + public: + MojoBufferBacking(mojo::ScopedSharedBufferHandle handle, + void* memory, + size_t size); + virtual ~MojoBufferBacking(); + + static scoped_ptr<gpu::BufferBacking> Create( + mojo::ScopedSharedBufferHandle handle, + size_t size); + + virtual void* GetMemory() const OVERRIDE; + virtual size_t GetSize() const OVERRIDE; + + private: + mojo::ScopedSharedBufferHandle handle_; + void* memory_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(MojoBufferBacking); +}; + +} // namespace gles2 +} // namespace mojo + +#endif // MOJO_SERVICES_GLES2_MOJO_BUFFER_BACKING_H_ diff --git a/chromium/mojo/services/launcher/DEPS b/chromium/mojo/services/launcher/DEPS new file mode 100644 index 00000000000..28f1ec1d788 --- /dev/null +++ b/chromium/mojo/services/launcher/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/services/public", +] diff --git a/chromium/mojo/services/launcher/launcher.cc b/chromium/mojo/services/launcher/launcher.cc new file mode 100644 index 00000000000..eb4de409c8b --- /dev/null +++ b/chromium/mojo/services/launcher/launcher.cc @@ -0,0 +1,176 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_tokenizer.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/interfaces/launcher/launcher.mojom.h" +#include "mojo/services/public/interfaces/network/network_service.mojom.h" +#include "mojo/services/public/interfaces/network/url_loader.mojom.h" +#include "url/gurl.h" + +namespace mojo { +namespace launcher { + +class LauncherApp; + +class LauncherConnection : public InterfaceImpl<Launcher> { + public: + explicit LauncherConnection(LauncherApp* app) : app_(app) {} + virtual ~LauncherConnection() {} + + private: + // Overridden from Launcher: + virtual void Launch(const String& url) OVERRIDE; + + LauncherApp* app_; + + DISALLOW_COPY_AND_ASSIGN(LauncherConnection); +}; + +class LaunchInstance : public URLLoaderClient { + public: + LaunchInstance(LauncherApp* app, + LauncherClient* client, + const String& url); + virtual ~LaunchInstance() {} + + private: + // Overridden from URLLoaderClient: + virtual void OnReceivedRedirect(URLResponsePtr response, + const String& new_url, + const String& new_method) OVERRIDE { + } + virtual void OnReceivedResponse(URLResponsePtr response) OVERRIDE; + virtual void OnReceivedError(NetworkErrorPtr error) OVERRIDE { + ScheduleDestroy(); + } + virtual void OnReceivedEndOfResponseBody() OVERRIDE { + ScheduleDestroy(); + } + + std::string GetContentType(const Array<String>& headers) { + for (size_t i = 0; i < headers.size(); ++i) { + base::StringTokenizer t(headers[i], ": ;="); + while (t.GetNext()) { + if (!t.token_is_delim() && t.token() == "Content-Type") { + while (t.GetNext()) { + if (!t.token_is_delim()) + return t.token(); + } + } + } + } + return ""; + } + + void ScheduleDestroy() { + if (destroy_scheduled_) + return; + destroy_scheduled_ = true; + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } + + LauncherApp* app_; + bool destroy_scheduled_; + LauncherClient* client_; + URLLoaderPtr url_loader_; + ScopedDataPipeConsumerHandle response_body_stream_; + + DISALLOW_COPY_AND_ASSIGN(LaunchInstance); +}; + +class LauncherApp : public Application { + public: + LauncherApp() { + handler_map_["text/html"] = "mojo:mojo_html_viewer"; + handler_map_["image/png"] = "mojo:mojo_image_viewer"; + } + virtual ~LauncherApp() {} + + URLLoaderPtr CreateURLLoader() { + URLLoaderPtr loader; + network_service_->CreateURLLoader(Get(&loader)); + return loader.Pass(); + } + + std::string GetHandlerForContentType(const std::string& content_type) { + HandlerMap::const_iterator it = handler_map_.find(content_type); + return it != handler_map_.end() ? it->second : ""; + } + + private: + typedef std::map<std::string, std::string> HandlerMap; + + // Overridden from Application: + virtual void Initialize() OVERRIDE { + AddService<LauncherConnection>(this); + ConnectTo("mojo:mojo_network_service", &network_service_); + } + + HandlerMap handler_map_; + + NetworkServicePtr network_service_; + + DISALLOW_COPY_AND_ASSIGN(LauncherApp); +}; + +void LauncherConnection::Launch(const String& url_string) { + GURL url(url_string.To<std::string>()); + + // For Mojo URLs, the handler can always be found at the origin. + // TODO(aa): Return error for invalid URL? + if (url.is_valid() && url.SchemeIs("mojo")) { + client()->OnLaunch(url_string, + url.GetOrigin().spec(), + navigation::ResponseDetailsPtr()); + return; + } + + new LaunchInstance(app_, client(), url_string); +} + +LaunchInstance::LaunchInstance(LauncherApp* app, + LauncherClient* client, + const String& url) + : app_(app), + destroy_scheduled_(false), + client_(client) { + url_loader_ = app_->CreateURLLoader(); + url_loader_.set_client(this); + + URLRequestPtr request(URLRequest::New()); + request->url = url; + request->method = "GET"; + request->auto_follow_redirects = true; + + DataPipe data_pipe; + response_body_stream_ = data_pipe.consumer_handle.Pass(); + + url_loader_->Start(request.Pass(), data_pipe.producer_handle.Pass()); +} + +void LaunchInstance::OnReceivedResponse(URLResponsePtr response) { + std::string content_type = GetContentType(response->headers); + std::string handler_url = app_->GetHandlerForContentType(content_type); + if (!handler_url.empty()) { + navigation::ResponseDetailsPtr nav_response( + navigation::ResponseDetails::New()); + nav_response->response = response.Pass(); + nav_response->response_body_stream = response_body_stream_.Pass(); + client_->OnLaunch(nav_response->response->url, handler_url, + nav_response.Pass()); + } +} + +} // namespace launcher + +// static +Application* Application::Create() { + return new launcher::LauncherApp; +} + +} // namespace mojo diff --git a/chromium/mojo/services/native_viewport/DEPS b/chromium/mojo/services/native_viewport/DEPS new file mode 100644 index 00000000000..be8903cd0fd --- /dev/null +++ b/chromium/mojo/services/native_viewport/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+mojo/services/public/cpp/geometry", + "+mojo/services/public/cpp/input_events", + "+mojo/services/public/interfaces/native_viewport", + "+mojo/services/gles2", + "+ui/events", + "+ui/gfx", +] diff --git a/chromium/mojo/services/native_viewport/native_viewport.h b/chromium/mojo/services/native_viewport/native_viewport.h new file mode 100644 index 00000000000..7ec54329b15 --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport.h @@ -0,0 +1,62 @@ +// 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 MOJO_SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_H_ +#define MOJO_SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/size.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class Event; +} + +namespace mojo { +namespace shell { +class Context; +} + +namespace services { + +class NativeViewportDelegate { + public: + virtual ~NativeViewportDelegate() {} + + virtual void OnBoundsChanged(const gfx::Rect& size) = 0; + virtual void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) = 0; + virtual bool OnEvent(ui::Event* ui_event) = 0; + virtual void OnDestroyed() = 0; +}; + +// Encapsulation of platform-specific Viewport. +// TODO(abarth): Rename this class so that it doesn't conflict with the name of +// the service. +class NativeViewport { + public: + virtual ~NativeViewport() {} + + virtual void Init(const gfx::Rect& bounds) = 0; + virtual void Show() = 0; + virtual void Hide() = 0; + virtual void Close() = 0; + virtual gfx::Size GetSize() = 0; + virtual void SetBounds(const gfx::Rect& bounds) = 0; + + virtual void SetCapture() = 0; + virtual void ReleaseCapture() = 0; + + // |context| is NULL when loaded into separate process. + static scoped_ptr<NativeViewport> Create(shell::Context* context, + NativeViewportDelegate* delegate); +}; + +} // namespace services +} // namespace mojo + +#endif // MOJO_SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_H_ diff --git a/chromium/mojo/services/native_viewport/native_viewport_android.cc b/chromium/mojo/services/native_viewport/native_viewport_android.cc new file mode 100644 index 00000000000..1763f170ad3 --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_android.cc @@ -0,0 +1,161 @@ +// 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 "mojo/services/native_viewport/native_viewport_android.h" + +#include <android/input.h> +#include <android/native_window_jni.h> + +#include "base/android/jni_android.h" +#include "jni/NativeViewportAndroid_jni.h" +#include "mojo/shell/context.h" +#include "ui/events/event.h" +#include "ui/gfx/point.h" + +namespace mojo { +namespace services { + +ui::EventType MotionEventActionToEventType(jint action) { + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + return ui::ET_TOUCH_PRESSED; + case AMOTION_EVENT_ACTION_MOVE: + return ui::ET_TOUCH_MOVED; + case AMOTION_EVENT_ACTION_UP: + return ui::ET_TOUCH_RELEASED; + default: + NOTREACHED(); + } + return ui::ET_UNKNOWN; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeViewportAndroid, public: + +// static +bool NativeViewportAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +NativeViewportAndroid::NativeViewportAndroid(shell::Context* context, + NativeViewportDelegate* delegate) + : delegate_(delegate), + context_(context), + window_(NULL), + id_generator_(0), + weak_factory_(this) { +} + +NativeViewportAndroid::~NativeViewportAndroid() { + if (window_) + ReleaseWindow(); +} + +void NativeViewportAndroid::Destroy(JNIEnv* env, jobject obj) { + delegate_->OnDestroyed(); +} + +void NativeViewportAndroid::SurfaceCreated(JNIEnv* env, + jobject obj, + jobject jsurface) { + base::android::ScopedJavaLocalRef<jobject> protector(env, jsurface); + // Note: This ensures that any local references used by + // ANativeWindow_fromSurface are released immediately. This is needed as a + // workaround for https://code.google.com/p/android/issues/detail?id=68174 + { + base::android::ScopedJavaLocalFrame scoped_local_reference_frame(env); + window_ = ANativeWindow_fromSurface(env, jsurface); + } + delegate_->OnAcceleratedWidgetAvailable(window_); +} + +void NativeViewportAndroid::SurfaceDestroyed(JNIEnv* env, jobject obj) { + DCHECK(window_); + ReleaseWindow(); +} + +void NativeViewportAndroid::SurfaceSetSize(JNIEnv* env, jobject obj, + jint width, jint height) { + bounds_ = gfx::Rect(width, height); + delegate_->OnBoundsChanged(bounds_); +} + +bool NativeViewportAndroid::TouchEvent(JNIEnv* env, jobject obj, + jint pointer_id, + jint action, + jfloat x, jfloat y, + jlong time_ms) { + gfx::Point location(static_cast<int>(x), static_cast<int>(y)); + ui::TouchEvent event(MotionEventActionToEventType(action), location, + id_generator_.GetGeneratedID(pointer_id), + base::TimeDelta::FromMilliseconds(time_ms)); + // TODO(beng): handle multiple touch-points. + delegate_->OnEvent(&event); + if (action == ui::ET_TOUCH_RELEASED) + id_generator_.ReleaseNumber(pointer_id); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeViewportAndroid, NativeViewport implementation: + +void NativeViewportAndroid::Init(const gfx::Rect& bounds) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_NativeViewportAndroid_createForActivity(env, context_->activity(), + reinterpret_cast<jlong>(this)); +} + +void NativeViewportAndroid::Show() { + // Nothing to do. View is created visible. +} + +void NativeViewportAndroid::Hide() { + // Nothing to do. View is always visible. +} + +void NativeViewportAndroid::Close() { + // TODO(beng): close activity containing MojoView? + + // TODO(beng): perform this in response to view destruction. + delegate_->OnDestroyed(); +} + +gfx::Size NativeViewportAndroid::GetSize() { + return bounds_.size(); +} + +void NativeViewportAndroid::SetBounds(const gfx::Rect& bounds) { + NOTIMPLEMENTED(); +} + +void NativeViewportAndroid::SetCapture() { + NOTIMPLEMENTED(); +} + +void NativeViewportAndroid::ReleaseCapture() { + NOTIMPLEMENTED(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeViewportAndroid, private: + +void NativeViewportAndroid::ReleaseWindow() { + ANativeWindow_release(window_); + window_ = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeViewport, public: + +// static +scoped_ptr<NativeViewport> NativeViewport::Create( + shell::Context* context, + NativeViewportDelegate* delegate) { + return scoped_ptr<NativeViewport>( + new NativeViewportAndroid(context, delegate)).Pass(); +} + +} // namespace services +} // namespace mojo diff --git a/chromium/mojo/services/native_viewport/native_viewport_android.h b/chromium/mojo/services/native_viewport/native_viewport_android.h new file mode 100644 index 00000000000..c24e38e7858 --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_android.h @@ -0,0 +1,71 @@ +// 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 MOJO_SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_ANDROID_H_ +#define MOJO_SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_ANDROID_H_ + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "base/memory/weak_ptr.h" +#include "mojo/services/native_viewport/native_viewport.h" +#include "mojo/services/native_viewport/native_viewport_export.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/sequential_id_generator.h" +#include "ui/gfx/size.h" + +namespace gpu { +class GLInProcessContext; +} + +struct ANativeWindow; + +namespace mojo { +namespace services { + +class MOJO_NATIVE_VIEWPORT_EXPORT NativeViewportAndroid + : public NativeViewport { + public: + static MOJO_NATIVE_VIEWPORT_EXPORT bool Register(JNIEnv* env); + + explicit NativeViewportAndroid(shell::Context* context, + NativeViewportDelegate* delegate); + virtual ~NativeViewportAndroid(); + + void Destroy(JNIEnv* env, jobject obj); + void SurfaceCreated(JNIEnv* env, jobject obj, jobject jsurface); + void SurfaceDestroyed(JNIEnv* env, jobject obj); + void SurfaceSetSize(JNIEnv* env, jobject obj, jint width, jint height); + bool TouchEvent(JNIEnv* env, jobject obj, jint pointer_id, jint action, + jfloat x, jfloat y, jlong time_ms); + + private: + // Overridden from NativeViewport: + virtual void Init(const gfx::Rect& bounds) OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void Close() OVERRIDE; + virtual gfx::Size GetSize() OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + + void ReleaseWindow(); + + NativeViewportDelegate* delegate_; + shell::Context* context_; + ANativeWindow* window_; + gfx::Rect bounds_; + ui::SequentialIDGenerator id_generator_; + + base::WeakPtrFactory<NativeViewportAndroid> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportAndroid); +}; + + +} // namespace services +} // namespace mojo + +#endif // MOJO_SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_ANDROID_H_ diff --git a/chromium/mojo/services/native_viewport/native_viewport_export.h b/chromium/mojo/services/native_viewport/native_viewport_export.h new file mode 100644 index 00000000000..4870c566f9d --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_export.h @@ -0,0 +1,32 @@ +// 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 MOJO_SERVICES_NATIVE_VIEWPORT_EXPORT_H_ +#define MOJO_SERVICES_NATIVE_VIEWPORT_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_NATIVE_VIEWPORT_IMPLEMENTATION) +#define MOJO_NATIVE_VIEWPORT_EXPORT __declspec(dllexport) +#else +#define MOJO_NATIVE_VIEWPORT_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_NATIVE_VIEWPORT_IMPLEMENTATION) +#define MOJO_NATIVE_VIEWPORT_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_NATIVE_VIEWPORT_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_NATIVE_VIEWPORT_EXPORT +#endif + +#endif // MOJO_SERVICES_NATIVE_VIEWPORT_EXPORT_H_ diff --git a/chromium/mojo/services/native_viewport/native_viewport_mac.mm b/chromium/mojo/services/native_viewport/native_viewport_mac.mm new file mode 100644 index 00000000000..9d7301f6767 --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_mac.mm @@ -0,0 +1,88 @@ +// 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 "mojo/services/native_viewport/native_viewport.h" + +#import <AppKit/NSApplication.h> +#import <AppKit/NSView.h> +#import <AppKit/NSWindow.h> + +#include "base/bind.h" +#include "ui/gfx/rect.h" + +namespace mojo { +namespace services { + +class NativeViewportMac : public NativeViewport { + public: + NativeViewportMac(NativeViewportDelegate* delegate) + : delegate_(delegate), + window_(nil) { + } + + virtual ~NativeViewportMac() { + [window_ orderOut:nil]; + [window_ close]; + } + + private: + // Overridden from NativeViewport: + virtual void Init(const gfx::Rect& bounds) OVERRIDE { + [NSApplication sharedApplication]; + + rect_ = bounds; + window_ = [[NSWindow alloc] + initWithContentRect:NSRectFromCGRect(rect_.ToCGRect()) + styleMask:NSTitledWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + delegate_->OnAcceleratedWidgetAvailable([window_ contentView]); + delegate_->OnBoundsChanged(rect_); + } + + virtual void Show() OVERRIDE { + [window_ orderFront:nil]; + } + + virtual void Hide() OVERRIDE { + [window_ orderOut:nil]; + } + + virtual void Close() OVERRIDE { + // TODO(beng): perform this in response to NSWindow destruction. + delegate_->OnDestroyed(); + } + + virtual gfx::Size GetSize() OVERRIDE { + return rect_.size(); + } + + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual void SetCapture() OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual void ReleaseCapture() OVERRIDE { + NOTIMPLEMENTED(); + } + + NativeViewportDelegate* delegate_; + NSWindow* window_; + gfx::Rect rect_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportMac); +}; + +// static +scoped_ptr<NativeViewport> NativeViewport::Create( + shell::Context* context, + NativeViewportDelegate* delegate) { + return scoped_ptr<NativeViewport>(new NativeViewportMac(delegate)).Pass(); +} + +} // namespace services +} // namespace mojo diff --git a/chromium/mojo/services/native_viewport/native_viewport_service.cc b/chromium/mojo/services/native_viewport/native_viewport_service.cc new file mode 100644 index 00000000000..886c4dcb4b9 --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_service.cc @@ -0,0 +1,165 @@ +// 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 "mojo/services/native_viewport/native_viewport_service.h" + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/gles2/command_buffer_impl.h" +#include "mojo/services/native_viewport/native_viewport.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/public/cpp/input_events/input_events_type_converters.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ui/events/event.h" + +namespace mojo { +namespace services { +namespace { + +bool IsRateLimitedEventType(ui::Event* event) { + return event->type() == ui::ET_MOUSE_MOVED || + event->type() == ui::ET_MOUSE_DRAGGED || + event->type() == ui::ET_TOUCH_MOVED; +} + +} + +class NativeViewportImpl + : public InterfaceImpl<mojo::NativeViewport>, + public NativeViewportDelegate { + public: + NativeViewportImpl(shell::Context* context) + : context_(context), + widget_(gfx::kNullAcceleratedWidget), + waiting_for_event_ack_(false), + weak_factory_(this) {} + virtual ~NativeViewportImpl() { + // Destroy the NativeViewport early on as it may call us back during + // destruction and we want to be in a known state. + native_viewport_.reset(); + } + + virtual void Create(RectPtr bounds) OVERRIDE { + native_viewport_ = + services::NativeViewport::Create(context_, this); + native_viewport_->Init(bounds.To<gfx::Rect>()); + client()->OnCreated(); + OnBoundsChanged(bounds.To<gfx::Rect>()); + } + + virtual void Show() OVERRIDE { + native_viewport_->Show(); + } + + virtual void Hide() OVERRIDE { + native_viewport_->Hide(); + } + + virtual void Close() OVERRIDE { + command_buffer_.reset(); + DCHECK(native_viewport_); + native_viewport_->Close(); + } + + virtual void SetBounds(RectPtr bounds) OVERRIDE { + native_viewport_->SetBounds(bounds.To<gfx::Rect>()); + } + + virtual void CreateGLES2Context( + InterfaceRequest<CommandBuffer> command_buffer_request) OVERRIDE { + if (command_buffer_.get() || command_buffer_request_.is_pending()) { + LOG(ERROR) << "Can't create multiple contexts on a NativeViewport"; + return; + } + command_buffer_request_ = command_buffer_request.Pass(); + CreateCommandBufferIfNeeded(); + } + + void AckEvent() { + waiting_for_event_ack_ = false; + } + + void CreateCommandBufferIfNeeded() { + if (!command_buffer_request_.is_pending()) + return; + DCHECK(!command_buffer_.get()); + if (widget_ == gfx::kNullAcceleratedWidget) + return; + gfx::Size size = native_viewport_->GetSize(); + if (size.IsEmpty()) + return; + command_buffer_.reset( + new CommandBufferImpl(widget_, native_viewport_->GetSize())); + BindToRequest(command_buffer_.get(), &command_buffer_request_); + } + + virtual bool OnEvent(ui::Event* ui_event) OVERRIDE { + // Must not return early before updating capture. + switch (ui_event->type()) { + case ui::ET_MOUSE_PRESSED: + case ui::ET_TOUCH_PRESSED: + native_viewport_->SetCapture(); + break; + case ui::ET_MOUSE_RELEASED: + case ui::ET_TOUCH_RELEASED: + native_viewport_->ReleaseCapture(); + break; + default: + break; + } + + if (waiting_for_event_ack_ && IsRateLimitedEventType(ui_event)) + return false; + + client()->OnEvent( + TypeConverter<EventPtr, ui::Event>::ConvertFrom(*ui_event), + base::Bind(&NativeViewportImpl::AckEvent, + weak_factory_.GetWeakPtr())); + waiting_for_event_ack_ = true; + return false; + } + + virtual void OnAcceleratedWidgetAvailable( + gfx::AcceleratedWidget widget) OVERRIDE { + widget_ = widget; + CreateCommandBufferIfNeeded(); + } + + virtual void OnBoundsChanged(const gfx::Rect& bounds) OVERRIDE { + CreateCommandBufferIfNeeded(); + client()->OnBoundsChanged(Rect::From(bounds)); + } + + virtual void OnDestroyed() OVERRIDE { + command_buffer_.reset(); + client()->OnDestroyed(); + base::MessageLoop::current()->Quit(); + } + + private: + shell::Context* context_; + gfx::AcceleratedWidget widget_; + scoped_ptr<services::NativeViewport> native_viewport_; + InterfaceRequest<CommandBuffer> command_buffer_request_; + scoped_ptr<CommandBufferImpl> command_buffer_; + bool waiting_for_event_ack_; + base::WeakPtrFactory<NativeViewportImpl> weak_factory_; +}; + +} // namespace services +} // namespace mojo + + +MOJO_NATIVE_VIEWPORT_EXPORT mojo::Application* + CreateNativeViewportService( + mojo::shell::Context* context, + mojo::ScopedMessagePipeHandle service_provider_handle) { + mojo::Application* app = new mojo::Application( + service_provider_handle.Pass()); + app->AddService<mojo::services::NativeViewportImpl>(context); + return app; +} diff --git a/chromium/mojo/services/native_viewport/native_viewport_service.h b/chromium/mojo/services/native_viewport/native_viewport_service.h new file mode 100644 index 00000000000..99b455c9bae --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_service.h @@ -0,0 +1,18 @@ +// 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 MOJO_SERVICES_NATIVE_VIEWPORT_SERVICE_H_ +#define MOJO_SERVICES_NATIVE_VIEWPORT_SERVICE_H_ + +#include "base/memory/scoped_vector.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/native_viewport/native_viewport_export.h" +#include "mojo/shell/context.h" + +MOJO_NATIVE_VIEWPORT_EXPORT mojo::Application* + CreateNativeViewportService( + mojo::shell::Context* context, + mojo::ScopedMessagePipeHandle service_provider_handle); + +#endif // MOJO_SERVICES_NATIVE_VIEWPORT_SERVICE_H_ diff --git a/chromium/mojo/services/native_viewport/native_viewport_stub.cc b/chromium/mojo/services/native_viewport/native_viewport_stub.cc new file mode 100644 index 00000000000..9dcb33a8806 --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_stub.cc @@ -0,0 +1,50 @@ +// 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 "mojo/services/native_viewport/native_viewport.h" + +// Stub to build on platforms we don't fully support yet. + +namespace mojo { +namespace services { + +class NativeViewportStub : public NativeViewport { + public: + NativeViewportStub(NativeViewportDelegate* delegate) + : delegate_(delegate) { + } + virtual ~NativeViewportStub() { + } + + private: + // Overridden from NativeViewport: + virtual void Init() OVERRIDE { + } + virtual void Show() OVERRIDE { + } + virtual void Hide() OVERRIDE { + } + virtual void Close() OVERRIDE { + delegate_->OnDestroyed(); + } + virtual gfx::Size GetSize() OVERRIDE { + return gfx::Size(); + } + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE { + } + + NativeViewportDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportStub); +}; + +// static +scoped_ptr<NativeViewport> NativeViewport::Create( + shell::Context* context, + NativeViewportDelegate* delegate) { + return scoped_ptr<NativeViewport>(new NativeViewportStub(delegate)).Pass(); +} + +} // namespace services +} // namespace mojo diff --git a/chromium/mojo/services/native_viewport/native_viewport_win.cc b/chromium/mojo/services/native_viewport/native_viewport_win.cc new file mode 100644 index 00000000000..52ff6cb6f0a --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_win.cc @@ -0,0 +1,167 @@ +// 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 "mojo/services/native_viewport/native_viewport.h" + +#include "ui/events/event.h" +#include "ui/gfx/win/msg_util.h" +#include "ui/gfx/win/window_impl.h" + +namespace mojo { +namespace services { +namespace { + +gfx::Rect GetWindowBoundsForClientBounds(DWORD style, DWORD ex_style, + const gfx::Rect& bounds) { + RECT wr; + wr.left = bounds.x(); + wr.top = bounds.y(); + wr.right = bounds.x() + bounds.width(); + wr.bottom = bounds.y() + bounds.height(); + AdjustWindowRectEx(&wr, style, FALSE, ex_style); + + // Make sure to keep the window onscreen, as AdjustWindowRectEx() may have + // moved part of it offscreen. + gfx::Rect window_bounds(wr.left, wr.top, + wr.right - wr.left, wr.bottom - wr.top); + window_bounds.set_x(std::max(0, window_bounds.x())); + window_bounds.set_y(std::max(0, window_bounds.y())); + return window_bounds; +} + +} + +class NativeViewportWin : public gfx::WindowImpl, + public NativeViewport { + public: + explicit NativeViewportWin(NativeViewportDelegate* delegate) + : delegate_(delegate) { + } + virtual ~NativeViewportWin() { + if (IsWindow(hwnd())) + DestroyWindow(hwnd()); + } + + private: + // Overridden from NativeViewport: + virtual void Init(const gfx::Rect& bounds) OVERRIDE { + gfx::Rect window_bounds = GetWindowBoundsForClientBounds( + WS_OVERLAPPEDWINDOW, window_ex_style(), bounds); + gfx::WindowImpl::Init(NULL, window_bounds); + SetWindowText(hwnd(), L"native_viewport::NativeViewportWin!"); + } + + virtual void Show() OVERRIDE { + ShowWindow(hwnd(), SW_SHOWNORMAL); + } + + virtual void Hide() OVERRIDE { + ShowWindow(hwnd(), SW_HIDE); + } + + virtual void Close() OVERRIDE { + DestroyWindow(hwnd()); + } + + virtual gfx::Size GetSize() OVERRIDE { + RECT cr; + GetClientRect(hwnd(), &cr); + return gfx::Size(cr.right - cr.left, cr.bottom - cr.top); + } + + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE { + gfx::Rect window_bounds = GetWindowBoundsForClientBounds( + GetWindowLong(hwnd(), GWL_STYLE), + GetWindowLong(hwnd(), GWL_EXSTYLE), + bounds); + SetWindowPos(hwnd(), NULL, window_bounds.x(), window_bounds.y(), + window_bounds.width(), window_bounds.height(), + SWP_NOREPOSITION); + } + + virtual void SetCapture() OVERRIDE { + DCHECK(::GetCapture() != hwnd()); + ::SetCapture(hwnd()); + } + + virtual void ReleaseCapture() OVERRIDE { + if (::GetCapture() == hwnd()) + ::ReleaseCapture(); + } + + CR_BEGIN_MSG_MAP_EX(NativeViewportWin) + CR_MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) + + CR_MESSAGE_HANDLER_EX(WM_KEYDOWN, OnKeyEvent) + CR_MESSAGE_HANDLER_EX(WM_KEYUP, OnKeyEvent) + CR_MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyEvent) + CR_MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyEvent) + CR_MESSAGE_HANDLER_EX(WM_CHAR, OnKeyEvent) + CR_MESSAGE_HANDLER_EX(WM_SYSCHAR, OnKeyEvent) + CR_MESSAGE_HANDLER_EX(WM_IME_CHAR, OnKeyEvent) + + CR_MSG_WM_CREATE(OnCreate) + CR_MSG_WM_DESTROY(OnDestroy) + CR_MSG_WM_PAINT(OnPaint) + CR_MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged) + CR_END_MSG_MAP() + + LRESULT OnMouseRange(UINT message, WPARAM w_param, LPARAM l_param) { + MSG msg = { hwnd(), message, w_param, l_param, 0, + { CR_GET_X_LPARAM(l_param), CR_GET_Y_LPARAM(l_param) } }; + ui::MouseEvent event(msg); + SetMsgHandled(delegate_->OnEvent(&event)); + return 0; + } + LRESULT OnKeyEvent(UINT message, WPARAM w_param, LPARAM l_param) { + MSG msg = { hwnd(), message, w_param, l_param }; + ui::KeyEvent event(msg, message == WM_CHAR); + SetMsgHandled(delegate_->OnEvent(&event)); + return 0; + } + LRESULT OnCreate(CREATESTRUCT* create_struct) { + delegate_->OnAcceleratedWidgetAvailable(hwnd()); + return 0; + } + void OnDestroy() { + delegate_->OnDestroyed(); + } + void OnPaint(HDC) { + RECT cr; + GetClientRect(hwnd(), &cr); + + PAINTSTRUCT ps; + HDC dc = BeginPaint(hwnd(), &ps); + HBRUSH red_brush = CreateSolidBrush(RGB(255, 0, 0)); + HGDIOBJ old_object = SelectObject(dc, red_brush); + Rectangle(dc, cr.left, cr.top, cr.right, cr.bottom); + SelectObject(dc, old_object); + DeleteObject(red_brush); + EndPaint(hwnd(), &ps); + } + void OnWindowPosChanged(WINDOWPOS* window_pos) { + if (!(window_pos->flags & SWP_NOSIZE) || + !(window_pos->flags & SWP_NOMOVE)) { + RECT cr; + GetClientRect(hwnd(), &cr); + delegate_->OnBoundsChanged( + gfx::Rect(window_pos->x, window_pos->y, + cr.right - cr.left, cr.bottom - cr.top)); + } + } + + NativeViewportDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportWin); +}; + +// static +scoped_ptr<NativeViewport> NativeViewport::Create( + shell::Context* context, + NativeViewportDelegate* delegate) { + return scoped_ptr<NativeViewport>(new NativeViewportWin(delegate)).Pass(); +} + +} // namespace services +} // namespace mojo diff --git a/chromium/mojo/services/native_viewport/native_viewport_x11.cc b/chromium/mojo/services/native_viewport/native_viewport_x11.cc new file mode 100644 index 00000000000..f1b4863529c --- /dev/null +++ b/chromium/mojo/services/native_viewport/native_viewport_x11.cc @@ -0,0 +1,163 @@ +// 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 "mojo/services/native_viewport/native_viewport.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "base/message_loop/message_loop.h" +#include "ui/events/event.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/events/platform/x11/x11_event_source.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/x/x11_types.h" + +namespace mojo { +namespace services { + +class NativeViewportX11 : public NativeViewport, + public ui::PlatformEventDispatcher { + public: + NativeViewportX11(NativeViewportDelegate* delegate) + : delegate_(delegate) { + } + + virtual ~NativeViewportX11() { + event_source_->RemovePlatformEventDispatcher(this); + + XDestroyWindow(gfx::GetXDisplay(), window_); + } + + private: + // Overridden from NativeViewport: + virtual void Init(const gfx::Rect& bounds) OVERRIDE { + XDisplay* display = gfx::GetXDisplay(); + + XSetWindowAttributes swa; + memset(&swa, 0, sizeof(swa)); + swa.override_redirect = False; + + bounds_ = bounds; + window_ = XCreateWindow( + display, + DefaultRootWindow(display), + bounds_.x(), bounds_.y(), bounds_.width(), bounds_.height(), + 0, // border width + CopyFromParent, // depth + InputOutput, + CopyFromParent, // visual + CWBackPixmap | CWOverrideRedirect, + &swa); + + atom_wm_protocols_ = XInternAtom(display, "WM_PROTOCOLS", 1); + atom_wm_delete_window_ = XInternAtom(display, "WM_DELETE_WINDOW", 1); + XSetWMProtocols(display, window_, &atom_wm_delete_window_, 1); + + event_source_ = ui::PlatformEventSource::CreateDefault(); + event_source_->AddPlatformEventDispatcher(this); + + long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | + KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask | + ExposureMask | VisibilityChangeMask | StructureNotifyMask | + PropertyChangeMask | PointerMotionMask; + XSelectInput(display, window_, event_mask); + + // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with + // the desktop environment. + XSetWMProperties(display, window_, NULL, NULL, NULL, 0, NULL, NULL, NULL); + + // TODO(aa): Setup xinput2 events. + // See desktop_aura/desktop_window_tree_host_x11.cc. + + delegate_->OnAcceleratedWidgetAvailable(window_); + } + + virtual void Show() OVERRIDE { + XDisplay* display = gfx::GetXDisplay(); + XMapWindow(display, window_); + static_cast<ui::X11EventSource*>( + event_source_.get())->BlockUntilWindowMapped(window_); + XFlush(display); + } + + virtual void Hide() OVERRIDE { + XWithdrawWindow(gfx::GetXDisplay(), window_, 0); + } + + virtual void Close() OVERRIDE { + // TODO(beng): perform this in response to XWindow destruction. + delegate_->OnDestroyed(); + } + + virtual gfx::Size GetSize() OVERRIDE { + return bounds_.size(); + } + + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual void SetCapture() OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual void ReleaseCapture() OVERRIDE { + NOTIMPLEMENTED(); + } + + // ui::PlatformEventDispatcher: + virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE { + // TODO(aa): This is going to have to be thought through more carefully. + // Which events are appropriate to pass to clients? + switch (event->type) { + case KeyPress: + case KeyRelease: + case ButtonPress: + case ButtonRelease: + case MotionNotify: + return true; + case ClientMessage: + return event->xclient.message_type == atom_wm_protocols_; + default: + return false; + } + } + + virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE { + if (event->type == ClientMessage) { + Atom protocol = static_cast<Atom>(event->xclient.data.l[0]); + if (protocol == atom_wm_delete_window_) + delegate_->OnDestroyed(); + } else if (event->type == KeyPress || event->type == KeyRelease) { + ui::KeyEvent key_event(event, false); + delegate_->OnEvent(&key_event); + } else if (event->type == ButtonPress || event->type == ButtonRelease || + event->type == MotionNotify) { + ui::MouseEvent mouse_event(event); + delegate_->OnEvent(&mouse_event); + } + return ui::POST_DISPATCH_NONE; + } + + scoped_ptr<ui::PlatformEventSource> event_source_; + NativeViewportDelegate* delegate_; + gfx::Rect bounds_; + XID window_; + Atom atom_wm_protocols_; + Atom atom_wm_delete_window_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportX11); +}; + +// static +scoped_ptr<NativeViewport> NativeViewport::Create( + shell::Context* context, + NativeViewportDelegate* delegate) { + return scoped_ptr<NativeViewport>(new NativeViewportX11(delegate)).Pass(); +} + +} // namespace services +} // namespace mojo diff --git a/chromium/mojo/services/network/DEPS b/chromium/mojo/services/network/DEPS new file mode 100644 index 00000000000..dc0a052ff7a --- /dev/null +++ b/chromium/mojo/services/network/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+mojo/services", + "+net", +] diff --git a/chromium/mojo/services/network/main.cc b/chromium/mojo/services/network/main.cc new file mode 100644 index 00000000000..1b69638a038 --- /dev/null +++ b/chromium/mojo/services/network/main.cc @@ -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. + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/network/network_context.h" +#include "mojo/services/network/network_service_impl.h" + +extern "C" APPLICATION_EXPORT MojoResult CDECL MojoMain( + MojoHandle service_provider_handle) { + base::CommandLine::Init(0, NULL); + base::AtExitManager at_exit; + + // The IO message loop allows us to use net::URLRequest on this thread. + base::MessageLoopForIO loop; + + mojo::NetworkContext context; + + mojo::Application app; + app.BindServiceProvider( + mojo::MakeScopedHandle(mojo::MessagePipeHandle(service_provider_handle))); + + app.AddService<mojo::NetworkServiceImpl>(&context); + + loop.Run(); + return MOJO_RESULT_OK; +} diff --git a/chromium/mojo/services/network/network_context.cc b/chromium/mojo/services/network/network_context.cc new file mode 100644 index 00000000000..099432ae9f2 --- /dev/null +++ b/chromium/mojo/services/network/network_context.cc @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/network/network_context.h" + +#include "base/base_paths.h" +#include "base/path_service.h" +#include "net/cert/cert_verifier.h" +#include "net/cookies/cookie_monster.h" +#include "net/http/http_cache.h" +#include "net/http/http_network_session.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/transport_security_persister.h" +#include "net/http/transport_security_state.h" +#include "net/proxy/proxy_service.h" +#include "net/ssl/default_server_bound_cert_store.h" +#include "net/ssl/server_bound_cert_service.h" +#include "net/ssl/ssl_config_service_defaults.h" +#include "net/url_request/file_protocol_handler.h" +#include "net/url_request/static_http_user_agent_settings.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_storage.h" +#include "net/url_request/url_request_job_factory_impl.h" + +namespace mojo { + +NetworkContext::NetworkContext() + : file_thread_("network_file_thread"), + cache_thread_("network_cache_thread") { + file_thread_.Start(); + + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + cache_thread_.StartWithOptions(options); + + // TODO(darin): Need to figure out a better base path, obviously. + base::FilePath base_path; + PathService::Get(base::DIR_TEMP, &base_path); + base_path = base_path.Append(FILE_PATH_LITERAL("network_service")); + + url_request_context_.reset(new net::URLRequestContext()); + url_request_context_->set_net_log(net_log_.get()); + + storage_.reset( + new net::URLRequestContextStorage(url_request_context_.get())); + + storage_->set_cookie_store(new net::CookieMonster(NULL, NULL)); + + // TODO(darin): This is surely the wrong UA string. + storage_->set_http_user_agent_settings( + new net::StaticHttpUserAgentSettings("en-us,en", "Mojo/0.1")); + + storage_->set_proxy_service(net::ProxyService::CreateDirect()); + storage_->set_ssl_config_service(new net::SSLConfigServiceDefaults); + storage_->set_cert_verifier(net::CertVerifier::CreateDefault()); + + net::TransportSecurityState* transport_security_state = + new net::TransportSecurityState(); + storage_->set_transport_security_state(transport_security_state); + + transport_security_persister_.reset( + new net::TransportSecurityPersister( + transport_security_state, + base_path, + file_thread_.message_loop_proxy(), + false)); + + storage_->set_server_bound_cert_service( + new net::ServerBoundCertService( + new net::DefaultServerBoundCertStore(NULL), + file_thread_.message_loop_proxy())); + storage_->set_http_server_properties( + scoped_ptr<net::HttpServerProperties>( + new net::HttpServerPropertiesImpl())); + storage_->set_host_resolver(net::HostResolver::CreateDefaultResolver( + url_request_context_->net_log())); + + net::HttpNetworkSession::Params network_session_params; + network_session_params.cert_verifier = + url_request_context_->cert_verifier(); + network_session_params.transport_security_state = + url_request_context_->transport_security_state(); + network_session_params.server_bound_cert_service = + url_request_context_->server_bound_cert_service(); + network_session_params.net_log = + url_request_context_->net_log(); + network_session_params.proxy_service = + url_request_context_->proxy_service(); + network_session_params.ssl_config_service = + url_request_context_->ssl_config_service(); + network_session_params.http_server_properties = + url_request_context_->http_server_properties(); + network_session_params.host_resolver = + url_request_context_->host_resolver(); + + base::FilePath cache_path = base_path.Append(FILE_PATH_LITERAL("Cache")); + + net::HttpCache::DefaultBackend* main_backend = + new net::HttpCache::DefaultBackend( + net::DISK_CACHE, + net::CACHE_BACKEND_DEFAULT, + cache_path, + 0, + cache_thread_.message_loop_proxy()); + + net::HttpCache* main_cache = new net::HttpCache( + network_session_params, main_backend); + storage_->set_http_transaction_factory(main_cache); + + scoped_ptr<net::URLRequestJobFactoryImpl> job_factory( + new net::URLRequestJobFactoryImpl()); + job_factory->SetProtocolHandler( + "file", + new net::FileProtocolHandler(file_thread_.message_loop_proxy())); + storage_->set_job_factory(job_factory.release()); +} + +NetworkContext::~NetworkContext() { + // TODO(darin): Be careful about destruction order of member variables? +} + +} // namespace mojo diff --git a/chromium/mojo/services/network/network_context.h b/chromium/mojo/services/network/network_context.h new file mode 100644 index 00000000000..b156c081213 --- /dev/null +++ b/chromium/mojo/services/network/network_context.h @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_NETWORK_NETWORK_CONTEXT_H_ +#define MOJO_SERVICES_NETWORK_NETWORK_CONTEXT_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" + +namespace net { +class NetLog; +class URLRequestContext; +class URLRequestContextStorage; +class TransportSecurityPersister; +} + +namespace mojo { + +class NetworkContext { + public: + NetworkContext(); + ~NetworkContext(); + + net::URLRequestContext* url_request_context() { + return url_request_context_.get(); + } + + private: + base::Thread file_thread_; + base::Thread cache_thread_; + scoped_ptr<net::NetLog> net_log_; + scoped_ptr<net::URLRequestContextStorage> storage_; + scoped_ptr<net::URLRequestContext> url_request_context_; + scoped_ptr<net::TransportSecurityPersister> transport_security_persister_; + + DISALLOW_COPY_AND_ASSIGN(NetworkContext); +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_NETWORK_NETWORK_CONTEXT_H_ diff --git a/chromium/mojo/services/network/network_service_impl.cc b/chromium/mojo/services/network/network_service_impl.cc new file mode 100644 index 00000000000..a216738ad46 --- /dev/null +++ b/chromium/mojo/services/network/network_service_impl.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/network/network_service_impl.h" + +#include "mojo/services/network/url_loader_impl.h" + +namespace mojo { + +NetworkServiceImpl::NetworkServiceImpl(NetworkContext* context) + : context_(context) { +} + +NetworkServiceImpl::~NetworkServiceImpl() { +} + +void NetworkServiceImpl::CreateURLLoader( + InterfaceRequest<URLLoader> loader) { + BindToRequest(new URLLoaderImpl(context_), &loader); +} + +} // namespace mojo diff --git a/chromium/mojo/services/network/network_service_impl.h b/chromium/mojo/services/network/network_service_impl.h new file mode 100644 index 00000000000..32d1713cb12 --- /dev/null +++ b/chromium/mojo/services/network/network_service_impl.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_NETWORK_NETWORK_SERVICE_IMPL_H_ +#define MOJO_SERVICES_NETWORK_NETWORK_SERVICE_IMPL_H_ + +#include "base/compiler_specific.h" +#include "mojo/public/cpp/bindings/interface_impl.h" +#include "mojo/services/public/interfaces/network/network_service.mojom.h" + +namespace mojo { +class NetworkContext; + +class NetworkServiceImpl : public InterfaceImpl<NetworkService> { + public: + explicit NetworkServiceImpl(NetworkContext* context); + virtual ~NetworkServiceImpl(); + + // NetworkService methods: + virtual void CreateURLLoader(InterfaceRequest<URLLoader> loader) OVERRIDE; + + private: + NetworkContext* context_; +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_NETWORK_NETWORK_SERVICE_IMPL_H_ diff --git a/chromium/mojo/services/network/url_loader_impl.cc b/chromium/mojo/services/network/url_loader_impl.cc new file mode 100644 index 00000000000..9b0cce9d525 --- /dev/null +++ b/chromium/mojo/services/network/url_loader_impl.cc @@ -0,0 +1,253 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/network/url_loader_impl.h" + +#include "mojo/common/common_type_converters.h" +#include "mojo/services/network/network_context.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "net/http/http_response_headers.h" + +namespace mojo { +namespace { + +const uint32_t kMaxReadSize = 64 * 1024; + +// Generates an URLResponsePtr from the response state of a net::URLRequest. +URLResponsePtr MakeURLResponse(const net::URLRequest* url_request) { + URLResponsePtr response(URLResponse::New()); + response->url = url_request->url().spec(); + + const net::HttpResponseHeaders* headers = url_request->response_headers(); + if (headers) { + response->status_code = headers->response_code(); + response->status_line = headers->GetStatusLine(); + + std::vector<String> header_lines; + void* iter = NULL; + std::string name, value; + while (headers->EnumerateHeaderLines(&iter, &name, &value)) + header_lines.push_back(name + ": " + value); + if (!header_lines.empty()) + response->headers.Swap(&header_lines); + } + + return response.Pass(); +} + +} // namespace + +// Keeps track of a pending two-phase write on a DataPipeProducerHandle. +class URLLoaderImpl::PendingWriteToDataPipe : + public base::RefCountedThreadSafe<PendingWriteToDataPipe> { + public: + explicit PendingWriteToDataPipe(ScopedDataPipeProducerHandle handle) + : handle_(handle.Pass()), + buffer_(NULL) { + } + + bool BeginWrite(uint32_t* num_bytes) { + MojoResult result = BeginWriteDataRaw(handle_.get(), &buffer_, num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (*num_bytes > kMaxReadSize) + *num_bytes = kMaxReadSize; + + return result == MOJO_RESULT_OK; + } + + ScopedDataPipeProducerHandle Complete(uint32_t num_bytes) { + EndWriteDataRaw(handle_.get(), num_bytes); + buffer_ = NULL; + return handle_.Pass(); + } + + char* buffer() { return static_cast<char*>(buffer_); } + + private: + friend class base::RefCountedThreadSafe<PendingWriteToDataPipe>; + + ~PendingWriteToDataPipe() { + if (handle_.is_valid()) + EndWriteDataRaw(handle_.get(), 0); + } + + ScopedDataPipeProducerHandle handle_; + void* buffer_; + + DISALLOW_COPY_AND_ASSIGN(PendingWriteToDataPipe); +}; + +// Takes ownership of a pending two-phase write on a DataPipeProducerHandle, +// and makes its buffer available as a net::IOBuffer. +class URLLoaderImpl::DependentIOBuffer : public net::WrappedIOBuffer { + public: + DependentIOBuffer(PendingWriteToDataPipe* pending_write) + : net::WrappedIOBuffer(pending_write->buffer()), + pending_write_(pending_write) { + } + private: + virtual ~DependentIOBuffer() {} + scoped_refptr<PendingWriteToDataPipe> pending_write_; +}; + +URLLoaderImpl::URLLoaderImpl(NetworkContext* context) + : context_(context), + auto_follow_redirects_(true), + weak_ptr_factory_(this) { +} + +URLLoaderImpl::~URLLoaderImpl() { +} + +void URLLoaderImpl::OnConnectionError() { + delete this; +} + +void URLLoaderImpl::Start(URLRequestPtr request, + ScopedDataPipeProducerHandle response_body_stream) { + // Do not allow starting another request. + if (url_request_) { + SendError(net::ERR_UNEXPECTED); + url_request_.reset(); + response_body_stream_.reset(); + return; + } + + if (!request) { + SendError(net::ERR_INVALID_ARGUMENT); + return; + } + + response_body_stream_ = response_body_stream.Pass(); + + GURL url(request->url); + url_request_.reset( + new net::URLRequest(url, + net::DEFAULT_PRIORITY, + this, + context_->url_request_context())); + url_request_->set_method(request->method); + if (request->headers) { + net::HttpRequestHeaders headers; + for (size_t i = 0; i < request->headers.size(); ++i) + headers.AddHeaderFromString(request->headers[i].To<base::StringPiece>()); + url_request_->SetExtraRequestHeaders(headers); + } + if (request->bypass_cache) + url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE); + // TODO(darin): Handle request body. + + auto_follow_redirects_ = request->auto_follow_redirects; + + url_request_->Start(); +} + +void URLLoaderImpl::FollowRedirect() { + if (auto_follow_redirects_) { + DLOG(ERROR) << "Spurious call to FollowRedirect"; + } else { + if (url_request_) + url_request_->FollowDeferredRedirect(); + } +} + +void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* url_request, + const GURL& new_url, + bool* defer_redirect) { + DCHECK(url_request == url_request_.get()); + DCHECK(url_request->status().is_success()); + + URLResponsePtr response = MakeURLResponse(url_request); + std::string redirect_method = + net::URLRequest::ComputeMethodForRedirect(url_request->method(), + response->status_code); + client()->OnReceivedRedirect( + response.Pass(), new_url.spec(), redirect_method); + + *defer_redirect = !auto_follow_redirects_; +} + +void URLLoaderImpl::OnResponseStarted(net::URLRequest* url_request) { + DCHECK(url_request == url_request_.get()); + + if (!url_request->status().is_success()) { + SendError(url_request->status().error()); + return; + } + + client()->OnReceivedResponse(MakeURLResponse(url_request)); + + // Start reading... + ReadMore(); +} + +void URLLoaderImpl::OnReadCompleted(net::URLRequest* url_request, + int bytes_read) { + if (url_request->status().is_success()) { + DidRead(static_cast<uint32_t>(bytes_read), false); + } else { + pending_write_ = NULL; // This closes the data pipe. + // TODO(darin): Perhaps we should communicate this error to our client. + } +} + +void URLLoaderImpl::SendError(int error_code) { + NetworkErrorPtr error(NetworkError::New()); + error->code = error_code; + error->description = net::ErrorToString(error_code); + client()->OnReceivedError(error.Pass()); +} + +void URLLoaderImpl::ReadMore() { + DCHECK(!pending_write_); + + pending_write_ = new PendingWriteToDataPipe(response_body_stream_.Pass()); + + uint32_t num_bytes; + if (!pending_write_->BeginWrite(&num_bytes)) + CHECK(false); // Oops! TODO(darin): crbug/386877: The pipe might be full! + if (num_bytes > static_cast<uint32_t>(std::numeric_limits<int>::max())) + CHECK(false); // Oops! + + scoped_refptr<net::IOBuffer> buf = new DependentIOBuffer(pending_write_); + + int bytes_read; + url_request_->Read(buf, static_cast<int>(num_bytes), &bytes_read); + + // Drop our reference to the buffer. + buf = NULL; + + if (url_request_->status().is_io_pending()) { + // Wait for OnReadCompleted. + } else if (url_request_->status().is_success() && bytes_read > 0) { + DidRead(static_cast<uint32_t>(bytes_read), true); + } else { + pending_write_->Complete(0); + pending_write_ = NULL; // This closes the data pipe. + if (bytes_read == 0) { + client()->OnReceivedEndOfResponseBody(); + } else { + DCHECK(!url_request_->status().is_success()); + SendError(url_request_->status().error()); + } + } +} + +void URLLoaderImpl::DidRead(uint32_t num_bytes, bool completed_synchronously) { + DCHECK(url_request_->status().is_success()); + + response_body_stream_ = pending_write_->Complete(num_bytes); + pending_write_ = NULL; + + if (completed_synchronously) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&URLLoaderImpl::ReadMore, weak_ptr_factory_.GetWeakPtr())); + } else { + ReadMore(); + } +} + +} // namespace mojo diff --git a/chromium/mojo/services/network/url_loader_impl.h b/chromium/mojo/services/network/url_loader_impl.h new file mode 100644 index 00000000000..a76a9669738 --- /dev/null +++ b/chromium/mojo/services/network/url_loader_impl.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_NETWORK_URL_LOADER_IMPL_H_ +#define MOJO_SERVICES_NETWORK_URL_LOADER_IMPL_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/interface_impl.h" +#include "mojo/services/public/interfaces/network/url_loader.mojom.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request.h" + +namespace mojo { +class NetworkContext; + +class URLLoaderImpl : public InterfaceImpl<URLLoader>, + public net::URLRequest::Delegate { + public: + explicit URLLoaderImpl(NetworkContext* context); + virtual ~URLLoaderImpl(); + + private: + class PendingWriteToDataPipe; + class DependentIOBuffer; + + // InterfaceImpl<> methods: + virtual void OnConnectionError() OVERRIDE; + + // URLLoader methods: + virtual void Start( + URLRequestPtr request, + ScopedDataPipeProducerHandle response_body_stream) OVERRIDE; + virtual void FollowRedirect() OVERRIDE; + + // net::URLRequest::Delegate methods: + virtual void OnReceivedRedirect(net::URLRequest* url_request, + const GURL& new_url, + bool* defer_redirect) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* url_request) OVERRIDE; + virtual void OnReadCompleted(net::URLRequest* url_request, int bytes_read) + OVERRIDE; + + void SendError(int error); + void ReadMore(); + void DidRead(uint32_t num_bytes, bool completed_synchronously); + + NetworkContext* context_; + scoped_ptr<net::URLRequest> url_request_; + ScopedDataPipeProducerHandle response_body_stream_; + scoped_refptr<PendingWriteToDataPipe> pending_write_; + bool auto_follow_redirects_; + + base::WeakPtrFactory<URLLoaderImpl> weak_ptr_factory_; +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_NETWORK_URL_LOADER_IMPL_H_ diff --git a/chromium/mojo/services/public/cpp/geometry/DEPS b/chromium/mojo/services/public/cpp/geometry/DEPS new file mode 100644 index 00000000000..194be1c46b7 --- /dev/null +++ b/chromium/mojo/services/public/cpp/geometry/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/gfx/geometry", +] diff --git a/chromium/mojo/services/public/cpp/geometry/geometry_type_converters.h b/chromium/mojo/services/public/cpp/geometry/geometry_type_converters.h new file mode 100644 index 00000000000..3c09f6707be --- /dev/null +++ b/chromium/mojo/services/public/cpp/geometry/geometry_type_converters.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_GEOMETRY_TYPE_CONVERTERS_H_ +#define MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_GEOMETRY_TYPE_CONVERTERS_H_ + +#include "mojo/services/public/cpp/geometry/mojo_geometry_export.h" +#include "mojo/services/public/interfaces/geometry/geometry.mojom.h" +#include "ui/gfx/geometry/rect.h" + +namespace mojo { + +template<> +class MOJO_GEOMETRY_EXPORT TypeConverter<PointPtr, gfx::Point> { + public: + static PointPtr ConvertFrom(const gfx::Point& input); + static gfx::Point ConvertTo(const PointPtr& input); +}; + +template<> +class MOJO_GEOMETRY_EXPORT TypeConverter<SizePtr, gfx::Size> { + public: + static SizePtr ConvertFrom(const gfx::Size& input); + static gfx::Size ConvertTo(const SizePtr& input); +}; + +template<> +class MOJO_GEOMETRY_EXPORT TypeConverter<RectPtr, gfx::Rect> { + public: + static RectPtr ConvertFrom(const gfx::Rect& input); + static gfx::Rect ConvertTo(const RectPtr& input); +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_GEOMETRY_TYPE_CONVERTERS_H_ diff --git a/chromium/mojo/services/public/cpp/geometry/lib/geometry_type_converters.cc b/chromium/mojo/services/public/cpp/geometry/lib/geometry_type_converters.cc new file mode 100644 index 00000000000..6d942376ac9 --- /dev/null +++ b/chromium/mojo/services/public/cpp/geometry/lib/geometry_type_converters.cc @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" + +namespace mojo { + +// static +PointPtr TypeConverter<PointPtr, gfx::Point>::ConvertFrom( + const gfx::Point& input) { + PointPtr point(Point::New()); + point->x = input.x(); + point->y = input.y(); + return point.Pass(); +} + +// static +gfx::Point TypeConverter<PointPtr, gfx::Point>::ConvertTo( + const PointPtr& input) { + if (!input) + return gfx::Point(); + return gfx::Point(input->x, input->y); +} + +// static +SizePtr TypeConverter<SizePtr, gfx::Size>::ConvertFrom(const gfx::Size& input) { + SizePtr size(Size::New()); + size->width = input.width(); + size->height = input.height(); + return size.Pass(); +} + +// static +gfx::Size TypeConverter<SizePtr, gfx::Size>::ConvertTo(const SizePtr& input) { + if (!input) + return gfx::Size(); + return gfx::Size(input->width, input->height); +} + +// static +RectPtr TypeConverter<RectPtr, gfx::Rect>::ConvertFrom(const gfx::Rect& input) { + RectPtr rect(Rect::New()); + rect->x = input.x(); + rect->y = input.y(); + rect->width = input.width(); + rect->height = input.height(); + return rect.Pass(); +} + +// static +gfx::Rect TypeConverter<RectPtr, gfx::Rect>::ConvertTo(const RectPtr& input) { + if (!input) + return gfx::Rect(); + return gfx::Rect(input->x, input->y, input->width, input->height); +} + +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/geometry/mojo_geometry_export.h b/chromium/mojo/services/public/cpp/geometry/mojo_geometry_export.h new file mode 100644 index 00000000000..f21aebce133 --- /dev/null +++ b/chromium/mojo/services/public/cpp/geometry/mojo_geometry_export.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_MOJO_GEOMETRY_EXPORT_H_ +#define MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_MOJO_GEOMETRY_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_GEOMETRY_IMPLEMENTATION) +#define MOJO_GEOMETRY_EXPORT __declspec(dllexport) +#else +#define MOJO_GEOMETRY_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_GEOMETRY_IMPLEMENTATION) +#define MOJO_GEOMETRY_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_GEOMETRY_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_GEOMETRY_EXPORT +#endif + +#endif // MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_MOJO_GEOMETRY_EXPORT_H_ diff --git a/chromium/mojo/services/public/cpp/input_events/DEPS b/chromium/mojo/services/public/cpp/input_events/DEPS new file mode 100644 index 00000000000..fe1d98e366d --- /dev/null +++ b/chromium/mojo/services/public/cpp/input_events/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/events", +] diff --git a/chromium/mojo/services/public/cpp/input_events/input_events_type_converters.h b/chromium/mojo/services/public/cpp/input_events/input_events_type_converters.h new file mode 100644 index 00000000000..7882f62ea30 --- /dev/null +++ b/chromium/mojo/services/public/cpp/input_events/input_events_type_converters.h @@ -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. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_INPUT_EVENTS_TYPE_CONVERTERS_H_ +#define MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_INPUT_EVENTS_TYPE_CONVERTERS_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/services/public/cpp/input_events/mojo_input_events_export.h" +#include "mojo/services/public/interfaces/input_events/input_events.mojom.h" +#include "ui/events/event.h" + +namespace mojo { + +template<> +class MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::Event> { + public: + static EventPtr ConvertFrom(const ui::Event& input); +}; + +template<> +class MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, + scoped_ptr<ui::Event> > { + public: + static scoped_ptr<ui::Event> ConvertTo(const EventPtr& input); +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_INPUT_EVENTS_TYPE_CONVERTERS_H_ diff --git a/chromium/mojo/services/public/cpp/input_events/lib/input_events_type_converters.cc b/chromium/mojo/services/public/cpp/input_events/lib/input_events_type_converters.cc new file mode 100644 index 00000000000..5a47d07e720 --- /dev/null +++ b/chromium/mojo/services/public/cpp/input_events/lib/input_events_type_converters.cc @@ -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. + +#include "mojo/services/public/cpp/input_events/input_events_type_converters.h" + +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace mojo { + +// static +EventPtr TypeConverter<EventPtr, ui::Event>::ConvertFrom( + const ui::Event& input) { + EventPtr event(Event::New()); + event->action = input.type(); + event->flags = input.flags(); + event->time_stamp = input.time_stamp().ToInternalValue(); + + if (input.IsMouseEvent() || input.IsTouchEvent()) { + const ui::LocatedEvent* located_event = + static_cast<const ui::LocatedEvent*>(&input); + event->location = + TypeConverter<PointPtr, gfx::Point>::ConvertFrom( + located_event->location()); + } + + if (input.IsTouchEvent()) { + const ui::TouchEvent* touch_event = + static_cast<const ui::TouchEvent*>(&input); + TouchDataPtr touch_data(TouchData::New()); + touch_data->pointer_id = touch_event->touch_id(); + event->touch_data = touch_data.Pass(); + } else if (input.IsKeyEvent()) { + const ui::KeyEvent* key_event = static_cast<const ui::KeyEvent*>(&input); + KeyDataPtr key_data(KeyData::New()); + key_data->key_code = key_event->key_code(); + key_data->is_char = key_event->is_char(); + event->key_data = key_data.Pass(); + } + return event.Pass(); +} + +// static +scoped_ptr<ui::Event> +TypeConverter<EventPtr, scoped_ptr<ui::Event> >::ConvertTo( + const EventPtr& input) { + scoped_ptr<ui::Event> ui_event; + switch (input->action) { + case ui::ET_KEY_PRESSED: + case ui::ET_KEY_RELEASED: + ui_event.reset(new ui::KeyEvent( + static_cast<ui::EventType>(input->action), + static_cast<ui::KeyboardCode>( + input->key_data->key_code), + input->flags, + input->key_data->is_char)); + break; + default: + // TODO: support other types. + // NOTIMPLEMENTED(); + ; + } + // TODO: need to support time_stamp. + return ui_event.Pass(); +} + +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/input_events/mojo_input_events_export.h b/chromium/mojo/services/public/cpp/input_events/mojo_input_events_export.h new file mode 100644 index 00000000000..d7b193e07cd --- /dev/null +++ b/chromium/mojo/services/public/cpp/input_events/mojo_input_events_export.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_MOJO_INPUT_EVENTS_EXPORT_H_ +#define MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_MOJO_INPUT_EVENTS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_INPUT_EVENTS_IMPLEMENTATION) +#define MOJO_INPUT_EVENTS_EXPORT __declspec(dllexport) +#else +#define MOJO_INPUT_EVENTS_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_INPUT_EVENTS_IMPLEMENTATION) +#define MOJO_INPUT_EVENTS_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_INPUT_EVENTS_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_INPUT_EVENTS_EXPORT +#endif + +#endif // MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_MOJO_INPUT_EVENTS_EXPORT_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/DEPS b/chromium/mojo/services/public/cpp/view_manager/DEPS new file mode 100644 index 00000000000..4460c199e75 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+third_party/skia/include/core", + "+ui/gfx/geometry" +] diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/DEPS b/chromium/mojo/services/public/cpp/view_manager/lib/DEPS new file mode 100644 index 00000000000..7ae7fe7dbae --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+mojo/geometry", + "+third_party/skia", + "+ui/gfx", +] + +specific_include_rules = { + "view_manager_test_suite.cc": [ + "+ui/gl", + ], +} diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/node.cc b/chromium/mojo/services/public/cpp/view_manager/lib/node.cc new file mode 100644 index 00000000000..e3ce2c9b8dd --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/node.cc @@ -0,0 +1,432 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/node.h" + +#include "mojo/services/public/cpp/view_manager/lib/node_private.h" +#include "mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h" +#include "mojo/services/public/cpp/view_manager/lib/view_private.h" +#include "mojo/services/public/cpp/view_manager/node_observer.h" +#include "mojo/services/public/cpp/view_manager/view.h" + +namespace mojo { +namespace view_manager { + +namespace { + +void NotifyViewTreeChangeAtReceiver( + Node* receiver, + const NodeObserver::TreeChangeParams& params) { + NodeObserver::TreeChangeParams local_params = params; + local_params.receiver = receiver; + FOR_EACH_OBSERVER(NodeObserver, + *NodePrivate(receiver).observers(), + OnTreeChange(local_params)); +} + +void NotifyViewTreeChangeUp( + Node* start_at, + const NodeObserver::TreeChangeParams& params) { + for (Node* current = start_at; current; current = current->parent()) + NotifyViewTreeChangeAtReceiver(current, params); +} + +void NotifyViewTreeChangeDown( + Node* start_at, + const NodeObserver::TreeChangeParams& params) { + NotifyViewTreeChangeAtReceiver(start_at, params); + Node::Children::const_iterator it = start_at->children().begin(); + for (; it != start_at->children().end(); ++it) + NotifyViewTreeChangeDown(*it, params); +} + +void NotifyViewTreeChange( + const NodeObserver::TreeChangeParams& params) { + NotifyViewTreeChangeDown(params.target, params); + if (params.old_parent) + NotifyViewTreeChangeUp(params.old_parent, params); + if (params.new_parent) + NotifyViewTreeChangeUp(params.new_parent, params); +} + +class ScopedTreeNotifier { + public: + ScopedTreeNotifier(Node* target, Node* old_parent, Node* new_parent) { + params_.target = target; + params_.old_parent = old_parent; + params_.new_parent = new_parent; + NotifyViewTreeChange(params_); + } + ~ScopedTreeNotifier() { + params_.phase = NodeObserver::DISPOSITION_CHANGED; + NotifyViewTreeChange(params_); + } + + private: + NodeObserver::TreeChangeParams params_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTreeNotifier); +}; + +void RemoveChildImpl(Node* child, Node::Children* children) { + Node::Children::iterator it = + std::find(children->begin(), children->end(), child); + if (it != children->end()) { + children->erase(it); + NodePrivate(child).ClearParent(); + } +} + +class ScopedOrderChangedNotifier { + public: + ScopedOrderChangedNotifier(Node* node, + Node* relative_node, + OrderDirection direction) + : node_(node), + relative_node_(relative_node), + direction_(direction) { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeReordered(node_, + relative_node_, + direction_, + NodeObserver::DISPOSITION_CHANGING)); + + } + ~ScopedOrderChangedNotifier() { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeReordered(node_, + relative_node_, + direction_, + NodeObserver::DISPOSITION_CHANGED)); + } + + private: + Node* node_; + Node* relative_node_; + OrderDirection direction_; + + DISALLOW_COPY_AND_ASSIGN(ScopedOrderChangedNotifier); +}; + +// Returns true if the order actually changed. +bool ReorderImpl(Node::Children* children, + Node* node, + Node* relative, + OrderDirection direction) { + DCHECK(relative); + DCHECK_NE(node, relative); + DCHECK_EQ(node->parent(), relative->parent()); + + const size_t child_i = + std::find(children->begin(), children->end(), node) - children->begin(); + const size_t target_i = + std::find(children->begin(), children->end(), relative) - + children->begin(); + if ((direction == ORDER_ABOVE && child_i == target_i + 1) || + (direction == ORDER_BELOW && child_i + 1 == target_i)) { + return false; + } + + ScopedOrderChangedNotifier notifier(node, relative, direction); + + const size_t dest_i = + direction == ORDER_ABOVE ? + (child_i < target_i ? target_i : target_i + 1) : + (child_i < target_i ? target_i - 1 : target_i); + children->erase(children->begin() + child_i); + children->insert(children->begin() + dest_i, node); + + return true; +} + +class ScopedSetActiveViewNotifier { + public: + ScopedSetActiveViewNotifier(Node* node, View* old_view, View* new_view) + : node_(node), + old_view_(old_view), + new_view_(new_view) { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node).observers(), + OnNodeActiveViewChange(node_, + old_view_, + new_view_, + NodeObserver::DISPOSITION_CHANGING)); + } + ~ScopedSetActiveViewNotifier() { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeActiveViewChange(node_, + old_view_, + new_view_, + NodeObserver::DISPOSITION_CHANGED)); + } + + private: + Node* node_; + View* old_view_; + View* new_view_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSetActiveViewNotifier); +}; + +class ScopedSetBoundsNotifier { + public: + ScopedSetBoundsNotifier(Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) + : node_(node), + old_bounds_(old_bounds), + new_bounds_(new_bounds) { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeBoundsChange(node_, + old_bounds_, + new_bounds_, + NodeObserver::DISPOSITION_CHANGING)); + } + ~ScopedSetBoundsNotifier() { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeBoundsChange(node_, + old_bounds_, + new_bounds_, + NodeObserver::DISPOSITION_CHANGED)); + } + + private: + Node* node_; + const gfx::Rect old_bounds_; + const gfx::Rect new_bounds_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSetBoundsNotifier); +}; + +class ScopedDestructionNotifier { + public: + explicit ScopedDestructionNotifier(Node* node) + : node_(node) { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeDestroy(node_, NodeObserver::DISPOSITION_CHANGING)); + } + ~ScopedDestructionNotifier() { + FOR_EACH_OBSERVER( + NodeObserver, + *NodePrivate(node_).observers(), + OnNodeDestroy(node_, NodeObserver::DISPOSITION_CHANGED)); + } + + private: + Node* node_; + + DISALLOW_COPY_AND_ASSIGN(ScopedDestructionNotifier); +}; + +// Some operations are only permitted in the connection that created the node. +bool OwnsNode(ViewManager* manager, Node* node) { + return !manager || + static_cast<ViewManagerClientImpl*>(manager)->OwnsNode(node->id()); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// Node, public: + +// static +Node* Node::Create(ViewManager* view_manager) { + Node* node = new Node(view_manager); + static_cast<ViewManagerClientImpl*>(view_manager)->AddNode(node); + return node; +} + +void Node::Destroy() { + if (!OwnsNode(manager_, this)) + return; + + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->DestroyNode(id_); + while (!children_.empty()) + children_.front()->Destroy(); + LocalDestroy(); +} + +void Node::SetBounds(const gfx::Rect& bounds) { + if (!OwnsNode(manager_, this)) + return; + + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->SetBounds(id_, bounds); + LocalSetBounds(bounds_, bounds); +} + +void Node::AddObserver(NodeObserver* observer) { + observers_.AddObserver(observer); +} + +void Node::RemoveObserver(NodeObserver* observer) { + observers_.RemoveObserver(observer); +} + +void Node::AddChild(Node* child) { + // TODO(beng): not necessarily valid to all connections, but possibly to the + // embeddee in an embedder-embeddee relationship. + if (manager_) + CHECK_EQ(NodePrivate(child).view_manager(), manager_); + LocalAddChild(child); + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->AddChild(child->id(), id_); +} + +void Node::RemoveChild(Node* child) { + // TODO(beng): not necessarily valid to all connections, but possibly to the + // embeddee in an embedder-embeddee relationship. + if (manager_) + CHECK_EQ(NodePrivate(child).view_manager(), manager_); + LocalRemoveChild(child); + if (manager_) { + static_cast<ViewManagerClientImpl*>(manager_)->RemoveChild(child->id(), + id_); + } +} + +void Node::MoveToFront() { + Reorder(parent_->children_.back(), ORDER_ABOVE); +} + +void Node::MoveToBack() { + Reorder(parent_->children_.front(), ORDER_BELOW); +} + +void Node::Reorder(Node* relative, OrderDirection direction) { + if (!LocalReorder(relative, direction)) + return; + if (manager_) { + static_cast<ViewManagerClientImpl*>(manager_)->Reorder(id_, + relative->id(), + direction); + } +} + +bool Node::Contains(Node* child) const { + if (manager_) + CHECK_EQ(NodePrivate(child).view_manager(), manager_); + for (Node* p = child->parent(); p; p = p->parent()) { + if (p == this) + return true; + } + return false; +} + +Node* Node::GetChildById(Id id) { + if (id == id_) + return this; + // TODO(beng): this could be improved depending on how we decide to own nodes. + Children::const_iterator it = children_.begin(); + for (; it != children_.end(); ++it) { + Node* node = (*it)->GetChildById(id); + if (node) + return node; + } + return NULL; +} + +void Node::SetActiveView(View* view) { + // TODO(beng): not necessarily valid to all connections, but possibly to the + // embeddee in an embedder-embeddee relationship. + if (manager_) + CHECK_EQ(ViewPrivate(view).view_manager(), manager_); + LocalSetActiveView(view); + if (manager_) { + static_cast<ViewManagerClientImpl*>(manager_)->SetActiveView( + id_, active_view_->id()); + } +} + +void Node::SetFocus() { + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->SetFocus(id_); +} + +void Node::Embed(const String& url) { + static_cast<ViewManagerClientImpl*>(manager_)->Embed(url, id_); +} + +//////////////////////////////////////////////////////////////////////////////// +// Node, protected: + +Node::Node() + : manager_(NULL), + id_(-1), + parent_(NULL), + active_view_(NULL) {} + +Node::~Node() { + ScopedDestructionNotifier notifier(this); + if (parent_) + parent_->LocalRemoveChild(this); + // TODO(beng): It'd be better to do this via a destruction observer in the + // ViewManagerClientImpl. + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->RemoveNode(id_); +} + +//////////////////////////////////////////////////////////////////////////////// +// Node, private: + +Node::Node(ViewManager* manager) + : manager_(manager), + id_(static_cast<ViewManagerClientImpl*>(manager_)->CreateNode()), + parent_(NULL), + active_view_(NULL) {} + +void Node::LocalDestroy() { + delete this; +} + +void Node::LocalAddChild(Node* child) { + ScopedTreeNotifier notifier(child, child->parent(), this); + if (child->parent()) + RemoveChildImpl(child, &child->parent_->children_); + children_.push_back(child); + child->parent_ = this; +} + +void Node::LocalRemoveChild(Node* child) { + DCHECK_EQ(this, child->parent()); + ScopedTreeNotifier notifier(child, this, NULL); + RemoveChildImpl(child, &children_); +} + +bool Node::LocalReorder(Node* relative, OrderDirection direction) { + return ReorderImpl(&parent_->children_, this, relative, direction); +} + +void Node::LocalSetActiveView(View* view) { + ScopedSetActiveViewNotifier notifier(this, active_view_, view); + if (active_view_) + ViewPrivate(active_view_).set_node(NULL); + active_view_ = view; + if (active_view_) + ViewPrivate(active_view_).set_node(this); +} + +void Node::LocalSetBounds(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + DCHECK(old_bounds == bounds_); + ScopedSetBoundsNotifier notifier(this, old_bounds, new_bounds); + bounds_ = new_bounds; +} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/node_observer.cc b/chromium/mojo/services/public/cpp/view_manager/lib/node_observer.cc new file mode 100644 index 00000000000..a0f9f4e67c2 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/node_observer.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/node_observer.h" + +#include "base/basictypes.h" + +namespace mojo { +namespace view_manager { + +//////////////////////////////////////////////////////////////////////////////// +// NodeObserver, public: + +NodeObserver::TreeChangeParams::TreeChangeParams() + : target(NULL), + old_parent(NULL), + new_parent(NULL), + receiver(NULL), + phase(NodeObserver::DISPOSITION_CHANGING) {} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/node_private.cc b/chromium/mojo/services/public/cpp/view_manager/lib/node_private.cc new file mode 100644 index 00000000000..92dfb537fee --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/node_private.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/lib/node_private.h" + +namespace mojo { +namespace view_manager { + +NodePrivate::NodePrivate(Node* node) + : node_(node) { +} + +NodePrivate::~NodePrivate() { +} + +// static +Node* NodePrivate::LocalCreate() { + return new Node; +} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/node_private.h b/chromium/mojo/services/public/cpp/view_manager/lib/node_private.h new file mode 100644 index 00000000000..6f634b3a95a --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/node_private.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_NODE_PRIVATE_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_NODE_PRIVATE_H_ + +#include "base/basictypes.h" + +#include "mojo/services/public/cpp/view_manager/node.h" + +namespace mojo { +namespace view_manager { + +class NodePrivate { + public: + explicit NodePrivate(Node* node); + ~NodePrivate(); + + static Node* LocalCreate(); + + ObserverList<NodeObserver>* observers() { return &node_->observers_; } + + void ClearParent() { node_->parent_ = NULL; } + + void set_id(Id id) { node_->id_ = id; } + + ViewManager* view_manager() { return node_->manager_; } + void set_view_manager(ViewManager* manager) { + node_->manager_ = manager; + } + + void LocalDestroy() { + node_->LocalDestroy(); + } + void LocalAddChild(Node* child) { + node_->LocalAddChild(child); + } + void LocalRemoveChild(Node* child) { + node_->LocalRemoveChild(child); + } + void LocalReorder(Node* relative, OrderDirection direction) { + node_->LocalReorder(relative, direction); + } + void LocalSetActiveView(View* view) { + node_->LocalSetActiveView(view); + } + void LocalSetBounds(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + node_->LocalSetBounds(old_bounds, new_bounds); + } + + private: + Node* node_; + + DISALLOW_COPY_AND_ASSIGN(NodePrivate); +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_NODE_PRIVATE_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view.cc b/chromium/mojo/services/public/cpp/view_manager/lib/view.cc new file mode 100644 index 00000000000..4bcedfce080 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view.cc @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/view.h" + +#include "mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h" +#include "mojo/services/public/cpp/view_manager/lib/view_private.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "ui/gfx/canvas.h" + +namespace mojo { +namespace view_manager { + +namespace { +class ScopedDestructionNotifier { + public: + explicit ScopedDestructionNotifier(View* view) + : view_(view) { + FOR_EACH_OBSERVER( + ViewObserver, + *ViewPrivate(view_).observers(), + OnViewDestroy(view_, ViewObserver::DISPOSITION_CHANGING)); + } + ~ScopedDestructionNotifier() { + FOR_EACH_OBSERVER( + ViewObserver, + *ViewPrivate(view_).observers(), + OnViewDestroy(view_, ViewObserver::DISPOSITION_CHANGED)); + } + + private: + View* view_; + + DISALLOW_COPY_AND_ASSIGN(ScopedDestructionNotifier); +}; +} // namespace + +// static +View* View::Create(ViewManager* manager) { + View* view = new View(manager); + static_cast<ViewManagerClientImpl*>(manager)->AddView(view); + return view; +} + +void View::Destroy() { + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->DestroyView(id_); + LocalDestroy(); +} + +void View::AddObserver(ViewObserver* observer) { + observers_.AddObserver(observer); +} + +void View::RemoveObserver(ViewObserver* observer) { + observers_.RemoveObserver(observer); +} + +void View::SetContents(const SkBitmap& contents) { + if (manager_) { + static_cast<ViewManagerClientImpl*>(manager_)->SetViewContents(id_, + contents); + } +} + +void View::SetColor(SkColor color) { + gfx::Canvas canvas(node_->bounds().size(), 1.0f, true); + canvas.DrawColor(color); + SetContents(skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(true)); +} + +View::View(ViewManager* manager) + : id_(static_cast<ViewManagerClientImpl*>(manager)->CreateView()), + node_(NULL), + manager_(manager) {} + +View::View() + : id_(-1), + node_(NULL), + manager_(NULL) {} + +View::~View() { + ScopedDestructionNotifier notifier(this); + // TODO(beng): It'd be better to do this via a destruction observer in the + // ViewManagerClientImpl. + if (manager_) + static_cast<ViewManagerClientImpl*>(manager_)->RemoveView(id_); +} + +void View::LocalDestroy() { + delete this; +} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.cc b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.cc new file mode 100644 index 00000000000..cb7b07136af --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.cc @@ -0,0 +1,841 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/application/connect.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/public/cpp/view_manager/lib/node_private.h" +#include "mojo/services/public/cpp/view_manager/lib/view_private.h" +#include "mojo/services/public/cpp/view_manager/node_observer.h" +#include "mojo/services/public/cpp/view_manager/util.h" +#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/codec/png_codec.h" + +namespace mojo { +namespace view_manager { + +Id MakeTransportId(ConnectionSpecificId connection_id, + ConnectionSpecificId local_id) { + return (connection_id << 16) | local_id; +} + +// Helper called to construct a local node/view object from transport data. +Node* AddNodeToViewManager(ViewManagerClientImpl* client, + Node* parent, + Id node_id, + Id view_id, + const gfx::Rect& bounds) { + // We don't use the ctor that takes a ViewManager here, since it will call + // back to the service and attempt to create a new node. + Node* node = NodePrivate::LocalCreate(); + NodePrivate private_node(node); + private_node.set_view_manager(client); + private_node.set_id(node_id); + private_node.LocalSetBounds(gfx::Rect(), bounds); + if (parent) + NodePrivate(parent).LocalAddChild(node); + client->AddNode(node); + + // View. + if (view_id != 0) { + View* view = ViewPrivate::LocalCreate(); + ViewPrivate private_view(view); + private_view.set_view_manager(client); + private_view.set_id(view_id); + private_view.set_node(node); + // TODO(beng): this broadcasts notifications locally... do we want this? I + // don't think so. same story for LocalAddChild above! + private_node.LocalSetActiveView(view); + client->AddView(view); + } + return node; +} + +Node* BuildNodeTree(ViewManagerClientImpl* client, + const Array<NodeDataPtr>& nodes) { + std::vector<Node*> parents; + Node* root = NULL; + Node* last_node = NULL; + for (size_t i = 0; i < nodes.size(); ++i) { + if (last_node && nodes[i]->parent_id == last_node->id()) { + parents.push_back(last_node); + } else if (!parents.empty()) { + while (parents.back()->id() != nodes[i]->parent_id) + parents.pop_back(); + } + Node* node = AddNodeToViewManager( + client, + !parents.empty() ? parents.back() : NULL, + nodes[i]->node_id, + nodes[i]->view_id, + nodes[i]->bounds.To<gfx::Rect>()); + if (!last_node) + root = node; + last_node = node; + } + return root; +} + +// Responsible for removing a root from the ViewManager when that node is +// destroyed. +class RootObserver : public NodeObserver { + public: + explicit RootObserver(Node* root) : root_(root) {} + virtual ~RootObserver() {} + + private: + // Overridden from NodeObserver: + virtual void OnNodeDestroy(Node* node, + DispositionChangePhase phase) OVERRIDE { + DCHECK_EQ(node, root_); + if (phase != NodeObserver::DISPOSITION_CHANGED) + return; + static_cast<ViewManagerClientImpl*>( + NodePrivate(root_).view_manager())->RemoveRoot(root_); + delete this; + } + + Node* root_; + + DISALLOW_COPY_AND_ASSIGN(RootObserver); +}; + +class ViewManagerTransaction { + public: + virtual ~ViewManagerTransaction() {} + + void Commit() { + DCHECK(!committed_); + DoCommit(); + committed_ = true; + } + + bool committed() const { return committed_; } + + protected: + explicit ViewManagerTransaction(ViewManagerClientImpl* client) + : committed_(false), + client_(client) { + } + + // Overridden to perform transaction-specific commit actions. + virtual void DoCommit() = 0; + + // Overridden to perform transaction-specific cleanup on commit ack from the + // service. + virtual void DoActionCompleted(bool success) = 0; + + ViewManagerService* service() { return client_->service_; } + + Id GetAndAdvanceNextServerChangeId() { + return client_->next_server_change_id_++; + } + + base::Callback<void(bool)> ActionCompletedCallback() { + return base::Bind(&ViewManagerTransaction::OnActionCompleted, + base::Unretained(this)); + } + + private: + // General callback to be used for commits to the service. + void OnActionCompleted(bool success) { + DCHECK(success); + DoActionCompleted(success); + client_->RemoveFromPendingQueue(this); + } + + bool committed_; + ViewManagerClientImpl* client_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerTransaction); +}; + +class CreateViewTransaction : public ViewManagerTransaction { + public: + CreateViewTransaction(Id view_id, ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + view_id_(view_id) {} + virtual ~CreateViewTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->CreateView(view_id_, ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): failure. + } + + const Id view_id_; + + DISALLOW_COPY_AND_ASSIGN(CreateViewTransaction); +}; + +class DestroyViewTransaction : public ViewManagerTransaction { + public: + DestroyViewTransaction(Id view_id, ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + view_id_(view_id) {} + virtual ~DestroyViewTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->DeleteView(view_id_, ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id view_id_; + + DISALLOW_COPY_AND_ASSIGN(DestroyViewTransaction); +}; + +class CreateNodeTransaction : public ViewManagerTransaction { + public: + CreateNodeTransaction(Id node_id, ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + node_id_(node_id) {} + virtual ~CreateNodeTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->CreateNode(node_id_, ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): Failure means we tried to create with an extant id for this + // connection. It also could mean we tried to do something + // invalid, or we tried applying a change out of order. Figure + // out what to do. + } + + const Id node_id_; + + DISALLOW_COPY_AND_ASSIGN(CreateNodeTransaction); +}; + +class DestroyNodeTransaction : public ViewManagerTransaction { + public: + DestroyNodeTransaction(Id node_id, ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + node_id_(node_id) {} + virtual ~DestroyNodeTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->DeleteNode(node_id_, + GetAndAdvanceNextServerChangeId(), + ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id node_id_; + DISALLOW_COPY_AND_ASSIGN(DestroyNodeTransaction); +}; + +class AddChildTransaction : public ViewManagerTransaction { + public: + AddChildTransaction(Id child_id, + Id parent_id, + ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + child_id_(child_id), + parent_id_(parent_id) {} + virtual ~AddChildTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->AddNode(parent_id_, + child_id_, + GetAndAdvanceNextServerChangeId(), + ActionCompletedCallback()); + } + + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id child_id_; + const Id parent_id_; + + DISALLOW_COPY_AND_ASSIGN(AddChildTransaction); +}; + +class RemoveChildTransaction : public ViewManagerTransaction { + public: + RemoveChildTransaction(Id child_id, ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + child_id_(child_id) {} + virtual ~RemoveChildTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->RemoveNodeFromParent( + child_id_, + GetAndAdvanceNextServerChangeId(), + ActionCompletedCallback()); + } + + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id child_id_; + + DISALLOW_COPY_AND_ASSIGN(RemoveChildTransaction); +}; + +class ReorderNodeTransaction : public ViewManagerTransaction { + public: + ReorderNodeTransaction(Id node_id, + Id relative_id, + OrderDirection direction, + ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + node_id_(node_id), + relative_id_(relative_id), + direction_(direction) {} + virtual ~ReorderNodeTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->ReorderNode(node_id_, + relative_id_, + direction_, + GetAndAdvanceNextServerChangeId(), + ActionCompletedCallback()); + } + + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id node_id_; + const Id relative_id_; + const OrderDirection direction_; + + DISALLOW_COPY_AND_ASSIGN(ReorderNodeTransaction); +}; + +class SetActiveViewTransaction : public ViewManagerTransaction { + public: + SetActiveViewTransaction(Id node_id, + Id view_id, + ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + node_id_(node_id), + view_id_(view_id) {} + virtual ~SetActiveViewTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->SetView(node_id_, view_id_, ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id node_id_; + const Id view_id_; + + DISALLOW_COPY_AND_ASSIGN(SetActiveViewTransaction); +}; + +class SetBoundsTransaction : public ViewManagerTransaction { + public: + SetBoundsTransaction(Id node_id, + const gfx::Rect& bounds, + ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + node_id_(node_id), + bounds_(bounds) {} + virtual ~SetBoundsTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->SetNodeBounds( + node_id_, Rect::From(bounds_), ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id node_id_; + const gfx::Rect bounds_; + + DISALLOW_COPY_AND_ASSIGN(SetBoundsTransaction); +}; + +class SetViewContentsTransaction : public ViewManagerTransaction { + public: + SetViewContentsTransaction(Id view_id, + const SkBitmap& contents, + ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + view_id_(view_id), + contents_(contents) {} + virtual ~SetViewContentsTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + std::vector<unsigned char> data; + gfx::PNGCodec::EncodeBGRASkBitmap(contents_, false, &data); + + void* memory = NULL; + ScopedSharedBufferHandle duped; + bool result = CreateMapAndDupSharedBuffer(data.size(), + &memory, + &shared_state_handle_, + &duped); + if (!result) + return; + + memcpy(memory, &data[0], data.size()); + + service()->SetViewContents(view_id_, duped.Pass(), + static_cast<uint32_t>(data.size()), + ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + bool CreateMapAndDupSharedBuffer(size_t size, + void** memory, + ScopedSharedBufferHandle* handle, + ScopedSharedBufferHandle* duped) { + MojoResult result = CreateSharedBuffer(NULL, size, handle); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(handle->is_valid()); + + result = DuplicateBuffer(handle->get(), NULL, duped); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(duped->is_valid()); + + result = MapBuffer( + handle->get(), 0, size, memory, MOJO_MAP_BUFFER_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(*memory); + + return true; + } + + const Id view_id_; + const SkBitmap contents_; + ScopedSharedBufferHandle shared_state_handle_; + + DISALLOW_COPY_AND_ASSIGN(SetViewContentsTransaction); +}; + +class EmbedTransaction : public ViewManagerTransaction { + public: + EmbedTransaction(const String& url, + Id node_id, + ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + url_(url), + node_id_(node_id) {} + virtual ~EmbedTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + std::vector<Id> ids; + ids.push_back(node_id_); + service()->Embed(url_, Array<Id>::From(ids), ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const String url_; + const Id node_id_; + + DISALLOW_COPY_AND_ASSIGN(EmbedTransaction); +}; + +class SetFocusTransaction : public ViewManagerTransaction { + public: + SetFocusTransaction(Id node_id, ViewManagerClientImpl* client) + : ViewManagerTransaction(client), + node_id_(node_id) {} + virtual ~SetFocusTransaction() {} + + private: + // Overridden from ViewManagerTransaction: + virtual void DoCommit() OVERRIDE { + service()->SetFocus(node_id_, ActionCompletedCallback()); + } + virtual void DoActionCompleted(bool success) OVERRIDE { + // TODO(beng): recovery? + } + + const Id node_id_; + + DISALLOW_COPY_AND_ASSIGN(SetFocusTransaction); +}; + +ViewManagerClientImpl::ViewManagerClientImpl(ViewManagerDelegate* delegate) + : connected_(false), + connection_id_(0), + next_id_(1), + next_server_change_id_(0), + delegate_(delegate) {} + +ViewManagerClientImpl::~ViewManagerClientImpl() { + while (!nodes_.empty()) { + IdToNodeMap::iterator it = nodes_.begin(); + if (OwnsNode(it->second->id())) + it->second->Destroy(); + else + nodes_.erase(it); + } + while (!views_.empty()) { + IdToViewMap::iterator it = views_.begin(); + if (OwnsView(it->second->id())) + it->second->Destroy(); + else + views_.erase(it); + } +} + +Id ViewManagerClientImpl::CreateNode() { + DCHECK(connected_); + const Id node_id(MakeTransportId(connection_id_, ++next_id_)); + pending_transactions_.push_back(new CreateNodeTransaction(node_id, this)); + Sync(); + return node_id; +} + +void ViewManagerClientImpl::DestroyNode(Id node_id) { + DCHECK(connected_); + pending_transactions_.push_back(new DestroyNodeTransaction(node_id, this)); + Sync(); +} + +Id ViewManagerClientImpl::CreateView() { + DCHECK(connected_); + const Id view_id(MakeTransportId(connection_id_, ++next_id_)); + pending_transactions_.push_back(new CreateViewTransaction(view_id, this)); + Sync(); + return view_id; +} + +void ViewManagerClientImpl::DestroyView(Id view_id) { + DCHECK(connected_); + pending_transactions_.push_back(new DestroyViewTransaction(view_id, this)); + Sync(); +} + +void ViewManagerClientImpl::AddChild(Id child_id, + Id parent_id) { + DCHECK(connected_); + pending_transactions_.push_back( + new AddChildTransaction(child_id, parent_id, this)); + Sync(); +} + +void ViewManagerClientImpl::RemoveChild(Id child_id, Id parent_id) { + DCHECK(connected_); + pending_transactions_.push_back(new RemoveChildTransaction(child_id, this)); + Sync(); +} + +void ViewManagerClientImpl::Reorder( + Id node_id, + Id relative_node_id, + OrderDirection direction) { + DCHECK(connected_); + pending_transactions_.push_back( + new ReorderNodeTransaction(node_id, relative_node_id, direction, this)); + Sync(); +} + +bool ViewManagerClientImpl::OwnsNode(Id id) const { + return HiWord(id) == connection_id_; +} + +bool ViewManagerClientImpl::OwnsView(Id id) const { + return HiWord(id) == connection_id_; +} + +void ViewManagerClientImpl::SetActiveView(Id node_id, Id view_id) { + DCHECK(connected_); + pending_transactions_.push_back( + new SetActiveViewTransaction(node_id, view_id, this)); + Sync(); +} + +void ViewManagerClientImpl::SetBounds(Id node_id, const gfx::Rect& bounds) { + DCHECK(connected_); + pending_transactions_.push_back( + new SetBoundsTransaction(node_id, bounds, this)); + Sync(); +} + +void ViewManagerClientImpl::SetViewContents(Id view_id, + const SkBitmap& contents) { + DCHECK(connected_); + pending_transactions_.push_back( + new SetViewContentsTransaction(view_id, contents, this)); + Sync(); +} + +void ViewManagerClientImpl::SetFocus(Id node_id) { + DCHECK(connected_); + pending_transactions_.push_back(new SetFocusTransaction(node_id, this)); + Sync(); +} + +void ViewManagerClientImpl::Embed(const String& url, Id node_id) { + DCHECK(connected_); + pending_transactions_.push_back(new EmbedTransaction(url, node_id, this)); + Sync(); +} + +void ViewManagerClientImpl::AddNode(Node* node) { + DCHECK(nodes_.find(node->id()) == nodes_.end()); + nodes_[node->id()] = node; +} + +void ViewManagerClientImpl::RemoveNode(Id node_id) { + IdToNodeMap::iterator it = nodes_.find(node_id); + if (it != nodes_.end()) + nodes_.erase(it); +} + +void ViewManagerClientImpl::AddView(View* view) { + DCHECK(views_.find(view->id()) == views_.end()); + views_[view->id()] = view; +} + +void ViewManagerClientImpl::RemoveView(Id view_id) { + IdToViewMap::iterator it = views_.find(view_id); + if (it != views_.end()) + views_.erase(it); +} + +//////////////////////////////////////////////////////////////////////////////// +// ViewManagerClientImpl, ViewManager implementation: + +const std::string& ViewManagerClientImpl::GetEmbedderURL() const { + return creator_url_; +} + +const std::vector<Node*>& ViewManagerClientImpl::GetRoots() const { + return roots_; +} + +Node* ViewManagerClientImpl::GetNodeById(Id id) { + IdToNodeMap::const_iterator it = nodes_.find(id); + return it != nodes_.end() ? it->second : NULL; +} + +View* ViewManagerClientImpl::GetViewById(Id id) { + IdToViewMap::const_iterator it = views_.find(id); + return it != views_.end() ? it->second : NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// ViewManagerClientImpl, InterfaceImpl overrides: + +void ViewManagerClientImpl::OnConnectionEstablished() { + service_ = client(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ViewManagerClientImpl, ViewManagerClient implementation: + +void ViewManagerClientImpl::OnViewManagerConnectionEstablished( + ConnectionSpecificId connection_id, + const String& creator_url, + Id next_server_change_id, + Array<NodeDataPtr> nodes) { + connected_ = true; + connection_id_ = connection_id; + creator_url_ = TypeConverter<String, std::string>::ConvertFrom(creator_url); + next_server_change_id_ = next_server_change_id; + + DCHECK(pending_transactions_.empty()); + AddRoot(BuildNodeTree(this, nodes)); +} + +void ViewManagerClientImpl::OnRootsAdded(Array<NodeDataPtr> nodes) { + AddRoot(BuildNodeTree(this, nodes)); +} + +void ViewManagerClientImpl::OnServerChangeIdAdvanced( + Id next_server_change_id) { + next_server_change_id_ = next_server_change_id; +} + +void ViewManagerClientImpl::OnNodeBoundsChanged(Id node_id, + RectPtr old_bounds, + RectPtr new_bounds) { + Node* node = GetNodeById(node_id); + NodePrivate(node).LocalSetBounds(old_bounds.To<gfx::Rect>(), + new_bounds.To<gfx::Rect>()); +} + +void ViewManagerClientImpl::OnNodeHierarchyChanged( + Id node_id, + Id new_parent_id, + Id old_parent_id, + Id server_change_id, + mojo::Array<NodeDataPtr> nodes) { + next_server_change_id_ = server_change_id + 1; + + BuildNodeTree(this, nodes); + + Node* new_parent = GetNodeById(new_parent_id); + Node* old_parent = GetNodeById(old_parent_id); + Node* node = GetNodeById(node_id); + if (new_parent) + NodePrivate(new_parent).LocalAddChild(node); + else + NodePrivate(old_parent).LocalRemoveChild(node); +} + +void ViewManagerClientImpl::OnNodeReordered(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id) { + next_server_change_id_ = server_change_id + 1; + + Node* node = GetNodeById(node_id); + Node* relative_node = GetNodeById(relative_node_id); + if (node && relative_node) { + NodePrivate(node).LocalReorder(relative_node, direction); + } +} + +void ViewManagerClientImpl::OnNodeDeleted(Id node_id, Id server_change_id) { + next_server_change_id_ = server_change_id + 1; + + Node* node = GetNodeById(node_id); + if (node) + NodePrivate(node).LocalDestroy(); +} + +void ViewManagerClientImpl::OnNodeViewReplaced(Id node_id, + Id new_view_id, + Id old_view_id) { + Node* node = GetNodeById(node_id); + View* new_view = GetViewById(new_view_id); + if (!new_view && new_view_id != 0) { + // This client wasn't aware of this View until now. + new_view = ViewPrivate::LocalCreate(); + ViewPrivate private_view(new_view); + private_view.set_view_manager(this); + private_view.set_id(new_view_id); + private_view.set_node(node); + AddView(new_view); + } + View* old_view = GetViewById(old_view_id); + DCHECK_EQ(old_view, node->active_view()); + NodePrivate(node).LocalSetActiveView(new_view); +} + +void ViewManagerClientImpl::OnViewDeleted(Id view_id) { + View* view = GetViewById(view_id); + if (view) + ViewPrivate(view).LocalDestroy(); +} + +void ViewManagerClientImpl::OnViewInputEvent( + Id view_id, + EventPtr event, + const Callback<void()>& ack_callback) { + View* view = GetViewById(view_id); + if (view) { + FOR_EACH_OBSERVER(ViewObserver, + *ViewPrivate(view).observers(), + OnViewInputEvent(view, event)); + } + ack_callback.Run(); +} + +void ViewManagerClientImpl::DispatchOnViewInputEvent(Id view_id, + EventPtr event) { + // For now blindly bounce the message back to the server. Doing this means the + // event is sent to the correct target (|view_id|). + // Note: This function is only invoked on the window manager. + service_->DispatchOnViewInputEvent(view_id, event.Pass()); +} + +//////////////////////////////////////////////////////////////////////////////// +// ViewManagerClientImpl, private: + +void ViewManagerClientImpl::Sync() { + // The service connection may not be set up yet. OnConnectionEstablished() + // will schedule another sync when it is. + if (!connected_) + return; + + Transactions::const_iterator it = pending_transactions_.begin(); + for (; it != pending_transactions_.end(); ++it) { + if (!(*it)->committed()) + (*it)->Commit(); + } +} + +void ViewManagerClientImpl::RemoveFromPendingQueue( + ViewManagerTransaction* transaction) { + DCHECK_EQ(transaction, pending_transactions_.front()); + pending_transactions_.erase(pending_transactions_.begin()); + if (pending_transactions_.empty() && !changes_acked_callback_.is_null()) + changes_acked_callback_.Run(); +} + +void ViewManagerClientImpl::AddRoot(Node* root) { + // A new root must not already exist as a root or be contained by an existing + // hierarchy visible to this view manager. + std::vector<Node*>::const_iterator it = roots_.begin(); + for (; it != roots_.end(); ++it) { + if (*it == root || (*it)->Contains(root)) + return; + } + roots_.push_back(root); + root->AddObserver(new RootObserver(root)); + delegate_->OnRootAdded(this, root); +} + +void ViewManagerClientImpl::RemoveRoot(Node* root) { + std::vector<Node*>::iterator it = + std::find(roots_.begin(), roots_.end(), root); + if (it != roots_.end()) + roots_.erase(it); +} + +//////////////////////////////////////////////////////////////////////////////// +// ViewManager, public: + +// static +void ViewManager::Create(Application* application, + ViewManagerDelegate* delegate) { + application->AddService<ViewManagerClientImpl>(delegate); +} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h new file mode 100644 index 00000000000..01cdc34110d --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h @@ -0,0 +1,162 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_CLIENT_IMPL_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_CLIENT_IMPL_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/public/cpp/view_manager/node.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/cpp/view_manager/view_manager.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" + +class SkBitmap; + +namespace mojo { +namespace view_manager { + +class ViewManager; +class ViewManagerTransaction; + +// Manages the connection with the View Manager service. +class ViewManagerClientImpl : public ViewManager, + public InterfaceImpl<ViewManagerClient> { + public: + explicit ViewManagerClientImpl(ViewManagerDelegate* delegate); + virtual ~ViewManagerClientImpl(); + + bool connected() const { return connected_; } + ConnectionSpecificId connection_id() const { return connection_id_; } + + // API exposed to the node/view implementations that pushes local changes to + // the service. + Id CreateNode(); + void DestroyNode(Id node_id); + + Id CreateView(); + void DestroyView(Id view_id); + + // These methods take TransportIds. For views owned by the current connection, + // the connection id high word can be zero. In all cases, the TransportId 0x1 + // refers to the root node. + void AddChild(Id child_id, Id parent_id); + void RemoveChild(Id child_id, Id parent_id); + + void Reorder(Id node_id, Id relative_node_id, OrderDirection direction); + + // Returns true if the specified node/view was created by this connection. + bool OwnsNode(Id id) const; + bool OwnsView(Id id) const; + + void SetActiveView(Id node_id, Id view_id); + void SetBounds(Id node_id, const gfx::Rect& bounds); + void SetViewContents(Id view_id, const SkBitmap& contents); + void SetFocus(Id node_id); + + void Embed(const String& url, Id node_id); + + void set_changes_acked_callback(const base::Callback<void(void)>& callback) { + changes_acked_callback_ = callback; + } + void ClearChangesAckedCallback() { + changes_acked_callback_ = base::Callback<void(void)>(); + } + + // Start/stop tracking nodes & views. While tracked, they can be retrieved via + // ViewManager::GetNode/ViewById. + void AddNode(Node* node); + void RemoveNode(Id node_id); + + void AddView(View* view); + void RemoveView(Id view_id); + + private: + friend class RootObserver; + friend class ViewManagerTransaction; + + typedef ScopedVector<ViewManagerTransaction> Transactions; + typedef std::map<Id, Node*> IdToNodeMap; + typedef std::map<Id, View*> IdToViewMap; + + // Overridden from ViewManager: + virtual const std::string& GetEmbedderURL() const OVERRIDE; + virtual const std::vector<Node*>& GetRoots() const OVERRIDE; + virtual Node* GetNodeById(Id id) OVERRIDE; + virtual View* GetViewById(Id id) OVERRIDE; + + // Overridden from InterfaceImpl: + virtual void OnConnectionEstablished() OVERRIDE; + + // Overridden from ViewManagerClient: + virtual void OnViewManagerConnectionEstablished( + ConnectionSpecificId connection_id, + const String& creator_url, + Id next_server_change_id, + Array<NodeDataPtr> nodes) OVERRIDE; + virtual void OnRootsAdded(Array<NodeDataPtr> nodes) OVERRIDE; + virtual void OnServerChangeIdAdvanced(Id next_server_change_id) OVERRIDE; + virtual void OnNodeBoundsChanged(Id node_id, + RectPtr old_bounds, + RectPtr new_bounds) OVERRIDE; + virtual void OnNodeHierarchyChanged(Id node_id, + Id new_parent_id, + Id old_parent_id, + Id server_change_id, + Array<NodeDataPtr> nodes) OVERRIDE; + virtual void OnNodeReordered(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id) OVERRIDE; + virtual void OnNodeDeleted(Id node_id, Id server_change_id) OVERRIDE; + virtual void OnNodeViewReplaced(Id node, + Id new_view_id, + Id old_view_id) OVERRIDE; + virtual void OnViewDeleted(Id view_id) OVERRIDE; + virtual void OnViewInputEvent(Id view, + EventPtr event, + const Callback<void()>& callback) OVERRIDE; + virtual void DispatchOnViewInputEvent(Id view_id, EventPtr event) OVERRIDE; + + // Sync the client model with the service by enumerating the pending + // transaction queue and applying them in order. + void Sync(); + + // Removes |transaction| from the pending queue. |transaction| must be at the + // front of the queue. + void RemoveFromPendingQueue(ViewManagerTransaction* transaction); + + void AddRoot(Node* root); + void RemoveRoot(Node* root); + + bool connected_; + ConnectionSpecificId connection_id_; + ConnectionSpecificId next_id_; + Id next_server_change_id_; + + std::string creator_url_; + + Transactions pending_transactions_; + + base::Callback<void(void)> changes_acked_callback_; + + ViewManagerDelegate* delegate_; + + std::vector<Node*> roots_; + + IdToNodeMap nodes_; + IdToViewMap views_; + + ViewManagerService* service_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl); +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_CLIENT_IMPL_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.cc b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.cc new file mode 100644 index 00000000000..565b469c5b6 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.cc @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h" + +#include "ui/gl/gl_surface.h" + +namespace mojo { +namespace view_manager { + +ViewManagerTestSuite::ViewManagerTestSuite(int argc, char** argv) + : TestSuite(argc, argv) {} + +ViewManagerTestSuite::~ViewManagerTestSuite() { +} + +void ViewManagerTestSuite::Initialize() { + base::TestSuite::Initialize(); + gfx::GLSurface::InitializeOneOffForTests(); +} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h new file mode 100644 index 00000000000..d0744ade822 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_TEST_SUITE_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_TEST_SUITE_H_ + +#include "base/test/test_suite.h" + +namespace mojo { +namespace view_manager { + +class ViewManagerTestSuite : public base::TestSuite { + public: + ViewManagerTestSuite(int argc, char** argv); + virtual ~ViewManagerTestSuite(); + + protected: + virtual void Initialize() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(ViewManagerTestSuite); +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_TEST_SUITE_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_unittests.cc b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_unittests.cc new file mode 100644 index 00000000000..9a831397ba8 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_manager_unittests.cc @@ -0,0 +1,14 @@ +// 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/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h" + +int main(int argc, char** argv) { + mojo::view_manager::ViewManagerTestSuite test_suite(argc, argv); + + return base::LaunchUnitTests( + argc, argv, base::Bind(&TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_private.cc b/chromium/mojo/services/public/cpp/view_manager/lib/view_private.cc new file mode 100644 index 00000000000..14ecf435f0b --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_private.cc @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/public/cpp/view_manager/lib/view_private.h" + +namespace mojo { +namespace view_manager { + +ViewPrivate::ViewPrivate(View* view) + : view_(view) { +} + +ViewPrivate::~ViewPrivate() { +} + +// static +View* ViewPrivate::LocalCreate() { + return new View; +} + +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/public/cpp/view_manager/lib/view_private.h b/chromium/mojo/services/public/cpp/view_manager/lib/view_private.h new file mode 100644 index 00000000000..2e38613dfad --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/lib/view_private.h @@ -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. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_PRIVATE_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_PRIVATE_H_ + +#include "base/basictypes.h" + +#include "mojo/services/public/cpp/view_manager/view.h" + +namespace mojo { +namespace view_manager { + +class ViewPrivate { + public: + explicit ViewPrivate(View* view); + ~ViewPrivate(); + + static View* LocalCreate(); + void LocalDestroy() { + view_->LocalDestroy(); + } + + void set_id(Id id) { view_->id_ = id; } + void set_node(Node* node) { view_->node_ = node; } + + ViewManager* view_manager() { return view_->manager_; } + void set_view_manager(ViewManager* manager) { + view_->manager_ = manager; + } + + ObserverList<ViewObserver>* observers() { return &view_->observers_; } + + private: + View* view_; + + DISALLOW_COPY_AND_ASSIGN(ViewPrivate); +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_PRIVATE_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/node.h b/chromium/mojo/services/public/cpp/view_manager/node.h new file mode 100644 index 00000000000..03dbd376def --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/node.h @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_NODE_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_NODE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/interfaces/view_manager/view_manager_constants.mojom.h" +#include "ui/gfx/geometry/rect.h" + +namespace mojo { +namespace view_manager { + +class View; +class ViewManager; +class NodeObserver; + +// Nodes are owned by the ViewManager. +// TODO(beng): Right now, you'll have to implement a NodeObserver to track +// destruction and NULL any pointers you have. +// Investigate some kind of smart pointer or weak pointer for these. +class Node { + public: + typedef std::vector<Node*> Children; + + static Node* Create(ViewManager* view_manager); + + // Destroys this node and all its children. + void Destroy(); + + // Configuration. + Id id() const { return id_; } + + // Geometric disposition. + const gfx::Rect& bounds() { return bounds_; } + void SetBounds(const gfx::Rect& bounds); + + // Observation. + void AddObserver(NodeObserver* observer); + void RemoveObserver(NodeObserver* observer); + + // Tree. + Node* parent() { return parent_; } + const Node* parent() const { return parent_; } + const Children& children() const { return children_; } + + void AddChild(Node* child); + void RemoveChild(Node* child); + + void Reorder(Node* relative, OrderDirection direction); + void MoveToFront(); + void MoveToBack(); + + bool Contains(Node* child) const; + + Node* GetChildById(Id id); + + // View. + void SetActiveView(View* view); + View* active_view() { return active_view_; } + + // Focus. + void SetFocus(); + + // Embedding. + void Embed(const String& url); + + protected: + // This class is subclassed only by test classes that provide a public ctor. + Node(); + ~Node(); + + private: + friend class NodePrivate; + + explicit Node(ViewManager* manager); + + void LocalDestroy(); + void LocalAddChild(Node* child); + void LocalRemoveChild(Node* child); + // Returns true if the order actually changed. + bool LocalReorder(Node* relative, OrderDirection direction); + void LocalSetActiveView(View* view); + void LocalSetBounds(const gfx::Rect& old_bounds, const gfx::Rect& new_bounds); + + ViewManager* manager_; + Id id_; + Node* parent_; + Children children_; + + ObserverList<NodeObserver> observers_; + + gfx::Rect bounds_; + View* active_view_; + + DISALLOW_COPY_AND_ASSIGN(Node); +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_NODE_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/node_observer.h b/chromium/mojo/services/public/cpp/view_manager/node_observer.h new file mode 100644 index 00000000000..7b0cdb010b9 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/node_observer.h @@ -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. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_NODE_OBSERVER_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_NODE_OBSERVER_H_ + +#include <vector> + +#include "base/basictypes.h" + +#include "mojo/services/public/cpp/view_manager/node.h" + +namespace gfx { +class Rect; +} + +namespace mojo { +namespace view_manager { + +class Node; +class View; + +class NodeObserver { + public: + enum DispositionChangePhase { + DISPOSITION_CHANGING, + DISPOSITION_CHANGED + }; + + struct TreeChangeParams { + TreeChangeParams(); + Node* target; + Node* old_parent; + Node* new_parent; + Node* receiver; + DispositionChangePhase phase; + }; + + virtual void OnTreeChange(const TreeChangeParams& params) {} + + virtual void OnNodeReordered(Node* node, + Node* relative_node, + OrderDirection direction, + DispositionChangePhase phase) {} + + virtual void OnNodeDestroy(Node* node, DispositionChangePhase phase) {} + + virtual void OnNodeActiveViewChange(Node* node, + View* old_view, + View* new_view, + DispositionChangePhase phase) {} + + virtual void OnNodeBoundsChange(Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + DispositionChangePhase phase) {} + + protected: + virtual ~NodeObserver() {} +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_NODE_OBSERVER_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/types.h b/chromium/mojo/services/public/cpp/view_manager/types.h new file mode 100644 index 00000000000..c1f33f30b8d --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/types.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_TYPES_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_TYPES_H_ + +#include "base/basictypes.h" + +// Typedefs for the transport types. These typedefs match that of the mojom +// file, see it for specifics. + +namespace mojo { +namespace view_manager { + +// Used to identify nodes, views and change ids. +typedef uint32_t Id; + +// Used to identify a connection as well as a connection specific view or node +// id. For example, the Id for a node consists of the ConnectionSpecificId of +// the connection and the ConnectionSpecificId of the node. +typedef uint16_t ConnectionSpecificId; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_TYPES_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/util.h b/chromium/mojo/services/public/cpp/view_manager/util.h new file mode 100644 index 00000000000..6716075afc3 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/util.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_UTIL_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_UTIL_H_ + +#include "mojo/services/public/cpp/view_manager/types.h" + +// TODO(beng): #$*&@#(@ MacOSX SDK! +#if defined(HiWord) +#undef HiWord +#endif +#if defined(LoWord) +#undef LoWord +#endif + +namespace mojo { +namespace view_manager { + +inline uint16_t HiWord(uint32_t id) { + return static_cast<uint16_t>((id >> 16) & 0xFFFF); +} + +inline uint16_t LoWord(uint32_t id) { + return static_cast<uint16_t>(id & 0xFFFF); +} + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_UTIL_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/view.h b/chromium/mojo/services/public/cpp/view_manager/view.h new file mode 100644 index 00000000000..b10417b21ba --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/view.h @@ -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. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_H_ + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "third_party/skia/include/core/SkColor.h" + +class SkBitmap; + +namespace mojo { +namespace view_manager { + +class Node; +class ViewManager; +class ViewObserver; + +// Views are owned by the ViewManager. +class View { + public: + static View* Create(ViewManager* manager); + + void Destroy(); + + Id id() const { return id_; } + Node* node() { return node_; } + + void AddObserver(ViewObserver* observer); + void RemoveObserver(ViewObserver* observer); + + // TODO(beng): temporary only. + void SetContents(const SkBitmap& contents); + void SetColor(SkColor color); + + private: + friend class ViewPrivate; + + explicit View(ViewManager* manager); + View(); + ~View(); + + void LocalDestroy(); + + Id id_; + Node* node_; + ViewManager* manager_; + + ObserverList<ViewObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(View); +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/view_manager.h b/chromium/mojo/services/public/cpp/view_manager/view_manager.h new file mode 100644 index 00000000000..6ed6e901d38 --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/view_manager.h @@ -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. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_H_ + +#include <string> +#include <vector> + +#include "mojo/services/public/cpp/view_manager/types.h" + +namespace mojo { +class Application; +namespace view_manager { + +class Node; +class View; +class ViewManagerDelegate; + +class ViewManager { + public: + // Delegate is owned by the caller. + static void Create(Application* application, ViewManagerDelegate* delegate); + + // Returns the URL of the application that embedded this application. + virtual const std::string& GetEmbedderURL() const = 0; + + // Returns all root nodes known to this connection. + virtual const std::vector<Node*>& GetRoots() const = 0; + + // Returns a Node or View known to this connection. + virtual Node* GetNodeById(Id id) = 0; + virtual View* GetViewById(Id id) = 0; + + protected: + virtual ~ViewManager() {} + +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/view_manager_delegate.h b/chromium/mojo/services/public/cpp/view_manager/view_manager_delegate.h new file mode 100644 index 00000000000..bceee02d9be --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/view_manager_delegate.h @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_DELEGATE_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_DELEGATE_H_ + +namespace mojo { +namespace view_manager { + +class Node; +class ViewManager; + +class ViewManagerDelegate { + public: + virtual void OnRootAdded(ViewManager* view_manager, Node* root) {} + + protected: + virtual ~ViewManagerDelegate() {} +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_DELEGATE_H_ diff --git a/chromium/mojo/services/public/cpp/view_manager/view_observer.h b/chromium/mojo/services/public/cpp/view_manager/view_observer.h new file mode 100644 index 00000000000..9e189fd609e --- /dev/null +++ b/chromium/mojo/services/public/cpp/view_manager/view_observer.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_OBSERVER_H_ +#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_OBSERVER_H_ + +#include <vector> + +#include "base/basictypes.h" + +#include "mojo/services/public/interfaces/input_events/input_events.mojom.h" + +namespace mojo { +namespace view_manager { + +class View; + +class ViewObserver { + public: + enum DispositionChangePhase { + DISPOSITION_CHANGING, + DISPOSITION_CHANGED + }; + + virtual void OnViewDestroy(View* view, DispositionChangePhase phase) {} + + virtual void OnViewInputEvent(View* view, const EventPtr& event) {} + + protected: + virtual ~ViewObserver() {} +}; + +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_OBSERVER_H_ diff --git a/chromium/mojo/services/public/interfaces/geometry/geometry.mojom b/chromium/mojo/services/public/interfaces/geometry/geometry.mojom new file mode 100644 index 00000000000..ab68c8e61ce --- /dev/null +++ b/chromium/mojo/services/public/interfaces/geometry/geometry.mojom @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +struct Point { + int32 x; + int32 y; +}; + +struct PointF { + float x; + float y; +}; + +struct Size { + int32 width; + int32 height; +}; + +struct Rect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +struct Transform { + // Should have exactly 16 entries. + float[] matrix; +}; + +} diff --git a/chromium/mojo/services/public/interfaces/input_events/input_events.mojom b/chromium/mojo/services/public/interfaces/input_events/input_events.mojom new file mode 100644 index 00000000000..255fe284fe0 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/input_events/input_events.mojom @@ -0,0 +1,27 @@ +// 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 "../geometry/geometry.mojom" + +module mojo { + +struct KeyData { + int32 key_code; + bool is_char; +}; + +struct TouchData { + int32 pointer_id; +}; + +struct Event { + int32 action; + int32 flags; + int64 time_stamp; + Point location; + KeyData key_data; + TouchData touch_data; +}; + +} diff --git a/chromium/mojo/services/public/interfaces/launcher/launcher.mojom b/chromium/mojo/services/public/interfaces/launcher/launcher.mojom new file mode 100644 index 00000000000..e35eb6746ac --- /dev/null +++ b/chromium/mojo/services/public/interfaces/launcher/launcher.mojom @@ -0,0 +1,36 @@ +// 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 "../navigation/navigation.mojom" + +module mojo.launcher { + +[Client=LauncherClient] +interface Launcher { + // Determines the correct handler application that can render a given URL. + // + // Sometimes this is known statically from the URL (e.g., from its scheme, or + // because we already have an application locally that has said it can handle + // the URL. Other times, we will actually fetch |url| to examine its + // content-type and/or sniff it to determine the correct handler. + Launch(string url); +}; + +[Client=Launcher] +interface LauncherClient { + // Called when a handler has been resolved in response to Launch(). Receiving + // application should connect to the application at |handler_url| and navigate + // it to |view_url|. + // + // Note: |view_url| might not be the same as the URL that was initially + // requested, e.g., in the case of redirects. Applications should always + // navigate to view_url, not the initial URL. + // + // Note: |response| can be NULL in the case where a request was not needed to + // determine the correct handler. + OnLaunch(string handler_url, string view_url, + mojo.navigation.ResponseDetails response); +}; + +} diff --git a/chromium/mojo/services/public/interfaces/native_viewport/native_viewport.mojom b/chromium/mojo/services/public/interfaces/native_viewport/native_viewport.mojom new file mode 100644 index 00000000000..e1ce173c08f --- /dev/null +++ b/chromium/mojo/services/public/interfaces/native_viewport/native_viewport.mojom @@ -0,0 +1,28 @@ +// 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 "../../../gles2/command_buffer.mojom" +import "../geometry/geometry.mojom" +import "../input_events/input_events.mojom" + +module mojo { + +[Client=NativeViewportClient] +interface NativeViewport { + Create(Rect bounds); + Show(); + Hide(); + Close(); + SetBounds(Rect bounds); + CreateGLES2Context(CommandBuffer& gles2_client); +}; + +interface NativeViewportClient { + OnCreated(); + OnBoundsChanged(Rect bounds); + OnDestroyed(); + OnEvent(Event event) => (); +}; + +} diff --git a/chromium/mojo/services/public/interfaces/navigation/navigation.mojom b/chromium/mojo/services/public/interfaces/navigation/navigation.mojom new file mode 100644 index 00000000000..fbe082f06e4 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/navigation/navigation.mojom @@ -0,0 +1,36 @@ +// 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 "../network/url_loader.mojom" + +module mojo.navigation { + +struct NavigationDetails { + string url; + // TODO(aa): method, data, etc. +}; + +struct ResponseDetails { + // TODO(beng): consider providing access to URLRequest too. Currently it is + // not possible to obtain from the URLLoader. + mojo.URLResponse response; + handle<data_pipe_consumer> response_body_stream; +}; + +// Embedders that support navigation of implement this interface. +interface NavigatorHost { + RequestNavigate(uint32 source_node_id, NavigationDetails details); +}; + +// Applications implement this interface to support navigation of their views +// by embedders. +// |response_details| can be NULL when a navigation was not the result of a +// network load. +interface Navigator { + Navigate(uint32 node_id, + NavigationDetails navigation_details, + ResponseDetails response_details); +}; + +} diff --git a/chromium/mojo/services/public/interfaces/network/network_error.mojom b/chromium/mojo/services/public/interfaces/network/network_error.mojom new file mode 100644 index 00000000000..d345e935504 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/network/network_error.mojom @@ -0,0 +1,12 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +struct NetworkError { + int32 code; + string description; +}; + +} diff --git a/chromium/mojo/services/public/interfaces/network/network_service.mojom b/chromium/mojo/services/public/interfaces/network/network_service.mojom new file mode 100644 index 00000000000..7d9cd178cf6 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/network/network_service.mojom @@ -0,0 +1,15 @@ +// 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 "url_loader.mojom" + +module mojo { + +interface NetworkService { + CreateURLLoader(URLLoader& loader); + + // TODO(darin): Add other methods here (e.g., cookies). +}; + +} diff --git a/chromium/mojo/services/public/interfaces/network/url_loader.mojom b/chromium/mojo/services/public/interfaces/network/url_loader.mojom new file mode 100644 index 00000000000..b4bcf09f89d --- /dev/null +++ b/chromium/mojo/services/public/interfaces/network/url_loader.mojom @@ -0,0 +1,94 @@ +// 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 "network_error.mojom" + +module mojo { + +struct URLRequest { + // The URL to load. + string url; + + // The HTTP method if applicable. + string method = "GET"; + + // Additional HTTP request headers. + string[] headers; + + // The payload for the request body. For HTTP requests, the method must be + // set to "POST" or "PUT". + handle<data_pipe_consumer> body; + + // The number of bytes to be read from |body|. A Content-Length header of + // this value will be sent. Set to -1 if length is unknown, which will cause + // |body| to be uploaded using a chunked encoding. + int64 body_length = 0; + + // If set to true, then redirects will be automatically followed. Otherwise, + // when a redirect is encounterd, FollowRedirect must be called to proceed. + bool auto_follow_redirects = false; + + // If set to true, then the HTTP request will bypass the local cache and will + // have a 'Cache-Control: nocache' header added in that causes any proxy + // servers to also not satisfy the request from their cache. This has the + // effect of forcing a full end-to-end fetch. + bool bypass_cache = false; +}; + +struct URLResponse { + // The final URL of the response, after redirects have been followed. + string url; + + // The HTTP status code. 0 if not applicable. + uint32 status_code; + + // The HTTP status line. + string status_line; + + // The HTTP response headers. + string[] headers; +}; + +[Client=URLLoaderClient] +interface URLLoader { + // Start loading the given |request|. When available, the response body will + // be copied to |response_body_stream|. + // + // The client's |OnReceivedResponse| method will run when response meta data + // becomes available, or if a redirect response is encountered and + // |auto_follow_redirects| is false, the client's |OnRecievedRedirect| method + // will called instead. + // + // NOTE: You may observe data being pushed to |response_body_stream| before + // you receive |OnReceivedResponse|. + Start(URLRequest request, handle<data_pipe_producer> response_body_stream); + + // If the request passed to |Start| had |auto_follow_redirects| set to false, + // then upon receiving a redirect, |OnReceivedRedirect| will be called. To + // follow the indicated redirect, call the |FollowRedirect| method. + FollowRedirect(); +}; + +interface URLLoaderClient { + // This method is called when a redirect is encountered, provided the + // request's |auto_follow_redirects| attribute was set to false. + OnReceivedRedirect(URLResponse response, string new_url, string new_method); + + // This method is called when response meta data becomes available. + OnReceivedResponse(URLResponse response); + + // This method is called when a network level error is encountered. This can + // happen before or after OnReceivedResponse, but cannot happen after + // OnReceivedEndOfResponseBody. + OnReceivedError(NetworkError error); + + // This method is called when the response body has been successfully + // downloaded and copied in its entirety to |response_body_stream|. + // + // NOTE: Because |response_body_stream| is limited in size, this event may be + // delayed until |response_body_stream| is consumed. + OnReceivedEndOfResponseBody(); +}; + +} diff --git a/chromium/mojo/services/public/interfaces/surfaces/quads.mojom b/chromium/mojo/services/public/interfaces/surfaces/quads.mojom new file mode 100644 index 00000000000..eb8898b0f36 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/surfaces/quads.mojom @@ -0,0 +1,165 @@ +// 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 "../geometry/geometry.mojom" +import "surface_id.mojom" + +module mojo.surfaces { + +struct Color { + uint32 rgba; +}; + +// TODO(jamesr): Populate subtype fields. +struct CheckerboardQuadState {}; + +struct DebugBorderQuadState {}; + +struct IoSurfaceContentQuadState {}; + +struct RenderPassQuadState {}; + +struct SolidColorQuadState { + Color color; +}; + +struct SurfaceQuadState { + SurfaceId surface; +}; + +struct TextureQuadState { + uint32 resource_id; + bool premultiplied_alpha; + mojo.PointF uv_top_left; + mojo.PointF uv_bottom_right; + Color background_color; + // Should have exactly 4 entries. + float[] vertex_opacity; + bool flipped; +}; + +struct TiledContentQuadState {}; + +struct StreamVideoQuadState {}; + +struct YUVVideoQuadState {}; + +enum Material { + CHECKERBOARD = 1, + DEBUG_BORDER, + IO_SURFACE_CONTENT, + RENDER_PASS, + SOLID_COLOR, + STREAM_VIDEO_CONTENT, + SURFACE_CONTENT, + TEXTURE_CONTENT, + TILED_CONTENT, + YUV_VIDEO_CONTENT, +}; + +struct Quad { + Material material; + + // This rect, after applying the quad_transform(), gives the geometry that + // this quad should draw to. This rect lives in content space. + mojo.Rect rect; + + // This specifies the region of the quad that is opaque. This rect lives in + // content space. + mojo.Rect opaque_rect; + + // Allows changing the rect that gets drawn to make it smaller. This value + // should be clipped to |rect|. This rect lives in content space. + mojo.Rect visible_rect; + + // Allows changing the rect that gets drawn to make it smaller. This value + // should be clipped to |rect|. This rect lives in content space. + bool needs_blending; + + // Index into the containing pass' shared quad state array which has state + // (transforms etc) shared by multiple quads. + int32 shared_quad_state_index; + + // Only one of the following will be set, depending on the material. + CheckerboardQuadState checkerboard_quad_state; + DebugBorderQuadState debug_border_quad_state; + IoSurfaceContentQuadState io_surface_quad_state; + RenderPassQuadState render_pass_quad_state; + SolidColorQuadState solid_color_quad_state; + SurfaceQuadState surface_quad_state; + TextureQuadState texture_quad_state; + TiledContentQuadState tiled_content_quad_state; + StreamVideoQuadState stream_video_quad_state; + YUVVideoQuadState yuv_video_quad_state; +}; + +enum SkXfermode { + kClear_Mode = 0, //!< [0, 0] + kSrc_Mode, //!< [Sa, Sc] + kDst_Mode, //!< [Da, Dc] + kSrcOver_Mode, //!< [Sa + Da - Sa*Da, Rc = Sc + (1 - Sa)*Dc] + kDstOver_Mode, //!< [Sa + Da - Sa*Da, Rc = Dc + (1 - Da)*Sc] + kSrcIn_Mode, //!< [Sa * Da, Sc * Da] + kDstIn_Mode, //!< [Sa * Da, Sa * Dc] + kSrcOut_Mode, //!< [Sa * (1 - Da), Sc * (1 - Da)] + kDstOut_Mode, //!< [Da * (1 - Sa), Dc * (1 - Sa)] + kSrcATop_Mode, //!< [Da, Sc * Da + (1 - Sa) * Dc] + kDstATop_Mode, //!< [Sa, Sa * Dc + Sc * (1 - Da)] + kXor_Mode, //!< [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] + kPlus_Mode, //!< [Sa + Da, Sc + Dc] + kModulate_Mode, // multiplies all components (= alpha and color) + + // Following blend modes are defined in the CSS Compositing standard: + // https://dvcs.w3.org/hg/FXTF/rawfile/tip/compositing/index.html#blending + kScreen_Mode, + kLastCoeffMode = kScreen_Mode, + + kOverlay_Mode, + kDarken_Mode, + kLighten_Mode, + kColorDodge_Mode, + kColorBurn_Mode, + kHardLight_Mode, + kSoftLight_Mode, + kDifference_Mode, + kExclusion_Mode, + kMultiply_Mode, + kLastSeparableMode = kMultiply_Mode, + + kHue_Mode, + kSaturation_Mode, + kColor_Mode, + kLuminosity_Mode, + kLastMode = kLuminosity_Mode +}; + +struct SharedQuadState { + // Transforms from quad's original content space to its target content space. + mojo.Transform content_to_target_transform; + + // This size lives in the content space for the quad's originating layer. + mojo.Size content_bounds; + + // This rect lives in the content space for the quad's originating layer. + mojo.Rect visible_content_rect; + + // This rect lives in the target content space. + mojo.Rect clip_rect; + + bool is_clipped; + float opacity; + SkXfermode blend_mode; +}; + +struct Pass { + int32 id; + mojo.Rect output_rect; + mojo.Rect damage_rect; + mojo.Transform transform_to_root_target; + bool has_transparent_pixels; + Quad[] quads; + SharedQuadState[] shared_quad_states; +}; + +} diff --git a/chromium/mojo/services/public/interfaces/surfaces/surface_id.mojom b/chromium/mojo/services/public/interfaces/surfaces/surface_id.mojom new file mode 100644 index 00000000000..42ab9924471 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/surfaces/surface_id.mojom @@ -0,0 +1,11 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.surfaces { + +struct SurfaceId { + int32 id; +}; + +} diff --git a/chromium/mojo/services/public/interfaces/surfaces/surfaces.mojom b/chromium/mojo/services/public/interfaces/surfaces/surfaces.mojom new file mode 100644 index 00000000000..6bb38b7e0b3 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/surfaces/surfaces.mojom @@ -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. + +import "../geometry/geometry.mojom" +import "quads.mojom" +import "surface_id.mojom" + +module mojo.surfaces { + +enum ResourceFormat { + RGBA_8888, + RGBA_4444, + BGRA_8888, + LUMINANCE_8, + RGB_565, + ETC1, +}; + +struct Mailbox { + // Should have exactly 64 entries. + int8[] name; +}; + +struct MailboxHolder { + Mailbox mailbox; + uint32 texture_target; + uint32 sync_point; +}; + +struct TransferableResource { + uint32 id; + ResourceFormat format; + uint32 filter; + mojo.Size size; + MailboxHolder mailbox_holder; + bool is_repeated; + bool is_software; +}; + +struct Frame { + TransferableResource[] resources; + Pass[] passes; +}; + +interface SurfaceClient { + ReturnResources(TransferableResource[] resources); +}; + +[client=SurfaceClient] +interface Surface { + CreateSurface(SurfaceId id, mojo.Size size); + SubmitFrame(SurfaceId id, Frame frame); + DestroySurface(SurfaceId id); +}; + +} diff --git a/chromium/mojo/services/public/interfaces/view_manager/view_manager.mojom b/chromium/mojo/services/public/interfaces/view_manager/view_manager.mojom new file mode 100644 index 00000000000..ea1d3b56265 --- /dev/null +++ b/chromium/mojo/services/public/interfaces/view_manager/view_manager.mojom @@ -0,0 +1,193 @@ +// 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 "../geometry/geometry.mojom" +import "../input_events/input_events.mojom" +import "view_manager_constants.mojom" + +module mojo.view_manager { + +struct NodeData { + uint32 parent_id; + uint32 node_id; + uint32 view_id; + mojo.Rect bounds; +}; + +// ViewManagerInitService is responsible for launching the client that controls +// the root node. mojo::view_manager returns an instance of this. All other +// connections are established by the client this creates. +interface ViewManagerInitService { + EmbedRoot(string url) => (bool success); +}; + +// Functions that mutate the hierarchy take a change id. This is an ever +// increasing integer used to identify the change. Every hierarchy change +// increases this value. The server only accepts changes where the supplied +// |server_change_id| matches the expected next value. This ensures changes are +// made in a well defined order. +// +// Nodes and Views are identified by a uint32. The upper 16 bits are the +// connection id, and the lower 16 the id assigned by the client. +// +// The root node is identified with a connection id of 0, and value of 1. +[Client=ViewManagerClient] +interface ViewManagerService { + // Creates a new node with the specified id. It is up to the client to ensure + // the id is unique to the connection (the id need not be globally unique). + // Additionally the connection id (embedded in |node_id|) must match that of + // the connection. + CreateNode(uint32 node_id) => (bool success); + + // Deletes a node. This does not recurse. No hierarchy change notifications + // are sent as a result of this. Only the connection that created the node can + // delete it. + DeleteNode(uint32 node_id, uint32 change_id) => (bool success); + + // Sets the specified bounds of the specified node. + SetNodeBounds(uint32 node_id, mojo.Rect bounds) => (bool success); + + // Reparents a node. See description above class for details of |change_id|. + // This fails for any of the following reasons: + // . |server_change_id| is not the expected id. + // . |parent| or |child| does not identify a valid node. + // . |child| is an ancestor of |parent|. + // . |child| is already a child of |parent|. + // + // This may result in a connection getting OnNodeDeleted(). See + // RemoveNodeFromParent for details. + AddNode(uint32 parent, + uint32 child, + uint32 server_change_id) => (bool success); + + // Removes a view from its current parent. See description above class for + // details of |change_id|. This fails if the node is not valid, + // |server_change_id| doesn't match, or the node already has no parent. + // + // Removing a node from a parent may result in OnNodeDeleted() being sent to + // other connections. For example, connection A has nodes 1 and 2, with 2 a + // child of 1. Connection B has a root 1. If 2 is removed from 1 then B gets + // OnNodeDeleted(). This is done as node 2 is effectively no longer visible to + // connection B. + RemoveNodeFromParent(uint32 node_id, + uint32 server_change_id) => (bool success); + + // Reorders a node in its parent, relative to |relative_node_id| according to + // |direction|. + // Only the connection that created the node's parent can reorder its + // children. + ReorderNode(uint32 node_id, + uint32 relative_node_id, + OrderDirection direction, + uint32 server_change_id) => (bool success); + + // Returns the nodes comprising the tree starting at |node_id|. |node_id| is + // the first result in the return value, unless |node_id| is invalid, in which + // case an empty vector is returned. The nodes are visited using a depth first + // search (pre-order). + GetNodeTree(uint32 node_id) => (NodeData[] nodes); + + // Creates a new view with the specified id. It is up to the client to ensure + // the id is unique to the connection (the id need not be globally unique). + // Additionally the connection id (embedded in |view_id|) must match that of + // the connection. + CreateView(uint32 view_id) => (bool success); + + // Deletes the view with the specified id. Only the connection that created + // the view can delete it. + DeleteView(uint32 view_id) => (bool success); + + // Sets the view a node is showing. + SetView(uint32 node_id, uint32 view_id) => (bool success); + + // Shows the specified image (png encoded) in the specified view. + SetViewContents(uint32 view_id, + handle<shared_buffer> buffer, + uint32 buffer_size) => (bool success); + + // Sets focus to the specified node. + SetFocus(uint32 node_id) => (bool success); + + // Embeds the app at |url| in the specified nodes. More specifically this + // creates a new connection to the specified url, expecting to get an + // ViewManagerClient and configures it with the root nodes |nodes|. Fails + // if |nodes| is empty or contains nodes that were not created by this + // connection. + // If a particular client invokes Embed() multiple times with the same url, + // the connection is reused. When this happens the ViewManagerClient is + // notified of the additional roots by way of OnRootsAdded(). + Embed(string url, uint32[] nodes) => (bool success); + + // TODO(sky): move these to a separate interface when FIFO works. + + // Sends OnViewInputEvent() to the owner of the specified view. + DispatchOnViewInputEvent(uint32 view_id, mojo.Event event); +}; + +// Changes to nodes/views are not sent to the connection that originated the +// change. For example, if connection 1 attaches a view to a node (SetView()) +// connection 1 does not receive OnNodeViewReplaced(). +[Client=ViewManagerService] +interface ViewManagerClient { + // Invoked once the connection has been established. |connection_id| is the id + // that uniquely identifies this connection. |next_server_change_id| is the + // id of the next change the server is expecting. |nodes| are the nodes + // parented to the root. + OnViewManagerConnectionEstablished(uint16 connection_id, + string creator_url, + uint32 next_server_change_id, + NodeData[] nodes); + + // See description of ViewManagerService::Connect() for details as to when + // this is invoked. + OnRootsAdded(NodeData[] nodes); + + // This is sent to clients when a change is made to the server that results + // in the |server_change_id| changing but the client isn't notified. This is + // not sent if the client receives a callback giving a new + // |server_change_id|. For example, if a client 1 changes the hierarchy in + // some way but client 2 isn't notified of the change, then client 2 gets + // OnServerChangeIdAdvanced(). + OnServerChangeIdAdvanced(uint32 next_server_change_id); + + // Invoked when a node's bounds have changed. + OnNodeBoundsChanged(uint32 node, mojo.Rect old_bounds, mojo.Rect new_bounds); + + // Invoked when a change is done to the hierarchy. A value of 0 is used to + // identify a null node. For example, if the old_parent is NULL, 0 is + // supplied. See description above ViewManager for details on the change ids. + // |nodes| contains any nodes that are that the client has not been told + // about. This is not sent for hierarchy changes of nodes not known to this + // client or not attached to the tree. + OnNodeHierarchyChanged(uint32 node, + uint32 new_parent, + uint32 old_parent, + uint32 server_change_id, + NodeData[] nodes); + + // Invoked when the order of nodes within a parent changes. + OnNodeReordered(uint32 node_id, + uint32 relative_node_id, + OrderDirection direction, + uint32 server_change_id); + + // Invoked when a node is deleted. + OnNodeDeleted(uint32 node, uint32 server_change_id); + + // Invoked when the view associated with a node is replaced by another view. + // 0 is used to identify a null view. + OnNodeViewReplaced(uint32 node, uint32 new_view_id, uint32 old_view_id); + + // Invoked when a view is deleted. + OnViewDeleted(uint32 view); + + // Invoked when an event is targeted at the specified view. + OnViewInputEvent(uint32 view, mojo.Event event) => (); + + // TODO(sky): move to separate interface when FIFO sorted out. + + DispatchOnViewInputEvent(uint32 view, mojo.Event event); +}; + +} diff --git a/chromium/mojo/services/public/interfaces/view_manager/view_manager_constants.mojom b/chromium/mojo/services/public/interfaces/view_manager/view_manager_constants.mojom new file mode 100644 index 00000000000..5c73190b70f --- /dev/null +++ b/chromium/mojo/services/public/interfaces/view_manager/view_manager_constants.mojom @@ -0,0 +1,12 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.view_manager { + +enum OrderDirection { + ORDER_ABOVE = 1, + ORDER_BELOW, +}; + +} diff --git a/chromium/mojo/services/test_service/test_service.mojom b/chromium/mojo/services/test_service/test_service.mojom new file mode 100644 index 00000000000..1065b20ab30 --- /dev/null +++ b/chromium/mojo/services/test_service/test_service.mojom @@ -0,0 +1,11 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test { + +interface ITestService { + Ping() => (); +}; + +} // module mojo.test diff --git a/chromium/mojo/services/test_service/test_service_application.cc b/chromium/mojo/services/test_service/test_service_application.cc new file mode 100644 index 00000000000..3737823c9d2 --- /dev/null +++ b/chromium/mojo/services/test_service/test_service_application.cc @@ -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. + +#include "mojo/services/test_service/test_service_application.h" + +#include <assert.h> + +#include "mojo/public/cpp/utility/run_loop.h" +#include "mojo/services/test_service/test_service_impl.h" + +namespace mojo { +namespace test { + +TestServiceApplication::TestServiceApplication() : ref_count_(0) { +} + +TestServiceApplication::~TestServiceApplication() { +} + +void TestServiceApplication::Initialize() { + AddService<TestServiceImpl>(this); +} + +void TestServiceApplication::AddRef() { + assert(ref_count_ >= 0); + ref_count_++; +} + +void TestServiceApplication::ReleaseRef() { + assert(ref_count_ > 0); + ref_count_--; + if (ref_count_ <= 0) + RunLoop::current()->Quit(); +} + +} // namespace test + +// static +Application* Application::Create() { + return new mojo::test::TestServiceApplication(); +} + +} // namespace mojo diff --git a/chromium/mojo/services/test_service/test_service_application.h b/chromium/mojo/services/test_service/test_service_application.h new file mode 100644 index 00000000000..9f042c1987a --- /dev/null +++ b/chromium/mojo/services/test_service/test_service_application.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_ +#define MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_ + +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +namespace test { + +class TestServiceApplication : public Application { + public: + TestServiceApplication(); + virtual ~TestServiceApplication(); + + virtual void Initialize() MOJO_OVERRIDE; + + void AddRef(); + void ReleaseRef(); + + private: + int ref_count_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceApplication); +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_ diff --git a/chromium/mojo/services/test_service/test_service_impl.cc b/chromium/mojo/services/test_service/test_service_impl.cc new file mode 100644 index 00000000000..a38d619d90e --- /dev/null +++ b/chromium/mojo/services/test_service/test_service_impl.cc @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/test_service/test_service_impl.h" + +#include "mojo/services/test_service/test_service_application.h" + +namespace mojo { +namespace test { + +TestServiceImpl::TestServiceImpl(TestServiceApplication* application) + : application_(application) { +} + +TestServiceImpl::~TestServiceImpl() { +} + +void TestServiceImpl::OnConnectionEstablished() { + application_->AddRef(); +} + +void TestServiceImpl::OnConnectionError() { + application_->ReleaseRef(); +} + +void TestServiceImpl::Ping(const mojo::Callback<void()>& callback) { + callback.Run(); +} + +} // namespace test +} // namespace mojo diff --git a/chromium/mojo/services/test_service/test_service_impl.h b/chromium/mojo/services/test_service/test_service_impl.h new file mode 100644 index 00000000000..df2d4a160be --- /dev/null +++ b/chromium/mojo/services/test_service/test_service_impl.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_ +#define MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_ + +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/test_service/test_service.mojom.h" + +namespace mojo { +namespace test { + +class TestServiceApplication; + +class TestServiceImpl : public InterfaceImpl<ITestService> { + public: + explicit TestServiceImpl(TestServiceApplication* application); + virtual ~TestServiceImpl(); + + // |ITestService| methods: + virtual void OnConnectionEstablished() MOJO_OVERRIDE; + virtual void OnConnectionError() MOJO_OVERRIDE; + virtual void Ping(const mojo::Callback<void()>& callback) MOJO_OVERRIDE; + + private: + TestServiceApplication* const application_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceImpl); +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_ diff --git a/chromium/mojo/services/view_manager/DEPS b/chromium/mojo/services/view_manager/DEPS new file mode 100644 index 00000000000..e6f1501ffe1 --- /dev/null +++ b/chromium/mojo/services/view_manager/DEPS @@ -0,0 +1,22 @@ +include_rules = [ + "+cc", + "+mojo/aura", + "+mojo/cc", + "+mojo/geometry", + "+mojo/services", + "+third_party/skia", + "+ui/aura", + "+ui/base/cursor/cursor.h", + "+ui/base/hit_test.h", + "+ui/compositor", + "+ui/events", + "+ui/gfx", + "+ui/gl", + "+webkit/common/gpu", +] + +specific_include_rules = { + "view_manager_unittest.cc": [ + "+mojo/service_manager/service_manager.h", + ], +} diff --git a/chromium/mojo/services/view_manager/context_factory_impl.cc b/chromium/mojo/services/view_manager/context_factory_impl.cc new file mode 100644 index 00000000000..c3977e7d2e4 --- /dev/null +++ b/chromium/mojo/services/view_manager/context_factory_impl.cc @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/context_factory_impl.h" + +#include "cc/output/output_surface.h" +#include "mojo/cc/context_provider_mojo.h" +#include "ui/compositor/reflector.h" +#include "ui/gl/gl_implementation.h" +#include "ui/gl/gl_surface.h" +#include "webkit/common/gpu/context_provider_in_process.h" +#include "webkit/common/gpu/grcontext_for_webgraphicscontext3d.h" +#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h" + +namespace mojo { +namespace view_manager { +namespace service { + +ContextFactoryImpl::ContextFactoryImpl( + ScopedMessagePipeHandle command_buffer_handle) + : command_buffer_handle_(command_buffer_handle.Pass()) { +} + +ContextFactoryImpl::~ContextFactoryImpl() { +} + +scoped_ptr<cc::OutputSurface> ContextFactoryImpl::CreateOutputSurface( + ui::Compositor* compositor, bool software_fallback) { + return make_scoped_ptr( + new cc::OutputSurface( + new ContextProviderMojo(command_buffer_handle_.Pass()))); +} + +scoped_refptr<ui::Reflector> ContextFactoryImpl::CreateReflector( + ui::Compositor* mirroed_compositor, + ui::Layer* mirroring_layer) { + return NULL; +} + +void ContextFactoryImpl::RemoveReflector( + scoped_refptr<ui::Reflector> reflector) { +} + +scoped_refptr<cc::ContextProvider> +ContextFactoryImpl::SharedMainThreadContextProvider() { + if (!shared_main_thread_contexts_ || + shared_main_thread_contexts_->DestroyedOnMainThread()) { + bool lose_context_when_out_of_memory = false; + shared_main_thread_contexts_ = + webkit::gpu::ContextProviderInProcess::CreateOffscreen( + lose_context_when_out_of_memory); + if (shared_main_thread_contexts_ && + !shared_main_thread_contexts_->BindToCurrentThread()) + shared_main_thread_contexts_ = NULL; + } + return shared_main_thread_contexts_; +} + +void ContextFactoryImpl::RemoveCompositor(ui::Compositor* compositor) { +} + +bool ContextFactoryImpl::DoesCreateTestContexts() { return false; } + +cc::SharedBitmapManager* ContextFactoryImpl::GetSharedBitmapManager() { + return NULL; +} + +base::MessageLoopProxy* ContextFactoryImpl::GetCompositorMessageLoop() { + return NULL; +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/context_factory_impl.h b/chromium/mojo/services/view_manager/context_factory_impl.h new file mode 100644 index 00000000000..30cd26754b8 --- /dev/null +++ b/chromium/mojo/services/view_manager/context_factory_impl.h @@ -0,0 +1,56 @@ +// 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 MOJO_SERVICES_VIEW_MANAGER_CONTEXT_FACTORY_IMPL_H_ +#define MOJO_SERVICES_VIEW_MANAGER_CONTEXT_FACTORY_IMPL_H_ + +#include "mojo/public/cpp/system/core.h" +#include "ui/compositor/compositor.h" + +namespace webkit { +namespace gpu { +class ContextProviderInProcess; +} +} + +namespace mojo { +namespace view_manager { +namespace service { + +// The default factory that creates in-process contexts. +class ContextFactoryImpl : public ui::ContextFactory { + public: + explicit ContextFactoryImpl(ScopedMessagePipeHandle command_buffer_handle); + virtual ~ContextFactoryImpl(); + + // ContextFactory implementation + virtual scoped_ptr<cc::OutputSurface> CreateOutputSurface( + ui::Compositor* compositor, bool software_fallback) OVERRIDE; + + virtual scoped_refptr<ui::Reflector> CreateReflector( + ui::Compositor* compositor, + ui::Layer* layer) OVERRIDE; + virtual void RemoveReflector(scoped_refptr<ui::Reflector> reflector) OVERRIDE; + + virtual scoped_refptr<cc::ContextProvider> + SharedMainThreadContextProvider() OVERRIDE; + virtual void RemoveCompositor(ui::Compositor* compositor) OVERRIDE; + virtual bool DoesCreateTestContexts() OVERRIDE; + virtual cc::SharedBitmapManager* GetSharedBitmapManager() OVERRIDE; + virtual base::MessageLoopProxy* GetCompositorMessageLoop() OVERRIDE; + + private: + scoped_refptr<webkit::gpu::ContextProviderInProcess> + shared_main_thread_contexts_; + + ScopedMessagePipeHandle command_buffer_handle_; + + DISALLOW_COPY_AND_ASSIGN(ContextFactoryImpl); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_CONTEXT_FACTORY_IMPL_H_ diff --git a/chromium/mojo/services/view_manager/ids.h b/chromium/mojo/services/view_manager/ids.h new file mode 100644 index 00000000000..8d906beecb6 --- /dev/null +++ b/chromium/mojo/services/view_manager/ids.h @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_IDS_H_ +#define MOJO_SERVICES_VIEW_MANAGER_IDS_H_ + +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/cpp/view_manager/util.h" +#include "mojo/services/view_manager/view_manager_export.h" + +namespace mojo { +namespace view_manager { +namespace service { + +// Connection id reserved for the root. +const ConnectionSpecificId kRootConnection = 0; + +// TODO(sky): remove this, temporary while window manager API is in existing +// api. +const ConnectionSpecificId kWindowManagerConnection = 1; + +// Adds a bit of type safety to node ids. +struct MOJO_VIEW_MANAGER_EXPORT NodeId { + NodeId(ConnectionSpecificId connection_id, ConnectionSpecificId node_id) + : connection_id(connection_id), + node_id(node_id) {} + NodeId() : connection_id(0), node_id(0) {} + + bool operator==(const NodeId& other) const { + return other.connection_id == connection_id && + other.node_id == node_id; + } + + bool operator!=(const NodeId& other) const { + return !(*this == other); + } + + ConnectionSpecificId connection_id; + ConnectionSpecificId node_id; +}; + +// Adds a bit of type safety to view ids. +struct MOJO_VIEW_MANAGER_EXPORT ViewId { + ViewId(ConnectionSpecificId connection_id, ConnectionSpecificId view_id) + : connection_id(connection_id), + view_id(view_id) {} + ViewId() : connection_id(0), view_id(0) {} + + bool operator==(const ViewId& other) const { + return other.connection_id == connection_id && + other.view_id == view_id; + } + + bool operator!=(const ViewId& other) const { + return !(*this == other); + } + + ConnectionSpecificId connection_id; + ConnectionSpecificId view_id; +}; + +// Functions for converting to/from structs and transport values. +inline NodeId NodeIdFromTransportId(Id id) { + return NodeId(HiWord(id), LoWord(id)); +} + +inline Id NodeIdToTransportId(const NodeId& id) { + return (id.connection_id << 16) | id.node_id; +} + +inline ViewId ViewIdFromTransportId(Id id) { + return ViewId(HiWord(id), LoWord(id)); +} + +inline Id ViewIdToTransportId(const ViewId& id) { + return (id.connection_id << 16) | id.view_id; +} + +inline NodeId RootNodeId() { + return NodeId(kRootConnection, 1); +} + +// Returns a NodeId that is reserved to indicate no node. That is, no node will +// ever be created with this id. +inline NodeId InvalidNodeId() { + return NodeId(kRootConnection, 0); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_IDS_H_ diff --git a/chromium/mojo/services/view_manager/main.cc b/chromium/mojo/services/view_manager/main.cc new file mode 100644 index 00000000000..de0a7684095 --- /dev/null +++ b/chromium/mojo/services/view_manager/main.cc @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/view_manager/view_manager_init_service_impl.h" + +namespace mojo { +namespace view_manager { +namespace service { + +class ViewManagerApp : public Application { + public: + ViewManagerApp() {} + virtual ~ViewManagerApp() {} + + virtual void Initialize() MOJO_OVERRIDE { + // TODO(sky): this needs some sort of authentication as well as making sure + // we only ever have one active at a time. + AddService<ViewManagerInitServiceImpl>(service_provider()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ViewManagerApp); +}; + +} // namespace service +} // namespace view_manager + +// static +Application* Application::Create() { + return new mojo::view_manager::service::ViewManagerApp(); +} + +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/node.cc b/chromium/mojo/services/view_manager/node.cc new file mode 100644 index 00000000000..cbdd16d712b --- /dev/null +++ b/chromium/mojo/services/view_manager/node.cc @@ -0,0 +1,189 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/node.h" + +#include "mojo/services/view_manager/node_delegate.h" +#include "mojo/services/view_manager/view.h" +#include "ui/aura/window_property.h" +#include "ui/base/cursor/cursor.h" +#include "ui/base/hit_test.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/native_widget_types.h" + +DECLARE_WINDOW_PROPERTY_TYPE(mojo::view_manager::service::Node*); + +namespace mojo { +namespace view_manager { +namespace service { + +DEFINE_WINDOW_PROPERTY_KEY(Node*, kNodeKey, NULL); + +Node::Node(NodeDelegate* delegate, const NodeId& id) + : delegate_(delegate), + id_(id), + view_(NULL), + window_(this) { + DCHECK(delegate); // Must provide a delegate. + window_.set_owned_by_parent(false); + window_.AddObserver(this); + window_.SetProperty(kNodeKey, this); + window_.Init(aura::WINDOW_LAYER_TEXTURED); + + // TODO(sky): this likely needs to be false and add a visibility API. + window_.Show(); +} + +Node::~Node() { + SetView(NULL); + // This is implicitly done during deletion of the window, but we do it here so + // that we're in a known state. + if (window_.parent()) + window_.parent()->RemoveChild(&window_); +} + +const Node* Node::GetParent() const { + if (!window_.parent()) + return NULL; + return window_.parent()->GetProperty(kNodeKey); +} + +void Node::Add(Node* child) { + window_.AddChild(&child->window_); +} + +void Node::Remove(Node* child) { + window_.RemoveChild(&child->window_); +} + +void Node::Reorder(Node* child, Node* relative, OrderDirection direction) { + if (direction == ORDER_ABOVE) + window_.StackChildAbove(child->window(), relative->window()); + else if (direction == ORDER_BELOW) + window_.StackChildBelow(child->window(), relative->window()); +} + +const Node* Node::GetRoot() const { + const aura::Window* window = &window_; + while (window && window->parent()) + window = window->parent(); + return window->GetProperty(kNodeKey); +} + +std::vector<const Node*> Node::GetChildren() const { + std::vector<const Node*> children; + children.reserve(window_.children().size()); + for (size_t i = 0; i < window_.children().size(); ++i) + children.push_back(window_.children()[i]->GetProperty(kNodeKey)); + return children; +} + +std::vector<Node*> Node::GetChildren() { + std::vector<Node*> children; + children.reserve(window_.children().size()); + for (size_t i = 0; i < window_.children().size(); ++i) + children.push_back(window_.children()[i]->GetProperty(kNodeKey)); + return children; +} + +bool Node::Contains(const Node* node) const { + return node && window_.Contains(&(node->window_)); +} + +void Node::SetView(View* view) { + if (view == view_) + return; + + // Detach view from existing node. This way notifications are sent out. + if (view && view->node()) + view->node()->SetView(NULL); + + View* old_view = view_; + if (view_) + view_->set_node(NULL); + view_ = view; + if (view) + view->set_node(this); + delegate_->OnNodeViewReplaced(this, view, old_view); +} + +void Node::OnWindowHierarchyChanged( + const aura::WindowObserver::HierarchyChangeParams& params) { + if (params.target != &window_ || params.receiver != &window_) + return; + const Node* new_parent = params.new_parent ? + params.new_parent->GetProperty(kNodeKey) : NULL; + const Node* old_parent = params.old_parent ? + params.old_parent->GetProperty(kNodeKey) : NULL; + delegate_->OnNodeHierarchyChanged(this, new_parent, old_parent); +} + +gfx::Size Node::GetMinimumSize() const { + return gfx::Size(); +} + +gfx::Size Node::GetMaximumSize() const { + return gfx::Size(); +} + +void Node::OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { +} + +gfx::NativeCursor Node::GetCursor(const gfx::Point& point) { + return gfx::kNullCursor; +} + +int Node::GetNonClientComponent(const gfx::Point& point) const { + return HTCAPTION; +} + +bool Node::ShouldDescendIntoChildForEventHandling( + aura::Window* child, + const gfx::Point& location) { + return true; +} + +bool Node::CanFocus() { + return true; +} + +void Node::OnCaptureLost() { +} + +void Node::OnPaint(gfx::Canvas* canvas) { + if (view_) { + canvas->DrawImageInt( + gfx::ImageSkia::CreateFrom1xBitmap(view_->bitmap()), 0, 0); + } +} + +void Node::OnDeviceScaleFactorChanged(float device_scale_factor) { +} + +void Node::OnWindowDestroying(aura::Window* window) { +} + +void Node::OnWindowDestroyed(aura::Window* window) { +} + +void Node::OnWindowTargetVisibilityChanged(bool visible) { +} + +bool Node::HasHitTestMask() const { + return false; +} + +void Node::GetHitTestMask(gfx::Path* mask) const { +} + +void Node::OnEvent(ui::Event* event) { + if (view_) + delegate_->OnViewInputEvent(view_, event); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/node.h b/chromium/mojo/services/view_manager/node.h new file mode 100644 index 00000000000..bfee6752378 --- /dev/null +++ b/chromium/mojo/services/view_manager/node.h @@ -0,0 +1,112 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_NODE_H_ +#define MOJO_SERVICES_VIEW_MANAGER_NODE_H_ + +#include <vector> + +#include "base/logging.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/view_manager_export.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/aura/window_observer.h" + +namespace mojo { +namespace view_manager { +namespace service { + +class NodeDelegate; +class View; + +// Represents a node in the graph. Delegate is informed of interesting events. +class MOJO_VIEW_MANAGER_EXPORT Node + : public aura::WindowObserver, + public aura::WindowDelegate { + public: + Node(NodeDelegate* delegate, const NodeId& id); + virtual ~Node(); + + void set_view_id(const ViewId& view_id) { view_id_ = view_id; } + const ViewId& view_id() const { return view_id_; } + + const NodeId& id() const { return id_; } + + void Add(Node* child); + void Remove(Node* child); + + void Reorder(Node* child, Node* relative, OrderDirection direction); + + aura::Window* window() { return &window_; } + + const gfx::Rect& bounds() const { return window_.bounds(); } + + const Node* GetParent() const; + Node* GetParent() { + return const_cast<Node*>(const_cast<const Node*>(this)->GetParent()); + } + + const Node* GetRoot() const; + Node* GetRoot() { + return const_cast<Node*>(const_cast<const Node*>(this)->GetRoot()); + } + + std::vector<const Node*> GetChildren() const; + std::vector<Node*> GetChildren(); + + bool Contains(const Node* node) const; + + // Sets the view associated with this node. Node does not own its View. + void SetView(View* view); + View* view() { return view_; } + const View* view() const { return view_; } + + private: + // WindowObserver overrides: + virtual void OnWindowHierarchyChanged( + const aura::WindowObserver::HierarchyChangeParams& params) OVERRIDE; + + // WindowDelegate overrides: + virtual gfx::Size GetMinimumSize() const OVERRIDE; + virtual gfx::Size GetMaximumSize() const OVERRIDE; + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE; + virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE; + virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE; + virtual bool ShouldDescendIntoChildForEventHandling( + aura::Window* child, + const gfx::Point& location) OVERRIDE; + virtual bool CanFocus() OVERRIDE; + virtual void OnCaptureLost() OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE; + virtual bool HasHitTestMask() const OVERRIDE; + virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE; + + // ui::EventHandler overrides: + virtual void OnEvent(ui::Event* event) OVERRIDE; + + NodeDelegate* delegate_; + const NodeId id_; + + // Weak pointer to view associated with this node. + View* view_; + + ViewId view_id_; + + aura::Window window_; + + DISALLOW_COPY_AND_ASSIGN(Node); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_NODE_H_ diff --git a/chromium/mojo/services/view_manager/node_delegate.h b/chromium/mojo/services/view_manager/node_delegate.h new file mode 100644 index 00000000000..d63acc91998 --- /dev/null +++ b/chromium/mojo/services/view_manager/node_delegate.h @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_NODE_DELEGATE_H_ +#define MOJO_SERVICES_VIEW_MANAGER_NODE_DELEGATE_H_ + +#include "mojo/services/view_manager/view_manager_export.h" + +namespace ui { +class Event; +} + +namespace mojo { +namespace view_manager { +namespace service { + +class Node; +class View; + +class MOJO_VIEW_MANAGER_EXPORT NodeDelegate { + public: + // Invoked when the hierarchy has changed. + virtual void OnNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent) = 0; + + // Invoked when the View associated with a node changes. + virtual void OnNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view) = 0; + + // Invoked when an input event is received by the View at this node. + virtual void OnViewInputEvent(const View* view, + const ui::Event* event) = 0; + + protected: + virtual ~NodeDelegate() {} +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_NODE_DELEGATE_H_ diff --git a/chromium/mojo/services/view_manager/root_node_manager.cc b/chromium/mojo/services/view_manager/root_node_manager.cc new file mode 100644 index 00000000000..f5c681fc7a1 --- /dev/null +++ b/chromium/mojo/services/view_manager/root_node_manager.cc @@ -0,0 +1,265 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/root_node_manager.h" + +#include "base/logging.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/public/cpp/input_events/input_events_type_converters.h" +#include "mojo/services/view_manager/view.h" +#include "mojo/services/view_manager/view_manager_service_impl.h" +#include "ui/aura/env.h" + +namespace mojo { +namespace view_manager { +namespace service { + +RootNodeManager::ScopedChange::ScopedChange( + ViewManagerServiceImpl* connection, + RootNodeManager* root, + RootNodeManager::ChangeType change_type, + bool is_delete_node) + : root_(root), + connection_id_(connection->id()), + change_type_(change_type), + is_delete_node_(is_delete_node) { + root_->PrepareForChange(this); +} + +RootNodeManager::ScopedChange::~ScopedChange() { + root_->FinishChange(); +} + +RootNodeManager::Context::Context() { + // Pass in false as native viewport creates the PlatformEventSource. + aura::Env::CreateInstance(false); +} + +RootNodeManager::Context::~Context() { + aura::Env::DeleteInstance(); +} + +RootNodeManager::RootNodeManager(ServiceProvider* service_provider, + RootViewManagerDelegate* view_manager_delegate) + : service_provider_(service_provider), + next_connection_id_(1), + next_server_change_id_(1), + root_view_manager_(service_provider, this, view_manager_delegate), + root_(this, RootNodeId()), + current_change_(NULL) { +} + +RootNodeManager::~RootNodeManager() { + while (!connections_created_by_connect_.empty()) + delete *(connections_created_by_connect_.begin()); + // All the connections should have been destroyed. + DCHECK(connection_map_.empty()); +} + +ConnectionSpecificId RootNodeManager::GetAndAdvanceNextConnectionId() { + const ConnectionSpecificId id = next_connection_id_++; + DCHECK_LT(id, next_connection_id_); + return id; +} + +void RootNodeManager::AddConnection(ViewManagerServiceImpl* connection) { + DCHECK_EQ(0u, connection_map_.count(connection->id())); + connection_map_[connection->id()] = connection; +} + +void RootNodeManager::RemoveConnection(ViewManagerServiceImpl* connection) { + connection_map_.erase(connection->id()); + connections_created_by_connect_.erase(connection); + + // Notify remaining connections so that they can cleanup. + for (ConnectionMap::const_iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->OnViewManagerServiceImplDestroyed(connection->id()); + } +} + +void RootNodeManager::EmbedRoot(const std::string& url) { + CHECK(connection_map_.empty()); + Array<Id> roots(0); + EmbedImpl(kRootConnection, String::From(url), roots); +} + +void RootNodeManager::Embed(ConnectionSpecificId creator_id, + const String& url, + const Array<Id>& node_ids) { + CHECK_GT(node_ids.size(), 0u); + EmbedImpl(creator_id, url, node_ids)->set_delete_on_connection_error(); +} + +ViewManagerServiceImpl* RootNodeManager::GetConnection( + ConnectionSpecificId connection_id) { + ConnectionMap::iterator i = connection_map_.find(connection_id); + return i == connection_map_.end() ? NULL : i->second; +} + +Node* RootNodeManager::GetNode(const NodeId& id) { + if (id == root_.id()) + return &root_; + ConnectionMap::iterator i = connection_map_.find(id.connection_id); + return i == connection_map_.end() ? NULL : i->second->GetNode(id); +} + +View* RootNodeManager::GetView(const ViewId& id) { + ConnectionMap::iterator i = connection_map_.find(id.connection_id); + return i == connection_map_.end() ? NULL : i->second->GetView(id); +} + +void RootNodeManager::OnConnectionMessagedClient(ConnectionSpecificId id) { + if (current_change_) + current_change_->MarkConnectionAsMessaged(id); +} + +bool RootNodeManager::DidConnectionMessageClient( + ConnectionSpecificId id) const { + return current_change_ && current_change_->DidMessageConnection(id); +} + +ViewManagerServiceImpl* RootNodeManager::GetConnectionByCreator( + ConnectionSpecificId creator_id, + const std::string& url) const { + for (ConnectionMap::const_iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + if (i->second->creator_id() == creator_id && i->second->url() == url) + return i->second; + } + return NULL; +} + +void RootNodeManager::DispatchViewInputEventToWindowManager( + const View* view, + const ui::Event* event) { + // Input events are forwarded to the WindowManager. The WindowManager + // eventually calls back to us with DispatchOnViewInputEvent(). + ViewManagerServiceImpl* connection = GetConnection(kWindowManagerConnection); + if (!connection) + return; + connection->client()->DispatchOnViewInputEvent( + ViewIdToTransportId(view->id()), + TypeConverter<EventPtr, ui::Event>::ConvertFrom(*event)); +} + +void RootNodeManager::ProcessNodeBoundsChanged(const Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + for (ConnectionMap::iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->ProcessNodeBoundsChanged(node, old_bounds, new_bounds, + IsChangeSource(i->first)); + } +} + +void RootNodeManager::ProcessNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent) { + for (ConnectionMap::iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->ProcessNodeHierarchyChanged( + node, new_parent, old_parent, next_server_change_id_, + IsChangeSource(i->first)); + } +} + +void RootNodeManager::ProcessNodeReorder(const Node* node, + const Node* relative_node, + const OrderDirection direction) { + for (ConnectionMap::iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->ProcessNodeReorder( + node, relative_node, direction, next_server_change_id_, + IsChangeSource(i->first)); + } +} + +void RootNodeManager::ProcessNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view) { + for (ConnectionMap::iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->ProcessNodeViewReplaced(node, new_view, old_view, + IsChangeSource(i->first)); + } +} + +void RootNodeManager::ProcessNodeDeleted(const NodeId& node) { + for (ConnectionMap::iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->ProcessNodeDeleted(node, next_server_change_id_, + IsChangeSource(i->first)); + } +} + +void RootNodeManager::ProcessViewDeleted(const ViewId& view) { + for (ConnectionMap::iterator i = connection_map_.begin(); + i != connection_map_.end(); ++i) { + i->second->ProcessViewDeleted(view, IsChangeSource(i->first)); + } +} + +void RootNodeManager::PrepareForChange(ScopedChange* change) { + // Should only ever have one change in flight. + CHECK(!current_change_); + current_change_ = change; +} + +void RootNodeManager::FinishChange() { + // PrepareForChange/FinishChange should be balanced. + CHECK(current_change_); + if (current_change_->change_type() == CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID) + next_server_change_id_++; + current_change_ = NULL; +} + +ViewManagerServiceImpl* RootNodeManager::EmbedImpl( + const ConnectionSpecificId creator_id, + const String& url, + const Array<Id>& node_ids) { + MessagePipe pipe; + service_provider_->ConnectToService( + url, + ViewManagerServiceImpl::Client::Name_, + pipe.handle1.Pass(), + String()); + + std::string creator_url; + ConnectionMap::const_iterator it = connection_map_.find(creator_id); + if (it != connection_map_.end()) + creator_url = it->second->url(); + + ViewManagerServiceImpl* connection = + new ViewManagerServiceImpl(this, + creator_id, + creator_url, + url.To<std::string>()); + connection->SetRoots(node_ids); + BindToPipe(connection, pipe.handle0.Pass()); + connections_created_by_connect_.insert(connection); + return connection; +} + +void RootNodeManager::OnNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent) { + if (!root_view_manager_.in_setup()) + ProcessNodeHierarchyChanged(node, new_parent, old_parent); +} + +void RootNodeManager::OnNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view) { + ProcessNodeViewReplaced(node, new_view, old_view); +} + +void RootNodeManager::OnViewInputEvent(const View* view, + const ui::Event* event) { + DispatchViewInputEventToWindowManager(view, event); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/root_node_manager.h b/chromium/mojo/services/view_manager/root_node_manager.h new file mode 100644 index 00000000000..dba45d922c9 --- /dev/null +++ b/chromium/mojo/services/view_manager/root_node_manager.h @@ -0,0 +1,224 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_ROOT_NODE_MANAGER_H_ +#define MOJO_SERVICES_VIEW_MANAGER_ROOT_NODE_MANAGER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/node.h" +#include "mojo/services/view_manager/node_delegate.h" +#include "mojo/services/view_manager/root_view_manager.h" +#include "mojo/services/view_manager/view_manager_export.h" + +namespace ui { +class Event; +} + +namespace mojo { + +class ServiceProvider; + +namespace view_manager { +namespace service { + +class RootViewManagerDelegate; +class View; +class ViewManagerServiceImpl; + +// RootNodeManager is responsible for managing the set of +// ViewManagerServiceImpls as well as providing the root of the node hierarchy. +class MOJO_VIEW_MANAGER_EXPORT RootNodeManager : public NodeDelegate { + public: + // Used to indicate if the server id should be incremented after notifiying + // clients of the change. + enum ChangeType { + CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID, + CHANGE_TYPE_DONT_ADVANCE_SERVER_CHANGE_ID, + }; + + // Create when a ViewManagerServiceImpl is about to make a change. Ensures + // clients are notified of the correct change id. + class ScopedChange { + public: + ScopedChange(ViewManagerServiceImpl* connection, + RootNodeManager* root, + RootNodeManager::ChangeType change_type, + bool is_delete_node); + ~ScopedChange(); + + ConnectionSpecificId connection_id() const { return connection_id_; } + ChangeType change_type() const { return change_type_; } + bool is_delete_node() const { return is_delete_node_; } + + // Marks the connection with the specified id as having seen a message. + void MarkConnectionAsMessaged(ConnectionSpecificId connection_id) { + message_ids_.insert(connection_id); + } + + // Returns true if MarkConnectionAsMessaged(connection_id) was invoked. + bool DidMessageConnection(ConnectionSpecificId connection_id) const { + return message_ids_.count(connection_id) > 0; + } + + private: + RootNodeManager* root_; + const ConnectionSpecificId connection_id_; + const ChangeType change_type_; + const bool is_delete_node_; + + // See description of MarkConnectionAsMessaged/DidMessageConnection. + std::set<ConnectionSpecificId> message_ids_; + + DISALLOW_COPY_AND_ASSIGN(ScopedChange); + }; + + RootNodeManager(ServiceProvider* service_provider, + RootViewManagerDelegate* view_manager_delegate); + virtual ~RootNodeManager(); + + // Returns the id for the next ViewManagerServiceImpl. + ConnectionSpecificId GetAndAdvanceNextConnectionId(); + + Id next_server_change_id() const { + return next_server_change_id_; + } + + void AddConnection(ViewManagerServiceImpl* connection); + void RemoveConnection(ViewManagerServiceImpl* connection); + + // Establishes the initial client. Similar to Connect(), but the resulting + // client is allowed to do anything. + void EmbedRoot(const std::string& url); + + // See description of ViewManagerService::Embed() for details. This assumes + // |node_ids| has been validated. + void Embed(ConnectionSpecificId creator_id, + const String& url, + const Array<Id>& node_ids); + + // Returns the connection by id. + ViewManagerServiceImpl* GetConnection(ConnectionSpecificId connection_id); + + // Returns the Node identified by |id|. + Node* GetNode(const NodeId& id); + + // Returns the View identified by |id|. + View* GetView(const ViewId& id); + + Node* root() { return &root_; } + + bool IsProcessingChange() const { return current_change_ != NULL; } + + bool is_processing_delete_node() const { + return current_change_ && current_change_->is_delete_node(); } + + // Invoked when a connection messages a client about the change. This is used + // to avoid sending ServerChangeIdAdvanced() unnecessarily. + void OnConnectionMessagedClient(ConnectionSpecificId id); + + // Returns true if OnConnectionMessagedClient() was invoked for id. + bool DidConnectionMessageClient(ConnectionSpecificId id) const; + + ViewManagerServiceImpl* GetConnectionByCreator( + ConnectionSpecificId creator_id, + const std::string& url) const; + + void DispatchViewInputEventToWindowManager(const View* view, + const ui::Event* event); + + // These functions trivially delegate to all ViewManagerServiceImpls, which in + // term notify their clients. + void ProcessNodeBoundsChanged(const Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds); + void ProcessNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent); + void ProcessNodeReorder(const Node* node, + const Node* relative_node, + const OrderDirection direction); + void ProcessNodeViewReplaced(const Node* node, + const View* new_view_id, + const View* old_view_id); + void ProcessNodeDeleted(const NodeId& node); + void ProcessViewDeleted(const ViewId& view); + + private: + // Used to setup any static state needed by RootNodeManager. + struct Context { + Context(); + ~Context(); + }; + + typedef std::map<ConnectionSpecificId, ViewManagerServiceImpl*> ConnectionMap; + + // Invoked when a connection is about to make a change. Subsequently followed + // by FinishChange() once the change is done. + // + // Changes should never nest, meaning each PrepareForChange() must be + // balanced with a call to FinishChange() with no PrepareForChange() + // in between. + void PrepareForChange(ScopedChange* change); + + // Balances a call to PrepareForChange(). + void FinishChange(); + + // Returns true if the specified connection originated the current change. + bool IsChangeSource(ConnectionSpecificId connection_id) const { + return current_change_ && current_change_->connection_id() == connection_id; + } + + // Implementation of the two embed variants. + ViewManagerServiceImpl* EmbedImpl(ConnectionSpecificId creator_id, + const String& url, + const Array<Id>& node_ids); + + // Overridden from NodeDelegate: + virtual void OnNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent) OVERRIDE; + virtual void OnNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view) OVERRIDE; + virtual void OnViewInputEvent(const View* view, + const ui::Event* event) OVERRIDE; + + Context context_; + + ServiceProvider* service_provider_; + + // ID to use for next ViewManagerServiceImpl. + ConnectionSpecificId next_connection_id_; + + Id next_server_change_id_; + + // Set of ViewManagerServiceImpls. + ConnectionMap connection_map_; + + RootViewManager root_view_manager_; + + // Root node. + Node root_; + + // Set of ViewManagerServiceImpls created by way of Connect(). These have to + // be explicitly destroyed. + std::set<ViewManagerServiceImpl*> connections_created_by_connect_; + + // If non-null we're processing a change. The ScopedChange is not owned by us + // (it's created on the stack by ViewManagerServiceImpl). + ScopedChange* current_change_; + + DISALLOW_COPY_AND_ASSIGN(RootNodeManager); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_ROOT_NODE_MANAGER_H_ diff --git a/chromium/mojo/services/view_manager/root_view_manager.cc b/chromium/mojo/services/view_manager/root_view_manager.cc new file mode 100644 index 00000000000..0bcdbfbb20e --- /dev/null +++ b/chromium/mojo/services/view_manager/root_view_manager.cc @@ -0,0 +1,159 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/root_view_manager.h" + +#include "base/auto_reset.h" +#include "base/scoped_observer.h" +#include "mojo/public/cpp/application/connect.h" +#include "mojo/services/view_manager/root_node_manager.h" +#include "mojo/services/view_manager/root_view_manager_delegate.h" +#include "mojo/services/view_manager/screen_impl.h" +#include "mojo/services/view_manager/window_tree_host_impl.h" +#include "ui/aura/client/default_capture_client.h" +#include "ui/aura/client/focus_change_observer.h" +#include "ui/aura/client/focus_client.h" +#include "ui/aura/client/window_tree_client.h" +#include "ui/aura/window.h" +#include "ui/aura/window_observer.h" + +namespace mojo { +namespace view_manager { +namespace service { + +// TODO(sky): revisit this, we may need a more sophisticated FocusClient +// implementation. +class FocusClientImpl : public aura::client::FocusClient, + public aura::WindowObserver { + public: + FocusClientImpl() + : focused_window_(NULL), + observer_manager_(this) { + } + virtual ~FocusClientImpl() {} + + private: + // Overridden from aura::client::FocusClient: + virtual void AddObserver(aura::client::FocusChangeObserver* observer) + OVERRIDE { + } + virtual void RemoveObserver(aura::client::FocusChangeObserver* observer) + OVERRIDE { + } + virtual void FocusWindow(aura::Window* window) OVERRIDE { + if (window && !window->CanFocus()) + return; + if (focused_window_) + observer_manager_.Remove(focused_window_); + aura::Window* old_focused_window = focused_window_; + focused_window_ = window; + if (focused_window_) + observer_manager_.Add(focused_window_); + + aura::client::FocusChangeObserver* observer = + aura::client::GetFocusChangeObserver(old_focused_window); + if (observer) + observer->OnWindowFocused(focused_window_, old_focused_window); + observer = aura::client::GetFocusChangeObserver(focused_window_); + if (observer) + observer->OnWindowFocused(focused_window_, old_focused_window); + } + virtual void ResetFocusWithinActiveWindow(aura::Window* window) OVERRIDE { + if (!window->Contains(focused_window_)) + FocusWindow(window); + } + virtual aura::Window* GetFocusedWindow() OVERRIDE { + return focused_window_; + } + + // Overridden from WindowObserver: + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE { + DCHECK_EQ(window, focused_window_); + FocusWindow(NULL); + } + + aura::Window* focused_window_; + ScopedObserver<aura::Window, aura::WindowObserver> observer_manager_; + + DISALLOW_COPY_AND_ASSIGN(FocusClientImpl); +}; + +class WindowTreeClientImpl : public aura::client::WindowTreeClient { + public: + explicit WindowTreeClientImpl(aura::Window* window) : window_(window) { + aura::client::SetWindowTreeClient(window_, this); + } + + virtual ~WindowTreeClientImpl() { + aura::client::SetWindowTreeClient(window_, NULL); + } + + // Overridden from aura::client::WindowTreeClient: + virtual aura::Window* GetDefaultParent(aura::Window* context, + aura::Window* window, + const gfx::Rect& bounds) OVERRIDE { + if (!capture_client_) { + capture_client_.reset( + new aura::client::DefaultCaptureClient(window_->GetRootWindow())); + } + return window_; + } + + private: + aura::Window* window_; + + scoped_ptr<aura::client::DefaultCaptureClient> capture_client_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeClientImpl); +}; + +RootViewManager::RootViewManager(ServiceProvider* service_provider, + RootNodeManager* root_node, + RootViewManagerDelegate* delegate) + : delegate_(delegate), + root_node_manager_(root_node), + in_setup_(false) { + screen_.reset(ScreenImpl::Create()); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get()); + NativeViewportPtr viewport; + ConnectToService(service_provider, + "mojo:mojo_native_viewport_service", + &viewport); + window_tree_host_.reset(new WindowTreeHostImpl( + viewport.Pass(), + gfx::Rect(800, 600), + base::Bind(&RootViewManager::OnCompositorCreated, + base::Unretained(this)))); +} + +RootViewManager::~RootViewManager() { + window_tree_client_.reset(); + window_tree_host_.reset(); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, NULL); +} + +void RootViewManager::OnCompositorCreated() { + base::AutoReset<bool> resetter(&in_setup_, true); + window_tree_host_->InitHost(); + + aura::Window* root = root_node_manager_->root()->window(); + window_tree_host_->window()->AddChild(root); + root->SetBounds(gfx::Rect(window_tree_host_->window()->bounds().size())); + root_node_manager_->root()->window()->Show(); + + window_tree_client_.reset( + new WindowTreeClientImpl(window_tree_host_->window())); + + focus_client_.reset(new FocusClientImpl()); + aura::client::SetFocusClient(window_tree_host_->window(), + focus_client_.get()); + + window_tree_host_->Show(); + + delegate_->OnRootViewManagerWindowTreeHostCreated(); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/root_view_manager.h b/chromium/mojo/services/view_manager/root_view_manager.h new file mode 100644 index 00000000000..cfec2291f95 --- /dev/null +++ b/chromium/mojo/services/view_manager/root_view_manager.h @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_ROOT_VIEW_MANAGER_H_ +#define MOJO_SERVICES_VIEW_MANAGER_ROOT_VIEW_MANAGER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/gles2/gles2.h" +#include "mojo/services/view_manager/view_manager_export.h" + +namespace aura { +namespace client { +class FocusClient; +class WindowTreeClient; +} +class WindowTreeHost; +} + +namespace gfx { +class Screen; +} + +namespace mojo { + +class ServiceProvider; + +namespace view_manager { +namespace service { + +class RootNodeManager; +class RootViewManagerDelegate; + +// RootViewManager binds the root node to an actual display. +class MOJO_VIEW_MANAGER_EXPORT RootViewManager { + public: + RootViewManager(ServiceProvider* service_provider, + RootNodeManager* root_node, + RootViewManagerDelegate* delegate); + virtual ~RootViewManager(); + + // See description above field for details. + bool in_setup() const { return in_setup_; } + + private: + void OnCompositorCreated(); + + RootViewManagerDelegate* delegate_; + + RootNodeManager* root_node_manager_; + + GLES2Initializer gles_initializer_; + + // Returns true if adding the root node's window to |window_tree_host_|. + bool in_setup_; + + scoped_ptr<gfx::Screen> screen_; + scoped_ptr<aura::WindowTreeHost> window_tree_host_; + scoped_ptr<aura::client::WindowTreeClient> window_tree_client_; + scoped_ptr<aura::client::FocusClient> focus_client_; + + DISALLOW_COPY_AND_ASSIGN(RootViewManager); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_ROOT_VIEW_MANAGER_H_ diff --git a/chromium/mojo/services/view_manager/root_view_manager_delegate.h b/chromium/mojo/services/view_manager/root_view_manager_delegate.h new file mode 100644 index 00000000000..2920f51c0d0 --- /dev/null +++ b/chromium/mojo/services/view_manager/root_view_manager_delegate.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_ROOT_VIEW_MANAGER_DELEGATE_H_ +#define MOJO_SERVICES_VIEW_MANAGER_ROOT_VIEW_MANAGER_DELEGATE_H_ + +#include "mojo/services/view_manager/view_manager_export.h" + +namespace mojo { +namespace view_manager { +namespace service { + +class MOJO_VIEW_MANAGER_EXPORT RootViewManagerDelegate { + public: + // Invoked when the WindowTreeHost is ready. + virtual void OnRootViewManagerWindowTreeHostCreated() = 0; + + protected: + virtual ~RootViewManagerDelegate() {} +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_ROOT_VIEW_MANAGER_H_ diff --git a/chromium/mojo/services/view_manager/screen_impl.cc b/chromium/mojo/services/view_manager/screen_impl.cc new file mode 100644 index 00000000000..6339626af97 --- /dev/null +++ b/chromium/mojo/services/view_manager/screen_impl.cc @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/screen_impl.h" + +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect_conversions.h" + +namespace mojo { +namespace view_manager { +namespace service { + +// static +gfx::Screen* ScreenImpl::Create() { + return new ScreenImpl(gfx::Rect(0, 0, 800, 600)); +} + +ScreenImpl::~ScreenImpl() { +} + +bool ScreenImpl::IsDIPEnabled() { + NOTIMPLEMENTED(); + return true; +} + +gfx::Point ScreenImpl::GetCursorScreenPoint() { + NOTIMPLEMENTED(); + return gfx::Point(); +} + +gfx::NativeWindow ScreenImpl::GetWindowUnderCursor() { + return GetWindowAtScreenPoint(GetCursorScreenPoint()); +} + +gfx::NativeWindow ScreenImpl::GetWindowAtScreenPoint(const gfx::Point& point) { + NOTIMPLEMENTED(); + return NULL; +} + +int ScreenImpl::GetNumDisplays() const { + return 1; +} + +std::vector<gfx::Display> ScreenImpl::GetAllDisplays() const { + return std::vector<gfx::Display>(1, display_); +} + +gfx::Display ScreenImpl::GetDisplayNearestWindow( + gfx::NativeWindow window) const { + return display_; +} + +gfx::Display ScreenImpl::GetDisplayNearestPoint(const gfx::Point& point) const { + return display_; +} + +gfx::Display ScreenImpl::GetDisplayMatching(const gfx::Rect& match_rect) const { + return display_; +} + +gfx::Display ScreenImpl::GetPrimaryDisplay() const { + return display_; +} + +void ScreenImpl::AddObserver(gfx::DisplayObserver* observer) { +} + +void ScreenImpl::RemoveObserver(gfx::DisplayObserver* observer) { +} + +ScreenImpl::ScreenImpl(const gfx::Rect& screen_bounds) { + static int64 synthesized_display_id = 2000; + display_.set_id(synthesized_display_id++); + display_.SetScaleAndBounds(1.0f, screen_bounds); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/screen_impl.h b/chromium/mojo/services/view_manager/screen_impl.h new file mode 100644 index 00000000000..08a6b04bb41 --- /dev/null +++ b/chromium/mojo/services/view_manager/screen_impl.h @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_SCREEN_IMPL_H_ +#define MOJO_SERVICES_VIEW_MANAGER_SCREEN_IMPL_H_ + +#include "base/compiler_specific.h" +#include "ui/gfx/display.h" +#include "ui/gfx/screen.h" + +namespace gfx { +class Rect; +class Transform; +} + +namespace mojo { +namespace view_manager { +namespace service { + +// A minimal implementation of gfx::Screen for the view manager. +class ScreenImpl : public gfx::Screen { + public: + static gfx::Screen* Create(); + virtual ~ScreenImpl(); + + protected: + // gfx::Screen overrides: + virtual bool IsDIPEnabled() OVERRIDE; + virtual gfx::Point GetCursorScreenPoint() OVERRIDE; + virtual gfx::NativeWindow GetWindowUnderCursor() OVERRIDE; + virtual gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) + OVERRIDE; + virtual int GetNumDisplays() const OVERRIDE; + virtual std::vector<gfx::Display> GetAllDisplays() const OVERRIDE; + virtual gfx::Display GetDisplayNearestWindow( + gfx::NativeView view) const OVERRIDE; + virtual gfx::Display GetDisplayNearestPoint( + const gfx::Point& point) const OVERRIDE; + virtual gfx::Display GetDisplayMatching( + const gfx::Rect& match_rect) const OVERRIDE; + virtual gfx::Display GetPrimaryDisplay() const OVERRIDE; + virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE; + virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE; + + private: + explicit ScreenImpl(const gfx::Rect& screen_bounds); + + gfx::Display display_; + + DISALLOW_COPY_AND_ASSIGN(ScreenImpl); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_SCREEN_IMPL_H_ diff --git a/chromium/mojo/services/view_manager/test_change_tracker.cc b/chromium/mojo/services/view_manager/test_change_tracker.cc new file mode 100644 index 00000000000..89e7fbb4445 --- /dev/null +++ b/chromium/mojo/services/view_manager/test_change_tracker.cc @@ -0,0 +1,263 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/test_change_tracker.h" + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/public/cpp/view_manager/util.h" + +namespace mojo { +namespace view_manager { +namespace service { + +std::string NodeIdToString(Id id) { + return (id == 0) ? "null" : + base::StringPrintf("%d,%d", HiWord(id), LoWord(id)); +} + +namespace { + +std::string RectToString(const gfx::Rect& rect) { + return base::StringPrintf("%d,%d %dx%d", rect.x(), rect.y(), rect.width(), + rect.height()); +} + +std::string DirectionToString(OrderDirection direction) { + return direction == ORDER_ABOVE ? "above" : "below"; +} + +std::string ChangeToDescription1(const Change& change) { + switch (change.type) { + case CHANGE_TYPE_CONNECTION_ESTABLISHED: + return base::StringPrintf("OnConnectionEstablished creator=%s", + change.creator_url.data()); + + case CHANGE_TYPE_ROOTS_ADDED: + return "OnRootsAdded"; + + case CHANGE_TYPE_SERVER_CHANGE_ID_ADVANCED: + return base::StringPrintf( + "ServerChangeIdAdvanced %d", static_cast<int>(change.change_id)); + + + case CHANGE_TYPE_NODE_BOUNDS_CHANGED: + return base::StringPrintf( + "BoundsChanged node=%s old_bounds=%s new_bounds=%s", + NodeIdToString(change.node_id).c_str(), + RectToString(change.bounds).c_str(), + RectToString(change.bounds2).c_str()); + + case CHANGE_TYPE_NODE_HIERARCHY_CHANGED: + return base::StringPrintf( + "HierarchyChanged change_id=%d node=%s new_parent=%s old_parent=%s", + static_cast<int>(change.change_id), + NodeIdToString(change.node_id).c_str(), + NodeIdToString(change.node_id2).c_str(), + NodeIdToString(change.node_id3).c_str()); + + case CHANGE_TYPE_NODE_REORDERED: + return base::StringPrintf( + "Reordered change_id=%d node=%s relative=%s direction=%s", + static_cast<int>(change.change_id), + NodeIdToString(change.node_id).c_str(), + NodeIdToString(change.node_id2).c_str(), + DirectionToString(change.direction).c_str()); + + case CHANGE_TYPE_NODE_DELETED: + return base::StringPrintf("NodeDeleted change_id=%d node=%s", + static_cast<int>(change.change_id), + NodeIdToString(change.node_id).c_str()); + + case CHANGE_TYPE_VIEW_DELETED: + return base::StringPrintf("ViewDeleted view=%s", + NodeIdToString(change.view_id).c_str()); + + case CHANGE_TYPE_VIEW_REPLACED: + return base::StringPrintf( + "ViewReplaced node=%s new_view=%s old_view=%s", + NodeIdToString(change.node_id).c_str(), + NodeIdToString(change.view_id).c_str(), + NodeIdToString(change.view_id2).c_str()); + + case CHANGE_TYPE_INPUT_EVENT: + return base::StringPrintf( + "InputEvent view=%s event_action=%d", + NodeIdToString(change.view_id).c_str(), + change.event_action); + } + return std::string(); +} + +} // namespace + +std::vector<std::string> ChangesToDescription1( + const std::vector<Change>& changes) { + std::vector<std::string> strings(changes.size()); + for (size_t i = 0; i < changes.size(); ++i) + strings[i] = ChangeToDescription1(changes[i]); + return strings; +} + +std::string ChangeNodeDescription(const std::vector<Change>& changes) { + if (changes.size() != 1) + return std::string(); + std::vector<std::string> node_strings(changes[0].nodes.size()); + for (size_t i = 0; i < changes[0].nodes.size(); ++i) + node_strings[i] = "[" + changes[0].nodes[i].ToString() + "]"; + return JoinString(node_strings, ','); +} + +void NodeDatasToTestNodes(const Array<NodeDataPtr>& data, + std::vector<TestNode>* test_nodes) { + for (size_t i = 0; i < data.size(); ++i) { + TestNode node; + node.parent_id = data[i]->parent_id; + node.node_id = data[i]->node_id; + node.view_id = data[i]->view_id; + test_nodes->push_back(node); + } +} + +Change::Change() + : type(CHANGE_TYPE_CONNECTION_ESTABLISHED), + connection_id(0), + change_id(0), + node_id(0), + node_id2(0), + node_id3(0), + view_id(0), + view_id2(0), + event_action(0), + direction(ORDER_ABOVE) {} + +Change::~Change() { +} + +TestChangeTracker::TestChangeTracker() + : delegate_(NULL) { +} + +TestChangeTracker::~TestChangeTracker() { +} + +void TestChangeTracker::OnViewManagerConnectionEstablished( + ConnectionSpecificId connection_id, + const String& creator_url, + Id next_server_change_id, + Array<NodeDataPtr> nodes) { + Change change; + change.type = CHANGE_TYPE_CONNECTION_ESTABLISHED; + change.connection_id = connection_id; + change.change_id = next_server_change_id; + change.creator_url = creator_url; + NodeDatasToTestNodes(nodes, &change.nodes); + AddChange(change); +} + +void TestChangeTracker::OnRootsAdded(Array<NodeDataPtr> nodes) { + Change change; + change.type = CHANGE_TYPE_ROOTS_ADDED; + NodeDatasToTestNodes(nodes, &change.nodes); + AddChange(change); +} + +void TestChangeTracker::OnServerChangeIdAdvanced(Id change_id) { + Change change; + change.type = CHANGE_TYPE_SERVER_CHANGE_ID_ADVANCED; + change.change_id = change_id; + AddChange(change); +} + +void TestChangeTracker::OnNodeBoundsChanged(Id node_id, + RectPtr old_bounds, + RectPtr new_bounds) { + Change change; + change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED; + change.node_id = node_id; + change.bounds = old_bounds.To<gfx::Rect>(); + change.bounds2 = new_bounds.To<gfx::Rect>(); + AddChange(change); +} + +void TestChangeTracker::OnNodeHierarchyChanged(Id node_id, + Id new_parent_id, + Id old_parent_id, + Id server_change_id, + Array<NodeDataPtr> nodes) { + Change change; + change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED; + change.node_id = node_id; + change.node_id2 = new_parent_id; + change.node_id3 = old_parent_id; + change.change_id = server_change_id; + NodeDatasToTestNodes(nodes, &change.nodes); + AddChange(change); +} + +void TestChangeTracker::OnNodeReordered(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id) { + Change change; + change.type = CHANGE_TYPE_NODE_REORDERED; + change.node_id = node_id; + change.node_id2 = relative_node_id; + change.direction = direction; + change.change_id = server_change_id; + AddChange(change); +} + +void TestChangeTracker::OnNodeDeleted(Id node_id, Id server_change_id) { + Change change; + change.type = CHANGE_TYPE_NODE_DELETED; + change.node_id = node_id; + change.change_id = server_change_id; + AddChange(change); +} + +void TestChangeTracker::OnViewDeleted(Id view_id) { + Change change; + change.type = CHANGE_TYPE_VIEW_DELETED; + change.view_id = view_id; + AddChange(change); +} + +void TestChangeTracker::OnNodeViewReplaced(Id node_id, + Id new_view_id, + Id old_view_id) { + Change change; + change.type = CHANGE_TYPE_VIEW_REPLACED; + change.node_id = node_id; + change.view_id = new_view_id; + change.view_id2 = old_view_id; + AddChange(change); +} + +void TestChangeTracker::OnViewInputEvent(Id view_id, EventPtr event) { + Change change; + change.type = CHANGE_TYPE_INPUT_EVENT; + change.view_id = view_id; + change.event_action = event->action; + AddChange(change); +} + +void TestChangeTracker::AddChange(const Change& change) { + changes_.push_back(change); + if (delegate_) + delegate_->OnChangeAdded(); +} + +std::string TestNode::ToString() const { + return base::StringPrintf("node=%s parent=%s view=%s", + NodeIdToString(node_id).c_str(), + NodeIdToString(parent_id).c_str(), + NodeIdToString(view_id).c_str()); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/test_change_tracker.h b/chromium/mojo/services/view_manager/test_change_tracker.h new file mode 100644 index 00000000000..5a5edf7d126 --- /dev/null +++ b/chromium/mojo/services/view_manager/test_change_tracker.h @@ -0,0 +1,134 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ +#define MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ + +#include <string> +#include <vector> + +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" +#include "ui/gfx/rect.h" + +namespace mojo { +namespace view_manager { +namespace service { + +enum ChangeType { + CHANGE_TYPE_CONNECTION_ESTABLISHED, + CHANGE_TYPE_ROOTS_ADDED, + CHANGE_TYPE_SERVER_CHANGE_ID_ADVANCED, + CHANGE_TYPE_NODE_BOUNDS_CHANGED, + CHANGE_TYPE_NODE_HIERARCHY_CHANGED, + CHANGE_TYPE_NODE_REORDERED, + CHANGE_TYPE_NODE_DELETED, + CHANGE_TYPE_VIEW_DELETED, + CHANGE_TYPE_VIEW_REPLACED, + CHANGE_TYPE_INPUT_EVENT, +}; + +// TODO(sky): consider nuking and converting directly to NodeData. +struct TestNode { + // Returns a string description of this. + std::string ToString() const; + + Id parent_id; + Id node_id; + Id view_id; +}; + +// Tracks a call to ViewManagerClient. See the individual functions for the +// fields that are used. +struct Change { + Change(); + ~Change(); + + ChangeType type; + ConnectionSpecificId connection_id; + Id change_id; + std::vector<TestNode> nodes; + Id node_id; + Id node_id2; + Id node_id3; + Id view_id; + Id view_id2; + gfx::Rect bounds; + gfx::Rect bounds2; + int32 event_action; + String creator_url; + OrderDirection direction; +}; + +// Converts Changes to string descriptions. +std::vector<std::string> ChangesToDescription1( + const std::vector<Change>& changes); + +// Returns a string description of |changes[0].nodes|. Returns an empty string +// if change.size() != 1. +std::string ChangeNodeDescription(const std::vector<Change>& changes); + +// Converts NodeDatas to TestNodes. +void NodeDatasToTestNodes(const Array<NodeDataPtr>& data, + std::vector<TestNode>* test_nodes); + +// TestChangeTracker is used to record ViewManagerClient functions. It notifies +// a delegate any time a change is added. +class TestChangeTracker { + public: + // Used to notify the delegate when a change is added. A change corresponds to + // a single ViewManagerClient function. + class Delegate { + public: + virtual void OnChangeAdded() = 0; + + protected: + virtual ~Delegate() {} + }; + + TestChangeTracker(); + ~TestChangeTracker(); + + void set_delegate(Delegate* delegate) { delegate_ = delegate; } + + std::vector<Change>* changes() { return &changes_; } + + // Each of these functions generate a Change. There is one per + // ViewManagerClient function. + void OnViewManagerConnectionEstablished(ConnectionSpecificId connection_id, + const String& creator_url, + Id next_server_change_id, + Array<NodeDataPtr> nodes); + void OnRootsAdded(Array<NodeDataPtr> nodes); + void OnServerChangeIdAdvanced(Id change_id); + void OnNodeBoundsChanged(Id node_id, RectPtr old_bounds, RectPtr new_bounds); + void OnNodeHierarchyChanged(Id node_id, + Id new_parent_id, + Id old_parent_id, + Id server_change_id, + Array<NodeDataPtr> nodes); + void OnNodeReordered(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id); + void OnNodeDeleted(Id node_id, Id server_change_id); + void OnViewDeleted(Id view_id); + void OnNodeViewReplaced(Id node_id, Id new_view_id, Id old_view_id); + void OnViewInputEvent(Id view_id, EventPtr event); + + private: + void AddChange(const Change& change); + + Delegate* delegate_; + std::vector<Change> changes_; + + DISALLOW_COPY_AND_ASSIGN(TestChangeTracker); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ diff --git a/chromium/mojo/services/view_manager/view.cc b/chromium/mojo/services/view_manager/view.cc new file mode 100644 index 00000000000..c33f5f847fc --- /dev/null +++ b/chromium/mojo/services/view_manager/view.cc @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/view.h" + +#include "mojo/services/view_manager/node.h" + +namespace mojo { +namespace view_manager { +namespace service { + +View::View(const ViewId& id) : id_(id), node_(NULL) {} + +View::~View() { +} + +void View::SetBitmap(const SkBitmap& bitmap) { + bitmap_ = bitmap; + if (node_) { + node_->window()->SchedulePaintInRect( + gfx::Rect(node_->window()->bounds().size())); + } +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/view.h b/chromium/mojo/services/view_manager/view.h new file mode 100644 index 00000000000..c4b0c0521b6 --- /dev/null +++ b/chromium/mojo/services/view_manager/view.h @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_VIEW_H_ +#define MOJO_SERVICES_VIEW_MANAGER_VIEW_H_ + +#include <vector> + +#include "base/logging.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/view_manager_export.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace mojo { +namespace view_manager { +namespace service { +class Node; + +// Represents a view. A view may be associated with a single Node. +class MOJO_VIEW_MANAGER_EXPORT View { + public: + explicit View(const ViewId& id); + ~View(); + + const ViewId& id() const { return id_; } + + Node* node() { return node_; } + + void SetBitmap(const SkBitmap& contents); + const SkBitmap& bitmap() const { return bitmap_; } + + private: + // Node is responsible for maintaining |node_|. + friend class Node; + + void set_node(Node* node) { node_ = node; } + + const ViewId id_; + Node* node_; + SkBitmap bitmap_; + + DISALLOW_COPY_AND_ASSIGN(View); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_H_ diff --git a/chromium/mojo/services/view_manager/view_manager_export.h b/chromium/mojo/services/view_manager/view_manager_export.h new file mode 100644 index 00000000000..29b0e6219c9 --- /dev/null +++ b/chromium/mojo/services/view_manager/view_manager_export.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_EXPORT_H_ +#define MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_VIEW_MANAGER_IMPLEMENTATION) +#define MOJO_VIEW_MANAGER_EXPORT __declspec(dllexport) +#else +#define MOJO_VIEW_MANAGER_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_VIEW_MANAGER_IMPLEMENTATION) +#define MOJO_VIEW_MANAGER_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_VIEW_MANAGER_EXPORT +#endif + +#endif // defined(WIN32) + +#else // defined(COMPONENT_BUILD) +#define MOJO_VIEW_MANAGER_EXPORT +#endif + +#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_EXPORT_H_ diff --git a/chromium/mojo/services/view_manager/view_manager_init_service_impl.cc b/chromium/mojo/services/view_manager/view_manager_init_service_impl.cc new file mode 100644 index 00000000000..be2ce15935b --- /dev/null +++ b/chromium/mojo/services/view_manager/view_manager_init_service_impl.cc @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/view_manager_init_service_impl.h" + +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/view_manager_service_impl.h" + +namespace mojo { +namespace view_manager { +namespace service { + +ViewManagerInitServiceImpl::ConnectParams::ConnectParams() {} + +ViewManagerInitServiceImpl::ConnectParams::~ConnectParams() {} + +ViewManagerInitServiceImpl::ViewManagerInitServiceImpl( + ServiceProvider* service_provider) + : service_provider_(service_provider), + root_node_manager_(service_provider, this), + is_tree_host_ready_(false) { +} + +ViewManagerInitServiceImpl::~ViewManagerInitServiceImpl() { +} + +void ViewManagerInitServiceImpl::MaybeEmbedRoot( + const std::string& url, + const Callback<void(bool)>& callback) { + if (!is_tree_host_ready_) + return; + + root_node_manager_.EmbedRoot(url); + callback.Run(true); +} + +void ViewManagerInitServiceImpl::EmbedRoot( + const String& url, + const Callback<void(bool)>& callback) { + if (connect_params_.get()) { + DVLOG(1) << "Ignoring second connect"; + callback.Run(false); + return; + } + connect_params_.reset(new ConnectParams); + connect_params_->url = url.To<std::string>(); + connect_params_->callback = callback; + MaybeEmbedRoot(url.To<std::string>(), callback); +} + +void ViewManagerInitServiceImpl::OnRootViewManagerWindowTreeHostCreated() { + DCHECK(!is_tree_host_ready_); + is_tree_host_ready_ = true; + if (connect_params_.get()) + MaybeEmbedRoot(connect_params_->url, connect_params_->callback); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/view_manager_init_service_impl.h b/chromium/mojo/services/view_manager/view_manager_init_service_impl.h new file mode 100644 index 00000000000..59d438ef666 --- /dev/null +++ b/chromium/mojo/services/view_manager/view_manager_init_service_impl.h @@ -0,0 +1,79 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_IMPL_H_ +#define MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_IMPL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" +#include "mojo/services/view_manager/root_node_manager.h" +#include "mojo/services/view_manager/root_view_manager_delegate.h" +#include "mojo/services/view_manager/view_manager_export.h" + +namespace mojo { + +class ServiceProvider; + +namespace view_manager { +namespace service { + +#if defined(OS_WIN) +// Equivalent of NON_EXPORTED_BASE which does not work with the template snafu +// below. +#pragma warning(push) +#pragma warning(disable : 4275) +#endif + +// Used to create the initial ViewManagerClient. Doesn't initiate the Connect() +// until the WindowTreeHost has been created. +class MOJO_VIEW_MANAGER_EXPORT ViewManagerInitServiceImpl + : public InterfaceImpl<ViewManagerInitService>, + public RootViewManagerDelegate { + public: + explicit ViewManagerInitServiceImpl(ServiceProvider* service_provider); + virtual ~ViewManagerInitServiceImpl(); + + private: + struct ConnectParams { + ConnectParams(); + ~ConnectParams(); + + std::string url; + Callback<void(bool)> callback; + }; + + void MaybeEmbedRoot(const std::string& url, + const Callback<void(bool)>& callback); + + // ViewManagerInitService overrides: + virtual void EmbedRoot(const String& url, + const Callback<void(bool)>& callback) OVERRIDE; + + // RootViewManagerDelegate overrides: + virtual void OnRootViewManagerWindowTreeHostCreated() OVERRIDE; + + ServiceProvider* service_provider_; + + RootNodeManager root_node_manager_; + + // Parameters passed to Connect(). If non-null Connect() has been invoked. + scoped_ptr<ConnectParams> connect_params_; + + bool is_tree_host_ready_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerInitServiceImpl); +}; + +#if defined(OS_WIN) +#pragma warning(pop) +#endif + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_IMPL_H_ diff --git a/chromium/mojo/services/view_manager/view_manager_service_impl.cc b/chromium/mojo/services/view_manager/view_manager_service_impl.cc new file mode 100644 index 00000000000..0af11c3f167 --- /dev/null +++ b/chromium/mojo/services/view_manager/view_manager_service_impl.cc @@ -0,0 +1,803 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/view_manager/view_manager_service_impl.h" + +#include "base/bind.h" +#include "base/stl_util.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/public/cpp/input_events/input_events_type_converters.h" +#include "mojo/services/view_manager/node.h" +#include "mojo/services/view_manager/root_node_manager.h" +#include "mojo/services/view_manager/view.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/aura/window.h" +#include "ui/gfx/codec/png_codec.h" + +namespace mojo { +namespace view_manager { +namespace service { +namespace { + +// Places |node| in |nodes| and recurses through the children. +void GetDescendants(const Node* node, std::vector<const Node*>* nodes) { + if (!node) + return; + + nodes->push_back(node); + + std::vector<const Node*> children(node->GetChildren()); + for (size_t i = 0 ; i < children.size(); ++i) + GetDescendants(children[i], nodes); +} + +} // namespace + +ViewManagerServiceImpl::ViewManagerServiceImpl( + RootNodeManager* root_node_manager, + ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url) + : root_node_manager_(root_node_manager), + id_(root_node_manager_->GetAndAdvanceNextConnectionId()), + url_(url), + creator_id_(creator_id), + creator_url_(creator_url), + delete_on_connection_error_(false) { +} + +ViewManagerServiceImpl::~ViewManagerServiceImpl() { + // Delete any views we own. + while (!view_map_.empty()) { + bool result = DeleteViewImpl(this, view_map_.begin()->second->id()); + DCHECK(result); + } + + // We're about to destroy all our nodes. Detach any views from them. + for (NodeMap::iterator i = node_map_.begin(); i != node_map_.end(); ++i) { + if (i->second->view()) { + bool result = SetViewImpl(i->second, ViewId()); + DCHECK(result); + } + } + + if (!node_map_.empty()) { + RootNodeManager::ScopedChange change( + this, root_node_manager_, + RootNodeManager::CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID, true); + while (!node_map_.empty()) { + scoped_ptr<Node> node(node_map_.begin()->second); + Node* parent = node->GetParent(); + const NodeId node_id(node->id()); + if (parent) + parent->Remove(node.get()); + root_node_manager_->ProcessNodeDeleted(node_id); + node_map_.erase(NodeIdToTransportId(node_id)); + } + } + + root_node_manager_->RemoveConnection(this); +} + +const Node* ViewManagerServiceImpl::GetNode(const NodeId& id) const { + if (id_ == id.connection_id) { + NodeMap::const_iterator i = node_map_.find(id.node_id); + return i == node_map_.end() ? NULL : i->second; + } + return root_node_manager_->GetNode(id); +} + +const View* ViewManagerServiceImpl::GetView(const ViewId& id) const { + if (id_ == id.connection_id) { + ViewMap::const_iterator i = view_map_.find(id.view_id); + return i == view_map_.end() ? NULL : i->second; + } + return root_node_manager_->GetView(id); +} + +void ViewManagerServiceImpl::SetRoots(const Array<Id>& node_ids) { + DCHECK(roots_.empty()); + NodeIdSet roots; + for (size_t i = 0; i < node_ids.size(); ++i) { + DCHECK(GetNode(NodeIdFromTransportId(node_ids[i]))); + roots.insert(node_ids[i]); + } + roots_.swap(roots); +} + +void ViewManagerServiceImpl::OnViewManagerServiceImplDestroyed( + ConnectionSpecificId id) { + if (creator_id_ == id) + creator_id_ = kRootConnection; +} + +void ViewManagerServiceImpl::ProcessNodeBoundsChanged( + const Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change) { + if (originated_change) + return; + Id node_id = NodeIdToTransportId(node->id()); + if (known_nodes_.count(node_id) > 0) { + client()->OnNodeBoundsChanged(node_id, + Rect::From(old_bounds), + Rect::From(new_bounds)); + } +} + +void ViewManagerServiceImpl::ProcessNodeHierarchyChanged( + const Node* node, + const Node* new_parent, + const Node* old_parent, + Id server_change_id, + bool originated_change) { + if (known_nodes_.count(NodeIdToTransportId(node->id())) > 0) { + if (originated_change) + return; + if (node->id().connection_id != id_ && !IsNodeDescendantOfRoots(node)) { + // Node was a descendant of roots and is no longer, treat it as though the + // node was deleted. + RemoveFromKnown(node); + client()->OnNodeDeleted(NodeIdToTransportId(node->id()), + server_change_id); + root_node_manager_->OnConnectionMessagedClient(id_); + return; + } + } + + if (originated_change || root_node_manager_->is_processing_delete_node()) + return; + std::vector<const Node*> to_send; + if (!ShouldNotifyOnHierarchyChange(node, &new_parent, &old_parent, + &to_send)) { + if (root_node_manager_->IsProcessingChange()) { + client()->OnServerChangeIdAdvanced( + root_node_manager_->next_server_change_id() + 1); + } + return; + } + const NodeId new_parent_id(new_parent ? new_parent->id() : NodeId()); + const NodeId old_parent_id(old_parent ? old_parent->id() : NodeId()); + DCHECK((node->id().connection_id == id_) || + (roots_.count(NodeIdToTransportId(node->id())) > 0) || + (new_parent && IsNodeDescendantOfRoots(new_parent)) || + (old_parent && IsNodeDescendantOfRoots(old_parent))); + client()->OnNodeHierarchyChanged(NodeIdToTransportId(node->id()), + NodeIdToTransportId(new_parent_id), + NodeIdToTransportId(old_parent_id), + server_change_id, + NodesToNodeDatas(to_send)); +} + +void ViewManagerServiceImpl::ProcessNodeReorder(const Node* node, + const Node* relative_node, + OrderDirection direction, + Id server_change_id, + bool originated_change) { + if (originated_change || + !known_nodes_.count(NodeIdToTransportId(node->id())) || + !known_nodes_.count(NodeIdToTransportId(relative_node->id()))) { + return; + } + + client()->OnNodeReordered(NodeIdToTransportId(node->id()), + NodeIdToTransportId(relative_node->id()), + direction, + server_change_id); +} + +void ViewManagerServiceImpl::ProcessNodeViewReplaced( + const Node* node, + const View* new_view, + const View* old_view, + bool originated_change) { + if (originated_change || !known_nodes_.count(NodeIdToTransportId(node->id()))) + return; + const Id new_view_id = new_view ? + ViewIdToTransportId(new_view->id()) : 0; + const Id old_view_id = old_view ? + ViewIdToTransportId(old_view->id()) : 0; + client()->OnNodeViewReplaced(NodeIdToTransportId(node->id()), + new_view_id, old_view_id); +} + +void ViewManagerServiceImpl::ProcessNodeDeleted(const NodeId& node, + Id server_change_id, + bool originated_change) { + const bool in_known = known_nodes_.erase(NodeIdToTransportId(node)) > 0; + const bool in_roots = roots_.erase(NodeIdToTransportId(node)) > 0; + + if (in_roots && roots_.empty()) + roots_.insert(NodeIdToTransportId(InvalidNodeId())); + + if (originated_change) + return; + + if (in_known) { + client()->OnNodeDeleted(NodeIdToTransportId(node), server_change_id); + root_node_manager_->OnConnectionMessagedClient(id_); + } else if (root_node_manager_->IsProcessingChange() && + !root_node_manager_->DidConnectionMessageClient(id_)) { + client()->OnServerChangeIdAdvanced( + root_node_manager_->next_server_change_id() + 1); + root_node_manager_->OnConnectionMessagedClient(id_); + } +} + +void ViewManagerServiceImpl::ProcessViewDeleted(const ViewId& view, + bool originated_change) { + if (originated_change) + return; + client()->OnViewDeleted(ViewIdToTransportId(view)); +} + +void ViewManagerServiceImpl::OnConnectionError() { + if (delete_on_connection_error_) + delete this; +} + +bool ViewManagerServiceImpl::CanRemoveNodeFromParent(const Node* node) const { + if (!node) + return false; + + const Node* parent = node->GetParent(); + if (!parent) + return false; + + // Always allow the remove if there are no roots. Otherwise the remove is + // allowed if the parent is a descendant of the roots, or the node and its + // parent were created by this connection. We explicitly disallow removal of + // the node from its parent if the parent isn't visible to this connection + // (not in roots). + return (roots_.empty() || + (IsNodeDescendantOfRoots(parent) || + (node->id().connection_id == id_ && + parent->id().connection_id == id_))); +} + +bool ViewManagerServiceImpl::CanAddNode(const Node* parent, + const Node* child) const { + if (!parent || !child) + return false; // Both nodes must be valid. + + if (child->GetParent() == parent || child->Contains(parent)) + return false; // Would result in an invalid hierarchy. + + if (roots_.empty()) + return true; // No restriction if there are no roots. + + if (!IsNodeDescendantOfRoots(parent) && parent->id().connection_id != id_) + return false; // |parent| is not visible to this connection. + + // Allow the add if the child is already a descendant of the roots or was + // created by this connection. + return (IsNodeDescendantOfRoots(child) || child->id().connection_id == id_); +} + +bool ViewManagerServiceImpl::CanReorderNode(const Node* node, + const Node* relative_node, + OrderDirection direction) const { + if (!node || !relative_node) + return false; + + if (node->id().connection_id != id_) + return false; + + const Node* parent = node->GetParent(); + if (!parent || parent != relative_node->GetParent()) + return false; + + if (known_nodes_.count(NodeIdToTransportId(parent->id())) == 0) + return false; + + std::vector<const Node*> children = parent->GetChildren(); + const size_t child_i = + std::find(children.begin(), children.end(), node) - children.begin(); + const size_t target_i = + std::find(children.begin(), children.end(), relative_node) - + children.begin(); + if ((direction == ORDER_ABOVE && child_i == target_i + 1) || + (direction == ORDER_BELOW && child_i + 1 == target_i)) { + return false; + } + + return true; +} + +bool ViewManagerServiceImpl::CanDeleteNode(const NodeId& node_id) const { + return node_id.connection_id == id_; +} + +bool ViewManagerServiceImpl::CanDeleteView(const ViewId& view_id) const { + return view_id.connection_id == id_; +} + +bool ViewManagerServiceImpl::CanSetView(const Node* node, + const ViewId& view_id) const { + if (!node || !IsNodeDescendantOfRoots(node)) + return false; + + const View* view = GetView(view_id); + return (view && view_id.connection_id == id_) || view_id == ViewId(); +} + +bool ViewManagerServiceImpl::CanSetFocus(const Node* node) const { + // TODO(beng): security. + return true; +} + +bool ViewManagerServiceImpl::CanGetNodeTree(const Node* node) const { + return node && + (IsNodeDescendantOfRoots(node) || node->id().connection_id == id_); +} + +bool ViewManagerServiceImpl::CanEmbed( + const mojo::Array<uint32_t>& node_ids) const { + for (size_t i = 0; i < node_ids.size(); ++i) { + const Node* node = GetNode(NodeIdFromTransportId(node_ids[i])); + if (!node || node->id().connection_id != id_) + return false; + } + return node_ids.size() > 0; +} + +bool ViewManagerServiceImpl::DeleteNodeImpl(ViewManagerServiceImpl* source, + const NodeId& node_id) { + DCHECK_EQ(node_id.connection_id, id_); + Node* node = GetNode(node_id); + if (!node) + return false; + RootNodeManager::ScopedChange change( + source, root_node_manager_, + RootNodeManager::CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID, true); + if (node->GetParent()) + node->GetParent()->Remove(node); + std::vector<Node*> children(node->GetChildren()); + for (size_t i = 0; i < children.size(); ++i) + node->Remove(children[i]); + DCHECK(node->GetChildren().empty()); + node_map_.erase(node_id.node_id); + delete node; + node = NULL; + root_node_manager_->ProcessNodeDeleted(node_id); + return true; +} + +bool ViewManagerServiceImpl::DeleteViewImpl(ViewManagerServiceImpl* source, + const ViewId& view_id) { + DCHECK_EQ(view_id.connection_id, id_); + View* view = GetView(view_id); + if (!view) + return false; + RootNodeManager::ScopedChange change( + source, root_node_manager_, + RootNodeManager::CHANGE_TYPE_DONT_ADVANCE_SERVER_CHANGE_ID, false); + if (view->node()) + view->node()->SetView(NULL); + view_map_.erase(view_id.view_id); + // Make a copy of |view_id| as once we delete view |view_id| may no longer be + // valid. + const ViewId view_id_copy(view_id); + delete view; + root_node_manager_->ProcessViewDeleted(view_id_copy); + return true; +} + +bool ViewManagerServiceImpl::SetViewImpl(Node* node, const ViewId& view_id) { + DCHECK(node); // CanSetView() should have verified node exists. + View* view = GetView(view_id); + RootNodeManager::ScopedChange change( + this, root_node_manager_, + RootNodeManager::CHANGE_TYPE_DONT_ADVANCE_SERVER_CHANGE_ID, false); + node->SetView(view); + + // TODO(sky): this is temporary, need a real focus API. + if (view && root_node_manager_->root()->Contains(node)) + node->window()->Focus(); + + return true; +} + +void ViewManagerServiceImpl::GetUnknownNodesFrom( + const Node* node, + std::vector<const Node*>* nodes) { + const Id transport_id = NodeIdToTransportId(node->id()); + if (known_nodes_.count(transport_id) == 1) + return; + nodes->push_back(node); + known_nodes_.insert(transport_id); + std::vector<const Node*> children(node->GetChildren()); + for (size_t i = 0 ; i < children.size(); ++i) + GetUnknownNodesFrom(children[i], nodes); +} + +void ViewManagerServiceImpl::RemoveFromKnown(const Node* node) { + if (node->id().connection_id == id_) + return; + known_nodes_.erase(NodeIdToTransportId(node->id())); + std::vector<const Node*> children = node->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + RemoveFromKnown(children[i]); +} + +bool ViewManagerServiceImpl::AddRoots( + const std::vector<Id>& node_ids) { + std::vector<const Node*> to_send; + bool did_add_root = false; + for (size_t i = 0; i < node_ids.size(); ++i) { + CHECK_EQ(creator_id_, NodeIdFromTransportId(node_ids[i]).connection_id); + if (roots_.count(node_ids[i]) > 0) + continue; + + did_add_root = true; + roots_.insert(node_ids[i]); + Node* node = GetNode(NodeIdFromTransportId(node_ids[i])); + DCHECK(node); + if (known_nodes_.count(node_ids[i]) == 0) { + GetUnknownNodesFrom(node, &to_send); + } else { + // Even though the connection knows about the new root we need to tell it + // |node| is now a root. + to_send.push_back(node); + } + } + + if (!did_add_root) + return false; + + client()->OnRootsAdded(NodesToNodeDatas(to_send)); + return true; +} + +bool ViewManagerServiceImpl::IsNodeDescendantOfRoots(const Node* node) const { + if (roots_.empty()) + return true; + if (!node) + return false; + const Id invalid_node_id = + NodeIdToTransportId(InvalidNodeId()); + for (NodeIdSet::const_iterator i = roots_.begin(); i != roots_.end(); ++i) { + if (*i == invalid_node_id) + continue; + const Node* root = GetNode(NodeIdFromTransportId(*i)); + DCHECK(root); + if (root->Contains(node)) + return true; + } + return false; +} + +bool ViewManagerServiceImpl::ShouldNotifyOnHierarchyChange( + const Node* node, + const Node** new_parent, + const Node** old_parent, + std::vector<const Node*>* to_send) { + // If the node is not in |roots_| or was never known to this connection then + // don't notify the client about it. + if (node->id().connection_id != id_ && + known_nodes_.count(NodeIdToTransportId(node->id())) == 0 && + !IsNodeDescendantOfRoots(node)) { + return false; + } + if (!IsNodeDescendantOfRoots(*new_parent)) + *new_parent = NULL; + if (!IsNodeDescendantOfRoots(*old_parent)) + *old_parent = NULL; + + if (*new_parent) { + // On getting a new parent we may need to communicate new nodes to the + // client. We do that in the following cases: + // . New parent is a descendant of the roots. In this case the client + // already knows all ancestors, so we only have to communicate descendants + // of node the client doesn't know about. + // . If the client knew about the parent, we have to do the same. + // . If the client knows about the node and is added to a tree the client + // doesn't know about we have to communicate from the root down (the + // client is learning about a new root). + if (root_node_manager_->root()->Contains(*new_parent) || + known_nodes_.count(NodeIdToTransportId((*new_parent)->id()))) { + GetUnknownNodesFrom(node, to_send); + return true; + } + // If parent wasn't known we have to communicate from the root down. + if (known_nodes_.count(NodeIdToTransportId(node->id()))) { + // No need to check against |roots_| as client should always know it's + // |roots_|. + GetUnknownNodesFrom((*new_parent)->GetRoot(), to_send); + return true; + } + } + // Otherwise only communicate the change if the node was known. We shouldn't + // need to communicate any nodes on a remove. + return known_nodes_.count(NodeIdToTransportId(node->id())) > 0; +} + +Array<NodeDataPtr> ViewManagerServiceImpl::NodesToNodeDatas( + const std::vector<const Node*>& nodes) { + Array<NodeDataPtr> array(nodes.size()); + for (size_t i = 0; i < nodes.size(); ++i) { + const Node* node = nodes[i]; + DCHECK(known_nodes_.count(NodeIdToTransportId(node->id())) > 0); + const Node* parent = node->GetParent(); + // If the parent isn't known, it means the parent is not visible to us (not + // in roots), and should not be sent over. + if (parent && known_nodes_.count(NodeIdToTransportId(parent->id())) == 0) + parent = NULL; + NodeDataPtr inode(NodeData::New()); + inode->parent_id = NodeIdToTransportId(parent ? parent->id() : NodeId()); + inode->node_id = NodeIdToTransportId(node->id()); + inode->view_id = + ViewIdToTransportId(node->view() ? node->view()->id() : ViewId()); + inode->bounds = Rect::From(node->bounds()); + array[i] = inode.Pass(); + } + return array.Pass(); +} + +void ViewManagerServiceImpl::CreateNode( + Id transport_node_id, + const Callback<void(bool)>& callback) { + const NodeId node_id(NodeIdFromTransportId(transport_node_id)); + if (node_id.connection_id != id_ || + node_map_.find(node_id.node_id) != node_map_.end()) { + callback.Run(false); + return; + } + node_map_[node_id.node_id] = new Node(this, node_id); + known_nodes_.insert(transport_node_id); + callback.Run(true); +} + +void ViewManagerServiceImpl::DeleteNode( + Id transport_node_id, + Id server_change_id, + const Callback<void(bool)>& callback) { + const NodeId node_id(NodeIdFromTransportId(transport_node_id)); + bool success = false; + if (server_change_id == root_node_manager_->next_server_change_id() && + CanDeleteNode(node_id)) { + ViewManagerServiceImpl* connection = root_node_manager_->GetConnection( + node_id.connection_id); + success = connection && connection->DeleteNodeImpl(this, node_id); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::AddNode( + Id parent_id, + Id child_id, + Id server_change_id, + const Callback<void(bool)>& callback) { + bool success = false; + if (server_change_id == root_node_manager_->next_server_change_id()) { + Node* parent = GetNode(NodeIdFromTransportId(parent_id)); + Node* child = GetNode(NodeIdFromTransportId(child_id)); + if (CanAddNode(parent, child)) { + success = true; + RootNodeManager::ScopedChange change( + this, root_node_manager_, + RootNodeManager::CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID, false); + parent->Add(child); + } + } + callback.Run(success); +} + +void ViewManagerServiceImpl::RemoveNodeFromParent( + Id node_id, + Id server_change_id, + const Callback<void(bool)>& callback) { + bool success = false; + if (server_change_id == root_node_manager_->next_server_change_id()) { + Node* node = GetNode(NodeIdFromTransportId(node_id)); + if (CanRemoveNodeFromParent(node)) { + success = true; + RootNodeManager::ScopedChange change( + this, root_node_manager_, + RootNodeManager::CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID, false); + node->GetParent()->Remove(node); + } + } + callback.Run(success); +} + +void ViewManagerServiceImpl::ReorderNode(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id, + const Callback<void(bool)>& callback) { + bool success = false; + if (server_change_id == root_node_manager_->next_server_change_id()) { + Node* node = GetNode(NodeIdFromTransportId(node_id)); + Node* relative_node = GetNode(NodeIdFromTransportId(relative_node_id)); + if (CanReorderNode(node, relative_node, direction)) { + success = true; + RootNodeManager::ScopedChange change( + this, root_node_manager_, + RootNodeManager::CHANGE_TYPE_ADVANCE_SERVER_CHANGE_ID, false); + node->GetParent()->Reorder(node, relative_node, direction); + root_node_manager_->ProcessNodeReorder(node, relative_node, direction); + } + } + callback.Run(success); +} + +void ViewManagerServiceImpl::GetNodeTree( + Id node_id, + const Callback<void(Array<NodeDataPtr>)>& callback) { + Node* node = GetNode(NodeIdFromTransportId(node_id)); + std::vector<const Node*> nodes; + if (CanGetNodeTree(node)) { + GetDescendants(node, &nodes); + for (size_t i = 0; i < nodes.size(); ++i) + known_nodes_.insert(NodeIdToTransportId(nodes[i]->id())); + } + callback.Run(NodesToNodeDatas(nodes)); +} + +void ViewManagerServiceImpl::CreateView( + Id transport_view_id, + const Callback<void(bool)>& callback) { + const ViewId view_id(ViewIdFromTransportId(transport_view_id)); + if (view_id.connection_id != id_ || view_map_.count(view_id.view_id)) { + callback.Run(false); + return; + } + view_map_[view_id.view_id] = new View(view_id); + callback.Run(true); +} + +void ViewManagerServiceImpl::DeleteView( + Id transport_view_id, + const Callback<void(bool)>& callback) { + const ViewId view_id(ViewIdFromTransportId(transport_view_id)); + bool did_delete = CanDeleteView(view_id); + if (did_delete) { + ViewManagerServiceImpl* connection = root_node_manager_->GetConnection( + view_id.connection_id); + did_delete = (connection && connection->DeleteViewImpl(this, view_id)); + } + callback.Run(did_delete); +} + +void ViewManagerServiceImpl::SetView(Id transport_node_id, + Id transport_view_id, + const Callback<void(bool)>& callback) { + Node* node = GetNode(NodeIdFromTransportId(transport_node_id)); + const ViewId view_id(ViewIdFromTransportId(transport_view_id)); + callback.Run(CanSetView(node, view_id) && SetViewImpl(node, view_id)); +} + +void ViewManagerServiceImpl::SetViewContents( + Id view_id, + ScopedSharedBufferHandle buffer, + uint32_t buffer_size, + const Callback<void(bool)>& callback) { + View* view = GetView(ViewIdFromTransportId(view_id)); + if (!view) { + callback.Run(false); + return; + } + void* handle_data; + if (MapBuffer(buffer.get(), 0, buffer_size, &handle_data, + MOJO_MAP_BUFFER_FLAG_NONE) != MOJO_RESULT_OK) { + callback.Run(false); + return; + } + SkBitmap bitmap; + gfx::PNGCodec::Decode(static_cast<const unsigned char*>(handle_data), + buffer_size, &bitmap); + view->SetBitmap(bitmap); + UnmapBuffer(handle_data); + callback.Run(true); +} + +void ViewManagerServiceImpl::SetFocus(Id node_id, + const Callback<void(bool)> & callback) { + bool success = false; + Node* node = GetNode(NodeIdFromTransportId(node_id)); + if (CanSetFocus(node)) { + success = true; + node->window()->Focus(); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::SetNodeBounds( + Id node_id, + RectPtr bounds, + const Callback<void(bool)>& callback) { + if (NodeIdFromTransportId(node_id).connection_id != id_) { + callback.Run(false); + return; + } + + Node* node = GetNode(NodeIdFromTransportId(node_id)); + if (!node) { + callback.Run(false); + return; + } + + RootNodeManager::ScopedChange change( + this, root_node_manager_, + RootNodeManager::CHANGE_TYPE_DONT_ADVANCE_SERVER_CHANGE_ID, false); + gfx::Rect old_bounds = node->window()->bounds(); + node->window()->SetBounds(bounds.To<gfx::Rect>()); + root_node_manager_->ProcessNodeBoundsChanged( + node, old_bounds, bounds.To<gfx::Rect>()); + callback.Run(true); +} + +void ViewManagerServiceImpl::Embed(const String& url, + Array<uint32_t> node_ids, + const Callback<void(bool)>& callback) { + bool success = CanEmbed(node_ids); + if (success) { + // We may already have this connection, if so reuse it. + ViewManagerServiceImpl* existing_connection = + root_node_manager_->GetConnectionByCreator(id_, url.To<std::string>()); + if (existing_connection) + success = existing_connection->AddRoots(node_ids.storage()); + else + root_node_manager_->Embed(id_, url, node_ids); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::DispatchOnViewInputEvent(Id transport_view_id, + EventPtr event) { + // We only allow the WM to dispatch events. At some point this function will + // move to a separate interface and the check can go away. + if (id_ != kWindowManagerConnection) + return; + + const ViewId view_id(ViewIdFromTransportId(transport_view_id)); + ViewManagerServiceImpl* connection = root_node_manager_->GetConnection( + view_id.connection_id); + if (connection) + connection->client()->OnViewInputEvent( + transport_view_id, + event.Pass(), + base::Bind(&base::DoNothing)); +} + +void ViewManagerServiceImpl::OnNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent) { + root_node_manager_->ProcessNodeHierarchyChanged(node, new_parent, old_parent); +} + +void ViewManagerServiceImpl::OnNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view) { + root_node_manager_->ProcessNodeViewReplaced(node, new_view, old_view); +} + +void ViewManagerServiceImpl::OnViewInputEvent(const View* view, + const ui::Event* event) { + root_node_manager_->DispatchViewInputEventToWindowManager(view, event); +} + +void ViewManagerServiceImpl::OnConnectionEstablished() { + root_node_manager_->AddConnection(this); + + std::vector<const Node*> to_send; + if (roots_.empty()) { + GetUnknownNodesFrom(root_node_manager_->root(), &to_send); + } else { + for (NodeIdSet::const_iterator i = roots_.begin(); i != roots_.end(); ++i) + GetUnknownNodesFrom(GetNode(NodeIdFromTransportId(*i)), &to_send); + } + + client()->OnViewManagerConnectionEstablished( + id_, + creator_url_, + root_node_manager_->next_server_change_id(), + NodesToNodeDatas(to_send)); +} + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/view_manager_service_impl.h b/chromium/mojo/services/view_manager/view_manager_service_impl.h new file mode 100644 index 00000000000..53950dc3c11 --- /dev/null +++ b/chromium/mojo/services/view_manager/view_manager_service_impl.h @@ -0,0 +1,269 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ +#define MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/node_delegate.h" +#include "mojo/services/view_manager/view_manager_export.h" + +namespace gfx { +class Rect; +} + +namespace mojo { +namespace view_manager { +namespace service { + +class Node; +class RootNodeManager; +class View; + +#if defined(OS_WIN) +// Equivalent of NON_EXPORTED_BASE which does not work with the template snafu +// below. +#pragma warning(push) +#pragma warning(disable : 4275) +#endif + +// Manages a connection from the client. +class MOJO_VIEW_MANAGER_EXPORT ViewManagerServiceImpl + : public InterfaceImpl<ViewManagerService>, + public NodeDelegate { + public: + ViewManagerServiceImpl(RootNodeManager* root_node_manager, + ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url); + virtual ~ViewManagerServiceImpl(); + + // Used to mark this connection as originating from a call to + // ViewManagerService::Connect(). When set OnConnectionError() deletes |this|. + void set_delete_on_connection_error() { delete_on_connection_error_ = true; } + + ConnectionSpecificId id() const { return id_; } + ConnectionSpecificId creator_id() const { return creator_id_; } + const std::string& url() const { return url_; } + + // Returns the Node with the specified id. + Node* GetNode(const NodeId& id) { + return const_cast<Node*>( + const_cast<const ViewManagerServiceImpl*>(this)->GetNode(id)); + } + const Node* GetNode(const NodeId& id) const; + + // Returns the View with the specified id. + View* GetView(const ViewId& id) { + return const_cast<View*>( + const_cast<const ViewManagerServiceImpl*>(this)->GetView(id)); + } + const View* GetView(const ViewId& id) const; + + void SetRoots(const Array<Id>& node_ids); + + // Invoked when a connection is destroyed. + void OnViewManagerServiceImplDestroyed(ConnectionSpecificId id); + + // The following methods are invoked after the corresponding change has been + // processed. They do the appropriate bookkeeping and update the client as + // necessary. + void ProcessNodeBoundsChanged(const Node* node, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change); + void ProcessNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent, + Id server_change_id, + bool originated_change); + void ProcessNodeReorder(const Node* node, + const Node* relative_node, + OrderDirection direction, + Id server_change_id, + bool originated_change); + void ProcessNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view, + bool originated_change); + void ProcessNodeDeleted(const NodeId& node, + Id server_change_id, + bool originated_change); + void ProcessViewDeleted(const ViewId& view, bool originated_change); + + // TODO(sky): move this to private section (currently can't because of + // bindings). + // InterfaceImp overrides: + virtual void OnConnectionError() MOJO_OVERRIDE; + + private: + typedef std::map<ConnectionSpecificId, Node*> NodeMap; + typedef std::map<ConnectionSpecificId, View*> ViewMap; + typedef base::hash_set<Id> NodeIdSet; + + // These functions return true if the corresponding mojom function is allowed + // for this connection. + bool CanRemoveNodeFromParent(const Node* node) const; + bool CanAddNode(const Node* parent, const Node* child) const; + bool CanReorderNode(const Node* node, + const Node* relative_node, + OrderDirection direction) const; + bool CanDeleteNode(const NodeId& node_id) const; + bool CanDeleteView(const ViewId& view_id) const; + bool CanSetView(const Node* node, const ViewId& view_id) const; + bool CanSetFocus(const Node* node) const; + bool CanGetNodeTree(const Node* node) const; + bool CanEmbed(const mojo::Array<uint32_t>& node_ids) const; + + // Deletes a node owned by this connection. Returns true on success. |source| + // is the connection that originated the change. + bool DeleteNodeImpl(ViewManagerServiceImpl* source, const NodeId& node_id); + + // Deletes a view owned by this connection. Returns true on success. |source| + // is the connection that originated the change. + bool DeleteViewImpl(ViewManagerServiceImpl* source, const ViewId& view_id); + + // Sets the view associated with a node. + bool SetViewImpl(Node* node, const ViewId& view_id); + + // If |node| is known (in |known_nodes_|) does nothing. Otherwise adds |node| + // to |nodes|, marks |node| as known and recurses. + void GetUnknownNodesFrom(const Node* node, std::vector<const Node*>* nodes); + + // Removes |node| and all its descendants from |known_nodes_|. This does not + // recurse through nodes that were created by this connection. + void RemoveFromKnown(const Node* node); + + // Adds |node_ids| to roots, returning true if at least one of the nodes was + // not already a root. If at least one of the nodes was not already a root + // the client is told of the new roots. + bool AddRoots(const std::vector<Id>& node_ids); + + // Returns true if |node| is a non-null and a descendant of |roots_| (or + // |roots_| is empty). + bool IsNodeDescendantOfRoots(const Node* node) const; + + // Returns true if notification should be sent of a hierarchy change. If true + // is returned, any nodes that need to be sent to the client are added to + // |to_send|. + bool ShouldNotifyOnHierarchyChange(const Node* node, + const Node** new_parent, + const Node** old_parent, + std::vector<const Node*>* to_send); + + // Converts an array of Nodes to NodeDatas. This assumes all the nodes are + // valid for the client. The parent of nodes the client is not allowed to see + // are set to NULL (in the returned NodeDatas). + Array<NodeDataPtr> NodesToNodeDatas(const std::vector<const Node*>& nodes); + + // Overridden from ViewManagerService: + virtual void CreateNode(Id transport_node_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void DeleteNode(Id transport_node_id, + Id server_change_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void AddNode(Id parent_id, + Id child_id, + Id server_change_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void RemoveNodeFromParent( + Id node_id, + Id server_change_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void ReorderNode(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void GetNodeTree( + Id node_id, + const Callback<void(Array<NodeDataPtr>)>& callback) OVERRIDE; + virtual void CreateView(Id transport_view_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void DeleteView(Id transport_view_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void SetView(Id transport_node_id, + Id transport_view_id, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void SetViewContents(Id view_id, + ScopedSharedBufferHandle buffer, + uint32_t buffer_size, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void SetFocus(Id node_id, + const Callback<void(bool)> & callback) OVERRIDE; + virtual void SetNodeBounds(Id node_id, + RectPtr bounds, + const Callback<void(bool)>& callback) OVERRIDE; + virtual void Embed(const mojo::String& url, + mojo::Array<uint32_t> node_ids, + const mojo::Callback<void(bool)>& callback) OVERRIDE; + virtual void DispatchOnViewInputEvent(Id transport_view_id, + EventPtr event) OVERRIDE; + + // Overridden from NodeDelegate: + virtual void OnNodeHierarchyChanged(const Node* node, + const Node* new_parent, + const Node* old_parent) OVERRIDE; + virtual void OnNodeViewReplaced(const Node* node, + const View* new_view, + const View* old_view) OVERRIDE; + virtual void OnViewInputEvent(const View* view, + const ui::Event* event) OVERRIDE; + + // InterfaceImp overrides: + virtual void OnConnectionEstablished() MOJO_OVERRIDE; + + RootNodeManager* root_node_manager_; + + // Id of this connection as assigned by RootNodeManager. + const ConnectionSpecificId id_; + + // URL this connection was created for. + const std::string url_; + + // ID of the connection that created us. If 0 it indicates either we were + // created by the root, or the connection that created us has been destroyed. + ConnectionSpecificId creator_id_; + + // The URL of the app that embedded the app this connection was created for. + const std::string creator_url_; + + NodeMap node_map_; + + ViewMap view_map_; + + // The set of nodes that has been communicated to the client. + NodeIdSet known_nodes_; + + // This is the set of nodes the connection can parent nodes to (in addition to + // any nodes created by this connection). If empty the connection can + // manipulate any nodes (except for deleting other connections nodes/views). + // The connection can not delete or move these. If this is set to a non-empty + // value and all the nodes are deleted (by another connection), then an + // invalid node is added here to ensure this connection is still constrained. + NodeIdSet roots_; + + // See description above setter. + bool delete_on_connection_error_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceImpl); +}; + +#if defined(OS_WIN) +#pragma warning(pop) +#endif + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ diff --git a/chromium/mojo/services/view_manager/view_manager_unittest.cc b/chromium/mojo/services/view_manager/view_manager_unittest.cc new file mode 100644 index 00000000000..ed540926a89 --- /dev/null +++ b/chromium/mojo/services/view_manager/view_manager_unittest.cc @@ -0,0 +1,1370 @@ +// 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 <string> +#include <vector> + +#include "base/at_exit.h" +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/public/cpp/application/connect.h" +#include "mojo/public/cpp/bindings/lib/router.h" +#include "mojo/service_manager/service_manager.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/public/cpp/view_manager/types.h" +#include "mojo/services/public/cpp/view_manager/util.h" +#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/test_change_tracker.h" +#include "mojo/shell/shell_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" + +namespace mojo { +namespace view_manager { +namespace service { + +namespace { + +const char kTestServiceURL[] = "mojo:test_url"; + +// ViewManagerProxy is a proxy to an ViewManagerService. It handles invoking +// ViewManagerService functions on the right thread in a synchronous manner +// (each ViewManagerService cover function blocks until the response from the +// ViewManagerService is returned). In addition it tracks the set of +// ViewManagerClient messages received by way of a vector of Changes. Use +// DoRunLoopUntilChangesCount() to wait for a certain number of messages to be +// received. +class ViewManagerProxy : public TestChangeTracker::Delegate { + public: + explicit ViewManagerProxy(TestChangeTracker* tracker) + : tracker_(tracker), + view_manager_(NULL), + quit_count_(0), + router_(NULL) { + SetInstance(this); + } + + virtual ~ViewManagerProxy() {} + + // Runs a message loop until the single instance has been created. + static ViewManagerProxy* WaitForInstance() { + if (!instance_) + RunMainLoop(); + ViewManagerProxy* instance = instance_; + instance_ = NULL; + return instance; + } + + ViewManagerService* view_manager() { return view_manager_; } + + // Runs the main loop until |count| changes have been received. + std::vector<Change> DoRunLoopUntilChangesCount(size_t count) { + DCHECK_EQ(0u, quit_count_); + if (tracker_->changes()->size() >= count) { + CopyChangesFromTracker(); + return changes_; + } + quit_count_ = count - tracker_->changes()->size(); + // Run the current message loop. When |count| Changes have been received, + // we'll quit. + RunMainLoop(); + return changes_; + } + + const std::vector<Change>& changes() const { return changes_; } + + // Destroys the connection, blocking until done. + void Destroy() { + router_->CloseMessagePipe(); + } + + // The following functions are cover methods for ViewManagerService. They + // block until the result is received. + bool CreateNode(Id node_id) { + changes_.clear(); + bool result = false; + view_manager_->CreateNode(node_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool AddNode(Id parent, Id child, Id server_change_id) { + changes_.clear(); + bool result = false; + view_manager_->AddNode(parent, child, server_change_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool RemoveNodeFromParent(Id node_id, Id server_change_id) { + changes_.clear(); + bool result = false; + view_manager_->RemoveNodeFromParent(node_id, server_change_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool ReorderNode(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id) { + changes_.clear(); + bool result = false; + view_manager_->ReorderNode(node_id, relative_node_id, direction, + server_change_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool SetView(Id node_id, Id view_id) { + changes_.clear(); + bool result = false; + view_manager_->SetView(node_id, view_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool CreateView(Id view_id) { + changes_.clear(); + bool result = false; + view_manager_->CreateView(view_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + void GetNodeTree(Id node_id, std::vector<TestNode>* nodes) { + changes_.clear(); + view_manager_->GetNodeTree(node_id, + base::Bind(&ViewManagerProxy::GotNodeTree, + base::Unretained(this), nodes)); + RunMainLoop(); + } + bool Embed(const std::vector<Id>& nodes) { + changes_.clear(); + base::AutoReset<bool> auto_reset(&in_embed_, true); + bool result = false; + view_manager_->Embed(kTestServiceURL, Array<Id>::From(nodes), + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool DeleteNode(Id node_id, Id server_change_id) { + changes_.clear(); + bool result = false; + view_manager_->DeleteNode(node_id, + server_change_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool DeleteView(Id view_id) { + changes_.clear(); + bool result = false; + view_manager_->DeleteView(view_id, + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + bool SetNodeBounds(Id node_id, const gfx::Rect& bounds) { + changes_.clear(); + bool result = false; + view_manager_->SetNodeBounds(node_id, Rect::From(bounds), + base::Bind(&ViewManagerProxy::GotResult, + base::Unretained(this), &result)); + RunMainLoop(); + return result; + } + + private: + friend class TestViewManagerClientConnection; + + void set_router(mojo::internal::Router* router) { router_ = router; } + + void set_view_manager(ViewManagerService* view_manager) { + view_manager_ = view_manager; + } + + static void RunMainLoop() { + DCHECK(!main_run_loop_); + main_run_loop_ = new base::RunLoop; + main_run_loop_->Run(); + delete main_run_loop_; + main_run_loop_ = NULL; + } + + void QuitCountReached() { + CopyChangesFromTracker(); + main_run_loop_->Quit(); + } + + void CopyChangesFromTracker() { + std::vector<Change> changes; + tracker_->changes()->swap(changes); + changes_.swap(changes); + } + + static void SetInstance(ViewManagerProxy* instance) { + DCHECK(!instance_); + instance_ = instance; + // Embed() runs its own run loop that is quit when the result is + // received. Embed() also results in a new instance. If we quit here while + // waiting for a Embed() we would prematurely return before we got the + // result from Embed(). + if (!in_embed_ && main_run_loop_) + main_run_loop_->Quit(); + } + + // Callbacks from the various ViewManagerService functions. + void GotResult(bool* result_cache, bool result) { + *result_cache = result; + DCHECK(main_run_loop_); + main_run_loop_->Quit(); + } + + void GotNodeTree(std::vector<TestNode>* nodes, Array<NodeDataPtr> results) { + NodeDatasToTestNodes(results, nodes); + DCHECK(main_run_loop_); + main_run_loop_->Quit(); + } + + // TestChangeTracker::Delegate: + virtual void OnChangeAdded() OVERRIDE { + if (quit_count_ > 0 && --quit_count_ == 0) + QuitCountReached(); + } + + static ViewManagerProxy* instance_; + static base::RunLoop* main_run_loop_; + static bool in_embed_; + + TestChangeTracker* tracker_; + + // MessageLoop of the test. + base::MessageLoop* main_loop_; + + ViewManagerService* view_manager_; + + // Number of changes we're waiting on until we quit the current loop. + size_t quit_count_; + + std::vector<Change> changes_; + + mojo::internal::Router* router_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerProxy); +}; + +// static +ViewManagerProxy* ViewManagerProxy::instance_ = NULL; + +// static +base::RunLoop* ViewManagerProxy::main_run_loop_ = NULL; + +// static +bool ViewManagerProxy::in_embed_ = false; + +class TestViewManagerClientConnection + : public InterfaceImpl<ViewManagerClient> { + public: + TestViewManagerClientConnection() : connection_(&tracker_) { + tracker_.set_delegate(&connection_); + } + + // InterfaceImp: + virtual void OnConnectionEstablished() OVERRIDE { + connection_.set_router(internal_state()->router()); + connection_.set_view_manager(client()); + } + + // ViewMangerClient: + virtual void OnViewManagerConnectionEstablished( + ConnectionSpecificId connection_id, + const String& creator_url, + Id next_server_change_id, + Array<NodeDataPtr> nodes) OVERRIDE { + tracker_.OnViewManagerConnectionEstablished( + connection_id, creator_url, next_server_change_id, nodes.Pass()); + } + virtual void OnRootsAdded(Array<NodeDataPtr> nodes) OVERRIDE { + tracker_.OnRootsAdded(nodes.Pass()); + } + virtual void OnServerChangeIdAdvanced( + Id next_server_change_id) OVERRIDE { + tracker_.OnServerChangeIdAdvanced(next_server_change_id); + } + virtual void OnNodeBoundsChanged(Id node_id, + RectPtr old_bounds, + RectPtr new_bounds) OVERRIDE { + tracker_.OnNodeBoundsChanged(node_id, old_bounds.Pass(), new_bounds.Pass()); + } + virtual void OnNodeHierarchyChanged(Id node, + Id new_parent, + Id old_parent, + Id server_change_id, + Array<NodeDataPtr> nodes) OVERRIDE { + tracker_.OnNodeHierarchyChanged(node, new_parent, old_parent, + server_change_id, nodes.Pass()); + } + virtual void OnNodeReordered(Id node_id, + Id relative_node_id, + OrderDirection direction, + Id server_change_id) OVERRIDE { + tracker_.OnNodeReordered(node_id, relative_node_id, direction, + server_change_id); + } + virtual void OnNodeDeleted(Id node, Id server_change_id) OVERRIDE { + tracker_.OnNodeDeleted(node, server_change_id); + } + virtual void OnViewDeleted(Id view) OVERRIDE { + tracker_.OnViewDeleted(view); + } + virtual void OnNodeViewReplaced(Id node, + Id new_view_id, + Id old_view_id) OVERRIDE { + tracker_.OnNodeViewReplaced(node, new_view_id, old_view_id); + } + virtual void OnViewInputEvent(Id view_id, + EventPtr event, + const Callback<void()>& callback) OVERRIDE { + tracker_.OnViewInputEvent(view_id, event.Pass()); + } + virtual void DispatchOnViewInputEvent(Id view_id, + mojo::EventPtr event) OVERRIDE { + } + + private: + TestChangeTracker tracker_; + ViewManagerProxy connection_; + + DISALLOW_COPY_AND_ASSIGN(TestViewManagerClientConnection); +}; + +// Used with ViewManagerService::Embed(). Creates a +// TestViewManagerClientConnection, which creates and owns the ViewManagerProxy. +class EmbedServiceLoader : public ServiceLoader { + public: + EmbedServiceLoader() {} + virtual ~EmbedServiceLoader() {} + + // ServiceLoader: + virtual void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle shell_handle) OVERRIDE { + scoped_ptr<Application> app(new Application(shell_handle.Pass())); + app->AddService<TestViewManagerClientConnection>(); + apps_.push_back(app.release()); + } + virtual void OnServiceError(ServiceManager* manager, + const GURL& url) OVERRIDE { + } + + private: + ScopedVector<Application> apps_; + + DISALLOW_COPY_AND_ASSIGN(EmbedServiceLoader); +}; + +// Creates an id used for transport from the specified parameters. +Id BuildNodeId(ConnectionSpecificId connection_id, + ConnectionSpecificId node_id) { + return (connection_id << 16) | node_id; +} + +// Creates an id used for transport from the specified parameters. +Id BuildViewId(ConnectionSpecificId connection_id, + ConnectionSpecificId view_id) { + return (connection_id << 16) | view_id; +} + +// Callback from EmbedRoot(). |result| is the result of the +// Embed() call and |run_loop| the nested RunLoop. +void EmbedRootCallback(bool* result_cache, + base::RunLoop* run_loop, + bool result) { + *result_cache = result; + run_loop->Quit(); +} + +// Resposible for establishing the initial ViewManagerService connection. Blocks +// until result is determined. +bool EmbedRoot(ViewManagerInitService* view_manager_init, + const std::string& url) { + bool result = false; + base::RunLoop run_loop; + view_manager_init->EmbedRoot(url, base::Bind(&EmbedRootCallback, + &result, &run_loop)); + run_loop.Run(); + return result; +} + +} // namespace + +typedef std::vector<std::string> Changes; + +class ViewManagerTest : public testing::Test { + public: + ViewManagerTest() : connection_(NULL), connection2_(NULL) {} + + virtual void SetUp() OVERRIDE { + test_helper_.Init(); + + test_helper_.SetLoaderForURL( + scoped_ptr<ServiceLoader>(new EmbedServiceLoader()), + GURL(kTestServiceURL)); + + ConnectToService(test_helper_.service_provider(), + "mojo:mojo_view_manager", + &view_manager_init_); + ASSERT_TRUE(EmbedRoot(view_manager_init_.get(), kTestServiceURL)); + + connection_ = ViewManagerProxy::WaitForInstance(); + ASSERT_TRUE(connection_ != NULL); + connection_->DoRunLoopUntilChangesCount(1); + } + + virtual void TearDown() OVERRIDE { + if (connection2_) + connection2_->Destroy(); + if (connection_) + connection_->Destroy(); + } + + protected: + void EstablishSecondConnectionWithRoots(Id id1, Id id2) { + std::vector<Id> node_ids; + node_ids.push_back(id1); + if (id2 != 0) + node_ids.push_back(id2); + ASSERT_TRUE(connection_->Embed(node_ids)); + connection2_ = ViewManagerProxy::WaitForInstance(); + ASSERT_TRUE(connection2_ != NULL); + connection2_->DoRunLoopUntilChangesCount(1); + ASSERT_EQ(1u, connection2_->changes().size()); + } + + // Creates a second connection to the viewmanager. + void EstablishSecondConnection(bool create_initial_node) { + if (create_initial_node) + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_NO_FATAL_FAILURE( + EstablishSecondConnectionWithRoots(BuildNodeId(1, 1), 0)); + const std::vector<Change>& changes(connection2_->changes()); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("OnConnectionEstablished creator=mojo:test_url", + ChangesToDescription1(changes)[0]); + if (create_initial_node) { + EXPECT_EQ("[node=1,1 parent=null view=null]", + ChangeNodeDescription(changes)); + } + } + + void DestroySecondConnection() { + connection2_->Destroy(); + connection2_ = NULL; + } + + base::ShadowingAtExitManager at_exit_; + base::MessageLoop loop_; + shell::ShellTestHelper test_helper_; + + ViewManagerInitServicePtr view_manager_init_; + + ViewManagerProxy* connection_; + ViewManagerProxy* connection2_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); +}; + +// TODO(sky): reenable tests: http://crbug.com/385475 + +// Verifies client gets a valid id. +TEST_F(ViewManagerTest, DISABLED_ValidId) { + // TODO(beng): this should really have the URL of the application that + // connected to ViewManagerInit. + EXPECT_EQ("OnConnectionEstablished creator=", + ChangesToDescription1(connection_->changes())[0]); + + // All these tests assume 1 for the client id. The only real assertion here is + // the client id is not zero, but adding this as rest of code here assumes 1. + EXPECT_EQ(1, connection_->changes()[0].connection_id); + + // Change ids start at 1 as well. + EXPECT_EQ(static_cast<Id>(1), connection_->changes()[0].change_id); +} + +// Verifies two clients/connections get different ids. +TEST_F(ViewManagerTest, DISABLED_TwoClientsGetDifferentConnectionIds) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + EXPECT_EQ("OnConnectionEstablished creator=mojo:test_url", + ChangesToDescription1(connection2_->changes())[0]); + + // It isn't strickly necessary that the second connection gets 2, but these + // tests are written assuming that is the case. The key thing is the + // connection ids of |connection_| and |connection2_| differ. + EXPECT_EQ(2, connection2_->changes()[0].connection_id); + + // Change ids start at 1 as well. + EXPECT_EQ(static_cast<Id>(1), connection2_->changes()[0].change_id); +} + +// Verifies client gets a valid id. +TEST_F(ViewManagerTest, DISABLED_CreateNode) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + EXPECT_TRUE(connection_->changes().empty()); + + // Can't create a node with the same id. + ASSERT_FALSE(connection_->CreateNode(BuildNodeId(1, 1))); + EXPECT_TRUE(connection_->changes().empty()); + + // Can't create a node with a bogus connection id. + EXPECT_FALSE(connection_->CreateNode(BuildNodeId(2, 1))); + EXPECT_TRUE(connection_->changes().empty()); +} + +TEST_F(ViewManagerTest, DISABLED_CreateViewFailsWithBogusConnectionId) { + EXPECT_FALSE(connection_->CreateView(BuildViewId(2, 1))); + EXPECT_TRUE(connection_->changes().empty()); +} + +// Verifies hierarchy changes. +TEST_F(ViewManagerTest, DISABLED_AddRemoveNotify) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 3), 1)); + EXPECT_TRUE(connection_->changes().empty()); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 2", changes[0]); + } + + // Remove 3 from its parent. + { + ASSERT_TRUE(connection_->RemoveNodeFromParent(BuildNodeId(1, 3), 2)); + EXPECT_TRUE(connection_->changes().empty()); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 3", changes[0]); + } +} + +// Verifies AddNode fails when node is already in position. +TEST_F(ViewManagerTest, DISABLED_AddNodeWithNoChange) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 3), 1)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 2", changes[0]); + } + + // Try again, this should fail. + { + EXPECT_FALSE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 3), 2)); + } +} + +// Verifies AddNode fails when node is already in position. +TEST_F(ViewManagerTest, DISABLED_AddAncestorFails) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 3), 1)); + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 2", changes[0]); + } + + // Try to make 2 a child of 3, this should fail since 2 is an ancestor of 3. + { + EXPECT_FALSE(connection_->AddNode(BuildNodeId(1, 3), BuildNodeId(1, 2), 2)); + } +} + +// Verifies adding with an invalid id fails. +TEST_F(ViewManagerTest, DISABLED_AddWithInvalidServerId) { + // Create two nodes. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + // Make 2 a child of 1. Supply an invalid change id, which should fail. + ASSERT_FALSE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 0)); +} + +// Verifies adding to root sends right notifications. +TEST_F(ViewManagerTest, DISABLED_AddToRoot) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 21))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 21. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 21), BuildNodeId(1, 3), 1)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 2", changes[0]); + } + + // Make 21 a child of 1. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 21), 2)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=2 node=1,21 new_parent=1,1 old_parent=null", + changes[0]); + } +} + +// Verifies HierarchyChanged is correctly sent for various adds/removes. +TEST_F(ViewManagerTest, DISABLED_NodeHierarchyChangedNodes) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 11))); + // Make 11 a child of 2. + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 11), 1)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 2 a child of 1. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 2)); + + // Client 2 should get a hierarchy change that includes the new nodes as it + // has not yet seen them. + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=2 node=1,2 new_parent=1,1 old_parent=null", + changes[0]); + EXPECT_EQ("[node=1,2 parent=1,1 view=null]," + "[node=1,11 parent=1,2 view=null]", + ChangeNodeDescription(connection2_->changes())); + } + + // Add 1 to the root. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 3)); + + // Client 2 should get a hierarchy change that includes the new nodes as it + // has not yet seen them. + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=3 node=1,1 new_parent=null old_parent=null", + changes[0]); + EXPECT_EQ(std::string(), ChangeNodeDescription(connection2_->changes())); + } + + // Remove 1 from its parent. + { + ASSERT_TRUE(connection_->RemoveNodeFromParent(BuildNodeId(1, 1), 4)); + EXPECT_TRUE(connection_->changes().empty()); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=4 node=1,1 new_parent=null old_parent=null", + changes[0]); + } + + // Create another node, 111, parent it to 11. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 111))); + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 11), BuildNodeId(1, 111), + 5)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=5 node=1,111 new_parent=1,11 " + "old_parent=null", changes[0]); + EXPECT_EQ("[node=1,111 parent=1,11 view=null]", + ChangeNodeDescription(connection2_->changes())); + } + + // Reattach 1 to the root. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 6)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=6 node=1,1 new_parent=null old_parent=null", + changes[0]); + EXPECT_EQ(std::string(), ChangeNodeDescription(connection2_->changes())); + } +} + +TEST_F(ViewManagerTest, DISABLED_NodeHierarchyChangedAddingKnownToUnknown) { + // Create the following structure: root -> 1 -> 11 and 2->21 (2 has no + // parent). + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 11))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 21))); + + // Set up the hierarchy. + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 11), 2)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 21), 3)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + { + EXPECT_EQ("[node=1,1 parent=null view=null]," + "[node=1,11 parent=1,1 view=null]", + ChangeNodeDescription(connection2_->changes())); + } + + // Remove 11, should result in a delete (since 11 is no longer in connection + // 2's root). + { + ASSERT_TRUE(connection_->RemoveNodeFromParent(BuildNodeId(1, 11), 4)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("NodeDeleted change_id=4 node=1,11", changes[0]); + } + + // Add 2 to 1. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 5)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=5 node=1,2 new_parent=1,1 old_parent=null", + changes[0]); + EXPECT_EQ("[node=1,2 parent=1,1 view=null]," + "[node=1,21 parent=1,2 view=null]", + ChangeNodeDescription(connection2_->changes())); + } +} + +TEST_F(ViewManagerTest, DISABLED_ReorderNode) { + Id node1_id = BuildNodeId(1, 1); + Id node2_id = BuildNodeId(1, 2); + Id node3_id = BuildNodeId(1, 3); + Id node4_id = BuildNodeId(1, 4); // Peer to 1,1 + Id node5_id = BuildNodeId(1, 5); // Peer to 1,1 + Id node6_id = BuildNodeId(1, 6); // Child of 1,2. + Id node7_id = BuildNodeId(1, 7); // Unparented. + Id node8_id = BuildNodeId(1, 8); // Unparented. + ASSERT_TRUE(connection_->CreateNode(node1_id)); + ASSERT_TRUE(connection_->CreateNode(node2_id)); + ASSERT_TRUE(connection_->CreateNode(node3_id)); + ASSERT_TRUE(connection_->CreateNode(node4_id)); + ASSERT_TRUE(connection_->CreateNode(node5_id)); + ASSERT_TRUE(connection_->CreateNode(node6_id)); + ASSERT_TRUE(connection_->CreateNode(node7_id)); + ASSERT_TRUE(connection_->CreateNode(node8_id)); + ASSERT_TRUE(connection_->AddNode(node1_id, node2_id, 1)); + ASSERT_TRUE(connection_->AddNode(node2_id, node6_id, 2)); + ASSERT_TRUE(connection_->AddNode(node1_id, node3_id, 3)); + ASSERT_TRUE(connection_->AddNode( + NodeIdToTransportId(RootNodeId()), node4_id, 4)); + ASSERT_TRUE(connection_->AddNode( + NodeIdToTransportId(RootNodeId()), node5_id, 5)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + { + connection_->ReorderNode(node2_id, node3_id, ORDER_ABOVE, 6); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "Reordered change_id=6 node=1,2 relative=1,3 direction=above", + changes[0]); + } + + { + connection_->ReorderNode(node2_id, node3_id, ORDER_BELOW, 7); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "Reordered change_id=7 node=1,2 relative=1,3 direction=below", + changes[0]); + } + + { + // node2 is already below node3. + EXPECT_FALSE(connection_->ReorderNode(node2_id, node3_id, ORDER_BELOW, 8)); + } + + { + // node4 & 5 are unknown to connection2_. + EXPECT_FALSE(connection2_->ReorderNode(node4_id, node5_id, ORDER_ABOVE, 8)); + } + + { + // node6 & node3 have different parents. + EXPECT_FALSE(connection_->ReorderNode(node3_id, node6_id, ORDER_ABOVE, 8)); + } + + { + // Non-existent node-ids + EXPECT_FALSE(connection_->ReorderNode(BuildNodeId(1, 27), + BuildNodeId(1, 28), + ORDER_ABOVE, + 8)); + } + + { + // node7 & node8 are un-parented. + EXPECT_FALSE(connection_->ReorderNode(node7_id, node8_id, ORDER_ABOVE, 8)); + } +} + +// Verifies DeleteNode works. +TEST_F(ViewManagerTest, DISABLED_DeleteNode) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 2 a child of 1. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 1)); + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("HierarchyChanged change_id=1 node=1,2 new_parent=1,1 " + "old_parent=null", changes[0]); + } + + // Delete 2. + { + ASSERT_TRUE(connection_->DeleteNode(BuildNodeId(1, 2), 2)); + EXPECT_TRUE(connection_->changes().empty()); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("NodeDeleted change_id=2 node=1,2", changes[0]); + } +} + +// Verifies DeleteNode isn't allowed from a separate connection. +TEST_F(ViewManagerTest, DISABLED_DeleteNodeFromAnotherConnectionDisallowed) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + EXPECT_FALSE(connection2_->DeleteNode(BuildNodeId(1, 1), 1)); +} + +// Verifies DeleteView isn't allowed from a separate connection. +TEST_F(ViewManagerTest, DISABLED_DeleteViewFromAnotherConnectionDisallowed) { + ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1))); + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + EXPECT_FALSE(connection2_->DeleteView(BuildViewId(1, 1))); +} + +// Verifies if a node was deleted and then reused that other clients are +// properly notified. +TEST_F(ViewManagerTest, DISABLED_ReuseDeletedNodeId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + // Add 2 to 1. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 1)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + EXPECT_EQ( + "HierarchyChanged change_id=1 node=1,2 new_parent=1,1 old_parent=null", + changes[0]); + EXPECT_EQ("[node=1,2 parent=1,1 view=null]", + ChangeNodeDescription(connection2_->changes())); + } + + // Delete 2. + { + ASSERT_TRUE(connection_->DeleteNode(BuildNodeId(1, 2), 2)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("NodeDeleted change_id=2 node=1,2", changes[0]); + } + + // Create 2 again, and add it back to 1. Should get the same notification. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 3)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + EXPECT_EQ( + "HierarchyChanged change_id=3 node=1,2 new_parent=1,1 old_parent=null", + changes[0]); + EXPECT_EQ("[node=1,2 parent=1,1 view=null]", + ChangeNodeDescription(connection2_->changes())); + } +} + +// Assertions around setting a view. +TEST_F(ViewManagerTest, DISABLED_SetView) { + // Create nodes 1, 2 and 3 and the view 11. Nodes 2 and 3 are parented to 1. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 11))); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 1)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 3), 2)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Set view 11 on node 1. + { + ASSERT_TRUE(connection_->SetView(BuildNodeId(1, 1), + BuildViewId(1, 11))); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ViewReplaced node=1,1 new_view=1,11 old_view=null", + changes[0]); + } + + // Set view 11 on node 2. + { + ASSERT_TRUE(connection_->SetView(BuildNodeId(1, 2), BuildViewId(1, 11))); + + connection2_->DoRunLoopUntilChangesCount(2); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(2u, changes.size()); + EXPECT_EQ("ViewReplaced node=1,1 new_view=null old_view=1,11", + changes[0]); + EXPECT_EQ("ViewReplaced node=1,2 new_view=1,11 old_view=null", + changes[1]); + } +} + +// Verifies deleting a node with a view sends correct notifications. +TEST_F(ViewManagerTest, DISABLED_DeleteNodeWithView) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 11))); + + // Set view 11 on node 2. + ASSERT_TRUE(connection_->SetView(BuildNodeId(1, 2), BuildViewId(1, 11))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Delete node 2. The second connection should not see this because the node + // was not known to it. + { + ASSERT_TRUE(connection_->DeleteNode(BuildNodeId(1, 2), 1)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 2", changes[0]); + } + + // Parent 3 to 1. + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 3), 2)); + connection2_->DoRunLoopUntilChangesCount(1); + + // Set view 11 on node 3. + { + ASSERT_TRUE(connection_->SetView(BuildNodeId(1, 3), BuildViewId(1, 11))); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ViewReplaced node=1,3 new_view=1,11 old_view=null", changes[0]); + } + + // Delete 3. + { + ASSERT_TRUE(connection_->DeleteNode(BuildNodeId(1, 3), 3)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("NodeDeleted change_id=3 node=1,3", changes[0]); + } +} + +// Sets view from one connection on another. +TEST_F(ViewManagerTest, DISABLED_SetViewFromSecondConnection) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + // Create a view in the second connection. + ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 51))); + + // Attach view to node 1 in the first connection. + { + ASSERT_TRUE(connection2_->SetView(BuildNodeId(1, 1), BuildViewId(2, 51))); + connection_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ViewReplaced node=1,1 new_view=2,51 old_view=null", changes[0]); + } + + // Shutdown the second connection and verify view is removed. + { + DestroySecondConnection(); + connection_->DoRunLoopUntilChangesCount(2); + const Changes changes(ChangesToDescription1(connection_->changes())); + ASSERT_EQ(2u, changes.size()); + EXPECT_EQ("ViewReplaced node=1,1 new_view=null old_view=2,51", changes[0]); + EXPECT_EQ("ViewDeleted view=2,51", changes[1]); + } +} + +// Assertions for GetNodeTree. +TEST_F(ViewManagerTest, DISABLED_GetNodeTree) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Create 11 in first connection and make it a child of 1. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 11))); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 11), 2)); + + // Create two nodes in second connection, 2 and 3, both children of 1. + ASSERT_TRUE(connection2_->CreateNode(BuildNodeId(2, 2))); + ASSERT_TRUE(connection2_->CreateNode(BuildNodeId(2, 3))); + ASSERT_TRUE(connection2_->AddNode(BuildNodeId(1, 1), BuildNodeId(2, 2), 3)); + ASSERT_TRUE(connection2_->AddNode(BuildNodeId(1, 1), BuildNodeId(2, 3), 4)); + + // Attach view to node 11 in the first connection. + ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 51))); + ASSERT_TRUE(connection_->SetView(BuildNodeId(1, 11), BuildViewId(1, 51))); + + // Verifies GetNodeTree() on the root. + { + std::vector<TestNode> nodes; + connection_->GetNodeTree(BuildNodeId(0, 1), &nodes); + ASSERT_EQ(5u, nodes.size()); + EXPECT_EQ("node=0,1 parent=null view=null", nodes[0].ToString()); + EXPECT_EQ("node=1,1 parent=0,1 view=null", nodes[1].ToString()); + EXPECT_EQ("node=1,11 parent=1,1 view=1,51", nodes[2].ToString()); + EXPECT_EQ("node=2,2 parent=1,1 view=null", nodes[3].ToString()); + EXPECT_EQ("node=2,3 parent=1,1 view=null", nodes[4].ToString()); + } + + // Verifies GetNodeTree() on the node 1,1. + { + std::vector<TestNode> nodes; + connection2_->GetNodeTree(BuildNodeId(1, 1), &nodes); + ASSERT_EQ(4u, nodes.size()); + EXPECT_EQ("node=1,1 parent=null view=null", nodes[0].ToString()); + EXPECT_EQ("node=1,11 parent=1,1 view=1,51", nodes[1].ToString()); + EXPECT_EQ("node=2,2 parent=1,1 view=null", nodes[2].ToString()); + EXPECT_EQ("node=2,3 parent=1,1 view=null", nodes[3].ToString()); + } + + // Connection 2 shouldn't be able to get the root tree. + { + std::vector<TestNode> nodes; + connection2_->GetNodeTree(BuildNodeId(0, 1), &nodes); + ASSERT_EQ(0u, nodes.size()); + } +} + +TEST_F(ViewManagerTest, DISABLED_SetNodeBounds) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + ASSERT_TRUE(connection_->SetNodeBounds(BuildNodeId(1, 1), + gfx::Rect(0, 0, 100, 100))); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("BoundsChanged node=1,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100", + changes[0]); + + // Should not be possible to change the bounds of a node created by another + // connection. + ASSERT_FALSE(connection2_->SetNodeBounds(BuildNodeId(1, 1), + gfx::Rect(0, 0, 0, 0))); +} + +// Various assertions around SetRoots. +TEST_F(ViewManagerTest, DISABLED_SetRoots) { + // Create 1, 2, and 3 in the first connection. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 3))); + + // Parent 1 to the root. + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + + // Establish the second connection and give it the roots 1 and 3. + { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnectionWithRoots( + BuildNodeId(1, 1), BuildNodeId(1, 3))); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("OnConnectionEstablished creator=mojo:test_url", changes[0]); + EXPECT_EQ("[node=1,1 parent=null view=null]," + "[node=1,3 parent=null view=null]", + ChangeNodeDescription(connection2_->changes())); + } + + // Create 4 and add it to the root, connection 2 should only get id advanced. + { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 4))); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 4), 2)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 3", changes[0]); + } + + // Move 4 under 3, this should expose 4 to the client. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 3), BuildNodeId(1, 4), 3)); + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ( + "HierarchyChanged change_id=3 node=1,4 new_parent=1,3 " + "old_parent=null", changes[0]); + EXPECT_EQ("[node=1,4 parent=1,3 view=null]", + ChangeNodeDescription(connection2_->changes())); + } + + // Move 4 under 2, since 2 isn't a root client should get a delete. + { + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 2), BuildNodeId(1, 4), 4)); + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("NodeDeleted change_id=4 node=1,4", changes[0]); + } + + // Delete 4, client shouldn't receive a delete since it should no longer know + // about 4. + { + ASSERT_TRUE(connection_->DeleteNode(BuildNodeId(1, 4), 5)); + + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("ServerChangeIdAdvanced 6", changes[0]); + } +} + +// Verify AddNode fails when trying to manipulate nodes in other roots. +TEST_F(ViewManagerTest, DISABLED_CantMoveNodesFromOtherRoot) { + // Create 1 and 2 in the first connection. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Try to move 2 to be a child of 1 from connection 2. This should fail as 2 + // should not be able to access 1. + ASSERT_FALSE(connection2_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 1)); + + // Try to reparent 1 to the root. A connection is not allowed to reparent its + // roots. + ASSERT_FALSE(connection2_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); +} + +// Verify RemoveNodeFromParent fails for nodes that are descendants of the +// roots. +TEST_F(ViewManagerTest, DISABLED_CantRemoveNodesInOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 2), 2)); + + // Establish the second connection and give it the root 1. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Connection 2 should not be able to remove node 2 or 1 from its parent. + ASSERT_FALSE(connection2_->RemoveNodeFromParent(BuildNodeId(1, 2), 3)); + ASSERT_FALSE(connection2_->RemoveNodeFromParent(BuildNodeId(1, 1), 3)); + + // Create nodes 10 and 11 in 2. + ASSERT_TRUE(connection2_->CreateNode(BuildNodeId(2, 10))); + ASSERT_TRUE(connection2_->CreateNode(BuildNodeId(2, 11))); + + // Parent 11 to 10. + ASSERT_TRUE(connection2_->AddNode(BuildNodeId(2, 10), BuildNodeId(2, 11), 3)); + // Remove 11 from 10. + ASSERT_TRUE(connection2_->RemoveNodeFromParent( BuildNodeId(2, 11), 4)); + + // Verify nothing was actually removed. + { + std::vector<TestNode> nodes; + connection_->GetNodeTree(BuildNodeId(0, 1), &nodes); + ASSERT_EQ(3u, nodes.size()); + EXPECT_EQ("node=0,1 parent=null view=null", nodes[0].ToString()); + EXPECT_EQ("node=1,1 parent=0,1 view=null", nodes[1].ToString()); + EXPECT_EQ("node=1,2 parent=0,1 view=null", nodes[2].ToString()); + } +} + +// Verify SetView fails for nodes that are not descendants of the roots. +TEST_F(ViewManagerTest, DISABLED_CantRemoveSetViewInOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 2), 2)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Create a view in the second connection. + ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 51))); + + // Connection 2 should be able to set the view on node 1 (it's root), but not + // on 2. + ASSERT_TRUE(connection2_->SetView(BuildNodeId(1, 1), BuildViewId(2, 51))); + ASSERT_FALSE(connection2_->SetView(BuildNodeId(1, 2), BuildViewId(2, 51))); +} + +// Verify GetNodeTree fails for nodes that are not descendants of the roots. +TEST_F(ViewManagerTest, DISABLED_CantGetNodeTreeOfOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 1), 1)); + ASSERT_TRUE(connection_->AddNode(BuildNodeId(0, 1), BuildNodeId(1, 2), 2)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + std::vector<TestNode> nodes; + + // Should get nothing for the root. + connection2_->GetNodeTree(BuildNodeId(0, 1), &nodes); + ASSERT_TRUE(nodes.empty()); + + // Should get nothing for node 2. + connection2_->GetNodeTree(BuildNodeId(1, 2), &nodes); + ASSERT_TRUE(nodes.empty()); + + // Should get node 1 if asked for. + connection2_->GetNodeTree(BuildNodeId(1, 1), &nodes); + ASSERT_EQ(1u, nodes.size()); + EXPECT_EQ("node=1,1 parent=null view=null", nodes[0].ToString()); +} + +TEST_F(ViewManagerTest, DISABLED_ConnectTwice) { + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 2))); + + ASSERT_TRUE(connection_->AddNode(BuildNodeId(1, 1), BuildNodeId(1, 2), 1)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Try to connect again to 1,1, this should fail as already connected to that + // root. + { + std::vector<Id> node_ids; + node_ids.push_back(BuildNodeId(1, 1)); + ASSERT_FALSE(connection_->Embed(node_ids)); + } + + // Connecting to 1,2 should succeed and end up in connection2. + { + std::vector<Id> node_ids; + node_ids.push_back(BuildNodeId(1, 2)); + ASSERT_TRUE(connection_->Embed(node_ids)); + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("OnRootsAdded", changes[0]); + EXPECT_EQ("[node=1,2 parent=1,1 view=null]", + ChangeNodeDescription(connection2_->changes())); + } +} + +TEST_F(ViewManagerTest, DISABLED_OnViewInput) { + // Create node 1 and assign a view from connection 2 to it. + ASSERT_TRUE(connection_->CreateNode(BuildNodeId(1, 1))); + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 11))); + ASSERT_TRUE(connection2_->SetView(BuildNodeId(1, 1), BuildViewId(2, 11))); + + // Dispatch an event to the view and verify its received. + { + EventPtr event(Event::New()); + event->action = 1; + connection_->view_manager()->DispatchOnViewInputEvent( + BuildViewId(2, 11), + event.Pass()); + connection2_->DoRunLoopUntilChangesCount(1); + const Changes changes(ChangesToDescription1(connection2_->changes())); + ASSERT_EQ(1u, changes.size()); + EXPECT_EQ("InputEvent view=2,11 event_action=1", changes[0]); + } +} + +// TODO(sky): add coverage of test that destroys connections and ensures other +// connections get deletion notification (or advanced server id). + +// TODO(sky): need to better track changes to initial connection. For example, +// that SetBounsdNodes/AddNode and the like don't result in messages to the +// originating connection. + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/window_tree_host_impl.cc b/chromium/mojo/services/view_manager/window_tree_host_impl.cc new file mode 100644 index 00000000000..c344ba4bfe2 --- /dev/null +++ b/chromium/mojo/services/view_manager/window_tree_host_impl.cc @@ -0,0 +1,177 @@ +// Copyright (c) 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 "mojo/services/view_manager/window_tree_host_impl.h" + +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/services/public/cpp/geometry/geometry_type_converters.h" +#include "mojo/services/view_manager/context_factory_impl.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/compositor/compositor.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" + +namespace mojo { +namespace view_manager { +namespace service { + +// TODO(sky): nuke this. It shouldn't be static. +// static +ContextFactoryImpl* WindowTreeHostImpl::context_factory_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostImpl, public: + +WindowTreeHostImpl::WindowTreeHostImpl( + NativeViewportPtr viewport, + const gfx::Rect& bounds, + const base::Callback<void()>& compositor_created_callback) + : native_viewport_(viewport.Pass()), + compositor_created_callback_(compositor_created_callback), + bounds_(bounds) { + native_viewport_.set_client(this); + native_viewport_->Create(Rect::From(bounds)); + + MessagePipe pipe; + native_viewport_->CreateGLES2Context( + MakeRequest<CommandBuffer>(pipe.handle0.Pass())); + + // The ContextFactory must exist before any Compositors are created. + if (context_factory_) { + delete context_factory_; + context_factory_ = NULL; + } + context_factory_ = new ContextFactoryImpl(pipe.handle1.Pass()); + aura::Env::GetInstance()->set_context_factory(context_factory_); + CHECK(context_factory_) << "No GL bindings."; +} + +WindowTreeHostImpl::~WindowTreeHostImpl() { + DestroyCompositor(); + DestroyDispatcher(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostImpl, aura::WindowTreeHost implementation: + +ui::EventSource* WindowTreeHostImpl::GetEventSource() { + return this; +} + +gfx::AcceleratedWidget WindowTreeHostImpl::GetAcceleratedWidget() { + NOTIMPLEMENTED() << "GetAcceleratedWidget"; + return gfx::kNullAcceleratedWidget; +} + +void WindowTreeHostImpl::Show() { + window()->Show(); + native_viewport_->Show(); +} + +void WindowTreeHostImpl::Hide() { + native_viewport_->Hide(); + window()->Hide(); +} + +gfx::Rect WindowTreeHostImpl::GetBounds() const { + return bounds_; +} + +void WindowTreeHostImpl::SetBounds(const gfx::Rect& bounds) { + native_viewport_->SetBounds(Rect::From(bounds)); +} + +gfx::Point WindowTreeHostImpl::GetLocationOnNativeScreen() const { + return gfx::Point(0, 0); +} + +void WindowTreeHostImpl::SetCapture() { + NOTIMPLEMENTED(); +} + +void WindowTreeHostImpl::ReleaseCapture() { + NOTIMPLEMENTED(); +} + +void WindowTreeHostImpl::PostNativeEvent( + const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostImpl::OnDeviceScaleFactorChanged(float device_scale_factor) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostImpl::SetCursorNative(gfx::NativeCursor cursor) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostImpl::MoveCursorToNative(const gfx::Point& location) { + NOTIMPLEMENTED(); +} + +void WindowTreeHostImpl::OnCursorVisibilityChangedNative(bool show) { + NOTIMPLEMENTED(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostImpl, ui::EventSource implementation: + +ui::EventProcessor* WindowTreeHostImpl::GetEventProcessor() { + return dispatcher(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeHostImpl, NativeViewportClient implementation: + +void WindowTreeHostImpl::OnCreated() { + CreateCompositor(GetAcceleratedWidget()); + compositor_created_callback_.Run(); +} + +void WindowTreeHostImpl::OnBoundsChanged(RectPtr bounds) { + bounds_ = bounds.To<gfx::Rect>(); + OnHostResized(bounds_.size()); +} + +void WindowTreeHostImpl::OnDestroyed() { + base::MessageLoop::current()->Quit(); +} + +void WindowTreeHostImpl::OnEvent(EventPtr event, + const mojo::Callback<void()>& callback) { + switch (event->action) { + case ui::ET_MOUSE_PRESSED: + case ui::ET_MOUSE_DRAGGED: + case ui::ET_MOUSE_RELEASED: + case ui::ET_MOUSE_MOVED: + case ui::ET_MOUSE_ENTERED: + case ui::ET_MOUSE_EXITED: { + gfx::Point location(event->location->x, event->location->y); + ui::MouseEvent ev(static_cast<ui::EventType>(event->action), location, + location, event->flags, 0); + SendEventToProcessor(&ev); + break; + } + case ui::ET_KEY_PRESSED: + case ui::ET_KEY_RELEASED: { + ui::KeyEvent ev( + static_cast<ui::EventType>(event->action), + static_cast<ui::KeyboardCode>(event->key_data->key_code), + event->flags, event->key_data->is_char); + SendEventToProcessor(&ev); + break; + } + // TODO(beng): touch, etc. + } + callback.Run(); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo diff --git a/chromium/mojo/services/view_manager/window_tree_host_impl.h b/chromium/mojo/services/view_manager/window_tree_host_impl.h new file mode 100644 index 00000000000..6c11f9a9441 --- /dev/null +++ b/chromium/mojo/services/view_manager/window_tree_host_impl.h @@ -0,0 +1,76 @@ +// Copyright (c) 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 MOJO_AURA_WINDOW_TREE_HOST_MOJO_H_ +#define MOJO_AURA_WINDOW_TREE_HOST_MOJO_H_ + +#include "base/bind.h" +#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h" +#include "ui/aura/window_tree_host.h" +#include "ui/events/event_source.h" +#include "ui/gfx/rect.h" + +namespace ui { +class ContextFactory; +} + +namespace mojo { +namespace view_manager { +namespace service { + +class ContextFactoryImpl; + +class WindowTreeHostImpl : public aura::WindowTreeHost, + public ui::EventSource, + public NativeViewportClient { + public: + WindowTreeHostImpl(NativeViewportPtr viewport, + const gfx::Rect& bounds, + const base::Callback<void()>& compositor_created_callback); + virtual ~WindowTreeHostImpl(); + + gfx::Rect bounds() const { return bounds_; } + + private: + // WindowTreeHost: + virtual ui::EventSource* GetEventSource() OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual gfx::Rect GetBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void PostNativeEvent(const base::NativeEvent& native_event) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void SetCursorNative(gfx::NativeCursor cursor) OVERRIDE; + virtual void MoveCursorToNative(const gfx::Point& location) OVERRIDE; + virtual void OnCursorVisibilityChangedNative(bool show) OVERRIDE; + + // ui::EventSource: + virtual ui::EventProcessor* GetEventProcessor() OVERRIDE; + + // Overridden from NativeViewportClient: + virtual void OnCreated() OVERRIDE; + virtual void OnDestroyed() OVERRIDE; + virtual void OnBoundsChanged(RectPtr bounds) OVERRIDE; + virtual void OnEvent(EventPtr event, + const mojo::Callback<void()>& callback) OVERRIDE; + + static ContextFactoryImpl* context_factory_; + + NativeViewportPtr native_viewport_; + base::Callback<void()> compositor_created_callback_; + + gfx::Rect bounds_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeHostImpl); +}; + +} // namespace service +} // namespace view_manager +} // namespace mojo + +#endif // MOJO_AURA_WINDOW_TREE_HOST_MOJO_H_ diff --git a/chromium/mojo/shell/DEPS b/chromium/mojo/shell/DEPS new file mode 100644 index 00000000000..7b96bb6884b --- /dev/null +++ b/chromium/mojo/shell/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+dbus", + "+mojo/embedder", + "+net", + "+ui/gl", +] diff --git a/chromium/mojo/shell/android/DEPS b/chromium/mojo/shell/android/DEPS new file mode 100644 index 00000000000..c80012b5621 --- /dev/null +++ b/chromium/mojo/shell/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+jni", +] diff --git a/chromium/mojo/shell/android/apk/AndroidManifest.xml b/chromium/mojo/shell/android/apk/AndroidManifest.xml new file mode 100644 index 00000000000..325901817cc --- /dev/null +++ b/chromium/mojo/shell/android/apk/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.chromium.mojo_shell_apk"> + + <application android:name="MojoShellApplication" + android:label="Mojo Shell"> + <activity android:name="MojoShellActivity" + android:launchMode="singleTask" + android:theme="@android:style/Theme.Holo.Light.NoActionBar" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize" + android:hardwareAccelerated="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" /> + <uses-permission android:name="android.permission.INTERNET"/> +</manifest> diff --git a/chromium/mojo/shell/android/apk/res/layout/mojo_shell_activity.xml b/chromium/mojo/shell/android/apk/res/layout/mojo_shell_activity.xml new file mode 100644 index 00000000000..d319941501b --- /dev/null +++ b/chromium/mojo/shell/android/apk/res/layout/mojo_shell_activity.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> +</LinearLayout> diff --git a/chromium/mojo/shell/android/apk/res/values/strings.xml b/chromium/mojo/shell/android/apk/res/values/strings.xml new file mode 100644 index 00000000000..ff3f8bb8221 --- /dev/null +++ b/chromium/mojo/shell/android/apk/res/values/strings.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. + --> + +<resources> +</resources> diff --git a/chromium/mojo/shell/android/library_loader.cc b/chromium/mojo/shell/android/library_loader.cc new file mode 100644 index 00000000000..37d6592521f --- /dev/null +++ b/chromium/mojo/shell/android/library_loader.cc @@ -0,0 +1,46 @@ +// 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 "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/logging.h" +#include "mojo/services/native_viewport/native_viewport_android.h" +#include "mojo/shell/android/mojo_main.h" +#include "net/android/net_jni_registrar.h" + +namespace { + +base::android::RegistrationMethod kMojoRegisteredMethods[] = { + { "MojoMain", mojo::RegisterMojoMain }, + { "NativeViewportAndroid", mojo::services::NativeViewportAndroid::Register }, +}; + +bool RegisterMojoJni(JNIEnv* env) { + return RegisterNativeMethods(env, kMojoRegisteredMethods, + arraysize(kMojoRegisteredMethods)); +} + +} // namespace + +// This is called by the VM when the shared library is first loaded. +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + + if (!base::android::RegisterLibraryLoaderEntryHook(env)) + return -1; + + if (!base::android::RegisterJni(env)) + return -1; + + if (!net::android::RegisterJni(env)) + return -1; + + if (!RegisterMojoJni(env)) + return -1; + + return JNI_VERSION_1_4; +} diff --git a/chromium/mojo/shell/android/mojo_main.cc b/chromium/mojo/shell/android/mojo_main.cc new file mode 100644 index 00000000000..c2656321d19 --- /dev/null +++ b/chromium/mojo/shell/android/mojo_main.cc @@ -0,0 +1,80 @@ +// 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 "mojo/shell/android/mojo_main.h" + +#include "base/android/jni_string.h" +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "jni/MojoMain_jni.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/service_manager/service_manager.h" +#include "mojo/shell/context.h" +#include "mojo/shell/init.h" +#include "mojo/shell/run.h" +#include "ui/gl/gl_surface_egl.h" + +using base::LazyInstance; + +namespace mojo { + +namespace { + +LazyInstance<scoped_ptr<base::MessageLoop> > g_java_message_loop = + LAZY_INSTANCE_INITIALIZER; + +LazyInstance<scoped_ptr<shell::Context> > g_context = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +static void Init(JNIEnv* env, jclass clazz, jobject context) { + base::android::ScopedJavaLocalRef<jobject> scoped_context(env, context); + + base::android::InitApplicationContext(env, scoped_context); + + base::CommandLine::Init(0, 0); + mojo::shell::InitializeLogging(); + + g_java_message_loop.Get().reset(new base::MessageLoopForUI); + base::MessageLoopForUI::current()->Start(); + + // TODO(abarth): At which point should we switch to cross-platform + // initialization? + + gfx::GLSurface::InitializeOneOff(); +} + +static void Start(JNIEnv* env, jclass clazz, jobject context, jstring jurl) { + std::vector<GURL> app_urls; +#if defined(MOJO_SHELL_DEBUG_URL) + app_urls.push_back(GURL(MOJO_SHELL_DEBUG_URL)); + // Sleep for 5 seconds to give the debugger a chance to attach. + sleep(5); +#else + if (jurl) + app_urls.push_back(GURL(base::android::ConvertJavaStringToUTF8(env, jurl))); +#endif + + base::android::ScopedJavaGlobalRef<jobject> activity; + activity.Reset(env, context); + + shell::Context* shell_context = new shell::Context(); + shell_context->set_activity(activity.obj()); + + g_context.Get().reset(shell_context); + shell::Run(shell_context, app_urls); +} + +bool RegisterMojoMain(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace mojo diff --git a/chromium/mojo/shell/android/mojo_main.h b/chromium/mojo/shell/android/mojo_main.h new file mode 100644 index 00000000000..161ca71d684 --- /dev/null +++ b/chromium/mojo/shell/android/mojo_main.h @@ -0,0 +1,16 @@ +// 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 MOJO_SHELL_ANDROID_MOJO_MAIN_H_ +#define MOJO_SHELL_ANDROID_MOJO_MAIN_H_ + +#include <jni.h> + +namespace mojo { + +bool RegisterMojoMain(JNIEnv* env); + +} // namespace mojo + +#endif // MOJO_SHELL_ANDROID_MOJO_MAIN_H_ diff --git a/chromium/mojo/shell/app_child_process.cc b/chromium/mojo/shell/app_child_process.cc new file mode 100644 index 00000000000..8cc4d81e69f --- /dev/null +++ b/chromium/mojo/shell/app_child_process.cc @@ -0,0 +1,289 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/app_child_process.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/scoped_native_library.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/embedder/embedder.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/shell/app_child_process.mojom.h" + +namespace mojo { +namespace shell { + +namespace { + +// Blocker --------------------------------------------------------------------- + +// Blocks a thread until another thread unblocks it, at which point it unblocks +// and runs a closure provided by that thread. +class Blocker { + public: + class Unblocker { + public: + ~Unblocker() {} + + void Unblock(base::Closure run_after) { + DCHECK(blocker_); + DCHECK(blocker_->run_after_.is_null()); + blocker_->run_after_ = run_after; + blocker_->event_.Signal(); + blocker_ = NULL; + } + + private: + friend class Blocker; + Unblocker(Blocker* blocker) : blocker_(blocker) { + DCHECK(blocker_); + } + + Blocker* blocker_; + + // Copy and assign allowed. + }; + + Blocker() : event_(true, false) {} + ~Blocker() {} + + void Block() { + DCHECK(run_after_.is_null()); + event_.Wait(); + run_after_.Run(); + } + + Unblocker GetUnblocker() { + return Unblocker(this); + } + + private: + base::WaitableEvent event_; + base::Closure run_after_; + + DISALLOW_COPY_AND_ASSIGN(Blocker); +}; + +// AppContext ------------------------------------------------------------------ + +class AppChildControllerImpl; + +static void DestroyController(scoped_ptr<AppChildControllerImpl> controller) { +} + +// Should be created and initialized on the main thread. +class AppContext { + public: + AppContext() + : io_thread_("io_thread"), + controller_thread_("controller_thread") {} + ~AppContext() {} + + void Init() { + // Initialize Mojo before starting any threads. + embedder::Init(); + + // Create and start our I/O thread. + base::Thread::Options io_thread_options(base::MessageLoop::TYPE_IO, 0); + CHECK(io_thread_.StartWithOptions(io_thread_options)); + io_runner_ = io_thread_.message_loop_proxy().get(); + CHECK(io_runner_); + + // Create and start our controller thread. + base::Thread::Options controller_thread_options; + controller_thread_options.message_loop_type = + base::MessageLoop::TYPE_CUSTOM; + controller_thread_options.message_pump_factory = + base::Bind(&common::MessagePumpMojo::Create); + CHECK(controller_thread_.StartWithOptions(controller_thread_options)); + controller_runner_ = controller_thread_.message_loop_proxy().get(); + CHECK(controller_runner_); + } + + void Shutdown() { + controller_runner_->PostTask( + FROM_HERE, + base::Bind(&DestroyController, base::Passed(&controller_))); + } + + base::SingleThreadTaskRunner* io_runner() const { + return io_runner_.get(); + } + + base::SingleThreadTaskRunner* controller_runner() const { + return controller_runner_.get(); + } + + AppChildControllerImpl* controller() const { + return controller_.get(); + } + + void set_controller(scoped_ptr<AppChildControllerImpl> controller) { + controller_ = controller.Pass(); + } + + private: + // Accessed only on the controller thread. + // IMPORTANT: This must be BEFORE |controller_thread_|, so that the controller + // thread gets joined (and thus |controller_| reset) before |controller_| is + // destroyed. + scoped_ptr<AppChildControllerImpl> controller_; + + base::Thread io_thread_; + scoped_refptr<base::SingleThreadTaskRunner> io_runner_; + + base::Thread controller_thread_; + scoped_refptr<base::SingleThreadTaskRunner> controller_runner_; + + DISALLOW_COPY_AND_ASSIGN(AppContext); +}; + +// AppChildControllerImpl ------------------------------------------------------ + +class AppChildControllerImpl : public InterfaceImpl<AppChildController> { + public: + virtual ~AppChildControllerImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // TODO(vtl): Pass in the result from |MainMain()|. + client()->AppCompleted(MOJO_RESULT_UNIMPLEMENTED); + } + + // To be executed on the controller thread. Creates the |AppChildController|, + // etc. + static void Init( + AppContext* app_context, + embedder::ScopedPlatformHandle platform_channel, + const Blocker::Unblocker& unblocker) { + DCHECK(app_context); + DCHECK(platform_channel.is_valid()); + + DCHECK(!app_context->controller()); + + scoped_ptr<AppChildControllerImpl> impl( + new AppChildControllerImpl(app_context, unblocker)); + + ScopedMessagePipeHandle host_message_pipe(embedder::CreateChannel( + platform_channel.Pass(), + app_context->io_runner(), + base::Bind(&AppChildControllerImpl::DidCreateChannel, + base::Unretained(impl.get())), + base::MessageLoopProxy::current())); + + BindToPipe(impl.get(), host_message_pipe.Pass()); + + app_context->set_controller(impl.Pass()); + } + + virtual void OnConnectionError() OVERRIDE { + // TODO(darin): How should we handle a connection error here? + } + + // |AppChildController| methods: + virtual void StartApp(const String& app_path, + ScopedMessagePipeHandle service) OVERRIDE { + DVLOG(2) << "AppChildControllerImpl::StartApp(" << app_path << ", ...)"; + DCHECK(thread_checker_.CalledOnValidThread()); + + unblocker_.Unblock(base::Bind(&AppChildControllerImpl::StartAppOnMainThread, + base::FilePath::FromUTF8Unsafe(app_path), + base::Passed(&service))); + } + + private: + AppChildControllerImpl(AppContext* app_context, + const Blocker::Unblocker& unblocker) + : app_context_(app_context), + unblocker_(unblocker), + channel_info_(NULL) { + } + + // Callback for |embedder::CreateChannel()|. + void DidCreateChannel(embedder::ChannelInfo* channel_info) { + DVLOG(2) << "AppChildControllerImpl::DidCreateChannel()"; + DCHECK(thread_checker_.CalledOnValidThread()); + channel_info_ = channel_info; + } + + static void StartAppOnMainThread(const base::FilePath& app_path, + ScopedMessagePipeHandle service) { + // TODO(vtl): This is copied from in_process_dynamic_service_runner.cc. + DVLOG(2) << "Loading/running Mojo app from " << app_path.value() + << " out of process"; + + do { + base::NativeLibraryLoadError load_error; + base::ScopedNativeLibrary app_library( + base::LoadNativeLibrary(app_path, &load_error)); + if (!app_library.is_valid()) { + LOG(ERROR) << "Failed to load library (error: " << load_error.ToString() + << ")"; + break; + } + + typedef MojoResult (*MojoMainFunction)(MojoHandle); + MojoMainFunction main_function = reinterpret_cast<MojoMainFunction>( + app_library.GetFunctionPointer("MojoMain")); + if (!main_function) { + LOG(ERROR) << "Entrypoint MojoMain not found"; + break; + } + + // TODO(vtl): Report the result back to our parent process. + // |MojoMain()| takes ownership of the service handle. + MojoResult result = main_function(service.release().value()); + if (result < MOJO_RESULT_OK) + LOG(ERROR) << "MojoMain returned an error: " << result; + } while (false); + } + + base::ThreadChecker thread_checker_; + AppContext* const app_context_; + Blocker::Unblocker unblocker_; + + embedder::ChannelInfo* channel_info_; + + DISALLOW_COPY_AND_ASSIGN(AppChildControllerImpl); +}; + +} // namespace + +// AppChildProcess ------------------------------------------------------------- + +AppChildProcess::AppChildProcess() { +} + +AppChildProcess::~AppChildProcess() { +} + +void AppChildProcess::Main() { + DVLOG(2) << "AppChildProcess::Main()"; + + AppContext app_context; + app_context.Init(); + + Blocker blocker; + app_context.controller_runner()->PostTask( + FROM_HERE, + base::Bind(&AppChildControllerImpl::Init, base::Unretained(&app_context), + base::Passed(platform_channel()), blocker.GetUnblocker())); + // This will block, then run whatever the controller wants. + blocker.Block(); + + app_context.Shutdown(); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/app_child_process.h b/chromium/mojo/shell/app_child_process.h new file mode 100644 index 00000000000..b5b1eb6660e --- /dev/null +++ b/chromium/mojo/shell/app_child_process.h @@ -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. + +#ifndef MOJO_SHELL_APP_CHILD_PROCESS_H_ +#define MOJO_SHELL_APP_CHILD_PROCESS_H_ + +#include "base/macros.h" +#include "mojo/shell/child_process.h" + +namespace mojo { +namespace shell { + +// An implementation of |ChildProcess| for a |TYPE_APP| child process, which +// runs a single app (loaded from the file system) on its main thread. +class AppChildProcess : public ChildProcess { + public: + AppChildProcess(); + virtual ~AppChildProcess(); + + virtual void Main() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(AppChildProcess); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_APP_CHILD_PROCESS_H_ diff --git a/chromium/mojo/shell/app_child_process.mojom b/chromium/mojo/shell/app_child_process.mojom new file mode 100644 index 00000000000..5b0f180c496 --- /dev/null +++ b/chromium/mojo/shell/app_child_process.mojom @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.shell { + +[Client=AppChildControllerClient] +interface AppChildController { + // TODO(vtl): |service| should be of a more specific type. + StartApp(string app_path, handle<message_pipe> service); +}; + +interface AppChildControllerClient { + AppCompleted(int32 result); +}; + +} // module mojo.shell diff --git a/chromium/mojo/shell/app_child_process_host.cc b/chromium/mojo/shell/app_child_process_host.cc new file mode 100644 index 00000000000..0740608789a --- /dev/null +++ b/chromium/mojo/shell/app_child_process_host.cc @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/app_child_process_host.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "mojo/embedder/embedder.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/shell/context.h" +#include "mojo/shell/task_runners.h" + +namespace mojo { +namespace shell { + +AppChildProcessHost::AppChildProcessHost( + Context* context, + AppChildControllerClient* controller_client) + : ChildProcessHost(context, this, ChildProcess::TYPE_APP), + controller_client_(controller_client), + channel_info_(NULL) { +} + +AppChildProcessHost::~AppChildProcessHost() { +} + +void AppChildProcessHost::WillStart() { + DCHECK(platform_channel()->is_valid()); + + mojo::ScopedMessagePipeHandle handle(embedder::CreateChannel( + platform_channel()->Pass(), + context()->task_runners()->io_runner(), + base::Bind(&AppChildProcessHost::DidCreateChannel, + base::Unretained(this)), + base::MessageLoop::current()->message_loop_proxy())); + + controller_.Bind(handle.Pass()); + controller_.set_client(controller_client_); +} + +void AppChildProcessHost::DidStart(bool success) { + DVLOG(2) << "AppChildProcessHost::DidStart()"; + + if (!success) { + LOG(ERROR) << "Failed to start app child process"; + controller_client_->AppCompleted(MOJO_RESULT_UNKNOWN); + return; + } +} + +// Callback for |embedder::CreateChannel()|. +void AppChildProcessHost::DidCreateChannel( + embedder::ChannelInfo* channel_info) { + DVLOG(2) << "AppChildProcessHost::DidCreateChannel()"; + + CHECK(channel_info); + channel_info_ = channel_info; +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/app_child_process_host.h b/chromium/mojo/shell/app_child_process_host.h new file mode 100644 index 00000000000..30e6b798a24 --- /dev/null +++ b/chromium/mojo/shell/app_child_process_host.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_APP_CHILD_PROCESS_HOST_H_ +#define MOJO_SHELL_APP_CHILD_PROCESS_HOST_H_ + +#include "base/macros.h" +#include "mojo/shell/app_child_process.mojom.h" +#include "mojo/shell/child_process_host.h" + +namespace mojo { + +namespace embedder { +struct ChannelInfo; +} + +namespace shell { + +// A subclass of |ChildProcessHost| to host a |TYPE_APP| child process, which +// runs a single app (loaded from the file system). +// +// Note: After |Start()|, this object must remain alive until the controller +// client's |AppCompleted()| is called. +class AppChildProcessHost : public ChildProcessHost, + public ChildProcessHost::Delegate { + public: + AppChildProcessHost(Context* context, + AppChildControllerClient* controller_client); + virtual ~AppChildProcessHost(); + + AppChildController* controller() { + return controller_.get(); + } + + private: + // |ChildProcessHost::Delegate| methods: + virtual void WillStart() OVERRIDE; + virtual void DidStart(bool success) OVERRIDE; + + // Callback for |embedder::CreateChannel()|. + void DidCreateChannel(embedder::ChannelInfo* channel_info); + + AppChildControllerClient* const controller_client_; + + AppChildControllerPtr controller_; + embedder::ChannelInfo* channel_info_; + + DISALLOW_COPY_AND_ASSIGN(AppChildProcessHost); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_APP_CHILD_PROCESS_HOST_H_ diff --git a/chromium/mojo/shell/child_process.cc b/chromium/mojo/shell/child_process.cc new file mode 100644 index 00000000000..24728af482e --- /dev/null +++ b/chromium/mojo/shell/child_process.cc @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/child_process.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/shell/app_child_process.h" +#include "mojo/shell/switches.h" +#include "mojo/shell/test_child_process.h" + +namespace mojo { +namespace shell { + +ChildProcess::~ChildProcess() { +} + +// static +scoped_ptr<ChildProcess> ChildProcess::Create( + const base::CommandLine& command_line) { + if (!command_line.HasSwitch(switches::kChildProcessType)) + return scoped_ptr<ChildProcess>(); + + int type_as_int; + CHECK(base::StringToInt(command_line.GetSwitchValueASCII( + switches::kChildProcessType), &type_as_int)); + + scoped_ptr<ChildProcess> rv; + switch (type_as_int) { + case TYPE_TEST: + rv.reset(new TestChildProcess()); + break; + case TYPE_APP: + rv.reset(new AppChildProcess()); + break; + default: + CHECK(false) << "Invalid child process type"; + break; + } + + if (rv) { + rv->platform_channel_ = + embedder::PlatformChannelPair::PassClientHandleFromParentProcess( + command_line); + CHECK(rv->platform_channel_.is_valid()); + } + + return rv.Pass(); +} + +ChildProcess::ChildProcess() { +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/child_process.h b/chromium/mojo/shell/child_process.h new file mode 100644 index 00000000000..d794a4ea948 --- /dev/null +++ b/chromium/mojo/shell/child_process.h @@ -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. + +#ifndef MOJO_SHELL_CHILD_PROCESS_H_ +#define MOJO_SHELL_CHILD_PROCESS_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/embedder/scoped_platform_handle.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace shell { + +// A base class for child processes -- i.e., code that is actually run within +// the child process. (Instances are manufactured by |Create()|.) +class ChildProcess { + public: + enum Type { + TYPE_TEST, + // Hosts a single app (see app_child_process(_host).*). + TYPE_APP + }; + + virtual ~ChildProcess(); + + // Returns null if the command line doesn't indicate that this is a child + // process. |main()| should call this, and if it returns non-null it should + // call |Main()| (without a message loop on the current thread). + static scoped_ptr<ChildProcess> Create(const base::CommandLine& command_line); + + // To be implemented by subclasses. This is the "entrypoint" for a child + // process. Run with no message loop for the main thread. + virtual void Main() = 0; + + protected: + ChildProcess(); + + embedder::ScopedPlatformHandle* platform_channel() { + return &platform_channel_; + } + + private: + // Available in |Main()| (after a successful |Create()|). + embedder::ScopedPlatformHandle platform_channel_; + + DISALLOW_COPY_AND_ASSIGN(ChildProcess); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_CHILD_PROCESS_H_ diff --git a/chromium/mojo/shell/child_process_host.cc b/chromium/mojo/shell/child_process_host.cc new file mode 100644 index 00000000000..e743522e5ff --- /dev/null +++ b/chromium/mojo/shell/child_process_host.cc @@ -0,0 +1,105 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/child_process_host.h" + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner.h" +#include "base/task_runner_util.h" +#include "mojo/shell/context.h" +#include "mojo/shell/switches.h" + +namespace mojo { +namespace shell { + +ChildProcessHost::ChildProcessHost(Context* context, + Delegate* delegate, + ChildProcess::Type type) + : context_(context), + delegate_(delegate), + type_(type), + child_process_handle_(base::kNullProcessHandle) { + DCHECK(delegate); + platform_channel_ = platform_channel_pair_.PassServerHandle(); + CHECK(platform_channel_.is_valid()); +} + +ChildProcessHost::~ChildProcessHost() { + if (child_process_handle_ != base::kNullProcessHandle) { + LOG(WARNING) << "Destroying ChildProcessHost with unjoined child"; + base::CloseProcessHandle(child_process_handle_); + child_process_handle_ = base::kNullProcessHandle; + } +} + +void ChildProcessHost::Start() { + DCHECK_EQ(child_process_handle_, base::kNullProcessHandle); + + delegate_->WillStart(); + + CHECK(base::PostTaskAndReplyWithResult( + context_->task_runners()->blocking_pool(), + FROM_HERE, + base::Bind(&ChildProcessHost::DoLaunch, base::Unretained(this)), + base::Bind(&ChildProcessHost::DidLaunch, base::Unretained(this)))); +} + +int ChildProcessHost::Join() { + DCHECK_NE(child_process_handle_, base::kNullProcessHandle); + int rv = -1; + LOG_IF(ERROR, !base::WaitForExitCode(child_process_handle_, &rv)) + << "Failed to wait for child process"; + base::CloseProcessHandle(child_process_handle_); + child_process_handle_ = base::kNullProcessHandle; + return rv; +} + +bool ChildProcessHost::DoLaunch() { + static const char* kForwardSwitches[] = { + switches::kTraceToConsole, + switches::kV, + switches::kVModule, + }; + + const base::CommandLine* parent_command_line = + base::CommandLine::ForCurrentProcess(); + base::CommandLine child_command_line(parent_command_line->GetProgram()); + child_command_line.CopySwitchesFrom(*parent_command_line, kForwardSwitches, + arraysize(kForwardSwitches)); + child_command_line.AppendSwitchASCII( + switches::kChildProcessType, base::IntToString(static_cast<int>(type_))); + + embedder::HandlePassingInformation handle_passing_info; + platform_channel_pair_.PrepareToPassClientHandleToChildProcess( + &child_command_line, &handle_passing_info); + + base::LaunchOptions options; +#if defined(OS_WIN) + options.start_hidden = true; + options.handles_to_inherit = &handle_passing_info; +#elif defined(OS_POSIX) + options.fds_to_remap = &handle_passing_info; +#endif + + if (!base::LaunchProcess(child_command_line, options, &child_process_handle_)) + return false; + + platform_channel_pair_.ChildProcessLaunched(); + return true; +} + +void ChildProcessHost::DidLaunch(bool success) { + delegate_->DidStart(success); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/child_process_host.h b/chromium/mojo/shell/child_process_host.h new file mode 100644 index 00000000000..d3c7669a1b4 --- /dev/null +++ b/chromium/mojo/shell/child_process_host.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_CHILD_PROCESS_HOST_H_ +#define MOJO_SHELL_CHILD_PROCESS_HOST_H_ + +#include "base/macros.h" +#include "base/process/process_handle.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/shell/child_process.h" // For |ChildProcess::Type|. + +namespace mojo { +namespace shell { + +class Context; + +// (Base) class for a "child process host". Handles launching and connecting a +// platform-specific "pipe" to the child, and supports joining the child +// process. Intended for use as a base class, but may be used on its own in +// simple cases. +// +// This class is not thread-safe. It should be created/used/destroyed on a +// single thread. +// +// Note: Does not currently work on Windows before Vista. +class ChildProcessHost { + public: + class Delegate { + public: + virtual void WillStart() = 0; + virtual void DidStart(bool success) = 0; + }; + + ChildProcessHost(Context* context, + Delegate* delegate, + ChildProcess::Type type); + virtual ~ChildProcessHost(); + + // |Start()|s the child process; calls the delegate's |DidStart()| (on the + // thread on which |Start()| was called) when the child has been started (or + // failed to start). After calling |Start()|, this object must not be + // destroyed until |DidStart()| has been called. + // TODO(vtl): Consider using weak pointers and removing this requirement. + void Start(); + + // Waits for the child process to terminate, and returns its exit code. + // Note: If |Start()| has been called, this must not be called until the + // callback has been called. + int Join(); + + embedder::ScopedPlatformHandle* platform_channel() { + return &platform_channel_; + } + + protected: + Context* context() const { + return context_; + } + + private: + bool DoLaunch(); + void DidLaunch(bool success); + + Context* const context_; + Delegate* const delegate_; + const ChildProcess::Type type_; + + base::ProcessHandle child_process_handle_; + + embedder::PlatformChannelPair platform_channel_pair_; + + // Platform-specific "pipe" to the child process. Valid immediately after + // creation. + embedder::ScopedPlatformHandle platform_channel_; + + DISALLOW_COPY_AND_ASSIGN(ChildProcessHost); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_CHILD_PROCESS_HOST_H_ diff --git a/chromium/mojo/shell/child_process_host_unittest.cc b/chromium/mojo/shell/child_process_host_unittest.cc new file mode 100644 index 00000000000..37f06c3a174 --- /dev/null +++ b/chromium/mojo/shell/child_process_host_unittest.cc @@ -0,0 +1,56 @@ +// 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. + +// Note: This file also tests child_process.*. + +#include "mojo/shell/child_process_host.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/shell/context.h" +#include "mojo/shell/shell_test_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace test { +namespace { + +class TestChildProcessHostDelegate : public ChildProcessHost::Delegate { + public: + TestChildProcessHostDelegate() {} + virtual ~TestChildProcessHostDelegate() {} + virtual void WillStart() OVERRIDE { + VLOG(2) << "TestChildProcessHostDelegate::WillStart()"; + } + virtual void DidStart(bool success) OVERRIDE { + VLOG(2) << "TestChildProcessHostDelegate::DidStart(" << success << ")"; + base::MessageLoop::current()->QuitWhenIdle(); + } +}; + +typedef ShellTestBase ChildProcessHostTest; + +TEST_F(ChildProcessHostTest, Basic) { + base::MessageLoop message_loop( + scoped_ptr<base::MessagePump>(new common::MessagePumpMojo())); + + Context context; + TestChildProcessHostDelegate child_process_host_delegate; + ChildProcessHost child_process_host(&context, + &child_process_host_delegate, + ChildProcess::TYPE_TEST); + child_process_host.Start(); + message_loop.Run(); + int exit_code = child_process_host.Join(); + VLOG(2) << "Joined child: exit_code = " << exit_code; + EXPECT_EQ(0, exit_code); +} + +} // namespace +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/context.cc b/chromium/mojo/shell/context.cc new file mode 100644 index 00000000000..b47967e59c7 --- /dev/null +++ b/chromium/mojo/shell/context.cc @@ -0,0 +1,141 @@ +// 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 "mojo/shell/context.h" + +#include "build/build_config.h" +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_vector.h" +#include "mojo/embedder/embedder.h" +#include "mojo/gles2/gles2_support_impl.h" +#include "mojo/public/cpp/application/application.h" +#include "mojo/service_manager/background_service_loader.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/service_manager/service_manager.h" +#include "mojo/services/native_viewport/native_viewport_service.h" +#include "mojo/shell/dynamic_service_loader.h" +#include "mojo/shell/in_process_dynamic_service_runner.h" +#include "mojo/shell/out_of_process_dynamic_service_runner.h" +#include "mojo/shell/switches.h" +#include "mojo/spy/spy.h" + +#if defined(OS_LINUX) +#include "mojo/shell/dbus_service_loader_linux.h" +#endif // defined(OS_LINUX) + +#if defined(USE_AURA) +#include "mojo/shell/view_manager_loader.h" +#endif + +namespace mojo { +namespace shell { +namespace { + +// These mojo: URLs are loaded directly from the local filesystem. They +// correspond to shared libraries bundled alongside the mojo_shell. +const char* kLocalMojoURLs[] = { + "mojo:mojo_network_service", +}; + +// Used to ensure we only init once. +class Setup { + public: + Setup() { + embedder::Init(); + gles2::GLES2SupportImpl::Init(); + } + + ~Setup() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(Setup); +}; + +static base::LazyInstance<Setup>::Leaky setup = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +class Context::NativeViewportServiceLoader : public ServiceLoader { + public: + explicit NativeViewportServiceLoader(Context* context) : context_(context) {} + virtual ~NativeViewportServiceLoader() {} + + private: + virtual void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) OVERRIDE { + app_.reset(::CreateNativeViewportService(context_, service_handle.Pass())); + } + + virtual void OnServiceError(ServiceManager* manager, + const GURL& url) OVERRIDE { + } + + Context* context_; + scoped_ptr<Application> app_; + DISALLOW_COPY_AND_ASSIGN(NativeViewportServiceLoader); +}; + +Context::Context() + : task_runners_(base::MessageLoop::current()->message_loop_proxy()) { + setup.Get(); + + for (size_t i = 0; i < arraysize(kLocalMojoURLs); ++i) + mojo_url_resolver_.AddLocalFileMapping(GURL(kLocalMojoURLs[i])); + + base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); + scoped_ptr<DynamicServiceRunnerFactory> runner_factory; + if (cmdline->HasSwitch(switches::kEnableMultiprocess)) + runner_factory.reset(new OutOfProcessDynamicServiceRunnerFactory()); + else + runner_factory.reset(new InProcessDynamicServiceRunnerFactory()); + + service_manager_.set_default_loader( + scoped_ptr<ServiceLoader>( + new DynamicServiceLoader(this, runner_factory.Pass()))); + // The native viewport service synchronously waits for certain messages. If we + // don't run it on its own thread we can easily deadlock. Long term native + // viewport should run its own process so that this isn't an issue. + service_manager_.SetLoaderForURL( + scoped_ptr<ServiceLoader>( + new BackgroundServiceLoader( + scoped_ptr<ServiceLoader>(new NativeViewportServiceLoader(this)), + "native_viewport", + base::MessageLoop::TYPE_UI)), + GURL("mojo:mojo_native_viewport_service")); +#if defined(USE_AURA) + // TODO(sky): need a better way to find this. It shouldn't be linked in. + service_manager_.SetLoaderForURL( + scoped_ptr<ServiceLoader>(new ViewManagerLoader()), + GURL("mojo:mojo_view_manager")); +#endif + +#if defined(OS_LINUX) + service_manager_.SetLoaderForScheme( + scoped_ptr<ServiceLoader>(new DBusServiceLoader(this)), + "dbus"); +#endif // defined(OS_LINUX) + + if (cmdline->HasSwitch(switches::kSpy)) { + spy_.reset(new mojo::Spy(&service_manager_, + cmdline->GetSwitchValueASCII(switches::kSpy))); + } +} + +Context::~Context() { + // mojo_view_manager uses native_viewport. Destroy mojo_view_manager first so + // that there aren't shutdown ordering issues. Once native viewport service is + // moved into its own process this can likely be nuked. +#if defined(USE_AURA) + service_manager_.SetLoaderForURL( + scoped_ptr<ServiceLoader>(), + GURL("mojo:mojo_view_manager")); +#endif + service_manager_.set_default_loader(scoped_ptr<ServiceLoader>()); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/context.h b/chromium/mojo/shell/context.h new file mode 100644 index 00000000000..c643cd93317 --- /dev/null +++ b/chromium/mojo/shell/context.h @@ -0,0 +1,62 @@ +// 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 MOJO_SHELL_CONTEXT_H_ +#define MOJO_SHELL_CONTEXT_H_ + +#include <string> + +#include "mojo/service_manager/service_manager.h" +#include "mojo/shell/keep_alive.h" +#include "mojo/shell/mojo_url_resolver.h" +#include "mojo/shell/task_runners.h" + +#if defined(OS_ANDROID) +#include "base/android/scoped_java_ref.h" +#endif // defined(OS_ANDROID) + +namespace mojo { + +class Spy; + +namespace shell { + +class DynamicServiceLoader; + +// The "global" context for the shell's main process. +class Context { + public: + Context(); + ~Context(); + + TaskRunners* task_runners() { return &task_runners_; } + ServiceManager* service_manager() { return &service_manager_; } + KeepAliveCounter* keep_alive_counter() { return &keep_alive_counter_; } + MojoURLResolver* mojo_url_resolver() { return &mojo_url_resolver_; } + +#if defined(OS_ANDROID) + jobject activity() const { return activity_.obj(); } + void set_activity(jobject activity) { activity_.Reset(NULL, activity); } +#endif // defined(OS_ANDROID) + + private: + class NativeViewportServiceLoader; + + TaskRunners task_runners_; + ServiceManager service_manager_; + MojoURLResolver mojo_url_resolver_; + scoped_ptr<Spy> spy_; +#if defined(OS_ANDROID) + base::android::ScopedJavaGlobalRef<jobject> activity_; +#endif // defined(OS_ANDROID) + + KeepAliveCounter keep_alive_counter_; + + DISALLOW_COPY_AND_ASSIGN(Context); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_CONTEXT_H_ diff --git a/chromium/mojo/shell/dbus_service_loader_linux.cc b/chromium/mojo/shell/dbus_service_loader_linux.cc new file mode 100644 index 00000000000..bd4255c2fab --- /dev/null +++ b/chromium/mojo/shell/dbus_service_loader_linux.cc @@ -0,0 +1,181 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/dbus_service_loader_linux.h" + +#include <string> + +#include "base/command_line.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/task_runner_util.h" +#include "base/threading/thread_restrictions.h" +#include "dbus/bus.h" +#include "dbus/file_descriptor.h" +#include "dbus/message.h" +#include "dbus/object_path.h" +#include "dbus/object_proxy.h" +#include "mojo/dbus/dbus_external_service.h" +#include "mojo/embedder/channel_init.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/shell/context.h" +#include "mojo/shell/external_service.mojom.h" +#include "mojo/shell/keep_alive.h" + +namespace mojo { +namespace shell { + +// Manages the connection to a single externally-running service. +class DBusServiceLoader::LoadContext { + public: + // Kicks off the attempt to bootstrap a connection to the externally-running + // service specified by url_. + // Creates a MessagePipe and passes one end over DBus to the service. Then, + // calls ExternalService::Activate(ShellHandle) over the now-shared pipe. + LoadContext(DBusServiceLoader* loader, + const scoped_refptr<dbus::Bus>& bus, + const GURL& url, + ScopedMessagePipeHandle service_provider_handle) + : loader_(loader), + bus_(bus), + service_dbus_proxy_(NULL), + url_(url), + service_provider_handle_(service_provider_handle.Pass()), + keep_alive_(loader->context_) { + base::PostTaskAndReplyWithResult( + loader_->context_->task_runners()->io_runner(), + FROM_HERE, + base::Bind(&LoadContext::CreateChannelOnIOThread, + base::Unretained(this)), + base::Bind(&LoadContext::ConnectChannel, base::Unretained(this))); + } + + virtual ~LoadContext() { + } + + private: + // Sets up a pipe to share with the externally-running service and returns + // the endpoint that should be sent over DBus. + // The FD for the endpoint must be validated on an IO thread. + scoped_ptr<dbus::FileDescriptor> CreateChannelOnIOThread() { + base::ThreadRestrictions::AssertIOAllowed(); + CHECK(bus_->Connect()); + CHECK(bus_->SetUpAsyncOperations()); + + embedder::PlatformChannelPair channel_pair; + channel_init_.reset(new embedder::ChannelInit); + mojo::ScopedMessagePipeHandle bootstrap_message_pipe = + channel_init_->Init(channel_pair.PassServerHandle().release().fd, + loader_->context_->task_runners()->io_runner()); + CHECK(bootstrap_message_pipe.is_valid()); + + external_service_.Bind(bootstrap_message_pipe.Pass()); + + scoped_ptr<dbus::FileDescriptor> client_fd(new dbus::FileDescriptor); + client_fd->PutValue(channel_pair.PassClientHandle().release().fd); + client_fd->CheckValidity(); // Must be run on an IO thread. + return client_fd.Pass(); + } + + // Sends client_fd over to the externally-running service. If that + // attempt is successful, the service will then be "activated" by + // sending it a ShellHandle. + void ConnectChannel(scoped_ptr<dbus::FileDescriptor> client_fd) { + size_t first_slash = url_.path().find_first_of('/'); + DCHECK_NE(first_slash, std::string::npos); + + const std::string service_name = url_.path().substr(0, first_slash); + const std::string object_path = url_.path().substr(first_slash); + service_dbus_proxy_ = + bus_->GetObjectProxy(service_name, dbus::ObjectPath(object_path)); + + dbus::MethodCall call(kMojoDBusInterface, kMojoDBusConnectMethod); + dbus::MessageWriter writer(&call); + writer.AppendFileDescriptor(*client_fd.get()); + + // TODO(cmasone): handle errors! + service_dbus_proxy_->CallMethod( + &call, + dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::Bind(&LoadContext::ActivateService, base::Unretained(this))); + } + + // Sends a ShellHandle over to the now-connected externally-running service, + // using the Mojo ExternalService API. + void ActivateService(dbus::Response* response) { + external_service_->Activate( + mojo::ScopedMessagePipeHandle( + mojo::MessagePipeHandle( + service_provider_handle_.release().value()))); + } + + // Should the ExternalService disappear completely, destroy connection state. + // NB: This triggers off of the service disappearing from + // DBus. Perhaps there's a way to watch at the Mojo layer instead, + // and that would be superior? + void HandleNameOwnerChanged(const std::string& old_owner, + const std::string& new_owner) { + DCHECK(loader_->context_->task_runners()->ui_runner()-> + BelongsToCurrentThread()); + + if (new_owner.empty()) { + loader_->context_->task_runners()->ui_runner()->PostTask( + FROM_HERE, + base::Bind(&DBusServiceLoader::ForgetService, + base::Unretained(loader_), url_)); + } + } + + DBusServiceLoader* const loader_; + scoped_refptr<dbus::Bus> bus_; + dbus::ObjectProxy* service_dbus_proxy_; // Owned by bus_; + const GURL url_; + ScopedMessagePipeHandle service_provider_handle_; + KeepAlive keep_alive_; + scoped_ptr<embedder::ChannelInit> channel_init_; + ExternalServicePtr external_service_; + + DISALLOW_COPY_AND_ASSIGN(LoadContext); +}; + +DBusServiceLoader::DBusServiceLoader(Context* context) : context_(context) { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SESSION; + options.dbus_task_runner = context_->task_runners()->io_runner(); + bus_ = new dbus::Bus(options); +} + +DBusServiceLoader::~DBusServiceLoader() { + DCHECK(url_to_load_context_.empty()); +} + +void DBusServiceLoader::LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) { + DCHECK(url.SchemeIs("dbus")); + DCHECK(url_to_load_context_.find(url) == url_to_load_context_.end()); + url_to_load_context_[url] = + new LoadContext(this, bus_, url, service_handle.Pass()); +} + +void DBusServiceLoader::OnServiceError(ServiceManager* manager, + const GURL& url) { + // TODO(cmasone): Anything at all in this method here. +} + +void DBusServiceLoader::ForgetService(const GURL& url) { + DCHECK(context_->task_runners()->ui_runner()->BelongsToCurrentThread()); + DVLOG(2) << "Forgetting service (url: " << url << ")"; + + LoadContextMap::iterator it = url_to_load_context_.find(url); + DCHECK(it != url_to_load_context_.end()) << url; + + LoadContext* doomed = it->second; + url_to_load_context_.erase(it); + + delete doomed; +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/dbus_service_loader_linux.h b/chromium/mojo/shell/dbus_service_loader_linux.h new file mode 100644 index 00000000000..5bc24cab5fe --- /dev/null +++ b/chromium/mojo/shell/dbus_service_loader_linux.h @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_DBUS_SERVICE_LOADER_H_ +#define MOJO_SHELL_DBUS_SERVICE_LOADER_H_ + +#include <map> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/shell/keep_alive.h" +#include "url/gurl.h" + +namespace dbus { +class Bus; +} // namespace dbus + +namespace mojo { +namespace shell { + +class Context; + +// An implementation of ServiceLoader that contacts a system service +// and bootstraps a Mojo connection to it over DBus. +// +// In order to allow the externally-running service to accept connections from +// a Mojo shell, we need to get it a ShellHandle. This class creates a +// dedicated MessagePipe, passes a handle to one end to the desired service +// over DBus, and then passes the ShellHandle over that pipe. +// +// This class assumes the following: +// 1) Your service is already running. +// 2) Your service implements the Mojo ExternalService API +// (from external_service.mojom). +// 3) Your service exports an object that implements the org.chromium.Mojo DBus +// interface: +// <interface name="org.chromium.Mojo"> +// <method name="ConnectChannel"> +// <arg type="h" name="file_descriptor" direction="in" /> +// </method> +// </interface> +class DBusServiceLoader : public ServiceLoader { + public: + DBusServiceLoader(Context* context); + virtual ~DBusServiceLoader(); + + // URL for DBus services are of the following format: + // dbus:tld.domain.ServiceName/path/to/DBusObject + // + // This is simply the scheme (dbus:) and then the DBus service name followed + // by the DBus object path of an object that implements the org.chromium.Mojo + // interface as discussed above. + // + // Example: + // dbus:org.chromium.EchoService/org/chromium/MojoImpl + // + // This will tell DBusServiceLoader to reach out to a service with + // the name "org.chromium.EchoService" and invoke the method + // "org.chromium.Mojo.ConnectChannel" on the object exported at + // "/org/chromium/MojoImpl". + virtual void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) OVERRIDE; + + virtual void OnServiceError(ServiceManager* manager, const GURL& url) + OVERRIDE; + + private: + class LoadContext; + + // Tosses out connection-related state to service at given URL. + void ForgetService(const GURL& url); + + Context* const context_; + scoped_refptr<dbus::Bus> bus_; + + typedef std::map<GURL, LoadContext*> LoadContextMap; + LoadContextMap url_to_load_context_; + + DISALLOW_COPY_AND_ASSIGN(DBusServiceLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_DBUS_SERVICE_LOADER_H_ diff --git a/chromium/mojo/shell/desktop/mojo_main.cc b/chromium/mojo/shell/desktop/mojo_main.cc new file mode 100644 index 00000000000..de2d0d4906e --- /dev/null +++ b/chromium/mojo/shell/desktop/mojo_main.cc @@ -0,0 +1,56 @@ +// 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 "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "mojo/shell/child_process.h" +#include "mojo/shell/context.h" +#include "mojo/shell/init.h" +#include "mojo/shell/run.h" +#include "mojo/shell/switches.h" +#include "ui/gl/gl_surface.h" + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + base::CommandLine::Init(argc, argv); + mojo::shell::InitializeLogging(); + + // TODO(vtl): Unify parent and child process cases to the extent possible. + if (scoped_ptr<mojo::shell::ChildProcess> child_process = + mojo::shell::ChildProcess::Create( + *base::CommandLine::ForCurrentProcess())) { + child_process->Main(); + } else { + gfx::GLSurface::InitializeOneOff(); + + base::MessageLoop message_loop; + mojo::shell::Context shell_context; + + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kOrigin)) { + shell_context.mojo_url_resolver()->set_origin( + command_line.GetSwitchValueASCII(switches::kOrigin)); + } + + std::vector<GURL> app_urls; + base::CommandLine::StringVector args = command_line.GetArgs(); + for (base::CommandLine::StringVector::const_iterator it = args.begin(); + it != args.end(); + ++it) + app_urls.push_back(GURL(*it)); + + message_loop.PostTask(FROM_HERE, + base::Bind(mojo::shell::Run, + &shell_context, + app_urls)); + + message_loop.Run(); + } + + return 0; +} diff --git a/chromium/mojo/shell/dynamic_service_loader.cc b/chromium/mojo/shell/dynamic_service_loader.cc new file mode 100644 index 00000000000..9def450ed9b --- /dev/null +++ b/chromium/mojo/shell/dynamic_service_loader.cc @@ -0,0 +1,186 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/dynamic_service_loader.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/services/public/interfaces/network/url_loader.mojom.h" +#include "mojo/shell/context.h" +#include "mojo/shell/keep_alive.h" +#include "mojo/shell/switches.h" +#include "net/base/filename_util.h" + +namespace mojo { +namespace shell { +namespace { + +class Loader { + public: + explicit Loader(scoped_ptr<DynamicServiceRunner> runner) + : runner_(runner.Pass()) { + } + + virtual void Start(const GURL& url, + ScopedMessagePipeHandle service_handle, + Context* context) = 0; + + void StartService(const base::FilePath& path, + ScopedMessagePipeHandle service_handle, + bool path_is_valid) { + if (path_is_valid) { + runner_->Start(path, service_handle.Pass(), + base::Bind(&Loader::AppCompleted, base::Unretained(this))); + } else { + AppCompleted(); + } + } + + protected: + virtual ~Loader() {} + + private: + void AppCompleted() { + delete this; + } + + scoped_ptr<DynamicServiceRunner> runner_; +}; + +// For loading services via file:// URLs. +class LocalLoader : public Loader { + public: + explicit LocalLoader(scoped_ptr<DynamicServiceRunner> runner) + : Loader(runner.Pass()) { + } + + virtual void Start(const GURL& url, + ScopedMessagePipeHandle service_handle, + Context* context) OVERRIDE { + base::FilePath path; + net::FileURLToFilePath(url, &path); + + // TODO(darin): Check if the given file path exists. + + // Complete asynchronously for consistency with NetworkServiceLoader. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&Loader::StartService, + base::Unretained(this), + path, + base::Passed(&service_handle), + true)); + } +}; + +// For loading services via the network stack. +class NetworkLoader : public Loader, public URLLoaderClient { + public: + explicit NetworkLoader(scoped_ptr<DynamicServiceRunner> runner, + NetworkService* network_service) + : Loader(runner.Pass()) { + network_service->CreateURLLoader(Get(&url_loader_)); + url_loader_.set_client(this); + } + + virtual void Start(const GURL& url, + ScopedMessagePipeHandle service_handle, + Context* context) OVERRIDE { + service_handle_ = service_handle.Pass(); + + URLRequestPtr request(URLRequest::New()); + request->url = url.spec(); + request->auto_follow_redirects = true; + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableCache)) { + request->bypass_cache = true; + } + + DataPipe data_pipe; + url_loader_->Start(request.Pass(), data_pipe.producer_handle.Pass()); + + base::CreateTemporaryFile(&file_); + common::CopyToFile(data_pipe.consumer_handle.Pass(), + file_, + context->task_runners()->blocking_pool(), + base::Bind(&Loader::StartService, + base::Unretained(this), + file_, + base::Passed(&service_handle_))); + } + + private: + virtual ~NetworkLoader() { + if (!file_.empty()) + base::DeleteFile(file_, false); + } + + // URLLoaderClient methods: + virtual void OnReceivedRedirect(URLResponsePtr response, + const String& new_url, + const String& new_method) OVERRIDE { + // TODO(darin): Handle redirects properly! + } + virtual void OnReceivedResponse(URLResponsePtr response) OVERRIDE {} + virtual void OnReceivedError(NetworkErrorPtr error) OVERRIDE {} + virtual void OnReceivedEndOfResponseBody() OVERRIDE {} + + NetworkServicePtr network_service_; + URLLoaderPtr url_loader_; + ScopedMessagePipeHandle service_handle_; + base::FilePath file_; +}; + +} // namespace + +DynamicServiceLoader::DynamicServiceLoader( + Context* context, + scoped_ptr<DynamicServiceRunnerFactory> runner_factory) + : context_(context), + runner_factory_(runner_factory.Pass()) { +} + +DynamicServiceLoader::~DynamicServiceLoader() { +} + +void DynamicServiceLoader::LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) { + scoped_ptr<DynamicServiceRunner> runner = runner_factory_->Create(context_); + + GURL resolved_url; + if (url.SchemeIs("mojo")) { + resolved_url = context_->mojo_url_resolver()->Resolve(url); + } else { + resolved_url = url; + } + + Loader* loader; + if (resolved_url.SchemeIsFile()) { + loader = new LocalLoader(runner.Pass()); + } else { + if (!network_service_.get()) { + context_->service_manager()->ConnectTo(GURL("mojo:mojo_network_service"), + &network_service_, + GURL()); + } + loader = new NetworkLoader(runner.Pass(), network_service_.get()); + } + loader->Start(resolved_url, service_handle.Pass(), context_); +} + +void DynamicServiceLoader::OnServiceError(ServiceManager* manager, + const GURL& url) { + // TODO(darin): What should we do about service errors? This implies that + // the app closed its handle to the service manager. Maybe we don't care? +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/dynamic_service_loader.h b/chromium/mojo/shell/dynamic_service_loader.h new file mode 100644 index 00000000000..72bf093e56e --- /dev/null +++ b/chromium/mojo/shell/dynamic_service_loader.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_DYNAMIC_SERVICE_LOADER_H_ +#define MOJO_SHELL_DYNAMIC_SERVICE_LOADER_H_ + +#include <map> + +#include "base/macros.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/services/public/interfaces/network/network_service.mojom.h" +#include "mojo/shell/dynamic_service_runner.h" +#include "mojo/shell/keep_alive.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +class Context; +class DynamicServiceRunnerFactory; + +// An implementation of ServiceLoader that retrieves a dynamic library +// containing the implementation of the service and loads/runs it (via a +// DynamicServiceRunner). +class DynamicServiceLoader : public ServiceLoader { + public: + DynamicServiceLoader(Context* context, + scoped_ptr<DynamicServiceRunnerFactory> runner_factory); + virtual ~DynamicServiceLoader(); + + // ServiceLoader methods: + virtual void LoadService(ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_handle) OVERRIDE; + virtual void OnServiceError(ServiceManager* manager, const GURL& url) + OVERRIDE; + + private: + Context* const context_; + scoped_ptr<DynamicServiceRunnerFactory> runner_factory_; + NetworkServicePtr network_service_; + + DISALLOW_COPY_AND_ASSIGN(DynamicServiceLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_DYNAMIC_SERVICE_LOADER_H_ diff --git a/chromium/mojo/shell/dynamic_service_runner.h b/chromium/mojo/shell/dynamic_service_runner.h new file mode 100644 index 00000000000..1409fb0e5f7 --- /dev/null +++ b/chromium/mojo/shell/dynamic_service_runner.h @@ -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. + +#ifndef MOJO_SHELL_DYNAMIC_SERVICE_RUNNER_H_ +#define MOJO_SHELL_DYNAMIC_SERVICE_RUNNER_H_ + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/system/core.h" + +namespace base { +class FilePath; +} + +namespace mojo { +namespace shell { + +class Context; + +// Abstraction for loading a service (from the file system) and running it (on +// another thread or in a separate process). +class DynamicServiceRunner { + public: + virtual ~DynamicServiceRunner() {} + + // Takes ownership of the file at |app_path|. Loads the app in that file and + // runs it on some other thread/process. |app_completed_callback| is posted + // (to the thread on which |Start()| was called) after |MojoMain()| completes. + virtual void Start(const base::FilePath& app_path, + ScopedMessagePipeHandle service_handle, + const base::Closure& app_completed_callback) = 0; +}; + +class DynamicServiceRunnerFactory { + public: + virtual ~DynamicServiceRunnerFactory() {} + virtual scoped_ptr<DynamicServiceRunner> Create(Context* context) = 0; +}; + +// A generic factory. +template <class DynamicServiceRunnerImpl> +class DynamicServiceRunnerFactoryImpl : public DynamicServiceRunnerFactory { + public: + DynamicServiceRunnerFactoryImpl() {} + virtual ~DynamicServiceRunnerFactoryImpl() {} + virtual scoped_ptr<DynamicServiceRunner> Create(Context* context) OVERRIDE { + return scoped_ptr<DynamicServiceRunner>( + new DynamicServiceRunnerImpl(context)); + } +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_DYNAMIC_SERVICE_RUNNER_H_ diff --git a/chromium/mojo/shell/external_service.mojom b/chromium/mojo/shell/external_service.mojom new file mode 100644 index 00000000000..e08f71fe504 --- /dev/null +++ b/chromium/mojo/shell/external_service.mojom @@ -0,0 +1,11 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo { + +interface ExternalService { + Activate(handle<message_pipe> service_provider_handle); +}; + +} diff --git a/chromium/mojo/shell/in_process_dynamic_service_runner.cc b/chromium/mojo/shell/in_process_dynamic_service_runner.cc new file mode 100644 index 00000000000..6bcaa5c67a8 --- /dev/null +++ b/chromium/mojo/shell/in_process_dynamic_service_runner.cc @@ -0,0 +1,102 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/in_process_dynamic_service_runner.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/scoped_native_library.h" +#include "mojo/public/platform/native/system_thunks.h" + +namespace mojo { +namespace shell { + +InProcessDynamicServiceRunner::InProcessDynamicServiceRunner( + Context* context) + : keep_alive_(context), + thread_(this, "app_thread") { +} + +InProcessDynamicServiceRunner::~InProcessDynamicServiceRunner() { + if (thread_.HasBeenStarted()) { + DCHECK(!thread_.HasBeenJoined()); + thread_.Join(); + } +} + +void InProcessDynamicServiceRunner::Start( + const base::FilePath& app_path, + ScopedMessagePipeHandle service_handle, + const base::Closure& app_completed_callback) { + app_path_ = app_path; + + DCHECK(!service_handle_.is_valid()); + service_handle_ = service_handle.Pass(); + + DCHECK(app_completed_callback_runner_.is_null()); + app_completed_callback_runner_ = base::Bind(&base::TaskRunner::PostTask, + base::MessageLoopProxy::current(), + FROM_HERE, + app_completed_callback); + + DCHECK(!thread_.HasBeenStarted()); + thread_.Start(); +} + +void InProcessDynamicServiceRunner::Run() { + DVLOG(2) << "Loading/running Mojo app in process from library: " + << app_path_.value(); + + do { + base::NativeLibraryLoadError error; + base::ScopedNativeLibrary app_library( + base::LoadNativeLibrary(app_path_, &error)); + if (!app_library.is_valid()) { + LOG(ERROR) << "Failed to load app library (error: " << error.ToString() + << ")"; + break; + } + + MojoSetSystemThunksFn mojo_set_system_thunks_fn = + reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer( + "MojoSetSystemThunks")); + if (mojo_set_system_thunks_fn) { + MojoSystemThunks system_thunks = MojoMakeSystemThunks(); + size_t expected_size = mojo_set_system_thunks_fn(&system_thunks); + if (expected_size > sizeof(MojoSystemThunks)) { + LOG(ERROR) + << "Invalid app library: expected MojoSystemThunks size: " + << expected_size; + break; + } + } else { + // Strictly speaking this is not required, but it's very unusual to have + // an app that doesn't require the basic system library. + LOG(WARNING) << "MojoSetSystemThunks not found in app library"; + } + + typedef MojoResult (*MojoMainFunction)(MojoHandle); + MojoMainFunction main_function = reinterpret_cast<MojoMainFunction>( + app_library.GetFunctionPointer("MojoMain")); + if (!main_function) { + LOG(ERROR) << "Entrypoint MojoMain not found"; + break; + } + + // |MojoMain()| takes ownership of the service handle. + MojoResult result = main_function(service_handle_.release().value()); + if (result < MOJO_RESULT_OK) + LOG(ERROR) << "MojoMain returned an error: " << result; + } while (false); + + bool success = app_completed_callback_runner_.Run(); + app_completed_callback_runner_.Reset(); + LOG_IF(ERROR, !success) << "Failed post run app_completed_callback"; +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/in_process_dynamic_service_runner.h b/chromium/mojo/shell/in_process_dynamic_service_runner.h new file mode 100644 index 00000000000..ce6b029c782 --- /dev/null +++ b/chromium/mojo/shell/in_process_dynamic_service_runner.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_IN_PROCESS_DYNAMIC_SERVICE_RUNNER_H_ +#define MOJO_SHELL_IN_PROCESS_DYNAMIC_SERVICE_RUNNER_H_ + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/threading/simple_thread.h" +#include "mojo/shell/dynamic_service_runner.h" +#include "mojo/shell/keep_alive.h" + +namespace mojo { +namespace shell { + +// An implementation of |DynamicServiceRunner| that loads/runs the given app +// (from the file system) on a separate thread (in the current process). +class InProcessDynamicServiceRunner + : public DynamicServiceRunner, + public base::DelegateSimpleThread::Delegate { + public: + explicit InProcessDynamicServiceRunner(Context* context); + virtual ~InProcessDynamicServiceRunner(); + + // |DynamicServiceRunner| method: + virtual void Start(const base::FilePath& app_path, + ScopedMessagePipeHandle service_handle, + const base::Closure& app_completed_callback) OVERRIDE; + + private: + // |base::DelegateSimpleThread::Delegate| method: + virtual void Run() OVERRIDE; + + KeepAlive keep_alive_; + base::FilePath app_path_; + ScopedMessagePipeHandle service_handle_; + base::Callback<bool(void)> app_completed_callback_runner_; + + base::DelegateSimpleThread thread_; + + DISALLOW_COPY_AND_ASSIGN(InProcessDynamicServiceRunner); +}; + +typedef DynamicServiceRunnerFactoryImpl<InProcessDynamicServiceRunner> + InProcessDynamicServiceRunnerFactory; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_IN_PROCESS_DYNAMIC_SERVICE_RUNNER_H_ diff --git a/chromium/mojo/shell/init.cc b/chromium/mojo/shell/init.cc new file mode 100644 index 00000000000..481e5ee4a4d --- /dev/null +++ b/chromium/mojo/shell/init.cc @@ -0,0 +1,24 @@ +// 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 "mojo/shell/init.h" + +#include "base/logging.h" + +namespace mojo { +namespace shell { + +void InitializeLogging() { + logging::LoggingSettings settings; + settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; + logging::InitLogging(settings); + // To view log output with IDs and timestamps use "adb logcat -v threadtime". + logging::SetLogItems(false, // Process ID + false, // Thread ID + false, // Timestamp + false); // Tick count +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/init.h b/chromium/mojo/shell/init.h new file mode 100644 index 00000000000..c83134aa88c --- /dev/null +++ b/chromium/mojo/shell/init.h @@ -0,0 +1,18 @@ +// 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 MOJO_SHELL_INIT_H_ +#define MOJO_SHELL_INIT_H_ + +namespace mojo { +namespace shell { + +// Initialization routines shared by desktop and Android main functions. + +void InitializeLogging(); + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_INIT_H_ diff --git a/chromium/mojo/shell/keep_alive.cc b/chromium/mojo/shell/keep_alive.cc new file mode 100644 index 00000000000..387c6a790c3 --- /dev/null +++ b/chromium/mojo/shell/keep_alive.cc @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/keep_alive.h" + +#include "base/bind.h" +#include "mojo/shell/context.h" + +namespace mojo { +namespace shell { + +KeepAlive::KeepAlive(Context* context) : context_(context) { + DCHECK(context_->task_runners()->ui_runner()->RunsTasksOnCurrentThread()); + ++context_->keep_alive_counter()->count_; +} + +KeepAlive::~KeepAlive() { + DCHECK(context_->task_runners()->ui_runner()->RunsTasksOnCurrentThread()); + if (--context_->keep_alive_counter()->count_ == 0) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&KeepAlive::MaybeQuit, context_)); + } +} + +// static +void KeepAlive::MaybeQuit(Context* context) { + if (context->keep_alive_counter()->count_ == 0) + base::MessageLoop::current()->Quit(); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/keep_alive.h b/chromium/mojo/shell/keep_alive.h new file mode 100644 index 00000000000..d9b61275ca8 --- /dev/null +++ b/chromium/mojo/shell/keep_alive.h @@ -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. + +#ifndef MOJO_SHELL_KEEP_ALIVE_H_ +#define MOJO_SHELL_KEEP_ALIVE_H_ + +#include "base/basictypes.h" + +namespace mojo { +namespace shell { + +class Context; +class KeepAlive; + +class KeepAliveCounter { + public: + KeepAliveCounter() : count_(0) { + } + private: + friend class KeepAlive; + int count_; +}; + +// Instantiate this class to extend the lifetime of the thread associated +// with |context| (i.e., the shell's UI thread). Must only be used from +// the shell's UI thread. +class KeepAlive { + public: + explicit KeepAlive(Context* context); + ~KeepAlive(); + + private: + static void MaybeQuit(Context* context); + + Context* context_; + + DISALLOW_COPY_AND_ASSIGN(KeepAlive); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_KEEP_ALIVE_H_ diff --git a/chromium/mojo/shell/mojo_url_resolver.cc b/chromium/mojo/shell/mojo_url_resolver.cc new file mode 100644 index 00000000000..35059547e94 --- /dev/null +++ b/chromium/mojo/shell/mojo_url_resolver.cc @@ -0,0 +1,78 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/mojo_url_resolver.h" + +#include "base/base_paths.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "net/base/filename_util.h" +#include "url/url_util.h" + +namespace mojo { +namespace shell { +namespace { + +std::string MakeSharedLibraryName(const std::string& host_name) { +#if defined(OS_WIN) + return host_name + ".dll"; +#elif defined(OS_LINUX) || defined(OS_ANDROID) + return "lib" + host_name + ".so"; +#elif defined(OS_MACOSX) + return "lib" + host_name + ".dylib"; +#else + NOTREACHED() << "dynamic loading of services not supported"; + return std::string(); +#endif +} + +} // namespace + +MojoURLResolver::MojoURLResolver() { + // Needed to treat first component of mojo URLs as host, not path. + url::AddStandardScheme("mojo"); +} + +MojoURLResolver::~MojoURLResolver() { +} + +void MojoURLResolver::AddCustomMapping(const GURL& mojo_url, + const GURL& resolved_url) { + url_map_[mojo_url] = resolved_url; +} + +void MojoURLResolver::AddLocalFileMapping(const GURL& mojo_url) { + local_file_set_.insert(mojo_url); +} + +GURL MojoURLResolver::Resolve(const GURL& mojo_url) const { + std::map<GURL, GURL>::const_iterator it = url_map_.find(mojo_url); + if (it != url_map_.end()) + return it->second; + + std::string lib = MakeSharedLibraryName(mojo_url.host()); + + if (local_file_set_.find(mojo_url) != local_file_set_.end()) { + // Resolve to a local file URL. + base::FilePath path; +#if defined(OS_ANDROID) + // On Android, additional lib are bundled. + PathService::Get(base::DIR_MODULE, &path); +#else + PathService::Get(base::DIR_EXE, &path); +#if !defined(OS_WIN) + path = path.Append(FILE_PATH_LITERAL("lib")); +#endif // !defined(OS_WIN) +#endif // defined(OS_ANDROID) + path = path.Append(base::FilePath::FromUTF8Unsafe(lib)); + return net::FilePathToFileURL(path); + } + + // Otherwise, resolve to an URL relative to origin_. + return GURL(origin_ + "/" + lib); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/mojo_url_resolver.h b/chromium/mojo/shell/mojo_url_resolver.h new file mode 100644 index 00000000000..d232a407035 --- /dev/null +++ b/chromium/mojo/shell/mojo_url_resolver.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_MOJO_URL_RESOLVER_H_ +#define MOJO_SHELL_MOJO_URL_RESOLVER_H_ + +#include <map> +#include <set> + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +// This class resolves "mojo:" URLs to physical URLs (e.g., "file:" and +// "https:" URLs). By default, "mojo:" URLs resolve to a file location, but +// that resolution can be customized via the AddCustomMapping method. +class MojoURLResolver { + public: + MojoURLResolver(); + ~MojoURLResolver(); + + // If specified, then unknown "mojo:" URLs will be resolved relative to this + // origin. That is, the portion after the colon will be appeneded to origin + // with platform-specific shared library prefix and suffix inserted. + void set_origin(const std::string& origin) { origin_ = origin; } + + // Add a custom mapping for a particular "mojo:" URL. + void AddCustomMapping(const GURL& mojo_url, const GURL& resolved_url); + + // Add a local file mapping for a particular "mojo:" URL. This causes the + // "mojo:" URL to be resolved to an base::DIR_EXE-relative shared library. + void AddLocalFileMapping(const GURL& mojo_url); + + // Resolve the given "mojo:" URL to the URL that should be used to fetch the + // code for the corresponding Mojo App. + GURL Resolve(const GURL& mojo_url) const; + + private: + std::map<GURL, GURL> url_map_; + std::set<GURL> local_file_set_; + std::string origin_; +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_MOJO_URL_RESOLVER_H_ diff --git a/chromium/mojo/shell/out_of_process_dynamic_service_runner.cc b/chromium/mojo/shell/out_of_process_dynamic_service_runner.cc new file mode 100644 index 00000000000..9b2e3cfac72 --- /dev/null +++ b/chromium/mojo/shell/out_of_process_dynamic_service_runner.cc @@ -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. + +#include "mojo/shell/out_of_process_dynamic_service_runner.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_native_library.h" + +namespace mojo { +namespace shell { + +OutOfProcessDynamicServiceRunner::OutOfProcessDynamicServiceRunner( + Context* context) + : context_(context), + keep_alive_(context) { +} + +OutOfProcessDynamicServiceRunner::~OutOfProcessDynamicServiceRunner() { + if (app_child_process_host_) { + // TODO(vtl): Race condition: If |AppChildProcessHost::DidStart()| hasn't + // been called yet, we shouldn't call |Join()| here. (Until |DidStart()|, we + // may not have a child process to wait on.) Probably we should fix + // |Join()|. + app_child_process_host_->Join(); + } +} + +void OutOfProcessDynamicServiceRunner::Start( + const base::FilePath& app_path, + ScopedMessagePipeHandle service_handle, + const base::Closure& app_completed_callback) { + app_path_ = app_path; + + DCHECK(!service_handle_.is_valid()); + service_handle_ = service_handle.Pass(); + + DCHECK(app_completed_callback_.is_null()); + app_completed_callback_ = app_completed_callback; + + app_child_process_host_.reset(new AppChildProcessHost(context_, this)); + app_child_process_host_->Start(); + + // TODO(vtl): |app_path.AsUTF8Unsafe()| is unsafe. + app_child_process_host_->controller()->StartApp( + app_path.AsUTF8Unsafe(), + ScopedMessagePipeHandle(MessagePipeHandle( + service_handle.release().value()))); +} + +void OutOfProcessDynamicServiceRunner::AppCompleted(int32_t result) { + DVLOG(2) << "OutOfProcessDynamicServiceRunner::AppCompleted(" << result + << ")"; + + app_completed_callback_.Run(); + app_completed_callback_.Reset(); + app_child_process_host_.reset(); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/out_of_process_dynamic_service_runner.h b/chromium/mojo/shell/out_of_process_dynamic_service_runner.h new file mode 100644 index 00000000000..da07ff24fee --- /dev/null +++ b/chromium/mojo/shell/out_of_process_dynamic_service_runner.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_OUT_OF_PROCESS_DYNAMIC_SERVICE_RUNNER_H_ +#define MOJO_SHELL_OUT_OF_PROCESS_DYNAMIC_SERVICE_RUNNER_H_ + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "mojo/shell/app_child_process.mojom.h" +#include "mojo/shell/app_child_process_host.h" +#include "mojo/shell/dynamic_service_runner.h" +#include "mojo/shell/keep_alive.h" + +namespace mojo { +namespace shell { + +// An implementation of |DynamicServiceRunner| that loads/runs the given app +// (from the file system) in a separate process (of its own). +class OutOfProcessDynamicServiceRunner + : public DynamicServiceRunner, + public AppChildControllerClient { + public: + explicit OutOfProcessDynamicServiceRunner(Context* context); + virtual ~OutOfProcessDynamicServiceRunner(); + + // |DynamicServiceRunner| method: + virtual void Start(const base::FilePath& app_path, + ScopedMessagePipeHandle service_handle, + const base::Closure& app_completed_callback) OVERRIDE; + + private: + // |AppChildControllerClient| method: + virtual void AppCompleted(int32_t result) OVERRIDE; + + Context* const context_; + + KeepAlive keep_alive_; + base::FilePath app_path_; + ScopedMessagePipeHandle service_handle_; + base::Closure app_completed_callback_; + + scoped_ptr<AppChildProcessHost> app_child_process_host_; + + DISALLOW_COPY_AND_ASSIGN(OutOfProcessDynamicServiceRunner); +}; + +typedef DynamicServiceRunnerFactoryImpl<OutOfProcessDynamicServiceRunner> + OutOfProcessDynamicServiceRunnerFactory; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_OUT_OF_PROCESS_DYNAMIC_SERVICE_RUNNER_H_ diff --git a/chromium/mojo/shell/run.cc b/chromium/mojo/shell/run.cc new file mode 100644 index 00000000000..3d6840442ba --- /dev/null +++ b/chromium/mojo/shell/run.cc @@ -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. + +#include "mojo/shell/run.h" + +#include "base/logging.h" +#include "mojo/service_manager/service_manager.h" +#include "mojo/shell/context.h" +#include "mojo/shell/keep_alive.h" + +namespace mojo { +namespace shell { + +void Run(Context* context, const std::vector<GURL>& app_urls) { + KeepAlive keep_alive(context); + + if (app_urls.empty()) { + LOG(ERROR) << "No app path specified"; + return; + } + + for (std::vector<GURL>::const_iterator it = app_urls.begin(); + it != app_urls.end(); + ++it) { + ScopedMessagePipeHandle no_handle; + context->service_manager()->ConnectToService( + *it, std::string(), no_handle.Pass(), GURL()); + } +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/run.h b/chromium/mojo/shell/run.h new file mode 100644 index 00000000000..2b60b4bd7aa --- /dev/null +++ b/chromium/mojo/shell/run.h @@ -0,0 +1,22 @@ +// 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 MOJO_SHELL_RUN_H_ +#define MOJO_SHELL_RUN_H_ + +#include <vector> + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +class Context; + +void Run(Context* context, const std::vector<GURL>& app_urls); + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_RUN_H_ diff --git a/chromium/mojo/shell/shell_test_base.cc b/chromium/mojo/shell/shell_test_base.cc new file mode 100644 index 00000000000..947e70efdc1 --- /dev/null +++ b/chromium/mojo/shell/shell_test_base.cc @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/shell_test_base.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "build/build_config.h" +#include "mojo/shell/context.h" +#include "net/base/filename_util.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { +namespace test { + +ShellTestBase::ShellTestBase() { +} + +ShellTestBase::~ShellTestBase() { +} + +void ShellTestBase::InitMojo() { + DCHECK(!message_loop_); + DCHECK(!shell_context_); + message_loop_.reset(new base::MessageLoop()); + shell_context_.reset(new Context()); +} + +void ShellTestBase::LaunchServiceInProcess( + const GURL& service_url, + const std::string& service_name, + ScopedMessagePipeHandle client_handle) { + DCHECK(message_loop_); + DCHECK(shell_context_); + + base::FilePath base_dir; + CHECK(PathService::Get(base::DIR_EXE, &base_dir)); + // On android, the library is bundled with the app. +#if defined(OS_ANDROID) + base::FilePath service_dir; + CHECK(PathService::Get(base::DIR_MODULE, &service_dir)); + // On Mac and Windows, libraries are dumped beside the executables. +#elif defined(OS_MACOSX) || defined(OS_WIN) + base::FilePath service_dir(base_dir); +#else + // On Linux, they're under lib/. + base::FilePath service_dir(base_dir.AppendASCII("lib")); +#endif + shell_context_->mojo_url_resolver()->set_origin( + net::FilePathToFileURL(service_dir).spec()); + + shell_context_->service_manager()->ConnectToService( + service_url, service_name, client_handle.Pass(), GURL()); +} + +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/shell_test_base.h b/chromium/mojo/shell/shell_test_base.h new file mode 100644 index 00000000000..84864b7300f --- /dev/null +++ b/chromium/mojo/shell/shell_test_base.h @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_SHELL_TEST_BASE_H_ +#define MOJO_SHELL_SHELL_TEST_BASE_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/system/core.h" +#include "testing/gtest/include/gtest/gtest.h" + +class GURL; + +namespace base { +class MessageLoop; +} + +namespace mojo { +namespace shell { + +class Context; + +namespace test { + +class ShellTestBase : public testing::Test { + public: + ShellTestBase(); + virtual ~ShellTestBase(); + + // Should be called before any of the methods below are called. + void InitMojo(); + + // Launches the given service in-process; |service_url| should typically be a + // mojo: URL (the origin will be set to an "appropriate" file: URL). + void LaunchServiceInProcess(const GURL& service_url, + const std::string& service_name, + ScopedMessagePipeHandle client_handle); + + base::MessageLoop* message_loop() { return message_loop_.get(); } + Context* shell_context() { return shell_context_.get(); } + + private: + // Only set if/when |InitMojo()| is called. + scoped_ptr<base::MessageLoop> message_loop_; + scoped_ptr<Context> shell_context_; + + DISALLOW_COPY_AND_ASSIGN(ShellTestBase); +}; + +} // namespace test + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_SHELL_TEST_BASE_H_ diff --git a/chromium/mojo/shell/shell_test_base_unittest.cc b/chromium/mojo/shell/shell_test_base_unittest.cc new file mode 100644 index 00000000000..126249c06ad --- /dev/null +++ b/chromium/mojo/shell/shell_test_base_unittest.cc @@ -0,0 +1,110 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/shell_test_base.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/services/test_service/test_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { +namespace test { +namespace { + +typedef ShellTestBase ShellTestBaseTest; + +class QuitMessageLoopErrorHandler : public ErrorHandler { + public: + QuitMessageLoopErrorHandler() {} + virtual ~QuitMessageLoopErrorHandler() {} + + // |ErrorHandler| implementation: + virtual void OnConnectionError() OVERRIDE { + base::MessageLoop::current()->QuitWhenIdle(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler); +}; + +void PingCallback(base::MessageLoop* message_loop, bool* was_run) { + *was_run = true; + VLOG(2) << "Ping callback"; + message_loop->QuitWhenIdle(); +} + +TEST_F(ShellTestBaseTest, LaunchServiceInProcess) { + InitMojo(); + + InterfacePtr<mojo::test::ITestService> test_service; + + { + MessagePipe mp; + test_service.Bind(mp.handle0.Pass()); + LaunchServiceInProcess(GURL("mojo:mojo_test_service"), + mojo::test::ITestService::Name_, + mp.handle1.Pass()); + } + + bool was_run = false; + test_service->Ping(base::Bind(&PingCallback, + base::Unretained(message_loop()), + base::Unretained(&was_run))); + message_loop()->Run(); + EXPECT_TRUE(was_run); + EXPECT_FALSE(test_service.encountered_error()); + + test_service.reset(); + + // This will run until the test service has actually quit (which it will, + // since we killed the only connection to it). + message_loop()->Run(); +} + +// Tests that launching a service in process fails properly if the service +// doesn't exist. +TEST_F(ShellTestBaseTest, LaunchServiceInProcessInvalidService) { + InitMojo(); + + InterfacePtr<mojo::test::ITestService> test_service; + + { + MessagePipe mp; + test_service.Bind(mp.handle0.Pass()); + LaunchServiceInProcess(GURL("mojo:non_existent_service"), + mojo::test::ITestService::Name_, + mp.handle1.Pass()); + } + + bool was_run = false; + test_service->Ping(base::Bind(&PingCallback, + base::Unretained(message_loop()), + base::Unretained(&was_run))); + + // This will quit because there's nothing running. + message_loop()->Run(); + EXPECT_FALSE(was_run); + + // It may have quit before an error was processed. + if (!test_service.encountered_error()) { + QuitMessageLoopErrorHandler quitter; + test_service.set_error_handler(&quitter); + message_loop()->Run(); + EXPECT_TRUE(test_service.encountered_error()); + } + + test_service.reset(); +} + +} // namespace +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/shell_test_helper.cc b/chromium/mojo/shell/shell_test_helper.cc new file mode 100644 index 00000000000..cec75ce47d4 --- /dev/null +++ b/chromium/mojo/shell/shell_test_helper.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/shell_test_helper.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "mojo/shell/init.h" + +namespace mojo { +namespace shell { + +class ShellTestHelper::TestServiceProvider : public ServiceProvider { + public: + TestServiceProvider() {} + virtual ~TestServiceProvider() {} + + // ServiceProvider: + virtual void ConnectToService( + const mojo::String& service_url, + const mojo::String& service_name, + ScopedMessagePipeHandle client_handle, + const mojo::String& requestor_url) OVERRIDE {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestServiceProvider); +}; + +ShellTestHelper::ShellTestHelper() { + base::CommandLine::Init(0, NULL); + mojo::shell::InitializeLogging(); +} + +ShellTestHelper::~ShellTestHelper() { +} + +void ShellTestHelper::Init() { + context_.reset(new Context); + test_api_.reset(new ServiceManager::TestAPI(context_->service_manager())); + local_service_provider_.reset(new TestServiceProvider); + service_provider_.Bind(test_api_->GetServiceProviderHandle().Pass()); + service_provider_.set_client(local_service_provider_.get()); +} + +void ShellTestHelper::SetLoaderForURL(scoped_ptr<ServiceLoader> loader, + const GURL& url) { + context_->service_manager()->SetLoaderForURL(loader.Pass(), url); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/shell_test_helper.h b/chromium/mojo/shell/shell_test_helper.h new file mode 100644 index 00000000000..ef8af899e11 --- /dev/null +++ b/chromium/mojo/shell/shell_test_helper.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_SHELL_TEST_HELPER_ +#define MOJO_SHELL_SHELL_TEST_HELPER_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "mojo/public/interfaces/service_provider/service_provider.mojom.h" +#include "mojo/service_manager/service_loader.h" +#include "mojo/shell/context.h" + +class GURL; + +namespace mojo { + +class ServiceLoader; + +namespace shell { + +// ShellTestHelper is useful for tests to establish a connection to the +// ServiceProvider. Invoke Init() to establish the connection. Once done, +// service_provider() returns the handle to the ServiceProvider. +class ShellTestHelper { + public: + ShellTestHelper(); + ~ShellTestHelper(); + + void Init(); + + // Returns a handle to the ServiceProvider. ShellTestHelper owns the + // ServiceProvider. + ServiceProvider* service_provider() { return service_provider_.get(); } + + // Sets a ServiceLoader for the specified URL. |loader| is ultimately used on + // the thread this class spawns. + void SetLoaderForURL(scoped_ptr<ServiceLoader> loader, const GURL& url); + + private: + class TestServiceProvider; + + scoped_ptr<Context> context_; + + scoped_ptr<ServiceManager::TestAPI> test_api_; + + // ScopedMessagePipeHandle service_provider_handle_; + + // Client interface for the shell. + scoped_ptr<TestServiceProvider> local_service_provider_; + + ServiceProviderPtr service_provider_; + + DISALLOW_COPY_AND_ASSIGN(ShellTestHelper); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_SHELL_TEST_HELPER_ diff --git a/chromium/mojo/shell/shell_test_main.cc b/chromium/mojo/shell/shell_test_main.cc new file mode 100644 index 00000000000..7f057f707ce --- /dev/null +++ b/chromium/mojo/shell/shell_test_main.cc @@ -0,0 +1,31 @@ +// 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/bind.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "mojo/shell/child_process.h" +#include "mojo/shell/switches.h" +#include "testing/gtest/include/gtest/gtest.h" + +int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + + if (command_line.HasSwitch(switches::kChildProcessType)) { + scoped_ptr<mojo::shell::ChildProcess> child_process = + mojo::shell::ChildProcess::Create(command_line); + CHECK(child_process); + child_process->Main(); + return 0; + } + + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, argv, base::Bind(&base::TestSuite::Run, + base::Unretained(&test_suite))); +} diff --git a/chromium/mojo/shell/switches.cc b/chromium/mojo/shell/switches.cc new file mode 100644 index 00000000000..c597218f412 --- /dev/null +++ b/chromium/mojo/shell/switches.cc @@ -0,0 +1,30 @@ +// 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 "mojo/shell/switches.h" + +namespace switches { + +// Used to specify the type of child process (switch values from +// |ChildProcess::Type|). +const char kChildProcessType[] = "child-process-type"; + +// Force dynamically loaded apps / services to be loaded irrespective of cache +// instructions. +const char kDisableCache[] = "disable-cache"; + +// Load apps in separate processes. +// TODO(vtl): Work in progress; doesn't work. Flip this to "disable" (or maybe +// change it to "single-process") when it works. +const char kEnableMultiprocess[] = "enable-multiprocess"; + +// Map mojo: URLs to a shared library of similar name at this origin. See +// mojo_url_resolver.cc for details. +const char kOrigin[] = "origin"; + +// Enables the mojo spy, which acts as a man-in-the-middle inspector for +// message pipes and other activities. This is work in progress. +const char kSpy[] = "spy"; + +} // namespace switches diff --git a/chromium/mojo/shell/switches.h b/chromium/mojo/shell/switches.h new file mode 100644 index 00000000000..a1d4ac578f9 --- /dev/null +++ b/chromium/mojo/shell/switches.h @@ -0,0 +1,19 @@ +// 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 MOJO_SHELL_SWITCHES_H_ +#define MOJO_SHELL_SWITCHES_H_ + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kChildProcessType[]; +extern const char kDisableCache[]; +extern const char kEnableMultiprocess[]; +extern const char kOrigin[]; +extern const char kSpy[]; +} // namespace switches + +#endif // MOJO_SHELL_SWITCHES_H_ diff --git a/chromium/mojo/shell/task_runners.cc b/chromium/mojo/shell/task_runners.cc new file mode 100644 index 00000000000..573dca6ea29 --- /dev/null +++ b/chromium/mojo/shell/task_runners.cc @@ -0,0 +1,38 @@ +// 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 "mojo/shell/task_runners.h" + +#include "base/threading/sequenced_worker_pool.h" + +namespace mojo { +namespace shell { + +namespace { + +const size_t kMaxBlockingPoolThreads = 3; + +scoped_ptr<base::Thread> CreateIOThread(const char* name) { + scoped_ptr<base::Thread> thread(new base::Thread(name)); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + thread->StartWithOptions(options); + return thread.Pass(); +} + +} // namespace + +TaskRunners::TaskRunners(base::SingleThreadTaskRunner* ui_runner) + : ui_runner_(ui_runner), + io_thread_(CreateIOThread("io_thread")), + blocking_pool_(new base::SequencedWorkerPool(kMaxBlockingPoolThreads, + "blocking_pool")) { +} + +TaskRunners::~TaskRunners() { + blocking_pool_->Shutdown(); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/task_runners.h b/chromium/mojo/shell/task_runners.h new file mode 100644 index 00000000000..7331fa8e524 --- /dev/null +++ b/chromium/mojo/shell/task_runners.h @@ -0,0 +1,53 @@ +// 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 MOJO_SHELL_TASK_RUNNERS_H_ +#define MOJO_SHELL_TASK_RUNNERS_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/thread.h" + +namespace base { +class SequencedWorkerPool; +} + +namespace mojo { +namespace shell { + +// A context object that contains the common task runners for the shell's main +// process. +class TaskRunners { + public: + explicit TaskRunners(base::SingleThreadTaskRunner* ui_runner); + ~TaskRunners(); + + base::SingleThreadTaskRunner* ui_runner() const { + return ui_runner_.get(); + } + + base::SingleThreadTaskRunner* io_runner() const { + return io_thread_->message_loop_proxy(); + } + + base::SequencedWorkerPool* blocking_pool() const { + return blocking_pool_.get(); + } + + private: + // TODO(beng): should this be named shell_runner_? + scoped_refptr<base::SingleThreadTaskRunner> ui_runner_; + scoped_ptr<base::Thread> io_thread_; + + scoped_refptr<base::SequencedWorkerPool> blocking_pool_; + + DISALLOW_COPY_AND_ASSIGN(TaskRunners); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_TASK_RUNNERS_H_ diff --git a/chromium/mojo/shell/test_child_process.cc b/chromium/mojo/shell/test_child_process.cc new file mode 100644 index 00000000000..fe187a546dc --- /dev/null +++ b/chromium/mojo/shell/test_child_process.cc @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/test_child_process.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" + +namespace mojo { +namespace shell { + +TestChildProcess::TestChildProcess() { +} + +TestChildProcess::~TestChildProcess() { +} + +void TestChildProcess::Main() { + VLOG(2) << "TestChildProcess::Main()"; + + CHECK(!base::MessageLoop::current()); +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/test_child_process.h b/chromium/mojo/shell/test_child_process.h new file mode 100644 index 00000000000..c2d440b8295 --- /dev/null +++ b/chromium/mojo/shell/test_child_process.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_TEST_CHILD_PROCESS_H_ +#define MOJO_SHELL_TEST_CHILD_PROCESS_H_ + +#include "base/macros.h" +#include "mojo/shell/child_process.h" + +namespace mojo { +namespace shell { + +class TestChildProcess : public ChildProcess { + public: + TestChildProcess(); + virtual ~TestChildProcess(); + + virtual void Main() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TestChildProcess); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_TEST_CHILD_PROCESS_H_ diff --git a/chromium/mojo/shell/view_manager_loader.cc b/chromium/mojo/shell/view_manager_loader.cc new file mode 100644 index 00000000000..278ab81e05b --- /dev/null +++ b/chromium/mojo/shell/view_manager_loader.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/shell/view_manager_loader.h" + +#include "mojo/public/cpp/application/application.h" +#include "mojo/services/view_manager/view_manager_init_service_impl.h" + +namespace mojo { +namespace shell { + +ViewManagerLoader::ViewManagerLoader() { +} + +ViewManagerLoader::~ViewManagerLoader() { +} + +void ViewManagerLoader::LoadService( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_provider_handle) { + // TODO(sky): this needs some sort of authentication as well as making sure + // we only ever have one active at a time. + scoped_ptr<Application> app(new Application(service_provider_handle.Pass())); + app->AddService<view_manager::service::ViewManagerInitServiceImpl>( + app->service_provider()); + apps_.push_back(app.release()); +} + +void ViewManagerLoader::OnServiceError(ServiceManager* manager, + const GURL& url) { +} + +} // namespace shell +} // namespace mojo diff --git a/chromium/mojo/shell/view_manager_loader.h b/chromium/mojo/shell/view_manager_loader.h new file mode 100644 index 00000000000..8bebc5742cb --- /dev/null +++ b/chromium/mojo/shell/view_manager_loader.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SHELL_VIEW_MANAGER_LOADER_H_ +#define MOJO_SHELL_VIEW_MANAGER_LOADER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "mojo/service_manager/service_loader.h" + +namespace mojo { + +class Application; + +namespace shell { + +// ServiceLoader responsible for creating connections to the ViewManager. +class ViewManagerLoader : public ServiceLoader { + public: + ViewManagerLoader(); + virtual ~ViewManagerLoader(); + + private: + // ServiceLoader overrides: + virtual void LoadService( + ServiceManager* manager, + const GURL& url, + ScopedMessagePipeHandle service_provider_handle) OVERRIDE; + virtual void OnServiceError(ServiceManager* manager, + const GURL& url) OVERRIDE; + + ScopedVector<Application> apps_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_VIEW_MANAGER_LOADER_H_ diff --git a/chromium/mojo/spy/DEPS b/chromium/mojo/spy/DEPS new file mode 100644 index 00000000000..8fa9d48d882 --- /dev/null +++ b/chromium/mojo/spy/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net", +] diff --git a/chromium/mojo/spy/PRESUBMIT.py b/chromium/mojo/spy/PRESUBMIT.py new file mode 100644 index 00000000000..5090fb8a1db --- /dev/null +++ b/chromium/mojo/spy/PRESUBMIT.py @@ -0,0 +1,40 @@ +# 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 os +import sys + +def _CommonChecks(input_api, output_api): + results = [] + # Importing ui actually brings tvcm into the path. + import ui + from tvcm import presubmit_checker + checker = presubmit_checker.PresubmitChecker(input_api, output_api) + results += checker.RunChecks() + return results + +def GetPathsToPrepend(input_api): + return [input_api.PresubmitLocalPath()] + +def RunWithPrependedPath(prepended_path, fn, *args): + old_path = sys.path + + try: + sys.path = prepended_path + old_path + return fn(*args) + finally: + sys.path = old_path + +def CheckChangeOnUpload(input_api, output_api): + def go(): + results = [] + results.extend(_CommonChecks(input_api, output_api)) + return results + return RunWithPrependedPath(GetPathsToPrepend(input_api), go) + +def CheckChangeOnCommit(input_api, output_api): + def go(): + results = [] + results.extend(_CommonChecks(input_api, output_api)) + return results + return RunWithPrependedPath(GetPathsToPrepend(input_api), go) diff --git a/chromium/mojo/spy/run_ui_dev_server b/chromium/mojo/spy/run_ui_dev_server new file mode 100755 index 00000000000..5dd294f0514 --- /dev/null +++ b/chromium/mojo/spy/run_ui_dev_server @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# 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. +"""Starts the mojo spy dev server. + +During normal usage of mojo spy, the spy files are compiled into standalone +HTML+JS+CSS snippets that are then embedded in the mojo shell. + +The dev server allows edit-reload style development of the spy UI in isolation +of the c++ bits. To use, start the dev server, navigate to the URL the script +prints, and run any of the tests listed. Reloading in the browser loads the +latest content from disk, enabling a traditional web development workflow. +""" +import sys + +from ui import dev_server + +COMPONENTS_PORT = 8015 + +if __name__ == '__main__': + sys.exit(dev_server.Main(COMPONENTS_PORT, sys.argv[1:])) diff --git a/chromium/mojo/spy/run_ui_tests b/chromium/mojo/spy/run_ui_tests new file mode 100755 index 00000000000..00f8ba1bb99 --- /dev/null +++ b/chromium/mojo/spy/run_ui_tests @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# 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. +"""Starts the mojo spy dev server. + +During normal usage of mojo spy, the spy files are compiled into standalone +HTML+JS+CSS snippets that are then embedded in the mojo shell. + +The dev server allows edit-reload style development of the spy UI in isolation +of the c++ bits. To use, start the dev server, navigate to the URL the script +prints, and run any of the tests listed. Reloading in the browser loads the +latest content from disk, enabling a traditional web development workflow. +""" +import sys + +import ui +from tvcm import test_runner + +if __name__ == '__main__': + runner = test_runner.TestRunner() + runner.AddModule(ui) + sys.exit(runner.Main()) diff --git a/chromium/mojo/spy/spy.cc b/chromium/mojo/spy/spy.cc new file mode 100644 index 00000000000..3fcc49c3d66 --- /dev/null +++ b/chromium/mojo/spy/spy.cc @@ -0,0 +1,215 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/spy/spy.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/threading/thread.h" +#include "base/threading/worker_pool.h" + +#include "mojo/public/cpp/system/core.h" +#include "mojo/service_manager/service_manager.h" +#include "mojo/spy/websocket_server.h" + +namespace { + +const size_t kMessageBufSize = 2 * 1024; +const size_t kHandleBufSize = 64; +const int kDefaultWebSocketPort = 42424; + +void CloseHandles(MojoHandle* handles, size_t count) { + for (size_t ix = 0; ix != count; ++count) + MojoClose(handles[ix]); +} + +// In charge of processing messages that flow over a +// single message pipe. +class MessageProcessor : + public base::RefCountedThreadSafe<MessageProcessor> { + public: + + MessageProcessor() + : last_result_(MOJO_RESULT_OK), + bytes_transfered_(0) { + + message_count_[0] = 0; + message_count_[1] = 0; + handle_count_[0] = 0; + handle_count_[1] = 0; + } + + void Start(mojo::ScopedMessagePipeHandle client, + mojo::ScopedMessagePipeHandle interceptor) { + std::vector<mojo::MessagePipeHandle> pipes; + pipes.push_back(client.get()); + pipes.push_back(interceptor.get()); + std::vector<MojoHandleSignals> handle_signals; + handle_signals.push_back(MOJO_HANDLE_SIGNAL_READABLE); + handle_signals.push_back(MOJO_HANDLE_SIGNAL_READABLE); + + scoped_ptr<char[]> mbuf(new char[kMessageBufSize]); + scoped_ptr<MojoHandle[]> hbuf(new MojoHandle[kHandleBufSize]); + + // Main processing loop: + // 1- Wait for an endpoint to have a message. + // 2- Read the message + // 3- Log data + // 4- Wait until the opposite port is ready for writting + // 4- Write the message to opposite port. + + for (;;) { + int r = WaitMany(pipes, handle_signals, MOJO_DEADLINE_INDEFINITE); + if ((r < 0) || (r > 1)) { + last_result_ = r; + break; + } + + uint32_t bytes_read = kMessageBufSize; + uint32_t handles_read = kHandleBufSize; + + if (!CheckResult(ReadMessageRaw(pipes[r], + mbuf.get(), &bytes_read, + hbuf.get(), &handles_read, + MOJO_READ_MESSAGE_FLAG_NONE))) + break; + + if (!bytes_read && !handles_read) + continue; + + if (handles_read) + handle_count_[r] += handles_read; + + ++message_count_[r]; + bytes_transfered_ += bytes_read; + + mojo::MessagePipeHandle write_handle = (r == 0) ? pipes[1] : pipes[0]; + if (!CheckResult(Wait(write_handle, + MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_DEADLINE_INDEFINITE))) + break; + + if (!CheckResult(WriteMessageRaw(write_handle, + mbuf.get(), bytes_read, + hbuf.get(), handles_read, + MOJO_WRITE_MESSAGE_FLAG_NONE))) { + // On failure we own the handles. For now just close them. + if (handles_read) + CloseHandles(hbuf.get(), handles_read); + break; + } + } + } + + private: + friend class base::RefCountedThreadSafe<MessageProcessor>; + virtual ~MessageProcessor() {} + + bool CheckResult(MojoResult mr) { + if (mr == MOJO_RESULT_OK) + return true; + last_result_ = mr; + return false; + } + + MojoResult last_result_; + uint32_t bytes_transfered_; + uint32_t message_count_[2]; + uint32_t handle_count_[2]; +}; + +// In charge of intercepting access to the service manager. +class SpyInterceptor : public mojo::ServiceManager::Interceptor { + private: + virtual mojo::ScopedMessagePipeHandle OnConnectToClient( + const GURL& url, mojo::ScopedMessagePipeHandle real_client) OVERRIDE { + if (!MustIntercept(url)) + return real_client.Pass(); + + // You can get an invalid handle if the app (or service) is + // created by unconventional means, for example the command line. + if (!real_client.is_valid()) + return real_client.Pass(); + + mojo::ScopedMessagePipeHandle faux_client; + mojo::ScopedMessagePipeHandle interceptor; + CreateMessagePipe(NULL, &faux_client, &interceptor); + + scoped_refptr<MessageProcessor> processor = new MessageProcessor(); + base::WorkerPool::PostTask( + FROM_HERE, + base::Bind(&MessageProcessor::Start, + processor, + base::Passed(&real_client), base::Passed(&interceptor)), + true); + + return faux_client.Pass(); + } + + bool MustIntercept(const GURL& url) { + // TODO(cpu): manage who and when to intercept. + return true; + } +}; + +spy::WebSocketServer* ws_server = NULL; + +void StartServer(int port) { + // TODO(cpu) figure out lifetime of the server. See Spy() dtor. + ws_server = new spy::WebSocketServer(port); + ws_server->Start(); +} + +struct SpyOptions { + int websocket_port; + + SpyOptions() + : websocket_port(kDefaultWebSocketPort) { + } +}; + +SpyOptions ProcessOptions(const std::string& options) { + SpyOptions spy_options; + if (options.empty()) + return spy_options; + base::StringPairs kv_pairs; + base::SplitStringIntoKeyValuePairs(options, ':', ',', &kv_pairs); + base::StringPairs::iterator it = kv_pairs.begin(); + for (; it != kv_pairs.end(); ++it) { + if (it->first == "port") { + int port; + if (base::StringToInt(it->second, &port)) + spy_options.websocket_port = port; + } + } + return spy_options; +} + +} // namespace + +namespace mojo { + +Spy::Spy(mojo::ServiceManager* service_manager, const std::string& options) { + SpyOptions spy_options = ProcessOptions(options); + // Start the tread what will accept commands from the frontend. + control_thread_.reset(new base::Thread("mojo_spy_control_thread")); + base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); + control_thread_->StartWithOptions(thread_options); + control_thread_->message_loop_proxy()->PostTask( + FROM_HERE, base::Bind(&StartServer, spy_options.websocket_port)); + + // Start intercepting mojo services. + service_manager->SetInterceptor(new SpyInterceptor()); +} + +Spy::~Spy(){ + // TODO(cpu): Do not leak the interceptor. Lifetime between the + // service_manager and the spy is still unclear hence the leak. +} + +} // namespace mojo diff --git a/chromium/mojo/spy/spy.h b/chromium/mojo/spy/spy.h new file mode 100644 index 00000000000..c3673f453f4 --- /dev/null +++ b/chromium/mojo/spy/spy.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SPY_SPY_H_ +#define MOJO_SPY_SPY_H_ + +#include <string> +#include "base/memory/scoped_ptr.h" + +namespace base { + class Thread; +} + +namespace mojo { + +class ServiceManager; + +// mojo::Spy is a troubleshooting and debugging aid. It helps tracking +// the mojo system core activities like messages, service creation, etc. +// +// The |options| parameter in the constructor comes from the command +// line of the mojo_shell. Which takes --spy=<options>. Each option is +// separated by ',' and each option is a key+ value pair separated by ':'. +// +// For example --spy=port:13333 +// +class Spy { + public: + Spy(mojo::ServiceManager* service_manager, const std::string& options); + ~Spy(); + + private: + // This thread runs the code that talks to the frontend. + scoped_ptr<base::Thread> control_thread_; +}; + +} // namespace mojo + +#endif // MOJO_SPY_SPY_H_ diff --git a/chromium/mojo/spy/ui/__init__.py b/chromium/mojo/spy/ui/__init__.py new file mode 100644 index 00000000000..bfecdb26df6 --- /dev/null +++ b/chromium/mojo/spy/ui/__init__.py @@ -0,0 +1,5 @@ +# 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. + +from ui import tvcm_stub diff --git a/chromium/mojo/spy/ui/dev_server.py b/chromium/mojo/spy/ui/dev_server.py new file mode 100644 index 00000000000..2a830324b2d --- /dev/null +++ b/chromium/mojo/spy/ui/dev_server.py @@ -0,0 +1,23 @@ +# 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 optparse +import tvcm + +from ui import spy_project + + +def Main(port, args): + parser = optparse.OptionParser() + _, args = parser.parse_args(args) + + project = spy_project.SpyProject() + server = tvcm.DevServer( + port=port, project=project) + + def IsTestModuleResourcePartOfSpy(module_resource): + return module_resource.absolute_path.startswith(project.spy_path) + + server.test_module_resource_filter = IsTestModuleResourcePartOfSpy + return server.serve_forever() diff --git a/chromium/mojo/spy/ui/spy.html b/chromium/mojo/spy/ui/spy.html new file mode 100644 index 00000000000..79aeb7f8d88 --- /dev/null +++ b/chromium/mojo/spy/ui/spy.html @@ -0,0 +1,35 @@ +<!-- +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. +--> +<style> + x-spy-log-message { + display: block; + border-style: 1px solid black; + margin-top: 4px; + margin-bottom: 4px; + } + + x-spy { + display: -webkit-flex; + -webkit-flex-direction: column; + } + x-spy > messages { + display: block; + -webkit-flex: 1 1 auto; + } + x-spy > form { + -webkit-flex: 0 0 auto; + } + x-spy > form > input { + width: 300px; + } +</style> + + +<template id="x-spy-template"> + <messages> + </messages> + <input type="text" id="command" placeholder="enter spy command + enter" /> +</template> diff --git a/chromium/mojo/spy/ui/spy.js b/chromium/mojo/spy/ui/spy.js new file mode 100644 index 00000000000..e3f47e1f01a --- /dev/null +++ b/chromium/mojo/spy/ui/spy.js @@ -0,0 +1,96 @@ +// 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. + +'use strict'; + +tvcm.require('tvcm.utils'); +tvcm.require('tvcm.ui'); +tvcm.requireTemplate('ui.spy'); + +tvcm.exportTo('ui', function() { + /** + * @constructor + */ + var LogMessage = tvcm.ui.define('x-spy-log-message'); + + LogMessage.prototype = { + __proto__: HTMLUnknownElement.prototype, + + decorate: function() { + }, + + get message() { + return message_; + }, + + set message(message) { + this.message_ = message; + this.textContent = JSON.stringify(message); + } + }; + + + /** + * @constructor + */ + var Spy = tvcm.ui.define('x-spy'); + + Spy.prototype = { + __proto__: HTMLUnknownElement.prototype, + + decorate: function() { + var node = tvcm.instantiateTemplate('#x-spy-template'); + this.appendChild(node); + + this.channel_ = undefined; + this.onMessage_ = this.onMessage_.bind(this); + + var commandEl = this.querySelector('#command'); + commandEl.addEventListener('keydown', function(e) { + if (e.keyCode == 13) { + e.stopPropagation(); + this.onCommandEntered_(); + } + }.bind(this)); + + this.updateDisabledStates_(); + }, + + get channel() { + return channel_; + }, + + set channel(channel) { + if (this.channel_) + this.channel_.removeEventListener('message', this.onMessage_); + this.channel_ = channel; + if (this.channel_) + this.channel_.addEventListener('message', this.onMessage_); + this.updateDisabledStates_(); + }, + + updateDisabledStates_: function() { + var connected = this.channel_ !== undefined; + + this.querySelector('#command').disabled = !connected; + }, + + onCommandEntered_: function(cmd) { + var commandEl = this.querySelector('#command'); + this.channel_.send(JSON.stringify(commandEl.value)); + commandEl.value = ''; + }, + + onMessage_: function(message) { + var messageEl = new LogMessage(); + messageEl.message = message.data; + this.querySelector('messages').appendChild(messageEl); + } + + }; + + return { + Spy: Spy + }; +}); diff --git a/chromium/mojo/spy/ui/spy_project.py b/chromium/mojo/spy/ui/spy_project.py new file mode 100644 index 00000000000..409ecdfd5d1 --- /dev/null +++ b/chromium/mojo/spy/ui/spy_project.py @@ -0,0 +1,18 @@ +# 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 os + +import tvcm_stub + +from trace_viewer import trace_viewer_project + + +class SpyProject(trace_viewer_project.TraceViewerProject): + spy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), + '..')) + + def __init__(self): + super(SpyProject, self).__init__( + [self.spy_path]) diff --git a/chromium/mojo/spy/ui/spy_shell.html b/chromium/mojo/spy/ui/spy_shell.html new file mode 100644 index 00000000000..c46a24ff0fb --- /dev/null +++ b/chromium/mojo/spy/ui/spy_shell.html @@ -0,0 +1,38 @@ +<!-- +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. +--> +<style> + html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + display: -webkit-flex; + -webkit-flex-direction: column; + } + body > x-spy-shell { + -webkit-flex: 1 1 auto; + } + + x-spy-shell { + display: -webkit-flex; + -webkit-flex-direction: column; + } + + x-spy-shell > #status { + -webkit-flex: 0 0 auto; + border-bottom: 1px solid black; + } + + x-spy-shell > x-spy { + -webkit-flex: 1 1 auto; + } +</style> + + +<template id="x-spy-shell-template"> + <div id="status"></div> + <x-spy></x-spy> +</template> diff --git a/chromium/mojo/spy/ui/spy_shell.js b/chromium/mojo/spy/ui/spy_shell.js new file mode 100644 index 00000000000..bcf0430a37d --- /dev/null +++ b/chromium/mojo/spy/ui/spy_shell.js @@ -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. + +'use strict'; + +tvcm.require('ui.spy'); +tvcm.require('tvcm.ui'); +tvcm.require('tvcm.ui.dom_helpers'); +tvcm.requireTemplate('ui.spy_shell'); + +tvcm.exportTo('ui', function() { + /** + * @constructor + */ + var SpyShell = tvcm.ui.define('x-spy-shell'); + + SpyShell.prototype = { + __proto__: HTMLUnknownElement.prototype, + + decorate: function(socketURL) { + var node = tvcm.instantiateTemplate('#x-spy-shell-template'); + this.appendChild(node); + + this.socketURL_ = socketURL; + this.conn_ = undefined; + + this.statusEl_ = this.querySelector('#status'); + this.statusEl_.textContent = 'Not connected'; + + this.spy_ = this.querySelector('x-spy'); + tvcm.ui.decorate(this.spy_, ui.Spy); + + this.openConnection_(); + }, + + get socketURL() { + return this.socketURL_; + }, + + openConnection_: function() { + if (!(this.conn_ == undefined || + this.conn_.readyState === undefined || + conn.readyState > 1)) { + return; + } + + this.conn_ = new WebSocket(this.socketURL_); + this.conn_.onopen = function() { + this.statusEl_.textContent = 'connected at ' + this.socketURL_; + this.spy_.connection = this.conn_; + }.bind(this); + + this.conn_.onclose = function(event) { + this.statusEl_.textContent = 'connection closed'; + this.spy_.connection = undefined; + }.bind(this); + this.conn_.onerror = function(event) { + this.statusEl_.innerHTML = 'got error'; + }.bind(this); + } + + }; + + return { + SpyShell: SpyShell + }; +}); diff --git a/chromium/mojo/spy/ui/spy_shell_to_html b/chromium/mojo/spy/ui/spy_shell_to_html new file mode 100755 index 00000000000..68ba0899ab5 --- /dev/null +++ b/chromium/mojo/spy/ui/spy_shell_to_html @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# 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 os +import sys + +if __name__ == '__main__': + top_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + sys.path.append(top_dir) + from ui import spy_shell_to_html + sys.exit(spy_shell_to_html.Main(sys.argv)) diff --git a/chromium/mojo/spy/ui/spy_shell_to_html.py b/chromium/mojo/spy/ui/spy_shell_to_html.py new file mode 100644 index 00000000000..293b9fb0baf --- /dev/null +++ b/chromium/mojo/spy/ui/spy_shell_to_html.py @@ -0,0 +1,39 @@ +# 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 sys +import os +import optparse + +from ui import spy_project +from tvcm import generate + +def Main(args): + parser = optparse.OptionParser() + parser.add_option('--output-file', '-o') + options,args = parser.parse_args(args) + + if options.output_file: + ofile = open(options.output_file, 'w') + else: + ofile = sys.stdout + GenerateHTML(ofile) + if ofile != sys.stdout: + ofile.close() + +def GenerateHTML(ofile): + project = spy_project.SpyProject() + load_sequence = project.CalcLoadSequenceForModuleNames( + ['ui.spy_shell']) + bootstrap_js = """ + + document.addEventListener('DOMContentLoaded', function() { + document.body.appendChild(new ui.SpyShell('ws://127.0.0.1:42424')); + + }); +""" + bootstrap_script = generate.ExtraScript(text_content=bootstrap_js) + generate.GenerateStandaloneHTMLToFile( + ofile, load_sequence, + title='Mojo spy', + extra_scripts=[bootstrap_script]) diff --git a/chromium/mojo/spy/ui/spy_test.js b/chromium/mojo/spy/ui/spy_test.js new file mode 100644 index 00000000000..b70ecd9d63d --- /dev/null +++ b/chromium/mojo/spy/ui/spy_test.js @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +tvcm.require('ui.spy'); +tvcm.require('tvcm.event_target'); + +tvcm.unittest.testSuite('ui.spy_test', function() { + /** + * @constructor + */ + function FakeChannel() { + tvcm.EventTarget.call(this); + } + + FakeChannel.prototype = { + __proto__: tvcm.EventTarget.prototype, + + send: function(msg) { + }, + + dispatchMessage: function(msg) { + var event = new Event('message', false, false); + event.data = msg; + this.dispatchEvent(event); + } + }; + + test('basic', function() { + var channel = new FakeChannel(); + + var spy = new ui.Spy(); + spy.style.width = '600px'; + spy.style.height = '400px'; + spy.style.border = '1px solid black'; + this.addHTMLOutput(spy); + spy.channel = channel; + + channel.dispatchMessage({data: 'alo there'}); + + // Fake out echo reply + channel.send = function(msg) { + setTimeout(function() { + channel.dispatchMessage({data: {type: 'reply', msg: msg}}); + }, 10); + } + }); + +}); diff --git a/chromium/mojo/spy/ui/tvcm_stub.py b/chromium/mojo/spy/ui/tvcm_stub.py new file mode 100644 index 00000000000..8c7c0842dad --- /dev/null +++ b/chromium/mojo/spy/ui/tvcm_stub.py @@ -0,0 +1,18 @@ +# 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 os +import sys + +_CHROME_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), + '..', '..', '..')) + +# Bring in tvcm module for basic JS components capabilities. +sys.path.append(os.path.join(_CHROME_PATH, + 'third_party', 'trace-viewer', 'third_party', 'tvcm')) + +# Bring in trace_viewer module for the UI features that are part of the trace +# viewer. +sys.path.append(os.path.join(_CHROME_PATH, + 'third_party', 'trace-viewer')) diff --git a/chromium/mojo/spy/ui/ui_unittest.py b/chromium/mojo/spy/ui/ui_unittest.py new file mode 100644 index 00000000000..ffaffad6710 --- /dev/null +++ b/chromium/mojo/spy/ui/ui_unittest.py @@ -0,0 +1,15 @@ +# 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. + +from ui import spy_project +from tvcm import module_test_case + + +def load_tests(_, _2, _3): + project = spy_project.SpyProject() + suite = module_test_case.DiscoverTestsInModule( + project, + project.spy_path) + assert suite.countTestCases() > 0, 'Expected to find at least one test.' + return suite diff --git a/chromium/mojo/spy/websocket_server.cc b/chromium/mojo/spy/websocket_server.cc new file mode 100644 index 00000000000..649a135c13e --- /dev/null +++ b/chromium/mojo/spy/websocket_server.cc @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/spy/websocket_server.h" + +#include "base/bind.h" +#include "base/strings/stringprintf.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/server/http_server_request_info.h" +#include "net/server/http_server_response_info.h" +#include "net/socket/tcp_listen_socket.h" + +namespace spy { + +const int kNotConnected = -1; + +WebSocketServer::WebSocketServer(int port) + : port_(port), connection_id_(kNotConnected) { +} + +WebSocketServer::~WebSocketServer() { +} + +bool WebSocketServer::Start() { + net::TCPListenSocketFactory factory("0.0.0.0", port_); + server_ = new net::HttpServer(factory, this); + net::IPEndPoint address; + int error = server_->GetLocalAddress(&address); + port_ = address.port(); + return (error == net::OK); +} + +void WebSocketServer::OnHttpRequest( + int connection_id, + const net::HttpServerRequestInfo& info) { + server_->Send500(connection_id, "websockets protocol only"); +} + +void WebSocketServer::OnWebSocketRequest( + int connection_id, + const net::HttpServerRequestInfo& info) { + if (connection_id_ != kNotConnected) { + // Reject connection since we already have our client. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Close, server_, connection_id)); + return; + } + // Accept the connection. + server_->AcceptWebSocket(connection_id, info); + connection_id_ = connection_id; +} + +void WebSocketServer::OnWebSocketMessage( + int connection_id, + const std::string& data) { + // TODO(cpu): remove this test code soon. + if (data == "\"hello\"") + server_->SendOverWebSocket(connection_id, "\"hi there!\""); +} + +void WebSocketServer::OnClose( + int connection_id) { + if (connection_id == connection_id_) + connection_id_ = kNotConnected; +} + +} // namespace spy diff --git a/chromium/mojo/spy/websocket_server.h b/chromium/mojo/spy/websocket_server.h new file mode 100644 index 00000000000..84830b8b8da --- /dev/null +++ b/chromium/mojo/spy/websocket_server.h @@ -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. + +#ifndef MOJO_SPY_WEBSOCKET_SERVER_H_ +#define MOJO_SPY_WEBSOCKET_SERVER_H_ + +#include "net/server/http_server.h" + +namespace spy { + +class WebSocketServer : public net::HttpServer::Delegate { + public: + // Pass 0 in |port| to listen in one available port. + explicit WebSocketServer(int port); + virtual ~WebSocketServer(); + // Begin accepting HTTP requests. Must be called from an IO MessageLoop. + bool Start(); + // Returns the listening port, useful if 0 was passed to the contructor. + int port() const { return port_; } + + protected: + // Overridden from net::HttpServer::Delegate. + virtual void OnHttpRequest( + int connection_id, + const net::HttpServerRequestInfo& info) OVERRIDE; + virtual void OnWebSocketRequest( + int connection_id, + const net::HttpServerRequestInfo& info) OVERRIDE; + virtual void OnWebSocketMessage( + int connection_id, + const std::string& data) OVERRIDE; + virtual void OnClose(int connection_id) OVERRIDE; + + private: + int port_; + int connection_id_; + scoped_refptr<net::HttpServer> server_; + DISALLOW_COPY_AND_ASSIGN(WebSocketServer); +}; + +} // namespace spy + +#endif // MOJO_SPY_WEBSOCKET_SERVER_H_ diff --git a/chromium/mojo/system/BUILD.gn b/chromium/mojo/system/BUILD.gn new file mode 100644 index 00000000000..2ad4679d4c8 --- /dev/null +++ b/chromium/mojo/system/BUILD.gn @@ -0,0 +1,109 @@ +# 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. + +config("system_config") { + defines = [ + # Ensures that dependent projects import the core functions on Windows. + "MOJO_USE_SYSTEM_IMPL", + ] +} + +component("system") { + output_name = "mojo_system_impl" + + sources = [ + # Should there be a separate "embedder" target? + "../embedder/embedder.cc", + "../embedder/embedder.h", + "../embedder/platform_channel_pair.cc", + "../embedder/platform_channel_pair.h", + "../embedder/platform_channel_pair_posix.cc", + "../embedder/platform_channel_pair_win.cc", + "../embedder/platform_channel_utils_posix.cc", + "../embedder/platform_channel_utils_posix.h", + "../embedder/platform_handle.cc", + "../embedder/platform_handle.h", + "../embedder/platform_handle_utils.h", + "../embedder/platform_handle_utils_posix.cc", + "../embedder/platform_handle_utils_win.cc", + "../embedder/platform_handle_vector.h", + "../embedder/scoped_platform_handle.h", + "channel.cc", + "channel.h", + "constants.h", + "core.cc", + "core.h", + "data_pipe.cc", + "data_pipe.h", + "data_pipe_consumer_dispatcher.cc", + "data_pipe_consumer_dispatcher.h", + "data_pipe_producer_dispatcher.cc", + "data_pipe_producer_dispatcher.h", + "dispatcher.cc", + "dispatcher.h", + "entrypoints.cc", + "handle_signals_state.h", + "handle_table.cc", + "handle_table.h", + "local_data_pipe.cc", + "local_data_pipe.h", + "local_message_pipe_endpoint.cc", + "local_message_pipe_endpoint.h", + "mapping_table.cc", + "mapping_table.h", + "memory.cc", + "memory.h", + "message_in_transit.cc", + "message_in_transit.h", + "message_in_transit_queue.cc", + "message_in_transit_queue.h", + "message_pipe.cc", + "message_pipe.h", + "message_pipe_dispatcher.cc", + "message_pipe_dispatcher.h", + "message_pipe_endpoint.cc", + "message_pipe_endpoint.h", + "options_validation.h", + "platform_handle_dispatcher.cc", + "platform_handle_dispatcher.h", + "proxy_message_pipe_endpoint.cc", + "proxy_message_pipe_endpoint.h", + "raw_channel.cc", + "raw_channel.h", + "raw_channel_posix.cc", + "raw_channel_win.cc", + "raw_shared_buffer.cc", + "raw_shared_buffer.h", + "raw_shared_buffer_posix.cc", + "raw_shared_buffer_win.cc", + "shared_buffer_dispatcher.cc", + "shared_buffer_dispatcher.h", + "simple_dispatcher.cc", + "simple_dispatcher.h", + "transport_data.cc", + "transport_data.h", + "waiter.cc", + "waiter.h", + "waiter_list.cc", + "waiter_list.h", + # Test-only code: + # TODO(vtl): It's a little unfortunate that these end up in the same + # component as non-test-only code. In the static build, this code should + # hopefully be dead-stripped. + "../embedder/test_embedder.cc", + "../embedder/test_embedder.h", + ] + + defines = [ + "MOJO_SYSTEM_IMPL_IMPLEMENTATION", + "MOJO_SYSTEM_IMPLEMENTATION", + ] + + all_dependent_configs = [ ":system_config" ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + ] +} diff --git a/chromium/mojo/system/channel.cc b/chromium/mojo/system/channel.cc new file mode 100644 index 00000000000..a311695b233 --- /dev/null +++ b/chromium/mojo/system/channel.cc @@ -0,0 +1,505 @@ +// 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 "mojo/system/channel.h" + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "mojo/embedder/platform_handle_vector.h" +#include "mojo/system/message_pipe_endpoint.h" +#include "mojo/system/transport_data.h" + +namespace mojo { +namespace system { + +COMPILE_ASSERT(Channel::kBootstrapEndpointId != + MessageInTransit::kInvalidEndpointId, + kBootstrapEndpointId_is_invalid); + +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::EndpointId + Channel::kBootstrapEndpointId; + +Channel::EndpointInfo::EndpointInfo() + : state(STATE_NORMAL), + port() { +} + +Channel::EndpointInfo::EndpointInfo(scoped_refptr<MessagePipe> message_pipe, + unsigned port) + : state(STATE_NORMAL), + message_pipe(message_pipe), + port(port) { +} + +Channel::EndpointInfo::~EndpointInfo() { +} + +Channel::Channel() + : is_running_(false), + next_local_id_(kBootstrapEndpointId) { +} + +bool Channel::Init(scoped_ptr<RawChannel> raw_channel) { + DCHECK(creation_thread_checker_.CalledOnValidThread()); + DCHECK(raw_channel); + + // No need to take |lock_|, since this must be called before this object + // becomes thread-safe. + DCHECK(!is_running_no_lock()); + raw_channel_ = raw_channel.Pass(); + + if (!raw_channel_->Init(this)) { + raw_channel_.reset(); + return false; + } + + is_running_ = true; + return true; +} + +void Channel::Shutdown() { + DCHECK(creation_thread_checker_.CalledOnValidThread()); + + IdToEndpointInfoMap to_destroy; + { + base::AutoLock locker(lock_); + if (!is_running_no_lock()) + return; + + // Note: Don't reset |raw_channel_|, in case we're being called from within + // |OnReadMessage()| or |OnFatalError()|. + raw_channel_->Shutdown(); + is_running_ = false; + + // We need to deal with it outside the lock. + std::swap(to_destroy, local_id_to_endpoint_info_map_); + } + + size_t num_live = 0; + size_t num_zombies = 0; + for (IdToEndpointInfoMap::iterator it = to_destroy.begin(); + it != to_destroy.end(); + ++it) { + if (it->second.state == EndpointInfo::STATE_NORMAL) { + it->second.message_pipe->OnRemove(it->second.port); + num_live++; + } else { + DCHECK(!it->second.message_pipe); + num_zombies++; + } + } + DVLOG_IF(2, num_live || num_zombies) + << "Shut down Channel with " << num_live << " live endpoints and " + << num_zombies << " zombies"; +} + +MessageInTransit::EndpointId Channel::AttachMessagePipeEndpoint( + scoped_refptr<MessagePipe> message_pipe, + unsigned port) { + DCHECK(message_pipe); + DCHECK(port == 0 || port == 1); + + MessageInTransit::EndpointId local_id; + { + base::AutoLock locker(lock_); + + while (next_local_id_ == MessageInTransit::kInvalidEndpointId || + local_id_to_endpoint_info_map_.find(next_local_id_) != + local_id_to_endpoint_info_map_.end()) + next_local_id_++; + + local_id = next_local_id_; + next_local_id_++; + + // TODO(vtl): Use emplace when we move to C++11 unordered_maps. (It'll avoid + // some expensive reference count increment/decrements.) Once this is done, + // we should be able to delete |EndpointInfo|'s default constructor. + local_id_to_endpoint_info_map_[local_id] = EndpointInfo(message_pipe, port); + } + + // This might fail if that port got an |OnPeerClose()| before attaching. + if (message_pipe->Attach(port, scoped_refptr<Channel>(this), local_id)) + return local_id; + + // Note: If it failed, quite possibly the endpoint info was removed from that + // map (there's a race between us adding it to the map above and calling + // |Attach()|). And even if an entry exists for |local_id|, we need to check + // that it's the one we added (and not some other one that was added since). + { + base::AutoLock locker(lock_); + IdToEndpointInfoMap::iterator it = + local_id_to_endpoint_info_map_.find(local_id); + if (it != local_id_to_endpoint_info_map_.end() && + it->second.message_pipe.get() == message_pipe.get() && + it->second.port == port) { + DCHECK_EQ(it->second.state, EndpointInfo::STATE_NORMAL); + // TODO(vtl): FIXME -- This is wrong. We need to specify (to + // |AttachMessagePipeEndpoint()| who's going to be responsible for calling + // |RunMessagePipeEndpoint()| ("us", or the remote by sending us a + // |kSubtypeChannelRunMessagePipeEndpoint|). If the remote is going to + // run, then we'll get messages to an "invalid" local ID (for running, for + // removal). + local_id_to_endpoint_info_map_.erase(it); + } + } + return MessageInTransit::kInvalidEndpointId; +} + +bool Channel::RunMessagePipeEndpoint(MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id) { + EndpointInfo endpoint_info; + { + base::AutoLock locker(lock_); + + IdToEndpointInfoMap::const_iterator it = + local_id_to_endpoint_info_map_.find(local_id); + if (it == local_id_to_endpoint_info_map_.end()) + return false; + endpoint_info = it->second; + } + + // Assume that this was in response to |kSubtypeChannelRunMessagePipeEndpoint| + // and ignore it. + if (endpoint_info.state != EndpointInfo::STATE_NORMAL) { + DVLOG(2) << "Ignoring run message pipe endpoint for zombie endpoint " + "(local ID " << local_id << ", remote ID " << remote_id << ")"; + return true; + } + + // TODO(vtl): FIXME -- We need to handle the case that message pipe is already + // running when we're here due to |kSubtypeChannelRunMessagePipeEndpoint|). + endpoint_info.message_pipe->Run(endpoint_info.port, remote_id); + return true; +} + +void Channel::RunRemoteMessagePipeEndpoint( + MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id) { +#if DCHECK_IS_ON + { + base::AutoLock locker(lock_); + DCHECK(local_id_to_endpoint_info_map_.find(local_id) != + local_id_to_endpoint_info_map_.end()); + } +#endif + + if (!SendControlMessage( + MessageInTransit::kSubtypeChannelRunMessagePipeEndpoint, + local_id, remote_id)) { + HandleLocalError(base::StringPrintf( + "Failed to send message to run remote message pipe endpoint (local ID " + "%u, remote ID %u)", + static_cast<unsigned>(local_id), static_cast<unsigned>(remote_id))); + } +} + +bool Channel::WriteMessage(scoped_ptr<MessageInTransit> message) { + base::AutoLock locker(lock_); + if (!is_running_no_lock()) { + // TODO(vtl): I think this is probably not an error condition, but I should + // think about it (and the shutdown sequence) more carefully. + LOG(WARNING) << "WriteMessage() after shutdown"; + return false; + } + + return raw_channel_->WriteMessage(message.Pass()); +} + +bool Channel::IsWriteBufferEmpty() { + base::AutoLock locker(lock_); + if (!is_running_no_lock()) + return true; + return raw_channel_->IsWriteBufferEmpty(); +} + +void Channel::DetachMessagePipeEndpoint( + MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id) { + DCHECK_NE(local_id, MessageInTransit::kInvalidEndpointId); + + bool should_send_remove_message = false; + { + base::AutoLock locker_(lock_); + if (!is_running_no_lock()) + return; + + IdToEndpointInfoMap::iterator it = + local_id_to_endpoint_info_map_.find(local_id); + DCHECK(it != local_id_to_endpoint_info_map_.end()); + + switch (it->second.state) { + case EndpointInfo::STATE_NORMAL: + it->second.state = EndpointInfo::STATE_WAIT_REMOTE_REMOVE_ACK; + it->second.message_pipe = NULL; + should_send_remove_message = + (remote_id != MessageInTransit::kInvalidEndpointId); + break; + case EndpointInfo::STATE_WAIT_LOCAL_DETACH: + local_id_to_endpoint_info_map_.erase(it); + break; + case EndpointInfo::STATE_WAIT_REMOTE_REMOVE_ACK: + NOTREACHED(); + break; + case EndpointInfo::STATE_WAIT_LOCAL_DETACH_AND_REMOTE_REMOVE_ACK: + it->second.state = EndpointInfo::STATE_WAIT_REMOTE_REMOVE_ACK; + break; + } + } + if (!should_send_remove_message) + return; + + if (!SendControlMessage( + MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpoint, + local_id, remote_id)) { + HandleLocalError(base::StringPrintf( + "Failed to send message to remove remote message pipe endpoint (local " + "ID %u, remote ID %u)", + static_cast<unsigned>(local_id), static_cast<unsigned>(remote_id))); + } +} + +size_t Channel::GetSerializedPlatformHandleSize() const { + return raw_channel_->GetSerializedPlatformHandleSize(); +} + +Channel::~Channel() { + // The channel should have been shut down first. + DCHECK(!is_running_no_lock()); +} + +void Channel::OnReadMessage( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) { + switch (message_view.type()) { + case MessageInTransit::kTypeMessagePipeEndpoint: + case MessageInTransit::kTypeMessagePipe: + OnReadMessageForDownstream(message_view, platform_handles.Pass()); + break; + case MessageInTransit::kTypeChannel: + OnReadMessageForChannel(message_view, platform_handles.Pass()); + break; + default: + HandleRemoteError(base::StringPrintf( + "Received message of invalid type %u", + static_cast<unsigned>(message_view.type()))); + break; + } +} + +void Channel::OnFatalError(FatalError fatal_error) { + switch (fatal_error) { + case FATAL_ERROR_READ: + // Most read errors aren't notable: they just reflect that the other side + // tore down the channel. + DVLOG(1) << "RawChannel fatal error (read)"; + break; + case FATAL_ERROR_WRITE: + // Write errors are slightly notable: they probably shouldn't happen under + // normal operation (but maybe the other side crashed). + LOG(WARNING) << "RawChannel fatal error (write)"; + break; + } + Shutdown(); +} + +void Channel::OnReadMessageForDownstream( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) { + DCHECK(message_view.type() == MessageInTransit::kTypeMessagePipeEndpoint || + message_view.type() == MessageInTransit::kTypeMessagePipe); + + MessageInTransit::EndpointId local_id = message_view.destination_id(); + if (local_id == MessageInTransit::kInvalidEndpointId) { + HandleRemoteError("Received message with no destination ID"); + return; + } + + EndpointInfo endpoint_info; + { + base::AutoLock locker(lock_); + + // Since we own |raw_channel_|, and this method and |Shutdown()| should only + // be called from the creation thread, |raw_channel_| should never be null + // here. + DCHECK(is_running_no_lock()); + + IdToEndpointInfoMap::const_iterator it = + local_id_to_endpoint_info_map_.find(local_id); + if (it == local_id_to_endpoint_info_map_.end()) { + HandleRemoteError(base::StringPrintf( + "Received a message for nonexistent local destination ID %u", + static_cast<unsigned>(local_id))); + // This is strongly indicative of some problem. However, it's not a fatal + // error, since it may indicate a bug (or hostile) remote process. Don't + // die even for Debug builds, since handling this properly needs to be + // tested (TODO(vtl)). + DLOG(ERROR) << "This should not happen under normal operation."; + return; + } + endpoint_info = it->second; + } + + // Ignore messages for zombie endpoints (not an error). + if (endpoint_info.state != EndpointInfo::STATE_NORMAL) { + DVLOG(2) << "Ignoring downstream message for zombie endpoint (local ID = " + << local_id << ", remote ID = " << message_view.source_id() << ")"; + return; + } + + // We need to duplicate the message (data), because |EnqueueMessage()| will + // take ownership of it. + scoped_ptr<MessageInTransit> message(new MessageInTransit(message_view)); + if (message_view.transport_data_buffer_size() > 0) { + DCHECK(message_view.transport_data_buffer()); + message->SetDispatchers( + TransportData::DeserializeDispatchers( + message_view.transport_data_buffer(), + message_view.transport_data_buffer_size(), + platform_handles.Pass(), + this)); + } + MojoResult result = endpoint_info.message_pipe->EnqueueMessage( + MessagePipe::GetPeerPort(endpoint_info.port), message.Pass()); + if (result != MOJO_RESULT_OK) { + // TODO(vtl): This might be a "non-error", e.g., if the destination endpoint + // has been closed (in an unavoidable race). This might also be a "remote" + // error, e.g., if the remote side is sending invalid control messages (to + // the message pipe). + HandleLocalError(base::StringPrintf( + "Failed to enqueue message to local ID %u (result %d)", + static_cast<unsigned>(local_id), static_cast<int>(result))); + return; + } +} + +void Channel::OnReadMessageForChannel( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) { + DCHECK_EQ(message_view.type(), MessageInTransit::kTypeChannel); + + // Currently, no channel messages take platform handles. + if (platform_handles) { + HandleRemoteError( + "Received invalid channel message (has platform handles)"); + NOTREACHED(); + return; + } + + switch (message_view.subtype()) { + case MessageInTransit::kSubtypeChannelRunMessagePipeEndpoint: + DVLOG(2) << "Handling channel message to run message pipe (local ID " + << message_view.destination_id() << ", remote ID " + << message_view.source_id() << ")"; + if (!RunMessagePipeEndpoint(message_view.destination_id(), + message_view.source_id())) { + HandleRemoteError( + "Received invalid channel message to run message pipe"); + } + break; + case MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpoint: + DVLOG(2) << "Handling channel message to remove message pipe (local ID " + << message_view.destination_id() << ", remote ID " + << message_view.source_id() << ")"; + if (!RemoveMessagePipeEndpoint(message_view.destination_id(), + message_view.source_id())) { + HandleRemoteError( + "Received invalid channel message to remove message pipe"); + } + break; + case MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpointAck: + DVLOG(2) << "Handling channel message to ack remove message pipe (local " + "ID " + << message_view.destination_id() << ", remote ID " + << message_view.source_id() << ")"; + if (!RemoveMessagePipeEndpoint(message_view.destination_id(), + message_view.source_id())) { + HandleRemoteError( + "Received invalid channel message to ack remove message pipe"); + } + break; + default: + HandleRemoteError("Received invalid channel message"); + NOTREACHED(); + break; + } +} + +bool Channel::RemoveMessagePipeEndpoint( + MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id) { + EndpointInfo endpoint_info; + { + base::AutoLock locker(lock_); + + IdToEndpointInfoMap::iterator it = + local_id_to_endpoint_info_map_.find(local_id); + if (it == local_id_to_endpoint_info_map_.end()) { + DVLOG(2) << "Remove message pipe error: not found"; + return false; + } + + // If it's waiting for the remove ack, just do it and return. + if (it->second.state == EndpointInfo::STATE_WAIT_REMOTE_REMOVE_ACK) { + local_id_to_endpoint_info_map_.erase(it); + return true; + } + + if (it->second.state != EndpointInfo::STATE_NORMAL) { + DVLOG(2) << "Remove message pipe error: wrong state"; + return false; + } + + it->second.state = EndpointInfo::STATE_WAIT_LOCAL_DETACH; + endpoint_info = it->second; + it->second.message_pipe = NULL; + } + + if (!SendControlMessage( + MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpointAck, + local_id, remote_id)) { + HandleLocalError(base::StringPrintf( + "Failed to send message to remove remote message pipe endpoint ack " + "(local ID %u, remote ID %u)", + static_cast<unsigned>(local_id), static_cast<unsigned>(remote_id))); + } + + endpoint_info.message_pipe->OnRemove(endpoint_info.port); + + return true; +} + +bool Channel::SendControlMessage(MessageInTransit::Subtype subtype, + MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id) { + DVLOG(2) << "Sending channel control message: subtype " << subtype + << ", local ID " << local_id << ", remote ID " << remote_id; + scoped_ptr<MessageInTransit> message(new MessageInTransit( + MessageInTransit::kTypeChannel, subtype, 0, NULL)); + message->set_source_id(local_id); + message->set_destination_id(remote_id); + return WriteMessage(message.Pass()); +} + +void Channel::HandleRemoteError(const base::StringPiece& error_message) { + // TODO(vtl): Is this how we really want to handle this? Probably we want to + // terminate the connection, since it's spewing invalid stuff. + LOG(WARNING) << error_message; +} + +void Channel::HandleLocalError(const base::StringPiece& error_message) { + // TODO(vtl): Is this how we really want to handle this? + // Sometimes we'll want to propagate the error back to the message pipe + // (endpoint), and notify it that the remote is (effectively) closed. + // Sometimes we'll want to kill the channel (and notify all the endpoints that + // their remotes are dead. + LOG(WARNING) << error_message; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/channel.h b/chromium/mojo/system/channel.h new file mode 100644 index 00000000000..859e242ec94 --- /dev/null +++ b/chromium/mojo/system/channel.h @@ -0,0 +1,202 @@ +// 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 MOJO_SYSTEM_CHANNEL_H_ +#define MOJO_SYSTEM_CHANNEL_H_ + +#include <stdint.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/raw_channel.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// This class is mostly thread-safe. It must be created on an I/O thread. +// |Init()| must be called on that same thread before it becomes thread-safe (in +// particular, before references are given to any other thread) and |Shutdown()| +// must be called on that same thread before destruction. Its public methods are +// otherwise thread-safe. It may be destroyed on any thread, in the sense that +// the last reference to it may be released on any thread, with the proviso that +// |Shutdown()| must have been called first (so the pattern is that a "main" +// reference is kept on its creation thread and is released after |Shutdown()| +// is called, but other threads may have temporarily "dangling" references). +// +// Note that |MessagePipe| calls into |Channel| and the former's |lock_| must be +// acquired before the latter's. When |Channel| wants to call into a +// |MessagePipe|, it must obtain a reference to the |MessagePipe| (from +// |local_id_to_endpoint_info_map_|) under |Channel::lock_| and then release the +// lock. +// +// Also, care must be taken with respect to references: While a |Channel| has +// references to |MessagePipe|s, |MessagePipe|s (via |ProxyMessagePipeEndpoint|) +// may also have references to |Channel|s. These references are set up by +// calling |AttachMessagePipeEndpoint()|. The reference to |MessagePipe| owned +// by |Channel| must be removed by calling |DetachMessagePipeEndpoint()| (which +// is done by |MessagePipe|/|ProxyMessagePipeEndpoint|, which simultaneously +// removes its reference to |Channel|). +class MOJO_SYSTEM_IMPL_EXPORT Channel + : public base::RefCountedThreadSafe<Channel>, + public RawChannel::Delegate { + public: + // The first message pipe endpoint attached will have this as its local ID. + static const MessageInTransit::EndpointId kBootstrapEndpointId = 1; + + Channel(); + + // This must be called on the creation thread before any other methods are + // called, and before references to this object are given to any other + // threads. |raw_channel| should be uninitialized. Returns true on success. On + // failure, no other methods should be called (including |Shutdown()|). + bool Init(scoped_ptr<RawChannel> raw_channel); + + // This must be called on the creation thread before destruction (which can + // happen on any thread). + void Shutdown(); + + // Attaches the given message pipe/port's endpoint (which must be a + // |ProxyMessagePipeEndpoint|) to this channel. This assigns it a local ID, + // which it returns. The first message pipe endpoint attached will always have + // |kBootstrapEndpointId| as its local ID. (For bootstrapping, this occurs on + // both sides, so one should use |kBootstrapEndpointId| for the remote ID for + // the first message pipe across a channel.) Returns |kInvalidEndpointId| on + // failure. + // TODO(vtl): Maybe limit the number of attached message pipes. + MessageInTransit::EndpointId AttachMessagePipeEndpoint( + scoped_refptr<MessagePipe> message_pipe, + unsigned port); + + // Runs the message pipe with the given |local_id| (previously attached), with + // the given |remote_id| (negotiated using some other means, e.g., over an + // existing message pipe; see comments above for the bootstrap case). Returns + // false on failure, in particular if no message pipe with |local_id| is + // attached. + bool RunMessagePipeEndpoint(MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id); + + // Tells the other side of the channel to run a message pipe endpoint (which + // must already be attached); |local_id| and |remote_id| are relative to this + // channel (i.e., |local_id| is the other side's remote ID and |remote_id| is + // its local ID). + // TODO(vtl): Maybe we should just have a flag argument to + // |RunMessagePipeEndpoint()| that tells it to do this. + void RunRemoteMessagePipeEndpoint(MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id); + + // This forwards |message| verbatim to |raw_channel_|. + bool WriteMessage(scoped_ptr<MessageInTransit> message); + + // See |RawChannel::IsWriteBufferEmpty()|. + // TODO(vtl): Maybe we shouldn't expose this, and instead have a + // |FlushWriteBufferAndShutdown()| or something like that. + bool IsWriteBufferEmpty(); + + // This removes the message pipe/port's endpoint (with the given local ID and + // given remote ID, which should be |kInvalidEndpointId| if not yet running), + // returned by |AttachMessagePipeEndpoint()| from this channel. After this is + // called, |local_id| may be reused for another message pipe. + void DetachMessagePipeEndpoint(MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id); + + // See |RawChannel::GetSerializedPlatformHandleSize()|. + size_t GetSerializedPlatformHandleSize() const; + + private: + struct EndpointInfo { + enum State { + // Attached, possibly running or not. + STATE_NORMAL, + // "Zombie" states: + // Waiting for |DetachMessagePipeEndpoint()| before removing. + STATE_WAIT_LOCAL_DETACH, + // Waiting for a |kSubtypeChannelRemoveMessagePipeEndpointAck| before + // removing. + STATE_WAIT_REMOTE_REMOVE_ACK, + // Waiting for both of the above conditions before removing. + STATE_WAIT_LOCAL_DETACH_AND_REMOTE_REMOVE_ACK, + }; + + EndpointInfo(); + EndpointInfo(scoped_refptr<MessagePipe> message_pipe, unsigned port); + ~EndpointInfo(); + + State state; + scoped_refptr<MessagePipe> message_pipe; + unsigned port; + }; + + friend class base::RefCountedThreadSafe<Channel>; + virtual ~Channel(); + + // |RawChannel::Delegate| implementation: + virtual void OnReadMessage( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) OVERRIDE; + virtual void OnFatalError(FatalError fatal_error) OVERRIDE; + + // Helpers for |OnReadMessage|: + void OnReadMessageForDownstream( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles); + void OnReadMessageForChannel( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles); + + // Removes the message pipe endpoint with the given local ID, which must exist + // and be a zombie, and given remote ID. Returns false on failure, in + // particular if no message pipe with |local_id| is attached. + bool RemoveMessagePipeEndpoint(MessageInTransit::EndpointId local_id, + MessageInTransit::EndpointId remote_id); + + // Handles errors (e.g., invalid messages) from the remote side. + void HandleRemoteError(const base::StringPiece& error_message); + // Handles internal errors/failures from the local side. + void HandleLocalError(const base::StringPiece& error_message); + + // Helper to send channel control messages. Returns true on success. Should be + // called *without* |lock_| held. + bool SendControlMessage(MessageInTransit::Subtype subtype, + MessageInTransit::EndpointId source_id, + MessageInTransit::EndpointId destination_id); + + bool is_running_no_lock() const { return is_running_; } + + base::ThreadChecker creation_thread_checker_; + + // Note: |MessagePipe|s MUST NOT be used under |lock_|. I.e., |lock_| can only + // be acquired after |MessagePipe::lock_|, never before. Thus to call into a + // |MessagePipe|, a reference should be acquired from + // |local_id_to_endpoint_info_map_| under |lock_| (e.g., by copying the + // |EndpointInfo|) and then the lock released. + base::Lock lock_; // Protects the members below. + + scoped_ptr<RawChannel> raw_channel_; + bool is_running_; + + typedef base::hash_map<MessageInTransit::EndpointId, EndpointInfo> + IdToEndpointInfoMap; + IdToEndpointInfoMap local_id_to_endpoint_info_map_; + // The next local ID to try (when allocating new local IDs). Note: It should + // be checked for existence before use. + MessageInTransit::EndpointId next_local_id_; + + DISALLOW_COPY_AND_ASSIGN(Channel); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_CHANNEL_H_ diff --git a/chromium/mojo/system/channel_unittest.cc b/chromium/mojo/system/channel_unittest.cc new file mode 100644 index 00000000000..1c40d024796 --- /dev/null +++ b/chromium/mojo/system/channel_unittest.cc @@ -0,0 +1,329 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/channel.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/system/local_message_pipe_endpoint.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/proxy_message_pipe_endpoint.h" +#include "mojo/system/raw_channel.h" +#include "mojo/system/test_utils.h" +#include "mojo/system/waiter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +enum Tristate { + TRISTATE_UNKNOWN = -1, + TRISTATE_FALSE = 0, + TRISTATE_TRUE = 1 +}; + +Tristate BoolToTristate(bool b) { + return b ? TRISTATE_TRUE : TRISTATE_FALSE; +} + +class ChannelTest : public testing::Test { + public: + ChannelTest() + : io_thread_(test::TestIOThread::kAutoStart), + init_result_(TRISTATE_UNKNOWN) {} + virtual ~ChannelTest() {} + + virtual void SetUp() OVERRIDE { + io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::SetUpOnIOThread, base::Unretained(this))); + } + + void CreateChannelOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + channel_ = new Channel(); + } + + void InitChannelOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + CHECK(raw_channel_); + CHECK(channel_); + CHECK_EQ(init_result_, TRISTATE_UNKNOWN); + + init_result_ = BoolToTristate(channel_->Init(raw_channel_.Pass())); + } + + void ShutdownChannelOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + CHECK(channel_); + channel_->Shutdown(); + } + + test::TestIOThread* io_thread() { return &io_thread_; } + RawChannel* raw_channel() { return raw_channel_.get(); } + scoped_ptr<RawChannel>* mutable_raw_channel() { return &raw_channel_; } + Channel* channel() { return channel_.get(); } + scoped_refptr<Channel>* mutable_channel() { return &channel_; } + Tristate init_result() const { return init_result_; } + + private: + void SetUpOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + embedder::PlatformChannelPair channel_pair; + raw_channel_ = RawChannel::Create(channel_pair.PassServerHandle()).Pass(); + other_platform_handle_ = channel_pair.PassClientHandle(); + } + + test::TestIOThread io_thread_; + scoped_ptr<RawChannel> raw_channel_; + embedder::ScopedPlatformHandle other_platform_handle_; + scoped_refptr<Channel> channel_; + + Tristate init_result_; + + DISALLOW_COPY_AND_ASSIGN(ChannelTest); +}; + +// ChannelTest.InitShutdown ---------------------------------------------------- + +TEST_F(ChannelTest, InitShutdown) { + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::CreateChannelOnIOThread, + base::Unretained(this))); + ASSERT_TRUE(channel()); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::InitChannelOnIOThread, + base::Unretained(this))); + EXPECT_EQ(TRISTATE_TRUE, init_result()); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::ShutdownChannelOnIOThread, + base::Unretained(this))); + + // Okay to destroy |Channel| on not-the-I/O-thread. + EXPECT_TRUE(channel()->HasOneRef()); + *mutable_channel() = NULL; +} + +// ChannelTest.InitFails ------------------------------------------------------- + +class MockRawChannelOnInitFails : public RawChannel { + public: + MockRawChannelOnInitFails() : on_init_called_(false) {} + virtual ~MockRawChannelOnInitFails() {} + + // |RawChannel| public methods: + virtual size_t GetSerializedPlatformHandleSize() const OVERRIDE { + return 0; + } + + private: + // |RawChannel| protected methods: + virtual IOResult Read(size_t*) OVERRIDE { + CHECK(false); + return IO_FAILED; + } + virtual IOResult ScheduleRead() OVERRIDE { + CHECK(false); + return IO_FAILED; + } + virtual embedder::ScopedPlatformHandleVectorPtr GetReadPlatformHandles( + size_t, const void*) OVERRIDE { + CHECK(false); + return embedder::ScopedPlatformHandleVectorPtr(); + } + virtual IOResult WriteNoLock(size_t*, size_t*) OVERRIDE { + CHECK(false); + return IO_FAILED; + } + virtual IOResult ScheduleWriteNoLock() OVERRIDE { + CHECK(false); + return IO_FAILED; + } + virtual bool OnInit() OVERRIDE { + EXPECT_FALSE(on_init_called_); + on_init_called_ = true; + return false; + } + virtual void OnShutdownNoLock(scoped_ptr<ReadBuffer>, + scoped_ptr<WriteBuffer>) OVERRIDE { + CHECK(false); + } + + bool on_init_called_; + + DISALLOW_COPY_AND_ASSIGN(MockRawChannelOnInitFails); +}; + +TEST_F(ChannelTest, InitFails) { + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::CreateChannelOnIOThread, + base::Unretained(this))); + ASSERT_TRUE(channel()); + + ASSERT_TRUE(raw_channel()); + mutable_raw_channel()->reset(new MockRawChannelOnInitFails()); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::InitChannelOnIOThread, + base::Unretained(this))); + EXPECT_EQ(TRISTATE_FALSE, init_result()); + + // Should destroy |Channel| with no |Shutdown()| (on not-the-I/O-thread). + EXPECT_TRUE(channel()->HasOneRef()); + *mutable_channel() = NULL; +} + +// ChannelTest.CloseBeforeRun -------------------------------------------------- + +TEST_F(ChannelTest, CloseBeforeRun) { + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::CreateChannelOnIOThread, + base::Unretained(this))); + ASSERT_TRUE(channel()); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::InitChannelOnIOThread, + base::Unretained(this))); + EXPECT_EQ(TRISTATE_TRUE, init_result()); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + + MessageInTransit::EndpointId local_id = + channel()->AttachMessagePipeEndpoint(mp, 1); + EXPECT_EQ(Channel::kBootstrapEndpointId, local_id); + + mp->Close(0); + + // TODO(vtl): Currently, the |Close()| above won't detach (since it thinks + // we're still expecting a "run" message from the other side), so the + // |RunMessagePipeEndpoint()| below will return true. We need to refactor + // |AttachMessagePipeEndpoint()| to indicate whether |Run...()| will + // necessarily be called or not. (Then, in the case that it may not be called, + // this will return false.) + EXPECT_TRUE(channel()->RunMessagePipeEndpoint(local_id, + Channel::kBootstrapEndpointId)); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::ShutdownChannelOnIOThread, + base::Unretained(this))); + + EXPECT_TRUE(channel()->HasOneRef()); +} + +// ChannelTest.ShutdownAfterAttachAndRun --------------------------------------- + +TEST_F(ChannelTest, ShutdownAfterAttach) { + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::CreateChannelOnIOThread, + base::Unretained(this))); + ASSERT_TRUE(channel()); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::InitChannelOnIOThread, + base::Unretained(this))); + EXPECT_EQ(TRISTATE_TRUE, init_result()); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + + MessageInTransit::EndpointId local_id = + channel()->AttachMessagePipeEndpoint(mp, 1); + EXPECT_EQ(Channel::kBootstrapEndpointId, local_id); + + // TODO(vtl): Currently, we always "expect" a |RunMessagePipeEndpoint()| after + // an |AttachMessagePipeEndpoint()| (which is actually incorrect). We need to + // refactor |AttachMessagePipeEndpoint()| to indicate whether |Run...()| will + // necessarily be called or not. (Then, in the case that it may not be called, + // we should test a |Shutdown()| without the |Run...()|.) + EXPECT_TRUE(channel()->RunMessagePipeEndpoint(local_id, + Channel::kBootstrapEndpointId)); + + Waiter waiter; + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + // Don't wait for the shutdown to run ... + io_thread()->PostTask(FROM_HERE, + base::Bind(&ChannelTest::ShutdownChannelOnIOThread, + base::Unretained(this))); + + // ... since this |Wait()| should fail once the channel is shut down. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + waiter.Wait(MOJO_DEADLINE_INDEFINITE, NULL)); + mp->RemoveWaiter(0, &waiter); + + mp->Close(0); + + EXPECT_TRUE(channel()->HasOneRef()); +} + +// ChannelTest.WaitAfterAttachRunAndShutdown ----------------------------------- + +TEST_F(ChannelTest, WaitAfterAttachRunAndShutdown) { + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::CreateChannelOnIOThread, + base::Unretained(this))); + ASSERT_TRUE(channel()); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::InitChannelOnIOThread, + base::Unretained(this))); + EXPECT_EQ(TRISTATE_TRUE, init_result()); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + + MessageInTransit::EndpointId local_id = + channel()->AttachMessagePipeEndpoint(mp, 1); + EXPECT_EQ(Channel::kBootstrapEndpointId, local_id); + + EXPECT_TRUE(channel()->RunMessagePipeEndpoint(local_id, + Channel::kBootstrapEndpointId)); + + io_thread()->PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelTest::ShutdownChannelOnIOThread, + base::Unretained(this))); + + Waiter waiter; + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + mp->Close(0); + + EXPECT_TRUE(channel()->HasOneRef()); +} + +// TODO(vtl): More. ------------------------------------------------------------ + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/constants.h b/chromium/mojo/system/constants.h new file mode 100644 index 00000000000..b92eef10221 --- /dev/null +++ b/chromium/mojo/system/constants.h @@ -0,0 +1,48 @@ +// 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 MOJO_SYSTEM_CONSTANTS_H_ +#define MOJO_SYSTEM_CONSTANTS_H_ + +#include <stddef.h> + +namespace mojo { +namespace system { + +// Maximum number of open (Mojo) handles. +// TODO(vtl): This doesn't count "live" handles, some of which may live in +// messages. +const size_t kMaxHandleTableSize = 1000000; + +// Maximum number of active memory mappings. +const size_t kMaxMappingTableSize = 1000000; + +const size_t kMaxWaitManyNumHandles = kMaxHandleTableSize; + +const size_t kMaxMessageNumBytes = 4 * 1024 * 1024; + +const size_t kMaxMessageNumHandles = 10000; + +// Maximum capacity of a data pipe, in bytes. This value must fit into a +// |uint32_t|. +// WARNING: If you bump it closer to 2^32, you must audit all the code to check +// that we don't overflow (2^31 would definitely be risky; up to 2^30 is +// probably okay). +const size_t kMaxDataPipeCapacityBytes = 256 * 1024 * 1024; // 256 MB. + +const size_t kDefaultDataPipeCapacityBytes = 1024 * 1024; // 1 MB. + +// Alignment for the "start" of the data buffer used by data pipes. (The +// alignment of elements will depend on this and the element size.) +const size_t kDataPipeBufferAlignmentBytes = 16; + +// TODO(vtl): Set this hard limit appropriately (e.g., higher on 64-bit). (This +// will also entail some auditing to make sure I'm not messing up my checks +// anywhere.) +const size_t kMaxSharedMemoryNumBytes = 1024 * 1024 * 1024; // 1 GB. + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_CONSTANTS_H_ diff --git a/chromium/mojo/system/core.cc b/chromium/mojo/system/core.cc new file mode 100644 index 00000000000..d72198f1610 --- /dev/null +++ b/chromium/mojo/system/core.cc @@ -0,0 +1,561 @@ +// 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 "mojo/system/core.h" + +#include <vector> + +#include "base/logging.h" +#include "base/time/time.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/system/constants.h" +#include "mojo/system/data_pipe.h" +#include "mojo/system/data_pipe_consumer_dispatcher.h" +#include "mojo/system/data_pipe_producer_dispatcher.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/local_data_pipe.h" +#include "mojo/system/memory.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/message_pipe_dispatcher.h" +#include "mojo/system/raw_shared_buffer.h" +#include "mojo/system/shared_buffer_dispatcher.h" +#include "mojo/system/waiter.h" + +namespace mojo { +namespace system { + +// Implementation notes +// +// Mojo primitives are implemented by the singleton |Core| object. Most calls +// are for a "primary" handle (the first argument). |Core::GetDispatcher()| is +// used to look up a |Dispatcher| object for a given handle. That object +// implements most primitives for that object. The wait primitives are not +// attached to objects and are implemented by |Core| itself. +// +// Some objects have multiple handles associated to them, e.g., message pipes +// (which have two). In such a case, there is still a |Dispatcher| (e.g., +// |MessagePipeDispatcher|) for each handle, with each handle having a strong +// reference to the common "secondary" object (e.g., |MessagePipe|). This +// secondary object does NOT have any references to the |Dispatcher|s (even if +// it did, it wouldn't be able to do anything with them due to lock order +// requirements -- see below). +// +// Waiting is implemented by having the thread that wants to wait call the +// |Dispatcher|s for the handles that it wants to wait on with a |Waiter| +// object; this |Waiter| object may be created on the stack of that thread or be +// kept in thread local storage for that thread (TODO(vtl): future improvement). +// The |Dispatcher| then adds the |Waiter| to a |WaiterList| that's either owned +// by that |Dispatcher| (see |SimpleDispatcher|) or by a secondary object (e.g., +// |MessagePipe|). To signal/wake a |Waiter|, the object in question -- either a +// |SimpleDispatcher| or a secondary object -- talks to its |WaiterList|. + +// Thread-safety notes +// +// Mojo primitives calls are thread-safe. We achieve this with relatively +// fine-grained locking. There is a global handle table lock. This lock should +// be held as briefly as possible (TODO(vtl): a future improvement would be to +// switch it to a reader-writer lock). Each |Dispatcher| object then has a lock +// (which subclasses can use to protect their data). +// +// The lock ordering is as follows: +// 1. global handle table lock, global mapping table lock +// 2. |Dispatcher| locks +// 3. secondary object locks +// ... +// INF. |Waiter| locks +// +// Notes: +// - While holding a |Dispatcher| lock, you may not unconditionally attempt +// to take another |Dispatcher| lock. (This has consequences on the +// concurrency semantics of |MojoWriteMessage()| when passing handles.) +// Doing so would lead to deadlock. +// - Locks at the "INF" level may not have any locks taken while they are +// held. + +Core::Core() { +} + +Core::~Core() { +} + +MojoHandle Core::AddDispatcher( + const scoped_refptr<Dispatcher>& dispatcher) { + base::AutoLock locker(handle_table_lock_); + return handle_table_.AddDispatcher(dispatcher); +} + +scoped_refptr<Dispatcher> Core::GetDispatcher(MojoHandle handle) { + if (handle == MOJO_HANDLE_INVALID) + return NULL; + + base::AutoLock locker(handle_table_lock_); + return handle_table_.GetDispatcher(handle); +} + +MojoTimeTicks Core::GetTimeTicksNow() { + return base::TimeTicks::Now().ToInternalValue(); +} + +MojoResult Core::Close(MojoHandle handle) { + if (handle == MOJO_HANDLE_INVALID) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr<Dispatcher> dispatcher; + { + base::AutoLock locker(handle_table_lock_); + MojoResult result = handle_table_.GetAndRemoveDispatcher(handle, + &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + } + + // The dispatcher doesn't have a say in being closed, but gets notified of it. + // Note: This is done outside of |handle_table_lock_|. As a result, there's a + // race condition that the dispatcher must handle; see the comment in + // |Dispatcher| in dispatcher.h. + return dispatcher->Close(); +} + +MojoResult Core::Wait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline) { + return WaitManyInternal(&handle, &signals, 1, deadline); +} + +MojoResult Core::WaitMany(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline) { + if (!VerifyUserPointerWithCount<MojoHandle>(handles, num_handles)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointerWithCount<MojoHandleSignals>(signals, num_handles)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_handles < 1) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_handles > kMaxWaitManyNumHandles) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + return WaitManyInternal(handles, signals, num_handles, deadline); +} + +MojoResult Core::CreateMessagePipe(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + MojoCreateMessagePipeOptions validated_options = {}; + // This will verify the |options| pointer. + MojoResult result = MessagePipeDispatcher::ValidateCreateOptions( + options, &validated_options); + if (result != MOJO_RESULT_OK) + return result; + if (!VerifyUserPointer<MojoHandle>(message_pipe_handle0)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointer<MojoHandle>(message_pipe_handle1)) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr<MessagePipeDispatcher> dispatcher0( + new MessagePipeDispatcher(validated_options)); + scoped_refptr<MessagePipeDispatcher> dispatcher1( + new MessagePipeDispatcher(validated_options)); + + std::pair<MojoHandle, MojoHandle> handle_pair; + { + base::AutoLock locker(handle_table_lock_); + handle_pair = handle_table_.AddDispatcherPair(dispatcher0, dispatcher1); + } + if (handle_pair.first == MOJO_HANDLE_INVALID) { + DCHECK_EQ(handle_pair.second, MOJO_HANDLE_INVALID); + LOG(ERROR) << "Handle table full"; + dispatcher0->Close(); + dispatcher1->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + scoped_refptr<MessagePipe> message_pipe(new MessagePipe()); + dispatcher0->Init(message_pipe, 0); + dispatcher1->Init(message_pipe, 1); + + *message_pipe_handle0 = handle_pair.first; + *message_pipe_handle1 = handle_pair.second; + return MOJO_RESULT_OK; +} + +// Implementation note: To properly cancel waiters and avoid other races, this +// does not transfer dispatchers from one handle to another, even when sending a +// message in-process. Instead, it must transfer the "contents" of the +// dispatcher to a new dispatcher, and then close the old dispatcher. If this +// isn't done, in the in-process case, calls on the old handle may complete +// after the the message has been received and a new handle created (and +// possibly even after calls have been made on the new handle). +MojoResult Core::WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + scoped_refptr<Dispatcher> dispatcher(GetDispatcher(message_pipe_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + // Easy case: not sending any handles. + if (num_handles == 0) + return dispatcher->WriteMessage(bytes, num_bytes, NULL, flags); + + // We have to handle |handles| here, since we have to mark them busy in the + // global handle table. We can't delegate this to the dispatcher, since the + // handle table lock must be acquired before the dispatcher lock. + // + // (This leads to an oddity: |handles|/|num_handles| are always verified for + // validity, even for dispatchers that don't support |WriteMessage()| and will + // simply return failure unconditionally. It also breaks the usual + // left-to-right verification order of arguments.) + if (!VerifyUserPointerWithCount<MojoHandle>(handles, num_handles)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_handles > kMaxMessageNumHandles) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + // We'll need to hold on to the dispatchers so that we can pass them on to + // |WriteMessage()| and also so that we can unlock their locks afterwards + // without accessing the handle table. These can be dumb pointers, since their + // entries in the handle table won't get removed (since they'll be marked as + // busy). + std::vector<DispatcherTransport> transports(num_handles); + + // When we pass handles, we have to try to take all their dispatchers' locks + // and mark the handles as busy. If the call succeeds, we then remove the + // handles from the handle table. + { + base::AutoLock locker(handle_table_lock_); + MojoResult result = handle_table_.MarkBusyAndStartTransport( + message_pipe_handle, handles, num_handles, &transports); + if (result != MOJO_RESULT_OK) + return result; + } + + MojoResult rv = dispatcher->WriteMessage(bytes, num_bytes, &transports, + flags); + + // We need to release the dispatcher locks before we take the handle table + // lock. + for (uint32_t i = 0; i < num_handles; i++) + transports[i].End(); + + { + base::AutoLock locker(handle_table_lock_); + if (rv == MOJO_RESULT_OK) + handle_table_.RemoveBusyHandles(handles, num_handles); + else + handle_table_.RestoreBusyHandles(handles, num_handles); + } + + return rv; +} + +MojoResult Core::ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + scoped_refptr<Dispatcher> dispatcher(GetDispatcher(message_pipe_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_handles) { + if (!VerifyUserPointer<uint32_t>(num_handles)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointerWithCount<MojoHandle>(handles, *num_handles)) + return MOJO_RESULT_INVALID_ARGUMENT; + } + + // Easy case: won't receive any handles. + if (!num_handles || *num_handles == 0) + return dispatcher->ReadMessage(bytes, num_bytes, NULL, num_handles, flags); + + DispatcherVector dispatchers; + MojoResult rv = dispatcher->ReadMessage(bytes, num_bytes, + &dispatchers, num_handles, + flags); + if (!dispatchers.empty()) { + DCHECK_EQ(rv, MOJO_RESULT_OK); + DCHECK(num_handles); + DCHECK_LE(dispatchers.size(), static_cast<size_t>(*num_handles)); + + bool success; + { + base::AutoLock locker(handle_table_lock_); + success = handle_table_.AddDispatcherVector(dispatchers, handles); + } + if (!success) { + LOG(ERROR) << "Received message with " << dispatchers.size() + << " handles, but handle table full"; + // Close dispatchers (outside the lock). + for (size_t i = 0; i < dispatchers.size(); i++) { + if (dispatchers[i]) + dispatchers[i]->Close(); + } + } + } + + return rv; +} + +MojoResult Core::CreateDataPipe(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + MojoCreateDataPipeOptions validated_options = {}; + // This will verify the |options| pointer. + MojoResult result = DataPipe::ValidateCreateOptions(options, + &validated_options); + if (result != MOJO_RESULT_OK) + return result; + if (!VerifyUserPointer<MojoHandle>(data_pipe_producer_handle)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointer<MojoHandle>(data_pipe_consumer_handle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr<DataPipeProducerDispatcher> producer_dispatcher( + new DataPipeProducerDispatcher()); + scoped_refptr<DataPipeConsumerDispatcher> consumer_dispatcher( + new DataPipeConsumerDispatcher()); + + std::pair<MojoHandle, MojoHandle> handle_pair; + { + base::AutoLock locker(handle_table_lock_); + handle_pair = handle_table_.AddDispatcherPair(producer_dispatcher, + consumer_dispatcher); + } + if (handle_pair.first == MOJO_HANDLE_INVALID) { + DCHECK_EQ(handle_pair.second, MOJO_HANDLE_INVALID); + LOG(ERROR) << "Handle table full"; + producer_dispatcher->Close(); + consumer_dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + DCHECK_NE(handle_pair.second, MOJO_HANDLE_INVALID); + + scoped_refptr<DataPipe> data_pipe(new LocalDataPipe(validated_options)); + producer_dispatcher->Init(data_pipe); + consumer_dispatcher->Init(data_pipe); + + *data_pipe_producer_handle = handle_pair.first; + *data_pipe_consumer_handle = handle_pair.second; + return MOJO_RESULT_OK; +} + +MojoResult Core::WriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + scoped_refptr<Dispatcher> dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->WriteData(elements, num_bytes, flags); +} + +MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + scoped_refptr<Dispatcher> dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->BeginWriteData(buffer, buffer_num_bytes, flags); +} + +MojoResult Core::EndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written) { + scoped_refptr<Dispatcher> dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->EndWriteData(num_bytes_written); +} + +MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + scoped_refptr<Dispatcher> dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->ReadData(elements, num_bytes, flags); +} + +MojoResult Core::BeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + scoped_refptr<Dispatcher> dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->BeginReadData(buffer, buffer_num_bytes, flags); +} + +MojoResult Core::EndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read) { + scoped_refptr<Dispatcher> dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->EndReadData(num_bytes_read); +} + +MojoResult Core::CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + MojoCreateSharedBufferOptions validated_options = {}; + // This will verify the |options| pointer. + MojoResult result = + SharedBufferDispatcher::ValidateCreateOptions(options, + &validated_options); + if (result != MOJO_RESULT_OK) + return result; + if (!VerifyUserPointer<MojoHandle>(shared_buffer_handle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr<SharedBufferDispatcher> dispatcher; + result = SharedBufferDispatcher::Create(validated_options, num_bytes, + &dispatcher); + if (result != MOJO_RESULT_OK) { + DCHECK(!dispatcher); + return result; + } + + MojoHandle h = AddDispatcher(dispatcher); + if (h == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + *shared_buffer_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::DuplicateBufferHandle( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + // Don't verify |options| here; that's the dispatcher's job. + if (!VerifyUserPointer<MojoHandle>(new_buffer_handle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr<Dispatcher> new_dispatcher; + MojoResult result = dispatcher->DuplicateBufferHandle(options, + &new_dispatcher); + if (result != MOJO_RESULT_OK) + return result; + + MojoHandle new_handle = AddDispatcher(new_dispatcher); + if (new_handle == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + *new_buffer_handle = new_handle; + return MOJO_RESULT_OK; +} + +MojoResult Core::MapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!VerifyUserPointerWithCount<void*>(buffer, 1)) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_ptr<RawSharedBufferMapping> mapping; + MojoResult result = dispatcher->MapBuffer(offset, num_bytes, flags, &mapping); + if (result != MOJO_RESULT_OK) + return result; + + DCHECK(mapping); + void* address = mapping->base(); + { + base::AutoLock locker(mapping_table_lock_); + result = mapping_table_.AddMapping(mapping.Pass()); + } + if (result != MOJO_RESULT_OK) + return result; + + *buffer = address; + return MOJO_RESULT_OK; +} + +MojoResult Core::UnmapBuffer(void* buffer) { + base::AutoLock locker(mapping_table_lock_); + return mapping_table_.RemoveMapping(buffer); +} + +// Note: We allow |handles| to repeat the same handle multiple times, since +// different flags may be specified. +// TODO(vtl): This incurs a performance cost in |RemoveWaiter()|. Analyze this +// more carefully and address it if necessary. +MojoResult Core::WaitManyInternal(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline) { + DCHECK_GT(num_handles, 0u); + + DispatcherVector dispatchers; + dispatchers.reserve(num_handles); + for (uint32_t i = 0; i < num_handles; i++) { + scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handles[i]); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + dispatchers.push_back(dispatcher); + } + + // TODO(vtl): Should make the waiter live (permanently) in TLS. + Waiter waiter; + waiter.Init(); + + uint32_t i; + MojoResult rv = MOJO_RESULT_OK; + for (i = 0; i < num_handles; i++) { + rv = dispatchers[i]->AddWaiter(&waiter, signals[i], i); + if (rv != MOJO_RESULT_OK) + break; + } + uint32_t num_added = i; + + if (rv == MOJO_RESULT_ALREADY_EXISTS) { + rv = static_cast<MojoResult>(i); // The i-th one is already "triggered". + } else if (rv == MOJO_RESULT_OK) { + uint32_t context = static_cast<uint32_t>(-1); + rv = waiter.Wait(deadline, &context); + if (rv == MOJO_RESULT_OK) + rv = static_cast<MojoResult>(context); + } + + // Make sure no other dispatchers try to wake |waiter| for the current + // |Wait()|/|WaitMany()| call. (Only after doing this can |waiter| be + // destroyed, but this would still be required if the waiter were in TLS.) + for (i = 0; i < num_added; i++) + dispatchers[i]->RemoveWaiter(&waiter); + + return rv; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/core.h b/chromium/mojo/system/core.h new file mode 100644 index 00000000000..a0bc293e203 --- /dev/null +++ b/chromium/mojo/system/core.h @@ -0,0 +1,131 @@ +// 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 MOJO_SYSTEM_CORE_H_ +#define MOJO_SYSTEM_CORE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/handle_table.h" +#include "mojo/system/mapping_table.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Dispatcher; + +// |Core| is an object that implements the Mojo system calls. All public methods +// are thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT Core { + public: + // These methods are only to be used by via the embedder API (and internally). + Core(); + virtual ~Core(); + + // Adds |dispatcher| to the handle table, returning the handle for it. Returns + // |MOJO_HANDLE_INVALID| on failure, namely if the handle table is full. + MojoHandle AddDispatcher(const scoped_refptr<Dispatcher>& dispatcher); + + // Looks up the dispatcher for the given handle. Returns null if the handle is + // invalid. + scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle); + + // System calls implementation. + MojoTimeTicks GetTimeTicksNow(); + MojoResult Close(MojoHandle handle); + MojoResult Wait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline); + MojoResult WaitMany(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline); + MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1); + MojoResult WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags); + MojoResult ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult CreateDataPipe(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle); + MojoResult WriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags); + MojoResult BeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + MojoResult EndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written); + MojoResult ReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + MojoResult BeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + MojoResult EndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read); + MojoResult CreateSharedBuffer(const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle); + MojoResult DuplicateBufferHandle( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle); + MojoResult MapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags); + MojoResult UnmapBuffer(void* buffer); + + private: + friend bool internal::ShutdownCheckNoLeaks(Core*); + + // Internal implementation of |Wait()| and |WaitMany()|; doesn't do basic + // validation of arguments. + MojoResult WaitManyInternal(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline); + + // --------------------------------------------------------------------------- + + // TODO(vtl): |handle_table_lock_| should be a reader-writer lock (if only we + // had them). + base::Lock handle_table_lock_; // Protects |handle_table_|. + HandleTable handle_table_; + + base::Lock mapping_table_lock_; // Protects |mapping_table_|. + MappingTable mapping_table_; + + // --------------------------------------------------------------------------- + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_CORE_H_ diff --git a/chromium/mojo/system/core_test_base.cc b/chromium/mojo/system/core_test_base.cc new file mode 100644 index 00000000000..c91437c42c6 --- /dev/null +++ b/chromium/mojo/system/core_test_base.cc @@ -0,0 +1,349 @@ +// 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 "mojo/system/core_test_base.h" + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/constants.h" +#include "mojo/system/core.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/memory.h" + +namespace mojo { +namespace system { +namespace test { + +namespace { + +// MockDispatcher -------------------------------------------------------------- + +class MockDispatcher : public Dispatcher { + public: + explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) + : info_(info) { + CHECK(info_); + info_->IncrementCtorCallCount(); + } + + // |Dispatcher| private methods: + virtual Type GetType() const OVERRIDE { + return kTypeUnknown; + } + + private: + virtual ~MockDispatcher() { + info_->IncrementDtorCallCount(); + } + + // |Dispatcher| protected methods: + virtual void CloseImplNoLock() OVERRIDE { + info_->IncrementCloseCallCount(); + lock().AssertAcquired(); + } + + virtual MojoResult WriteMessageImplNoLock( + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags /*flags*/) OVERRIDE { + info_->IncrementWriteMessageCallCount(); + lock().AssertAcquired(); + + if (!VerifyUserPointerWithSize<1>(bytes, num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > kMaxMessageNumBytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + if (transports) + return MOJO_RESULT_UNIMPLEMENTED; + + return MOJO_RESULT_OK; + } + + virtual MojoResult ReadMessageImplNoLock( + void* bytes, + uint32_t* num_bytes, + DispatcherVector* /*dispatchers*/, + uint32_t* /*num_dispatchers*/, + MojoReadMessageFlags /*flags*/) OVERRIDE { + info_->IncrementReadMessageCallCount(); + lock().AssertAcquired(); + + if (num_bytes && !VerifyUserPointerWithSize<1>(bytes, *num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + + return MOJO_RESULT_OK; + } + + virtual MojoResult WriteDataImplNoLock( + const void* /*elements*/, + uint32_t* /*num_bytes*/, + MojoWriteDataFlags /*flags*/) OVERRIDE { + info_->IncrementWriteDataCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + virtual MojoResult BeginWriteDataImplNoLock( + void** /*buffer*/, + uint32_t* /*buffer_num_bytes*/, + MojoWriteDataFlags /*flags*/) OVERRIDE { + info_->IncrementBeginWriteDataCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + virtual MojoResult EndWriteDataImplNoLock( + uint32_t /*num_bytes_written*/) OVERRIDE { + info_->IncrementEndWriteDataCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + virtual MojoResult ReadDataImplNoLock(void* /*elements*/, + uint32_t* /*num_bytes*/, + MojoReadDataFlags /*flags*/) OVERRIDE { + info_->IncrementReadDataCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + virtual MojoResult BeginReadDataImplNoLock( + const void** /*buffer*/, + uint32_t* /*buffer_num_bytes*/, + MojoReadDataFlags /*flags*/) OVERRIDE { + info_->IncrementBeginReadDataCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + virtual MojoResult EndReadDataImplNoLock( + uint32_t /*num_bytes_read*/) OVERRIDE { + info_->IncrementEndReadDataCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + virtual MojoResult AddWaiterImplNoLock(Waiter* /*waiter*/, + MojoHandleSignals /*signals*/, + uint32_t /*context*/) OVERRIDE { + info_->IncrementAddWaiterCallCount(); + lock().AssertAcquired(); + return MOJO_RESULT_FAILED_PRECONDITION; + } + + virtual void RemoveWaiterImplNoLock(Waiter* /*waiter*/) OVERRIDE { + info_->IncrementRemoveWaiterCallCount(); + lock().AssertAcquired(); + } + + virtual void CancelAllWaitersNoLock() OVERRIDE { + info_->IncrementCancelAllWaitersCallCount(); + lock().AssertAcquired(); + } + + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE { + return scoped_refptr<Dispatcher>(new MockDispatcher(info_)); + } + + CoreTestBase::MockHandleInfo* const info_; + + DISALLOW_COPY_AND_ASSIGN(MockDispatcher); +}; + +} // namespace + +// CoreTestBase ---------------------------------------------------------------- + +CoreTestBase::CoreTestBase() { +} + +CoreTestBase::~CoreTestBase() { +} + +void CoreTestBase::SetUp() { + core_ = new Core(); +} + +void CoreTestBase::TearDown() { + delete core_; + core_ = NULL; +} + +MojoHandle CoreTestBase::CreateMockHandle(CoreTestBase::MockHandleInfo* info) { + CHECK(core_); + scoped_refptr<MockDispatcher> dispatcher(new MockDispatcher(info)); + return core_->AddDispatcher(dispatcher); +} + +// CoreTestBase_MockHandleInfo ------------------------------------------------- + +CoreTestBase_MockHandleInfo::CoreTestBase_MockHandleInfo() + : ctor_call_count_(0), + dtor_call_count_(0), + close_call_count_(0), + write_message_call_count_(0), + read_message_call_count_(0), + write_data_call_count_(0), + begin_write_data_call_count_(0), + end_write_data_call_count_(0), + read_data_call_count_(0), + begin_read_data_call_count_(0), + end_read_data_call_count_(0), + add_waiter_call_count_(0), + remove_waiter_call_count_(0), + cancel_all_waiters_call_count_(0) { +} + +CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() { +} + +unsigned CoreTestBase_MockHandleInfo::GetCtorCallCount() const { + base::AutoLock locker(lock_); + return ctor_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetDtorCallCount() const { + base::AutoLock locker(lock_); + return dtor_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetCloseCallCount() const { + base::AutoLock locker(lock_); + return close_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetWriteMessageCallCount() const { + base::AutoLock locker(lock_); + return write_message_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetReadMessageCallCount() const { + base::AutoLock locker(lock_); + return read_message_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetWriteDataCallCount() const { + base::AutoLock locker(lock_); + return write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetBeginWriteDataCallCount() const { + base::AutoLock locker(lock_); + return begin_write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetEndWriteDataCallCount() const { + base::AutoLock locker(lock_); + return end_write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetReadDataCallCount() const { + base::AutoLock locker(lock_); + return read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetBeginReadDataCallCount() const { + base::AutoLock locker(lock_); + return begin_read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetEndReadDataCallCount() const { + base::AutoLock locker(lock_); + return end_read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetAddWaiterCallCount() const { + base::AutoLock locker(lock_); + return add_waiter_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetRemoveWaiterCallCount() const { + base::AutoLock locker(lock_); + return remove_waiter_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetCancelAllWaitersCallCount() const { + base::AutoLock locker(lock_); + return cancel_all_waiters_call_count_; +} + +void CoreTestBase_MockHandleInfo::IncrementCtorCallCount() { + base::AutoLock locker(lock_); + ctor_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementDtorCallCount() { + base::AutoLock locker(lock_); + dtor_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementCloseCallCount() { + base::AutoLock locker(lock_); + close_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementWriteMessageCallCount() { + base::AutoLock locker(lock_); + write_message_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementReadMessageCallCount() { + base::AutoLock locker(lock_); + read_message_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementWriteDataCallCount() { + base::AutoLock locker(lock_); + write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementBeginWriteDataCallCount() { + base::AutoLock locker(lock_); + begin_write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementEndWriteDataCallCount() { + base::AutoLock locker(lock_); + end_write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementReadDataCallCount() { + base::AutoLock locker(lock_); + read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementBeginReadDataCallCount() { + base::AutoLock locker(lock_); + begin_read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementEndReadDataCallCount() { + base::AutoLock locker(lock_); + end_read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementAddWaiterCallCount() { + base::AutoLock locker(lock_); + add_waiter_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementRemoveWaiterCallCount() { + base::AutoLock locker(lock_); + remove_waiter_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementCancelAllWaitersCallCount() { + base::AutoLock locker(lock_); + cancel_all_waiters_call_count_++; +} + +} // namespace test +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/core_test_base.h b/chromium/mojo/system/core_test_base.h new file mode 100644 index 00000000000..9f5bae76846 --- /dev/null +++ b/chromium/mojo/system/core_test_base.h @@ -0,0 +1,105 @@ +// 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 MOJO_SYSTEM_CORE_TEST_BASE_H_ +#define MOJO_SYSTEM_CORE_TEST_BASE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { + +class Core; + +namespace test { + +class CoreTestBase_MockHandleInfo; + +class CoreTestBase : public testing::Test { + public: + typedef CoreTestBase_MockHandleInfo MockHandleInfo; + + CoreTestBase(); + virtual ~CoreTestBase(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + protected: + // |info| must remain alive until the returned handle is closed. + MojoHandle CreateMockHandle(MockHandleInfo* info); + + Core* core() { return core_; } + + private: + Core* core_; + + DISALLOW_COPY_AND_ASSIGN(CoreTestBase); +}; + +class CoreTestBase_MockHandleInfo { + public: + CoreTestBase_MockHandleInfo(); + ~CoreTestBase_MockHandleInfo(); + + unsigned GetCtorCallCount() const; + unsigned GetDtorCallCount() const; + unsigned GetCloseCallCount() const; + unsigned GetWriteMessageCallCount() const; + unsigned GetReadMessageCallCount() const; + unsigned GetWriteDataCallCount() const; + unsigned GetBeginWriteDataCallCount() const; + unsigned GetEndWriteDataCallCount() const; + unsigned GetReadDataCallCount() const; + unsigned GetBeginReadDataCallCount() const; + unsigned GetEndReadDataCallCount() const; + unsigned GetAddWaiterCallCount() const; + unsigned GetRemoveWaiterCallCount() const; + unsigned GetCancelAllWaitersCallCount() const; + + // For use by |MockDispatcher|: + void IncrementCtorCallCount(); + void IncrementDtorCallCount(); + void IncrementCloseCallCount(); + void IncrementWriteMessageCallCount(); + void IncrementReadMessageCallCount(); + void IncrementWriteDataCallCount(); + void IncrementBeginWriteDataCallCount(); + void IncrementEndWriteDataCallCount(); + void IncrementReadDataCallCount(); + void IncrementBeginReadDataCallCount(); + void IncrementEndReadDataCallCount(); + void IncrementAddWaiterCallCount(); + void IncrementRemoveWaiterCallCount(); + void IncrementCancelAllWaitersCallCount(); + + private: + mutable base::Lock lock_; // Protects the following members. + unsigned ctor_call_count_; + unsigned dtor_call_count_; + unsigned close_call_count_; + unsigned write_message_call_count_; + unsigned read_message_call_count_; + unsigned write_data_call_count_; + unsigned begin_write_data_call_count_; + unsigned end_write_data_call_count_; + unsigned read_data_call_count_; + unsigned begin_read_data_call_count_; + unsigned end_read_data_call_count_; + unsigned add_waiter_call_count_; + unsigned remove_waiter_call_count_; + unsigned cancel_all_waiters_call_count_; + + DISALLOW_COPY_AND_ASSIGN(CoreTestBase_MockHandleInfo); +}; + +} // namespace test +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_CORE_TEST_BASE_H_ diff --git a/chromium/mojo/system/core_unittest.cc b/chromium/mojo/system/core_unittest.cc new file mode 100644 index 00000000000..af3d0c13451 --- /dev/null +++ b/chromium/mojo/system/core_unittest.cc @@ -0,0 +1,888 @@ +// 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 "mojo/system/core.h" + +#include <limits> + +#include "base/basictypes.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "mojo/system/core_test_base.h" + +namespace mojo { +namespace system { +namespace { + +typedef test::CoreTestBase CoreTest; + +TEST_F(CoreTest, GetTimeTicksNow) { + const MojoTimeTicks start = core()->GetTimeTicksNow(); + EXPECT_NE(static_cast<MojoTimeTicks>(0), start) + << "GetTimeTicksNow should return nonzero value"; + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(15)); + const MojoTimeTicks finish = core()->GetTimeTicksNow(); + // Allow for some fuzz in sleep. + EXPECT_GE((finish - start), static_cast<MojoTimeTicks>(8000)) + << "Sleeping should result in increasing time ticks"; +} + +TEST_F(CoreTest, Basic) { + MockHandleInfo info; + + EXPECT_EQ(0u, info.GetCtorCallCount()); + MojoHandle h = CreateMockHandle(&info); + EXPECT_EQ(1u, info.GetCtorCallCount()); + EXPECT_NE(h, MOJO_HANDLE_INVALID); + + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h, NULL, 0, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, info.GetWriteMessageCallCount()); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, NULL, 1, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(2u, info.GetWriteMessageCallCount()); + + EXPECT_EQ(0u, info.GetReadMessageCallCount()); + uint32_t num_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h, NULL, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, info.GetReadMessageCallCount()); + num_bytes = 1; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadMessage(h, NULL, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(2u, info.GetReadMessageCallCount()); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h, NULL, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(3u, info.GetReadMessageCallCount()); + + EXPECT_EQ(0u, info.GetWriteDataCallCount()); + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->WriteData(h, NULL, NULL, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(1u, info.GetWriteDataCallCount()); + + EXPECT_EQ(0u, info.GetBeginWriteDataCallCount()); + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->BeginWriteData(h, NULL, NULL, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(1u, info.GetBeginWriteDataCallCount()); + + EXPECT_EQ(0u, info.GetEndWriteDataCallCount()); + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->EndWriteData(h, 0)); + EXPECT_EQ(1u, info.GetEndWriteDataCallCount()); + + EXPECT_EQ(0u, info.GetReadDataCallCount()); + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->ReadData(h, NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(1u, info.GetReadDataCallCount()); + + EXPECT_EQ(0u, info.GetBeginReadDataCallCount()); + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->BeginReadData(h, NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(1u, info.GetBeginReadDataCallCount()); + + EXPECT_EQ(0u, info.GetEndReadDataCallCount()); + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->EndReadData(h, 0)); + EXPECT_EQ(1u, info.GetEndReadDataCallCount()); + + EXPECT_EQ(0u, info.GetAddWaiterCallCount()); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, + MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(1u, info.GetAddWaiterCallCount()); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 0)); + EXPECT_EQ(2u, info.GetAddWaiterCallCount()); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000)); + EXPECT_EQ(3u, info.GetAddWaiterCallCount()); + MojoHandleSignals handle_signals = ~MOJO_HANDLE_SIGNAL_NONE; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(4u, info.GetAddWaiterCallCount()); + + EXPECT_EQ(0u, info.GetDtorCallCount()); + EXPECT_EQ(0u, info.GetCloseCallCount()); + EXPECT_EQ(0u, info.GetCancelAllWaitersCallCount()); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h)); + EXPECT_EQ(1u, info.GetCancelAllWaitersCallCount()); + EXPECT_EQ(1u, info.GetCloseCallCount()); + EXPECT_EQ(1u, info.GetDtorCallCount()); + + // No waiters should ever have ever been added. + EXPECT_EQ(0u, info.GetRemoveWaiterCallCount()); +} + +TEST_F(CoreTest, InvalidArguments) { + // |Close()|: + { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(MOJO_HANDLE_INVALID)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(10)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(1000000000)); + + // Test a double-close. + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h)); + EXPECT_EQ(1u, info.GetCloseCallCount()); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h)); + EXPECT_EQ(1u, info.GetCloseCallCount()); + } + + // |Wait()|: + { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE, + MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE, + MOJO_DEADLINE_INDEFINITE)); + } + + // |WaitMany()|: + { + MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID}; + MojoHandleSignals signals[2] = {~MOJO_HANDLE_SIGNAL_NONE, + ~MOJO_HANDLE_SIGNAL_NONE}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(handles, signals, 0, MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(NULL, signals, 0, MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(handles, NULL, 0, MOJO_DEADLINE_INDEFINITE)); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(NULL, signals, 1, MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(handles, NULL, 1, MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(handles, signals, 1, MOJO_DEADLINE_INDEFINITE)); + + MockHandleInfo info[2]; + handles[0] = CreateMockHandle(&info[0]); + + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->WaitMany(handles, signals, 1, MOJO_DEADLINE_INDEFINITE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE)); + handles[1] = handles[0] + 1; // Invalid handle. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE)); + handles[1] = CreateMockHandle(&info[1]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE)); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(handles[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(handles[1])); + } + + // |CreateMessagePipe()|: + { + MojoHandle h; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->CreateMessagePipe(NULL, NULL, NULL)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->CreateMessagePipe(NULL, &h, NULL)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->CreateMessagePipe(NULL, NULL, &h)); + } + + // |WriteMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(MOJO_HANDLE_INVALID, NULL, 0, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID}; + + // Null |handles| with nonzero |num_handles|. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, NULL, 0, NULL, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // Checked by |Core|, shouldn't go through to the dispatcher. + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + + // Huge handle count (implausibly big on some systems -- more than can be + // stored in a 32-bit address space). + // Note: This may return either |MOJO_RESULT_INVALID_ARGUMENT| or + // |MOJO_RESULT_RESOURCE_EXHAUSTED|, depending on whether it's plausible or + // not. + EXPECT_NE(MOJO_RESULT_OK, + core()->WriteMessage(h, NULL, 0, handles, + std::numeric_limits<uint32_t>::max(), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + + // Huge handle count (plausibly big). + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->WriteMessage(h, NULL, 0, handles, + std::numeric_limits<uint32_t>::max() / + sizeof(handles[0]), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + + // Invalid handle in |handles|. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, NULL, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + + // Two invalid handles in |handles|. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, NULL, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + + // Can't send a handle over itself. + handles[0] = h; + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h, NULL, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(0u, info.GetWriteMessageCallCount()); + + MockHandleInfo info2; + MojoHandle h2 = CreateMockHandle(&info2); + + // This is "okay", but |MockDispatcher| doesn't implement it. + handles[0] = h2; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->WriteMessage(h, NULL, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, info.GetWriteMessageCallCount()); + + // One of the |handles| is still invalid. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, NULL, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, info.GetWriteMessageCallCount()); + + // One of the |handles| is the same as |handle|. + handles[1] = h; + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h, NULL, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, info.GetWriteMessageCallCount()); + + // Can't send a handle twice in the same message. + handles[1] = h2; + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h, NULL, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, info.GetWriteMessageCallCount()); + + // Note: Since we never successfully sent anything with it, |h2| should + // still be valid. + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h2)); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } + + // |ReadMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadMessage(MOJO_HANDLE_INVALID, NULL, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + + uint32_t handle_count = 1; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadMessage(h, NULL, NULL, NULL, &handle_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Checked by |Core|, shouldn't go through to the dispatcher. + EXPECT_EQ(0u, info.GetReadMessageCallCount()); + + // Okay. + handle_count = 0; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h, NULL, NULL, NULL, &handle_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Checked by |Core|, shouldn't go through to the dispatcher. + EXPECT_EQ(1u, info.GetReadMessageCallCount()); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } +} + +// TODO(vtl): test |Wait()| and |WaitMany()| properly +// - including |WaitMany()| with the same handle more than once (with +// same/different signals) + +TEST_F(CoreTest, MessagePipe) { + MojoHandle h[2]; + + EXPECT_EQ(MOJO_RESULT_OK, core()->CreateMessagePipe(NULL, &h[0], &h[1])); + // Should get two distinct, valid handles. + EXPECT_NE(h[0], MOJO_HANDLE_INVALID); + EXPECT_NE(h[1], MOJO_HANDLE_INVALID); + EXPECT_NE(h[0], h[1]); + + // Neither should be readable. + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + core()->WaitMany(h, signals, 2, 0)); + + // Try to read anyway. + char buffer[1] = {'a'}; + uint32_t buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + core()->ReadMessage(h[0], buffer, &buffer_size, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Check that it left its inputs alone. + EXPECT_EQ('a', buffer[0]); + EXPECT_EQ(1u, buffer_size); + + // Both should be writable. + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h[0], MOJO_HANDLE_SIGNAL_WRITABLE, 1000000000)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE, 1000000000)); + + // Also check that |h[1]| is writable using |WaitMany()|. + signals[0] = MOJO_HANDLE_SIGNAL_READABLE; + signals[1] = MOJO_HANDLE_SIGNAL_WRITABLE; + EXPECT_EQ(1, core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE)); + + // Write to |h[1]|. + buffer[0] = 'b'; + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h[1], buffer, 1, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Check that |h[0]| is now readable. + signals[0] = MOJO_HANDLE_SIGNAL_READABLE; + signals[1] = MOJO_HANDLE_SIGNAL_READABLE; + EXPECT_EQ(0, core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE)); + + // Read from |h[0]|. + // First, get only the size. + buffer_size = 0; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->ReadMessage(h[0], NULL, &buffer_size, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(1u, buffer_size); + // Then actually read it. + buffer[0] = 'c'; + buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h[0], buffer, &buffer_size, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ('b', buffer[0]); + EXPECT_EQ(1u, buffer_size); + + // |h[0]| should no longer be readable. + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + core()->Wait(h[0], MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Write to |h[0]|. + buffer[0] = 'd'; + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h[0], buffer, 1, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Close |h[0]|. + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h[0])); + + // Check that |h[1]| is no longer writable (and will never be). + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE, 1000000000)); + + // Check that |h[1]| is still readable (for the moment). + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + + // Discard a message from |h[1]|. + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->ReadMessage(h[1], NULL, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // |h[1]| is no longer readable (and will never be). + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + + // Try writing to |h[1]|. + buffer[0] = 'e'; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->WriteMessage(h[1], buffer, 1, NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h[1])); +} + +// Tests passing a message pipe handle. +TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + const char kWorld[] = "world!!!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + char buffer[100]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t num_bytes; + MojoHandle handles[10]; + uint32_t num_handles; + MojoHandle h_received; + + MojoHandle h_passing[2]; + EXPECT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(NULL, &h_passing[0], &h_passing[1])); + + // Make sure that |h_passing[]| work properly. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, + 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passing[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, num_bytes); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(0u, num_handles); + + // Make sure that you can't pass either of the message pipe's handles over + // itself. + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + &h_passing[0], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + &h_passing[1], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MojoHandle h_passed[2]; + EXPECT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(NULL, &h_passed[0], &h_passed[1])); + + // Make sure that |h_passed[]| work properly. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passed[0], + kHello, kHelloSize, + NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passed[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passed[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, num_bytes); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(0u, num_handles); + + // Send |h_passed[1]| from |h_passing[0]| to |h_passing[1]|. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], + kWorld, kWorldSize, + &h_passed[1], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, + 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passing[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, num_bytes); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(1u, num_handles); + h_received = handles[0]; + EXPECT_NE(h_received, MOJO_HANDLE_INVALID); + EXPECT_NE(h_received, h_passing[0]); + EXPECT_NE(h_received, h_passing[1]); + EXPECT_NE(h_received, h_passed[0]); + + // Note: We rely on the Mojo system not re-using handle values very often. + EXPECT_NE(h_received, h_passed[1]); + + // |h_passed[1]| should no longer be valid; check that trying to close it + // fails. See above note. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h_passed[1])); + + // Write to |h_passed[0]|. Should receive on |h_received|. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passed[0], + kHello, kHelloSize, + NULL, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_received, + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, num_bytes); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(0u, num_handles); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1])); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passed[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_received)); +} + +TEST_F(CoreTest, DataPipe) { + MojoHandle ph, ch; // p is for producer and c is for consumer. + + EXPECT_EQ(MOJO_RESULT_OK, core()->CreateDataPipe(NULL, &ph, &ch)); + // Should get two distinct, valid handles. + EXPECT_NE(ph, MOJO_HANDLE_INVALID); + EXPECT_NE(ch, MOJO_HANDLE_INVALID); + EXPECT_NE(ph, ch); + + // Producer should be never-readable, but already writable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(ph, MOJO_HANDLE_SIGNAL_READABLE, 0)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(ph, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + + // Consumer should be never-writable, and not yet readable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(ch, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Write. + char elements[2] = {'A', 'B'}; + uint32_t num_bytes = 2u; + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph, elements, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(2u, num_bytes); + + // Consumer should now be readable. + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Read one character. + elements[0] = -1; + elements[1] = -1; + num_bytes = 1u; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch, elements, &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ('A', elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Two-phase write. + void* write_ptr = NULL; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginWriteData(ph, &write_ptr, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + // We count on the default options providing a decent buffer size. + ASSERT_GE(num_bytes, 3u); + + // Trying to do a normal write during a two-phase write should fail. + elements[0] = 'X'; + num_bytes = 1u; + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteData(ph, elements, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + + // Actually write the data, and complete it now. + static_cast<char*>(write_ptr)[0] = 'C'; + static_cast<char*>(write_ptr)[1] = 'D'; + static_cast<char*>(write_ptr)[2] = 'E'; + EXPECT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 3u)); + + // Query how much data we have. + num_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch, NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY)); + EXPECT_EQ(4u, num_bytes); + + // Try to discard ten characters, in all-or-none mode. Should fail. + num_bytes = 10; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + core()->ReadData(ch, NULL, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Discard two characters. + num_bytes = 2; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch, NULL, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Read the remaining two characters, in two-phase mode (all-or-none). + const void* read_ptr = NULL; + num_bytes = 2; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + // Note: Count on still being able to do the contiguous read here. + ASSERT_EQ(2u, num_bytes); + + // Discarding right now should fail. + num_bytes = 1; + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->ReadData(ch, NULL, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD)); + + // Actually check our data and end the two-phase read. + EXPECT_EQ('D', static_cast<const char*>(read_ptr)[0]); + EXPECT_EQ('E', static_cast<const char*>(read_ptr)[1]); + EXPECT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 2u)); + + // Consumer should now be no longer readable. + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // TODO(vtl): More. + + // Close the producer. + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ph)); + + // The consumer should now be never-readable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ch)); +} + +// Tests passing data pipe producer and consumer handles. +TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + const char kWorld[] = "world!!!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + char buffer[100]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t num_bytes; + MojoHandle handles[10]; + uint32_t num_handles; + + MojoHandle h_passing[2]; + EXPECT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(NULL, &h_passing[0], &h_passing[1])); + + MojoHandle ph, ch; + EXPECT_EQ(MOJO_RESULT_OK, + core()->CreateDataPipe(NULL, &ph, &ch)); + + // Send |ch| from |h_passing[0]| to |h_passing[1]|. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, + 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passing[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, num_bytes); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(1u, num_handles); + MojoHandle ch_received = handles[0]; + EXPECT_NE(ch_received, MOJO_HANDLE_INVALID); + EXPECT_NE(ch_received, h_passing[0]); + EXPECT_NE(ch_received, h_passing[1]); + EXPECT_NE(ch_received, ph); + + // Note: We rely on the Mojo system not re-using handle values very often. + EXPECT_NE(ch_received, ch); + + // |ch| should no longer be valid; check that trying to close it fails. See + // above note. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ch)); + + // Write to |ph|. Should receive on |ch_received|. + num_bytes = kWorldSize; + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph, kWorld, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + num_bytes = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch_received, buffer, &num_bytes, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, num_bytes); + EXPECT_STREQ(kWorld, buffer); + + // Now pass |ph| in the same direction. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], + kWorld, kWorldSize, + &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, + 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passing[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, num_bytes); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(1u, num_handles); + MojoHandle ph_received = handles[0]; + EXPECT_NE(ph_received, MOJO_HANDLE_INVALID); + EXPECT_NE(ph_received, h_passing[0]); + EXPECT_NE(ph_received, h_passing[1]); + EXPECT_NE(ph_received, ch_received); + + // Again, rely on the Mojo system not re-using handle values very often. + EXPECT_NE(ph_received, ph); + + // |ph| should no longer be valid; check that trying to close it fails. See + // above note. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ph)); + + // Write to |ph_received|. Should receive on |ch_received|. + num_bytes = kHelloSize; + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph_received, kHello, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + num_bytes = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch_received, buffer, &num_bytes, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, num_bytes); + EXPECT_STREQ(kHello, buffer); + + ph = ph_received; + ph_received = MOJO_HANDLE_INVALID; + ch = ch_received; + ch_received = MOJO_HANDLE_INVALID; + + // Make sure that |ph| can't be sent if it's in a two-phase write. + void* write_ptr = NULL; + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginWriteData(ph, &write_ptr, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_GE(num_bytes, 1u); + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // But |ch| can, even if |ph| is in a two-phase write. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ch = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, + 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passing[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, num_bytes); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(1u, num_handles); + ch = handles[0]; + EXPECT_NE(ch, MOJO_HANDLE_INVALID); + + // Complete the two-phase write. + static_cast<char*>(write_ptr)[0] = 'x'; + EXPECT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 1)); + + // Wait for |ch| to be readable. + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000)); + + // Make sure that |ch| can't be sent if it's in a two-phase read. + const void* read_ptr = NULL; + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + EXPECT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], + kHello, kHelloSize, + &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // But |ph| can, even if |ch| is in a two-phase read. + EXPECT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], + kWorld, kWorldSize, + &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ph = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, + core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, + 1000000000)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + EXPECT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h_passing[1], + buffer, &num_bytes, + handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, num_bytes); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(1u, num_handles); + ph = handles[0]; + EXPECT_NE(ph, MOJO_HANDLE_INVALID); + + // Complete the two-phase read. + EXPECT_EQ('x', static_cast<const char*>(read_ptr)[0]); + EXPECT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 1)); + + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1])); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ph)); + EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ch)); +} + +// TODO(vtl): Test |DuplicateBufferHandle()| and |MapBuffer()|. + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/data_pipe.cc b/chromium/mojo/system/data_pipe.cc new file mode 100644 index 00000000000..6337648d01c --- /dev/null +++ b/chromium/mojo/system/data_pipe.cc @@ -0,0 +1,403 @@ +// 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 "mojo/system/data_pipe.h" + +#include <string.h> + +#include <algorithm> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/system/constants.h" +#include "mojo/system/memory.h" +#include "mojo/system/options_validation.h" +#include "mojo/system/waiter_list.h" + +namespace mojo { +namespace system { + +// static +const MojoCreateDataPipeOptions DataPipe::kDefaultCreateOptions = { + static_cast<uint32_t>(sizeof(MojoCreateDataPipeOptions)), + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + 1u, + static_cast<uint32_t>(kDefaultDataPipeCapacityBytes) +}; + +// static +MojoResult DataPipe::ValidateCreateOptions( + const MojoCreateDataPipeOptions* in_options, + MojoCreateDataPipeOptions* out_options) { + const MojoCreateDataPipeOptionsFlags kKnownFlags = + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD; + + *out_options = kDefaultCreateOptions; + if (!in_options) + return MOJO_RESULT_OK; + + MojoResult result = + ValidateOptionsStructPointerSizeAndFlags<MojoCreateDataPipeOptions>( + in_options, kKnownFlags, out_options); + if (result != MOJO_RESULT_OK) + return result; + + // Checks for fields beyond |flags|: + + if (!HAS_OPTIONS_STRUCT_MEMBER(MojoCreateDataPipeOptions, element_num_bytes, + in_options)) + return MOJO_RESULT_OK; + if (in_options->element_num_bytes == 0) + return MOJO_RESULT_INVALID_ARGUMENT; + out_options->element_num_bytes = in_options->element_num_bytes; + + if (!HAS_OPTIONS_STRUCT_MEMBER(MojoCreateDataPipeOptions, capacity_num_bytes, + in_options) || + in_options->capacity_num_bytes == 0) { + // Round the default capacity down to a multiple of the element size (but at + // least one element). + out_options->capacity_num_bytes = std::max( + static_cast<uint32_t>(kDefaultDataPipeCapacityBytes - + (kDefaultDataPipeCapacityBytes % out_options->element_num_bytes)), + out_options->element_num_bytes); + return MOJO_RESULT_OK; + } + if (in_options->capacity_num_bytes % out_options->element_num_bytes != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + if (in_options->capacity_num_bytes > kMaxDataPipeCapacityBytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + out_options->capacity_num_bytes = in_options->capacity_num_bytes; + + return MOJO_RESULT_OK; +} + +void DataPipe::ProducerCancelAllWaiters() { + base::AutoLock locker(lock_); + DCHECK(has_local_producer_no_lock()); + producer_waiter_list_->CancelAllWaiters(); +} + +void DataPipe::ProducerClose() { + base::AutoLock locker(lock_); + DCHECK(producer_open_); + producer_open_ = false; + DCHECK(has_local_producer_no_lock()); + producer_waiter_list_.reset(); + // Not a bug, except possibly in "user" code. + DVLOG_IF(2, producer_in_two_phase_write_no_lock()) + << "Producer closed with active two-phase write"; + producer_two_phase_max_num_bytes_written_ = 0; + ProducerCloseImplNoLock(); + AwakeConsumerWaitersForStateChangeNoLock( + ConsumerGetHandleSignalsStateNoLock()); +} + +MojoResult DataPipe::ProducerWriteData(const void* elements, + uint32_t* num_bytes, + bool all_or_none) { + base::AutoLock locker(lock_); + DCHECK(has_local_producer_no_lock()); + + if (producer_in_two_phase_write_no_lock()) + return MOJO_RESULT_BUSY; + if (!consumer_open_no_lock()) + return MOJO_RESULT_FAILED_PRECONDITION; + + // Returning "busy" takes priority over "invalid argument". + if (*num_bytes % element_num_bytes_ != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (*num_bytes == 0) + return MOJO_RESULT_OK; // Nothing to do. + + HandleSignalsState old_consumer_state = ConsumerGetHandleSignalsStateNoLock(); + MojoResult rv = ProducerWriteDataImplNoLock(elements, num_bytes, all_or_none); + HandleSignalsState new_consumer_state = ConsumerGetHandleSignalsStateNoLock(); + if (!new_consumer_state.equals(old_consumer_state)) + AwakeConsumerWaitersForStateChangeNoLock(new_consumer_state); + return rv; +} + +MojoResult DataPipe::ProducerBeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) { + base::AutoLock locker(lock_); + DCHECK(has_local_producer_no_lock()); + + if (producer_in_two_phase_write_no_lock()) + return MOJO_RESULT_BUSY; + if (!consumer_open_no_lock()) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (all_or_none && *buffer_num_bytes % element_num_bytes_ != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + MojoResult rv = ProducerBeginWriteDataImplNoLock(buffer, buffer_num_bytes, + all_or_none); + if (rv != MOJO_RESULT_OK) + return rv; + // Note: No need to awake producer waiters, even though we're going from + // writable to non-writable (since you can't wait on non-writability). + // Similarly, though this may have discarded data (in "may discard" mode), + // making it non-readable, there's still no need to awake consumer waiters. + DCHECK(producer_in_two_phase_write_no_lock()); + return MOJO_RESULT_OK; +} + +MojoResult DataPipe::ProducerEndWriteData(uint32_t num_bytes_written) { + base::AutoLock locker(lock_); + DCHECK(has_local_producer_no_lock()); + + if (!producer_in_two_phase_write_no_lock()) + return MOJO_RESULT_FAILED_PRECONDITION; + // Note: Allow successful completion of the two-phase write even if the + // consumer has been closed. + + HandleSignalsState old_consumer_state = ConsumerGetHandleSignalsStateNoLock(); + MojoResult rv; + if (num_bytes_written > producer_two_phase_max_num_bytes_written_ || + num_bytes_written % element_num_bytes_ != 0) { + rv = MOJO_RESULT_INVALID_ARGUMENT; + producer_two_phase_max_num_bytes_written_ = 0; + } else { + rv = ProducerEndWriteDataImplNoLock(num_bytes_written); + } + // Two-phase write ended even on failure. + DCHECK(!producer_in_two_phase_write_no_lock()); + // If we're now writable, we *became* writable (since we weren't writable + // during the two-phase write), so awake producer waiters. + HandleSignalsState new_producer_state = ProducerGetHandleSignalsStateNoLock(); + if (new_producer_state.satisfies(MOJO_HANDLE_SIGNAL_WRITABLE)) + AwakeProducerWaitersForStateChangeNoLock(new_producer_state); + HandleSignalsState new_consumer_state = ConsumerGetHandleSignalsStateNoLock(); + if (!new_consumer_state.equals(old_consumer_state)) + AwakeConsumerWaitersForStateChangeNoLock(new_consumer_state); + return rv; +} + +MojoResult DataPipe::ProducerAddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + base::AutoLock locker(lock_); + DCHECK(has_local_producer_no_lock()); + + HandleSignalsState producer_state = ProducerGetHandleSignalsStateNoLock(); + if (producer_state.satisfies(signals)) + return MOJO_RESULT_ALREADY_EXISTS; + if (!producer_state.can_satisfy(signals)) + return MOJO_RESULT_FAILED_PRECONDITION; + + producer_waiter_list_->AddWaiter(waiter, signals, context); + return MOJO_RESULT_OK; +} + +void DataPipe::ProducerRemoveWaiter(Waiter* waiter) { + base::AutoLock locker(lock_); + DCHECK(has_local_producer_no_lock()); + producer_waiter_list_->RemoveWaiter(waiter); +} + +bool DataPipe::ProducerIsBusy() const { + base::AutoLock locker(lock_); + return producer_in_two_phase_write_no_lock(); +} + +void DataPipe::ConsumerCancelAllWaiters() { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + consumer_waiter_list_->CancelAllWaiters(); +} + +void DataPipe::ConsumerClose() { + base::AutoLock locker(lock_); + DCHECK(consumer_open_); + consumer_open_ = false; + DCHECK(has_local_consumer_no_lock()); + consumer_waiter_list_.reset(); + // Not a bug, except possibly in "user" code. + DVLOG_IF(2, consumer_in_two_phase_read_no_lock()) + << "Consumer closed with active two-phase read"; + consumer_two_phase_max_num_bytes_read_ = 0; + ConsumerCloseImplNoLock(); + AwakeProducerWaitersForStateChangeNoLock( + ProducerGetHandleSignalsStateNoLock()); +} + +MojoResult DataPipe::ConsumerReadData(void* elements, + uint32_t* num_bytes, + bool all_or_none) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + + if (consumer_in_two_phase_read_no_lock()) + return MOJO_RESULT_BUSY; + + if (*num_bytes % element_num_bytes_ != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (*num_bytes == 0) + return MOJO_RESULT_OK; // Nothing to do. + + HandleSignalsState old_producer_state = ProducerGetHandleSignalsStateNoLock(); + MojoResult rv = ConsumerReadDataImplNoLock(elements, num_bytes, all_or_none); + HandleSignalsState new_producer_state = ProducerGetHandleSignalsStateNoLock(); + if (!new_producer_state.equals(old_producer_state)) + AwakeProducerWaitersForStateChangeNoLock(new_producer_state); + return rv; +} + +MojoResult DataPipe::ConsumerDiscardData(uint32_t* num_bytes, + bool all_or_none) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + + if (consumer_in_two_phase_read_no_lock()) + return MOJO_RESULT_BUSY; + + if (*num_bytes % element_num_bytes_ != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (*num_bytes == 0) + return MOJO_RESULT_OK; // Nothing to do. + + HandleSignalsState old_producer_state = ProducerGetHandleSignalsStateNoLock(); + MojoResult rv = ConsumerDiscardDataImplNoLock(num_bytes, all_or_none); + HandleSignalsState new_producer_state = ProducerGetHandleSignalsStateNoLock(); + if (!new_producer_state.equals(old_producer_state)) + AwakeProducerWaitersForStateChangeNoLock(new_producer_state); + return rv; +} + +MojoResult DataPipe::ConsumerQueryData(uint32_t* num_bytes) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + + if (consumer_in_two_phase_read_no_lock()) + return MOJO_RESULT_BUSY; + + // Note: Don't need to validate |*num_bytes| for query. + return ConsumerQueryDataImplNoLock(num_bytes); +} + +MojoResult DataPipe::ConsumerBeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + + if (consumer_in_two_phase_read_no_lock()) + return MOJO_RESULT_BUSY; + + if (all_or_none && *buffer_num_bytes % element_num_bytes_ != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + MojoResult rv = ConsumerBeginReadDataImplNoLock(buffer, buffer_num_bytes, + all_or_none); + if (rv != MOJO_RESULT_OK) + return rv; + DCHECK(consumer_in_two_phase_read_no_lock()); + return MOJO_RESULT_OK; +} + +MojoResult DataPipe::ConsumerEndReadData(uint32_t num_bytes_read) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + + if (!consumer_in_two_phase_read_no_lock()) + return MOJO_RESULT_FAILED_PRECONDITION; + + HandleSignalsState old_producer_state = ProducerGetHandleSignalsStateNoLock(); + MojoResult rv; + if (num_bytes_read > consumer_two_phase_max_num_bytes_read_ || + num_bytes_read % element_num_bytes_ != 0) { + rv = MOJO_RESULT_INVALID_ARGUMENT; + consumer_two_phase_max_num_bytes_read_ = 0; + } else { + rv = ConsumerEndReadDataImplNoLock(num_bytes_read); + } + // Two-phase read ended even on failure. + DCHECK(!consumer_in_two_phase_read_no_lock()); + // If we're now readable, we *became* readable (since we weren't readable + // during the two-phase read), so awake consumer waiters. + HandleSignalsState new_consumer_state = ConsumerGetHandleSignalsStateNoLock(); + if (new_consumer_state.satisfies(MOJO_HANDLE_SIGNAL_READABLE)) + AwakeConsumerWaitersForStateChangeNoLock(new_consumer_state); + HandleSignalsState new_producer_state = ProducerGetHandleSignalsStateNoLock(); + if (!new_producer_state.equals(old_producer_state)) + AwakeProducerWaitersForStateChangeNoLock(new_producer_state); + return rv; +} + +MojoResult DataPipe::ConsumerAddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + + HandleSignalsState consumer_state = ConsumerGetHandleSignalsStateNoLock(); + if (consumer_state.satisfies(signals)) + return MOJO_RESULT_ALREADY_EXISTS; + if (!consumer_state.can_satisfy(signals)) + return MOJO_RESULT_FAILED_PRECONDITION; + + consumer_waiter_list_->AddWaiter(waiter, signals, context); + return MOJO_RESULT_OK; +} + +void DataPipe::ConsumerRemoveWaiter(Waiter* waiter) { + base::AutoLock locker(lock_); + DCHECK(has_local_consumer_no_lock()); + consumer_waiter_list_->RemoveWaiter(waiter); +} + +bool DataPipe::ConsumerIsBusy() const { + base::AutoLock locker(lock_); + return consumer_in_two_phase_read_no_lock(); +} + + +DataPipe::DataPipe(bool has_local_producer, + bool has_local_consumer, + const MojoCreateDataPipeOptions& validated_options) + : may_discard_((validated_options.flags & + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD)), + element_num_bytes_(validated_options.element_num_bytes), + capacity_num_bytes_(validated_options.capacity_num_bytes), + producer_open_(true), + consumer_open_(true), + producer_waiter_list_(has_local_producer ? new WaiterList() : NULL), + consumer_waiter_list_(has_local_consumer ? new WaiterList() : NULL), + producer_two_phase_max_num_bytes_written_(0), + consumer_two_phase_max_num_bytes_read_(0) { + // Check that the passed in options actually are validated. + MojoCreateDataPipeOptions unused ALLOW_UNUSED = { 0 }; + DCHECK_EQ(ValidateCreateOptions(&validated_options, &unused), MOJO_RESULT_OK); +} + +DataPipe::~DataPipe() { + DCHECK(!producer_open_); + DCHECK(!consumer_open_); + DCHECK(!producer_waiter_list_); + DCHECK(!consumer_waiter_list_); +} + +void DataPipe::AwakeProducerWaitersForStateChangeNoLock( + const HandleSignalsState& new_producer_state) { + lock_.AssertAcquired(); + if (!has_local_producer_no_lock()) + return; + producer_waiter_list_->AwakeWaitersForStateChange(new_producer_state); +} + +void DataPipe::AwakeConsumerWaitersForStateChangeNoLock( + const HandleSignalsState& new_consumer_state) { + lock_.AssertAcquired(); + if (!has_local_consumer_no_lock()) + return; + consumer_waiter_list_->AwakeWaitersForStateChange(new_consumer_state); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/data_pipe.h b/chromium/mojo/system/data_pipe.h new file mode 100644 index 00000000000..2cab507e462 --- /dev/null +++ b/chromium/mojo/system/data_pipe.h @@ -0,0 +1,205 @@ +// 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 MOJO_SYSTEM_DATA_PIPE_H_ +#define MOJO_SYSTEM_DATA_PIPE_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/handle_signals_state.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Waiter; +class WaiterList; + +// |DataPipe| is a base class for secondary objects implementing data pipes, +// similar to |MessagePipe| (see the explanatory comment in core.cc). It is +// typically owned by the dispatcher(s) corresponding to the local endpoints. +// Its subclasses implement the three cases: local producer and consumer, local +// producer and remote consumer, and remote producer and local consumer. This +// class is thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipe : + public base::RefCountedThreadSafe<DataPipe> { + public: + // The default options for |MojoCreateDataPipe()|. (Real uses should obtain + // this via |ValidateCreateOptions()| with a null |in_options|; this is + // exposed directly for testing convenience.) + static const MojoCreateDataPipeOptions kDefaultCreateOptions; + + // Validates and/or sets default options for |MojoCreateDataPipeOptions|. If + // non-null, |in_options| must point to a struct of at least + // |in_options->struct_size| bytes. |out_options| must point to a (current) + // |MojoCreateDataPipeOptions| and will be entirely overwritten on success (it + // may be partly overwritten on failure). + static MojoResult ValidateCreateOptions( + const MojoCreateDataPipeOptions* in_options, + MojoCreateDataPipeOptions* out_options); + + // These are called by the producer dispatcher to implement its methods of + // corresponding names. + void ProducerCancelAllWaiters(); + void ProducerClose(); + // This does not validate its arguments, except to check that |*num_bytes| is + // a multiple of |element_num_bytes_|. + MojoResult ProducerWriteData(const void* elements, + uint32_t* num_bytes, + bool all_or_none); + // This does not validate its arguments. + MojoResult ProducerBeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none); + MojoResult ProducerEndWriteData(uint32_t num_bytes_written); + MojoResult ProducerAddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context); + void ProducerRemoveWaiter(Waiter* waiter); + bool ProducerIsBusy() const; + + // These are called by the consumer dispatcher to implement its methods of + // corresponding names. + void ConsumerCancelAllWaiters(); + void ConsumerClose(); + // This does not validate its arguments, except to check that |*num_bytes| is + // a multiple of |element_num_bytes_|. + MojoResult ConsumerReadData(void* elements, + uint32_t* num_bytes, + bool all_or_none); + MojoResult ConsumerDiscardData(uint32_t* num_bytes, + bool all_or_none); + MojoResult ConsumerQueryData(uint32_t* num_bytes); + // This does not validate its arguments. + MojoResult ConsumerBeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none); + MojoResult ConsumerEndReadData(uint32_t num_bytes_read); + MojoResult ConsumerAddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context); + void ConsumerRemoveWaiter(Waiter* waiter); + bool ConsumerIsBusy() const; + + protected: + DataPipe(bool has_local_producer, + bool has_local_consumer, + const MojoCreateDataPipeOptions& validated_options); + + friend class base::RefCountedThreadSafe<DataPipe>; + virtual ~DataPipe(); + + virtual void ProducerCloseImplNoLock() = 0; + // |*num_bytes| will be a nonzero multiple of |element_num_bytes_|. + virtual MojoResult ProducerWriteDataImplNoLock(const void* elements, + uint32_t* num_bytes, + bool all_or_none) = 0; + virtual MojoResult ProducerBeginWriteDataImplNoLock( + void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) = 0; + virtual MojoResult ProducerEndWriteDataImplNoLock( + uint32_t num_bytes_written) = 0; + // Note: A producer should not be writable during a two-phase write. + virtual HandleSignalsState ProducerGetHandleSignalsStateNoLock() const = 0; + + virtual void ConsumerCloseImplNoLock() = 0; + // |*num_bytes| will be a nonzero multiple of |element_num_bytes_|. + virtual MojoResult ConsumerReadDataImplNoLock(void* elements, + uint32_t* num_bytes, + bool all_or_none) = 0; + virtual MojoResult ConsumerDiscardDataImplNoLock(uint32_t* num_bytes, + bool all_or_none) = 0; + // |*num_bytes| will be a nonzero multiple of |element_num_bytes_|. + virtual MojoResult ConsumerQueryDataImplNoLock(uint32_t* num_bytes) = 0; + virtual MojoResult ConsumerBeginReadDataImplNoLock(const void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) = 0; + virtual MojoResult ConsumerEndReadDataImplNoLock(uint32_t num_bytes_read) = 0; + // Note: A consumer should not be writable during a two-phase read. + virtual HandleSignalsState ConsumerGetHandleSignalsStateNoLock() const = 0; + + // Thread-safe and fast (they don't take the lock): + bool may_discard() const { return may_discard_; } + size_t element_num_bytes() const { return element_num_bytes_; } + size_t capacity_num_bytes() const { return capacity_num_bytes_; } + + // Must be called under lock. + bool producer_open_no_lock() const { + lock_.AssertAcquired(); + return producer_open_; + } + bool consumer_open_no_lock() const { + lock_.AssertAcquired(); + return consumer_open_; + } + uint32_t producer_two_phase_max_num_bytes_written_no_lock() const { + lock_.AssertAcquired(); + return producer_two_phase_max_num_bytes_written_; + } + uint32_t consumer_two_phase_max_num_bytes_read_no_lock() const { + lock_.AssertAcquired(); + return consumer_two_phase_max_num_bytes_read_; + } + void set_producer_two_phase_max_num_bytes_written_no_lock( + uint32_t num_bytes) { + lock_.AssertAcquired(); + producer_two_phase_max_num_bytes_written_ = num_bytes; + } + void set_consumer_two_phase_max_num_bytes_read_no_lock(uint32_t num_bytes) { + lock_.AssertAcquired(); + consumer_two_phase_max_num_bytes_read_ = num_bytes; + } + bool producer_in_two_phase_write_no_lock() const { + lock_.AssertAcquired(); + return producer_two_phase_max_num_bytes_written_ > 0; + } + bool consumer_in_two_phase_read_no_lock() const { + lock_.AssertAcquired(); + return consumer_two_phase_max_num_bytes_read_ > 0; + } + + private: + void AwakeProducerWaitersForStateChangeNoLock( + const HandleSignalsState& new_producer_state); + void AwakeConsumerWaitersForStateChangeNoLock( + const HandleSignalsState& new_consumer_state); + + bool has_local_producer_no_lock() const { + lock_.AssertAcquired(); + return !!producer_waiter_list_; + } + bool has_local_consumer_no_lock() const { + lock_.AssertAcquired(); + return !!consumer_waiter_list_; + } + + const bool may_discard_; + const size_t element_num_bytes_; + const size_t capacity_num_bytes_; + + mutable base::Lock lock_; // Protects the following members. + // *Known* state of producer or consumer. + bool producer_open_; + bool consumer_open_; + // Non-null only if the producer or consumer, respectively, is local. + scoped_ptr<WaiterList> producer_waiter_list_; + scoped_ptr<WaiterList> consumer_waiter_list_; + // These are nonzero if and only if a two-phase write/read is in progress. + uint32_t producer_two_phase_max_num_bytes_written_; + uint32_t consumer_two_phase_max_num_bytes_read_; + + DISALLOW_COPY_AND_ASSIGN(DataPipe); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_DATA_PIPE_H_ diff --git a/chromium/mojo/system/data_pipe_consumer_dispatcher.cc b/chromium/mojo/system/data_pipe_consumer_dispatcher.cc new file mode 100644 index 00000000000..99818e2a120 --- /dev/null +++ b/chromium/mojo/system/data_pipe_consumer_dispatcher.cc @@ -0,0 +1,130 @@ +// 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 "mojo/system/data_pipe_consumer_dispatcher.h" + +#include "base/logging.h" +#include "mojo/system/data_pipe.h" +#include "mojo/system/memory.h" + +namespace mojo { +namespace system { + +DataPipeConsumerDispatcher::DataPipeConsumerDispatcher() { +} + +void DataPipeConsumerDispatcher::Init(scoped_refptr<DataPipe> data_pipe) { + DCHECK(data_pipe); + data_pipe_ = data_pipe; +} + +Dispatcher::Type DataPipeConsumerDispatcher::GetType() const { + return kTypeDataPipeConsumer; +} + +DataPipeConsumerDispatcher::~DataPipeConsumerDispatcher() { + // |Close()|/|CloseImplNoLock()| should have taken care of the pipe. + DCHECK(!data_pipe_); +} + +void DataPipeConsumerDispatcher::CancelAllWaitersNoLock() { + lock().AssertAcquired(); + data_pipe_->ConsumerCancelAllWaiters(); +} + +void DataPipeConsumerDispatcher::CloseImplNoLock() { + lock().AssertAcquired(); + data_pipe_->ConsumerClose(); + data_pipe_ = NULL; +} + +scoped_refptr<Dispatcher> +DataPipeConsumerDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() { + lock().AssertAcquired(); + + scoped_refptr<DataPipeConsumerDispatcher> rv = + new DataPipeConsumerDispatcher(); + rv->Init(data_pipe_); + data_pipe_ = NULL; + return scoped_refptr<Dispatcher>(rv.get()); +} + +MojoResult DataPipeConsumerDispatcher::ReadDataImplNoLock( + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + lock().AssertAcquired(); + + if (!VerifyUserPointer<uint32_t>(num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + + if ((flags & MOJO_READ_DATA_FLAG_DISCARD)) { + // These flags are mutally exclusive. + if ((flags & MOJO_READ_DATA_FLAG_QUERY)) + return MOJO_RESULT_INVALID_ARGUMENT; + DVLOG_IF(2, elements) << "Discard mode: ignoring non-null |elements|"; + return data_pipe_->ConsumerDiscardData( + num_bytes, (flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + } + + if ((flags & MOJO_READ_DATA_FLAG_QUERY)) { + DCHECK(!(flags & MOJO_READ_DATA_FLAG_DISCARD)); // Handled above. + DVLOG_IF(2, elements) << "Query mode: ignoring non-null |elements|"; + return data_pipe_->ConsumerQueryData(num_bytes); + } + + // Only verify |elements| if we're neither discarding nor querying. + if (!VerifyUserPointerWithSize<1>(elements, *num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + + return data_pipe_->ConsumerReadData( + elements, num_bytes, (flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE)); +} + +MojoResult DataPipeConsumerDispatcher::BeginReadDataImplNoLock( + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + lock().AssertAcquired(); + + if (!VerifyUserPointerWithCount<const void*>(buffer, 1)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointer<uint32_t>(buffer_num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + // These flags may not be used in two-phase mode. + if ((flags & MOJO_READ_DATA_FLAG_DISCARD) || + (flags & MOJO_READ_DATA_FLAG_QUERY)) + return MOJO_RESULT_INVALID_ARGUMENT; + + return data_pipe_->ConsumerBeginReadData( + buffer, buffer_num_bytes, (flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE)); +} + +MojoResult DataPipeConsumerDispatcher::EndReadDataImplNoLock( + uint32_t num_bytes_read) { + lock().AssertAcquired(); + + return data_pipe_->ConsumerEndReadData(num_bytes_read); +} + +MojoResult DataPipeConsumerDispatcher::AddWaiterImplNoLock( + Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + lock().AssertAcquired(); + return data_pipe_->ConsumerAddWaiter(waiter, signals, context); +} + +void DataPipeConsumerDispatcher::RemoveWaiterImplNoLock(Waiter* waiter) { + lock().AssertAcquired(); + data_pipe_->ConsumerRemoveWaiter(waiter); +} + +bool DataPipeConsumerDispatcher::IsBusyNoLock() const { + lock().AssertAcquired(); + return data_pipe_->ConsumerIsBusy(); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/data_pipe_consumer_dispatcher.h b/chromium/mojo/system/data_pipe_consumer_dispatcher.h new file mode 100644 index 00000000000..390d0bfe917 --- /dev/null +++ b/chromium/mojo/system/data_pipe_consumer_dispatcher.h @@ -0,0 +1,62 @@ +// 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 MOJO_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ +#define MOJO_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class DataPipe; + +// This is the |Dispatcher| implementation for the consumer handle for data +// pipes (created by the Mojo primitive |MojoCreateDataPipe()|). This class is +// thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher : public Dispatcher { + public: + DataPipeConsumerDispatcher(); + + // Must be called before any other methods. + void Init(scoped_refptr<DataPipe> data_pipe); + + // |Dispatcher| public methods: + virtual Type GetType() const OVERRIDE; + + private: + virtual ~DataPipeConsumerDispatcher(); + + // |Dispatcher| protected methods: + virtual void CancelAllWaitersNoLock() OVERRIDE; + virtual void CloseImplNoLock() OVERRIDE; + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE; + virtual MojoResult ReadDataImplNoLock(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) OVERRIDE; + virtual MojoResult BeginReadDataImplNoLock(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) OVERRIDE; + virtual MojoResult EndReadDataImplNoLock(uint32_t num_bytes_read) OVERRIDE; + virtual MojoResult AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) OVERRIDE; + virtual void RemoveWaiterImplNoLock(Waiter* waiter) OVERRIDE; + virtual bool IsBusyNoLock() const OVERRIDE; + + // Protected by |lock()|: + scoped_refptr<DataPipe> data_pipe_; // This will be null if closed. + + DISALLOW_COPY_AND_ASSIGN(DataPipeConsumerDispatcher); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ diff --git a/chromium/mojo/system/data_pipe_producer_dispatcher.cc b/chromium/mojo/system/data_pipe_producer_dispatcher.cc new file mode 100644 index 00000000000..ec99549b543 --- /dev/null +++ b/chromium/mojo/system/data_pipe_producer_dispatcher.cc @@ -0,0 +1,109 @@ +// 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 "mojo/system/data_pipe_producer_dispatcher.h" + +#include "base/logging.h" +#include "mojo/system/data_pipe.h" +#include "mojo/system/memory.h" + +namespace mojo { +namespace system { + +DataPipeProducerDispatcher::DataPipeProducerDispatcher() { +} + +void DataPipeProducerDispatcher::Init(scoped_refptr<DataPipe> data_pipe) { + DCHECK(data_pipe); + data_pipe_ = data_pipe; +} + +Dispatcher::Type DataPipeProducerDispatcher::GetType() const { + return kTypeDataPipeProducer; +} + +DataPipeProducerDispatcher::~DataPipeProducerDispatcher() { + // |Close()|/|CloseImplNoLock()| should have taken care of the pipe. + DCHECK(!data_pipe_); +} + +void DataPipeProducerDispatcher::CancelAllWaitersNoLock() { + lock().AssertAcquired(); + data_pipe_->ProducerCancelAllWaiters(); +} + +void DataPipeProducerDispatcher::CloseImplNoLock() { + lock().AssertAcquired(); + data_pipe_->ProducerClose(); + data_pipe_ = NULL; +} + +scoped_refptr<Dispatcher> +DataPipeProducerDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() { + lock().AssertAcquired(); + + scoped_refptr<DataPipeProducerDispatcher> rv = + new DataPipeProducerDispatcher(); + rv->Init(data_pipe_); + data_pipe_ = NULL; + return scoped_refptr<Dispatcher>(rv.get()); +} + +MojoResult DataPipeProducerDispatcher::WriteDataImplNoLock( + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + lock().AssertAcquired(); + + if (!VerifyUserPointer<uint32_t>(num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointerWithSize<1>(elements, *num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + + return data_pipe_->ProducerWriteData( + elements, num_bytes, (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); +} + +MojoResult DataPipeProducerDispatcher::BeginWriteDataImplNoLock( + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + lock().AssertAcquired(); + + if (!VerifyUserPointerWithCount<void*>(buffer, 1)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointer<uint32_t>(buffer_num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + + return data_pipe_->ProducerBeginWriteData( + buffer, buffer_num_bytes, (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); +} + +MojoResult DataPipeProducerDispatcher::EndWriteDataImplNoLock( + uint32_t num_bytes_written) { + lock().AssertAcquired(); + + return data_pipe_->ProducerEndWriteData(num_bytes_written); +} + +MojoResult DataPipeProducerDispatcher::AddWaiterImplNoLock( + Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + lock().AssertAcquired(); + return data_pipe_->ProducerAddWaiter(waiter, signals, context); +} + +void DataPipeProducerDispatcher::RemoveWaiterImplNoLock(Waiter* waiter) { + lock().AssertAcquired(); + data_pipe_->ProducerRemoveWaiter(waiter); +} + +bool DataPipeProducerDispatcher::IsBusyNoLock() const { + lock().AssertAcquired(); + return data_pipe_->ProducerIsBusy(); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/data_pipe_producer_dispatcher.h b/chromium/mojo/system/data_pipe_producer_dispatcher.h new file mode 100644 index 00000000000..63ad3184c50 --- /dev/null +++ b/chromium/mojo/system/data_pipe_producer_dispatcher.h @@ -0,0 +1,64 @@ +// 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 MOJO_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ +#define MOJO_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class DataPipe; + +// This is the |Dispatcher| implementation for the producer handle for data +// pipes (created by the Mojo primitive |MojoCreateDataPipe()|). This class is +// thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher : public Dispatcher { + public: + DataPipeProducerDispatcher(); + + // Must be called before any other methods. + void Init(scoped_refptr<DataPipe> data_pipe); + + // |Dispatcher| public methods: + virtual Type GetType() const OVERRIDE; + + private: + virtual ~DataPipeProducerDispatcher(); + + // |Dispatcher| protected methods: + virtual void CancelAllWaitersNoLock() OVERRIDE; + virtual void CloseImplNoLock() OVERRIDE; + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE; + virtual MojoResult WriteDataImplNoLock(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) OVERRIDE; + virtual MojoResult BeginWriteDataImplNoLock( + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) OVERRIDE; + virtual MojoResult EndWriteDataImplNoLock( + uint32_t num_bytes_written) OVERRIDE; + virtual MojoResult AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) OVERRIDE; + virtual void RemoveWaiterImplNoLock(Waiter* waiter) OVERRIDE; + virtual bool IsBusyNoLock() const OVERRIDE; + + // Protected by |lock()|: + scoped_refptr<DataPipe> data_pipe_; // This will be null if closed. + + DISALLOW_COPY_AND_ASSIGN(DataPipeProducerDispatcher); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ diff --git a/chromium/mojo/system/data_pipe_unittest.cc b/chromium/mojo/system/data_pipe_unittest.cc new file mode 100644 index 00000000000..6aa2ce786d0 --- /dev/null +++ b/chromium/mojo/system/data_pipe_unittest.cc @@ -0,0 +1,360 @@ +// 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 "mojo/system/data_pipe.h" + +#include <stddef.h> + +#include <limits> + +#include "base/basictypes.h" +#include "mojo/system/constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +const uint32_t kSizeOfCreateOptions = + static_cast<uint32_t>(sizeof(MojoCreateDataPipeOptions)); + +// Does a cursory sanity check of |validated_options|. Calls +// |ValidateCreateOptions()| on already-validated options. The validated options +// should be valid, and the revalidated copy should be the same. +void RevalidateCreateOptions( + const MojoCreateDataPipeOptions& validated_options) { + EXPECT_EQ(kSizeOfCreateOptions, validated_options.struct_size); + // Nothing to check for flags. + EXPECT_GT(validated_options.element_num_bytes, 0u); + EXPECT_GT(validated_options.capacity_num_bytes, 0u); + EXPECT_EQ(0u, + validated_options.capacity_num_bytes % + validated_options.element_num_bytes); + + MojoCreateDataPipeOptions revalidated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&validated_options, + &revalidated_options)); + EXPECT_EQ(validated_options.struct_size, revalidated_options.struct_size); + EXPECT_EQ(validated_options.element_num_bytes, + revalidated_options.element_num_bytes); + EXPECT_EQ(validated_options.capacity_num_bytes, + revalidated_options.capacity_num_bytes); + EXPECT_EQ(validated_options.flags, revalidated_options.flags); +} + +// Checks that a default-computed capacity is correct. (Does not duplicate the +// checks done by |RevalidateCreateOptions()|.) +void CheckDefaultCapacity(const MojoCreateDataPipeOptions& validated_options) { + EXPECT_LE(validated_options.capacity_num_bytes, + kDefaultDataPipeCapacityBytes); + EXPECT_GT(validated_options.capacity_num_bytes + + validated_options.element_num_bytes, + kDefaultDataPipeCapacityBytes); +} + +// Tests valid inputs to |ValidateCreateOptions()|. +TEST(DataPipeTest, ValidateCreateOptionsValid) { + // Default options. + { + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(NULL, &validated_options)); + RevalidateCreateOptions(validated_options); + CheckDefaultCapacity(validated_options); + } + + // Size member, but nothing beyond. + { + MojoCreateDataPipeOptions options = { + offsetof(MojoCreateDataPipeOptions, flags) // |struct_size|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + RevalidateCreateOptions(validated_options); + CheckDefaultCapacity(validated_options); + } + + // Different flags. + MojoCreateDataPipeOptionsFlags flags_values[] = { + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD + }; + for (size_t i = 0; i < arraysize(flags_values); i++) { + const MojoCreateDataPipeOptionsFlags flags = flags_values[i]; + + // Flags member, but nothing beyond. + { + MojoCreateDataPipeOptions options = { + // |struct_size|. + offsetof(MojoCreateDataPipeOptions, element_num_bytes), + flags // |flags|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + CheckDefaultCapacity(validated_options); + } + + // Different capacities (size 1). + for (uint32_t capacity = 1; capacity <= 100 * 1000 * 1000; capacity *= 10) { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags, // |flags|. + 1, // |element_num_bytes|. + capacity // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << capacity; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + EXPECT_EQ(options.capacity_num_bytes, + validated_options.capacity_num_bytes); + } + + // Small sizes. + for (uint32_t size = 1; size < 100; size++) { + // Different capacities. + for (uint32_t elements = 1; elements <= 1000 * 1000; elements *= 10) { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags, // |flags|. + size, // |element_num_bytes|. + size * elements // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << size << ", " << elements; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + EXPECT_EQ(options.capacity_num_bytes, + validated_options.capacity_num_bytes); + } + + // Default capacity. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags, // |flags|. + size, // |element_num_bytes|. + 0 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << size; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + CheckDefaultCapacity(validated_options); + } + + // No capacity field. + { + MojoCreateDataPipeOptions options = { + // |struct_size|. + offsetof(MojoCreateDataPipeOptions, capacity_num_bytes), + flags, // |flags|. + size // |element_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << size; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + CheckDefaultCapacity(validated_options); + } + } + + // Larger sizes. + for (uint32_t size = 100; size <= 100 * 1000; size *= 10) { + // Capacity of 1000 elements. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags, // |flags|. + size, // |element_num_bytes|. + 1000 * size // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << size; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + EXPECT_EQ(options.capacity_num_bytes, + validated_options.capacity_num_bytes); + } + + // Default capacity. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags, // |flags|. + size, // |element_num_bytes|. + 0 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << size; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + CheckDefaultCapacity(validated_options); + } + + // No capacity field. + { + MojoCreateDataPipeOptions options = { + // |struct_size|. + offsetof(MojoCreateDataPipeOptions, capacity_num_bytes), + flags, // |flags|. + size // |element_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)) + << size; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + EXPECT_EQ(options.element_num_bytes, + validated_options.element_num_bytes); + CheckDefaultCapacity(validated_options); + } + } + } +} + +TEST(DataPipeTest, ValidateCreateOptionsInvalid) { + // Invalid |struct_size|. + { + MojoCreateDataPipeOptions options = { + 1, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + 0 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + + // Unknown |flags|. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + ~0u, // |flags|. + 1, // |element_num_bytes|. + 0 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + + // Invalid |element_num_bytes|. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 0, // |element_num_bytes|. + 1000 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + // |element_num_bytes| too big. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + std::numeric_limits<uint32_t>::max(), // |element_num_bytes|. + std::numeric_limits<uint32_t>::max() // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + std::numeric_limits<uint32_t>::max() - 1000, // |element_num_bytes|. + std::numeric_limits<uint32_t>::max() - 1000 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + + // Invalid |capacity_num_bytes|. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 2, // |element_num_bytes|. + 1 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 2, // |element_num_bytes|. + 111 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 5, // |element_num_bytes|. + 104 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + DataPipe::ValidateCreateOptions(&options, &unused)); + } + // |capacity_num_bytes| too big. + { + MojoCreateDataPipeOptions options = { + kSizeOfCreateOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 8, // |element_num_bytes|. + 0xffff0000 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions unused; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + DataPipe::ValidateCreateOptions(&options, &unused)); + } +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/dispatcher.cc b/chromium/mojo/system/dispatcher.cc new file mode 100644 index 00000000000..d03fca71ab1 --- /dev/null +++ b/chromium/mojo/system/dispatcher.cc @@ -0,0 +1,461 @@ +// 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 "mojo/system/dispatcher.h" + +#include "base/logging.h" +#include "mojo/system/constants.h" +#include "mojo/system/message_pipe_dispatcher.h" +#include "mojo/system/platform_handle_dispatcher.h" +#include "mojo/system/shared_buffer_dispatcher.h" + +namespace mojo { +namespace system { + +namespace test { + +// TODO(vtl): Maybe this should be defined in a test-only file instead. +DispatcherTransport DispatcherTryStartTransport( + Dispatcher* dispatcher) { + return Dispatcher::HandleTableAccess::TryStartTransport(dispatcher); +} + +} // namespace test + +// Dispatcher ------------------------------------------------------------------ + +// static +DispatcherTransport Dispatcher::HandleTableAccess::TryStartTransport( + Dispatcher* dispatcher) { + DCHECK(dispatcher); + + if (!dispatcher->lock_.Try()) + return DispatcherTransport(); + + // We shouldn't race with things that close dispatchers, since closing can + // only take place either under |handle_table_lock_| or when the handle is + // marked as busy. + DCHECK(!dispatcher->is_closed_); + + return DispatcherTransport(dispatcher); +} + +// static +void Dispatcher::TransportDataAccess::StartSerialize( + Dispatcher* dispatcher, + Channel* channel, + size_t* max_size, + size_t* max_platform_handles) { + DCHECK(dispatcher); + dispatcher->StartSerialize(channel, max_size, max_platform_handles); +} + +// static +bool Dispatcher::TransportDataAccess::EndSerializeAndClose( + Dispatcher* dispatcher, + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) { + DCHECK(dispatcher); + return dispatcher->EndSerializeAndClose(channel, destination, actual_size, + platform_handles); +} + +// static +scoped_refptr<Dispatcher> Dispatcher::TransportDataAccess::Deserialize( + Channel* channel, + int32_t type, + const void* source, + size_t size, + embedder::PlatformHandleVector* platform_handles) { + switch (static_cast<int32_t>(type)) { + case kTypeUnknown: + DVLOG(2) << "Deserializing invalid handle"; + return scoped_refptr<Dispatcher>(); + case kTypeMessagePipe: + return scoped_refptr<Dispatcher>( + MessagePipeDispatcher::Deserialize(channel, source, size)); + case kTypeDataPipeProducer: + case kTypeDataPipeConsumer: + // TODO(vtl): Implement. + LOG(WARNING) << "Deserialization of dispatcher type " << type + << " not supported"; + return scoped_refptr<Dispatcher>(); + case kTypeSharedBuffer: + return scoped_refptr<Dispatcher>( + SharedBufferDispatcher::Deserialize(channel, source, size, + platform_handles)); + case kTypePlatformHandle: + return scoped_refptr<Dispatcher>( + PlatformHandleDispatcher::Deserialize(channel, source, size, + platform_handles)); + } + LOG(WARNING) << "Unknown dispatcher type " << type; + return scoped_refptr<Dispatcher>(); +} + +MojoResult Dispatcher::Close() { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + CloseNoLock(); + return MOJO_RESULT_OK; +} + +MojoResult Dispatcher::WriteMessage( + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags) { + DCHECK(!transports || (transports->size() > 0 && + transports->size() < kMaxMessageNumHandles)); + + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return WriteMessageImplNoLock(bytes, num_bytes, transports, flags); +} + +MojoResult Dispatcher::ReadMessage(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags) { + DCHECK(!num_dispatchers || *num_dispatchers == 0 || + (dispatchers && dispatchers->empty())); + + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return ReadMessageImplNoLock(bytes, num_bytes, dispatchers, num_dispatchers, + flags); +} + +MojoResult Dispatcher::WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return WriteDataImplNoLock(elements, num_bytes, flags); +} + +MojoResult Dispatcher::BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return BeginWriteDataImplNoLock(buffer, buffer_num_bytes, flags); +} + +MojoResult Dispatcher::EndWriteData(uint32_t num_bytes_written) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return EndWriteDataImplNoLock(num_bytes_written); +} + +MojoResult Dispatcher::ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return ReadDataImplNoLock(elements, num_bytes, flags); +} + +MojoResult Dispatcher::BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return BeginReadDataImplNoLock(buffer, buffer_num_bytes, flags); +} + +MojoResult Dispatcher::EndReadData(uint32_t num_bytes_read) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return EndReadDataImplNoLock(num_bytes_read); +} + +MojoResult Dispatcher::DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr<Dispatcher>* new_dispatcher) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return DuplicateBufferHandleImplNoLock(options, new_dispatcher); +} + +MojoResult Dispatcher::MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + scoped_ptr<RawSharedBufferMapping>* mapping) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return MapBufferImplNoLock(offset, num_bytes, flags, mapping); +} + +MojoResult Dispatcher::AddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + base::AutoLock locker(lock_); + if (is_closed_) + return MOJO_RESULT_INVALID_ARGUMENT; + + return AddWaiterImplNoLock(waiter, signals, context); +} + +void Dispatcher::RemoveWaiter(Waiter* waiter) { + base::AutoLock locker(lock_); + if (is_closed_) + return; + RemoveWaiterImplNoLock(waiter); +} + +Dispatcher::Dispatcher() + : is_closed_(false) { +} + +Dispatcher::~Dispatcher() { + // Make sure that |Close()| was called. + DCHECK(is_closed_); +} + +void Dispatcher::CancelAllWaitersNoLock() { + lock_.AssertAcquired(); + DCHECK(is_closed_); + // By default, waiting isn't supported. Only dispatchers that can be waited on + // will do something nontrivial. +} + +void Dispatcher::CloseImplNoLock() { + lock_.AssertAcquired(); + DCHECK(is_closed_); + // This may not need to do anything. Dispatchers should override this to do + // any actual close-time cleanup necessary. +} + +MojoResult Dispatcher::WriteMessageImplNoLock( + const void* /*bytes*/, + uint32_t /*num_bytes*/, + std::vector<DispatcherTransport>* /*transports*/, + MojoWriteMessageFlags /*flags*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for message pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::ReadMessageImplNoLock(void* /*bytes*/, + uint32_t* /*num_bytes*/, + DispatcherVector* /*dispatchers*/, + uint32_t* /*num_dispatchers*/, + MojoReadMessageFlags /*flags*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for message pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::WriteDataImplNoLock(const void* /*elements*/, + uint32_t* /*num_bytes*/, + MojoWriteDataFlags /*flags*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for data pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::BeginWriteDataImplNoLock(void** /*buffer*/, + uint32_t* /*buffer_num_bytes*/, + MojoWriteDataFlags /*flags*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for data pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::EndWriteDataImplNoLock(uint32_t /*num_bytes_written*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for data pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::ReadDataImplNoLock(void* /*elements*/, + uint32_t* /*num_bytes*/, + MojoReadDataFlags /*flags*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for data pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::BeginReadDataImplNoLock(const void** /*buffer*/, + uint32_t* /*buffer_num_bytes*/, + MojoReadDataFlags /*flags*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for data pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::EndReadDataImplNoLock(uint32_t /*num_bytes_read*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for data pipe dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::DuplicateBufferHandleImplNoLock( + const MojoDuplicateBufferHandleOptions* /*options*/, + scoped_refptr<Dispatcher>* /*new_dispatcher*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for buffer dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::MapBufferImplNoLock( + uint64_t /*offset*/, + uint64_t /*num_bytes*/, + MojoMapBufferFlags /*flags*/, + scoped_ptr<RawSharedBufferMapping>* /*mapping*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, not supported. Only needed for buffer dispatchers. + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::AddWaiterImplNoLock(Waiter* /*waiter*/, + MojoHandleSignals /*signals*/, + uint32_t /*context*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, waiting isn't supported. Only dispatchers that can be waited on + // will do something nontrivial. + return MOJO_RESULT_FAILED_PRECONDITION; +} + +void Dispatcher::RemoveWaiterImplNoLock(Waiter* /*waiter*/) { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // By default, waiting isn't supported. Only dispatchers that can be waited on + // will do something nontrivial. +} + +void Dispatcher::StartSerializeImplNoLock(Channel* /*channel*/, + size_t* max_size, + size_t* max_platform_handles) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + DCHECK(!is_closed_); + *max_size = 0; + *max_platform_handles = 0; +} + +bool Dispatcher::EndSerializeAndCloseImplNoLock( + Channel* /*channel*/, + void* /*destination*/, + size_t* /*actual_size*/, + embedder::PlatformHandleVector* /*platform_handles*/) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + DCHECK(is_closed_); + // By default, serializing isn't supported, so just close. + CloseImplNoLock(); + return false; +} + +bool Dispatcher::IsBusyNoLock() const { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + // Most dispatchers support only "atomic" operations, so they are never busy + // (in this sense). + return false; +} + +void Dispatcher::CloseNoLock() { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + + is_closed_ = true; + CancelAllWaitersNoLock(); + CloseImplNoLock(); +} + +scoped_refptr<Dispatcher> +Dispatcher::CreateEquivalentDispatcherAndCloseNoLock() { + lock_.AssertAcquired(); + DCHECK(!is_closed_); + + is_closed_ = true; + CancelAllWaitersNoLock(); + return CreateEquivalentDispatcherAndCloseImplNoLock(); +} + +void Dispatcher::StartSerialize(Channel* channel, + size_t* max_size, + size_t* max_platform_handles) { + DCHECK(channel); + DCHECK(max_size); + DCHECK(max_platform_handles); + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + DCHECK(!is_closed_); + StartSerializeImplNoLock(channel, max_size, max_platform_handles); +} + +bool Dispatcher::EndSerializeAndClose( + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) { + DCHECK(channel); + DCHECK(actual_size); + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + DCHECK(!is_closed_); + + // Like other |...Close()| methods, we mark ourselves as closed before calling + // the impl. + is_closed_ = true; + // No need to cancel waiters: we shouldn't have any (and shouldn't be in + // |Core|'s handle table. + +#if !defined(NDEBUG) + // See the comment above |EndSerializeAndCloseImplNoLock()|. In brief: Locking + // isn't actually needed, but we need to satisfy assertions (which we don't + // want to remove or weaken). + base::AutoLock locker(lock_); +#endif + + return EndSerializeAndCloseImplNoLock(channel, destination, actual_size, + platform_handles); +} + +// DispatcherTransport --------------------------------------------------------- + +void DispatcherTransport::End() { + DCHECK(dispatcher_); + dispatcher_->lock_.Release(); + dispatcher_ = NULL; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/dispatcher.h b/chromium/mojo/system/dispatcher.h new file mode 100644 index 00000000000..051e2bc2e9b --- /dev/null +++ b/chromium/mojo/system/dispatcher.h @@ -0,0 +1,379 @@ +// 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 MOJO_SYSTEM_DISPATCHER_H_ +#define MOJO_SYSTEM_DISPATCHER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/platform_handle_vector.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Channel; +class Core; +class Dispatcher; +class DispatcherTransport; +class HandleTable; +class LocalMessagePipeEndpoint; +class ProxyMessagePipeEndpoint; +class RawSharedBufferMapping; +class TransportData; +class Waiter; + +typedef std::vector<scoped_refptr<Dispatcher> > DispatcherVector; + +namespace test { + +// Test helper. We need to declare it here so we can friend it. +MOJO_SYSTEM_IMPL_EXPORT DispatcherTransport DispatcherTryStartTransport( + Dispatcher* dispatcher); + +} // namespace test + +// A |Dispatcher| implements Mojo primitives that are "attached" to a particular +// handle. This includes most (all?) primitives except for |MojoWait...()|. This +// object is thread-safe, with its state being protected by a single lock +// |lock_|, which is also made available to implementation subclasses (via the +// |lock()| method). +class MOJO_SYSTEM_IMPL_EXPORT Dispatcher : + public base::RefCountedThreadSafe<Dispatcher> { + public: + enum Type { + kTypeUnknown = 0, + kTypeMessagePipe, + kTypeDataPipeProducer, + kTypeDataPipeConsumer, + kTypeSharedBuffer, + + // "Private" types (not exposed via the public interface): + kTypePlatformHandle = -1 + }; + virtual Type GetType() const = 0; + + // These methods implement the various primitives named |Mojo...()|. These + // take |lock_| and handle races with |Close()|. Then they call out to + // subclasses' |...ImplNoLock()| methods (still under |lock_|), which actually + // implement the primitives. + // NOTE(vtl): This puts a big lock around each dispatcher (i.e., handle), and + // prevents the various |...ImplNoLock()|s from releasing the lock as soon as + // possible. If this becomes an issue, we can rethink this. + MojoResult Close(); + + // |transports| may be non-null if and only if there are handles to be + // written; not that |this| must not be in |transports|. On success, all the + // dispatchers in |transports| must have been moved to a closed state; on + // failure, they should remain in their original state. + MojoResult WriteMessage(const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags); + // |dispatchers| must be non-null but empty, if |num_dispatchers| is non-null + // and nonzero. On success, it will be set to the dispatchers to be received + // (and assigned handles) as part of the message. + MojoResult ReadMessage(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags); + MojoResult WriteData(const void* elements, + uint32_t* elements_num_bytes, + MojoWriteDataFlags flags); + MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + MojoResult EndWriteData(uint32_t num_bytes_written); + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + MojoResult EndReadData(uint32_t num_bytes_read); + // |options| may be null. |new_dispatcher| must not be null, but + // |*new_dispatcher| should be null (and will contain the dispatcher for the + // new handle on success). + MojoResult DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr<Dispatcher>* new_dispatcher); + MojoResult MapBuffer(uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + scoped_ptr<RawSharedBufferMapping>* mapping); + + // Adds a waiter to this dispatcher. The waiter will be woken up when this + // object changes state to satisfy |signals| with context |context|. It will + // also be woken up when it becomes impossible for the object to ever satisfy + // |signals| with a suitable error status. + // + // Returns: + // - |MOJO_RESULT_OK| if the waiter was added; + // - |MOJO_RESULT_ALREADY_EXISTS| if |signals| is already satisfied; + // - |MOJO_RESULT_INVALID_ARGUMENT| if the dispatcher has been closed; and + // - |MOJO_RESULT_FAILED_PRECONDITION| if it is not (or no longer) possible + // that |signals| will ever be satisfied. + MojoResult AddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context); + void RemoveWaiter(Waiter* waiter); + + // A dispatcher must be put into a special state in order to be sent across a + // message pipe. Outside of tests, only |HandleTableAccess| is allowed to do + // this, since there are requirements on the handle table (see below). + // + // In this special state, only a restricted set of operations is allowed. + // These are the ones available as |DispatcherTransport| methods. Other + // |Dispatcher| methods must not be called until |DispatcherTransport::End()| + // has been called. + class HandleTableAccess { + private: + friend class Core; + friend class HandleTable; + // Tests also need this, to avoid needing |Core|. + friend DispatcherTransport test::DispatcherTryStartTransport(Dispatcher*); + + // This must be called under the handle table lock and only if the handle + // table entry is not marked busy. The caller must maintain a reference to + // |dispatcher| until |DispatcherTransport::End()| is called. + static DispatcherTransport TryStartTransport(Dispatcher* dispatcher); + }; + + // A |TransportData| may serialize dispatchers that are given to it (and which + // were previously attached to the |MessageInTransit| that is creating it) to + // a given |Channel| and then (probably in a different process) deserialize. + // Note that the |MessageInTransit| "owns" (i.e., has the only ref to) these + // dispatchers, so there are no locking issues. (There's no lock ordering + // issue, and in fact no need to take dispatcher locks at all.) + // TODO(vtl): Consider making another wrapper similar to |DispatcherTransport| + // (but with an owning, unique reference), and having + // |CreateEquivalentDispatcherAndCloseImplNoLock()| return that wrapper (and + // |MessageInTransit|, etc. only holding on to such wrappers). + class TransportDataAccess { + private: + friend class TransportData; + + // Serialization API. These functions may only be called on such + // dispatchers. (|channel| is the |Channel| to which the dispatcher is to be + // serialized.) See the |Dispatcher| methods of the same names for more + // details. + static void StartSerialize(Dispatcher* dispatcher, + Channel* channel, + size_t* max_size, + size_t* max_platform_handles); + static bool EndSerializeAndClose( + Dispatcher* dispatcher, + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles); + + // Deserialization API. + // Note: This "clears" (i.e., reset to the invalid handle) any platform + // handles that it takes ownership of. + static scoped_refptr<Dispatcher> Deserialize( + Channel* channel, + int32_t type, + const void* source, + size_t size, + embedder::PlatformHandleVector* platform_handles); + }; + + protected: + friend class base::RefCountedThreadSafe<Dispatcher>; + + Dispatcher(); + virtual ~Dispatcher(); + + // These are to be overridden by subclasses (if necessary). They are called + // exactly once -- first |CancelAllWaitersNoLock()|, then |CloseImplNoLock()|, + // when the dispatcher is being closed. They are called under |lock_|. + virtual void CancelAllWaitersNoLock(); + virtual void CloseImplNoLock(); + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() = 0; + + // These are to be overridden by subclasses (if necessary). They are never + // called after the dispatcher has been closed. They are called under |lock_|. + // See the descriptions of the methods without the "ImplNoLock" for more + // information. + virtual MojoResult WriteMessageImplNoLock( + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags); + virtual MojoResult ReadMessageImplNoLock(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags); + virtual MojoResult WriteDataImplNoLock(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags); + virtual MojoResult BeginWriteDataImplNoLock(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + virtual MojoResult EndWriteDataImplNoLock(uint32_t num_bytes_written); + virtual MojoResult ReadDataImplNoLock(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + virtual MojoResult BeginReadDataImplNoLock(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + virtual MojoResult EndReadDataImplNoLock(uint32_t num_bytes_read); + virtual MojoResult DuplicateBufferHandleImplNoLock( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr<Dispatcher>* new_dispatcher); + virtual MojoResult MapBufferImplNoLock( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + scoped_ptr<RawSharedBufferMapping>* mapping); + virtual MojoResult AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context); + virtual void RemoveWaiterImplNoLock(Waiter* waiter); + + // These implement the API used to serialize dispatchers to a |Channel| + // (described below). They will only be called on a dispatcher that's attached + // to and "owned" by a |MessageInTransit|. See the non-"impl" versions for + // more information. + // + // Note: |StartSerializeImplNoLock()| is actually called with |lock_| NOT + // held, since the dispatcher should only be accessible to the calling thread. + // On Debug builds, |EndSerializeAndCloseImplNoLock()| is called with |lock_| + // held, to satisfy any |lock_.AssertAcquired()| (e.g., in |CloseImplNoLock()| + // -- and anything it calls); disentangling those assertions is + // difficult/fragile, and would weaken our general checking of invariants. + // + // TODO(vtl): Consider making these pure virtual once most things support + // being passed over a message pipe. + virtual void StartSerializeImplNoLock(Channel* channel, + size_t* max_size, + size_t* max_platform_handles); + virtual bool EndSerializeAndCloseImplNoLock( + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles); + + // Available to subclasses. (Note: Returns a non-const reference, just like + // |base::AutoLock|'s constructor takes a non-const reference.) + base::Lock& lock() const { return lock_; } + + private: + friend class DispatcherTransport; + + // This should be overridden to return true if/when there's an ongoing + // operation (e.g., two-phase read/writes on data pipes) that should prevent a + // handle from being sent over a message pipe (with status "busy"). + virtual bool IsBusyNoLock() const; + + // Closes the dispatcher. This must be done under lock, and unlike |Close()|, + // the dispatcher must not be closed already. (This is the "equivalent" of + // |CreateEquivalentDispatcherAndCloseNoLock()|, for situations where the + // dispatcher must be disposed of instead of "transferred".) + void CloseNoLock(); + + // Creates an equivalent dispatcher -- representing the same resource as this + // dispatcher -- and close (i.e., disable) this dispatcher. I.e., this + // dispatcher will look as though it was closed, but the resource it + // represents will be assigned to the new dispatcher. This must be called + // under the dispatcher's lock. + scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseNoLock(); + + // API to serialize dispatchers to a |Channel|, exposed to only + // |TransportData| (via |TransportData|). They may only be called on a + // dispatcher attached to a |MessageInTransit| (and in particular not in + // |CoreImpl|'s handle table). + // + // Starts the serialization. Returns (via the two "out" parameters) the + // maximum amount of space that may be needed to serialize this dispatcher to + // the given |Channel| (no more than + // |TransportData::kMaxSerializedDispatcherSize|) and the maximum number of + // |PlatformHandle|s that may need to be attached (no more than + // |TransportData::kMaxSerializedDispatcherPlatformHandles|). If this + // dispatcher cannot be serialized to the given |Channel|, |*max_size| and + // |*max_platform_handles| should be set to zero. A call to this method will + // ALWAYS be followed by a call to |EndSerializeAndClose()| (even if this + // dispatcher cannot be serialized to the given |Channel|). + void StartSerialize(Channel* channel, + size_t* max_size, + size_t* max_platform_handles); + // Completes the serialization of this dispatcher to the given |Channel| and + // closes it. (This call will always follow an earlier call to + // |StartSerialize()|, with the same |Channel|.) This does so by writing to + // |destination| and appending any |PlatformHandle|s needed to + // |platform_handles| (which may be null if no platform handles were indicated + // to be required to |StartSerialize()|). This may write no more than the + // amount indicated by |StartSerialize()|. (WARNING: Beware of races, e.g., if + // something can be mutated between the two calls!) Returns true on success, + // in which case |*actual_size| is set to the amount it actually wrote to + // |destination|. On failure, |*actual_size| should not be modified; however, + // the dispatcher will still be closed. + bool EndSerializeAndClose(Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles); + + // This protects the following members as well as any state added by + // subclasses. + mutable base::Lock lock_; + bool is_closed_; + + DISALLOW_COPY_AND_ASSIGN(Dispatcher); +}; + +// Wrapper around a |Dispatcher| pointer, while it's being processed to be +// passed in a message pipe. See the comment about +// |Dispatcher::HandleTableAccess| for more details. +// +// Note: This class is deliberately "thin" -- no more expensive than a +// |Dispatcher*|. +class MOJO_SYSTEM_IMPL_EXPORT DispatcherTransport { + public: + DispatcherTransport() : dispatcher_(NULL) {} + + void End(); + + Dispatcher::Type GetType() const { return dispatcher_->GetType(); } + bool IsBusy() const { return dispatcher_->IsBusyNoLock(); } + void Close() { dispatcher_->CloseNoLock(); } + scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndClose() { + return dispatcher_->CreateEquivalentDispatcherAndCloseNoLock(); + } + + bool is_valid() const { return !!dispatcher_; } + + protected: + Dispatcher* dispatcher() { return dispatcher_; } + + private: + friend class Dispatcher::HandleTableAccess; + + explicit DispatcherTransport(Dispatcher* dispatcher) + : dispatcher_(dispatcher) {} + + Dispatcher* dispatcher_; + + // Copy and assign allowed. +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_DISPATCHER_H_ diff --git a/chromium/mojo/system/dispatcher_unittest.cc b/chromium/mojo/system/dispatcher_unittest.cc new file mode 100644 index 00000000000..917808225d8 --- /dev/null +++ b/chromium/mojo/system/dispatcher_unittest.cc @@ -0,0 +1,274 @@ +// 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 "mojo/system/dispatcher.h" + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_vector.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/simple_thread.h" +#include "mojo/system/raw_shared_buffer.h" +#include "mojo/system/waiter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +// Trivial subclass that makes the constructor public. +class TrivialDispatcher : public Dispatcher { + public: + TrivialDispatcher() {} + + virtual Type GetType() const OVERRIDE { + return kTypeUnknown; + } + + private: + friend class base::RefCountedThreadSafe<TrivialDispatcher>; + virtual ~TrivialDispatcher() {} + + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE { + lock().AssertAcquired(); + return scoped_refptr<Dispatcher>(new TrivialDispatcher()); + } + + DISALLOW_COPY_AND_ASSIGN(TrivialDispatcher); +}; + +TEST(DispatcherTest, Basic) { + scoped_refptr<Dispatcher> d(new TrivialDispatcher()); + + EXPECT_EQ(Dispatcher::kTypeUnknown, d->GetType()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->WriteMessage(NULL, 0, NULL, MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->ReadMessage(NULL, NULL, NULL, NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->WriteData(NULL, NULL, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->BeginWriteData(NULL, NULL, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->EndWriteData(0)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->ReadData(NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->BeginReadData(NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->EndReadData(0)); + Waiter w; + w.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + d->AddWaiter(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0)); + // Okay to remove even if it wasn't added (or was already removed). + d->RemoveWaiter(&w); + d->RemoveWaiter(&w); + + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->WriteMessage(NULL, 0, NULL, MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->ReadMessage(NULL, NULL, NULL, NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->WriteData(NULL, NULL, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->BeginWriteData(NULL, NULL, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->EndWriteData(0)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->ReadData(NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->BeginReadData(NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->EndReadData(0)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->AddWaiter(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0)); + d->RemoveWaiter(&w); +} + +class ThreadSafetyStressThread : public base::SimpleThread { + public: + enum DispatcherOp { + CLOSE = 0, + WRITE_MESSAGE, + READ_MESSAGE, + WRITE_DATA, + BEGIN_WRITE_DATA, + END_WRITE_DATA, + READ_DATA, + BEGIN_READ_DATA, + END_READ_DATA, + DUPLICATE_BUFFER_HANDLE, + MAP_BUFFER, + ADD_WAITER, + REMOVE_WAITER, + + DISPATCHER_OP_COUNT + }; + + ThreadSafetyStressThread(base::WaitableEvent* event, + scoped_refptr<Dispatcher> dispatcher, + DispatcherOp op) + : base::SimpleThread("thread_safety_stress_thread"), + event_(event), + dispatcher_(dispatcher), + op_(op) { + CHECK_LE(0, op_); + CHECK_LT(op_, DISPATCHER_OP_COUNT); + } + + virtual ~ThreadSafetyStressThread() { + Join(); + } + + private: + virtual void Run() OVERRIDE { + event_->Wait(); + + waiter_.Init(); + switch(op_) { + case CLOSE: { + MojoResult r = dispatcher_->Close(); + EXPECT_TRUE(r == MOJO_RESULT_OK || r == MOJO_RESULT_INVALID_ARGUMENT) + << "Result: " << r; + break; + } + case WRITE_MESSAGE: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->WriteMessage(NULL, 0, NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + break; + case READ_MESSAGE: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->ReadMessage(NULL, NULL, NULL, NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + break; + case WRITE_DATA: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->WriteData(NULL, NULL, + MOJO_WRITE_DATA_FLAG_NONE)); + break; + case BEGIN_WRITE_DATA: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->BeginWriteData(NULL, NULL, + MOJO_WRITE_DATA_FLAG_NONE)); + break; + case END_WRITE_DATA: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->EndWriteData(0)); + break; + case READ_DATA: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->ReadData(NULL, NULL, MOJO_READ_DATA_FLAG_NONE)); + break; + case BEGIN_READ_DATA: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->BeginReadData(NULL, NULL, + MOJO_READ_DATA_FLAG_NONE)); + break; + case END_READ_DATA: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->EndReadData(0)); + break; + case DUPLICATE_BUFFER_HANDLE: { + scoped_refptr<Dispatcher> unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->DuplicateBufferHandle(NULL, &unused)); + break; + } + case MAP_BUFFER: { + scoped_ptr<RawSharedBufferMapping> unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher_->MapBuffer(0u, 0u, MOJO_MAP_BUFFER_FLAG_NONE, + &unused)); + break; + } + case ADD_WAITER: { + MojoResult r = dispatcher_->AddWaiter(&waiter_, + ~MOJO_HANDLE_SIGNAL_NONE, 0); + EXPECT_TRUE(r == MOJO_RESULT_FAILED_PRECONDITION || + r == MOJO_RESULT_INVALID_ARGUMENT); + break; + } + case REMOVE_WAITER: + dispatcher_->RemoveWaiter(&waiter_); + break; + default: + NOTREACHED(); + break; + } + + // Always try to remove the waiter, in case we added it. + dispatcher_->RemoveWaiter(&waiter_); + } + + base::WaitableEvent* const event_; + const scoped_refptr<Dispatcher> dispatcher_; + const DispatcherOp op_; + + Waiter waiter_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSafetyStressThread); +}; + +TEST(DispatcherTest, ThreadSafetyStress) { + static const size_t kRepeatCount = 20; + static const size_t kNumThreads = 100; + + for (size_t i = 0; i < kRepeatCount; i++) { + // Manual reset, not initially signalled. + base::WaitableEvent event(true, false); + scoped_refptr<Dispatcher> d(new TrivialDispatcher()); + + { + ScopedVector<ThreadSafetyStressThread> threads; + for (size_t j = 0; j < kNumThreads; j++) { + ThreadSafetyStressThread::DispatcherOp op = + static_cast<ThreadSafetyStressThread::DispatcherOp>( + (i+j) % ThreadSafetyStressThread::DISPATCHER_OP_COUNT); + threads.push_back(new ThreadSafetyStressThread(&event, d, op)); + threads.back()->Start(); + } + event.Signal(); // Kicks off real work on the threads. + } // Joins all the threads. + + // One of the threads should already have closed the dispatcher. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->Close()); + } +} + +TEST(DispatcherTest, ThreadSafetyStressNoClose) { + static const size_t kRepeatCount = 20; + static const size_t kNumThreads = 100; + + for (size_t i = 0; i < kRepeatCount; i++) { + // Manual reset, not initially signalled. + base::WaitableEvent event(true, false); + scoped_refptr<Dispatcher> d(new TrivialDispatcher()); + + { + ScopedVector<ThreadSafetyStressThread> threads; + for (size_t j = 0; j < kNumThreads; j++) { + ThreadSafetyStressThread::DispatcherOp op = + static_cast<ThreadSafetyStressThread::DispatcherOp>( + (i+j) % (ThreadSafetyStressThread::DISPATCHER_OP_COUNT-1) + 1); + threads.push_back(new ThreadSafetyStressThread(&event, d, op)); + threads.back()->Start(); + } + event.Signal(); // Kicks off real work on the threads. + } // Joins all the threads. + + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/entrypoints.cc b/chromium/mojo/system/entrypoints.cc new file mode 100644 index 00000000000..b70fcadf8b6 --- /dev/null +++ b/chromium/mojo/system/entrypoints.cc @@ -0,0 +1,158 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/entrypoints.h" + +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/system/core.h" + +static mojo::system::Core* g_core = NULL; + +namespace mojo { +namespace system { +namespace entrypoints { + +void SetCore(Core* core) { + g_core = core; +} + +Core* GetCore() { + return g_core; +} + +} // namespace entrypoints +} // namepace system +} // namespace mojo + +// Definitions of the system functions. +extern "C" { + +MojoTimeTicks MojoGetTimeTicksNow() { + return g_core->GetTimeTicksNow(); +} + +MojoResult MojoClose(MojoHandle handle) { + return g_core->Close(handle); +} + +MojoResult MojoWait(MojoHandle handle, + MojoHandleSignals signals, + MojoDeadline deadline) { + return g_core->Wait(handle, signals, deadline); +} + +MojoResult MojoWaitMany(const MojoHandle* handles, + const MojoHandleSignals* signals, + uint32_t num_handles, + MojoDeadline deadline) { + return g_core->WaitMany(handles, signals, num_handles, deadline); +} + +MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + return g_core->CreateMessagePipe( + options, message_pipe_handle0, message_pipe_handle1); +} + +MojoResult MojoWriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return g_core->WriteMessage( + message_pipe_handle, bytes, num_bytes, handles, num_handles, flags); +} + +MojoResult MojoReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return g_core->ReadMessage( + message_pipe_handle, bytes, num_bytes, handles, num_handles, flags); +} + +MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + return g_core->CreateDataPipe( + options, data_pipe_producer_handle, data_pipe_consumer_handle); +} + +MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags) { + return g_core->WriteData( + data_pipe_producer_handle, elements, num_elements, flags); +} + +MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags) { + return g_core->BeginWriteData( + data_pipe_producer_handle, buffer, buffer_num_elements, flags); +} + +MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written) { + return g_core->EndWriteData(data_pipe_producer_handle, num_elements_written); +} + +MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags) { + return g_core->ReadData( + data_pipe_consumer_handle, elements, num_elements, flags); +} + +MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags) { + return g_core->BeginReadData( + data_pipe_consumer_handle, buffer, buffer_num_elements, flags); +} + +MojoResult MojoEndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read) { + return g_core->EndReadData(data_pipe_consumer_handle, num_elements_read); +} + +MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + return g_core->CreateSharedBuffer(options, num_bytes, shared_buffer_handle); +} + +MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + return g_core->DuplicateBufferHandle( + buffer_handle, options, new_buffer_handle); +} + +MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + return g_core->MapBuffer(buffer_handle, offset, num_bytes, buffer, flags); +} + +MojoResult MojoUnmapBuffer(void* buffer) { + return g_core->UnmapBuffer(buffer); +} + +} // extern "C" diff --git a/chromium/mojo/system/entrypoints.h b/chromium/mojo/system/entrypoints.h new file mode 100644 index 00000000000..65a03631e6a --- /dev/null +++ b/chromium/mojo/system/entrypoints.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SYSTEM_ENTRYPOINTS_H +#define MOJO_SYSTEM_ENTRYPOINTS_H + +namespace mojo { +namespace system { + +class Core; + +namespace entrypoints { + +// Sets the instance of Core to be used by system functions. +void SetCore(Core* core); +// Gets the instance of Core to be used by system functions. +Core* GetCore(); + +} // namespace entrypoints +} // namepace system +} // namespace mojo + +#endif // MOJO_SYSTEM_ENTRYPOINTS_H diff --git a/chromium/mojo/system/handle_signals_state.h b/chromium/mojo/system/handle_signals_state.h new file mode 100644 index 00000000000..d76fafb302d --- /dev/null +++ b/chromium/mojo/system/handle_signals_state.h @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SYSTEM_HANDLE_SIGNALS_STATE_H_ +#define MOJO_SYSTEM_HANDLE_SIGNALS_STATE_H_ + +#include "base/macros.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// Just "add" some constructors and methods to the C struct +// |MojoHandleSignalsState| (for convenience). This should add no overhead. +struct MOJO_SYSTEM_IMPL_EXPORT HandleSignalsState + : public MojoHandleSignalsState { + HandleSignalsState() { + satisfied_signals = MOJO_HANDLE_SIGNAL_NONE; + satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE; + } + HandleSignalsState(MojoHandleSignals satisfied, + MojoHandleSignals satisfiable) { + satisfied_signals = satisfied; + satisfiable_signals = satisfiable; + } + + bool equals(const HandleSignalsState& other) const { + return satisfied_signals == other.satisfied_signals && + satisfiable_signals == other.satisfiable_signals; + } + + bool satisfies(MojoHandleSignals signals) const { + return !!(satisfied_signals & signals); + } + + bool can_satisfy(MojoHandleSignals signals) const { + return !!(satisfiable_signals & signals); + } + + // (Copy and assignment allowed.) +}; +COMPILE_ASSERT(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState), + HandleSignalsState_should_add_no_overhead); + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_HANDLE_SIGNALS_STATE_H_ diff --git a/chromium/mojo/system/handle_table.cc b/chromium/mojo/system/handle_table.cc new file mode 100644 index 00000000000..2e4d22a9824 --- /dev/null +++ b/chromium/mojo/system/handle_table.cc @@ -0,0 +1,237 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/handle_table.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "mojo/system/constants.h" +#include "mojo/system/dispatcher.h" + +namespace mojo { +namespace system { + +HandleTable::Entry::Entry() + : busy(false) { +} + +HandleTable::Entry::Entry(const scoped_refptr<Dispatcher>& dispatcher) + : dispatcher(dispatcher), + busy(false) { +} + +HandleTable::Entry::~Entry() { + DCHECK(!busy); +} + +HandleTable::HandleTable() + : next_handle_(MOJO_HANDLE_INVALID + 1) { +} + +HandleTable::~HandleTable() { + // This should usually not be reached (the only instance should be owned by + // the singleton |Core|, which lives forever), except in tests. +} + +Dispatcher* HandleTable::GetDispatcher(MojoHandle handle) { + DCHECK_NE(handle, MOJO_HANDLE_INVALID); + + HandleToEntryMap::iterator it = handle_to_entry_map_.find(handle); + if (it == handle_to_entry_map_.end()) + return NULL; + return it->second.dispatcher; +} + +MojoResult HandleTable::GetAndRemoveDispatcher( + MojoHandle handle, + scoped_refptr<Dispatcher>* dispatcher) { + DCHECK_NE(handle, MOJO_HANDLE_INVALID); + DCHECK(dispatcher); + + HandleToEntryMap::iterator it = handle_to_entry_map_.find(handle); + if (it == handle_to_entry_map_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + if (it->second.busy) + return MOJO_RESULT_BUSY; + *dispatcher = it->second.dispatcher; + handle_to_entry_map_.erase(it); + + return MOJO_RESULT_OK; +} + +MojoHandle HandleTable::AddDispatcher( + const scoped_refptr<Dispatcher>& dispatcher) { + if (handle_to_entry_map_.size() >= kMaxHandleTableSize) + return MOJO_HANDLE_INVALID; + return AddDispatcherNoSizeCheck(dispatcher); +} + +std::pair<MojoHandle, MojoHandle> HandleTable::AddDispatcherPair( + const scoped_refptr<Dispatcher>& dispatcher0, + const scoped_refptr<Dispatcher>& dispatcher1) { + if (handle_to_entry_map_.size() + 1 >= kMaxHandleTableSize) + return std::make_pair(MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID); + return std::make_pair(AddDispatcherNoSizeCheck(dispatcher0), + AddDispatcherNoSizeCheck(dispatcher1)); +} + +bool HandleTable::AddDispatcherVector(const DispatcherVector& dispatchers, + MojoHandle* handles) { + DCHECK_LE(dispatchers.size(), kMaxMessageNumHandles); + DCHECK(handles); + // TODO(vtl): |std::numeric_limits<size_t>::max()| isn't a compile-time + // expression in C++03. + COMPILE_ASSERT( + static_cast<uint64_t>(kMaxHandleTableSize) + kMaxMessageNumHandles < + (sizeof(size_t) == 8 ? kuint64max : + static_cast<uint64_t>(kuint32max)), + addition_may_overflow); + + if (handle_to_entry_map_.size() + dispatchers.size() > kMaxHandleTableSize) + return false; + + for (size_t i = 0; i < dispatchers.size(); i++) { + if (dispatchers[i]) { + handles[i] = AddDispatcherNoSizeCheck(dispatchers[i]); + } else { + LOG(WARNING) << "Invalid dispatcher at index " << i; + handles[i] = MOJO_HANDLE_INVALID; + } + } + return true; +} + +MojoResult HandleTable::MarkBusyAndStartTransport( + MojoHandle disallowed_handle, + const MojoHandle* handles, + uint32_t num_handles, + std::vector<DispatcherTransport>* transports) { + DCHECK_NE(disallowed_handle, MOJO_HANDLE_INVALID); + DCHECK(handles); + DCHECK_LE(num_handles, kMaxMessageNumHandles); + DCHECK(transports); + DCHECK_EQ(transports->size(), num_handles); + + std::vector<Entry*> entries(num_handles); + + // First verify all the handles and get their dispatchers. + uint32_t i; + MojoResult error_result = MOJO_RESULT_INTERNAL; + for (i = 0; i < num_handles; i++) { + // Sending your own handle is not allowed (and, for consistency, returns + // "busy"). + if (handles[i] == disallowed_handle) { + error_result = MOJO_RESULT_BUSY; + break; + } + + HandleToEntryMap::iterator it = handle_to_entry_map_.find(handles[i]); + if (it == handle_to_entry_map_.end()) { + error_result = MOJO_RESULT_INVALID_ARGUMENT; + break; + } + + entries[i] = &it->second; + if (entries[i]->busy) { + error_result = MOJO_RESULT_BUSY; + break; + } + // Note: By marking the handle as busy here, we're also preventing the + // same handle from being sent multiple times in the same message. + entries[i]->busy = true; + + // Try to start the transport. + DispatcherTransport transport = + Dispatcher::HandleTableAccess::TryStartTransport( + entries[i]->dispatcher.get()); + if (!transport.is_valid()) { + // Unset the busy flag (since it won't be unset below). + entries[i]->busy = false; + error_result = MOJO_RESULT_BUSY; + break; + } + + // Check if the dispatcher is busy (e.g., in a two-phase read/write). + // (Note that this must be done after the dispatcher's lock is acquired.) + if (transport.IsBusy()) { + // Unset the busy flag and end the transport (since it won't be done + // below). + entries[i]->busy = false; + transport.End(); + error_result = MOJO_RESULT_BUSY; + break; + } + + // Hang on to the transport (which we'll need to end the transport). + (*transports)[i] = transport; + } + if (i < num_handles) { + DCHECK_NE(error_result, MOJO_RESULT_INTERNAL); + + // Unset the busy flags and release the locks. + for (uint32_t j = 0; j < i; j++) { + DCHECK(entries[j]->busy); + entries[j]->busy = false; + (*transports)[j].End(); + } + return error_result; + } + + return MOJO_RESULT_OK; +} + +MojoHandle HandleTable::AddDispatcherNoSizeCheck( + const scoped_refptr<Dispatcher>& dispatcher) { + DCHECK(dispatcher); + DCHECK_LT(handle_to_entry_map_.size(), kMaxHandleTableSize); + DCHECK_NE(next_handle_, MOJO_HANDLE_INVALID); + + // TODO(vtl): Maybe we want to do something different/smarter. (Or maybe try + // assigning randomly?) + while (handle_to_entry_map_.find(next_handle_) != + handle_to_entry_map_.end()) { + next_handle_++; + if (next_handle_ == MOJO_HANDLE_INVALID) + next_handle_++; + } + + MojoHandle new_handle = next_handle_; + handle_to_entry_map_[new_handle] = Entry(dispatcher); + + next_handle_++; + if (next_handle_ == MOJO_HANDLE_INVALID) + next_handle_++; + + return new_handle; +} + +void HandleTable::RemoveBusyHandles(const MojoHandle* handles, + uint32_t num_handles) { + DCHECK(handles); + DCHECK_LE(num_handles, kMaxMessageNumHandles); + + for (uint32_t i = 0; i < num_handles; i++) { + HandleToEntryMap::iterator it = handle_to_entry_map_.find(handles[i]); + DCHECK(it != handle_to_entry_map_.end()); + DCHECK(it->second.busy); + it->second.busy = false; // For the sake of a |DCHECK()|. + handle_to_entry_map_.erase(it); + } +} + +void HandleTable::RestoreBusyHandles(const MojoHandle* handles, + uint32_t num_handles) { + DCHECK(handles); + DCHECK_LE(num_handles, kMaxMessageNumHandles); + + for (uint32_t i = 0; i < num_handles; i++) { + HandleToEntryMap::iterator it = handle_to_entry_map_.find(handles[i]); + DCHECK(it != handle_to_entry_map_.end()); + DCHECK(it->second.busy); + it->second.busy = false; + } +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/handle_table.h b/chromium/mojo/system/handle_table.h new file mode 100644 index 00000000000..4a25939175d --- /dev/null +++ b/chromium/mojo/system/handle_table.h @@ -0,0 +1,144 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SYSTEM_HANDLE_TABLE_H_ +#define MOJO_SYSTEM_HANDLE_TABLE_H_ + +#include <utility> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Core; +class Dispatcher; +class DispatcherTransport; + +typedef std::vector<scoped_refptr<Dispatcher> > DispatcherVector; + +// Test-only function (defined/used in embedder/test_embedder.cc). Declared here +// so it can be friended. +namespace internal { +bool ShutdownCheckNoLeaks(Core*); +} + +// This class provides the (global) handle table (owned by |Core|), which maps +// (valid) |MojoHandle|s to |Dispatcher|s. This is abstracted so that, e.g., +// caching may be added. +// +// This class is NOT thread-safe; locking is left to |Core| (since it may need +// to make several changes -- "atomically" or in rapid successsion, in which +// case the extra locking/unlocking would be unnecessary overhead). + +class MOJO_SYSTEM_IMPL_EXPORT HandleTable { + public: + HandleTable(); + ~HandleTable(); + + // Gets the dispatcher for a given handle (which should not be + // |MOJO_HANDLE_INVALID|). Returns null if there's no dispatcher for the given + // handle. + // WARNING: For efficiency, this returns a dumb pointer. If you're going to + // use the result outside |Core|'s lock, you MUST take a reference (e.g., by + // storing the result inside a |scoped_refptr|). + Dispatcher* GetDispatcher(MojoHandle handle); + + // On success, gets the dispatcher for a given handle (which should not be + // |MOJO_HANDLE_INVALID|) and removes it. (On failure, returns an appropriate + // result (and leaves |dispatcher| alone), namely + // |MOJO_RESULT_INVALID_ARGUMENT| if there's no dispatcher for the given + // handle or |MOJO_RESULT_BUSY| if the handle is marked as busy.) + MojoResult GetAndRemoveDispatcher(MojoHandle handle, + scoped_refptr<Dispatcher>* dispatcher); + + // Adds a dispatcher (which must be valid), returning the handle for it. + // Returns |MOJO_HANDLE_INVALID| on failure (if the handle table is full). + MojoHandle AddDispatcher(const scoped_refptr<Dispatcher>& dispatcher); + + // Adds a pair of dispatchers (which must be valid), return a pair of handles + // for them. On failure (if the handle table is full), the first (and second) + // handles will be |MOJO_HANDLE_INVALID|, and neither dispatcher will be + // added. + std::pair<MojoHandle, MojoHandle> AddDispatcherPair( + const scoped_refptr<Dispatcher>& dispatcher0, + const scoped_refptr<Dispatcher>& dispatcher1); + + // Adds the given vector of dispatchers (of size at most + // |kMaxMessageNumHandles|). |handles| must point to an array of size at least + // |dispatchers.size()|. Unlike the other |AddDispatcher...()| functions, some + // of the dispatchers may be invalid (null). Returns true on success and false + // on failure (if the handle table is full), in which case it leaves + // |handles[...]| untouched (and all dispatchers unadded). + bool AddDispatcherVector(const DispatcherVector& dispatchers, + MojoHandle* handles); + + // Tries to mark the given handles as busy and start transport on them (i.e., + // take their dispatcher locks); |transports| must be sized to contain + // |num_handles| elements. On failure, returns them to their original + // (non-busy, unlocked state). + MojoResult MarkBusyAndStartTransport( + MojoHandle disallowed_handle, + const MojoHandle* handles, + uint32_t num_handles, + std::vector<DispatcherTransport>* transports); + + // Remove the given handles, which must all be present and which should have + // previously been marked busy by |MarkBusyAndStartTransport()|. + void RemoveBusyHandles(const MojoHandle* handles, uint32_t num_handles); + + // Restores the given handles, which must all be present and which should have + // previously been marked busy by |MarkBusyAndStartTransport()|, to a non-busy + // state. + void RestoreBusyHandles(const MojoHandle* handles, uint32_t num_handles); + + private: + friend bool internal::ShutdownCheckNoLeaks(Core*); + + // The |busy| member is used only to deal with functions (in particular + // |Core::WriteMessage()|) that want to hold on to a dispatcher and later + // remove it from the handle table, without holding on to the handle table + // lock. + // + // For example, if |Core::WriteMessage()| is called with a handle to be sent, + // (under the handle table lock) it must first check that that handle is not + // busy (if it is busy, then it fails with |MOJO_RESULT_BUSY|) and then marks + // it as busy. To avoid deadlock, it should also try to acquire the locks for + // all the dispatchers for the handles that it is sending (and fail with + // |MOJO_RESULT_BUSY| if the attempt fails). At this point, it can release the + // handle table lock. + // + // If |Core::Close()| is simultaneously called on that handle, it too checks + // if the handle is marked busy. If it is, it fails (with |MOJO_RESULT_BUSY|). + // This prevents |Core::WriteMessage()| from sending a handle that has been + // closed (or learning about this too late). + struct Entry { + Entry(); + explicit Entry(const scoped_refptr<Dispatcher>& dispatcher); + ~Entry(); + + scoped_refptr<Dispatcher> dispatcher; + bool busy; + }; + typedef base::hash_map<MojoHandle, Entry> HandleToEntryMap; + + // Adds the given dispatcher to the handle table, not doing any size checks. + MojoHandle AddDispatcherNoSizeCheck( + const scoped_refptr<Dispatcher>& dispatcher); + + HandleToEntryMap handle_to_entry_map_; + MojoHandle next_handle_; // Invariant: never |MOJO_HANDLE_INVALID|. + + DISALLOW_COPY_AND_ASSIGN(HandleTable); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_HANDLE_TABLE_H_ diff --git a/chromium/mojo/system/local_data_pipe.cc b/chromium/mojo/system/local_data_pipe.cc new file mode 100644 index 00000000000..8f4bbd6a47e --- /dev/null +++ b/chromium/mojo/system/local_data_pipe.cc @@ -0,0 +1,332 @@ +// 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. + +// TODO(vtl): I currently potentially overflow in doing index calculations. +// E.g., |start_index_| and |current_num_bytes_| fit into a |uint32_t|, but +// their sum may not. This is bad and poses a security risk. (We're currently +// saved by the limit on capacity -- the maximum size of the buffer, checked in +// |DataPipe::ValidateOptions()|, is currently sufficiently small. + +#include "mojo/system/local_data_pipe.h" + +#include <string.h> + +#include <algorithm> + +#include "base/logging.h" +#include "mojo/system/constants.h" + +namespace mojo { +namespace system { + +LocalDataPipe::LocalDataPipe(const MojoCreateDataPipeOptions& options) + : DataPipe(true, true, options), + start_index_(0), + current_num_bytes_(0) { + // Note: |buffer_| is lazily allocated, since a common case will be that one + // of the handles is immediately passed off to another process. +} + +LocalDataPipe::~LocalDataPipe() { +} + +void LocalDataPipe::ProducerCloseImplNoLock() { + // If the consumer is still open and we still have data, we have to keep the + // buffer around. Currently, we won't free it even if it empties later. (We + // could do this -- requiring a check on every read -- but that seems to be + // optimizing for the uncommon case.) + if (!consumer_open_no_lock() || !current_num_bytes_) { + // Note: There can only be a two-phase *read* (by the consumer) if we still + // have data. + DCHECK(!consumer_in_two_phase_read_no_lock()); + DestroyBufferNoLock(); + } +} + +MojoResult LocalDataPipe::ProducerWriteDataImplNoLock(const void* elements, + uint32_t* num_bytes, + bool all_or_none) { + DCHECK_EQ(*num_bytes % element_num_bytes(), 0u); + DCHECK_GT(*num_bytes, 0u); + DCHECK(consumer_open_no_lock()); + + size_t num_bytes_to_write = 0; + if (may_discard()) { + if (all_or_none && *num_bytes > capacity_num_bytes()) + return MOJO_RESULT_OUT_OF_RANGE; + + num_bytes_to_write = std::min(static_cast<size_t>(*num_bytes), + capacity_num_bytes()); + if (num_bytes_to_write > capacity_num_bytes() - current_num_bytes_) { + // Discard as much as needed (discard oldest first). + MarkDataAsConsumedNoLock( + num_bytes_to_write - (capacity_num_bytes() - current_num_bytes_)); + // No need to wake up write waiters, since we're definitely going to leave + // the buffer full. + } + } else { + if (all_or_none && *num_bytes > capacity_num_bytes() - current_num_bytes_) { + // Don't return "should wait" since you can't wait for a specified amount + // of data. + return MOJO_RESULT_OUT_OF_RANGE; + } + + num_bytes_to_write = std::min(static_cast<size_t>(*num_bytes), + capacity_num_bytes() - current_num_bytes_); + } + if (num_bytes_to_write == 0) + return MOJO_RESULT_SHOULD_WAIT; + + // The amount we can write in our first |memcpy()|. + size_t num_bytes_to_write_first = + std::min(num_bytes_to_write, GetMaxNumBytesToWriteNoLock()); + // Do the first (and possibly only) |memcpy()|. + size_t first_write_index = + (start_index_ + current_num_bytes_) % capacity_num_bytes(); + EnsureBufferNoLock(); + memcpy(buffer_.get() + first_write_index, elements, num_bytes_to_write_first); + + if (num_bytes_to_write_first < num_bytes_to_write) { + // The "second write index" is zero. + memcpy(buffer_.get(), + static_cast<const char*>(elements) + num_bytes_to_write_first, + num_bytes_to_write - num_bytes_to_write_first); + } + + current_num_bytes_ += num_bytes_to_write; + DCHECK_LE(current_num_bytes_, capacity_num_bytes()); + *num_bytes = static_cast<uint32_t>(num_bytes_to_write); + return MOJO_RESULT_OK; +} + +MojoResult LocalDataPipe::ProducerBeginWriteDataImplNoLock( + void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) { + DCHECK(consumer_open_no_lock()); + + // The index we need to start writing at. + size_t write_index = + (start_index_ + current_num_bytes_) % capacity_num_bytes(); + + size_t max_num_bytes_to_write = GetMaxNumBytesToWriteNoLock(); + if (all_or_none && *buffer_num_bytes > max_num_bytes_to_write) { + // In "may discard" mode, we can always write from the write index to the + // end of the buffer. + if (may_discard() && + *buffer_num_bytes <= capacity_num_bytes() - write_index) { + // To do so, we need to discard an appropriate amount of data. + // We should only reach here if the start index is after the write index! + DCHECK_GE(start_index_, write_index); + DCHECK_GT(*buffer_num_bytes - max_num_bytes_to_write, 0u); + MarkDataAsConsumedNoLock(*buffer_num_bytes - max_num_bytes_to_write); + max_num_bytes_to_write = *buffer_num_bytes; + } else { + // Don't return "should wait" since you can't wait for a specified amount + // of data. + return MOJO_RESULT_OUT_OF_RANGE; + } + } + + // Don't go into a two-phase write if there's no room. + if (max_num_bytes_to_write == 0) + return MOJO_RESULT_SHOULD_WAIT; + + EnsureBufferNoLock(); + *buffer = buffer_.get() + write_index; + *buffer_num_bytes = static_cast<uint32_t>(max_num_bytes_to_write); + set_producer_two_phase_max_num_bytes_written_no_lock( + static_cast<uint32_t>(max_num_bytes_to_write)); + return MOJO_RESULT_OK; +} + +MojoResult LocalDataPipe::ProducerEndWriteDataImplNoLock( + uint32_t num_bytes_written) { + DCHECK_LE(num_bytes_written, + producer_two_phase_max_num_bytes_written_no_lock()); + current_num_bytes_ += num_bytes_written; + DCHECK_LE(current_num_bytes_, capacity_num_bytes()); + set_producer_two_phase_max_num_bytes_written_no_lock(0); + return MOJO_RESULT_OK; +} + +HandleSignalsState LocalDataPipe::ProducerGetHandleSignalsStateNoLock() const { + HandleSignalsState rv; + if (consumer_open_no_lock()) { + if ((may_discard() || current_num_bytes_ < capacity_num_bytes()) && + !producer_in_two_phase_write_no_lock()) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + } + return rv; +} + +void LocalDataPipe::ConsumerCloseImplNoLock() { + // If the producer is around and in a two-phase write, we have to keep the + // buffer around. (We then don't free it until the producer is closed. This + // could be rectified, but again seems like optimizing for the uncommon case.) + if (!producer_open_no_lock() || !producer_in_two_phase_write_no_lock()) + DestroyBufferNoLock(); + current_num_bytes_ = 0; +} + +MojoResult LocalDataPipe::ConsumerReadDataImplNoLock(void* elements, + uint32_t* num_bytes, + bool all_or_none) { + DCHECK_EQ(*num_bytes % element_num_bytes(), 0u); + DCHECK_GT(*num_bytes, 0u); + + if (all_or_none && *num_bytes > current_num_bytes_) { + // Don't return "should wait" since you can't wait for a specified amount of + // data. + return producer_open_no_lock() ? MOJO_RESULT_OUT_OF_RANGE : + MOJO_RESULT_FAILED_PRECONDITION; + } + + size_t num_bytes_to_read = + std::min(static_cast<size_t>(*num_bytes), current_num_bytes_); + if (num_bytes_to_read == 0) { + return producer_open_no_lock() ? MOJO_RESULT_SHOULD_WAIT : + MOJO_RESULT_FAILED_PRECONDITION; + } + + // The amount we can read in our first |memcpy()|. + size_t num_bytes_to_read_first = + std::min(num_bytes_to_read, GetMaxNumBytesToReadNoLock()); + memcpy(elements, buffer_.get() + start_index_, num_bytes_to_read_first); + + if (num_bytes_to_read_first < num_bytes_to_read) { + // The "second read index" is zero. + memcpy(static_cast<char*>(elements) + num_bytes_to_read_first, + buffer_.get(), + num_bytes_to_read - num_bytes_to_read_first); + } + + MarkDataAsConsumedNoLock(num_bytes_to_read); + *num_bytes = static_cast<uint32_t>(num_bytes_to_read); + return MOJO_RESULT_OK; +} + +MojoResult LocalDataPipe::ConsumerDiscardDataImplNoLock(uint32_t* num_bytes, + bool all_or_none) { + DCHECK_EQ(*num_bytes % element_num_bytes(), 0u); + DCHECK_GT(*num_bytes, 0u); + + if (all_or_none && *num_bytes > current_num_bytes_) { + // Don't return "should wait" since you can't wait for a specified amount of + // data. + return producer_open_no_lock() ? MOJO_RESULT_OUT_OF_RANGE : + MOJO_RESULT_FAILED_PRECONDITION; + } + + // Be consistent with other operations; error if no data available. + if (current_num_bytes_ == 0) { + return producer_open_no_lock() ? MOJO_RESULT_SHOULD_WAIT : + MOJO_RESULT_FAILED_PRECONDITION; + } + + size_t num_bytes_to_discard = + std::min(static_cast<size_t>(*num_bytes), current_num_bytes_); + MarkDataAsConsumedNoLock(num_bytes_to_discard); + *num_bytes = static_cast<uint32_t>(num_bytes_to_discard); + return MOJO_RESULT_OK; +} + +MojoResult LocalDataPipe::ConsumerQueryDataImplNoLock(uint32_t* num_bytes) { + // Note: This cast is safe, since the capacity fits into a |uint32_t|. + *num_bytes = static_cast<uint32_t>(current_num_bytes_); + return MOJO_RESULT_OK; +} + +MojoResult LocalDataPipe::ConsumerBeginReadDataImplNoLock( + const void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) { + size_t max_num_bytes_to_read = GetMaxNumBytesToReadNoLock(); + if (all_or_none && *buffer_num_bytes > max_num_bytes_to_read) { + // Don't return "should wait" since you can't wait for a specified amount of + // data. + return producer_open_no_lock() ? MOJO_RESULT_OUT_OF_RANGE : + MOJO_RESULT_FAILED_PRECONDITION; + } + + // Don't go into a two-phase read if there's no data. + if (max_num_bytes_to_read == 0) { + return producer_open_no_lock() ? MOJO_RESULT_SHOULD_WAIT : + MOJO_RESULT_FAILED_PRECONDITION; + } + + *buffer = buffer_.get() + start_index_; + *buffer_num_bytes = static_cast<uint32_t>(max_num_bytes_to_read); + set_consumer_two_phase_max_num_bytes_read_no_lock( + static_cast<uint32_t>(max_num_bytes_to_read)); + return MOJO_RESULT_OK; +} + +MojoResult LocalDataPipe::ConsumerEndReadDataImplNoLock( + uint32_t num_bytes_read) { + DCHECK_LE(num_bytes_read, consumer_two_phase_max_num_bytes_read_no_lock()); + DCHECK_LE(start_index_ + num_bytes_read, capacity_num_bytes()); + MarkDataAsConsumedNoLock(num_bytes_read); + set_consumer_two_phase_max_num_bytes_read_no_lock(0); + return MOJO_RESULT_OK; +} + +HandleSignalsState LocalDataPipe::ConsumerGetHandleSignalsStateNoLock() const { + HandleSignalsState rv; + if (current_num_bytes_ > 0) { + if (!consumer_in_two_phase_read_no_lock()) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } else if (producer_open_no_lock()) { + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } + return rv; +} + +void LocalDataPipe::EnsureBufferNoLock() { + DCHECK(producer_open_no_lock()); + if (buffer_.get()) + return; + buffer_.reset(static_cast<char*>( + base::AlignedAlloc(capacity_num_bytes(), kDataPipeBufferAlignmentBytes))); +} + +void LocalDataPipe::DestroyBufferNoLock() { +#ifndef NDEBUG + // Scribble on the buffer to help detect use-after-frees. (This also helps the + // unit test detect certain bugs without needing ASAN or similar.) + if (buffer_.get()) + memset(buffer_.get(), 0xcd, capacity_num_bytes()); +#endif + buffer_.reset(); +} + +size_t LocalDataPipe::GetMaxNumBytesToWriteNoLock() { + size_t next_index = start_index_ + current_num_bytes_; + if (next_index >= capacity_num_bytes()) { + next_index %= capacity_num_bytes(); + DCHECK_GE(start_index_, next_index); + DCHECK_EQ(start_index_ - next_index, + capacity_num_bytes() - current_num_bytes_); + return start_index_ - next_index; + } + return capacity_num_bytes() - next_index; +} + +size_t LocalDataPipe::GetMaxNumBytesToReadNoLock() { + if (start_index_ + current_num_bytes_ > capacity_num_bytes()) + return capacity_num_bytes() - start_index_; + return current_num_bytes_; +} + +void LocalDataPipe::MarkDataAsConsumedNoLock(size_t num_bytes) { + DCHECK_LE(num_bytes, current_num_bytes_); + start_index_ += num_bytes; + start_index_ %= capacity_num_bytes(); + current_num_bytes_ -= num_bytes; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/local_data_pipe.h b/chromium/mojo/system/local_data_pipe.h new file mode 100644 index 00000000000..74ed15604cb --- /dev/null +++ b/chromium/mojo/system/local_data_pipe.h @@ -0,0 +1,84 @@ +// 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 MOJO_SYSTEM_LOCAL_DATA_PIPE_H_ +#define MOJO_SYSTEM_LOCAL_DATA_PIPE_H_ + +#include "base/basictypes.h" +#include "base/memory/aligned_memory.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/system/data_pipe.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// |LocalDataPipe| is a subclass that "implements" |DataPipe| for data pipes +// whose producer and consumer are both local. This class is thread-safe (with +// protection provided by |DataPipe|'s |lock_|. +class MOJO_SYSTEM_IMPL_EXPORT LocalDataPipe : public DataPipe { + public: + // |validated_options| should be the output of |DataPipe::ValidateOptions()|. + // In particular: |struct_size| is ignored (so |validated_options| must be the + // current version of the struct) and |capacity_num_bytes| must be nonzero. + explicit LocalDataPipe(const MojoCreateDataPipeOptions& validated_options); + + private: + friend class base::RefCountedThreadSafe<LocalDataPipe>; + virtual ~LocalDataPipe(); + + // |DataPipe| implementation: + virtual void ProducerCloseImplNoLock() OVERRIDE; + virtual MojoResult ProducerWriteDataImplNoLock(const void* elements, + uint32_t* num_bytes, + bool all_or_none) OVERRIDE; + virtual MojoResult ProducerBeginWriteDataImplNoLock( + void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) OVERRIDE; + virtual MojoResult ProducerEndWriteDataImplNoLock( + uint32_t num_bytes_written) OVERRIDE; + virtual HandleSignalsState + ProducerGetHandleSignalsStateNoLock() const OVERRIDE; + virtual void ConsumerCloseImplNoLock() OVERRIDE; + virtual MojoResult ConsumerReadDataImplNoLock(void* elements, + uint32_t* num_bytes, + bool all_or_none) OVERRIDE; + virtual MojoResult ConsumerDiscardDataImplNoLock(uint32_t* num_bytes, + bool all_or_none) OVERRIDE; + virtual MojoResult ConsumerQueryDataImplNoLock(uint32_t* num_bytes) OVERRIDE; + virtual MojoResult ConsumerBeginReadDataImplNoLock(const void** buffer, + uint32_t* buffer_num_bytes, + bool all_or_none) OVERRIDE; + virtual MojoResult ConsumerEndReadDataImplNoLock( + uint32_t num_bytes_read) OVERRIDE; + virtual HandleSignalsState + ConsumerGetHandleSignalsStateNoLock() const OVERRIDE; + + void EnsureBufferNoLock(); + void DestroyBufferNoLock(); + + // Get the maximum (single) write/read size right now (in number of elements); + // result fits in a |uint32_t|. + size_t GetMaxNumBytesToWriteNoLock(); + size_t GetMaxNumBytesToReadNoLock(); + + // Marks the given number of bytes as consumed/discarded. |num_bytes| must be + // greater than |current_num_bytes_|. + void MarkDataAsConsumedNoLock(size_t num_bytes); + + // The members below are protected by |DataPipe|'s |lock_|: + scoped_ptr<char, base::AlignedFreeDeleter> buffer_; + // Circular buffer. + size_t start_index_; + size_t current_num_bytes_; + + DISALLOW_COPY_AND_ASSIGN(LocalDataPipe); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_LOCAL_DATA_PIPE_H_ diff --git a/chromium/mojo/system/local_data_pipe_unittest.cc b/chromium/mojo/system/local_data_pipe_unittest.cc new file mode 100644 index 00000000000..6d747bdf707 --- /dev/null +++ b/chromium/mojo/system/local_data_pipe_unittest.cc @@ -0,0 +1,1644 @@ +// 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 "mojo/system/local_data_pipe.h" + +#include <string.h> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/data_pipe.h" +#include "mojo/system/waiter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +const uint32_t kSizeOfOptions = + static_cast<uint32_t>(sizeof(MojoCreateDataPipeOptions)); + +// Validate options. +TEST(LocalDataPipeTest, Creation) { + // Create using default options. + { + // Get default options. + MojoCreateDataPipeOptions default_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(NULL, &default_options)); + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(default_options)); + dp->ProducerClose(); + dp->ConsumerClose(); + } + + // Create using non-default options. + { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + 1000 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + dp->ProducerClose(); + dp->ConsumerClose(); + } + { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 4, // |element_num_bytes|. + 4000 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + dp->ProducerClose(); + dp->ConsumerClose(); + } + { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD, // |flags|. + 7, // |element_num_bytes|. + 7000000 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + dp->ProducerClose(); + dp->ConsumerClose(); + } + // Default capacity. + { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD, // |flags|. + 100, // |element_num_bytes|. + 0 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + dp->ProducerClose(); + dp->ConsumerClose(); + } +} + +TEST(LocalDataPipeTest, SimpleReadWrite) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + int32_t elements[10] = { 0 }; + uint32_t num_bytes = 0; + + // Try reading; nothing there yet. + num_bytes = static_cast<uint32_t>(arraysize(elements) * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + dp->ConsumerReadData(elements, &num_bytes, false)); + + // Query; nothing there yet. + num_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + // Discard; nothing there yet. + num_bytes = static_cast<uint32_t>(5u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + dp->ConsumerDiscardData(&num_bytes, false)); + + // Read with invalid |num_bytes|. + num_bytes = sizeof(elements[0]) + 1; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dp->ConsumerReadData(elements, &num_bytes, false)); + + // Write two elements. + elements[0] = 123; + elements[1] = 456; + num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(elements, &num_bytes, false)); + // It should have written everything (even without "all or none"). + EXPECT_EQ(2u * sizeof(elements[0]), num_bytes); + + // Query. + num_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(2 * sizeof(elements[0]), num_bytes); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(elements, &num_bytes, false)); + EXPECT_EQ(1u * sizeof(elements[0]), num_bytes); + EXPECT_EQ(123, elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Query. + num_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(1 * sizeof(elements[0]), num_bytes); + + // Try to read two elements, with "all or none". + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerReadData(elements, &num_bytes, true)); + EXPECT_EQ(-1, elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Try to read two elements, without "all or none". + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(elements, &num_bytes, false)); + EXPECT_EQ(456, elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Query. + num_bytes = 0; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +// Note: The "basic" waiting tests test that the "wait states" are correct in +// various situations; they don't test that waiters are properly awoken on state +// changes. (For that, we need to use multiple threads.) +TEST(LocalDataPipeTest, BasicProducerWaiting) { + // Note: We take advantage of the fact that for |LocalDataPipe|, capacities + // are strict maximums. This is not guaranteed by the API. + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + Waiter waiter; + uint32_t context = 0; + + // Never readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 12)); + + // Already writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 34)); + + // Write two elements. + int32_t elements[2] = { 123, 456 }; + uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(elements, &num_bytes, true)); + EXPECT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes); + + // Adding a waiter should now succeed. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 56)); + // And it shouldn't be writable yet. + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ProducerRemoveWaiter(&waiter); + + // Do it again. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 78)); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(elements, &num_bytes, true)); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + EXPECT_EQ(123, elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Waiting should now succeed. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(1000, &context)); + EXPECT_EQ(78u, context); + dp->ProducerRemoveWaiter(&waiter); + + // Try writing, using a two-phase write. + void* buffer = NULL; + num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&buffer, &num_bytes, false)); + EXPECT_TRUE(buffer != NULL); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + + static_cast<int32_t*>(buffer)[0] = 789; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerEndWriteData( + static_cast<uint32_t>(1u * sizeof(elements[0])))); + + // Add a waiter. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 90)); + + // Read one element, using a two-phase read. + const void* read_buffer = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer, &num_bytes, false)); + EXPECT_TRUE(read_buffer != NULL); + // Since we only read one element (after having written three in all), the + // two-phase read should only allow us to read one. This checks an + // implementation detail! + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + EXPECT_EQ(456, static_cast<const int32_t*>(read_buffer)[0]); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerEndReadData( + static_cast<uint32_t>(1u * sizeof(elements[0])))); + + // Waiting should succeed. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(1000, &context)); + EXPECT_EQ(90u, context); + dp->ProducerRemoveWaiter(&waiter); + + // Write one element. + elements[0] = 123; + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(elements, &num_bytes, false)); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + + // Add a waiter. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 12)); + + // Close the consumer. + dp->ConsumerClose(); + + // It should now be never-writable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, waiter.Wait(1000, &context)); + EXPECT_EQ(12u, context); + dp->ProducerRemoveWaiter(&waiter); + + dp->ProducerClose(); +} + +TEST(LocalDataPipeTest, BasicConsumerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + { + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + Waiter waiter; + uint32_t context = 0; + + // Never writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 12)); + + // Not yet readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 34)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ConsumerRemoveWaiter(&waiter); + + // Write two elements. + int32_t elements[2] = { 123, 456 }; + uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(elements, &num_bytes, true)); + + // Should already be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 56)); + + // Discard one element. + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerDiscardData(&num_bytes, true)); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + + // Should still be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 78)); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(elements, &num_bytes, true)); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + EXPECT_EQ(456, elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Adding a waiter should now succeed. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 90)); + + // Write one element. + elements[0] = 789; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(elements, &num_bytes, true)); + + // Waiting should now succeed. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(1000, &context)); + EXPECT_EQ(90u, context); + dp->ConsumerRemoveWaiter(&waiter); + + // Close the producer. + dp->ProducerClose(); + + // Should still be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 12)); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(elements, &num_bytes, true)); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + EXPECT_EQ(789, elements[0]); + EXPECT_EQ(-1, elements[1]); + + // Should be never-readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 34)); + + dp->ConsumerClose(); + } + + // Test with two-phase APIs and closing the producer with an active consumer + // waiter. + { + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + Waiter waiter; + uint32_t context = 0; + + // Write two elements. + int32_t* elements = NULL; + void* buffer = NULL; + // Request room for three (but we'll only write two). + uint32_t num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&buffer, &num_bytes, true)); + EXPECT_TRUE(buffer != NULL); + EXPECT_GE(num_bytes, static_cast<uint32_t>(3u * sizeof(elements[0]))); + elements = static_cast<int32_t*>(buffer); + elements[0] = 123; + elements[1] = 456; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerEndWriteData( + static_cast<uint32_t>(2u * sizeof(elements[0])))); + + // Should already be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 12)); + + // Read one element. + // Request two in all-or-none mode, but only read one. + const void* read_buffer = NULL; + num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer, &num_bytes, true)); + EXPECT_TRUE(read_buffer != NULL); + EXPECT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes); + const int32_t* read_elements = static_cast<const int32_t*>(read_buffer); + EXPECT_EQ(123, read_elements[0]); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerEndReadData( + static_cast<uint32_t>(1u * sizeof(elements[0])))); + + // Should still be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 34)); + + // Read one element. + // Request three, but not in all-or-none mode. + read_buffer = NULL; + num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer, &num_bytes, false)); + EXPECT_TRUE(read_buffer != NULL); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes); + read_elements = static_cast<const int32_t*>(read_buffer); + EXPECT_EQ(456, read_elements[0]); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerEndReadData( + static_cast<uint32_t>(1u * sizeof(elements[0])))); + + // Adding a waiter should now succeed. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 56)); + + // Close the producer. + dp->ProducerClose(); + + // Should be never-readable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, waiter.Wait(1000, &context)); + EXPECT_EQ(56u, context); + dp->ConsumerRemoveWaiter(&waiter); + + dp->ConsumerClose(); + } +} + +// Tests that data pipes aren't writable/readable during two-phase writes/reads. +TEST(LocalDataPipeTest, BasicTwoPhaseWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + Waiter waiter; + + // It should be writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + + uint32_t num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t)); + void* write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, false)); + EXPECT_TRUE(write_ptr != NULL); + EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t))); + + // At this point, it shouldn't be writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 1)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ProducerRemoveWaiter(&waiter); + + // It shouldn't be readable yet either. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 2)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ConsumerRemoveWaiter(&waiter); + + static_cast<int32_t*>(write_ptr)[0] = 123; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerEndWriteData( + static_cast<uint32_t>(1u * sizeof(int32_t)))); + + // It should be writable again. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 3)); + + // And readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 4)); + + // Start another two-phase write and check that it's readable even in the + // middle of it. + num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t)); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, false)); + EXPECT_TRUE(write_ptr != NULL); + EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t))); + + // It should be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 5)); + + // End the two-phase write without writing anything. + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(0u)); + + // Start a two-phase read. + num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t)); + const void* read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, false)); + EXPECT_TRUE(read_ptr != NULL); + EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(int32_t)), num_bytes); + + // At this point, it should still be writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 6)); + + // But not readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 7)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ConsumerRemoveWaiter(&waiter); + + // End the two-phase read without reading anything. + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(0u)); + + // It should be readable again. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 8)); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +// Test that a "may discard" data pipe is writable even when it's full. +TEST(LocalDataPipeTest, BasicMayDiscardWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 1 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + Waiter waiter; + + // Writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + + // Not readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 1)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ConsumerRemoveWaiter(&waiter); + + uint32_t num_bytes = static_cast<uint32_t>(sizeof(int32_t)); + int32_t element = 123; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(&element, &num_bytes, false)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(int32_t)), num_bytes); + + // Still writable (even though it's full). + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 2)); + + // Now readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 3)); + + // Overwrite that element. + num_bytes = static_cast<uint32_t>(sizeof(int32_t)); + element = 456; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(&element, &num_bytes, false)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(int32_t)), num_bytes); + + // Still writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 4)); + + // And still readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 5)); + + // Read that element. + num_bytes = static_cast<uint32_t>(sizeof(int32_t)); + element = 0; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerReadData(&element, &num_bytes, false)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(int32_t)), num_bytes); + EXPECT_EQ(456, element); + + // Still writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 6)); + + // No longer readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 7)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + dp->ConsumerRemoveWaiter(&waiter); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +void Seq(int32_t start, size_t count, int32_t* out) { + for (size_t i = 0; i < count; i++) + out[i] = start + static_cast<int32_t>(i); +} + +TEST(LocalDataPipeTest, MayDiscard) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + int32_t buffer[100] = { 0 }; + uint32_t num_bytes = 0; + + num_bytes = 20u * sizeof(int32_t); + Seq(0, arraysize(buffer), buffer); + // Try writing more than capacity. (This test relies on the implementation + // enforcing the capacity strictly.) + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, false)); + EXPECT_EQ(10u * sizeof(int32_t), num_bytes); + + // Read half of what we wrote. + num_bytes = 5u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, false)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + int32_t expected_buffer[100]; + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(0, 5u, expected_buffer); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + // Internally, a circular buffer would now look like: + // -, -, -, -, -, 5, 6, 7, 8, 9 + + // Write a bit more than the space that's available. + num_bytes = 8u * sizeof(int32_t); + Seq(100, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, false)); + EXPECT_EQ(8u * sizeof(int32_t), num_bytes); + // Internally, a circular buffer would now look like: + // 100, 101, 102, 103, 104, 105, 106, 107, 8, 9 + + // Read half of what's available. + num_bytes = 5u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, false)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + expected_buffer[0] = 8; + expected_buffer[1] = 9; + expected_buffer[2] = 100; + expected_buffer[3] = 101; + expected_buffer[4] = 102; + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + // Internally, a circular buffer would now look like: + // -, -, -, 103, 104, 105, 106, 107, -, - + + // Write one integer. + num_bytes = 1u * sizeof(int32_t); + Seq(200, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, false)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + // Internally, a circular buffer would now look like: + // -, -, -, 103, 104, 105, 106, 107, 200, - + + // Write five more. + num_bytes = 5u * sizeof(int32_t); + Seq(300, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, false)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + // Internally, a circular buffer would now look like: + // 301, 302, 303, 304, 104, 105, 106, 107, 200, 300 + + // Read it all. + num_bytes = sizeof(buffer); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, false)); + EXPECT_EQ(10u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + expected_buffer[0] = 104; + expected_buffer[1] = 105; + expected_buffer[2] = 106; + expected_buffer[3] = 107; + expected_buffer[4] = 200; + expected_buffer[5] = 300; + expected_buffer[6] = 301; + expected_buffer[7] = 302; + expected_buffer[8] = 303; + expected_buffer[9] = 304; + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Test two-phase writes, including in all-or-none mode. + // Note: Again, the following depends on an implementation detail -- namely + // that the write pointer will point at the 5th element of the buffer (and the + // buffer has exactly the capacity requested). + + num_bytes = 0u; + void* write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, false)); + EXPECT_TRUE(write_ptr != NULL); + EXPECT_EQ(6u * sizeof(int32_t), num_bytes); + Seq(400, 6, static_cast<int32_t*>(write_ptr)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(6u * sizeof(int32_t))); + // Internally, a circular buffer would now look like: + // -, -, -, -, 400, 401, 402, 403, 404, 405 + + // |ProducerBeginWriteData()| ignores |*num_bytes| except in "all-or-none" + // mode. + num_bytes = 6u * sizeof(int32_t); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, false)); + EXPECT_EQ(4u * sizeof(int32_t), num_bytes); + static_cast<int32_t*>(write_ptr)[0] = 500; + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(1u * sizeof(int32_t))); + // Internally, a circular buffer would now look like: + // 500, -, -, -, 400, 401, 402, 403, 404, 405 + + // Requesting a 10-element buffer in all-or-none mode fails at this point. + num_bytes = 10u * sizeof(int32_t); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + + // But requesting, say, a 5-element (up to 9, really) buffer should be okay. + // It will discard two elements. + num_bytes = 5u * sizeof(int32_t); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + // Only write 4 elements though. + Seq(600, 4, static_cast<int32_t*>(write_ptr)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(4u * sizeof(int32_t))); + // Internally, a circular buffer would now look like: + // 500, 600, 601, 602, 603, -, 402, 403, 404, 405 + + // Do this again. Make sure we can get a buffer all the way out to the end of + // the internal buffer. + num_bytes = 5u * sizeof(int32_t); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + // Only write 3 elements though. + Seq(700, 3, static_cast<int32_t*>(write_ptr)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(3u * sizeof(int32_t))); + // Internally, a circular buffer would now look like: + // 500, 600, 601, 602, 603, 700, 701, 702, -, - + + // Read everything. + num_bytes = sizeof(buffer); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, false)); + EXPECT_EQ(8u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + expected_buffer[0] = 500; + expected_buffer[1] = 600; + expected_buffer[2] = 601; + expected_buffer[3] = 602; + expected_buffer[4] = 603; + expected_buffer[5] = 700; + expected_buffer[6] = 701; + expected_buffer[7] = 702; + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +TEST(LocalDataPipeTest, AllOrNone) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Try writing way too much. + uint32_t num_bytes = 20u * sizeof(int32_t); + int32_t buffer[100]; + Seq(0, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ProducerWriteData(buffer, &num_bytes, true)); + + // Should still be empty. + num_bytes = ~0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + // Write some data. + num_bytes = 5u * sizeof(int32_t); + Seq(100, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + + // Half full. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + + // Too much. + num_bytes = 6u * sizeof(int32_t); + Seq(200, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ProducerWriteData(buffer, &num_bytes, true)); + + // Try reading too much. + num_bytes = 11u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerReadData(buffer, &num_bytes, true)); + int32_t expected_buffer[100]; + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much. + num_bytes = 11u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerDiscardData(&num_bytes, true)); + + // Just a little. + num_bytes = 2u * sizeof(int32_t); + Seq(300, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(2u * sizeof(int32_t), num_bytes); + + // Just right. + num_bytes = 3u * sizeof(int32_t); + Seq(400, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(3u * sizeof(int32_t), num_bytes); + + // Exactly full. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(10u * sizeof(int32_t), num_bytes); + + // Read half. + num_bytes = 5u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, true)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(100, 5, expected_buffer); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try reading too much again. + num_bytes = 6u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much again. + num_bytes = 6u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerDiscardData(&num_bytes, true)); + + // Discard a little. + num_bytes = 2u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerDiscardData(&num_bytes, true)); + EXPECT_EQ(2u * sizeof(int32_t), num_bytes); + + // Three left. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(3u * sizeof(int32_t), num_bytes); + + // Close the producer, then test producer-closed cases. + dp->ProducerClose(); + + // Try reading too much; "failed precondition" since the producer is closed. + num_bytes = 4u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much; "failed precondition" again. + num_bytes = 4u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerDiscardData(&num_bytes, true)); + + // Read a little. + num_bytes = 2u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, true)); + EXPECT_EQ(2u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(400, 2, expected_buffer); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Discard the remaining element. + num_bytes = 1u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerDiscardData(&num_bytes, true)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + + // Empty again. + num_bytes = ~0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + dp->ConsumerClose(); +} + +TEST(LocalDataPipeTest, AllOrNoneMayDiscard) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Try writing way too much. + uint32_t num_bytes = 20u * sizeof(int32_t); + int32_t buffer[100]; + Seq(0, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ProducerWriteData(buffer, &num_bytes, true)); + + // Write some stuff. + num_bytes = 5u * sizeof(int32_t); + Seq(100, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + + // Write lots of stuff (discarding all but "104"). + num_bytes = 9u * sizeof(int32_t); + Seq(200, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(9u * sizeof(int32_t), num_bytes); + + // Read one. + num_bytes = 1u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, true)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + int32_t expected_buffer[100]; + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + expected_buffer[0] = 104; + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try reading too many. + num_bytes = 10u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too many. + num_bytes = 10u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerDiscardData(&num_bytes, true)); + + // Discard a bunch. + num_bytes = 4u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerDiscardData(&num_bytes, true)); + + // Half full. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(5u * sizeof(int32_t), num_bytes); + + // Write as much as possible. + num_bytes = 10u * sizeof(int32_t); + Seq(300, arraysize(buffer), buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(10u * sizeof(int32_t), num_bytes); + + // Read everything. + num_bytes = 10u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + EXPECT_EQ(10u * sizeof(int32_t), num_bytes); + Seq(300, 10, expected_buffer); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Note: All-or-none two-phase writes on a "may discard" data pipe are tested + // in LocalDataPipeTest.MayDiscard. + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +TEST(LocalDataPipeTest, TwoPhaseAllOrNone) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Try writing way too much (two-phase). + uint32_t num_bytes = 20u * sizeof(int32_t); + void* write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + + // Try writing an amount which isn't a multiple of the element size + // (two-phase). + COMPILE_ASSERT(sizeof(int32_t) > 1u, wow_int32_ts_have_size_1); + num_bytes = 1u; + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + + // Try reading way too much (two-phase). + num_bytes = 20u * sizeof(int32_t); + const void* read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, true)); + + // Write half (two-phase). + num_bytes = 5u * sizeof(int32_t); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + // May provide more space than requested. + EXPECT_GE(num_bytes, 5u * sizeof(int32_t)); + EXPECT_TRUE(write_ptr != NULL); + Seq(0, 5, static_cast<int32_t*>(write_ptr)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(5u * sizeof(int32_t))); + + // Try reading an amount which isn't a multiple of the element size + // (two-phase). + num_bytes = 1u; + read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, true)); + + // Read one (two-phase). + num_bytes = 1u * sizeof(int32_t); + read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, true)); + EXPECT_GE(num_bytes, 1u * sizeof(int32_t)); + EXPECT_EQ(0, static_cast<const int32_t*>(read_ptr)[0]); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(1u * sizeof(int32_t))); + + // We should have four left, leaving room for six. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(4u * sizeof(int32_t), num_bytes); + + // Assuming a tight circular buffer of the specified capacity, we can't do a + // two-phase write of six now. + num_bytes = 6u * sizeof(int32_t); + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, true)); + + // Write six elements (simple), filling the buffer. + num_bytes = 6u * sizeof(int32_t); + int32_t buffer[100]; + Seq(100, 6, buffer); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(buffer, &num_bytes, true)); + EXPECT_EQ(6u * sizeof(int32_t), num_bytes); + + // We have ten. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(10u * sizeof(int32_t), num_bytes); + + // But a two-phase read of ten should fail. + num_bytes = 10u * sizeof(int32_t); + read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, true)); + + // Close the producer. + dp->ProducerClose(); + + // A two-phase read of nine should work. + num_bytes = 9u * sizeof(int32_t); + read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, true)); + EXPECT_GE(num_bytes, 9u * sizeof(int32_t)); + EXPECT_EQ(1, static_cast<const int32_t*>(read_ptr)[0]); + EXPECT_EQ(2, static_cast<const int32_t*>(read_ptr)[1]); + EXPECT_EQ(3, static_cast<const int32_t*>(read_ptr)[2]); + EXPECT_EQ(4, static_cast<const int32_t*>(read_ptr)[3]); + EXPECT_EQ(100, static_cast<const int32_t*>(read_ptr)[4]); + EXPECT_EQ(101, static_cast<const int32_t*>(read_ptr)[5]); + EXPECT_EQ(102, static_cast<const int32_t*>(read_ptr)[6]); + EXPECT_EQ(103, static_cast<const int32_t*>(read_ptr)[7]); + EXPECT_EQ(104, static_cast<const int32_t*>(read_ptr)[8]); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(9u * sizeof(int32_t))); + + // A two-phase read of two should fail, with "failed precondition". + num_bytes = 2u * sizeof(int32_t); + read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, true)); + + dp->ConsumerClose(); +} + +// Tests that |ProducerWriteData()| and |ConsumerReadData()| writes and reads, +// respectively, as much as possible, even if it has to "wrap around" the +// internal circular buffer. (Note that the two-phase write and read do not do +// this.) +TEST(LocalDataPipeTest, WrapAround) { + unsigned char test_data[1000]; + for (size_t i = 0; i < arraysize(test_data); i++) + test_data[i] = static_cast<unsigned char>(i); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 100u // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + // This test won't be valid if |ValidateCreateOptions()| decides to give the + // pipe more space. + ASSERT_EQ(100u, validated_options.capacity_num_bytes); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Write 20 bytes. + uint32_t num_bytes = 20u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(&test_data[0], &num_bytes, false)); + EXPECT_EQ(20u, num_bytes); + + // Read 10 bytes. + unsigned char read_buffer[1000] = { 0 }; + num_bytes = 10u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerReadData(read_buffer, &num_bytes, false)); + EXPECT_EQ(10u, num_bytes); + EXPECT_EQ(0, memcmp(read_buffer, &test_data[0], 10u)); + + // Check that a two-phase write can now only write (at most) 80 bytes. (This + // checks an implementation detail; this behavior is not guaranteed, but we + // need it for this test.) + void* write_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr != NULL); + EXPECT_EQ(80u, num_bytes); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(0u)); + + // Write as much data as we can (using |ProducerWriteData()|). We should write + // 90 bytes. + num_bytes = 200u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(&test_data[20], &num_bytes, false)); + EXPECT_EQ(90u, num_bytes); + + // Check that a two-phase read can now only read (at most) 90 bytes. (This + // checks an implementation detail; this behavior is not guaranteed, but we + // need it for this test.) + const void* read_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(read_buffer_ptr != NULL); + EXPECT_EQ(90u, num_bytes); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(0u)); + + // Read as much as possible (using |ConsumerReadData()|). We should read 100 + // bytes. + num_bytes = + static_cast<uint32_t>(arraysize(read_buffer) * sizeof(read_buffer[0])); + memset(read_buffer, 0, num_bytes); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerReadData(read_buffer, &num_bytes, false)); + EXPECT_EQ(100u, num_bytes); + EXPECT_EQ(0, memcmp(read_buffer, &test_data[10], 100u)); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +// Tests the behavior of closing the producer or consumer with respect to +// writes and reads (simple and two-phase). +TEST(LocalDataPipeTest, CloseWriteRead) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast<uint32_t>(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + // Close producer first, then consumer. + { + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(kTestData, &num_bytes, false)); + EXPECT_EQ(kTestDataSize, num_bytes); + + // Write it again, so we'll have something left over. + num_bytes = kTestDataSize; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(kTestData, &num_bytes, false)); + EXPECT_EQ(kTestDataSize, num_bytes); + + // Start two-phase write. + void* write_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr != NULL); + EXPECT_GT(num_bytes, 0u); + + // Start two-phase read. + const void* read_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(read_buffer_ptr != NULL); + EXPECT_EQ(2u * kTestDataSize, num_bytes); + + // Close the producer. + dp->ProducerClose(); + + // The consumer can finish its two-phase read. + EXPECT_EQ(0, memcmp(read_buffer_ptr, kTestData, kTestDataSize)); + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(kTestDataSize)); + + // And start another. + read_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(read_buffer_ptr != NULL); + EXPECT_EQ(kTestDataSize, num_bytes); + + // Close the consumer, which cancels the two-phase read. + dp->ConsumerClose(); + } + + // Close consumer first, then producer. + { + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(kTestData, &num_bytes, false)); + EXPECT_EQ(kTestDataSize, num_bytes); + + // Start two-phase write. + void* write_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr != NULL); + ASSERT_GT(num_bytes, kTestDataSize); + + // Start two-phase read. + const void* read_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(read_buffer_ptr != NULL); + EXPECT_EQ(kTestDataSize, num_bytes); + + // Close the consumer. + dp->ConsumerClose(); + + // Actually write some data. (Note: Premature freeing of the buffer would + // probably only be detected under ASAN or similar.) + memcpy(write_buffer_ptr, kTestData, kTestDataSize); + // Note: Even though the consumer has been closed, ending the two-phase + // write will report success. + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerEndWriteData(kTestDataSize)); + + // But trying to write should result in failure. + num_bytes = kTestDataSize; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ProducerWriteData(kTestData, &num_bytes, false)); + + // As will trying to start another two-phase write. + write_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ProducerBeginWriteData(&write_buffer_ptr, &num_bytes, false)); + + dp->ProducerClose(); + } + + // Test closing the consumer first, then the producer, with an active + // two-phase write. + { + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Start two-phase write. + void* write_buffer_ptr = NULL; + uint32_t num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr != NULL); + ASSERT_GT(num_bytes, kTestDataSize); + + dp->ConsumerClose(); + dp->ProducerClose(); + } + + // Test closing the producer and then trying to read (with no data). + { + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerWriteData(kTestData, &num_bytes, false)); + EXPECT_EQ(kTestDataSize, num_bytes); + + // Close the producer. + dp->ProducerClose(); + + // Read that data. + char buffer[1000]; + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerReadData(buffer, &num_bytes, false)); + EXPECT_EQ(kTestDataSize, num_bytes); + EXPECT_EQ(0, memcmp(buffer, kTestData, kTestDataSize)); + + // A second read should fail. + num_bytes = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerReadData(buffer, &num_bytes, false)); + + // A two-phase read should also fail. + const void* read_buffer_ptr = NULL; + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerBeginReadData(&read_buffer_ptr, &num_bytes, false)); + + // Ditto for discard. + num_bytes = 10u; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerDiscardData(&num_bytes, false)); + + dp->ConsumerClose(); + } +} + +TEST(LocalDataPipeTest, TwoPhaseMoreInvalidArguments) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // No data. + uint32_t num_bytes = 1000u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + // Try "ending" a two-phase write when one isn't active. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ProducerEndWriteData(1u * sizeof(int32_t))); + + // Still no data. + num_bytes = 1000u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + // Try ending a two-phase write with an invalid amount (too much). + num_bytes = 0u; + void* write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, false)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dp->ProducerEndWriteData( + num_bytes + static_cast<uint32_t>(sizeof(int32_t)))); + + // But the two-phase write still ended. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, dp->ProducerEndWriteData(0u)); + + // Still no data. + num_bytes = 1000u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + // Try ending a two-phase write with an invalid amount (not a multiple of the + // element size). + num_bytes = 0u; + write_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ProducerBeginWriteData(&write_ptr, &num_bytes, false)); + EXPECT_GE(num_bytes, 1u); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dp->ProducerEndWriteData(1u)); + + // But the two-phase write still ended. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, dp->ProducerEndWriteData(0u)); + + // Still no data. + num_bytes = 1000u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(0u, num_bytes); + + // Now write some data, so we'll be able to try reading. + int32_t element = 123; + num_bytes = 1u * sizeof(int32_t); + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(&element, &num_bytes, false)); + + // One element available. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try "ending" a two-phase read when one isn't active. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + dp->ConsumerEndReadData(1u * sizeof(int32_t))); + + // Still one element available. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try ending a two-phase read with an invalid amount (too much). + num_bytes = 0u; + const void* read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, false)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dp->ConsumerEndReadData( + num_bytes + static_cast<uint32_t>(sizeof(int32_t)))); + + // Still one element available. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try ending a two-phase read with an invalid amount (not a multiple of the + // element size). + num_bytes = 0u; + read_ptr = NULL; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, false)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + EXPECT_EQ(123, static_cast<const int32_t*>(read_ptr)[0]); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dp->ConsumerEndReadData(1u)); + + // Still one element available. + num_bytes = 0u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(&num_bytes)); + EXPECT_EQ(1u * sizeof(int32_t), num_bytes); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +// Tests that even with "may discard", the data won't change under a two-phase +// read. +// TODO(vtl): crbug.com/348644: We currently don't pass this. (There are two +// related issues: First, we don't recognize that the data given to +// |ConsumerBeginReadData()| isn't discardable until |ConsumerEndReadData()|, +// and thus we erroneously allow |ProducerWriteData()| to succeed. Second, the +// |ProducerWriteData()| then changes the data underneath the two-phase read.) +TEST(LocalDataPipeTest, DISABLED_MayDiscardTwoPhaseConsistent) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD, // |flags|. + 1, // |element_num_bytes|. + 2 // |capacity_num_bytes|. + }; + MojoCreateDataPipeOptions validated_options = { 0 }; + EXPECT_EQ(MOJO_RESULT_OK, + DataPipe::ValidateCreateOptions(&options, &validated_options)); + + scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options)); + + // Write some elements. + char elements[2] = { 'a', 'b' }; + uint32_t num_bytes = 2u; + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(elements, &num_bytes, false)); + EXPECT_EQ(2u, num_bytes); + + // Begin reading. + const void* read_ptr = NULL; + num_bytes = 2u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, false)); + EXPECT_EQ(2u, num_bytes); + EXPECT_EQ('a', static_cast<const char*>(read_ptr)[0]); + EXPECT_EQ('b', static_cast<const char*>(read_ptr)[1]); + + // Try to write some more. But nothing should be discardable right now. + elements[0] = 'x'; + elements[1] = 'y'; + num_bytes = 2u; + // TODO(vtl): This should be: + // EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + // dp->ProducerWriteData(elements, &num_bytes, false)); + // but we incorrectly think that the bytes being read are discardable. Letting + // this through reveals the significant consequence. + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(elements, &num_bytes, false)); + + // Check that our read buffer hasn't changed underneath us. + EXPECT_EQ('a', static_cast<const char*>(read_ptr)[0]); + EXPECT_EQ('b', static_cast<const char*>(read_ptr)[1]); + + // End reading. + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(2u)); + + // Now writing should succeed. + EXPECT_EQ(MOJO_RESULT_OK, dp->ProducerWriteData(elements, &num_bytes, false)); + + // And if we read, we should get the new values. + read_ptr = NULL; + num_bytes = 2u; + EXPECT_EQ(MOJO_RESULT_OK, + dp->ConsumerBeginReadData(&read_ptr, &num_bytes, false)); + EXPECT_EQ(2u, num_bytes); + EXPECT_EQ('x', static_cast<const char*>(read_ptr)[0]); + EXPECT_EQ('y', static_cast<const char*>(read_ptr)[1]); + + // End reading. + EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(2u)); + + dp->ProducerClose(); + dp->ConsumerClose(); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/local_message_pipe_endpoint.cc b/chromium/mojo/system/local_message_pipe_endpoint.cc new file mode 100644 index 00000000000..09decd3fabb --- /dev/null +++ b/chromium/mojo/system/local_message_pipe_endpoint.cc @@ -0,0 +1,165 @@ +// 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 "mojo/system/local_message_pipe_endpoint.h" + +#include <string.h> + +#include "base/logging.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/message_in_transit.h" + +namespace mojo { +namespace system { + +LocalMessagePipeEndpoint::LocalMessagePipeEndpoint() + : is_open_(true), + is_peer_open_(true) { +} + +LocalMessagePipeEndpoint::~LocalMessagePipeEndpoint() { + DCHECK(!is_open_); + DCHECK(message_queue_.IsEmpty()); // Should be implied by not being open. +} + +MessagePipeEndpoint::Type LocalMessagePipeEndpoint::GetType() const { + return kTypeLocal; +} + +bool LocalMessagePipeEndpoint::OnPeerClose() { + DCHECK(is_open_); + DCHECK(is_peer_open_); + + HandleSignalsState old_state = GetHandleSignalsState(); + is_peer_open_ = false; + HandleSignalsState new_state = GetHandleSignalsState(); + + if (!new_state.equals(old_state)) + waiter_list_.AwakeWaitersForStateChange(new_state); + + return true; +} + +void LocalMessagePipeEndpoint::EnqueueMessage( + scoped_ptr<MessageInTransit> message) { + DCHECK(is_open_); + DCHECK(is_peer_open_); + + bool was_empty = message_queue_.IsEmpty(); + message_queue_.AddMessage(message.Pass()); + if (was_empty) + waiter_list_.AwakeWaitersForStateChange(GetHandleSignalsState()); +} + +void LocalMessagePipeEndpoint::Close() { + DCHECK(is_open_); + is_open_ = false; + message_queue_.Clear(); +} + +void LocalMessagePipeEndpoint::CancelAllWaiters() { + DCHECK(is_open_); + waiter_list_.CancelAllWaiters(); +} + +MojoResult LocalMessagePipeEndpoint::ReadMessage(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags) { + DCHECK(is_open_); + DCHECK(!dispatchers || dispatchers->empty()); + + const uint32_t max_bytes = num_bytes ? *num_bytes : 0; + const uint32_t max_num_dispatchers = num_dispatchers ? *num_dispatchers : 0; + + if (message_queue_.IsEmpty()) { + return is_peer_open_ ? MOJO_RESULT_SHOULD_WAIT : + MOJO_RESULT_FAILED_PRECONDITION; + } + + // TODO(vtl): If |flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD|, we could pop + // and release the lock immediately. + bool enough_space = true; + MessageInTransit* message = message_queue_.PeekMessage(); + if (num_bytes) + *num_bytes = message->num_bytes(); + if (message->num_bytes() <= max_bytes) + memcpy(bytes, message->bytes(), message->num_bytes()); + else + enough_space = false; + + if (DispatcherVector* queued_dispatchers = message->dispatchers()) { + if (num_dispatchers) + *num_dispatchers = static_cast<uint32_t>(queued_dispatchers->size()); + if (enough_space) { + if (queued_dispatchers->empty()) { + // Nothing to do. + } else if (queued_dispatchers->size() <= max_num_dispatchers) { + DCHECK(dispatchers); + dispatchers->swap(*queued_dispatchers); + } else { + enough_space = false; + } + } + } else { + if (num_dispatchers) + *num_dispatchers = 0; + } + + message = NULL; + + if (enough_space || (flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)) { + message_queue_.DiscardMessage(); + + // Now it's empty, thus no longer readable. + if (message_queue_.IsEmpty()) { + // It's currently not possible to wait for non-readability, but we should + // do the state change anyway. + waiter_list_.AwakeWaitersForStateChange(GetHandleSignalsState()); + } + } + + if (!enough_space) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + return MOJO_RESULT_OK; +} + +MojoResult LocalMessagePipeEndpoint::AddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + DCHECK(is_open_); + + HandleSignalsState state = GetHandleSignalsState(); + if (state.satisfies(signals)) + return MOJO_RESULT_ALREADY_EXISTS; + if (!state.can_satisfy(signals)) + return MOJO_RESULT_FAILED_PRECONDITION; + + waiter_list_.AddWaiter(waiter, signals, context); + return MOJO_RESULT_OK; +} + +void LocalMessagePipeEndpoint::RemoveWaiter(Waiter* waiter) { + DCHECK(is_open_); + waiter_list_.RemoveWaiter(waiter); +} + +HandleSignalsState LocalMessagePipeEndpoint::GetHandleSignalsState() { + HandleSignalsState rv; + if (!message_queue_.IsEmpty()) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } + if (is_peer_open_) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; + } + return rv; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/local_message_pipe_endpoint.h b/chromium/mojo/system/local_message_pipe_endpoint.h new file mode 100644 index 00000000000..4e59cfbc421 --- /dev/null +++ b/chromium/mojo/system/local_message_pipe_endpoint.h @@ -0,0 +1,63 @@ +// 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 MOJO_SYSTEM_LOCAL_MESSAGE_PIPE_ENDPOINT_H_ +#define MOJO_SYSTEM_LOCAL_MESSAGE_PIPE_ENDPOINT_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/system/handle_signals_state.h" +#include "mojo/system/message_in_transit_queue.h" +#include "mojo/system/message_pipe_endpoint.h" +#include "mojo/system/system_impl_export.h" +#include "mojo/system/waiter_list.h" + +namespace mojo { +namespace system { + +class MOJO_SYSTEM_IMPL_EXPORT LocalMessagePipeEndpoint + : public MessagePipeEndpoint { + public: + LocalMessagePipeEndpoint(); + virtual ~LocalMessagePipeEndpoint(); + + // |MessagePipeEndpoint| implementation: + virtual Type GetType() const OVERRIDE; + virtual bool OnPeerClose() OVERRIDE; + virtual void EnqueueMessage(scoped_ptr<MessageInTransit> message) OVERRIDE; + + // There's a dispatcher for |LocalMessagePipeEndpoint|s, so we have to + // implement/override these: + virtual void Close() OVERRIDE; + virtual void CancelAllWaiters() OVERRIDE; + virtual MojoResult ReadMessage(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags) OVERRIDE; + virtual MojoResult AddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) OVERRIDE; + virtual void RemoveWaiter(Waiter* waiter) OVERRIDE; + + // This is only to be used by |ProxyMessagePipeEndpoint|: + MessageInTransitQueue* message_queue() { return &message_queue_; } + + private: + HandleSignalsState GetHandleSignalsState(); + + bool is_open_; + bool is_peer_open_; + + // Queue of incoming messages. + MessageInTransitQueue message_queue_; + WaiterList waiter_list_; + + DISALLOW_COPY_AND_ASSIGN(LocalMessagePipeEndpoint); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_LOCAL_MESSAGE_PIPE_ENDPOINT_H_ diff --git a/chromium/mojo/system/mapping_table.cc b/chromium/mojo/system/mapping_table.cc new file mode 100644 index 00000000000..a6e5bb39cb8 --- /dev/null +++ b/chromium/mojo/system/mapping_table.cc @@ -0,0 +1,48 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/mapping_table.h" + +#include "base/logging.h" +#include "mojo/system/constants.h" +#include "mojo/system/raw_shared_buffer.h" + +namespace mojo { +namespace system { + +MappingTable::MappingTable() { +} + +MappingTable::~MappingTable() { + // This should usually not be reached (the only instance should be owned by + // the singleton |Core|, which lives forever), except in tests. +} + +MojoResult MappingTable::AddMapping( + scoped_ptr<RawSharedBufferMapping> mapping) { + DCHECK(mapping); + + if (address_to_mapping_map_.size() >= kMaxMappingTableSize) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + uintptr_t address = reinterpret_cast<uintptr_t>(mapping->base()); + DCHECK(address_to_mapping_map_.find(address) == + address_to_mapping_map_.end()); + address_to_mapping_map_[address] = mapping.release(); + return MOJO_RESULT_OK; +} + +MojoResult MappingTable::RemoveMapping(void* address) { + AddressToMappingMap::iterator it = + address_to_mapping_map_.find(reinterpret_cast<uintptr_t>(address)); + if (it == address_to_mapping_map_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + RawSharedBufferMapping* mapping_to_delete = it->second; + address_to_mapping_map_.erase(it); + delete mapping_to_delete; + return MOJO_RESULT_OK; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/mapping_table.h b/chromium/mojo/system/mapping_table.h new file mode 100644 index 00000000000..5268234b1fe --- /dev/null +++ b/chromium/mojo/system/mapping_table.h @@ -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. + +#ifndef MOJO_SYSTEM_MAPPING_TABLE_H_ +#define MOJO_SYSTEM_MAPPING_TABLE_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Core; +class RawSharedBufferMapping; + +// Test-only function (defined/used in embedder/test_embedder.cc). Declared here +// so it can be friended. +namespace internal { +bool ShutdownCheckNoLeaks(Core*); +} + +// This class provides the (global) table of memory mappings (owned by |Core|), +// which maps mapping base addresses to |RawSharedBuffer::Mapping|s. +// +// This class is NOT thread-safe; locking is left to |Core|. +class MOJO_SYSTEM_IMPL_EXPORT MappingTable { + public: + MappingTable(); + ~MappingTable(); + + // Tries to add a mapping. (Takes ownership of the mapping in all cases; on + // failure, it will be destroyed.) + MojoResult AddMapping(scoped_ptr<RawSharedBufferMapping> mapping); + MojoResult RemoveMapping(void* address); + + private: + friend bool internal::ShutdownCheckNoLeaks(Core*); + + typedef base::hash_map<uintptr_t, RawSharedBufferMapping*> + AddressToMappingMap; + AddressToMappingMap address_to_mapping_map_; + + DISALLOW_COPY_AND_ASSIGN(MappingTable); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MAPPING_TABLE_H_ diff --git a/chromium/mojo/system/memory.cc b/chromium/mojo/system/memory.cc new file mode 100644 index 00000000000..cee7e052145 --- /dev/null +++ b/chromium/mojo/system/memory.cc @@ -0,0 +1,99 @@ +// 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 "mojo/system/memory.h" + +#include <limits> + +#include "base/logging.h" +#include "build/build_config.h" + +namespace mojo { +namespace system { + +namespace internal { + +template <size_t alignment> +bool IsAligned(const void* pointer) { + return reinterpret_cast<uintptr_t>(pointer) % alignment == 0; +} + +// MSVS (2010, 2013) sometimes (on the stack) aligns, e.g., |int64_t|s (for +// which |__alignof(int64_t)| is 8) to 4-byte boundaries. http://goo.gl/Y2n56T +#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS) +template <> +bool IsAligned<8>(const void* pointer) { + return reinterpret_cast<uintptr_t>(pointer) % 4 == 0; +} +#endif + +template <size_t size, size_t alignment> +bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerHelper(const void* pointer) { + // TODO(vtl): If running in kernel mode, do a full verification. For now, just + // check that it's non-null and aligned. (A faster user mode implementation is + // also possible if this check is skipped.) + return !!pointer && IsAligned<alignment>(pointer); +} + +// Explicitly instantiate the sizes we need. Add instantiations as needed. +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerHelper<1, 1>( + const void*); +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerHelper<4, 4>( + const void*); +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerHelper<8, 8>( + const void*); +// Notwithstanding the comments above about MSVS, whenever we expect an +// alignment of 8 for something of size 4, it's due to an explicit (e.g., +// #pragma align) alignment specification, which MSVS *does* respect. We want +// this in particular to check that various Options structs are aligned. +#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS) +template <> +bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerHelper<4, 8>( + const void* pointer) { + return !!pointer && reinterpret_cast<uintptr_t>(pointer) % 8 == 0; +} +#else +template MOJO_SYSTEM_IMPL_EXPORT bool VerifyUserPointerHelper<4, 8>( + const void*); +#endif + +template <size_t size, size_t alignment> +bool VerifyUserPointerWithCountHelper(const void* pointer, size_t count) { + if (count > std::numeric_limits<size_t>::max() / size) + return false; + + // TODO(vtl): If running in kernel mode, do a full verification. For now, just + // check that it's non-null and aligned if |count| is nonzero. (A faster user + // mode implementation is also possible if this check is skipped.) + return count == 0 || (!!pointer && IsAligned<alignment>(pointer)); +} + +// Explicitly instantiate the sizes we need. Add instantiations as needed. +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithCountHelper<1, 1>( + const void*, size_t); +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithCountHelper<4, 4>( + const void*, size_t); +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithCountHelper<8, 8>( + const void*, size_t); + +} // nameespace internal + +template <size_t alignment> +bool VerifyUserPointerWithSize(const void* pointer, size_t size) { + // TODO(vtl): If running in kernel mode, do a full verification. For now, just + // check that it's non-null and aligned. (A faster user mode implementation is + // also possible if this check is skipped.) + return size == 0 || (!!pointer && internal::IsAligned<alignment>(pointer)); +} + +// Explicitly instantiate the alignments we need. Add instantiations as needed. +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithSize<1>(const void*, + size_t); +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithSize<4>(const void*, + size_t); +template bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithSize<8>(const void*, + size_t); + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/memory.h b/chromium/mojo/system/memory.h new file mode 100644 index 00000000000..96f800ee15d --- /dev/null +++ b/chromium/mojo/system/memory.h @@ -0,0 +1,56 @@ +// 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 MOJO_SYSTEM_MEMORY_H_ +#define MOJO_SYSTEM_MEMORY_H_ + +#include <stddef.h> + +#include "mojo/public/c/system/macros.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +namespace internal { + +template <size_t size, size_t alignment> +bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerHelper(const void* pointer); + +// Note: This is also used by options_validation.h. +template <size_t size, size_t alignment> +bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithCountHelper( + const void* pointer, + size_t count); + +} // namespace internal + +// Verify (insofar as possible/necessary) that a |T| can be read from the user +// |pointer|. +template <typename T> +bool VerifyUserPointer(const T* pointer) { + return internal::VerifyUserPointerHelper<sizeof(T), MOJO_ALIGNOF(T)>(pointer); +} + +// Verify (insofar as possible/necessary) that |count| |T|s can be read from the +// user |pointer|; |count| may be zero. (This is done carefully since |count * +// sizeof(T)| may overflow a |size_t|.) +template <typename T> +bool VerifyUserPointerWithCount(const T* pointer, size_t count) { + return internal::VerifyUserPointerWithCountHelper<sizeof(T), + MOJO_ALIGNOF(T)>(pointer, + count); +} + +// Verify that |size| bytes (which may be zero) can be read from the user +// |pointer|, and that |pointer| has the specified |alignment| (if |size| is +// nonzero). +template <size_t alignment> +bool MOJO_SYSTEM_IMPL_EXPORT VerifyUserPointerWithSize(const void* pointer, + size_t size); + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MEMORY_H_ diff --git a/chromium/mojo/system/memory_unittest.cc b/chromium/mojo/system/memory_unittest.cc new file mode 100644 index 00000000000..b23d50ae7be --- /dev/null +++ b/chromium/mojo/system/memory_unittest.cc @@ -0,0 +1,136 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/memory.h" + +#include <stddef.h> +#include <stdint.h> + +#include <limits> + +#include "mojo/public/c/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +TEST(MemoryTest, Valid) { + char my_char; + int32_t my_int32; + int64_t my_int64; + char my_char_array[5]; + int32_t my_int32_array[5]; + int64_t my_int64_array[5]; + + // |VerifyUserPointer|: + + EXPECT_TRUE(VerifyUserPointer<char>(&my_char)); + EXPECT_TRUE(VerifyUserPointer<int32_t>(&my_int32)); + EXPECT_TRUE(VerifyUserPointer<int64_t>(&my_int64)); + + // |VerifyUserPointerWithCount|: + + EXPECT_TRUE(VerifyUserPointerWithCount<char>(my_char_array, 5)); + EXPECT_TRUE(VerifyUserPointerWithCount<int32_t>(my_int32_array, 5)); + EXPECT_TRUE(VerifyUserPointerWithCount<int64_t>(my_int64_array, 5)); + + // It shouldn't care what the pointer is if the count is zero. + EXPECT_TRUE(VerifyUserPointerWithCount<char>(NULL, 0)); + EXPECT_TRUE(VerifyUserPointerWithCount<int32_t>(NULL, 0)); + EXPECT_TRUE(VerifyUserPointerWithCount<int32_t>( + reinterpret_cast<const int32_t*>(1), 0)); + EXPECT_TRUE(VerifyUserPointerWithCount<int64_t>(NULL, 0)); + EXPECT_TRUE(VerifyUserPointerWithCount<int64_t>( + reinterpret_cast<const int64_t*>(1), 0)); + + // |VerifyUserPointerWithSize|: + + EXPECT_TRUE(VerifyUserPointerWithSize<1>(&my_char, sizeof(my_char))); + EXPECT_TRUE(VerifyUserPointerWithSize<1>(&my_int32, sizeof(my_int32))); + EXPECT_TRUE(VerifyUserPointerWithSize<MOJO_ALIGNOF(int32_t)>( + &my_int32, sizeof(my_int32))); + EXPECT_TRUE(VerifyUserPointerWithSize<1>(&my_int64, sizeof(my_int64))); + EXPECT_TRUE(VerifyUserPointerWithSize<MOJO_ALIGNOF(int64_t)>( + &my_int64, sizeof(my_int64))); + + EXPECT_TRUE(VerifyUserPointerWithSize<1>(my_char_array, + sizeof(my_char_array))); + EXPECT_TRUE(VerifyUserPointerWithSize<1>(my_int32_array, + sizeof(my_int32_array))); + EXPECT_TRUE(VerifyUserPointerWithSize<MOJO_ALIGNOF(int32_t)>( + my_int32_array, sizeof(my_int32_array))); + EXPECT_TRUE(VerifyUserPointerWithSize<1>(my_int64_array, + sizeof(my_int64_array))); + EXPECT_TRUE(VerifyUserPointerWithSize<MOJO_ALIGNOF(int64_t)>( + my_int64_array, sizeof(my_int64_array))); +} + +TEST(MemoryTest, Invalid) { + // Note: |VerifyUserPointer...()| are defined to be "best effort" checks (and + // may always return true). Thus these tests of invalid cases only reflect the + // current implementation. + + // These tests depend on |int32_t| and |int64_t| having nontrivial alignment. + MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int32_t) != 1, + int32_t_does_not_have_to_be_aligned); + MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int64_t) != 1, + int64_t_does_not_have_to_be_aligned); + + int32_t my_int32; + int64_t my_int64; + + // |VerifyUserPointer|: + + EXPECT_FALSE(VerifyUserPointer<char>(NULL)); + EXPECT_FALSE(VerifyUserPointer<int32_t>(NULL)); + EXPECT_FALSE(VerifyUserPointer<int64_t>(NULL)); + + // Unaligned: + EXPECT_FALSE(VerifyUserPointer<int32_t>(reinterpret_cast<const int32_t*>(1))); + EXPECT_FALSE(VerifyUserPointer<int64_t>(reinterpret_cast<const int64_t*>(1))); + + // |VerifyUserPointerWithCount|: + + EXPECT_FALSE(VerifyUserPointerWithCount<char>(NULL, 1)); + EXPECT_FALSE(VerifyUserPointerWithCount<int32_t>(NULL, 1)); + EXPECT_FALSE(VerifyUserPointerWithCount<int64_t>(NULL, 1)); + + // Unaligned: + EXPECT_FALSE(VerifyUserPointerWithCount<int32_t>( + reinterpret_cast<const int32_t*>(1), 1)); + EXPECT_FALSE(VerifyUserPointerWithCount<int64_t>( + reinterpret_cast<const int64_t*>(1), 1)); + + // Count too big: + EXPECT_FALSE(VerifyUserPointerWithCount<int32_t>( + &my_int32, std::numeric_limits<size_t>::max())); + EXPECT_FALSE(VerifyUserPointerWithCount<int64_t>( + &my_int64, std::numeric_limits<size_t>::max())); + + // |VerifyUserPointerWithSize|: + + EXPECT_FALSE(VerifyUserPointerWithSize<1>(NULL, 1)); + EXPECT_FALSE(VerifyUserPointerWithSize<4>(NULL, 1)); + EXPECT_FALSE(VerifyUserPointerWithSize<4>(NULL, 4)); + EXPECT_FALSE(VerifyUserPointerWithSize<8>(NULL, 1)); + EXPECT_FALSE(VerifyUserPointerWithSize<8>(NULL, 4)); + EXPECT_FALSE(VerifyUserPointerWithSize<8>(NULL, 8)); + + // Unaligned: + EXPECT_FALSE(VerifyUserPointerWithSize<4>(reinterpret_cast<const int32_t*>(1), + 1)); + EXPECT_FALSE(VerifyUserPointerWithSize<4>(reinterpret_cast<const int32_t*>(1), + 4)); + EXPECT_FALSE(VerifyUserPointerWithSize<8>(reinterpret_cast<const int32_t*>(1), + 1)); + EXPECT_FALSE(VerifyUserPointerWithSize<8>(reinterpret_cast<const int32_t*>(1), + 4)); + EXPECT_FALSE(VerifyUserPointerWithSize<8>(reinterpret_cast<const int32_t*>(1), + 8)); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_in_transit.cc b/chromium/mojo/system/message_in_transit.cc new file mode 100644 index 00000000000..944e12b19ae --- /dev/null +++ b/chromium/mojo/system/message_in_transit.cc @@ -0,0 +1,206 @@ +// 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 "mojo/system/message_in_transit.h" + +#include <string.h> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/system/constants.h" +#include "mojo/system/transport_data.h" + +namespace mojo { +namespace system { + +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type + MessageInTransit::kTypeMessagePipeEndpoint; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type + MessageInTransit::kTypeMessagePipe; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type + MessageInTransit::kTypeChannel; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Type + MessageInTransit::kTypeRawChannel; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype + MessageInTransit::kSubtypeMessagePipeEndpointData; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype + MessageInTransit::kSubtypeChannelRunMessagePipeEndpoint; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype + MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpoint; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype + MessageInTransit::kSubtypeChannelRemoveMessagePipeEndpointAck; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::Subtype + MessageInTransit::kSubtypeRawChannelPosixExtraPlatformHandles; +STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::EndpointId + MessageInTransit::kInvalidEndpointId; +STATIC_CONST_MEMBER_DEFINITION const size_t MessageInTransit::kMessageAlignment; + +struct MessageInTransit::PrivateStructForCompileAsserts { + // The size of |Header| must be a multiple of the alignment. + COMPILE_ASSERT(sizeof(Header) % kMessageAlignment == 0, + sizeof_MessageInTransit_Header_invalid); + // Avoid dangerous situations, but making sure that the size of the "header" + + // the size of the data fits into a 31-bit number. + COMPILE_ASSERT(static_cast<uint64_t>(sizeof(Header)) + kMaxMessageNumBytes <= + 0x7fffffffULL, + kMaxMessageNumBytes_too_big); + + // We assume (to avoid extra rounding code) that the maximum message (data) + // size is a multiple of the alignment. + COMPILE_ASSERT(kMaxMessageNumBytes % kMessageAlignment == 0, + kMessageAlignment_not_a_multiple_of_alignment); +}; + +MessageInTransit::View::View(size_t message_size, const void* buffer) + : buffer_(buffer) { + size_t next_message_size = 0; + DCHECK(MessageInTransit::GetNextMessageSize(buffer_, message_size, + &next_message_size)); + DCHECK_EQ(message_size, next_message_size); + // This should be equivalent. + DCHECK_EQ(message_size, total_size()); +} + +bool MessageInTransit::View::IsValid(size_t serialized_platform_handle_size, + const char** error_message) const { + // Note: This also implies a check on the |main_buffer_size()|, which is just + // |RoundUpMessageAlignment(sizeof(Header) + num_bytes())|. + if (num_bytes() > kMaxMessageNumBytes) { + *error_message = "Message data payload too large"; + return false; + } + + if (transport_data_buffer_size() > 0) { + const char* e = + TransportData::ValidateBuffer(serialized_platform_handle_size, + transport_data_buffer(), + transport_data_buffer_size()); + if (e) { + *error_message = e; + return false; + } + } + + return true; +} + +MessageInTransit::MessageInTransit(Type type, + Subtype subtype, + uint32_t num_bytes, + const void* bytes) + : main_buffer_size_(RoundUpMessageAlignment(sizeof(Header) + num_bytes)), + main_buffer_(static_cast<char*>(base::AlignedAlloc(main_buffer_size_, + kMessageAlignment))) { + DCHECK_LE(num_bytes, kMaxMessageNumBytes); + + // |total_size| is updated below, from the other values. + header()->type = type; + header()->subtype = subtype; + header()->source_id = kInvalidEndpointId; + header()->destination_id = kInvalidEndpointId; + header()->num_bytes = num_bytes; + header()->unused = 0; + // Note: If dispatchers are subsequently attached, then |total_size| will have + // to be adjusted. + UpdateTotalSize(); + + if (bytes) { + memcpy(MessageInTransit::bytes(), bytes, num_bytes); + memset(static_cast<char*>(MessageInTransit::bytes()) + num_bytes, 0, + main_buffer_size_ - sizeof(Header) - num_bytes); + } else { + memset(MessageInTransit::bytes(), 0, main_buffer_size_ - sizeof(Header)); + } +} + +MessageInTransit::MessageInTransit(const View& message_view) + : main_buffer_size_(message_view.main_buffer_size()), + main_buffer_(static_cast<char*>(base::AlignedAlloc(main_buffer_size_, + kMessageAlignment))) { + DCHECK_GE(main_buffer_size_, sizeof(Header)); + DCHECK_EQ(main_buffer_size_ % kMessageAlignment, 0u); + + memcpy(main_buffer_.get(), message_view.main_buffer(), main_buffer_size_); + DCHECK_EQ(main_buffer_size_, + RoundUpMessageAlignment(sizeof(Header) + num_bytes())); +} + +MessageInTransit::~MessageInTransit() { + if (dispatchers_) { + for (size_t i = 0; i < dispatchers_->size(); i++) { + if (!(*dispatchers_)[i]) + continue; + + DCHECK((*dispatchers_)[i]->HasOneRef()); + (*dispatchers_)[i]->Close(); + } + } +} + +// static +bool MessageInTransit::GetNextMessageSize(const void* buffer, + size_t buffer_size, + size_t* next_message_size) { + DCHECK(next_message_size); + if (!buffer_size) + return false; + DCHECK(buffer); + DCHECK_EQ(reinterpret_cast<uintptr_t>(buffer) % + MessageInTransit::kMessageAlignment, 0u); + + if (buffer_size < sizeof(Header)) + return false; + + const Header* header = static_cast<const Header*>(buffer); + *next_message_size = header->total_size; + DCHECK_EQ(*next_message_size % kMessageAlignment, 0u); + return true; +} + +void MessageInTransit::SetDispatchers( + scoped_ptr<DispatcherVector> dispatchers) { + DCHECK(dispatchers); + DCHECK(!dispatchers_); + DCHECK(!transport_data_); + + dispatchers_ = dispatchers.Pass(); +#ifndef NDEBUG + for (size_t i = 0; i < dispatchers_->size(); i++) + DCHECK(!(*dispatchers_)[i] || (*dispatchers_)[i]->HasOneRef()); +#endif +} + +void MessageInTransit::SetTransportData( + scoped_ptr<TransportData> transport_data) { + DCHECK(transport_data); + DCHECK(!transport_data_); + DCHECK(!dispatchers_); + + transport_data_ = transport_data.Pass(); +} + +void MessageInTransit::SerializeAndCloseDispatchers(Channel* channel) { + DCHECK(channel); + DCHECK(!transport_data_); + + if (!dispatchers_ || !dispatchers_->size()) + return; + + transport_data_.reset(new TransportData(dispatchers_.Pass(), channel)); + + // Update the sizes in the message header. + UpdateTotalSize(); +} + +void MessageInTransit::UpdateTotalSize() { + DCHECK_EQ(main_buffer_size_ % kMessageAlignment, 0u); + header()->total_size = static_cast<uint32_t>(main_buffer_size_); + if (transport_data_) { + header()->total_size += + static_cast<uint32_t>(transport_data_->buffer_size()); + } +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_in_transit.h b/chromium/mojo/system/message_in_transit.h new file mode 100644 index 00000000000..5a4a614c119 --- /dev/null +++ b/chromium/mojo/system/message_in_transit.h @@ -0,0 +1,258 @@ +// 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 MOJO_SYSTEM_MESSAGE_IN_TRANSIT_H_ +#define MOJO_SYSTEM_MESSAGE_IN_TRANSIT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "base/memory/aligned_memory.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Channel; +class TransportData; + +// This class is used to represent data in transit. It is thread-unsafe. +// +// |MessageInTransit| buffers: +// +// A |MessageInTransit| can be serialized by writing the main buffer and then, +// if it has one, the transport data buffer. Both buffers are +// |kMessageAlignment|-byte aligned and a multiple of |kMessageAlignment| bytes +// in size. +// +// The main buffer consists of the header (of type |Header|, which is an +// internal detail of this class) followed immediately by the message data +// (accessed by |bytes()| and of size |num_bytes()|, and also +// |kMessageAlignment|-byte aligned), and then any padding needed to make the +// main buffer a multiple of |kMessageAlignment| bytes in size. +// +// See |TransportData| for a description of the (serialized) transport data +// buffer. +class MOJO_SYSTEM_IMPL_EXPORT MessageInTransit { + public: + typedef uint16_t Type; + // Messages that are forwarded to |MessagePipeEndpoint|s. + static const Type kTypeMessagePipeEndpoint = 0; + // Messages that are forwarded to |MessagePipe|s. + static const Type kTypeMessagePipe = 1; + // Messages that are consumed by the |Channel|. + static const Type kTypeChannel = 2; + // Messages that are consumed by the |RawChannel| (implementation). + static const Type kTypeRawChannel = 3; + + typedef uint16_t Subtype; + // Subtypes for type |kTypeMessagePipeEndpoint|: + static const Subtype kSubtypeMessagePipeEndpointData = 0; + // Subtypes for type |kTypeMessagePipe|: + // Nothing currently. + // Subtypes for type |kTypeChannel|: + static const Subtype kSubtypeChannelRunMessagePipeEndpoint = 0; + static const Subtype kSubtypeChannelRemoveMessagePipeEndpoint = 1; + static const Subtype kSubtypeChannelRemoveMessagePipeEndpointAck = 2; + // Subtypes for type |kTypeRawChannel|: + static const Subtype kSubtypeRawChannelPosixExtraPlatformHandles = 0; + + typedef uint32_t EndpointId; + // Never a valid endpoint ID. + static const EndpointId kInvalidEndpointId = 0; + + // Messages (the header and data) must always be aligned to a multiple of this + // quantity (which must be a power of 2). + static const size_t kMessageAlignment = 8; + + // Forward-declare |Header| so that |View| can use it: + private: + struct Header; + public: + // This represents a view of serialized message data in a raw buffer. + class MOJO_SYSTEM_IMPL_EXPORT View { + public: + // Constructs a view from the given buffer of the given size. (The size must + // be as provided by |MessageInTransit::GetNextMessageSize()|.) The buffer + // must remain alive/unmodified through the lifetime of this object. + // |buffer| should be |kMessageAlignment|-byte aligned. + View(size_t message_size, const void* buffer); + + // Checks that the given |View| appears to be for a valid message, within + // predetermined limits (e.g., |num_bytes()| and |main_buffer_size()|, that + // |transport_data_buffer()|/|transport_data_buffer_size()| is for valid + // transport data -- see |TransportData::ValidateBuffer()|). + // + // It returns true (and leaves |error_message| alone) if this object appears + // to be a valid message (according to the above) and false, pointing + // |*error_message| to a suitable error message, if not. + bool IsValid(size_t serialized_platform_handle_size, + const char** error_message) const; + + // API parallel to that for |MessageInTransit| itself (mostly getters for + // header data). + const void* main_buffer() const { return buffer_; } + size_t main_buffer_size() const { + return RoundUpMessageAlignment(sizeof(Header) + header()->num_bytes); + } + const void* transport_data_buffer() const { + return (total_size() > main_buffer_size()) ? + static_cast<const char*>(buffer_) + main_buffer_size() : NULL; + } + size_t transport_data_buffer_size() const { + return total_size() - main_buffer_size(); + } + size_t total_size() const { return header()->total_size; } + uint32_t num_bytes() const { return header()->num_bytes; } + const void* bytes() const { + return static_cast<const char*>(buffer_) + sizeof(Header); + } + Type type() const { return header()->type; } + Subtype subtype() const { return header()->subtype; } + EndpointId source_id() const { return header()->source_id; } + EndpointId destination_id() const { return header()->destination_id; } + + private: + const Header* header() const { return static_cast<const Header*>(buffer_); } + + const void* const buffer_; + + // Though this struct is trivial, disallow copy and assign, since it doesn't + // own its data. (If you're copying/assigning this, you're probably doing + // something wrong.) + DISALLOW_COPY_AND_ASSIGN(View); + }; + + // |bytes| is optional; if null, the message data will be zero-initialized. + MessageInTransit(Type type, + Subtype subtype, + uint32_t num_bytes, + const void* bytes); + // Constructs a |MessageInTransit| from a |View|. + explicit MessageInTransit(const View& message_view); + + ~MessageInTransit(); + + // Gets the size of the next message from |buffer|, which has |buffer_size| + // bytes currently available, returning true and setting |*next_message_size| + // on success. |buffer| should be aligned on a |kMessageAlignment| boundary + // (and on success, |*next_message_size| will be a multiple of + // |kMessageAlignment|). + // TODO(vtl): In |RawChannelPosix|, the alignment requirements are currently + // satisified on a faith-based basis. + static bool GetNextMessageSize(const void* buffer, + size_t buffer_size, + size_t* next_message_size); + + // Makes this message "own" the given set of dispatchers. The dispatchers must + // not be referenced from anywhere else (in particular, not from the handle + // table), i.e., each dispatcher must have a reference count of 1. This + // message must not already have dispatchers. + void SetDispatchers(scoped_ptr<DispatcherVector> dispatchers); + + // Sets the |TransportData| for this message. This should only be done when + // there are no dispatchers and no existing |TransportData|. + void SetTransportData(scoped_ptr<TransportData> transport_data); + + // Serializes any dispatchers to the secondary buffer. This message must not + // already have a secondary buffer (so this must only be called once). The + // caller must ensure (e.g., by holding on to a reference) that |channel| + // stays alive through the call. + void SerializeAndCloseDispatchers(Channel* channel); + + // Gets the main buffer and its size (in number of bytes), respectively. + const void* main_buffer() const { return main_buffer_.get(); } + size_t main_buffer_size() const { return main_buffer_size_; } + + // Gets the transport data buffer (if any). + const TransportData* transport_data() const { return transport_data_.get(); } + TransportData* transport_data() { return transport_data_.get(); } + + // Gets the total size of the message (see comment in |Header|, below). + size_t total_size() const { return header()->total_size; } + + // Gets the size of the message data. + uint32_t num_bytes() const { return header()->num_bytes; } + + // Gets the message data (of size |num_bytes()| bytes). + const void* bytes() const { return main_buffer_.get() + sizeof(Header); } + void* bytes() { return main_buffer_.get() + sizeof(Header); } + + Type type() const { return header()->type; } + Subtype subtype() const { return header()->subtype; } + EndpointId source_id() const { return header()->source_id; } + EndpointId destination_id() const { return header()->destination_id; } + + void set_source_id(EndpointId source_id) { header()->source_id = source_id; } + void set_destination_id(EndpointId destination_id) { + header()->destination_id = destination_id; + } + + // Gets the dispatchers attached to this message; this may return null if + // there are none. Note that the caller may mutate the set of dispatchers + // (e.g., take ownership of all the dispatchers, leaving the vector empty). + DispatcherVector* dispatchers() { return dispatchers_.get(); } + + // Returns true if this message has dispatchers attached. + bool has_dispatchers() const { + return dispatchers_ && !dispatchers_->empty(); + } + + // Rounds |n| up to a multiple of |kMessageAlignment|. + static inline size_t RoundUpMessageAlignment(size_t n) { + return (n + kMessageAlignment - 1) & ~(kMessageAlignment - 1); + } + + private: + // To allow us to make compile-assertions about |Header| in the .cc file. + struct PrivateStructForCompileAsserts; + + // Header for the data (main buffer). Must be a multiple of + // |kMessageAlignment| bytes in size. Must be POD. + struct Header { + // Total size of the message, including the header, the message data + // ("bytes") including padding (to make it a multiple of |kMessageAlignment| + // bytes), and serialized handle information. Note that this may not be the + // correct value if dispatchers are attached but + // |SerializeAndCloseDispatchers()| has not been called. + uint32_t total_size; + Type type; // 2 bytes. + Subtype subtype; // 2 bytes. + EndpointId source_id; // 4 bytes. + EndpointId destination_id; // 4 bytes. + // Size of actual message data. + uint32_t num_bytes; + uint32_t unused; + }; + + const Header* header() const { + return reinterpret_cast<const Header*>(main_buffer_.get()); + } + Header* header() { return reinterpret_cast<Header*>(main_buffer_.get()); } + + void UpdateTotalSize(); + + const size_t main_buffer_size_; + const scoped_ptr<char, base::AlignedFreeDeleter> main_buffer_; // Never null. + + scoped_ptr<TransportData> transport_data_; // May be null. + + // Any dispatchers that may be attached to this message. These dispatchers + // should be "owned" by this message, i.e., have a ref count of exactly 1. (We + // allow a dispatcher entry to be null, in case it couldn't be duplicated for + // some reason.) + scoped_ptr<DispatcherVector> dispatchers_; + + DISALLOW_COPY_AND_ASSIGN(MessageInTransit); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MESSAGE_IN_TRANSIT_H_ diff --git a/chromium/mojo/system/message_in_transit_queue.cc b/chromium/mojo/system/message_in_transit_queue.cc new file mode 100644 index 00000000000..48f77799e07 --- /dev/null +++ b/chromium/mojo/system/message_in_transit_queue.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/message_in_transit_queue.h" + +#include "base/logging.h" +#include "base/stl_util.h" + +namespace mojo { +namespace system { + +MessageInTransitQueue::MessageInTransitQueue() { +} + +MessageInTransitQueue::MessageInTransitQueue(PassContents, + MessageInTransitQueue* other) { + queue_.swap(other->queue_); +} + +MessageInTransitQueue::~MessageInTransitQueue() { + if (!IsEmpty()) { + LOG(WARNING) << "Destroying nonempty message queue"; + Clear(); + } +} + +void MessageInTransitQueue::Clear() { + STLDeleteElements(&queue_); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_in_transit_queue.h b/chromium/mojo/system/message_in_transit_queue.h new file mode 100644 index 00000000000..a70a7ef742b --- /dev/null +++ b/chromium/mojo/system/message_in_transit_queue.h @@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SYSTEM_MESSAGE_QUEUE_H_ +#define MOJO_SYSTEM_MESSAGE_QUEUE_H_ + +#include <deque> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// A simple queue for |MessageInTransit|s (that owns its messages). +// This class is not thread-safe. +// TODO(vtl): Write tests. +class MOJO_SYSTEM_IMPL_EXPORT MessageInTransitQueue { + public: + MessageInTransitQueue(); + + struct PassContents {}; + // Constructor that takes over the contents of another + // |MessageInTransitQueue|, leaving it empty. + MessageInTransitQueue(PassContents, MessageInTransitQueue* other); + + ~MessageInTransitQueue(); + + bool IsEmpty() const { + return queue_.empty(); + } + + void AddMessage(scoped_ptr<MessageInTransit> message) { + queue_.push_back(message.release()); + } + + scoped_ptr<MessageInTransit> GetMessage() { + MessageInTransit* rv = queue_.front(); + queue_.pop_front(); + return make_scoped_ptr(rv); + } + + MessageInTransit* PeekMessage() { + return queue_.front(); + } + + void DiscardMessage() { + delete queue_.front(); + queue_.pop_front(); + } + + void Clear(); + + private: + // TODO(vtl): When C++11 is available, switch this to a deque of + // |scoped_ptr|/|unique_ptr|s. + std::deque<MessageInTransit*> queue_; + + DISALLOW_COPY_AND_ASSIGN(MessageInTransitQueue); +}; + + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MESSAGE_QUEUE_H_ diff --git a/chromium/mojo/system/message_pipe.cc b/chromium/mojo/system/message_pipe.cc new file mode 100644 index 00000000000..c297f385f6f --- /dev/null +++ b/chromium/mojo/system/message_pipe.cc @@ -0,0 +1,285 @@ +// 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 "mojo/system/message_pipe.h" + +#include "base/logging.h" +#include "mojo/system/channel.h" +#include "mojo/system/local_message_pipe_endpoint.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_pipe_dispatcher.h" +#include "mojo/system/message_pipe_endpoint.h" +#include "mojo/system/proxy_message_pipe_endpoint.h" + +namespace mojo { +namespace system { + +MessagePipe::MessagePipe(scoped_ptr<MessagePipeEndpoint> endpoint0, + scoped_ptr<MessagePipeEndpoint> endpoint1) { + endpoints_[0].reset(endpoint0.release()); + endpoints_[1].reset(endpoint1.release()); +} + +MessagePipe::MessagePipe() { + endpoints_[0].reset(new LocalMessagePipeEndpoint()); + endpoints_[1].reset(new LocalMessagePipeEndpoint()); +} + +// static +unsigned MessagePipe::GetPeerPort(unsigned port) { + DCHECK(port == 0 || port == 1); + return port ^ 1; +} + +MessagePipeEndpoint::Type MessagePipe::GetType(unsigned port) { + DCHECK(port == 0 || port == 1); + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + + return endpoints_[port]->GetType(); +} + +void MessagePipe::CancelAllWaiters(unsigned port) { + DCHECK(port == 0 || port == 1); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + endpoints_[port]->CancelAllWaiters(); +} + +void MessagePipe::Close(unsigned port) { + DCHECK(port == 0 || port == 1); + + unsigned destination_port = GetPeerPort(port); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + + endpoints_[port]->Close(); + if (endpoints_[destination_port]) { + if (!endpoints_[destination_port]->OnPeerClose()) + endpoints_[destination_port].reset(); + } + endpoints_[port].reset(); +} + +// TODO(vtl): Handle flags. +MojoResult MessagePipe::WriteMessage( + unsigned port, + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags) { + DCHECK(port == 0 || port == 1); + return EnqueueMessageInternal( + GetPeerPort(port), + make_scoped_ptr(new MessageInTransit( + MessageInTransit::kTypeMessagePipeEndpoint, + MessageInTransit::kSubtypeMessagePipeEndpointData, + num_bytes, + bytes)), + transports); +} + +MojoResult MessagePipe::ReadMessage(unsigned port, + void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags) { + DCHECK(port == 0 || port == 1); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + + return endpoints_[port]->ReadMessage(bytes, num_bytes, dispatchers, + num_dispatchers, flags); +} + +MojoResult MessagePipe::AddWaiter(unsigned port, + Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + DCHECK(port == 0 || port == 1); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + + return endpoints_[port]->AddWaiter(waiter, signals, context); +} + +void MessagePipe::RemoveWaiter(unsigned port, Waiter* waiter) { + DCHECK(port == 0 || port == 1); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + + endpoints_[port]->RemoveWaiter(waiter); +} + +void MessagePipe::ConvertLocalToProxy(unsigned port) { + DCHECK(port == 0 || port == 1); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + DCHECK_EQ(endpoints_[port]->GetType(), MessagePipeEndpoint::kTypeLocal); + + bool is_peer_open = !!endpoints_[GetPeerPort(port)]; + + // TODO(vtl): Hopefully this will work if the peer has been closed and when + // the peer is local. If the peer is remote, we should do something more + // sophisticated. + DCHECK(!is_peer_open || + endpoints_[GetPeerPort(port)]->GetType() == + MessagePipeEndpoint::kTypeLocal); + + scoped_ptr<MessagePipeEndpoint> replacement_endpoint( + new ProxyMessagePipeEndpoint( + static_cast<LocalMessagePipeEndpoint*>(endpoints_[port].get()), + is_peer_open)); + endpoints_[port].swap(replacement_endpoint); +} + +MojoResult MessagePipe::EnqueueMessage( + unsigned port, + scoped_ptr<MessageInTransit> message) { + return EnqueueMessageInternal(port, message.Pass(), NULL); +} + +bool MessagePipe::Attach(unsigned port, + scoped_refptr<Channel> channel, + MessageInTransit::EndpointId local_id) { + DCHECK(port == 0 || port == 1); + DCHECK(channel); + DCHECK_NE(local_id, MessageInTransit::kInvalidEndpointId); + + base::AutoLock locker(lock_); + if (!endpoints_[port]) + return false; + + DCHECK_EQ(endpoints_[port]->GetType(), MessagePipeEndpoint::kTypeProxy); + endpoints_[port]->Attach(channel, local_id); + return true; +} + +void MessagePipe::Run(unsigned port, MessageInTransit::EndpointId remote_id) { + DCHECK(port == 0 || port == 1); + DCHECK_NE(remote_id, MessageInTransit::kInvalidEndpointId); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[port]); + if (!endpoints_[port]->Run(remote_id)) + endpoints_[port].reset(); +} + +void MessagePipe::OnRemove(unsigned port) { + unsigned destination_port = GetPeerPort(port); + + base::AutoLock locker(lock_); + // A |OnPeerClose()| can come in first, before |OnRemove()| gets called. + if (!endpoints_[port]) + return; + + endpoints_[port]->OnRemove(); + if (endpoints_[destination_port]) { + if (!endpoints_[destination_port]->OnPeerClose()) + endpoints_[destination_port].reset(); + } + endpoints_[port].reset(); +} + +MessagePipe::~MessagePipe() { + // Owned by the dispatchers. The owning dispatchers should only release us via + // their |Close()| method, which should inform us of being closed via our + // |Close()|. Thus these should already be null. + DCHECK(!endpoints_[0]); + DCHECK(!endpoints_[1]); +} + +MojoResult MessagePipe::EnqueueMessageInternal( + unsigned port, + scoped_ptr<MessageInTransit> message, + std::vector<DispatcherTransport>* transports) { + DCHECK(port == 0 || port == 1); + DCHECK(message); + + if (message->type() == MessageInTransit::kTypeMessagePipe) { + DCHECK(!transports); + return HandleControlMessage(port, message.Pass()); + } + + DCHECK_EQ(message->type(), MessageInTransit::kTypeMessagePipeEndpoint); + + base::AutoLock locker(lock_); + DCHECK(endpoints_[GetPeerPort(port)]); + + // The destination port need not be open, unlike the source port. + if (!endpoints_[port]) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (transports) { + MojoResult result = AttachTransportsNoLock(port, message.get(), transports); + if (result != MOJO_RESULT_OK) + return result; + } + + // The endpoint's |EnqueueMessage()| may not report failure. + endpoints_[port]->EnqueueMessage(message.Pass()); + return MOJO_RESULT_OK; +} + +MojoResult MessagePipe::AttachTransportsNoLock( + unsigned port, + MessageInTransit* message, + std::vector<DispatcherTransport>* transports) { + DCHECK(!message->has_dispatchers()); + + // You're not allowed to send either handle to a message pipe over the message + // pipe, so check for this. (The case of trying to write a handle to itself is + // taken care of by |Core|. That case kind of makes sense, but leads to + // complications if, e.g., both sides try to do the same thing with their + // respective handles simultaneously. The other case, of trying to write the + // peer handle to a handle, doesn't make sense -- since no handle will be + // available to read the message from.) + for (size_t i = 0; i < transports->size(); i++) { + if (!(*transports)[i].is_valid()) + continue; + if ((*transports)[i].GetType() == Dispatcher::kTypeMessagePipe) { + MessagePipeDispatcherTransport mp_transport((*transports)[i]); + if (mp_transport.GetMessagePipe() == this) { + // The other case should have been disallowed by |Core|. (Note: |port| + // is the peer port of the handle given to |WriteMessage()|.) + DCHECK_EQ(mp_transport.GetPort(), port); + return MOJO_RESULT_INVALID_ARGUMENT; + } + } + } + + // Clone the dispatchers and attach them to the message. (This must be done as + // a separate loop, since we want to leave the dispatchers alone on failure.) + scoped_ptr<DispatcherVector> dispatchers(new DispatcherVector()); + dispatchers->reserve(transports->size()); + for (size_t i = 0; i < transports->size(); i++) { + if ((*transports)[i].is_valid()) { + dispatchers->push_back( + (*transports)[i].CreateEquivalentDispatcherAndClose()); + } else { + LOG(WARNING) << "Enqueueing null dispatcher"; + dispatchers->push_back(scoped_refptr<Dispatcher>()); + } + } + message->SetDispatchers(dispatchers.Pass()); + return MOJO_RESULT_OK; +} + +MojoResult MessagePipe::HandleControlMessage( + unsigned /*port*/, + scoped_ptr<MessageInTransit> message) { + LOG(WARNING) << "Unrecognized MessagePipe control message subtype " + << message->subtype(); + return MOJO_RESULT_UNKNOWN; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_pipe.h b/chromium/mojo/system/message_pipe.h new file mode 100644 index 00000000000..e43dcb667a1 --- /dev/null +++ b/chromium/mojo/system/message_pipe.h @@ -0,0 +1,122 @@ +// 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 MOJO_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_SYSTEM_MESSAGE_PIPE_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_pipe_endpoint.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Channel; +class Waiter; + +// |MessagePipe| is the secondary object implementing a message pipe (see the +// explanatory comment in core.cc). It is typically owned by the dispatcher(s) +// corresponding to the local endpoints. This class is thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT MessagePipe : + public base::RefCountedThreadSafe<MessagePipe> { + public: + MessagePipe(scoped_ptr<MessagePipeEndpoint> endpoint0, + scoped_ptr<MessagePipeEndpoint> endpoint1); + + // Convenience constructor that constructs a |MessagePipe| with two new + // |LocalMessagePipeEndpoint|s. + MessagePipe(); + + // Gets the other port number (i.e., 0 -> 1, 1 -> 0). + static unsigned GetPeerPort(unsigned port); + + // Gets the type of the endpoint (used for assertions, etc.). + MessagePipeEndpoint::Type GetType(unsigned port); + + // These are called by the dispatcher to implement its methods of + // corresponding names. In all cases, the port |port| must be open. + void CancelAllWaiters(unsigned port); + void Close(unsigned port); + // Unlike |MessagePipeDispatcher::WriteMessage()|, this does not validate its + // arguments. + MojoResult WriteMessage(unsigned port, + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags); + // Unlike |MessagePipeDispatcher::ReadMessage()|, this does not validate its + // arguments. + MojoResult ReadMessage(unsigned port, + void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags); + MojoResult AddWaiter(unsigned port, + Waiter* waiter, + MojoHandleSignals signals, + uint32_t context); + void RemoveWaiter(unsigned port, Waiter* waiter); + + // This is called by the dispatcher to convert a local endpoint to a proxy + // endpoint. + void ConvertLocalToProxy(unsigned port); + + // This is used by |Channel| to enqueue messages (typically to a + // |LocalMessagePipeEndpoint|). Unlike |WriteMessage()|, |port| is the + // *destination* port. + MojoResult EnqueueMessage(unsigned port, + scoped_ptr<MessageInTransit> message); + + // These are used by |Channel|. + bool Attach(unsigned port, + scoped_refptr<Channel> channel, + MessageInTransit::EndpointId local_id); + void Run(unsigned port, MessageInTransit::EndpointId remote_id); + void OnRemove(unsigned port); + + private: + friend class base::RefCountedThreadSafe<MessagePipe>; + virtual ~MessagePipe(); + + // This is used internally by |WriteMessage()| and by |EnqueueMessage()|. + // |transports| may be non-null only if it's nonempty and |message| has no + // dispatchers attached. + MojoResult EnqueueMessageInternal( + unsigned port, + scoped_ptr<MessageInTransit> message, + std::vector<DispatcherTransport>* transports); + + // Helper for |EnqueueMessageInternal()|. Must be called with |lock_| held. + MojoResult AttachTransportsNoLock( + unsigned port, + MessageInTransit* message, + std::vector<DispatcherTransport>* transports); + + // Used by |EnqueueMessageInternal()| to handle control messages that are + // actually meant for us. + MojoResult HandleControlMessage(unsigned port, + scoped_ptr<MessageInTransit> message); + + base::Lock lock_; // Protects the following members. + scoped_ptr<MessagePipeEndpoint> endpoints_[2]; + + DISALLOW_COPY_AND_ASSIGN(MessagePipe); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MESSAGE_PIPE_H_ diff --git a/chromium/mojo/system/message_pipe_dispatcher.cc b/chromium/mojo/system/message_pipe_dispatcher.cc new file mode 100644 index 00000000000..484613ca5d1 --- /dev/null +++ b/chromium/mojo/system/message_pipe_dispatcher.cc @@ -0,0 +1,281 @@ +// 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 "mojo/system/message_pipe_dispatcher.h" + +#include "base/logging.h" +#include "mojo/system/channel.h" +#include "mojo/system/constants.h" +#include "mojo/system/local_message_pipe_endpoint.h" +#include "mojo/system/memory.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/options_validation.h" +#include "mojo/system/proxy_message_pipe_endpoint.h" + +namespace mojo { +namespace system { + +namespace { + +const unsigned kInvalidPort = static_cast<unsigned>(-1); + +struct SerializedMessagePipeDispatcher { + MessageInTransit::EndpointId endpoint_id; +}; + +} // namespace + +// MessagePipeDispatcher ------------------------------------------------------- + +// static +const MojoCreateMessagePipeOptions + MessagePipeDispatcher::kDefaultCreateOptions = { + static_cast<uint32_t>(sizeof(MojoCreateMessagePipeOptions)), + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE +}; + +MessagePipeDispatcher::MessagePipeDispatcher( + const MojoCreateMessagePipeOptions& /*validated_options*/) + : port_(kInvalidPort) { +} + +// static +MojoResult MessagePipeDispatcher::ValidateCreateOptions( + const MojoCreateMessagePipeOptions* in_options, + MojoCreateMessagePipeOptions* out_options) { + const MojoCreateMessagePipeOptionsFlags kKnownFlags = + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE; + + *out_options = kDefaultCreateOptions; + if (!in_options) + return MOJO_RESULT_OK; + + MojoResult result = + ValidateOptionsStructPointerSizeAndFlags<MojoCreateMessagePipeOptions>( + in_options, kKnownFlags, out_options); + if (result != MOJO_RESULT_OK) + return result; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +void MessagePipeDispatcher::Init(scoped_refptr<MessagePipe> message_pipe, + unsigned port) { + DCHECK(message_pipe); + DCHECK(port == 0 || port == 1); + + message_pipe_ = message_pipe; + port_ = port; +} + +Dispatcher::Type MessagePipeDispatcher::GetType() const { + return kTypeMessagePipe; +} + +// static +std::pair<scoped_refptr<MessagePipeDispatcher>, scoped_refptr<MessagePipe> > +MessagePipeDispatcher::CreateRemoteMessagePipe() { + scoped_refptr<MessagePipe> message_pipe( + new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipeDispatcher> dispatcher(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + dispatcher->Init(message_pipe, 0); + + return std::make_pair(dispatcher, message_pipe); +} + +// static +scoped_refptr<MessagePipeDispatcher> MessagePipeDispatcher::Deserialize( + Channel* channel, + const void* source, + size_t size) { + if (size != sizeof(SerializedMessagePipeDispatcher)) { + LOG(ERROR) << "Invalid serialized message pipe dispatcher"; + return scoped_refptr<MessagePipeDispatcher>(); + } + + std::pair<scoped_refptr<MessagePipeDispatcher>, scoped_refptr<MessagePipe> > + remote_message_pipe = CreateRemoteMessagePipe(); + + MessageInTransit::EndpointId remote_id = + static_cast<const SerializedMessagePipeDispatcher*>(source)->endpoint_id; + if (remote_id == MessageInTransit::kInvalidEndpointId) { + // This means that the other end was closed, and there were no messages + // enqueued for us. + // TODO(vtl): This is wrong. We should produce a "dead" message pipe + // dispatcher. + NOTIMPLEMENTED(); + return scoped_refptr<MessagePipeDispatcher>(); + } + MessageInTransit::EndpointId local_id = + channel->AttachMessagePipeEndpoint(remote_message_pipe.second, 1); + if (local_id == MessageInTransit::kInvalidEndpointId) { + LOG(ERROR) << "Failed to deserialize message pipe dispatcher (failed to " + "attach; remote ID = " << remote_id << ")"; + return scoped_refptr<MessagePipeDispatcher>(); + } + DVLOG(2) << "Deserializing message pipe dispatcher (remote ID = " + << remote_id << ", new local ID = " << local_id << ")"; + + if (!channel->RunMessagePipeEndpoint(local_id, remote_id)) { + // In general, this shouldn't fail, since we generated |local_id| locally. + NOTREACHED(); + return scoped_refptr<MessagePipeDispatcher>(); + } + + // TODO(vtl): FIXME -- Need some error handling here. + channel->RunRemoteMessagePipeEndpoint(local_id, remote_id); + return remote_message_pipe.first; +} + +MessagePipeDispatcher::~MessagePipeDispatcher() { + // |Close()|/|CloseImplNoLock()| should have taken care of the pipe. + DCHECK(!message_pipe_); +} + +MessagePipe* MessagePipeDispatcher::GetMessagePipeNoLock() const { + lock().AssertAcquired(); + return message_pipe_.get(); +} + +unsigned MessagePipeDispatcher::GetPortNoLock() const { + lock().AssertAcquired(); + return port_; +} + +void MessagePipeDispatcher::CancelAllWaitersNoLock() { + lock().AssertAcquired(); + message_pipe_->CancelAllWaiters(port_); +} + +void MessagePipeDispatcher::CloseImplNoLock() { + lock().AssertAcquired(); + message_pipe_->Close(port_); + message_pipe_ = NULL; + port_ = kInvalidPort; +} + +scoped_refptr<Dispatcher> +MessagePipeDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() { + lock().AssertAcquired(); + + // TODO(vtl): Currently, there are no options, so we just use + // |kDefaultCreateOptions|. Eventually, we'll have to duplicate the options + // too. + scoped_refptr<MessagePipeDispatcher> rv = + new MessagePipeDispatcher(kDefaultCreateOptions); + rv->Init(message_pipe_, port_); + message_pipe_ = NULL; + port_ = kInvalidPort; + return scoped_refptr<Dispatcher>(rv.get()); +} + +MojoResult MessagePipeDispatcher::WriteMessageImplNoLock( + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags) { + DCHECK(!transports || (transports->size() > 0 && + transports->size() <= kMaxMessageNumHandles)); + + lock().AssertAcquired(); + + if (!VerifyUserPointerWithSize<1>(bytes, num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > kMaxMessageNumBytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + return message_pipe_->WriteMessage(port_, bytes, num_bytes, transports, + flags); +} + +MojoResult MessagePipeDispatcher::ReadMessageImplNoLock( + void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags) { + lock().AssertAcquired(); + + if (num_bytes) { + if (!VerifyUserPointer<uint32_t>(num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + if (!VerifyUserPointerWithSize<1>(bytes, *num_bytes)) + return MOJO_RESULT_INVALID_ARGUMENT; + } + + return message_pipe_->ReadMessage(port_, bytes, num_bytes, dispatchers, + num_dispatchers, flags); +} + +MojoResult MessagePipeDispatcher::AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + lock().AssertAcquired(); + return message_pipe_->AddWaiter(port_, waiter, signals, context); +} + +void MessagePipeDispatcher::RemoveWaiterImplNoLock(Waiter* waiter) { + lock().AssertAcquired(); + message_pipe_->RemoveWaiter(port_, waiter); +} + +void MessagePipeDispatcher::StartSerializeImplNoLock( + Channel* /*channel*/, + size_t* max_size, + size_t* max_platform_handles) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + *max_size = sizeof(SerializedMessagePipeDispatcher); + *max_platform_handles = 0; +} + +bool MessagePipeDispatcher::EndSerializeAndCloseImplNoLock( + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* /*platform_handles*/) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + + // Convert the local endpoint to a proxy endpoint (moving the message queue). + message_pipe_->ConvertLocalToProxy(port_); + + // Attach the new proxy endpoint to the channel. + MessageInTransit::EndpointId endpoint_id = + channel->AttachMessagePipeEndpoint(message_pipe_, port_); + // Note: It's okay to get an endpoint ID of |kInvalidEndpointId|. (It's + // possible that the other endpoint -- the one that we're not sending -- was + // closed in the intervening time.) In that case, we need to deserialize a + // "dead" message pipe dispatcher on the other end. (Note that this is + // different from just producing |MOJO_HANDLE_INVALID|.) + DVLOG(2) << "Serializing message pipe dispatcher (local ID = " << endpoint_id + << ")"; + + // We now have a local ID. Before we can run the proxy endpoint, we need to + // get an ack back from the other side with the remote ID. + static_cast<SerializedMessagePipeDispatcher*>(destination)->endpoint_id = + endpoint_id; + + message_pipe_ = NULL; + port_ = kInvalidPort; + + *actual_size = sizeof(SerializedMessagePipeDispatcher); + return true; +} + +// MessagePipeDispatcherTransport ---------------------------------------------- + +MessagePipeDispatcherTransport::MessagePipeDispatcherTransport( + DispatcherTransport transport) : DispatcherTransport(transport) { + DCHECK_EQ(message_pipe_dispatcher()->GetType(), Dispatcher::kTypeMessagePipe); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_pipe_dispatcher.h b/chromium/mojo/system/message_pipe_dispatcher.h new file mode 100644 index 00000000000..d3a3946edfa --- /dev/null +++ b/chromium/mojo/system/message_pipe_dispatcher.h @@ -0,0 +1,131 @@ +// 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 MOJO_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ +#define MOJO_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ + +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class MessagePipe; +class MessagePipeDispatcherTransport; + +// This is the |Dispatcher| implementation for message pipes (created by the +// Mojo primitive |MojoCreateMessagePipe()|). This class is thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT MessagePipeDispatcher : public Dispatcher { + public: + // The default options to use for |MojoCreateMessagePipe()|. (Real uses + // should obtain this via |ValidateCreateOptions()| with a null |in_options|; + // this is exposed directly for testing convenience.) + static const MojoCreateMessagePipeOptions kDefaultCreateOptions; + + MessagePipeDispatcher( + const MojoCreateMessagePipeOptions& /*validated_options*/); + + // Validates and/or sets default options for |MojoCreateMessagePipeOptions|. + // If non-null, |in_options| must point to a struct of at least + // |in_options->struct_size| bytes. |out_options| must point to a (current) + // |MojoCreateMessagePipeOptions| and will be entirely overwritten on success + // (it may be partly overwritten on failure). + static MojoResult ValidateCreateOptions( + const MojoCreateMessagePipeOptions* in_options, + MojoCreateMessagePipeOptions* out_options); + + // Must be called before any other methods. (This method is not thread-safe.) + void Init(scoped_refptr<MessagePipe> message_pipe, unsigned port); + + // |Dispatcher| public methods: + virtual Type GetType() const OVERRIDE; + + // Creates a |MessagePipe| with a local endpoint (at port 0) and a proxy + // endpoint, and creates/initializes a |MessagePipeDispatcher| (attached to + // the message pipe, port 0). + // TODO(vtl): This currently uses |kDefaultCreateOptions|, which is okay since + // there aren't any options, but eventually options should be plumbed through. + static std::pair<scoped_refptr<MessagePipeDispatcher>, + scoped_refptr<MessagePipe> > CreateRemoteMessagePipe(); + + // The "opposite" of |SerializeAndClose()|. (Typically this is called by + // |Dispatcher::Deserialize()|.) + static scoped_refptr<MessagePipeDispatcher> Deserialize(Channel* channel, + const void* source, + size_t size); + + private: + friend class MessagePipeDispatcherTransport; + + virtual ~MessagePipeDispatcher(); + + // Gets a dumb pointer to |message_pipe_|. This must be called under the + // |Dispatcher| lock (that it's a dumb pointer is okay since it's under lock). + // This is needed when sending handles across processes, where nontrivial, + // invasive work needs to be done. + MessagePipe* GetMessagePipeNoLock() const; + // Similarly for the port. + unsigned GetPortNoLock() const; + + // |Dispatcher| protected methods: + virtual void CancelAllWaitersNoLock() OVERRIDE; + virtual void CloseImplNoLock() OVERRIDE; + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE; + virtual MojoResult WriteMessageImplNoLock( + const void* bytes, + uint32_t num_bytes, + std::vector<DispatcherTransport>* transports, + MojoWriteMessageFlags flags) OVERRIDE; + virtual MojoResult ReadMessageImplNoLock(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags) OVERRIDE; + virtual MojoResult AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) OVERRIDE; + virtual void RemoveWaiterImplNoLock(Waiter* waiter) OVERRIDE; + virtual void StartSerializeImplNoLock(Channel* channel, + size_t* max_size, + size_t* max_platform_handles) OVERRIDE; + virtual bool EndSerializeAndCloseImplNoLock( + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) OVERRIDE; + + // Protected by |lock()|: + scoped_refptr<MessagePipe> message_pipe_; // This will be null if closed. + unsigned port_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher); +}; + +class MessagePipeDispatcherTransport : public DispatcherTransport { + public: + explicit MessagePipeDispatcherTransport(DispatcherTransport transport); + + MessagePipe* GetMessagePipe() { + return message_pipe_dispatcher()->GetMessagePipeNoLock(); + } + unsigned GetPort() { return message_pipe_dispatcher()->GetPortNoLock(); } + + private: + MessagePipeDispatcher* message_pipe_dispatcher() { + return static_cast<MessagePipeDispatcher*>(dispatcher()); + } + + // Copy and assign allowed. +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ diff --git a/chromium/mojo/system/message_pipe_dispatcher_unittest.cc b/chromium/mojo/system/message_pipe_dispatcher_unittest.cc new file mode 100644 index 00000000000..291a2c152f5 --- /dev/null +++ b/chromium/mojo/system/message_pipe_dispatcher_unittest.cc @@ -0,0 +1,598 @@ +// 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. + +// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a +// heavily-loaded system). Sorry. |test::EpsilonTimeout()| may be increased to +// increase tolerance and reduce observed flakiness (though doing so reduces the +// meaningfulness of the test). + +#include "mojo/system/message_pipe_dispatcher.h" + +#include <string.h> + +#include <limits> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_vector.h" +#include "base/rand_util.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/test_utils.h" +#include "mojo/system/waiter.h" +#include "mojo/system/waiter_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +TEST(MessagePipeDispatcherTest, Basic) { + test::Stopwatch stopwatch; + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + + // Run this test both with |d0| as port 0, |d1| as port 1 and vice versa. + for (unsigned i = 0; i < 2; i++) { + scoped_refptr<MessagePipeDispatcher> d0(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + EXPECT_EQ(Dispatcher::kTypeMessagePipe, d0->GetType()); + scoped_refptr<MessagePipeDispatcher> d1(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + d0->Init(mp, i); // 0, 1. + d1->Init(mp, i ^ 1); // 1, 0. + } + Waiter w; + uint32_t context = 0; + + // Try adding a writable waiter when already writable. + w.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + // Shouldn't need to remove the waiter (it was not added). + + // Add a readable waiter to |d0|, then make it readable (by writing to + // |d1|), then wait. + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 1)); + buffer[0] = 123456789; + EXPECT_EQ(MOJO_RESULT_OK, + d1->WriteMessage(buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(1u, context); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + d0->RemoveWaiter(&w); + + // Try adding a readable waiter when already readable (from above). + w.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 2)); + // Shouldn't need to remove the waiter (it was not added). + + // Make |d0| no longer readable (by reading from it). + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + d0->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kBufferSize, buffer_size); + EXPECT_EQ(123456789, buffer[0]); + + // Wait for zero time for readability on |d0| (will time out). + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 3)); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, NULL)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + d0->RemoveWaiter(&w); + + // Wait for non-zero, finite time for readability on |d0| (will time out). + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 3)); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), NULL)); + base::TimeDelta elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + d0->RemoveWaiter(&w); + + EXPECT_EQ(MOJO_RESULT_OK, d0->Close()); + EXPECT_EQ(MOJO_RESULT_OK, d1->Close()); + } +} + +TEST(MessagePipeDispatcherTest, InvalidParams) { + char buffer[1]; + + scoped_refptr<MessagePipeDispatcher> d0(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + scoped_refptr<MessagePipeDispatcher> d1(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + d0->Init(mp, 0); + d1->Init(mp, 1); + } + + // |WriteMessage|: + // Null buffer with nonzero buffer size. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d0->WriteMessage(NULL, 1, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // Huge buffer size. + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + d0->WriteMessage(buffer, std::numeric_limits<uint32_t>::max(), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // |ReadMessage|: + // Null buffer with nonzero buffer size. + uint32_t buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d0->ReadMessage(NULL, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, d0->Close()); + EXPECT_EQ(MOJO_RESULT_OK, d1->Close()); +} + +// Test what happens when one end is closed (single-threaded test). +TEST(MessagePipeDispatcherTest, BasicClosed) { + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + + // Run this test both with |d0| as port 0, |d1| as port 1 and vice versa. + for (unsigned i = 0; i < 2; i++) { + scoped_refptr<MessagePipeDispatcher> d0(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + scoped_refptr<MessagePipeDispatcher> d1(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + d0->Init(mp, i); // 0, 1. + d1->Init(mp, i ^ 1); // 1, 0. + } + Waiter w; + + // Write (twice) to |d1|. + buffer[0] = 123456789; + EXPECT_EQ(MOJO_RESULT_OK, + d1->WriteMessage(buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + buffer[0] = 234567890; + EXPECT_EQ(MOJO_RESULT_OK, + d1->WriteMessage(buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Try waiting for readable on |d0|; should fail (already satisfied). + w.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Try reading from |d1|; should fail (nothing to read). + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + d1->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Close |d1|. + EXPECT_EQ(MOJO_RESULT_OK, d1->Close()); + + // Try waiting for readable on |d0|; should fail (already satisfied). + w.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 1)); + + // Read from |d0|. + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + d0->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kBufferSize, buffer_size); + EXPECT_EQ(123456789, buffer[0]); + + // Try waiting for readable on |d0|; should fail (already satisfied). + w.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 2)); + + // Read again from |d0|. + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + d0->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kBufferSize, buffer_size); + EXPECT_EQ(234567890, buffer[0]); + + // Try waiting for readable on |d0|; should fail (unsatisfiable). + w.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 3)); + + // Try waiting for writable on |d0|; should fail (unsatisfiable). + w.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4)); + + // Try reading from |d0|; should fail (nothing to read and other end + // closed). + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + d0->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Try writing to |d0|; should fail (other end closed). + buffer[0] = 345678901; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + d0->WriteMessage(buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, d0->Close()); + } +} + +TEST(MessagePipeDispatcherTest, BasicThreaded) { + test::Stopwatch stopwatch; + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + base::TimeDelta elapsed; + bool did_wait; + MojoResult result; + uint32_t context; + + // Run this test both with |d0| as port 0, |d1| as port 1 and vice versa. + for (unsigned i = 0; i < 2; i++) { + scoped_refptr<MessagePipeDispatcher> d0(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + scoped_refptr<MessagePipeDispatcher> d1(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + d0->Init(mp, i); // 0, 1. + d1->Init(mp, i ^ 1); // 1, 0. + } + + // Wait for readable on |d1|, which will become readable after some time. + { + test::WaiterThread thread(d1, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 1, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + // Wake it up by writing to |d0|. + buffer[0] = 123456789; + EXPECT_EQ(MOJO_RESULT_OK, + d0->WriteMessage(buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + } // Joins the thread. + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(1u, context); + + // Now |d1| is already readable. Try waiting for it again. + { + test::WaiterThread thread(d1, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 2, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + } // Joins the thread. + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_FALSE(did_wait); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, result); + + // Consume what we wrote to |d0|. + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + d1->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kBufferSize, buffer_size); + EXPECT_EQ(123456789, buffer[0]); + + // Wait for readable on |d1| and close |d0| after some time, which should + // cancel that wait. + { + test::WaiterThread thread(d1, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 3, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + EXPECT_EQ(MOJO_RESULT_OK, d0->Close()); + } // Joins the thread. + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(3u, context); + + EXPECT_EQ(MOJO_RESULT_OK, d1->Close()); + } + + for (unsigned i = 0; i < 2; i++) { + scoped_refptr<MessagePipeDispatcher> d0(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + scoped_refptr<MessagePipeDispatcher> d1(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + d0->Init(mp, i); // 0, 1. + d1->Init(mp, i ^ 1); // 1, 0. + } + + // Wait for readable on |d1| and close |d1| after some time, which should + // cancel that wait. + { + test::WaiterThread thread(d1, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 4, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + EXPECT_EQ(MOJO_RESULT_OK, d1->Close()); + } // Joins the thread. + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(4u, context); + + EXPECT_EQ(MOJO_RESULT_OK, d0->Close()); + } +} + +// Stress test ----------------------------------------------------------------- + +const size_t kMaxMessageSize = 2000; + +class WriterThread : public base::SimpleThread { + public: + // |*messages_written| and |*bytes_written| belong to the thread while it's + // alive. + WriterThread(scoped_refptr<Dispatcher> write_dispatcher, + size_t* messages_written, size_t* bytes_written) + : base::SimpleThread("writer_thread"), + write_dispatcher_(write_dispatcher), + messages_written_(messages_written), + bytes_written_(bytes_written) { + *messages_written_ = 0; + *bytes_written_ = 0; + } + + virtual ~WriterThread() { + Join(); + } + + private: + virtual void Run() OVERRIDE { + // Make some data to write. + unsigned char buffer[kMaxMessageSize]; + for (size_t i = 0; i < kMaxMessageSize; i++) + buffer[i] = static_cast<unsigned char>(i); + + // Number of messages to write. + *messages_written_ = static_cast<size_t>(base::RandInt(1000, 6000)); + + // Write messages. + for (size_t i = 0; i < *messages_written_; i++) { + uint32_t bytes_to_write = static_cast<uint32_t>( + base::RandInt(1, static_cast<int>(kMaxMessageSize))); + EXPECT_EQ(MOJO_RESULT_OK, + write_dispatcher_->WriteMessage(buffer, bytes_to_write, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + *bytes_written_ += bytes_to_write; + } + + // Write one last "quit" message. + EXPECT_EQ(MOJO_RESULT_OK, + write_dispatcher_->WriteMessage("quit", 4, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + } + + const scoped_refptr<Dispatcher> write_dispatcher_; + size_t* const messages_written_; + size_t* const bytes_written_; + + DISALLOW_COPY_AND_ASSIGN(WriterThread); +}; + +class ReaderThread : public base::SimpleThread { + public: + // |*messages_read| and |*bytes_read| belong to the thread while it's alive. + ReaderThread(scoped_refptr<Dispatcher> read_dispatcher, + size_t* messages_read, size_t* bytes_read) + : base::SimpleThread("reader_thread"), + read_dispatcher_(read_dispatcher), + messages_read_(messages_read), + bytes_read_(bytes_read) { + *messages_read_ = 0; + *bytes_read_ = 0; + } + + virtual ~ReaderThread() { + Join(); + } + + private: + virtual void Run() OVERRIDE { + unsigned char buffer[kMaxMessageSize]; + MojoResult result; + Waiter w; + + // Read messages. + for (;;) { + // Wait for it to be readable. + w.Init(); + result = read_dispatcher_->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 0); + EXPECT_TRUE(result == MOJO_RESULT_OK || + result == MOJO_RESULT_ALREADY_EXISTS) << "result: " << result; + if (result == MOJO_RESULT_OK) { + // Actually need to wait. + EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, NULL)); + read_dispatcher_->RemoveWaiter(&w); + } + + // Now, try to do the read. + // Clear the buffer so that we can check the result. + memset(buffer, 0, sizeof(buffer)); + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + result = read_dispatcher_->ReadMessage(buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); + EXPECT_TRUE(result == MOJO_RESULT_OK || + result == MOJO_RESULT_SHOULD_WAIT) << "result: " << result; + // We're racing with others to read, so maybe we failed. + if (result == MOJO_RESULT_SHOULD_WAIT) + continue; // In which case, try again. + // Check for quit. + if (buffer_size == 4 && memcmp("quit", buffer, 4) == 0) + return; + EXPECT_GE(buffer_size, 1u); + EXPECT_LE(buffer_size, kMaxMessageSize); + EXPECT_TRUE(IsValidMessage(buffer, buffer_size)); + + (*messages_read_)++; + *bytes_read_ += buffer_size; + } + } + + static bool IsValidMessage(const unsigned char* buffer, + uint32_t message_size) { + size_t i; + for (i = 0; i < message_size; i++) { + if (buffer[i] != static_cast<unsigned char>(i)) + return false; + } + // Check that the remaining bytes weren't stomped on. + for (; i < kMaxMessageSize; i++) { + if (buffer[i] != 0) + return false; + } + return true; + } + + const scoped_refptr<Dispatcher> read_dispatcher_; + size_t* const messages_read_; + size_t* const bytes_read_; + + DISALLOW_COPY_AND_ASSIGN(ReaderThread); +}; + +TEST(MessagePipeDispatcherTest, Stress) { + static const size_t kNumWriters = 30; + static const size_t kNumReaders = kNumWriters; + + scoped_refptr<MessagePipeDispatcher> d_write(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + scoped_refptr<MessagePipeDispatcher> d_read(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + d_write->Init(mp, 0); + d_read->Init(mp, 1); + } + + size_t messages_written[kNumWriters]; + size_t bytes_written[kNumWriters]; + size_t messages_read[kNumReaders]; + size_t bytes_read[kNumReaders]; + { + // Make writers. + ScopedVector<WriterThread> writers; + for (size_t i = 0; i < kNumWriters; i++) { + writers.push_back( + new WriterThread(d_write, &messages_written[i], &bytes_written[i])); + } + + // Make readers. + ScopedVector<ReaderThread> readers; + for (size_t i = 0; i < kNumReaders; i++) { + readers.push_back( + new ReaderThread(d_read, &messages_read[i], &bytes_read[i])); + } + + // Start writers. + for (size_t i = 0; i < kNumWriters; i++) + writers[i]->Start(); + + // Start readers. + for (size_t i = 0; i < kNumReaders; i++) + readers[i]->Start(); + + // TODO(vtl): Maybe I should have an event that triggers all the threads to + // start doing stuff for real (so that the first ones created/started aren't + // advantaged). + } // Joins all the threads. + + size_t total_messages_written = 0; + size_t total_bytes_written = 0; + for (size_t i = 0; i < kNumWriters; i++) { + total_messages_written += messages_written[i]; + total_bytes_written += bytes_written[i]; + } + size_t total_messages_read = 0; + size_t total_bytes_read = 0; + for (size_t i = 0; i < kNumReaders; i++) { + total_messages_read += messages_read[i]; + total_bytes_read += bytes_read[i]; + // We'd have to be really unlucky to have read no messages on a thread. + EXPECT_GT(messages_read[i], 0u) << "reader: " << i; + EXPECT_GE(bytes_read[i], messages_read[i]) << "reader: " << i; + } + EXPECT_EQ(total_messages_written, total_messages_read); + EXPECT_EQ(total_bytes_written, total_bytes_read); + + EXPECT_EQ(MOJO_RESULT_OK, d_write->Close()); + EXPECT_EQ(MOJO_RESULT_OK, d_read->Close()); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_pipe_endpoint.cc b/chromium/mojo/system/message_pipe_endpoint.cc new file mode 100644 index 00000000000..f5ccfc771b3 --- /dev/null +++ b/chromium/mojo/system/message_pipe_endpoint.cc @@ -0,0 +1,56 @@ +// 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 "mojo/system/message_pipe_endpoint.h" + +#include "base/logging.h" +#include "mojo/system/channel.h" + +namespace mojo { +namespace system { + +void MessagePipeEndpoint::Close() { + NOTREACHED(); +} + +void MessagePipeEndpoint::CancelAllWaiters() { + NOTREACHED(); +} + +MojoResult MessagePipeEndpoint::ReadMessage(void* /*bytes*/, + uint32_t* /*num_bytes*/, + DispatcherVector* /*dispatchers*/, + uint32_t* /*num_dispatchers*/, + MojoReadMessageFlags /*flags*/) { + NOTREACHED(); + return MOJO_RESULT_INTERNAL; +} + +MojoResult MessagePipeEndpoint::AddWaiter(Waiter* /*waiter*/, + MojoHandleSignals /*signals*/, + uint32_t /*context*/) { + NOTREACHED(); + return MOJO_RESULT_INTERNAL; +} + +void MessagePipeEndpoint::RemoveWaiter(Waiter* /*waiter*/) { + NOTREACHED(); +} + +void MessagePipeEndpoint::Attach(scoped_refptr<Channel> /*channel*/, + MessageInTransit::EndpointId /*local_id*/) { + NOTREACHED(); +} + +bool MessagePipeEndpoint::Run(MessageInTransit::EndpointId /*remote_id*/) { + NOTREACHED(); + return true; +} + +void MessagePipeEndpoint::OnRemove() { + NOTREACHED(); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/message_pipe_endpoint.h b/chromium/mojo/system/message_pipe_endpoint.h new file mode 100644 index 00000000000..7da65f07a26 --- /dev/null +++ b/chromium/mojo/system/message_pipe_endpoint.h @@ -0,0 +1,93 @@ +// 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 MOJO_SYSTEM_MESSAGE_PIPE_ENDPOINT_H_ +#define MOJO_SYSTEM_MESSAGE_PIPE_ENDPOINT_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Channel; +class Waiter; + +// This is an interface to one of the ends of a message pipe, and is used by +// |MessagePipe|. Its most important role is to provide a sink for messages +// (i.e., a place where messages can be sent). It has a secondary role: When the +// endpoint is local (i.e., in the current process), there'll be a dispatcher +// corresponding to the endpoint. In that case, the implementation of +// |MessagePipeEndpoint| also implements the functionality required by the +// dispatcher, e.g., to read messages and to wait. Implementations of this class +// are not thread-safe; instances are protected by |MesssagePipe|'s lock. +class MOJO_SYSTEM_IMPL_EXPORT MessagePipeEndpoint { + public: + virtual ~MessagePipeEndpoint() {} + + enum Type { + kTypeLocal, + kTypeProxy + }; + virtual Type GetType() const = 0; + + // All implementations must implement these. + // Returns false if the endpoint should be closed and destroyed, else true. + virtual bool OnPeerClose() = 0; + // Implements |MessagePipe::EnqueueMessage()|. The major differences are that: + // a) Dispatchers have been vetted and cloned/attached to the message. + // b) At this point, we cannot report failure (if, e.g., a channel is torn + // down at this point, we should silently swallow the message). + virtual void EnqueueMessage(scoped_ptr<MessageInTransit> message) = 0; + + // Implementations must override these if they represent a local endpoint, + // i.e., one for which there's a |MessagePipeDispatcher| (and thus a handle). + // An implementation for a proxy endpoint (for which there's no dispatcher) + // needs not override these methods, since they should never be called. + // + // These methods implement the methods of the same name in |MessagePipe|, + // though |MessagePipe|'s implementation may have to do a little more if the + // operation involves both endpoints. + virtual void Close(); + virtual void CancelAllWaiters(); + virtual MojoResult ReadMessage(void* bytes, + uint32_t* num_bytes, + DispatcherVector* dispatchers, + uint32_t* num_dispatchers, + MojoReadMessageFlags flags); + virtual MojoResult AddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context); + virtual void RemoveWaiter(Waiter* waiter); + + // Implementations must override these if they represent a proxy endpoint. An + // implementation for a local endpoint needs not override these methods, since + // they should never be called. + virtual void Attach(scoped_refptr<Channel> channel, + MessageInTransit::EndpointId local_id); + // Returns false if the endpoint should be closed and destroyed, else true. + virtual bool Run(MessageInTransit::EndpointId remote_id); + virtual void OnRemove(); + + protected: + MessagePipeEndpoint() {} + + private: + DISALLOW_COPY_AND_ASSIGN(MessagePipeEndpoint); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_MESSAGE_PIPE_ENDPOINT_H_ diff --git a/chromium/mojo/system/message_pipe_unittest.cc b/chromium/mojo/system/message_pipe_unittest.cc new file mode 100644 index 00000000000..5405fd52441 --- /dev/null +++ b/chromium/mojo/system/message_pipe_unittest.cc @@ -0,0 +1,525 @@ +// 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 "mojo/system/message_pipe.h" + +#include "base/memory/ref_counted.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "base/time/time.h" +#include "mojo/system/waiter.h" +#include "mojo/system/waiter_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +// Tests: +// - only default flags +// - reading messages from a port +// - when there are no/one/two messages available for that port +// - with buffer size 0 (and null buffer) -- should get size +// - with too-small buffer -- should get size +// - also verify that buffers aren't modified when/where they shouldn't be +// - writing messages to a port +// - in the obvious scenarios (as above) +// - to a port that's been closed +// - writing a message to a port, closing the other (would be the source) port, +// and reading it +TEST(MessagePipeTest, Basic) { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + + int32_t buffer[2]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + + // Nothing to read yet on port 0. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kBufferSize, buffer_size); + EXPECT_EQ(123, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Ditto for port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Write from port 1 (to port 0). + buffer[0] = 789012345; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(1, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Read from port 0. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + EXPECT_EQ(789012345, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Write two messages from port 0 (to port 1). + buffer[0] = 123456789; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + buffer[0] = 234567890; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Read from port 1 with buffer size 0 (should get the size of next message). + // Also test that giving a null buffer is okay when the buffer size is 0. + buffer_size = 0; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + mp->ReadMessage(1, + NULL, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + + // Read from port 1 with buffer size 1 (too small; should get the size of next + // message). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + EXPECT_EQ(123, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Read from port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + EXPECT_EQ(123456789, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Read again from port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + EXPECT_EQ(234567890, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Read again from port 1 -- it should be empty. + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Write from port 0 (to port 1). + buffer[0] = 345678901; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Close port 0. + mp->Close(0); + + // Try to write from port 1 (to port 0). + buffer[0] = 456789012; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mp->WriteMessage(1, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Read from port 1; should still get message (even though port 0 was closed). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + EXPECT_EQ(345678901, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Read again from port 1 -- it should be empty (and port 0 is closed). + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + mp->Close(1); +} + +TEST(MessagePipeTest, CloseWithQueuedIncomingMessages) { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + + // Write some messages from port 1 (to port 0). + for (int32_t i = 0; i < 5; i++) { + buffer[0] = i; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(1, + buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + } + + // Port 0 shouldn't be empty. + buffer_size = 0; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + mp->ReadMessage(0, + NULL, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kBufferSize, buffer_size); + + // Close port 0 first, which should have outstanding (incoming) messages. + mp->Close(0); + mp->Close(1); +} + +TEST(MessagePipeTest, DiscardMode) { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + + int32_t buffer[2]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + + // Write from port 1 (to port 0). + buffer[0] = 789012345; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(1, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Read/discard from port 0 (no buffer); get size. + buffer_size = 0; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + mp->ReadMessage(0, + NULL, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // Write from port 1 (to port 0). + buffer[0] = 890123456; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(1, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Read from port 0 (buffer big enough). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + EXPECT_EQ(890123456, buffer[0]); + EXPECT_EQ(456, buffer[1]); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // Write from port 1 (to port 0). + buffer[0] = 901234567; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(1, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Read/discard from port 0 (buffer too small); get size. + buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // Write from port 1 (to port 0). + buffer[0] = 123456789; + buffer[1] = 0; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(1, + buffer, static_cast<uint32_t>(sizeof(buffer[0])), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Discard from port 0. + buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + mp->ReadMessage(0, + NULL, NULL, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp->ReadMessage(0, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + mp->Close(0); + mp->Close(1); +} + +TEST(MessagePipeTest, BasicWaiting) { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + Waiter waiter; + + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + uint32_t buffer_size; + + // Always writable (until the other port is closed). + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + mp->AddWaiter(0, + &waiter, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE, + 0)); + + // Not yet readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 1)); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, NULL)); + mp->RemoveWaiter(0, &waiter); + + // Write from port 0 (to port 1), to make port 1 readable. + buffer[0] = 123456789; + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Port 1 should already be readable now. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 2)); + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + mp->AddWaiter(1, + &waiter, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE, + 0)); + // ... and still writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 3)); + + // Close port 0. + mp->Close(0); + + // Now port 1 should not be writable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 4)); + + // But it should still be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 5)); + + // Read from port 1. + buffer[0] = 0; + buffer_size = kBufferSize; + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(1, + buffer, &buffer_size, + 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(123456789, buffer[0]); + + // Now port 1 should no longer be readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 6)); + + mp->Close(1); +} + +TEST(MessagePipeTest, ThreadedWaiting) { + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer)); + + MojoResult result; + uint32_t context; + + // Write to wake up waiter waiting for read. + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + test::SimpleWaiterThread thread(&result, &context); + + thread.waiter()->Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp->AddWaiter(1, thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, + 1)); + thread.Start(); + + buffer[0] = 123456789; + // Write from port 0 (to port 1), which should wake up the waiter. + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + buffer, kBufferSize, + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + mp->RemoveWaiter(1, thread.waiter()); + + mp->Close(0); + mp->Close(1); + } // Joins |thread|. + // The waiter should have woken up successfully. + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(1u, context); + + // Close to cancel waiter. + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + test::SimpleWaiterThread thread(&result, &context); + + thread.waiter()->Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp->AddWaiter(1, thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, + 2)); + thread.Start(); + + // Close port 1 first -- this should result in the waiter being cancelled. + mp->CancelAllWaiters(1); + mp->Close(1); + + // Port 1 is closed, so |Dispatcher::RemoveWaiter()| wouldn't call into the + // |MessagePipe| to remove any waiter. + + mp->Close(0); + } // Joins |thread|. + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(2u, context); + + // Close to make waiter un-wake-up-able. + { + scoped_refptr<MessagePipe> mp(new MessagePipe()); + test::SimpleWaiterThread thread(&result, &context); + + thread.waiter()->Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp->AddWaiter(1, thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, + 3)); + thread.Start(); + + // Close port 0 first -- this should wake the waiter up, since port 1 will + // never be readable. + mp->CancelAllWaiters(0); + mp->Close(0); + + mp->RemoveWaiter(1, thread.waiter()); + + mp->CancelAllWaiters(1); + mp->Close(1); + } // Joins |thread|. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(3u, context); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/multiprocess_message_pipe_unittest.cc b/chromium/mojo/system/multiprocess_message_pipe_unittest.cc new file mode 100644 index 00000000000..55527788c13 --- /dev/null +++ b/chromium/mojo/system/multiprocess_message_pipe_unittest.cc @@ -0,0 +1,555 @@ +// 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 <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "build/build_config.h" // TODO(vtl): Remove this. +#include "mojo/common/test/multiprocess_test_helper.h" +#include "mojo/common/test/test_utils.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/channel.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/local_message_pipe_endpoint.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/platform_handle_dispatcher.h" +#include "mojo/system/proxy_message_pipe_endpoint.h" +#include "mojo/system/raw_channel.h" +#include "mojo/system/raw_shared_buffer.h" +#include "mojo/system/shared_buffer_dispatcher.h" +#include "mojo/system/test_utils.h" +#include "mojo/system/waiter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +class ChannelThread { + public: + ChannelThread() : test_io_thread_(test::TestIOThread::kManualStart) {} + ~ChannelThread() { + Stop(); + } + + void Start(embedder::ScopedPlatformHandle platform_handle, + scoped_refptr<MessagePipe> message_pipe) { + test_io_thread_.Start(); + test_io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelThread::InitChannelOnIOThread, + base::Unretained(this), base::Passed(&platform_handle), + message_pipe)); + } + + void Stop() { + if (channel_) { + // Hack to flush write buffers before quitting. + // TODO(vtl): Remove this once |Channel| has a + // |FlushWriteBufferAndShutdown()| (or whatever). + while (!channel_->IsWriteBufferEmpty()) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); + + test_io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&ChannelThread::ShutdownChannelOnIOThread, + base::Unretained(this))); + } + test_io_thread_.Stop(); + } + + private: + void InitChannelOnIOThread(embedder::ScopedPlatformHandle platform_handle, + scoped_refptr<MessagePipe> message_pipe) { + CHECK_EQ(base::MessageLoop::current(), test_io_thread_.message_loop()); + CHECK(platform_handle.is_valid()); + + // Create and initialize |Channel|. + channel_ = new Channel(); + CHECK(channel_->Init(RawChannel::Create(platform_handle.Pass()))); + + // Attach the message pipe endpoint. + // Note: On the "server" (parent process) side, we need not attach the + // message pipe endpoint immediately. However, on the "client" (child + // process) side, this *must* be done here -- otherwise, the |Channel| may + // receive/process messages (which it can do as soon as it's hooked up to + // the IO thread message loop, and that message loop runs) before the + // message pipe endpoint is attached. + CHECK_EQ(channel_->AttachMessagePipeEndpoint(message_pipe, 1), + Channel::kBootstrapEndpointId); + CHECK(channel_->RunMessagePipeEndpoint(Channel::kBootstrapEndpointId, + Channel::kBootstrapEndpointId)); + } + + void ShutdownChannelOnIOThread() { + CHECK(channel_); + channel_->Shutdown(); + channel_ = NULL; + } + + test::TestIOThread test_io_thread_; + scoped_refptr<Channel> channel_; + + DISALLOW_COPY_AND_ASSIGN(ChannelThread); +}; + +class MultiprocessMessagePipeTest : public testing::Test { + public: + MultiprocessMessagePipeTest() {} + virtual ~MultiprocessMessagePipeTest() {} + + protected: + void Init(scoped_refptr<MessagePipe> mp) { + channel_thread_.Start(helper_.server_platform_handle.Pass(), mp); + } + + mojo::test::MultiprocessTestHelper* helper() { return &helper_; } + + private: + ChannelThread channel_thread_; + mojo::test::MultiprocessTestHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(MultiprocessMessagePipeTest); +}; + +MojoResult WaitIfNecessary(scoped_refptr<MessagePipe> mp, + MojoHandleSignals signals) { + Waiter waiter; + waiter.Init(); + + MojoResult add_result = mp->AddWaiter(0, &waiter, signals, 0); + if (add_result != MOJO_RESULT_OK) { + return (add_result == MOJO_RESULT_ALREADY_EXISTS) ? MOJO_RESULT_OK : + add_result; + } + + MojoResult wait_result = waiter.Wait(MOJO_DEADLINE_INDEFINITE, NULL); + mp->RemoveWaiter(0, &waiter); + return wait_result; +} + +// For each message received, sends a reply message with the same contents +// repeated twice, until the other end is closed or it receives "quitquitquit" +// (which it doesn't reply to). It'll return the number of messages received, +// not including any "quitquitquit" message, modulo 100. +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(EchoEcho) { + ChannelThread channel_thread; + embedder::ScopedPlatformHandle client_platform_handle = + mojo::test::MultiprocessTestHelper::client_platform_handle.Pass(); + CHECK(client_platform_handle.is_valid()); + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + channel_thread.Start(client_platform_handle.Pass(), mp); + + const std::string quitquitquit("quitquitquit"); + int rv = 0; + for (;; rv = (rv + 1) % 100) { + // Wait for our end of the message pipe to be readable. + MojoResult result = WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE); + if (result != MOJO_RESULT_OK) { + // It was closed, probably. + CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION); + break; + } + + std::string read_buffer(1000, '\0'); + uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size()); + CHECK_EQ(mp->ReadMessage(0, + &read_buffer[0], &read_buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + VLOG(2) << "Child got: " << read_buffer; + + if (read_buffer == quitquitquit) { + VLOG(2) << "Child quitting."; + break; + } + + std::string write_buffer = read_buffer + read_buffer; + CHECK_EQ(mp->WriteMessage(0, + write_buffer.data(), + static_cast<uint32_t>(write_buffer.size()), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + mp->Close(0); + return rv; +} + +// Sends "hello" to child, and expects "hellohello" back. +TEST_F(MultiprocessMessagePipeTest, Basic) { + helper()->StartChild("EchoEcho"); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + Init(mp); + + std::string hello("hello"); + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + hello.data(), static_cast<uint32_t>(hello.size()), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(1000, '\0'); + uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size()); + CHECK_EQ(mp->ReadMessage(0, + &read_buffer[0], &read_buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + VLOG(2) << "Parent got: " << read_buffer; + EXPECT_EQ(hello + hello, read_buffer); + + mp->Close(0); + + // We sent one message. + EXPECT_EQ(1 % 100, helper()->WaitForChildShutdown()); +} + +// Sends a bunch of messages to the child. Expects them "repeated" back. Waits +// for the child to close its end before quitting. +TEST_F(MultiprocessMessagePipeTest, QueueMessages) { + helper()->StartChild("EchoEcho"); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + Init(mp); + + static const size_t kNumMessages = 1001; + for (size_t i = 0; i < kNumMessages; i++) { + std::string write_buffer(i, 'A' + (i % 26)); + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + write_buffer.data(), + static_cast<uint32_t>(write_buffer.size()), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + } + + const std::string quitquitquit("quitquitquit"); + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + quitquitquit.data(), + static_cast<uint32_t>(quitquitquit.size()), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + for (size_t i = 0; i < kNumMessages; i++) { + EXPECT_EQ(MOJO_RESULT_OK, WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(kNumMessages * 2, '\0'); + uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size()); + CHECK_EQ(mp->ReadMessage(0, + &read_buffer[0], &read_buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + + EXPECT_EQ(std::string(i * 2, 'A' + (i % 26)), read_buffer); + } + + // Wait for it to become readable, which should fail (since we sent + // "quitquitquit"). + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE)); + + mp->Close(0); + + EXPECT_EQ(static_cast<int>(kNumMessages % 100), + helper()->WaitForChildShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckSharedBuffer) { + ChannelThread channel_thread; + embedder::ScopedPlatformHandle client_platform_handle = + mojo::test::MultiprocessTestHelper::client_platform_handle.Pass(); + CHECK(client_platform_handle.is_valid()); + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + channel_thread.Start(client_platform_handle.Pass(), mp); + + // Wait for the first message from our parent. + CHECK_EQ(WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + // It should have a shared buffer. + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast<uint32_t>(read_buffer.size()); + DispatcherVector dispatchers; + uint32_t num_dispatchers = 10; // Maximum number to receive. + CHECK_EQ(mp->ReadMessage(0, + &read_buffer[0], &num_bytes, + &dispatchers, &num_dispatchers, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("go 1")); + CHECK_EQ(num_dispatchers, 1u); + + CHECK_EQ(dispatchers[0]->GetType(), Dispatcher::kTypeSharedBuffer); + + scoped_refptr<SharedBufferDispatcher> dispatcher( + static_cast<SharedBufferDispatcher*>(dispatchers[0].get())); + + // Make a mapping. + scoped_ptr<RawSharedBufferMapping> mapping; + CHECK_EQ(dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping), + MOJO_RESULT_OK); + CHECK(mapping); + CHECK(mapping->base()); + CHECK_EQ(mapping->length(), 100u); + + // Write some stuff to the shared buffer. + static const char kHello[] = "hello"; + memcpy(mapping->base(), kHello, sizeof(kHello)); + + // We should be able to close the dispatcher now. + dispatcher->Close(); + + // And send a message to signal that we've written stuff. + const std::string go2("go 2"); + CHECK_EQ(mp->WriteMessage(0, + &go2[0], + static_cast<uint32_t>(go2.size()), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + // Now wait for our parent to send us a message. + CHECK_EQ(WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + read_buffer = std::string(100, '\0'); + num_bytes = static_cast<uint32_t>(read_buffer.size()); + CHECK_EQ(mp->ReadMessage(0, + &read_buffer[0], &num_bytes, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("go 3")); + + // It should have written something to the shared buffer. + static const char kWorld[] = "world!!!"; + CHECK_EQ(memcmp(mapping->base(), kWorld, sizeof(kWorld)), 0); + + // And we're done. + mp->Close(0); + + return 0; +} + +#if defined(OS_POSIX) +#define MAYBE_SharedBufferPassing SharedBufferPassing +#else +// Not yet implemented (on Windows). +#define MAYBE_SharedBufferPassing DISABLED_SharedBufferPassing +#endif +TEST_F(MultiprocessMessagePipeTest, MAYBE_SharedBufferPassing) { + helper()->StartChild("CheckSharedBuffer"); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + Init(mp); + + // Make a shared buffer. + scoped_refptr<SharedBufferDispatcher> dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher)); + ASSERT_TRUE(dispatcher); + + // Make a mapping. + scoped_ptr<RawSharedBufferMapping> mapping; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->base()); + ASSERT_EQ(100u, mapping->length()); + + // Send the shared buffer. + const std::string go1("go 1"); + DispatcherTransport transport( + test::DispatcherTryStartTransport(dispatcher.get())); + ASSERT_TRUE(transport.is_valid()); + + std::vector<DispatcherTransport> transports; + transports.push_back(transport); + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + &go1[0], + static_cast<uint32_t>(go1.size()), + &transports, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + transport.End(); + + EXPECT_TRUE(dispatcher->HasOneRef()); + dispatcher = NULL; + + // Wait for a message from the child. + EXPECT_EQ(MOJO_RESULT_OK, WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast<uint32_t>(read_buffer.size()); + EXPECT_EQ(MOJO_RESULT_OK, + mp->ReadMessage(0, + &read_buffer[0], &num_bytes, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + read_buffer.resize(num_bytes); + EXPECT_EQ(std::string("go 2"), read_buffer); + + // After we get it, the child should have written something to the shared + // buffer. + static const char kHello[] = "hello"; + EXPECT_EQ(0, memcmp(mapping->base(), kHello, sizeof(kHello))); + + // Now we'll write some stuff to the shared buffer. + static const char kWorld[] = "world!!!"; + memcpy(mapping->base(), kWorld, sizeof(kWorld)); + + // And send a message to signal that we've written stuff. + const std::string go3("go 3"); + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + &go3[0], + static_cast<uint32_t>(go3.size()), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |mp| to become readable, which should fail. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE)); + + mp->Close(0); + + EXPECT_EQ(0, helper()->WaitForChildShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckPlatformHandleFile) { + ChannelThread channel_thread; + embedder::ScopedPlatformHandle client_platform_handle = + mojo::test::MultiprocessTestHelper::client_platform_handle.Pass(); + CHECK(client_platform_handle.is_valid()); + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + channel_thread.Start(client_platform_handle.Pass(), mp); + + CHECK_EQ(WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast<uint32_t>(read_buffer.size()); + DispatcherVector dispatchers; + uint32_t num_dispatchers = 10; // Maximum number to receive. + CHECK_EQ(mp->ReadMessage(0, + &read_buffer[0], &num_bytes, + &dispatchers, &num_dispatchers, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + mp->Close(0); + + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("hello")); + CHECK_EQ(num_dispatchers, 1u); + + CHECK_EQ(dispatchers[0]->GetType(), Dispatcher::kTypePlatformHandle); + + scoped_refptr<PlatformHandleDispatcher> dispatcher( + static_cast<PlatformHandleDispatcher*>(dispatchers[0].get())); + embedder::ScopedPlatformHandle h = dispatcher->PassPlatformHandle().Pass(); + CHECK(h.is_valid()); + dispatcher->Close(); + + base::ScopedFILE fp(mojo::test::FILEFromPlatformHandle(h.Pass(), "r")); + CHECK(fp); + std::string fread_buffer(100, '\0'); + size_t bytes_read = fread(&fread_buffer[0], 1, fread_buffer.size(), fp.get()); + fread_buffer.resize(bytes_read); + CHECK_EQ(fread_buffer, "world"); + + return 0; +} + +#if defined(OS_POSIX) +#define MAYBE_PlatformHandlePassing PlatformHandlePassing +#else +// Not yet implemented (on Windows). +#define MAYBE_PlatformHandlePassing DISABLED_PlatformHandlePassing +#endif +TEST_F(MultiprocessMessagePipeTest, MAYBE_PlatformHandlePassing) { + helper()->StartChild("CheckPlatformHandleFile"); + + scoped_refptr<MessagePipe> mp(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + Init(mp); + + base::FilePath unused; + base::ScopedFILE fp(CreateAndOpenTemporaryFile(&unused)); + const std::string world("world"); + ASSERT_EQ(fwrite(&world[0], 1, world.size(), fp.get()), world.size()); + fflush(fp.get()); + rewind(fp.get()); + + embedder::ScopedPlatformHandle h( + mojo::test::PlatformHandleFromFILE(fp.Pass())); + scoped_refptr<PlatformHandleDispatcher> dispatcher( + new PlatformHandleDispatcher(h.Pass())); + + const std::string hello("hello"); + DispatcherTransport transport( + test::DispatcherTryStartTransport(dispatcher.get())); + ASSERT_TRUE(transport.is_valid()); + + std::vector<DispatcherTransport> transports; + transports.push_back(transport); + EXPECT_EQ(MOJO_RESULT_OK, + mp->WriteMessage(0, + &hello[0], + static_cast<uint32_t>(hello.size()), + &transports, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + transport.End(); + + EXPECT_TRUE(dispatcher->HasOneRef()); + dispatcher = NULL; + + // Wait for it to become readable, which should fail. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE)); + + mp->Close(0); + + EXPECT_EQ(0, helper()->WaitForChildShutdown()); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/options_validation.h b/chromium/mojo/system/options_validation.h new file mode 100644 index 00000000000..04514adf752 --- /dev/null +++ b/chromium/mojo/system/options_validation.h @@ -0,0 +1,123 @@ +// 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. + +// Functions to help with verifying various |Mojo...Options| structs from the +// (public, C) API. These are "extensible" structs, which all have |struct_size| +// as their first member. All fields (other than |struct_size|) are optional, +// but any |flags| specified must be known to the system (otherwise, an error of +// |MOJO_RESULT_UNIMPLEMENTED| should be returned). + +#ifndef MOJO_SYSTEM_OPTIONS_VALIDATION_H_ +#define MOJO_SYSTEM_OPTIONS_VALIDATION_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/memory.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// Checks that |buffer| appears to contain a valid Options struct, namely +// properly aligned and with a |struct_size| field (which must the first field +// of the struct and be a |uint32_t|) containing a plausible size. +template <class Options> +bool IsOptionsStructPointerAndSizeValid(const void* buffer) { + COMPILE_ASSERT(offsetof(Options, struct_size) == 0, + Options_struct_size_not_first_member); + // TODO(vtl): With C++11, use |sizeof(Options::struct_size)| instead. + COMPILE_ASSERT(sizeof(static_cast<const Options*>(buffer)->struct_size) == + sizeof(uint32_t), + Options_struct_size_not_32_bits); + + // Note: Use |MOJO_ALIGNOF()| here to match the exact macro used in the + // declaration of Options structs. + if (!internal::VerifyUserPointerHelper<sizeof(uint32_t), + MOJO_ALIGNOF(Options)>(buffer)) + return false; + + return static_cast<const Options*>(buffer)->struct_size >= sizeof(uint32_t); +} + +// Checks that the Options struct in |buffer| has a member with the given offset +// and size. This may be called only if |IsOptionsStructPointerAndSizeValid()| +// returned true. +// +// You may want to use the macro |HAS_OPTIONS_STRUCT_MEMBER()| instead. +template <class Options, size_t offset, size_t size> +bool HasOptionsStructMember(const void* buffer) { + // We assume that |offset| and |size| are reasonable, since they should come + // from |offsetof(Options, some_member)| and |sizeof(Options::some_member)|, + // respectively. + return static_cast<const Options*>(buffer)->struct_size >= + offset + size; +} + +// Macro to invoke |HasOptionsStructMember()| parametrized by member name +// instead of offset and size. +// +// (We can't just give |HasOptionsStructMember()| a member pointer template +// argument instead, since there's no good/strictly-correct way to get an offset +// from that.) +// +// TODO(vtl): With C++11, use |sizeof(Options::member)| instead. +#define HAS_OPTIONS_STRUCT_MEMBER(Options, member, buffer) \ + (HasOptionsStructMember< \ + Options, \ + offsetof(Options, member), \ + sizeof(static_cast<const Options*>(buffer)->member)>(buffer)) + +// Checks that the (standard) |flags| member consists of only known flags. This +// should only be called if |HAS_OPTIONS_STRUCT_MEMBER()| returned true for the +// |flags| field. +// +// The rationale for *not* ignoring these flags is that the caller should have a +// way of specifying that certain options not be ignored. E.g., one may have a +// |MOJO_..._OPTIONS_FLAG_DONT_IGNORE_FOO| flag and a |foo| member; if the flag +// is set, it will guarantee that the version of the system knows about the +// |foo| member (and won't ignore it). +template <class Options> +bool AreOptionsFlagsAllKnown(const void* buffer, uint32_t known_flags) { + return (static_cast<const Options*>(buffer)->flags & ~known_flags) == 0; +} + +// Does basic cursory checks on |in_options| (|struct_size| and |flags|; |flags| +// must immediately follow |struct_size|); |in_options| must be non-null. The +// following should be done before calling this: +// - Set |out_options| to the default options. +// - If |in_options| is null, don't continue (success). +// This function then: +// - Checks if (according to |IsOptionsStructPointerAndSizeValid()|), +// |struct_size| is valid; if not returns |MOJO_RESULT_INVALID_ARGUMENT|. +// - If |in_options| has a |flags| field, checks that it only has +// |known_flags| set; if so copies it to |out_options->flags|, and if not +// returns |MOJO_RESULT_UNIMPLEMENTED|. +// - At this point, returns |MOJO_RESULT_OK|. +template <class Options> +MojoResult ValidateOptionsStructPointerSizeAndFlags( + const Options* in_options, + uint32_t known_flags, + Options* out_options) { + COMPILE_ASSERT(offsetof(Options, flags) == sizeof(uint32_t), + Options_flags_doesnt_immediately_follow_struct_size); + + if (!IsOptionsStructPointerAndSizeValid<Options>(in_options)) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (HAS_OPTIONS_STRUCT_MEMBER(Options, flags, in_options)) { + if (!AreOptionsFlagsAllKnown<Options>(in_options, known_flags)) + return MOJO_RESULT_UNIMPLEMENTED; + out_options->flags = in_options->flags; + } + + return MOJO_RESULT_OK; +} + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_OPTIONS_VALIDATION_H_ diff --git a/chromium/mojo/system/options_validation_unittest.cc b/chromium/mojo/system/options_validation_unittest.cc new file mode 100644 index 00000000000..4a26fc675db --- /dev/null +++ b/chromium/mojo/system/options_validation_unittest.cc @@ -0,0 +1,190 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/options_validation.h" + +#include <stddef.h> +#include <stdint.h> + +#include "mojo/public/c/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +// Declare a test options struct just as we do in actual public headers. + +typedef uint32_t TestOptionsFlags; + +MOJO_COMPILE_ASSERT(MOJO_ALIGNOF(int64_t) == 8, int64_t_has_weird_alignment); +struct MOJO_ALIGNAS(8) TestOptions { + uint32_t struct_size; + TestOptionsFlags flags; + uint32_t member1; + uint32_t member2; +}; +MOJO_COMPILE_ASSERT(sizeof(TestOptions) == 16, TestOptions_has_wrong_size); + +const uint32_t kSizeOfTestOptions = static_cast<uint32_t>(sizeof(TestOptions)); + +TEST(OptionsValidationTest, Valid) { + const TestOptions kOptions1 = { + kSizeOfTestOptions + }; + + EXPECT_TRUE(IsOptionsStructPointerAndSizeValid<TestOptions>(&kOptions1)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, flags, &kOptions1)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member1, &kOptions1)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member2, &kOptions1)); + + const TestOptions kOptions2 = { + static_cast<uint32_t>(offsetof(TestOptions, struct_size) + sizeof(uint32_t)) + }; + EXPECT_TRUE(IsOptionsStructPointerAndSizeValid<TestOptions>(&kOptions2)); + EXPECT_FALSE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, flags, &kOptions2)); + EXPECT_FALSE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member1, &kOptions2)); + EXPECT_FALSE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member2, &kOptions2)); + + const TestOptions kOptions3 = { + static_cast<uint32_t>(offsetof(TestOptions, flags) + sizeof(uint32_t)) + }; + EXPECT_TRUE(IsOptionsStructPointerAndSizeValid<TestOptions>(&kOptions3)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, flags, &kOptions3)); + EXPECT_FALSE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member1, &kOptions3)); + EXPECT_FALSE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member2, &kOptions3)); + + MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {}; + TestOptions* options = reinterpret_cast<TestOptions*>(buf); + options->struct_size = kSizeOfTestOptions + 1; + EXPECT_TRUE(IsOptionsStructPointerAndSizeValid<TestOptions>(options)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, flags, options)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member1, options)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member2, options)); + + options->struct_size = kSizeOfTestOptions + 4; + EXPECT_TRUE(IsOptionsStructPointerAndSizeValid<TestOptions>(options)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, flags, options)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member1, options)); + EXPECT_TRUE(HAS_OPTIONS_STRUCT_MEMBER(TestOptions, member2, options)); +} + +TEST(OptionsValidationTest, Invalid) { + // Null: + EXPECT_FALSE(IsOptionsStructPointerAndSizeValid<TestOptions>(NULL)); + + // Unaligned: + EXPECT_FALSE(IsOptionsStructPointerAndSizeValid<TestOptions>( + reinterpret_cast<const void*>(1))); + EXPECT_FALSE(IsOptionsStructPointerAndSizeValid<TestOptions>( + reinterpret_cast<const void*>(4))); + + // Size too small: + for (size_t i = 0; i < sizeof(uint32_t); i++) { + TestOptions options = {static_cast<uint32_t>(i)}; + EXPECT_FALSE(IsOptionsStructPointerAndSizeValid<TestOptions>(&options)) + << i; + } +} + +TEST(OptionsValidationTest, CheckFlags) { + const TestOptions kOptions1 = {kSizeOfTestOptions, 0}; + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions1, 0u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions1, 1u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions1, 3u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions1, 7u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions1, ~0u)); + + const TestOptions kOptions2 = {kSizeOfTestOptions, 1}; + EXPECT_FALSE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions2, 0u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions2, 1u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions2, 3u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions2, 7u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions2, ~0u)); + + const TestOptions kOptions3 = {kSizeOfTestOptions, 2}; + EXPECT_FALSE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions3, 0u)); + EXPECT_FALSE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions3, 1u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions3, 3u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions3, 7u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions3, ~0u)); + + const TestOptions kOptions4 = {kSizeOfTestOptions, 5}; + EXPECT_FALSE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions4, 0u)); + EXPECT_FALSE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions4, 1u)); + EXPECT_FALSE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions4, 3u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions4, 7u)); + EXPECT_TRUE(AreOptionsFlagsAllKnown<TestOptions>(&kOptions4, ~0u)); +} + +TEST(OptionsValidationTest, ValidateOptionsStructPointerSizeAndFlags) { + const TestOptions kDefaultOptions = {kSizeOfTestOptions, 1u, 123u, 456u}; + + // Valid cases: + + // "Normal": + { + const TestOptions kOptions = {kSizeOfTestOptions, 0u, 12u, 34u}; + TestOptions validated_options = kDefaultOptions; + EXPECT_EQ(MOJO_RESULT_OK, + ValidateOptionsStructPointerSizeAndFlags<TestOptions>( + &kOptions, 3u, &validated_options)); + EXPECT_EQ(kDefaultOptions.struct_size, validated_options.struct_size); + // Copied |flags|. + EXPECT_EQ(kOptions.flags, validated_options.flags); + // Didn't touch subsequent members. + EXPECT_EQ(kDefaultOptions.member1, validated_options.member1); + EXPECT_EQ(kDefaultOptions.member2, validated_options.member2); + } + + // Doesn't actually have |flags|: + { + const TestOptions kOptions = { + static_cast<uint32_t>(sizeof(uint32_t)), 0u, 12u, 34u + }; + TestOptions validated_options = kDefaultOptions; + EXPECT_EQ(MOJO_RESULT_OK, + ValidateOptionsStructPointerSizeAndFlags<TestOptions>( + &kOptions, 3u, &validated_options)); + EXPECT_EQ(kDefaultOptions.struct_size, validated_options.struct_size); + // Didn't copy |flags|. + EXPECT_EQ(kDefaultOptions.flags, validated_options.flags); + // Didn't touch subsequent members. + EXPECT_EQ(kDefaultOptions.member1, validated_options.member1); + EXPECT_EQ(kDefaultOptions.member2, validated_options.member2); + } + + // Invalid cases: + + // Unaligned: + { + TestOptions validated_options = kDefaultOptions; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ValidateOptionsStructPointerSizeAndFlags<TestOptions>( + reinterpret_cast<const TestOptions*>(1), 3u, + &validated_options)); + } + + // |struct_size| too small: + { + const TestOptions kOptions = {1u, 0u, 12u, 34u}; + TestOptions validated_options = kDefaultOptions; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ValidateOptionsStructPointerSizeAndFlags<TestOptions>( + &kOptions, 3u, &validated_options)); + } + + // Unknown |flag|: + { + const TestOptions kOptions = {kSizeOfTestOptions, 5u, 12u, 34u}; + TestOptions validated_options = kDefaultOptions; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + ValidateOptionsStructPointerSizeAndFlags<TestOptions>( + &kOptions, 3u, &validated_options)); + } +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/platform_handle_dispatcher.cc b/chromium/mojo/system/platform_handle_dispatcher.cc new file mode 100644 index 00000000000..63a5e716d21 --- /dev/null +++ b/chromium/mojo/system/platform_handle_dispatcher.cc @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/platform_handle_dispatcher.h" + +#include <algorithm> + +#include "base/logging.h" + +namespace mojo { +namespace system { + +namespace { + +const size_t kInvalidPlatformHandleIndex = static_cast<size_t>(-1); + +struct SerializedPlatformHandleDispatcher { + size_t platform_handle_index; // (Or |kInvalidPlatformHandleIndex|.) +}; + +} // namespace + +PlatformHandleDispatcher::PlatformHandleDispatcher( + embedder::ScopedPlatformHandle platform_handle) + : platform_handle_(platform_handle.Pass()) { +} + +embedder::ScopedPlatformHandle PlatformHandleDispatcher::PassPlatformHandle() { + base::AutoLock locker(lock()); + return platform_handle_.Pass(); +} + +Dispatcher::Type PlatformHandleDispatcher::GetType() const { + return kTypePlatformHandle; +} + +// static +scoped_refptr<PlatformHandleDispatcher> PlatformHandleDispatcher::Deserialize( + Channel* channel, + const void* source, + size_t size, + embedder::PlatformHandleVector* platform_handles) { + if (size != sizeof(SerializedPlatformHandleDispatcher)) { + LOG(ERROR) << "Invalid serialized platform handle dispatcher (bad size)"; + return scoped_refptr<PlatformHandleDispatcher>(); + } + + const SerializedPlatformHandleDispatcher* serialization = + static_cast<const SerializedPlatformHandleDispatcher*>(source); + size_t platform_handle_index = serialization->platform_handle_index; + + // Starts off invalid, which is what we want. + embedder::PlatformHandle platform_handle; + + if (platform_handle_index != kInvalidPlatformHandleIndex) { + if (!platform_handles || + platform_handle_index >= platform_handles->size()) { + LOG(ERROR) + << "Invalid serialized platform handle dispatcher (missing handles)"; + return scoped_refptr<PlatformHandleDispatcher>(); + } + + // We take ownership of the handle, so we have to invalidate the one in + // |platform_handles|. + std::swap(platform_handle, (*platform_handles)[platform_handle_index]); + } + + return scoped_refptr<PlatformHandleDispatcher>(new PlatformHandleDispatcher( + embedder::ScopedPlatformHandle(platform_handle))); +} + +PlatformHandleDispatcher::~PlatformHandleDispatcher() { +} + +void PlatformHandleDispatcher::CloseImplNoLock() { + lock().AssertAcquired(); + platform_handle_.reset(); +} + +scoped_refptr<Dispatcher> + PlatformHandleDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() { + lock().AssertAcquired(); + return scoped_refptr<Dispatcher>( + new PlatformHandleDispatcher(platform_handle_.Pass())); +} + +void PlatformHandleDispatcher::StartSerializeImplNoLock( + Channel* /*channel*/, + size_t* max_size, + size_t* max_platform_handles) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + *max_size = sizeof(SerializedPlatformHandleDispatcher); + *max_platform_handles = 1; +} + +bool PlatformHandleDispatcher::EndSerializeAndCloseImplNoLock( + Channel* /*channel*/, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + + SerializedPlatformHandleDispatcher* serialization = + static_cast<SerializedPlatformHandleDispatcher*>(destination); + if (platform_handle_.is_valid()) { + serialization->platform_handle_index = platform_handles->size(); + platform_handles->push_back(platform_handle_.release()); + } else { + serialization->platform_handle_index = kInvalidPlatformHandleIndex; + } + + *actual_size = sizeof(SerializedPlatformHandleDispatcher); + return true; +} + +HandleSignalsState + PlatformHandleDispatcher::GetHandleSignalsStateNoLock() const { + return HandleSignalsState(); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/platform_handle_dispatcher.h b/chromium/mojo/system/platform_handle_dispatcher.h new file mode 100644 index 00000000000..f09684efdc0 --- /dev/null +++ b/chromium/mojo/system/platform_handle_dispatcher.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 MOJO_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ +#define MOJO_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ + +#include "base/macros.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/simple_dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// A dispatcher that simply wraps/transports a |PlatformHandle| (only for use by +// the embedder). +class MOJO_SYSTEM_IMPL_EXPORT PlatformHandleDispatcher + : public SimpleDispatcher { + public: + explicit PlatformHandleDispatcher( + embedder::ScopedPlatformHandle platform_handle); + + embedder::ScopedPlatformHandle PassPlatformHandle(); + + // |Dispatcher| public methods: + virtual Type GetType() const OVERRIDE; + + // The "opposite" of |SerializeAndClose()|. (Typically this is called by + // |Dispatcher::Deserialize()|.) + static scoped_refptr<PlatformHandleDispatcher> Deserialize( + Channel* channel, + const void* source, + size_t size, + embedder::PlatformHandleVector* platform_handles); + + private: + virtual ~PlatformHandleDispatcher(); + + // |Dispatcher| protected methods: + virtual void CloseImplNoLock() OVERRIDE; + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE; + virtual void StartSerializeImplNoLock(Channel* channel, + size_t* max_size, + size_t* max_platform_handles) OVERRIDE; + virtual bool EndSerializeAndCloseImplNoLock( + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) OVERRIDE; + + // |SimpleDispatcher| method: + virtual HandleSignalsState GetHandleSignalsStateNoLock() const OVERRIDE; + + embedder::ScopedPlatformHandle platform_handle_; + + DISALLOW_COPY_AND_ASSIGN(PlatformHandleDispatcher); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ diff --git a/chromium/mojo/system/platform_handle_dispatcher_unittest.cc b/chromium/mojo/system/platform_handle_dispatcher_unittest.cc new file mode 100644 index 00000000000..5c22f55a114 --- /dev/null +++ b/chromium/mojo/system/platform_handle_dispatcher_unittest.cc @@ -0,0 +1,102 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/platform_handle_dispatcher.h" + +#include <stdio.h> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/memory/ref_counted.h" +#include "mojo/common/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +TEST(PlatformHandleDispatcherTest, Basic) { + static const char kHelloWorld[] = "hello world"; + + base::FilePath unused; + base::ScopedFILE fp(CreateAndOpenTemporaryFile(&unused)); + ASSERT_TRUE(fp); + EXPECT_EQ(sizeof(kHelloWorld), + fwrite(kHelloWorld, 1, sizeof(kHelloWorld), fp.get())); + + embedder::ScopedPlatformHandle + h(mojo::test::PlatformHandleFromFILE(fp.Pass())); + EXPECT_FALSE(fp); + ASSERT_TRUE(h.is_valid()); + + scoped_refptr<PlatformHandleDispatcher> dispatcher( + new PlatformHandleDispatcher(h.Pass())); + EXPECT_FALSE(h.is_valid()); + EXPECT_EQ(Dispatcher::kTypePlatformHandle, dispatcher->GetType()); + + h = dispatcher->PassPlatformHandle().Pass(); + EXPECT_TRUE(h.is_valid()); + + fp = mojo::test::FILEFromPlatformHandle(h.Pass(), "rb").Pass(); + EXPECT_FALSE(h.is_valid()); + EXPECT_TRUE(fp); + + rewind(fp.get()); + char read_buffer[1000] = {}; + EXPECT_EQ(sizeof(kHelloWorld), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kHelloWorld, read_buffer); + + // Try getting the handle again. (It should fail cleanly.) + h = dispatcher->PassPlatformHandle().Pass(); + EXPECT_FALSE(h.is_valid()); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +TEST(PlatformHandleDispatcherTest, CreateEquivalentDispatcherAndClose) { + static const char kFooBar[] = "foo bar"; + + base::FilePath unused; + base::ScopedFILE fp(CreateAndOpenTemporaryFile(&unused)); + EXPECT_EQ(sizeof(kFooBar), fwrite(kFooBar, 1, sizeof(kFooBar), fp.get())); + + scoped_refptr<PlatformHandleDispatcher> dispatcher( + new PlatformHandleDispatcher( + mojo::test::PlatformHandleFromFILE(fp.Pass()))); + + DispatcherTransport transport( + test::DispatcherTryStartTransport(dispatcher.get())); + EXPECT_TRUE(transport.is_valid()); + EXPECT_EQ(Dispatcher::kTypePlatformHandle, transport.GetType()); + EXPECT_FALSE(transport.IsBusy()); + + scoped_refptr<Dispatcher> generic_dispatcher = + transport.CreateEquivalentDispatcherAndClose(); + ASSERT_TRUE(generic_dispatcher); + + transport.End(); + EXPECT_TRUE(dispatcher->HasOneRef()); + dispatcher = NULL; + + ASSERT_EQ(Dispatcher::kTypePlatformHandle, generic_dispatcher->GetType()); + dispatcher = static_cast<PlatformHandleDispatcher*>(generic_dispatcher.get()); + + fp = mojo::test::FILEFromPlatformHandle(dispatcher->PassPlatformHandle(), + "rb").Pass(); + EXPECT_TRUE(fp); + + rewind(fp.get()); + char read_buffer[1000] = {}; + EXPECT_EQ(sizeof(kFooBar), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kFooBar, read_buffer); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/proxy_message_pipe_endpoint.cc b/chromium/mojo/system/proxy_message_pipe_endpoint.cc new file mode 100644 index 00000000000..79074d0222c --- /dev/null +++ b/chromium/mojo/system/proxy_message_pipe_endpoint.cc @@ -0,0 +1,148 @@ +// 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 "mojo/system/proxy_message_pipe_endpoint.h" + +#include <string.h> + +#include "base/logging.h" +#include "mojo/system/channel.h" +#include "mojo/system/local_message_pipe_endpoint.h" +#include "mojo/system/message_pipe_dispatcher.h" + +namespace mojo { +namespace system { + +ProxyMessagePipeEndpoint::ProxyMessagePipeEndpoint() + : local_id_(MessageInTransit::kInvalidEndpointId), + remote_id_(MessageInTransit::kInvalidEndpointId), + is_peer_open_(true) { +} + +ProxyMessagePipeEndpoint::ProxyMessagePipeEndpoint( + LocalMessagePipeEndpoint* local_message_pipe_endpoint, + bool is_peer_open) + : local_id_(MessageInTransit::kInvalidEndpointId), + remote_id_(MessageInTransit::kInvalidEndpointId), + is_peer_open_(is_peer_open), + paused_message_queue_(MessageInTransitQueue::PassContents(), + local_message_pipe_endpoint->message_queue()) { + local_message_pipe_endpoint->Close(); +} + +ProxyMessagePipeEndpoint::~ProxyMessagePipeEndpoint() { + DCHECK(!is_running()); + DCHECK(!is_attached()); + AssertConsistentState(); + DCHECK(paused_message_queue_.IsEmpty()); +} + +MessagePipeEndpoint::Type ProxyMessagePipeEndpoint::GetType() const { + return kTypeProxy; +} + +bool ProxyMessagePipeEndpoint::OnPeerClose() { + DCHECK(is_peer_open_); + + is_peer_open_ = false; + + // If our outgoing message queue isn't empty, we shouldn't be destroyed yet. + if (!paused_message_queue_.IsEmpty()) + return true; + + if (is_attached()) { + if (!is_running()) { + // If we're not running yet, we can't be destroyed yet, because we're + // still waiting for the "run" message from the other side. + return true; + } + + Detach(); + } + + return false; +} + +// Note: We may have to enqueue messages even when our (local) peer isn't open +// -- it may have been written to and closed immediately, before we were ready. +// This case is handled in |Run()| (which will call us). +void ProxyMessagePipeEndpoint::EnqueueMessage( + scoped_ptr<MessageInTransit> message) { + if (is_running()) { + message->SerializeAndCloseDispatchers(channel_.get()); + + message->set_source_id(local_id_); + message->set_destination_id(remote_id_); + if (!channel_->WriteMessage(message.Pass())) + LOG(WARNING) << "Failed to write message to channel"; + } else { + paused_message_queue_.AddMessage(message.Pass()); + } +} + +void ProxyMessagePipeEndpoint::Attach(scoped_refptr<Channel> channel, + MessageInTransit::EndpointId local_id) { + DCHECK(channel); + DCHECK_NE(local_id, MessageInTransit::kInvalidEndpointId); + + DCHECK(!is_attached()); + + AssertConsistentState(); + channel_ = channel; + local_id_ = local_id; + AssertConsistentState(); +} + +bool ProxyMessagePipeEndpoint::Run(MessageInTransit::EndpointId remote_id) { + // Assertions about arguments: + DCHECK_NE(remote_id, MessageInTransit::kInvalidEndpointId); + + // Assertions about current state: + DCHECK(is_attached()); + DCHECK(!is_running()); + + AssertConsistentState(); + remote_id_ = remote_id; + AssertConsistentState(); + + while (!paused_message_queue_.IsEmpty()) + EnqueueMessage(paused_message_queue_.GetMessage()); + + if (is_peer_open_) + return true; // Stay alive. + + // We were just waiting to die. + Detach(); + return false; +} + +void ProxyMessagePipeEndpoint::OnRemove() { + Detach(); +} + +void ProxyMessagePipeEndpoint::Detach() { + DCHECK(is_attached()); + + AssertConsistentState(); + channel_->DetachMessagePipeEndpoint(local_id_, remote_id_); + channel_ = NULL; + local_id_ = MessageInTransit::kInvalidEndpointId; + remote_id_ = MessageInTransit::kInvalidEndpointId; + paused_message_queue_.Clear(); + AssertConsistentState(); +} + +#ifndef NDEBUG +void ProxyMessagePipeEndpoint::AssertConsistentState() const { + if (is_attached()) { + DCHECK_NE(local_id_, MessageInTransit::kInvalidEndpointId); + } else { // Not attached. + DCHECK_EQ(local_id_, MessageInTransit::kInvalidEndpointId); + DCHECK_EQ(remote_id_, MessageInTransit::kInvalidEndpointId); + } +} +#endif + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/proxy_message_pipe_endpoint.h b/chromium/mojo/system/proxy_message_pipe_endpoint.h new file mode 100644 index 00000000000..695967995b1 --- /dev/null +++ b/chromium/mojo/system/proxy_message_pipe_endpoint.h @@ -0,0 +1,104 @@ +// 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 MOJO_SYSTEM_PROXY_MESSAGE_PIPE_ENDPOINT_H_ +#define MOJO_SYSTEM_PROXY_MESSAGE_PIPE_ENDPOINT_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/message_in_transit_queue.h" +#include "mojo/system/message_pipe_endpoint.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Channel; +class LocalMessagePipeEndpoint; +class MessagePipe; + +// A |ProxyMessagePipeEndpoint| connects an end of a |MessagePipe| to a +// |Channel|, over which it transmits and receives data (to/from another +// |ProxyMessagePipeEndpoint|). So a |MessagePipe| with one endpoint local and +// the other endpoint remote consists of a |LocalMessagePipeEndpoint| and a +// |ProxyMessagePipeEndpoint|, with only the local endpoint being accessible via +// a |MessagePipeDispatcher|. +// +// Like any |MessagePipeEndpoint|, a |ProxyMessagePipeEndpoint| is owned by a +// |MessagePipe|. +// - A |ProxyMessagePipeEndpoint| starts out *detached*, i.e., not associated +// to any |Channel|. When *attached*, it gets a reference to a |Channel| and +// is assigned a local ID. A |ProxyMessagePipeEndpoint| must be detached +// before destruction; this is done inside |Close()|. +// - When attached, a |ProxyMessagePipeEndpoint| starts out not running. When +// run, it gets a remote ID. +class MOJO_SYSTEM_IMPL_EXPORT ProxyMessagePipeEndpoint + : public MessagePipeEndpoint { + public: + ProxyMessagePipeEndpoint(); + // Constructs a |ProxyMessagePipeEndpoint| that replaces the given + // |LocalMessagePipeEndpoint| (which this constructor will close), taking its + // message queue's contents. This is done when transferring a message pipe + // handle over a remote message pipe. + ProxyMessagePipeEndpoint( + LocalMessagePipeEndpoint* local_message_pipe_endpoint, + bool is_peer_open); + virtual ~ProxyMessagePipeEndpoint(); + + // |MessagePipeEndpoint| implementation: + virtual Type GetType() const OVERRIDE; + virtual bool OnPeerClose() OVERRIDE; + virtual void EnqueueMessage(scoped_ptr<MessageInTransit> message) OVERRIDE; + virtual void Attach(scoped_refptr<Channel> channel, + MessageInTransit::EndpointId local_id) OVERRIDE; + virtual bool Run(MessageInTransit::EndpointId remote_id) OVERRIDE; + virtual void OnRemove() OVERRIDE; + + private: + void Detach(); + +#ifdef NDEBUG + void AssertConsistentState() const {} +#else + void AssertConsistentState() const; +#endif + + bool is_attached() const { + return !!channel_; + } + + bool is_running() const { + return remote_id_ != MessageInTransit::kInvalidEndpointId; + } + + // This should only be set if we're attached. + scoped_refptr<Channel> channel_; + + // |local_id_| should be set to something other than + // |MessageInTransit::kInvalidEndpointId| when we're attached. + MessageInTransit::EndpointId local_id_; + + // |remote_id_| being set to anything other than + // |MessageInTransit::kInvalidEndpointId| indicates that we're "running", + // i.e., actively able to send messages. We should only ever be running if + // we're attached. + MessageInTransit::EndpointId remote_id_; + + bool is_peer_open_; + + // This queue is only used while we're detached, to store messages while we're + // not ready to send them yet. + MessageInTransitQueue paused_message_queue_; + + DISALLOW_COPY_AND_ASSIGN(ProxyMessagePipeEndpoint); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_PROXY_MESSAGE_PIPE_ENDPOINT_H_ diff --git a/chromium/mojo/system/raw_channel.cc b/chromium/mojo/system/raw_channel.cc new file mode 100644 index 00000000000..f7b8029b49a --- /dev/null +++ b/chromium/mojo/system/raw_channel.cc @@ -0,0 +1,500 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/raw_channel.h" + +#include <string.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/transport_data.h" + +namespace mojo { +namespace system { + +const size_t kReadSize = 4096; + +// RawChannel::ReadBuffer ------------------------------------------------------ + +RawChannel::ReadBuffer::ReadBuffer() + : buffer_(kReadSize), + num_valid_bytes_(0) { +} + +RawChannel::ReadBuffer::~ReadBuffer() { +} + +void RawChannel::ReadBuffer::GetBuffer(char** addr, size_t* size) { + DCHECK_GE(buffer_.size(), num_valid_bytes_ + kReadSize); + *addr = &buffer_[0] + num_valid_bytes_; + *size = kReadSize; +} + +// RawChannel::WriteBuffer ----------------------------------------------------- + +RawChannel::WriteBuffer::WriteBuffer(size_t serialized_platform_handle_size) + : serialized_platform_handle_size_(serialized_platform_handle_size), + platform_handles_offset_(0), + data_offset_(0) { +} + +RawChannel::WriteBuffer::~WriteBuffer() { + STLDeleteElements(&message_queue_); +} + +bool RawChannel::WriteBuffer::HavePlatformHandlesToSend() const { + if (message_queue_.empty()) + return false; + + const TransportData* transport_data = + message_queue_.front()->transport_data(); + if (!transport_data) + return false; + + const embedder::PlatformHandleVector* all_platform_handles = + transport_data->platform_handles(); + if (!all_platform_handles) { + DCHECK_EQ(platform_handles_offset_, 0u); + return false; + } + if (platform_handles_offset_ >= all_platform_handles->size()) { + DCHECK_EQ(platform_handles_offset_, all_platform_handles->size()); + return false; + } + + return true; +} + +void RawChannel::WriteBuffer::GetPlatformHandlesToSend( + size_t* num_platform_handles, + embedder::PlatformHandle** platform_handles, + void** serialization_data) { + DCHECK(HavePlatformHandlesToSend()); + + TransportData* transport_data = message_queue_.front()->transport_data(); + embedder::PlatformHandleVector* all_platform_handles = + transport_data->platform_handles(); + *num_platform_handles = + all_platform_handles->size() - platform_handles_offset_; + *platform_handles = &(*all_platform_handles)[platform_handles_offset_]; + size_t serialization_data_offset = + transport_data->platform_handle_table_offset(); + DCHECK_GT(serialization_data_offset, 0u); + serialization_data_offset += + platform_handles_offset_ * serialized_platform_handle_size_; + *serialization_data = + static_cast<char*>(transport_data->buffer()) + serialization_data_offset; +} + +void RawChannel::WriteBuffer::GetBuffers(std::vector<Buffer>* buffers) const { + buffers->clear(); + + if (message_queue_.empty()) + return; + + MessageInTransit* message = message_queue_.front(); + DCHECK_LT(data_offset_, message->total_size()); + size_t bytes_to_write = message->total_size() - data_offset_; + + size_t transport_data_buffer_size = message->transport_data() ? + message->transport_data()->buffer_size() : 0; + + if (!transport_data_buffer_size) { + // Only write from the main buffer. + DCHECK_LT(data_offset_, message->main_buffer_size()); + DCHECK_LE(bytes_to_write, message->main_buffer_size()); + Buffer buffer = { + static_cast<const char*>(message->main_buffer()) + data_offset_, + bytes_to_write}; + buffers->push_back(buffer); + return; + } + + if (data_offset_ >= message->main_buffer_size()) { + // Only write from the transport data buffer. + DCHECK_LT(data_offset_ - message->main_buffer_size(), + transport_data_buffer_size); + DCHECK_LE(bytes_to_write, transport_data_buffer_size); + Buffer buffer = { + static_cast<const char*>(message->transport_data()->buffer()) + + (data_offset_ - message->main_buffer_size()), + bytes_to_write}; + buffers->push_back(buffer); + return; + } + + // TODO(vtl): We could actually send out buffers from multiple messages, with + // the "stopping" condition being reaching a message with platform handles + // attached. + + // Write from both buffers. + DCHECK_EQ(bytes_to_write, message->main_buffer_size() - data_offset_ + + transport_data_buffer_size); + Buffer buffer1 = { + static_cast<const char*>(message->main_buffer()) + data_offset_, + message->main_buffer_size() - data_offset_ + }; + buffers->push_back(buffer1); + Buffer buffer2 = { + static_cast<const char*>(message->transport_data()->buffer()), + transport_data_buffer_size + }; + buffers->push_back(buffer2); +} + +// RawChannel ------------------------------------------------------------------ + +RawChannel::RawChannel() + : message_loop_for_io_(NULL), + delegate_(NULL), + read_stopped_(false), + write_stopped_(false), + weak_ptr_factory_(this) { +} + +RawChannel::~RawChannel() { + DCHECK(!read_buffer_); + DCHECK(!write_buffer_); + + // No need to take the |write_lock_| here -- if there are still weak pointers + // outstanding, then we're hosed anyway (since we wouldn't be able to + // invalidate them cleanly, since we might not be on the I/O thread). + DCHECK(!weak_ptr_factory_.HasWeakPtrs()); +} + +bool RawChannel::Init(Delegate* delegate) { + DCHECK(delegate); + + DCHECK(!delegate_); + delegate_ = delegate; + + CHECK_EQ(base::MessageLoop::current()->type(), base::MessageLoop::TYPE_IO); + DCHECK(!message_loop_for_io_); + message_loop_for_io_ = + static_cast<base::MessageLoopForIO*>(base::MessageLoop::current()); + + // No need to take the lock. No one should be using us yet. + DCHECK(!read_buffer_); + read_buffer_.reset(new ReadBuffer); + DCHECK(!write_buffer_); + write_buffer_.reset(new WriteBuffer(GetSerializedPlatformHandleSize())); + + if (!OnInit()) { + delegate_ = NULL; + message_loop_for_io_ = NULL; + read_buffer_.reset(); + write_buffer_.reset(); + return false; + } + + if (ScheduleRead() != IO_PENDING) { + // This will notify the delegate about the read failure. Although we're on + // the I/O thread, don't call it in the nested context. + message_loop_for_io_->PostTask( + FROM_HERE, + base::Bind(&RawChannel::OnReadCompleted, weak_ptr_factory_.GetWeakPtr(), + false, 0)); + } + + // ScheduleRead() failure is treated as a read failure (by notifying the + // delegate), not as an init failure. + return true; +} + +void RawChannel::Shutdown() { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_); + + base::AutoLock locker(write_lock_); + + LOG_IF(WARNING, !write_buffer_->message_queue_.empty()) + << "Shutting down RawChannel with write buffer nonempty"; + + // Reset the delegate so that it won't receive further calls. + delegate_ = NULL; + read_stopped_ = true; + write_stopped_ = true; + weak_ptr_factory_.InvalidateWeakPtrs(); + + OnShutdownNoLock(read_buffer_.Pass(), write_buffer_.Pass()); +} + +// Reminder: This must be thread-safe. +bool RawChannel::WriteMessage(scoped_ptr<MessageInTransit> message) { + DCHECK(message); + + base::AutoLock locker(write_lock_); + if (write_stopped_) + return false; + + if (!write_buffer_->message_queue_.empty()) { + EnqueueMessageNoLock(message.Pass()); + return true; + } + + EnqueueMessageNoLock(message.Pass()); + DCHECK_EQ(write_buffer_->data_offset_, 0u); + + size_t platform_handles_written = 0; + size_t bytes_written = 0; + IOResult io_result = WriteNoLock(&platform_handles_written, &bytes_written); + if (io_result == IO_PENDING) + return true; + + bool result = OnWriteCompletedNoLock(io_result == IO_SUCCEEDED, + platform_handles_written, + bytes_written); + if (!result) { + // Even if we're on the I/O thread, don't call |OnFatalError()| in the + // nested context. + message_loop_for_io_->PostTask( + FROM_HERE, + base::Bind(&RawChannel::CallOnFatalError, + weak_ptr_factory_.GetWeakPtr(), + Delegate::FATAL_ERROR_WRITE)); + } + + return result; +} + +// Reminder: This must be thread-safe. +bool RawChannel::IsWriteBufferEmpty() { + base::AutoLock locker(write_lock_); + return write_buffer_->message_queue_.empty(); +} + +void RawChannel::OnReadCompleted(bool result, size_t bytes_read) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_); + + if (read_stopped_) { + NOTREACHED(); + return; + } + + IOResult io_result = result ? IO_SUCCEEDED : IO_FAILED; + + // Keep reading data in a loop, and dispatch messages if enough data is + // received. Exit the loop if any of the following happens: + // - one or more messages were dispatched; + // - the last read failed, was a partial read or would block; + // - |Shutdown()| was called. + do { + if (io_result != IO_SUCCEEDED) { + read_stopped_ = true; + CallOnFatalError(Delegate::FATAL_ERROR_READ); + return; + } + + read_buffer_->num_valid_bytes_ += bytes_read; + + // Dispatch all the messages that we can. + bool did_dispatch_message = false; + // Tracks the offset of the first undispatched message in |read_buffer_|. + // Currently, we copy data to ensure that this is zero at the beginning. + size_t read_buffer_start = 0; + size_t remaining_bytes = read_buffer_->num_valid_bytes_; + size_t message_size; + // Note that we rely on short-circuit evaluation here: + // - |read_buffer_start| may be an invalid index into + // |read_buffer_->buffer_| if |remaining_bytes| is zero. + // - |message_size| is only valid if |GetNextMessageSize()| returns true. + // TODO(vtl): Use |message_size| more intelligently (e.g., to request the + // next read). + // TODO(vtl): Validate that |message_size| is sane. + while (remaining_bytes > 0 && + MessageInTransit::GetNextMessageSize( + &read_buffer_->buffer_[read_buffer_start], remaining_bytes, + &message_size) && + remaining_bytes >= message_size) { + MessageInTransit::View + message_view(message_size, &read_buffer_->buffer_[read_buffer_start]); + DCHECK_EQ(message_view.total_size(), message_size); + + const char* error_message = NULL; + if (!message_view.IsValid(GetSerializedPlatformHandleSize(), + &error_message)) { + DCHECK(error_message); + LOG(WARNING) << "Received invalid message: " << error_message; + read_stopped_ = true; + CallOnFatalError(Delegate::FATAL_ERROR_READ); + return; + } + + if (message_view.type() == MessageInTransit::kTypeRawChannel) { + if (!OnReadMessageForRawChannel(message_view)) { + read_stopped_ = true; + CallOnFatalError(Delegate::FATAL_ERROR_READ); + return; + } + } else { + embedder::ScopedPlatformHandleVectorPtr platform_handles; + if (message_view.transport_data_buffer()) { + size_t num_platform_handles; + const void* platform_handle_table; + TransportData::GetPlatformHandleTable( + message_view.transport_data_buffer(), + &num_platform_handles, + &platform_handle_table); + + if (num_platform_handles > 0) { + platform_handles = + GetReadPlatformHandles(num_platform_handles, + platform_handle_table).Pass(); + if (!platform_handles) { + LOG(WARNING) << "Invalid number of platform handles received"; + read_stopped_ = true; + CallOnFatalError(Delegate::FATAL_ERROR_READ); + return; + } + } + } + + // TODO(vtl): In the case that we aren't expecting any platform handles, + // for the POSIX implementation, we should confirm that none are stored. + + // Dispatch the message. + DCHECK(delegate_); + delegate_->OnReadMessage(message_view, platform_handles.Pass()); + if (read_stopped_) { + // |Shutdown()| was called in |OnReadMessage()|. + // TODO(vtl): Add test for this case. + return; + } + } + + did_dispatch_message = true; + + // Update our state. + read_buffer_start += message_size; + remaining_bytes -= message_size; + } + + if (read_buffer_start > 0) { + // Move data back to start. + read_buffer_->num_valid_bytes_ = remaining_bytes; + if (read_buffer_->num_valid_bytes_ > 0) { + memmove(&read_buffer_->buffer_[0], + &read_buffer_->buffer_[read_buffer_start], remaining_bytes); + } + read_buffer_start = 0; + } + + if (read_buffer_->buffer_.size() - read_buffer_->num_valid_bytes_ < + kReadSize) { + // Use power-of-2 buffer sizes. + // TODO(vtl): Make sure the buffer doesn't get too large (and enforce the + // maximum message size to whatever extent necessary). + // TODO(vtl): We may often be able to peek at the header and get the real + // required extra space (which may be much bigger than |kReadSize|). + size_t new_size = std::max(read_buffer_->buffer_.size(), kReadSize); + while (new_size < read_buffer_->num_valid_bytes_ + kReadSize) + new_size *= 2; + + // TODO(vtl): It's suboptimal to zero out the fresh memory. + read_buffer_->buffer_.resize(new_size, 0); + } + + // (1) If we dispatched any messages, stop reading for now (and let the + // message loop do its thing for another round). + // TODO(vtl): Is this the behavior we want? (Alternatives: i. Dispatch only + // a single message. Risks: slower, more complex if we want to avoid lots of + // copying. ii. Keep reading until there's no more data and dispatch all the + // messages we can. Risks: starvation of other users of the message loop.) + // (2) If we didn't max out |kReadSize|, stop reading for now. + bool schedule_for_later = did_dispatch_message || bytes_read < kReadSize; + bytes_read = 0; + io_result = schedule_for_later ? ScheduleRead() : Read(&bytes_read); + } while (io_result != IO_PENDING); +} + +void RawChannel::OnWriteCompleted(bool result, + size_t platform_handles_written, + size_t bytes_written) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_); + + bool did_fail = false; + { + base::AutoLock locker(write_lock_); + DCHECK_EQ(write_stopped_, write_buffer_->message_queue_.empty()); + + if (write_stopped_) { + NOTREACHED(); + return; + } + + did_fail = !OnWriteCompletedNoLock(result, + platform_handles_written, + bytes_written); + } + + if (did_fail) + CallOnFatalError(Delegate::FATAL_ERROR_WRITE); +} + +void RawChannel::EnqueueMessageNoLock(scoped_ptr<MessageInTransit> message) { + write_lock_.AssertAcquired(); + write_buffer_->message_queue_.push_back(message.release()); +} + +bool RawChannel::OnReadMessageForRawChannel( + const MessageInTransit::View& message_view) { + // No non-implementation specific |RawChannel| control messages. + LOG(ERROR) << "Invalid control message (subtype " << message_view.subtype() + << ")"; + return false; +} + +void RawChannel::CallOnFatalError(Delegate::FatalError fatal_error) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_); + // TODO(vtl): Add a "write_lock_.AssertNotAcquired()"? + if (delegate_) + delegate_->OnFatalError(fatal_error); +} + +bool RawChannel::OnWriteCompletedNoLock(bool result, + size_t platform_handles_written, + size_t bytes_written) { + write_lock_.AssertAcquired(); + + DCHECK(!write_stopped_); + DCHECK(!write_buffer_->message_queue_.empty()); + + if (result) { + write_buffer_->platform_handles_offset_ += platform_handles_written; + write_buffer_->data_offset_ += bytes_written; + + MessageInTransit* message = write_buffer_->message_queue_.front(); + if (write_buffer_->data_offset_ >= message->total_size()) { + // Complete write. + DCHECK_EQ(write_buffer_->data_offset_, message->total_size()); + write_buffer_->message_queue_.pop_front(); + delete message; + write_buffer_->platform_handles_offset_ = 0; + write_buffer_->data_offset_ = 0; + + if (write_buffer_->message_queue_.empty()) + return true; + } + + // Schedule the next write. + IOResult io_result = ScheduleWriteNoLock(); + if (io_result == IO_PENDING) + return true; + DCHECK_EQ(io_result, IO_FAILED); + } + + write_stopped_ = true; + STLDeleteElements(&write_buffer_->message_queue_); + write_buffer_->platform_handles_offset_ = 0; + write_buffer_->data_offset_ = 0; + return false; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_channel.h b/chromium/mojo/system/raw_channel.h new file mode 100644 index 00000000000..965e10903aa --- /dev/null +++ b/chromium/mojo/system/raw_channel.h @@ -0,0 +1,315 @@ +// 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 MOJO_SYSTEM_RAW_CHANNEL_H_ +#define MOJO_SYSTEM_RAW_CHANNEL_H_ + +#include <deque> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/synchronization/lock.h" +#include "mojo/embedder/platform_handle_vector.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/constants.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/system_impl_export.h" + +namespace base { +class MessageLoopForIO; +} + +namespace mojo { +namespace system { + +// |RawChannel| is an interface and base class for objects that wrap an OS +// "pipe". It presents the following interface to users: +// - Receives and dispatches messages on an I/O thread (running a +// |MessageLoopForIO|. +// - Provides a thread-safe way of writing messages (|WriteMessage()|); +// writing/queueing messages will not block and is atomic from the point of +// view of the caller. If necessary, messages are queued (to be written on +// the aforementioned thread). +// +// OS-specific implementation subclasses are to be instantiated using the +// |Create()| static factory method. +// +// With the exception of |WriteMessage()|, this class is thread-unsafe (and in +// general its methods should only be used on the I/O thread, i.e., the thread +// on which |Init()| is called). +class MOJO_SYSTEM_IMPL_EXPORT RawChannel { + public: + virtual ~RawChannel(); + + // The |Delegate| is only accessed on the same thread as the message loop + // (passed in on creation). + class MOJO_SYSTEM_IMPL_EXPORT Delegate { + public: + enum FatalError { + FATAL_ERROR_READ = 0, + FATAL_ERROR_WRITE + }; + + // Called when a message is read. This may call |Shutdown()| (on the + // |RawChannel|), but must not destroy it. + virtual void OnReadMessage( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) = 0; + + // Called when there's a fatal error, which leads to the channel no longer + // being viable. This may call |Shutdown()| (on the |RawChannel()|), but + // must not destroy it. + // + // For each raw channel, at most one |FATAL_ERROR_READ| and at most one + // |FATAL_ERROR_WRITE| notification will be issued (both may be issued). + // After a |OnFatalError(FATAL_ERROR_READ)|, there will be no further calls + // to |OnReadMessage()|. + virtual void OnFatalError(FatalError fatal_error) = 0; + + protected: + virtual ~Delegate() {} + }; + + // Static factory method. |handle| should be a handle to a + // (platform-appropriate) bidirectional communication channel (e.g., a socket + // on POSIX, a named pipe on Windows). + static scoped_ptr<RawChannel> Create(embedder::ScopedPlatformHandle handle); + + // This must be called (on an I/O thread) before this object is used. Does + // *not* take ownership of |delegate|. Both the I/O thread and |delegate| must + // remain alive until |Shutdown()| is called (unless this fails); |delegate| + // will no longer be used after |Shutdown()|. Returns true on success. On + // failure, |Shutdown()| should *not* be called. + bool Init(Delegate* delegate); + + // This must be called (on the I/O thread) before this object is destroyed. + void Shutdown(); + + // Writes the given message (or schedules it to be written). |message| must + // have no |Dispatcher|s still attached (i.e., + // |SerializeAndCloseDispatchers()| should have been called). This method is + // thread-safe and may be called from any thread. Returns true on success. + bool WriteMessage(scoped_ptr<MessageInTransit> message); + + // Returns true if the write buffer is empty (i.e., all messages written using + // |WriteMessage()| have actually been sent. + // TODO(vtl): We should really also notify our delegate when the write buffer + // becomes empty (or something like that). + bool IsWriteBufferEmpty(); + + // Returns the amount of space needed in the |MessageInTransit|'s + // |TransportData|'s "platform handle table" per platform handle (to be + // attached to a message). (This amount may be zero.) + virtual size_t GetSerializedPlatformHandleSize() const = 0; + + protected: + // Return values of |[Schedule]Read()| and |[Schedule]WriteNoLock()|. + enum IOResult { + IO_SUCCEEDED, + IO_FAILED, + IO_PENDING + }; + + class MOJO_SYSTEM_IMPL_EXPORT ReadBuffer { + public: + ReadBuffer(); + ~ReadBuffer(); + + void GetBuffer(char** addr, size_t* size); + + private: + friend class RawChannel; + + // We store data from |[Schedule]Read()|s in |buffer_|. The start of + // |buffer_| is always aligned with a message boundary (we will copy memory + // to ensure this), but |buffer_| may be larger than the actual number of + // bytes we have. + std::vector<char> buffer_; + size_t num_valid_bytes_; + + DISALLOW_COPY_AND_ASSIGN(ReadBuffer); + }; + + class MOJO_SYSTEM_IMPL_EXPORT WriteBuffer { + public: + struct Buffer { + const char* addr; + size_t size; + }; + + explicit WriteBuffer(size_t serialized_platform_handle_size); + ~WriteBuffer(); + + // Returns true if there are (more) platform handles to be sent (from the + // front of |message_queue_|). + bool HavePlatformHandlesToSend() const; + // Gets platform handles to be sent (from the front of |message_queue_|). + // This should only be called if |HavePlatformHandlesToSend()| returned + // true. There are two components to this: the actual |PlatformHandle|s + // (which should be closed once sent) and any additional serialization + // information (which will be embedded in the message's data; there are + // |GetSerializedPlatformHandleSize()| bytes per handle). Once all platform + // handles have been sent, the message data should be written next (see + // |GetBuffers()|). + void GetPlatformHandlesToSend(size_t* num_platform_handles, + embedder::PlatformHandle** platform_handles, + void** serialization_data); + + // Gets buffers to be written. These buffers will always come from the front + // of |message_queue_|. Once they are completely written, the front + // |MessageInTransit| should be popped (and destroyed); this is done in + // |OnWriteCompletedNoLock()|. + void GetBuffers(std::vector<Buffer>* buffers) const; + + private: + friend class RawChannel; + + const size_t serialized_platform_handle_size_; + + // TODO(vtl): When C++11 is available, switch this to a deque of + // |scoped_ptr|/|unique_ptr|s. + std::deque<MessageInTransit*> message_queue_; + // Platform handles are sent before the message data, but doing so may + // require several passes. |platform_handles_offset_| indicates the position + // in the first message's vector of platform handles to send next. + size_t platform_handles_offset_; + // The first message's data may have been partially sent. |data_offset_| + // indicates the position in the first message's data to start the next + // write. + size_t data_offset_; + + DISALLOW_COPY_AND_ASSIGN(WriteBuffer); + }; + + RawChannel(); + + // Must be called on the I/O thread WITHOUT |write_lock_| held. + void OnReadCompleted(bool result, size_t bytes_read); + // Must be called on the I/O thread WITHOUT |write_lock_| held. + void OnWriteCompleted(bool result, + size_t platform_handles_written, + size_t bytes_written); + + base::MessageLoopForIO* message_loop_for_io() { return message_loop_for_io_; } + base::Lock& write_lock() { return write_lock_; } + + // Should only be called on the I/O thread. + ReadBuffer* read_buffer() { return read_buffer_.get(); } + + // Only called under |write_lock_|. + WriteBuffer* write_buffer_no_lock() { + write_lock_.AssertAcquired(); + return write_buffer_.get(); + } + + // Adds |message| to the write message queue. Implementation subclasses may + // override this to add any additional "control" messages needed. This is + // called (on any thread) with |write_lock_| held. + virtual void EnqueueMessageNoLock(scoped_ptr<MessageInTransit> message); + + // Handles any control messages targeted to the |RawChannel| (or + // implementation subclass). Implementation subclasses may override this to + // handle any implementation-specific control messages, but should call + // |RawChannel::OnReadMessageForRawChannel()| for any remaining messages. + // Returns true on success and false on error (e.g., invalid control message). + // This is only called on the I/O thread. + virtual bool OnReadMessageForRawChannel( + const MessageInTransit::View& message_view); + + // Reads into |read_buffer()|. + // This class guarantees that: + // - the area indicated by |GetBuffer()| will stay valid until read completion + // (but please also see the comments for |OnShutdownNoLock()|); + // - a second read is not started if there is a pending read; + // - the method is called on the I/O thread WITHOUT |write_lock_| held. + // + // The implementing subclass must guarantee that: + // - |bytes_read| is untouched unless |Read()| returns |IO_SUCCEEDED|; + // - if the method returns |IO_PENDING|, |OnReadCompleted()| will be called on + // the I/O thread to report the result, unless |Shutdown()| is called. + virtual IOResult Read(size_t* bytes_read) = 0; + // Similar to |Read()|, except that the implementing subclass must also + // guarantee that the method doesn't succeed synchronously, i.e., it only + // returns |IO_FAILED| or |IO_PENDING|. + virtual IOResult ScheduleRead() = 0; + + // Called by |OnReadCompleted()| to get the platform handles associated with + // the given platform handle table (from a message). This should only be + // called when |num_platform_handles| is nonzero. Returns null if the + // |num_platform_handles| handles are not available. Only called on the I/O + // thread (without |write_lock_| held). + virtual embedder::ScopedPlatformHandleVectorPtr GetReadPlatformHandles( + size_t num_platform_handles, + const void* platform_handle_table) = 0; + + // Writes contents in |write_buffer_no_lock()|. + // This class guarantees that: + // - the |PlatformHandle|s given by |GetPlatformHandlesToSend()| and the + // buffer(s) given by |GetBuffers()| will remain valid until write + // completion (see also the comments for |OnShutdownNoLock()|); + // - a second write is not started if there is a pending write; + // - the method is called under |write_lock_|. + // + // The implementing subclass must guarantee that: + // - |platform_handles_written| and |bytes_written| are untouched unless + // |WriteNoLock()| returns |IO_SUCCEEDED|; + // - if the method returns |IO_PENDING|, |OnWriteCompleted()| will be called + // on the I/O thread to report the result, unless |Shutdown()| is called. + virtual IOResult WriteNoLock(size_t* platform_handles_written, + size_t* bytes_written) = 0; + // Similar to |WriteNoLock()|, except that the implementing subclass must also + // guarantee that the method doesn't succeed synchronously, i.e., it only + // returns |IO_FAILED| or |IO_PENDING|. + virtual IOResult ScheduleWriteNoLock() = 0; + + // Must be called on the I/O thread WITHOUT |write_lock_| held. + virtual bool OnInit() = 0; + // On shutdown, passes the ownership of the buffers to subclasses, which may + // want to preserve them if there are pending read/write. Must be called on + // the I/O thread under |write_lock_|. + virtual void OnShutdownNoLock( + scoped_ptr<ReadBuffer> read_buffer, + scoped_ptr<WriteBuffer> write_buffer) = 0; + + private: + // Calls |delegate_->OnFatalError(fatal_error)|. Must be called on the I/O + // thread WITHOUT |write_lock_| held. + void CallOnFatalError(Delegate::FatalError fatal_error); + + // If |result| is true, updates the write buffer and schedules a write + // operation to run later if there are more contents to write. If |result| is + // false or any error occurs during the method execution, cancels pending + // writes and returns false. + // Must be called only if |write_stopped_| is false and under |write_lock_|. + bool OnWriteCompletedNoLock(bool result, + size_t platform_handles_written, + size_t bytes_written); + + // Set in |Init()| and never changed (hence usable on any thread without + // locking): + base::MessageLoopForIO* message_loop_for_io_; + + // Only used on the I/O thread: + Delegate* delegate_; + bool read_stopped_; + scoped_ptr<ReadBuffer> read_buffer_; + + base::Lock write_lock_; // Protects the following members. + bool write_stopped_; + scoped_ptr<WriteBuffer> write_buffer_; + + // This is used for posting tasks from write threads to the I/O thread. It + // must only be accessed under |write_lock_|. The weak pointers it produces + // are only used/invalidated on the I/O thread. + base::WeakPtrFactory<RawChannel> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(RawChannel); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_RAW_CHANNEL_H_ diff --git a/chromium/mojo/system/raw_channel_posix.cc b/chromium/mojo/system/raw_channel_posix.cc new file mode 100644 index 00000000000..ba23599f8f7 --- /dev/null +++ b/chromium/mojo/system/raw_channel_posix.cc @@ -0,0 +1,469 @@ +// 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 "mojo/system/raw_channel.h" + +#include <errno.h> +#include <sys/uio.h> +#include <unistd.h> + +#include <algorithm> +#include <deque> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "mojo/embedder/platform_channel_utils_posix.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/platform_handle_vector.h" +#include "mojo/system/transport_data.h" + +namespace mojo { +namespace system { + +namespace { + +class RawChannelPosix : public RawChannel, + public base::MessageLoopForIO::Watcher { + public: + explicit RawChannelPosix(embedder::ScopedPlatformHandle handle); + virtual ~RawChannelPosix(); + + // |RawChannel| public methods: + virtual size_t GetSerializedPlatformHandleSize() const OVERRIDE; + + private: + // |RawChannel| protected methods: + // Actually override this so that we can send multiple messages with (only) + // FDs if necessary. + virtual void EnqueueMessageNoLock( + scoped_ptr<MessageInTransit> message) OVERRIDE; + // Override this to handle those extra FD-only messages. + virtual bool OnReadMessageForRawChannel( + const MessageInTransit::View& message_view) OVERRIDE; + virtual IOResult Read(size_t* bytes_read) OVERRIDE; + virtual IOResult ScheduleRead() OVERRIDE; + virtual embedder::ScopedPlatformHandleVectorPtr GetReadPlatformHandles( + size_t num_platform_handles, + const void* platform_handle_table) OVERRIDE; + virtual IOResult WriteNoLock(size_t* platform_handles_written, + size_t* bytes_written) OVERRIDE; + virtual IOResult ScheduleWriteNoLock() OVERRIDE; + virtual bool OnInit() OVERRIDE; + virtual void OnShutdownNoLock( + scoped_ptr<ReadBuffer> read_buffer, + scoped_ptr<WriteBuffer> write_buffer) OVERRIDE; + + // |base::MessageLoopForIO::Watcher| implementation: + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + // Watches for |fd_| to become writable. Must be called on the I/O thread. + void WaitToWrite(); + + embedder::ScopedPlatformHandle fd_; + + // The following members are only used on the I/O thread: + scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> read_watcher_; + scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> write_watcher_; + + bool pending_read_; + + std::deque<embedder::PlatformHandle> read_platform_handles_; + + // The following members are used on multiple threads and protected by + // |write_lock()|: + bool pending_write_; + + // This is used for posting tasks from write threads to the I/O thread. It + // must only be accessed under |write_lock_|. The weak pointers it produces + // are only used/invalidated on the I/O thread. + base::WeakPtrFactory<RawChannelPosix> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(RawChannelPosix); +}; + +RawChannelPosix::RawChannelPosix(embedder::ScopedPlatformHandle handle) + : fd_(handle.Pass()), + pending_read_(false), + pending_write_(false), + weak_ptr_factory_(this) { + DCHECK(fd_.is_valid()); +} + +RawChannelPosix::~RawChannelPosix() { + DCHECK(!pending_read_); + DCHECK(!pending_write_); + + // No need to take the |write_lock()| here -- if there are still weak pointers + // outstanding, then we're hosed anyway (since we wouldn't be able to + // invalidate them cleanly, since we might not be on the I/O thread). + DCHECK(!weak_ptr_factory_.HasWeakPtrs()); + + // These must have been shut down/destroyed on the I/O thread. + DCHECK(!read_watcher_); + DCHECK(!write_watcher_); + + embedder::CloseAllPlatformHandles(&read_platform_handles_); +} + +size_t RawChannelPosix::GetSerializedPlatformHandleSize() const { + // We don't actually need any space on POSIX (since we just send FDs). + return 0; +} + +void RawChannelPosix::EnqueueMessageNoLock( + scoped_ptr<MessageInTransit> message) { + if (message->transport_data()) { + embedder::PlatformHandleVector* const platform_handles = + message->transport_data()->platform_handles(); + if (platform_handles && + platform_handles->size() > embedder::kPlatformChannelMaxNumHandles) { + // We can't attach all the FDs to a single message, so we have to "split" + // the message. Send as many control messages as needed first with FDs + // attached (and no data). + size_t i = 0; + for (; platform_handles->size() - i > + embedder::kPlatformChannelMaxNumHandles; + i += embedder::kPlatformChannelMaxNumHandles) { + scoped_ptr<MessageInTransit> fd_message( + new MessageInTransit( + MessageInTransit::kTypeRawChannel, + MessageInTransit::kSubtypeRawChannelPosixExtraPlatformHandles, + 0, + NULL)); + embedder::ScopedPlatformHandleVectorPtr fds( + new embedder::PlatformHandleVector( + platform_handles->begin() + i, + platform_handles->begin() + i + + embedder::kPlatformChannelMaxNumHandles)); + fd_message->SetTransportData( + make_scoped_ptr(new TransportData(fds.Pass()))); + RawChannel::EnqueueMessageNoLock(fd_message.Pass()); + } + + // Remove the handles that we "moved" into the other messages. + platform_handles->erase(platform_handles->begin(), + platform_handles->begin() + i); + } + } + + RawChannel::EnqueueMessageNoLock(message.Pass()); +} + +bool RawChannelPosix::OnReadMessageForRawChannel( + const MessageInTransit::View& message_view) { + DCHECK_EQ(message_view.type(), MessageInTransit::kTypeRawChannel); + + if (message_view.subtype() == + MessageInTransit::kSubtypeRawChannelPosixExtraPlatformHandles) { + // We don't need to do anything. |RawChannel| won't extract the platform + // handles, and they'll be accumulated in |Read()|. + return true; + } + + return RawChannel::OnReadMessageForRawChannel(message_view); +} + +RawChannel::IOResult RawChannelPosix::Read(size_t* bytes_read) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + DCHECK(!pending_read_); + + char* buffer = NULL; + size_t bytes_to_read = 0; + read_buffer()->GetBuffer(&buffer, &bytes_to_read); + + size_t old_num_platform_handles = read_platform_handles_.size(); + ssize_t read_result = + embedder::PlatformChannelRecvmsg(fd_.get(), + buffer, + bytes_to_read, + &read_platform_handles_); + if (read_platform_handles_.size() > old_num_platform_handles) { + DCHECK_LE(read_platform_handles_.size() - old_num_platform_handles, + embedder::kPlatformChannelMaxNumHandles); + + // We should never accumulate more than |TransportData::kMaxPlatformHandles + // + embedder::kPlatformChannelMaxNumHandles| handles. (The latter part is + // possible because we could have accumulated all the handles for a message, + // then received the message data plus the first set of handles for the next + // message in the subsequent |recvmsg()|.) + if (read_platform_handles_.size() > (TransportData::kMaxPlatformHandles + + embedder::kPlatformChannelMaxNumHandles)) { + LOG(WARNING) << "Received too many platform handles"; + embedder::CloseAllPlatformHandles(&read_platform_handles_); + read_platform_handles_.clear(); + return IO_FAILED; + } + } + + if (read_result > 0) { + *bytes_read = static_cast<size_t>(read_result); + return IO_SUCCEEDED; + } + + // |read_result == 0| means "end of file". + if (read_result == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { + PLOG_IF(WARNING, read_result != 0) << "recvmsg"; + + // Make sure that |OnFileCanReadWithoutBlocking()| won't be called again. + read_watcher_.reset(); + + return IO_FAILED; + } + + return ScheduleRead(); +} + +RawChannel::IOResult RawChannelPosix::ScheduleRead() { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + DCHECK(!pending_read_); + + pending_read_ = true; + + return IO_PENDING; +} + +embedder::ScopedPlatformHandleVectorPtr RawChannelPosix::GetReadPlatformHandles( + size_t num_platform_handles, + const void* /*platform_handle_table*/) { + DCHECK_GT(num_platform_handles, 0u); + + if (read_platform_handles_.size() < num_platform_handles) { + embedder::CloseAllPlatformHandles(&read_platform_handles_); + read_platform_handles_.clear(); + return embedder::ScopedPlatformHandleVectorPtr(); + } + + embedder::ScopedPlatformHandleVectorPtr rv( + new embedder::PlatformHandleVector(num_platform_handles)); + rv->assign(read_platform_handles_.begin(), + read_platform_handles_.begin() + num_platform_handles); + read_platform_handles_.erase( + read_platform_handles_.begin(), + read_platform_handles_.begin() + num_platform_handles); + return rv.Pass(); +} + +RawChannel::IOResult RawChannelPosix::WriteNoLock( + size_t* platform_handles_written, + size_t* bytes_written) { + write_lock().AssertAcquired(); + + DCHECK(!pending_write_); + + size_t num_platform_handles = 0; + ssize_t write_result; + if (write_buffer_no_lock()->HavePlatformHandlesToSend()) { + embedder::PlatformHandle* platform_handles; + void* serialization_data; // Actually unused. + write_buffer_no_lock()->GetPlatformHandlesToSend(&num_platform_handles, + &platform_handles, + &serialization_data); + DCHECK_GT(num_platform_handles, 0u); + DCHECK_LE(num_platform_handles, embedder::kPlatformChannelMaxNumHandles); + DCHECK(platform_handles); + + // TODO(vtl): Reduce code duplication. (This is duplicated from below.) + std::vector<WriteBuffer::Buffer> buffers; + write_buffer_no_lock()->GetBuffers(&buffers); + DCHECK(!buffers.empty()); + const size_t kMaxBufferCount = 10; + iovec iov[kMaxBufferCount]; + size_t buffer_count = std::min(buffers.size(), kMaxBufferCount); + for (size_t i = 0; i < buffer_count; ++i) { + iov[i].iov_base = const_cast<char*>(buffers[i].addr); + iov[i].iov_len = buffers[i].size; + } + + write_result = embedder::PlatformChannelSendmsgWithHandles( + fd_.get(), iov, buffer_count, platform_handles, num_platform_handles); + for (size_t i = 0; i < num_platform_handles; i++) + platform_handles[i].CloseIfNecessary(); + } else { + std::vector<WriteBuffer::Buffer> buffers; + write_buffer_no_lock()->GetBuffers(&buffers); + DCHECK(!buffers.empty()); + + if (buffers.size() == 1) { + write_result = embedder::PlatformChannelWrite(fd_.get(), buffers[0].addr, + buffers[0].size); + } else { + const size_t kMaxBufferCount = 10; + iovec iov[kMaxBufferCount]; + size_t buffer_count = std::min(buffers.size(), kMaxBufferCount); + for (size_t i = 0; i < buffer_count; ++i) { + iov[i].iov_base = const_cast<char*>(buffers[i].addr); + iov[i].iov_len = buffers[i].size; + } + + write_result = embedder::PlatformChannelWritev(fd_.get(), iov, + buffer_count); + } + } + + if (write_result >= 0) { + *platform_handles_written = num_platform_handles; + *bytes_written = static_cast<size_t>(write_result); + return IO_SUCCEEDED; + } + + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(ERROR) << "sendmsg/write/writev"; + return IO_FAILED; + } + + return ScheduleWriteNoLock(); +} + +RawChannel::IOResult RawChannelPosix::ScheduleWriteNoLock() { + write_lock().AssertAcquired(); + + DCHECK(!pending_write_); + + // Set up to wait for the FD to become writable. + // If we're not on the I/O thread, we have to post a task to do this. + if (base::MessageLoop::current() != message_loop_for_io()) { + message_loop_for_io()->PostTask( + FROM_HERE, + base::Bind(&RawChannelPosix::WaitToWrite, + weak_ptr_factory_.GetWeakPtr())); + pending_write_ = true; + return IO_PENDING; + } + + if (message_loop_for_io()->WatchFileDescriptor(fd_.get().fd, false, + base::MessageLoopForIO::WATCH_WRITE, write_watcher_.get(), this)) { + pending_write_ = true; + return IO_PENDING; + } + + return IO_FAILED; +} + +bool RawChannelPosix::OnInit() { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + + DCHECK(!read_watcher_); + read_watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher()); + DCHECK(!write_watcher_); + write_watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher()); + + if (!message_loop_for_io()->WatchFileDescriptor(fd_.get().fd, true, + base::MessageLoopForIO::WATCH_READ, read_watcher_.get(), this)) { + // TODO(vtl): I'm not sure |WatchFileDescriptor()| actually fails cleanly + // (in the sense of returning the message loop's state to what it was before + // it was called). + read_watcher_.reset(); + write_watcher_.reset(); + return false; + } + + return true; +} + +void RawChannelPosix::OnShutdownNoLock( + scoped_ptr<ReadBuffer> /*read_buffer*/, + scoped_ptr<WriteBuffer> /*write_buffer*/) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + write_lock().AssertAcquired(); + + read_watcher_.reset(); // This will stop watching (if necessary). + write_watcher_.reset(); // This will stop watching (if necessary). + + pending_read_ = false; + pending_write_ = false; + + DCHECK(fd_.is_valid()); + fd_.reset(); + + weak_ptr_factory_.InvalidateWeakPtrs(); +} + +void RawChannelPosix::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(fd, fd_.get().fd); + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + + if (!pending_read_) { + NOTREACHED(); + return; + } + + pending_read_ = false; + size_t bytes_read = 0; + IOResult result = Read(&bytes_read); + if (result != IO_PENDING) + OnReadCompleted(result == IO_SUCCEEDED, bytes_read); + + // On failure, |read_watcher_| must have been reset; on success, + // we assume that |OnReadCompleted()| always schedules another read. + // Otherwise, we could end up spinning -- getting + // |OnFileCanReadWithoutBlocking()| again and again but not doing any actual + // read. + // TODO(yzshen): An alternative is to stop watching if RawChannel doesn't + // schedule a new read. But that code won't be reached under the current + // RawChannel implementation. + DCHECK(!read_watcher_ || pending_read_); +} + +void RawChannelPosix::OnFileCanWriteWithoutBlocking(int fd) { + DCHECK_EQ(fd, fd_.get().fd); + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + + IOResult result = IO_FAILED; + size_t platform_handles_written = 0; + size_t bytes_written = 0; + { + base::AutoLock locker(write_lock()); + + DCHECK(pending_write_); + + pending_write_ = false; + result = WriteNoLock(&platform_handles_written, &bytes_written); + } + + if (result != IO_PENDING) { + OnWriteCompleted(result == IO_SUCCEEDED, + platform_handles_written, + bytes_written); + } +} + +void RawChannelPosix::WaitToWrite() { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + + DCHECK(write_watcher_); + + if (!message_loop_for_io()->WatchFileDescriptor( + fd_.get().fd, false, base::MessageLoopForIO::WATCH_WRITE, + write_watcher_.get(), this)) { + { + base::AutoLock locker(write_lock()); + + DCHECK(pending_write_); + pending_write_ = false; + } + OnWriteCompleted(false, 0, 0); + } +} + +} // namespace + +// ----------------------------------------------------------------------------- + +// Static factory method declared in raw_channel.h. +// static +scoped_ptr<RawChannel> RawChannel::Create( + embedder::ScopedPlatformHandle handle) { + return scoped_ptr<RawChannel>(new RawChannelPosix(handle.Pass())); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_channel_unittest.cc b/chromium/mojo/system/raw_channel_unittest.cc new file mode 100644 index 00000000000..2a386833e8a --- /dev/null +++ b/chromium/mojo/system/raw_channel_unittest.cc @@ -0,0 +1,689 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/raw_channel.h" + +#include <stdint.h> + +#include <vector> + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/rand_util.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "mojo/common/test/test_utils.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/message_in_transit.h" +#include "mojo/system/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +scoped_ptr<MessageInTransit> MakeTestMessage(uint32_t num_bytes) { + std::vector<unsigned char> bytes(num_bytes, 0); + for (size_t i = 0; i < num_bytes; i++) + bytes[i] = static_cast<unsigned char>(i + num_bytes); + return make_scoped_ptr( + new MessageInTransit(MessageInTransit::kTypeMessagePipeEndpoint, + MessageInTransit::kSubtypeMessagePipeEndpointData, + num_bytes, bytes.empty() ? NULL : &bytes[0])); +} + +bool CheckMessageData(const void* bytes, uint32_t num_bytes) { + const unsigned char* b = static_cast<const unsigned char*>(bytes); + for (uint32_t i = 0; i < num_bytes; i++) { + if (b[i] != static_cast<unsigned char>(i + num_bytes)) + return false; + } + return true; +} + +void InitOnIOThread(RawChannel* raw_channel, RawChannel::Delegate* delegate) { + CHECK(raw_channel->Init(delegate)); +} + +bool WriteTestMessageToHandle(const embedder::PlatformHandle& handle, + uint32_t num_bytes) { + scoped_ptr<MessageInTransit> message(MakeTestMessage(num_bytes)); + + size_t write_size = 0; + mojo::test::BlockingWrite( + handle, message->main_buffer(), message->main_buffer_size(), &write_size); + return write_size == message->main_buffer_size(); +} + +// ----------------------------------------------------------------------------- + +class RawChannelTest : public testing::Test { + public: + RawChannelTest() : io_thread_(test::TestIOThread::kManualStart) {} + virtual ~RawChannelTest() {} + + virtual void SetUp() OVERRIDE { + embedder::PlatformChannelPair channel_pair; + handles[0] = channel_pair.PassServerHandle(); + handles[1] = channel_pair.PassClientHandle(); + io_thread_.Start(); + } + + virtual void TearDown() OVERRIDE { + io_thread_.Stop(); + handles[0].reset(); + handles[1].reset(); + } + + protected: + test::TestIOThread* io_thread() { return &io_thread_; } + + embedder::ScopedPlatformHandle handles[2]; + + private: + test::TestIOThread io_thread_; + + DISALLOW_COPY_AND_ASSIGN(RawChannelTest); +}; + +// RawChannelTest.WriteMessage ------------------------------------------------- + +class WriteOnlyRawChannelDelegate : public RawChannel::Delegate { + public: + WriteOnlyRawChannelDelegate() {} + virtual ~WriteOnlyRawChannelDelegate() {} + + // |RawChannel::Delegate| implementation: + virtual void OnReadMessage( + const MessageInTransit::View& /*message_view*/, + embedder::ScopedPlatformHandleVectorPtr /*platform_handles*/) OVERRIDE { + CHECK(false); // Should not get called. + } + virtual void OnFatalError(FatalError fatal_error) OVERRIDE { + // We'll get a read error when the connection is closed. + CHECK_EQ(fatal_error, FATAL_ERROR_READ); + } + + private: + DISALLOW_COPY_AND_ASSIGN(WriteOnlyRawChannelDelegate); +}; + +static const int64_t kMessageReaderSleepMs = 1; +static const size_t kMessageReaderMaxPollIterations = 3000; + +class TestMessageReaderAndChecker { + public: + explicit TestMessageReaderAndChecker(embedder::PlatformHandle handle) + : handle_(handle) {} + ~TestMessageReaderAndChecker() { CHECK(bytes_.empty()); } + + bool ReadAndCheckNextMessage(uint32_t expected_size) { + unsigned char buffer[4096]; + + for (size_t i = 0; i < kMessageReaderMaxPollIterations;) { + size_t read_size = 0; + CHECK(mojo::test::NonBlockingRead(handle_, buffer, sizeof(buffer), + &read_size)); + + // Append newly-read data to |bytes_|. + bytes_.insert(bytes_.end(), buffer, buffer + read_size); + + // If we have the header.... + size_t message_size; + if (MessageInTransit::GetNextMessageSize( + bytes_.empty() ? NULL : &bytes_[0], + bytes_.size(), + &message_size)) { + // If we've read the whole message.... + if (bytes_.size() >= message_size) { + bool rv = true; + MessageInTransit::View message_view(message_size, &bytes_[0]); + CHECK_EQ(message_view.main_buffer_size(), message_size); + + if (message_view.num_bytes() != expected_size) { + LOG(ERROR) << "Wrong size: " << message_size << " instead of " + << expected_size << " bytes."; + rv = false; + } else if (!CheckMessageData(message_view.bytes(), + message_view.num_bytes())) { + LOG(ERROR) << "Incorrect message bytes."; + rv = false; + } + + // Erase message data. + bytes_.erase(bytes_.begin(), + bytes_.begin() + + message_view.main_buffer_size()); + return rv; + } + } + + if (static_cast<size_t>(read_size) < sizeof(buffer)) { + i++; + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds(kMessageReaderSleepMs)); + } + } + + LOG(ERROR) << "Too many iterations."; + return false; + } + + private: + const embedder::PlatformHandle handle_; + + // The start of the received data should always be on a message boundary. + std::vector<unsigned char> bytes_; + + DISALLOW_COPY_AND_ASSIGN(TestMessageReaderAndChecker); +}; + +// Tests writing (and verifies reading using our own custom reader). +TEST_F(RawChannelTest, WriteMessage) { + WriteOnlyRawChannelDelegate delegate; + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + TestMessageReaderAndChecker checker(handles[1].get()); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + // Write and read, for a variety of sizes. + for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) { + EXPECT_TRUE(rc->WriteMessage(MakeTestMessage(size))); + EXPECT_TRUE(checker.ReadAndCheckNextMessage(size)) << size; + } + + // Write/queue and read afterwards, for a variety of sizes. + for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) + EXPECT_TRUE(rc->WriteMessage(MakeTestMessage(size))); + for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) + EXPECT_TRUE(checker.ReadAndCheckNextMessage(size)) << size; + + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(rc.get()))); +} + +// RawChannelTest.OnReadMessage ------------------------------------------------ + +class ReadCheckerRawChannelDelegate : public RawChannel::Delegate { + public: + ReadCheckerRawChannelDelegate() + : done_event_(false, false), + position_(0) {} + virtual ~ReadCheckerRawChannelDelegate() {} + + // |RawChannel::Delegate| implementation (called on the I/O thread): + virtual void OnReadMessage( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) OVERRIDE { + EXPECT_FALSE(platform_handles); + + size_t position; + size_t expected_size; + bool should_signal = false; + { + base::AutoLock locker(lock_); + CHECK_LT(position_, expected_sizes_.size()); + position = position_; + expected_size = expected_sizes_[position]; + position_++; + if (position_ >= expected_sizes_.size()) + should_signal = true; + } + + EXPECT_EQ(expected_size, message_view.num_bytes()) << position; + if (message_view.num_bytes() == expected_size) { + EXPECT_TRUE(CheckMessageData(message_view.bytes(), + message_view.num_bytes())) << position; + } + + if (should_signal) + done_event_.Signal(); + } + virtual void OnFatalError(FatalError fatal_error) OVERRIDE { + // We'll get a read error when the connection is closed. + CHECK_EQ(fatal_error, FATAL_ERROR_READ); + } + + // Waits for all the messages (of sizes |expected_sizes_|) to be seen. + void Wait() { + done_event_.Wait(); + } + + void SetExpectedSizes(const std::vector<uint32_t>& expected_sizes) { + base::AutoLock locker(lock_); + CHECK_EQ(position_, expected_sizes_.size()); + expected_sizes_ = expected_sizes; + position_ = 0; + } + + private: + base::WaitableEvent done_event_; + + base::Lock lock_; // Protects the following members. + std::vector<uint32_t> expected_sizes_; + size_t position_; + + DISALLOW_COPY_AND_ASSIGN(ReadCheckerRawChannelDelegate); +}; + +// Tests reading (writing using our own custom writer). +TEST_F(RawChannelTest, OnReadMessage) { + ReadCheckerRawChannelDelegate delegate; + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + // Write and read, for a variety of sizes. + for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) { + delegate.SetExpectedSizes(std::vector<uint32_t>(1, size)); + + EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), size)); + + delegate.Wait(); + } + + // Set up reader and write as fast as we can. + // Write/queue and read afterwards, for a variety of sizes. + std::vector<uint32_t> expected_sizes; + for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) + expected_sizes.push_back(size); + delegate.SetExpectedSizes(expected_sizes); + for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) + EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), size)); + delegate.Wait(); + + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(rc.get()))); +} + +// RawChannelTest.WriteMessageAndOnReadMessage --------------------------------- + +class RawChannelWriterThread : public base::SimpleThread { + public: + RawChannelWriterThread(RawChannel* raw_channel, size_t write_count) + : base::SimpleThread("raw_channel_writer_thread"), + raw_channel_(raw_channel), + left_to_write_(write_count) { + } + + virtual ~RawChannelWriterThread() { + Join(); + } + + private: + virtual void Run() OVERRIDE { + static const int kMaxRandomMessageSize = 25000; + + while (left_to_write_-- > 0) { + EXPECT_TRUE(raw_channel_->WriteMessage(MakeTestMessage( + static_cast<uint32_t>(base::RandInt(1, kMaxRandomMessageSize))))); + } + } + + RawChannel* const raw_channel_; + size_t left_to_write_; + + DISALLOW_COPY_AND_ASSIGN(RawChannelWriterThread); +}; + +class ReadCountdownRawChannelDelegate : public RawChannel::Delegate { + public: + explicit ReadCountdownRawChannelDelegate(size_t expected_count) + : done_event_(false, false), + expected_count_(expected_count), + count_(0) {} + virtual ~ReadCountdownRawChannelDelegate() {} + + // |RawChannel::Delegate| implementation (called on the I/O thread): + virtual void OnReadMessage( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) OVERRIDE { + EXPECT_FALSE(platform_handles); + + EXPECT_LT(count_, expected_count_); + count_++; + + EXPECT_TRUE(CheckMessageData(message_view.bytes(), + message_view.num_bytes())); + + if (count_ >= expected_count_) + done_event_.Signal(); + } + virtual void OnFatalError(FatalError fatal_error) OVERRIDE { + // We'll get a read error when the connection is closed. + CHECK_EQ(fatal_error, FATAL_ERROR_READ); + } + + // Waits for all the messages to have been seen. + void Wait() { + done_event_.Wait(); + } + + private: + base::WaitableEvent done_event_; + size_t expected_count_; + size_t count_; + + DISALLOW_COPY_AND_ASSIGN(ReadCountdownRawChannelDelegate); +}; + +TEST_F(RawChannelTest, WriteMessageAndOnReadMessage) { + static const size_t kNumWriterThreads = 10; + static const size_t kNumWriteMessagesPerThread = 4000; + + WriteOnlyRawChannelDelegate writer_delegate; + scoped_ptr<RawChannel> writer_rc(RawChannel::Create(handles[0].Pass())); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, writer_rc.get(), + base::Unretained(&writer_delegate))); + + ReadCountdownRawChannelDelegate reader_delegate( + kNumWriterThreads * kNumWriteMessagesPerThread); + scoped_ptr<RawChannel> reader_rc(RawChannel::Create(handles[1].Pass())); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, reader_rc.get(), + base::Unretained(&reader_delegate))); + + { + ScopedVector<RawChannelWriterThread> writer_threads; + for (size_t i = 0; i < kNumWriterThreads; i++) { + writer_threads.push_back(new RawChannelWriterThread( + writer_rc.get(), kNumWriteMessagesPerThread)); + } + for (size_t i = 0; i < writer_threads.size(); i++) + writer_threads[i]->Start(); + } // Joins all the writer threads. + + // Sleep a bit, to let any extraneous reads be processed. (There shouldn't be + // any, but we want to know about them.) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + + // Wait for reading to finish. + reader_delegate.Wait(); + + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(reader_rc.get()))); + + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(writer_rc.get()))); +} + +// RawChannelTest.OnFatalError ------------------------------------------------- + +class FatalErrorRecordingRawChannelDelegate + : public ReadCountdownRawChannelDelegate { + public: + FatalErrorRecordingRawChannelDelegate(size_t expected_read_count, + bool expect_read_error, + bool expect_write_error) + : ReadCountdownRawChannelDelegate(expected_read_count), + got_read_fatal_error_event_(false, false), + got_write_fatal_error_event_(false, false), + expecting_read_error_(expect_read_error), + expecting_write_error_(expect_write_error) { + } + + virtual ~FatalErrorRecordingRawChannelDelegate() {} + + virtual void OnFatalError(FatalError fatal_error) OVERRIDE { + switch (fatal_error) { + case FATAL_ERROR_READ: + ASSERT_TRUE(expecting_read_error_); + expecting_read_error_ = false; + got_read_fatal_error_event_.Signal(); + break; + case FATAL_ERROR_WRITE: + ASSERT_TRUE(expecting_write_error_); + expecting_write_error_ = false; + got_write_fatal_error_event_.Signal(); + break; + } + } + + void WaitForReadFatalError() { got_read_fatal_error_event_.Wait(); } + void WaitForWriteFatalError() { got_write_fatal_error_event_.Wait(); } + + private: + base::WaitableEvent got_read_fatal_error_event_; + base::WaitableEvent got_write_fatal_error_event_; + + bool expecting_read_error_; + bool expecting_write_error_; + + DISALLOW_COPY_AND_ASSIGN(FatalErrorRecordingRawChannelDelegate); +}; + +// Tests fatal errors. +TEST_F(RawChannelTest, OnFatalError) { + FatalErrorRecordingRawChannelDelegate delegate(0, true, true); + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + // Close the handle of the other end, which should make writing fail. + handles[1].reset(); + + EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1))); + + // We should get a write fatal error. + delegate.WaitForWriteFatalError(); + + // We should also get a read fatal error. + delegate.WaitForReadFatalError(); + + EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(2))); + + // Sleep a bit, to make sure we don't get another |OnFatalError()| + // notification. (If we actually get another one, |OnFatalError()| crashes.) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); + + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(rc.get()))); +} + +// RawChannelTest.ReadUnaffectedByWriteFatalError ------------------------------ + +TEST_F(RawChannelTest, ReadUnaffectedByWriteFatalError) { + const size_t kMessageCount = 5; + + // Write a few messages into the other end. + uint32_t message_size = 1; + for (size_t i = 0; i < kMessageCount; + i++, message_size += message_size / 2 + 1) + EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), message_size)); + + // Close the other end, which should make writing fail. + handles[1].reset(); + + // Only start up reading here. The system buffer should still contain the + // messages that were written. + FatalErrorRecordingRawChannelDelegate delegate(kMessageCount, true, true); + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1))); + + // We should definitely get a write fatal error. + delegate.WaitForWriteFatalError(); + + // Wait for reading to finish. A writing failure shouldn't affect reading. + delegate.Wait(); + + // And then we should get a read fatal error. + delegate.WaitForReadFatalError(); + + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(rc.get()))); +} + +// RawChannelTest.WriteMessageAfterShutdown ------------------------------------ + +// Makes sure that calling |WriteMessage()| after |Shutdown()| behaves +// correctly. +TEST_F(RawChannelTest, WriteMessageAfterShutdown) { + WriteOnlyRawChannelDelegate delegate; + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&RawChannel::Shutdown, + base::Unretained(rc.get()))); + + EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1))); +} + +// RawChannelTest.ShutdownOnReadMessage ---------------------------------------- + +class ShutdownOnReadMessageRawChannelDelegate : public RawChannel::Delegate { + public: + explicit ShutdownOnReadMessageRawChannelDelegate(RawChannel* raw_channel) + : raw_channel_(raw_channel), + done_event_(false, false), + did_shutdown_(false) {} + virtual ~ShutdownOnReadMessageRawChannelDelegate() {} + + // |RawChannel::Delegate| implementation (called on the I/O thread): + virtual void OnReadMessage( + const MessageInTransit::View& message_view, + embedder::ScopedPlatformHandleVectorPtr platform_handles) OVERRIDE { + EXPECT_FALSE(platform_handles); + EXPECT_FALSE(did_shutdown_); + EXPECT_TRUE(CheckMessageData(message_view.bytes(), + message_view.num_bytes())); + raw_channel_->Shutdown(); + did_shutdown_ = true; + done_event_.Signal(); + } + virtual void OnFatalError(FatalError /*fatal_error*/) OVERRIDE { + CHECK(false); // Should not get called. + } + + // Waits for shutdown. + void Wait() { + done_event_.Wait(); + EXPECT_TRUE(did_shutdown_); + } + + private: + RawChannel* const raw_channel_; + base::WaitableEvent done_event_; + bool did_shutdown_; + + DISALLOW_COPY_AND_ASSIGN(ShutdownOnReadMessageRawChannelDelegate); +}; + +TEST_F(RawChannelTest, ShutdownOnReadMessage) { + // Write a few messages into the other end. + for (size_t count = 0; count < 5; count++) + EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), 10)); + + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + ShutdownOnReadMessageRawChannelDelegate delegate(rc.get()); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + // Wait for the delegate, which will shut the |RawChannel| down. + delegate.Wait(); +} + +// RawChannelTest.ShutdownOnFatalError{Read, Write} ---------------------------- + +class ShutdownOnFatalErrorRawChannelDelegate : public RawChannel::Delegate { + public: + ShutdownOnFatalErrorRawChannelDelegate(RawChannel* raw_channel, + FatalError shutdown_on_error_type) + : raw_channel_(raw_channel), + shutdown_on_error_type_(shutdown_on_error_type), + done_event_(false, false), + did_shutdown_(false) {} + virtual ~ShutdownOnFatalErrorRawChannelDelegate() {} + + // |RawChannel::Delegate| implementation (called on the I/O thread): + virtual void OnReadMessage( + const MessageInTransit::View& /*message_view*/, + embedder::ScopedPlatformHandleVectorPtr /*platform_handles*/) OVERRIDE { + CHECK(false); // Should not get called. + } + virtual void OnFatalError(FatalError fatal_error) OVERRIDE { + EXPECT_FALSE(did_shutdown_); + if (fatal_error != shutdown_on_error_type_) + return; + raw_channel_->Shutdown(); + did_shutdown_ = true; + done_event_.Signal(); + } + + // Waits for shutdown. + void Wait() { + done_event_.Wait(); + EXPECT_TRUE(did_shutdown_); + } + + private: + RawChannel* const raw_channel_; + const FatalError shutdown_on_error_type_; + base::WaitableEvent done_event_; + bool did_shutdown_; + + DISALLOW_COPY_AND_ASSIGN(ShutdownOnFatalErrorRawChannelDelegate); +}; + +TEST_F(RawChannelTest, ShutdownOnFatalErrorRead) { + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + ShutdownOnFatalErrorRawChannelDelegate delegate( + rc.get(), RawChannel::Delegate::FATAL_ERROR_READ); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + // Close the handle of the other end, which should stuff fail. + handles[1].reset(); + + // Wait for the delegate, which will shut the |RawChannel| down. + delegate.Wait(); +} + +TEST_F(RawChannelTest, ShutdownOnFatalErrorWrite) { + scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass())); + ShutdownOnFatalErrorRawChannelDelegate delegate( + rc.get(), RawChannel::Delegate::FATAL_ERROR_WRITE); + io_thread()->PostTaskAndWait(FROM_HERE, + base::Bind(&InitOnIOThread, rc.get(), + base::Unretained(&delegate))); + + // Close the handle of the other end, which should stuff fail. + handles[1].reset(); + + EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1))); + + // Wait for the delegate, which will shut the |RawChannel| down. + delegate.Wait(); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_channel_win.cc b/chromium/mojo/system/raw_channel_win.cc new file mode 100644 index 00000000000..005a344907f --- /dev/null +++ b/chromium/mojo/system/raw_channel_win.cc @@ -0,0 +1,568 @@ +// 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 "mojo/system/raw_channel.h" + +#include <windows.h> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/win/windows_version.h" +#include "mojo/embedder/platform_handle.h" + +namespace mojo { +namespace system { + +namespace { + +class VistaOrHigherFunctions { + public: + VistaOrHigherFunctions(); + + bool is_vista_or_higher() const { return is_vista_or_higher_; } + + BOOL SetFileCompletionNotificationModes(HANDLE handle, UCHAR flags) { + return set_file_completion_notification_modes_(handle, flags); + } + + BOOL CancelIoEx(HANDLE handle, LPOVERLAPPED overlapped) { + return cancel_io_ex_(handle, overlapped); + } + + private: + typedef BOOL (WINAPI *SetFileCompletionNotificationModesFunc)(HANDLE, UCHAR); + typedef BOOL (WINAPI *CancelIoExFunc)(HANDLE, LPOVERLAPPED); + + bool is_vista_or_higher_; + SetFileCompletionNotificationModesFunc + set_file_completion_notification_modes_; + CancelIoExFunc cancel_io_ex_; +}; + +VistaOrHigherFunctions::VistaOrHigherFunctions() + : is_vista_or_higher_(base::win::GetVersion() >= base::win::VERSION_VISTA), + set_file_completion_notification_modes_(NULL), + cancel_io_ex_(NULL) { + if (!is_vista_or_higher_) + return; + + HMODULE module = GetModuleHandleW(L"kernel32.dll"); + set_file_completion_notification_modes_ = + reinterpret_cast<SetFileCompletionNotificationModesFunc>( + GetProcAddress(module, "SetFileCompletionNotificationModes")); + DCHECK(set_file_completion_notification_modes_); + + cancel_io_ex_ = reinterpret_cast<CancelIoExFunc>( + GetProcAddress(module, "CancelIoEx")); + DCHECK(cancel_io_ex_); +} + +base::LazyInstance<VistaOrHigherFunctions> g_vista_or_higher_functions = + LAZY_INSTANCE_INITIALIZER; + +class RawChannelWin : public RawChannel { + public: + RawChannelWin(embedder::ScopedPlatformHandle handle); + virtual ~RawChannelWin(); + + // |RawChannel| public methods: + virtual size_t GetSerializedPlatformHandleSize() const OVERRIDE; + + private: + // RawChannelIOHandler receives OS notifications for I/O completion. It must + // be created on the I/O thread. + // + // It manages its own destruction. Destruction happens on the I/O thread when + // all the following conditions are satisfied: + // - |DetachFromOwnerNoLock()| has been called; + // - there is no pending read; + // - there is no pending write. + class RawChannelIOHandler : public base::MessageLoopForIO::IOHandler { + public: + RawChannelIOHandler(RawChannelWin* owner, + embedder::ScopedPlatformHandle handle); + + HANDLE handle() const { return handle_.get().handle; } + + // The following methods are only called by the owner on the I/O thread. + bool pending_read() const; + base::MessageLoopForIO::IOContext* read_context(); + // Instructs the object to wait for an |OnIOCompleted()| notification. + void OnPendingReadStarted(); + + // The following methods are only called by the owner under + // |owner_->write_lock()|. + bool pending_write_no_lock() const; + base::MessageLoopForIO::IOContext* write_context_no_lock(); + // Instructs the object to wait for an |OnIOCompleted()| notification. + void OnPendingWriteStartedNoLock(); + + // |base::MessageLoopForIO::IOHandler| implementation: + // Must be called on the I/O thread. It could be called before or after + // detached from the owner. + virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context, + DWORD bytes_transferred, + DWORD error) OVERRIDE; + + // Must be called on the I/O thread under |owner_->write_lock()|. + // After this call, the owner must not make any further calls on this + // object, and therefore the object is used on the I/O thread exclusively + // (if it stays alive). + void DetachFromOwnerNoLock(scoped_ptr<ReadBuffer> read_buffer, + scoped_ptr<WriteBuffer> write_buffer); + + private: + virtual ~RawChannelIOHandler(); + + // Returns true if |owner_| has been reset and there is not pending read or + // write. + // Must be called on the I/O thread. + bool ShouldSelfDestruct() const; + + // Must be called on the I/O thread. It may be called before or after + // detaching from the owner. + void OnReadCompleted(DWORD bytes_read, DWORD error); + // Must be called on the I/O thread. It may be called before or after + // detaching from the owner. + void OnWriteCompleted(DWORD bytes_written, DWORD error); + + embedder::ScopedPlatformHandle handle_; + + // |owner_| is reset on the I/O thread under |owner_->write_lock()|. + // Therefore, it may be used on any thread under lock; or on the I/O thread + // without locking. + RawChannelWin* owner_; + + // The following members must be used on the I/O thread. + scoped_ptr<ReadBuffer> preserved_read_buffer_after_detach_; + scoped_ptr<WriteBuffer> preserved_write_buffer_after_detach_; + bool suppress_self_destruct_; + + bool pending_read_; + base::MessageLoopForIO::IOContext read_context_; + + // The following members must be used under |owner_->write_lock()| while the + // object is still attached to the owner, and only on the I/O thread + // afterwards. + bool pending_write_; + base::MessageLoopForIO::IOContext write_context_; + + DISALLOW_COPY_AND_ASSIGN(RawChannelIOHandler); + }; + + // |RawChannel| private methods: + virtual IOResult Read(size_t* bytes_read) OVERRIDE; + virtual IOResult ScheduleRead() OVERRIDE; + virtual embedder::ScopedPlatformHandleVectorPtr GetReadPlatformHandles( + size_t num_platform_handles, + const void* platform_handle_table) OVERRIDE; + virtual IOResult WriteNoLock(size_t* platform_handles_written, + size_t* bytes_written) OVERRIDE; + virtual IOResult ScheduleWriteNoLock() OVERRIDE; + virtual bool OnInit() OVERRIDE; + virtual void OnShutdownNoLock( + scoped_ptr<ReadBuffer> read_buffer, + scoped_ptr<WriteBuffer> write_buffer) OVERRIDE; + + // Passed to |io_handler_| during initialization. + embedder::ScopedPlatformHandle handle_; + + RawChannelIOHandler* io_handler_; + + const bool skip_completion_port_on_success_; + + DISALLOW_COPY_AND_ASSIGN(RawChannelWin); +}; + +RawChannelWin::RawChannelIOHandler::RawChannelIOHandler( + RawChannelWin* owner, + embedder::ScopedPlatformHandle handle) : handle_(handle.Pass()), + owner_(owner), + suppress_self_destruct_(false), + pending_read_(false), + pending_write_(false) { + memset(&read_context_.overlapped, 0, sizeof(read_context_.overlapped)); + read_context_.handler = this; + memset(&write_context_.overlapped, 0, sizeof(write_context_.overlapped)); + write_context_.handler = this; + + owner_->message_loop_for_io()->RegisterIOHandler(handle_.get().handle, this); +} + +RawChannelWin::RawChannelIOHandler::~RawChannelIOHandler() { + DCHECK(ShouldSelfDestruct()); +} + +bool RawChannelWin::RawChannelIOHandler::pending_read() const { + DCHECK(owner_); + DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io()); + return pending_read_; +} + +base::MessageLoopForIO::IOContext* + RawChannelWin::RawChannelIOHandler::read_context() { + DCHECK(owner_); + DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io()); + return &read_context_; +} + +void RawChannelWin::RawChannelIOHandler::OnPendingReadStarted() { + DCHECK(owner_); + DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io()); + DCHECK(!pending_read_); + pending_read_ = true; +} + +bool RawChannelWin::RawChannelIOHandler::pending_write_no_lock() const { + DCHECK(owner_); + owner_->write_lock().AssertAcquired(); + return pending_write_; +} + +base::MessageLoopForIO::IOContext* + RawChannelWin::RawChannelIOHandler::write_context_no_lock() { + DCHECK(owner_); + owner_->write_lock().AssertAcquired(); + return &write_context_; +} + +void RawChannelWin::RawChannelIOHandler::OnPendingWriteStartedNoLock() { + DCHECK(owner_); + owner_->write_lock().AssertAcquired(); + DCHECK(!pending_write_); + pending_write_ = true; +} + +void RawChannelWin::RawChannelIOHandler::OnIOCompleted( + base::MessageLoopForIO::IOContext* context, + DWORD bytes_transferred, + DWORD error) { + DCHECK(!owner_ || + base::MessageLoop::current() == owner_->message_loop_for_io()); + + { + // Suppress self-destruction inside |OnReadCompleted()|, etc. (in case they + // result in a call to |Shutdown()|). + base::AutoReset<bool> resetter(&suppress_self_destruct_, true); + + if (context == &read_context_) + OnReadCompleted(bytes_transferred, error); + else if (context == &write_context_) + OnWriteCompleted(bytes_transferred, error); + else + NOTREACHED(); + } + + if (ShouldSelfDestruct()) + delete this; +} + +void RawChannelWin::RawChannelIOHandler::DetachFromOwnerNoLock( + scoped_ptr<ReadBuffer> read_buffer, + scoped_ptr<WriteBuffer> write_buffer) { + DCHECK(owner_); + DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io()); + owner_->write_lock().AssertAcquired(); + + // If read/write is pending, we have to retain the corresponding buffer. + if (pending_read_) + preserved_read_buffer_after_detach_ = read_buffer.Pass(); + if (pending_write_) + preserved_write_buffer_after_detach_ = write_buffer.Pass(); + + owner_ = NULL; + if (ShouldSelfDestruct()) + delete this; +} + +bool RawChannelWin::RawChannelIOHandler::ShouldSelfDestruct() const { + if (owner_ || suppress_self_destruct_) + return false; + + // Note: Detached, hence no lock needed for |pending_write_|. + return !pending_read_ && !pending_write_; +} + +void RawChannelWin::RawChannelIOHandler::OnReadCompleted(DWORD bytes_read, + DWORD error) { + DCHECK(!owner_ || + base::MessageLoop::current() == owner_->message_loop_for_io()); + DCHECK(suppress_self_destruct_); + + CHECK(pending_read_); + pending_read_ = false; + if (!owner_) + return; + + if (error != ERROR_SUCCESS) { + DCHECK_EQ(bytes_read, 0u); + LOG_IF(ERROR, error != ERROR_BROKEN_PIPE) + << "ReadFile: " << logging::SystemErrorCodeToString(error); + owner_->OnReadCompleted(false, 0); + } else { + DCHECK_GT(bytes_read, 0u); + owner_->OnReadCompleted(true, bytes_read); + } +} + +void RawChannelWin::RawChannelIOHandler::OnWriteCompleted(DWORD bytes_written, + DWORD error) { + DCHECK(!owner_ || + base::MessageLoop::current() == owner_->message_loop_for_io()); + DCHECK(suppress_self_destruct_); + + if (!owner_) { + // No lock needed. + CHECK(pending_write_); + pending_write_ = false; + return; + } + + { + base::AutoLock locker(owner_->write_lock()); + CHECK(pending_write_); + pending_write_ = false; + } + + if (error != ERROR_SUCCESS) { + LOG(ERROR) << "WriteFile: " << logging::SystemErrorCodeToString(error); + owner_->OnWriteCompleted(false, 0, 0); + } else { + owner_->OnWriteCompleted(true, 0, bytes_written); + } +} + +RawChannelWin::RawChannelWin(embedder::ScopedPlatformHandle handle) + : handle_(handle.Pass()), + io_handler_(NULL), + skip_completion_port_on_success_( + g_vista_or_higher_functions.Get().is_vista_or_higher()) { + DCHECK(handle_.is_valid()); +} + +RawChannelWin::~RawChannelWin() { + DCHECK(!io_handler_); +} + +size_t RawChannelWin::GetSerializedPlatformHandleSize() const { + // TODO(vtl): Implement. + return 0; +} + +RawChannel::IOResult RawChannelWin::Read(size_t* bytes_read) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + DCHECK(io_handler_); + DCHECK(!io_handler_->pending_read()); + + char* buffer = NULL; + size_t bytes_to_read = 0; + read_buffer()->GetBuffer(&buffer, &bytes_to_read); + + DWORD bytes_read_dword = 0; + BOOL result = ReadFile(io_handler_->handle(), + buffer, + static_cast<DWORD>(bytes_to_read), + &bytes_read_dword, + &io_handler_->read_context()->overlapped); + if (!result) { + DCHECK_EQ(bytes_read_dword, 0u); + DWORD error = GetLastError(); + if (error != ERROR_IO_PENDING) { + LOG_IF(ERROR, error != ERROR_BROKEN_PIPE) + << "ReadFile: " << logging::SystemErrorCodeToString(error); + return IO_FAILED; + } + } + + if (result && skip_completion_port_on_success_) { + *bytes_read = bytes_read_dword; + return IO_SUCCEEDED; + } + + // If the read is pending or the read has succeeded but we don't skip + // completion port on success, instruct |io_handler_| to wait for the + // completion packet. + // + // TODO(yzshen): It seems there isn't document saying that all error cases + // (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion + // packet. If we do get one for errors, |RawChannelIOHandler::OnIOCompleted()| + // will crash so we will learn about it. + + io_handler_->OnPendingReadStarted(); + return IO_PENDING; +} + +RawChannel::IOResult RawChannelWin::ScheduleRead() { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + DCHECK(io_handler_); + DCHECK(!io_handler_->pending_read()); + + size_t bytes_read = 0; + IOResult io_result = Read(&bytes_read); + if (io_result == IO_SUCCEEDED) { + DCHECK(skip_completion_port_on_success_); + + // We have finished reading successfully. Queue a notification manually. + io_handler_->OnPendingReadStarted(); + // |io_handler_| won't go away before the task is run, so it is safe to use + // |base::Unretained()|. + message_loop_for_io()->PostTask( + FROM_HERE, + base::Bind(&RawChannelIOHandler::OnIOCompleted, + base::Unretained(io_handler_), + base::Unretained(io_handler_->read_context()), + static_cast<DWORD>(bytes_read), + ERROR_SUCCESS)); + return IO_PENDING; + } + + return io_result; +} + +embedder::ScopedPlatformHandleVectorPtr RawChannelWin::GetReadPlatformHandles( + size_t num_platform_handles, + const void* platform_handle_table) { + // TODO(vtl): Implement. + NOTIMPLEMENTED(); + return embedder::ScopedPlatformHandleVectorPtr(); +} + +RawChannel::IOResult RawChannelWin::WriteNoLock( + size_t* platform_handles_written, + size_t* bytes_written) { + write_lock().AssertAcquired(); + + DCHECK(io_handler_); + DCHECK(!io_handler_->pending_write_no_lock()); + + if (write_buffer_no_lock()->HavePlatformHandlesToSend()) { + // TODO(vtl): Implement. + NOTIMPLEMENTED(); + } + + std::vector<WriteBuffer::Buffer> buffers; + write_buffer_no_lock()->GetBuffers(&buffers); + DCHECK(!buffers.empty()); + + // TODO(yzshen): Handle multi-segment writes more efficiently. + DWORD bytes_written_dword = 0; + BOOL result = WriteFile(io_handler_->handle(), + buffers[0].addr, + static_cast<DWORD>(buffers[0].size), + &bytes_written_dword, + &io_handler_->write_context_no_lock()->overlapped); + if (!result && GetLastError() != ERROR_IO_PENDING) { + PLOG(ERROR) << "WriteFile"; + return IO_FAILED; + } + + if (result && skip_completion_port_on_success_) { + *platform_handles_written = 0; + *bytes_written = bytes_written_dword; + return IO_SUCCEEDED; + } + + // If the write is pending or the write has succeeded but we don't skip + // completion port on success, instruct |io_handler_| to wait for the + // completion packet. + // + // TODO(yzshen): it seems there isn't document saying that all error cases + // (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion + // packet. If we do get one for errors, |RawChannelIOHandler::OnIOCompleted()| + // will crash so we will learn about it. + + io_handler_->OnPendingWriteStartedNoLock(); + return IO_PENDING; +} + +RawChannel::IOResult RawChannelWin::ScheduleWriteNoLock() { + write_lock().AssertAcquired(); + + DCHECK(io_handler_); + DCHECK(!io_handler_->pending_write_no_lock()); + + // TODO(vtl): Do something with |platform_handles_written|. + size_t platform_handles_written = 0; + size_t bytes_written = 0; + IOResult io_result = WriteNoLock(&platform_handles_written, &bytes_written); + if (io_result == IO_SUCCEEDED) { + DCHECK(skip_completion_port_on_success_); + + // We have finished writing successfully. Queue a notification manually. + io_handler_->OnPendingWriteStartedNoLock(); + // |io_handler_| won't go away before that task is run, so it is safe to use + // |base::Unretained()|. + message_loop_for_io()->PostTask( + FROM_HERE, + base::Bind(&RawChannelIOHandler::OnIOCompleted, + base::Unretained(io_handler_), + base::Unretained(io_handler_->write_context_no_lock()), + static_cast<DWORD>(bytes_written), + ERROR_SUCCESS)); + return IO_PENDING; + } + + return io_result; +} + +bool RawChannelWin::OnInit() { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + + DCHECK(handle_.is_valid()); + if (skip_completion_port_on_success_ && + !g_vista_or_higher_functions.Get().SetFileCompletionNotificationModes( + handle_.get().handle, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)) { + return false; + } + + DCHECK(!io_handler_); + io_handler_ = new RawChannelIOHandler(this, handle_.Pass()); + + return true; +} + +void RawChannelWin::OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer, + scoped_ptr<WriteBuffer> write_buffer) { + DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io()); + DCHECK(io_handler_); + + write_lock().AssertAcquired(); + + if (io_handler_->pending_read() || io_handler_->pending_write_no_lock()) { + // |io_handler_| will be alive until pending read/write (if any) completes. + // Call |CancelIoEx()| or |CancelIo()| so that resources can be freed up as + // soon as possible. + // Note: |CancelIo()| only cancels read/write requests started from this + // thread. + if (g_vista_or_higher_functions.Get().is_vista_or_higher()) + g_vista_or_higher_functions.Get().CancelIoEx(io_handler_->handle(), NULL); + else + CancelIo(io_handler_->handle()); + } + + io_handler_->DetachFromOwnerNoLock(read_buffer.Pass(), write_buffer.Pass()); + io_handler_ = NULL; +} + +} // namespace + +// ----------------------------------------------------------------------------- + +// Static factory method declared in raw_channel.h. +// static +scoped_ptr<RawChannel> RawChannel::Create( + embedder::ScopedPlatformHandle handle) { + return scoped_ptr<RawChannel>(new RawChannelWin(handle.Pass())); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_shared_buffer.cc b/chromium/mojo/system/raw_shared_buffer.cc new file mode 100644 index 00000000000..e6e3ebce239 --- /dev/null +++ b/chromium/mojo/system/raw_shared_buffer.cc @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/raw_shared_buffer.h" + +#include "base/logging.h" +#include "mojo/embedder/platform_handle_utils.h" + +namespace mojo { +namespace system { + +// static +RawSharedBuffer* RawSharedBuffer::Create(size_t num_bytes) { + DCHECK_GT(num_bytes, 0u); + + RawSharedBuffer* rv = new RawSharedBuffer(num_bytes); + if (!rv->Init()) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr<RawSharedBuffer> deleter(rv); + return NULL; + } + + return rv; +} + +RawSharedBuffer* RawSharedBuffer::CreateFromPlatformHandle( + size_t num_bytes, + embedder::ScopedPlatformHandle platform_handle) { + DCHECK_GT(num_bytes, 0u); + + RawSharedBuffer* rv = new RawSharedBuffer(num_bytes); + if (!rv->InitFromPlatformHandle(platform_handle.Pass())) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr<RawSharedBuffer> deleter(rv); + return NULL; + } + + return rv; +} + +scoped_ptr<RawSharedBufferMapping> RawSharedBuffer::Map(size_t offset, + size_t length) { + if (!IsValidMap(offset, length)) + return scoped_ptr<RawSharedBufferMapping>(); + + return MapNoCheck(offset, length); +} + +bool RawSharedBuffer::IsValidMap(size_t offset, size_t length) { + if (offset > num_bytes_ || length == 0) + return false; + + // Note: This is an overflow-safe check of |offset + length > num_bytes_| + // (that |num_bytes >= offset| is verified above). + if (length > num_bytes_ - offset) + return false; + + return true; +} + +scoped_ptr<RawSharedBufferMapping> RawSharedBuffer::MapNoCheck(size_t offset, + size_t length) { + DCHECK(IsValidMap(offset, length)); + return MapImpl(offset, length); +} + +embedder::ScopedPlatformHandle RawSharedBuffer::DuplicatePlatformHandle() { + return embedder::DuplicatePlatformHandle(handle_.get()); +} + +embedder::ScopedPlatformHandle RawSharedBuffer::PassPlatformHandle() { + DCHECK(HasOneRef()); + return handle_.Pass(); +} + +RawSharedBuffer::RawSharedBuffer(size_t num_bytes) : num_bytes_(num_bytes) { +} + +RawSharedBuffer::~RawSharedBuffer() { +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_shared_buffer.h b/chromium/mojo/system/raw_shared_buffer.h new file mode 100644 index 00000000000..d38acb841c6 --- /dev/null +++ b/chromium/mojo/system/raw_shared_buffer.h @@ -0,0 +1,137 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SYSTEM_RAW_SHARED_BUFFER_H_ +#define MOJO_SYSTEM_RAW_SHARED_BUFFER_H_ + +#include <stddef.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class RawSharedBufferMapping; + +// |RawSharedBuffer| is a thread-safe, ref-counted wrapper around OS-specific +// shared memory. It has the following features: +// - A |RawSharedBuffer| simply represents a piece of shared memory that *may* +// be mapped and *may* be shared to another process. +// - A single |RawSharedBuffer| may be mapped multiple times. The lifetime of +// the mapping (owned by |RawSharedBufferMapping|) is separate from the +// lifetime of the |RawSharedBuffer|. +// - Sizes/offsets (of the shared memory and mappings) are arbitrary, and not +// restricted by page size. However, more memory may actually be mapped than +// requested. +// +// It currently does NOT support the following: +// - Sharing read-only. (This will probably eventually be supported.) +// +// TODO(vtl): Rectify this with |base::SharedMemory|. +class MOJO_SYSTEM_IMPL_EXPORT RawSharedBuffer + : public base::RefCountedThreadSafe<RawSharedBuffer> { + public: + // Creates a shared buffer of size |num_bytes| bytes (initially zero-filled). + // |num_bytes| must be nonzero. Returns null on failure. + static RawSharedBuffer* Create(size_t num_bytes); + + static RawSharedBuffer* CreateFromPlatformHandle( + size_t num_bytes, + embedder::ScopedPlatformHandle platform_handle); + + // Maps (some) of the shared buffer into memory; [|offset|, |offset + length|] + // must be contained in [0, |num_bytes|], and |length| must be at least 1. + // Returns null on failure. + scoped_ptr<RawSharedBufferMapping> Map(size_t offset, size_t length); + + // Checks if |offset| and |length| are valid arguments. + bool IsValidMap(size_t offset, size_t length); + + // Like |Map()|, but doesn't check its arguments (which should have been + // preflighted using |IsValidMap()|). + scoped_ptr<RawSharedBufferMapping> MapNoCheck(size_t offset, size_t length); + + // Duplicates the underlying platform handle and passes it to the caller. + embedder::ScopedPlatformHandle DuplicatePlatformHandle(); + + // Passes the underlying platform handle to the caller. This should only be + // called if there's a unique reference to this object (owned by the caller). + // After calling this, this object should no longer be used, but should only + // be disposed of. + embedder::ScopedPlatformHandle PassPlatformHandle(); + + size_t num_bytes() const { return num_bytes_; } + + private: + friend class base::RefCountedThreadSafe<RawSharedBuffer>; + + explicit RawSharedBuffer(size_t num_bytes); + ~RawSharedBuffer(); + + // Implemented in raw_shared_buffer_{posix,win}.cc: + + // This is called by |Create()| before this object is given to anyone. + bool Init(); + + // This is like |Init()|, but for |CreateFromPlatformHandle()|. (Note: It + // should verify that |platform_handle| is an appropriate handle for the + // claimed |num_bytes_|.) + bool InitFromPlatformHandle(embedder::ScopedPlatformHandle platform_handle); + + // The platform-dependent part of |Map()|; doesn't check arguments. + scoped_ptr<RawSharedBufferMapping> MapImpl(size_t offset, size_t length); + + const size_t num_bytes_; + + // This is set in |Init()|/|InitFromPlatformHandle()| and never modified + // (except by |PassPlatformHandle()|; see the comments above its declaration), + // hence does not need to be protected by a lock. + embedder::ScopedPlatformHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(RawSharedBuffer); +}; + +// A mapping of a |RawSharedBuffer| (compararable to a "file view" in Windows); +// see above. Created by |RawSharedBuffer::Map()|. Automatically unmaps memory +// on destruction. +// +// Mappings are NOT thread-safe. +// +// Note: This is an entirely separate class (instead of +// |RawSharedBuffer::Mapping|) so that it can be forward-declared. +class MOJO_SYSTEM_IMPL_EXPORT RawSharedBufferMapping { + public: + ~RawSharedBufferMapping() { Unmap(); } + + void* base() const { return base_; } + size_t length() const { return length_; } + + private: + friend class RawSharedBuffer; + + RawSharedBufferMapping(void* base, + size_t length, + void* real_base, + size_t real_length) + : base_(base), length_(length), + real_base_(real_base), real_length_(real_length) {} + void Unmap(); + + void* const base_; + const size_t length_; + + void* const real_base_; + const size_t real_length_; + + DISALLOW_COPY_AND_ASSIGN(RawSharedBufferMapping); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_RAW_SHARED_BUFFER_H_ diff --git a/chromium/mojo/system/raw_shared_buffer_posix.cc b/chromium/mojo/system/raw_shared_buffer_posix.cc new file mode 100644 index 00000000000..5c98737efdc --- /dev/null +++ b/chromium/mojo/system/raw_shared_buffer_posix.cc @@ -0,0 +1,151 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/raw_shared_buffer.h" + +#include <stdint.h> +#include <stdio.h> // For |fileno()|. +#include <sys/mman.h> // For |mmap()|/|munmap()|. +#include <sys/stat.h> +#include <sys/types.h> // For |off_t|. +#include <unistd.h> + +#include <limits> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "base/sys_info.h" +#include "base/threading/thread_restrictions.h" +#include "mojo/embedder/platform_handle.h" + +// We assume that |size_t| and |off_t| (type for |ftruncate()|) fits in a +// |uint64_t|. +COMPILE_ASSERT(sizeof(size_t) <= sizeof(uint64_t), size_t_too_big); +COMPILE_ASSERT(sizeof(off_t) <= sizeof(uint64_t), off_t_too_big); + +namespace mojo { +namespace system { + +// RawSharedBuffer ------------------------------------------------------------- + +bool RawSharedBuffer::Init() { + DCHECK(!handle_.is_valid()); + + base::ThreadRestrictions::ScopedAllowIO allow_io; + + if (static_cast<uint64_t>(num_bytes_) > + static_cast<uint64_t>(std::numeric_limits<off_t>::max())) { + return false; + } + + // TODO(vtl): This is stupid. The implementation of + // |CreateAndOpenTemporaryFileInDir()| starts with an FD, |fdopen()|s to get a + // |FILE*|, and then we have to |dup(fileno(fp))| to get back to an FD that we + // can own. (base/memory/shared_memory_posix.cc does this too, with more + // |fstat()|s thrown in for good measure.) + base::FilePath shared_buffer_dir; + if (!base::GetShmemTempDir(false, &shared_buffer_dir)) { + LOG(ERROR) << "Failed to get temporary directory for shared memory"; + return false; + } + base::FilePath shared_buffer_file; + base::ScopedFILE fp(base::CreateAndOpenTemporaryFileInDir( + shared_buffer_dir, &shared_buffer_file)); + if (!fp) { + LOG(ERROR) << "Failed to create/open temporary file for shared memory"; + return false; + } + // Note: |unlink()| is not interruptible. + if (unlink(shared_buffer_file.value().c_str()) != 0) { + PLOG(WARNING) << "unlink"; + // This isn't "fatal" (e.g., someone else may have unlinked the file first), + // so we may as well continue. + } + + // Note: |dup()| is not interruptible (but |dup2()|/|dup3()| are). + base::ScopedFD fd(dup(fileno(fp.get()))); + if (!fd.is_valid()) { + PLOG(ERROR) << "dup"; + return false; + } + + if (HANDLE_EINTR(ftruncate(fd.get(), static_cast<off_t>(num_bytes_))) != 0) { + PLOG(ERROR) << "ftruncate"; + return false; + } + + handle_.reset(embedder::PlatformHandle(fd.release())); + return true; +} + +bool RawSharedBuffer::InitFromPlatformHandle( + embedder::ScopedPlatformHandle platform_handle) { + DCHECK(!handle_.is_valid()); + + if (static_cast<uint64_t>(num_bytes_) > + static_cast<uint64_t>(std::numeric_limits<off_t>::max())) { + return false; + } + + struct stat sb = {}; + // Note: |fstat()| isn't interruptible. + if (fstat(platform_handle.get().fd, &sb) != 0) { + PLOG(ERROR) << "fstat"; + return false; + } + + if (!S_ISREG(sb.st_mode)) { + LOG(ERROR) << "Platform handle not to a regular file"; + return false; + } + + if (sb.st_size != static_cast<off_t>(num_bytes_)) { + LOG(ERROR) << "Shared memory file has the wrong size"; + return false; + } + + // TODO(vtl): More checks? + + handle_ = platform_handle.Pass(); + return true; +} + +scoped_ptr<RawSharedBufferMapping> RawSharedBuffer::MapImpl(size_t offset, + size_t length) { + size_t offset_rounding = offset % base::SysInfo::VMAllocationGranularity(); + size_t real_offset = offset - offset_rounding; + size_t real_length = length + offset_rounding; + + // This should hold (since we checked |num_bytes| versus the maximum value of + // |off_t| on creation, but it never hurts to be paranoid. + DCHECK_LE(static_cast<uint64_t>(real_offset), + static_cast<uint64_t>(std::numeric_limits<off_t>::max())); + + void* real_base = mmap(NULL, real_length, PROT_READ | PROT_WRITE, MAP_SHARED, + handle_.get().fd, static_cast<off_t>(real_offset)); + // |mmap()| should return |MAP_FAILED| (a.k.a. -1) on error. But it shouldn't + // return null either. + if (real_base == MAP_FAILED || !real_base) { + PLOG(ERROR) << "mmap"; + return scoped_ptr<RawSharedBufferMapping>(); + } + + void* base = static_cast<char*>(real_base) + offset_rounding; + return make_scoped_ptr( + new RawSharedBufferMapping(base, length, real_base, real_length)); +} + +// RawSharedBufferMapping ------------------------------------------------------ + +void RawSharedBufferMapping::Unmap() { + int result = munmap(real_base_, real_length_); + PLOG_IF(ERROR, result != 0) << "munmap"; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_shared_buffer_unittest.cc b/chromium/mojo/system/raw_shared_buffer_unittest.cc new file mode 100644 index 00000000000..07da4f90a5e --- /dev/null +++ b/chromium/mojo/system/raw_shared_buffer_unittest.cc @@ -0,0 +1,181 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/raw_shared_buffer.h" + +#include <limits> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +TEST(RawSharedBufferTest, Basic) { + const size_t kNumInts = 100; + const size_t kNumBytes = kNumInts * sizeof(int); + // A fudge so that we're not just writing zero bytes 75% of the time. + const int kFudge = 1234567890; + + // Make some memory. + scoped_refptr<RawSharedBuffer> buffer(RawSharedBuffer::Create(kNumBytes)); + ASSERT_TRUE(buffer); + + // Map it all, scribble some stuff, and then unmap it. + { + EXPECT_TRUE(buffer->IsValidMap(0, kNumBytes)); + scoped_ptr<RawSharedBufferMapping> mapping(buffer->Map(0, kNumBytes)); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->base()); + int* stuff = static_cast<int*>(mapping->base()); + for (size_t i = 0; i < kNumInts; i++) + stuff[i] = static_cast<int>(i) + kFudge; + } + + // Map it all again, check that our scribbling is still there, then do a + // partial mapping and scribble on that, check that everything is coherent, + // unmap the first mapping, scribble on some of the second mapping, and then + // unmap it. + { + ASSERT_TRUE(buffer->IsValidMap(0, kNumBytes)); + // Use |MapNoCheck()| this time. + scoped_ptr<RawSharedBufferMapping> mapping1( + buffer->MapNoCheck(0, kNumBytes)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->base()); + int* stuff1 = static_cast<int*>(mapping1->base()); + for (size_t i = 0; i < kNumInts; i++) + EXPECT_EQ(static_cast<int>(i) + kFudge, stuff1[i]) << i; + + scoped_ptr<RawSharedBufferMapping> mapping2( + buffer->Map((kNumInts / 2) * sizeof(int), 2 * sizeof(int))); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->base()); + int* stuff2 = static_cast<int*>(mapping2->base()); + EXPECT_EQ(static_cast<int>(kNumInts / 2) + kFudge, stuff2[0]); + EXPECT_EQ(static_cast<int>(kNumInts / 2) + 1 + kFudge, stuff2[1]); + + stuff2[0] = 123; + stuff2[1] = 456; + EXPECT_EQ(123, stuff1[kNumInts / 2]); + EXPECT_EQ(456, stuff1[kNumInts / 2 + 1]); + + mapping1.reset(); + + EXPECT_EQ(123, stuff2[0]); + EXPECT_EQ(456, stuff2[1]); + stuff2[1] = 789; + } + + // Do another partial mapping and check that everything is the way we expect + // it to be. + { + EXPECT_TRUE(buffer->IsValidMap(sizeof(int), kNumBytes - sizeof(int))); + scoped_ptr<RawSharedBufferMapping> mapping( + buffer->Map(sizeof(int), kNumBytes - sizeof(int))); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->base()); + int* stuff = static_cast<int*>(mapping->base()); + + for (size_t j = 0; j < kNumInts - 1; j++) { + int i = static_cast<int>(j) + 1; + if (i == kNumInts / 2) { + EXPECT_EQ(123, stuff[j]); + } else if (i == kNumInts / 2 + 1) { + EXPECT_EQ(789, stuff[j]); + } else { + EXPECT_EQ(i + kFudge, stuff[j]) << i; + } + } + } +} + +// TODO(vtl): Bigger buffers. + +TEST(RawSharedBufferTest, InvalidMappings) { + scoped_refptr<RawSharedBuffer> buffer(RawSharedBuffer::Create(100)); + ASSERT_TRUE(buffer); + + // Zero length not allowed. + EXPECT_FALSE(buffer->Map(0, 0)); + EXPECT_FALSE(buffer->IsValidMap(0, 0)); + + // Okay: + EXPECT_TRUE(buffer->Map(0, 100)); + EXPECT_TRUE(buffer->IsValidMap(0, 100)); + // Offset + length too big. + EXPECT_FALSE(buffer->Map(0, 101)); + EXPECT_FALSE(buffer->IsValidMap(0, 101)); + EXPECT_FALSE(buffer->Map(1, 100)); + EXPECT_FALSE(buffer->IsValidMap(1, 100)); + + // Okay: + EXPECT_TRUE(buffer->Map(50, 50)); + EXPECT_TRUE(buffer->IsValidMap(50, 50)); + // Offset + length too big. + EXPECT_FALSE(buffer->Map(50, 51)); + EXPECT_FALSE(buffer->IsValidMap(50, 51)); + EXPECT_FALSE(buffer->Map(51, 50)); + EXPECT_FALSE(buffer->IsValidMap(51, 50)); +} + +TEST(RawSharedBufferTest, TooBig) { + // If |size_t| is 32-bit, it's quite possible/likely that |Create()| succeeds + // (since it only involves creating a 4 GB file). + const size_t kMaxSizeT = std::numeric_limits<size_t>::max(); + scoped_refptr<RawSharedBuffer> buffer(RawSharedBuffer::Create(kMaxSizeT)); + // But, assuming |sizeof(size_t) == sizeof(void*)|, mapping all of it should + // always fail. + if (buffer) + EXPECT_FALSE(buffer->Map(0, kMaxSizeT)); +} + +// Tests that separate mappings get distinct addresses. +// Note: It's not inconceivable that the OS could ref-count identical mappings +// and reuse the same address, in which case we'd have to be more careful about +// using the address as the key for unmapping. +TEST(RawSharedBufferTest, MappingsDistinct) { + scoped_refptr<RawSharedBuffer> buffer(RawSharedBuffer::Create(100)); + scoped_ptr<RawSharedBufferMapping> mapping1(buffer->Map(0, 100)); + scoped_ptr<RawSharedBufferMapping> mapping2(buffer->Map(0, 100)); + EXPECT_NE(mapping1->base(), mapping2->base()); +} + +TEST(RawSharedBufferTest, BufferZeroInitialized) { + static const size_t kSizes[] = { 10, 100, 1000, 10000, 100000 }; + for (size_t i = 0; i < arraysize(kSizes); i++) { + scoped_refptr<RawSharedBuffer> buffer(RawSharedBuffer::Create(kSizes[i])); + scoped_ptr<RawSharedBufferMapping> mapping(buffer->Map(0, kSizes[i])); + for (size_t j = 0; j < kSizes[i]; j++) { + // "Assert" instead of "expect" so we don't spam the output with thousands + // of failures if we fail. + ASSERT_EQ('\0', static_cast<char*>(mapping->base())[j]) + << "size " << kSizes[i] << ", offset " << j; + } + } +} + +TEST(RawSharedBufferTest, MappingsOutliveBuffer) { + scoped_ptr<RawSharedBufferMapping> mapping1; + scoped_ptr<RawSharedBufferMapping> mapping2; + + { + scoped_refptr<RawSharedBuffer> buffer(RawSharedBuffer::Create(100)); + mapping1 = buffer->Map(0, 100).Pass(); + mapping2 = buffer->Map(50, 50).Pass(); + static_cast<char*>(mapping1->base())[50] = 'x'; + } + + EXPECT_EQ('x', static_cast<char*>(mapping2->base())[0]); + + static_cast<char*>(mapping2->base())[1] = 'y'; + EXPECT_EQ('y', static_cast<char*>(mapping1->base())[51]); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/raw_shared_buffer_win.cc b/chromium/mojo/system/raw_shared_buffer_win.cc new file mode 100644 index 00000000000..fdccd363cb5 --- /dev/null +++ b/chromium/mojo/system/raw_shared_buffer_win.cc @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/raw_shared_buffer.h" + +#include <windows.h> + +#include <limits> + +#include "base/logging.h" +#include "base/sys_info.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace system { + +// RawSharedBuffer ------------------------------------------------------------- + +bool RawSharedBuffer::Init() { + DCHECK(!handle_.is_valid()); + + // TODO(vtl): Currently, we only support mapping up to 2^32-1 bytes. + if (static_cast<uint64_t>(num_bytes_) > + static_cast<uint64_t>(std::numeric_limits<DWORD>::max())) { + return false; + } + + // IMPORTANT NOTE: Unnamed objects are NOT SECURABLE. Thus if we ever want to + // share read-only to other processes, we'll have to name our file mapping + // object. + // TODO(vtl): Unlike |base::SharedMemory|, we don't round up the size (to a + // multiple of 64 KB). This may cause problems with NaCl. Cross this bridge + // when we get there. crbug.com/210609 + handle_.reset(embedder::PlatformHandle(CreateFileMapping( + INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, + static_cast<DWORD>(num_bytes_), NULL))); + if (!handle_.is_valid()) { + PLOG(ERROR) << "CreateFileMapping"; + return false; + } + + return true; +} + +bool RawSharedBuffer::InitFromPlatformHandle( + embedder::ScopedPlatformHandle platform_handle) { + DCHECK(!handle_.is_valid()); + + // TODO(vtl): Implement. + NOTIMPLEMENTED(); + return false; +} + +scoped_ptr<RawSharedBufferMapping> RawSharedBuffer::MapImpl(size_t offset, + size_t length) { + size_t offset_rounding = offset % base::SysInfo::VMAllocationGranularity(); + size_t real_offset = offset - offset_rounding; + size_t real_length = length + offset_rounding; + + // This should hold (since we checked |num_bytes| versus the maximum value of + // |off_t| on creation, but it never hurts to be paranoid. + DCHECK_LE(static_cast<uint64_t>(real_offset), + static_cast<uint64_t>(std::numeric_limits<DWORD>::max())); + + void* real_base = MapViewOfFile( + handle_.get().handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, + static_cast<DWORD>(real_offset), real_length); + if (!real_base) { + PLOG(ERROR) << "MapViewOfFile"; + return scoped_ptr<RawSharedBufferMapping>(); + } + + void* base = static_cast<char*>(real_base) + offset_rounding; + return make_scoped_ptr( + new RawSharedBufferMapping(base, length, real_base, real_length)); +} + +// RawSharedBufferMapping ------------------------------------------------------ + +void RawSharedBufferMapping::Unmap() { + BOOL result = UnmapViewOfFile(real_base_); + PLOG_IF(ERROR, !result) << "UnmapViewOfFile"; +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/remote_message_pipe_unittest.cc b/chromium/mojo/system/remote_message_pipe_unittest.cc new file mode 100644 index 00000000000..e1f55579171 --- /dev/null +++ b/chromium/mojo/system/remote_message_pipe_unittest.cc @@ -0,0 +1,843 @@ +// 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 <stdio.h> +#include <string.h> + +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "build/build_config.h" // TODO(vtl): Remove this. +#include "mojo/common/test/test_utils.h" +#include "mojo/embedder/platform_channel_pair.h" +#include "mojo/embedder/scoped_platform_handle.h" +#include "mojo/system/channel.h" +#include "mojo/system/local_message_pipe_endpoint.h" +#include "mojo/system/message_pipe.h" +#include "mojo/system/message_pipe_dispatcher.h" +#include "mojo/system/platform_handle_dispatcher.h" +#include "mojo/system/proxy_message_pipe_endpoint.h" +#include "mojo/system/raw_channel.h" +#include "mojo/system/shared_buffer_dispatcher.h" +#include "mojo/system/test_utils.h" +#include "mojo/system/waiter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +class RemoteMessagePipeTest : public testing::Test { + public: + RemoteMessagePipeTest() : io_thread_(test::TestIOThread::kAutoStart) {} + virtual ~RemoteMessagePipeTest() {} + + virtual void SetUp() OVERRIDE { + io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&RemoteMessagePipeTest::SetUpOnIOThread, + base::Unretained(this))); + } + + virtual void TearDown() OVERRIDE { + io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&RemoteMessagePipeTest::TearDownOnIOThread, + base::Unretained(this))); + } + + protected: + // This connects MP 0, port 1 and MP 1, port 0 (leaving MP 0, port 0 and MP 1, + // port 1 as the user-visible endpoints) to channel 0 and 1, respectively. MP + // 0, port 1 and MP 1, port 0 must have |ProxyMessagePipeEndpoint|s. + void ConnectMessagePipes(scoped_refptr<MessagePipe> mp0, + scoped_refptr<MessagePipe> mp1) { + io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&RemoteMessagePipeTest::ConnectMessagePipesOnIOThread, + base::Unretained(this), mp0, mp1)); + } + + // This connects |mp|'s port |channel_index ^ 1| to channel |channel_index|. + // It assumes/requires that this is the bootstrap case, i.e., that the + // endpoint IDs are both/will both be |Channel::kBootstrapEndpointId|. This + // returns *without* waiting for it to finish connecting. + void BootstrapMessagePipeNoWait(unsigned channel_index, + scoped_refptr<MessagePipe> mp) { + io_thread_.PostTask( + FROM_HERE, + base::Bind(&RemoteMessagePipeTest::BootstrapMessagePipeOnIOThread, + base::Unretained(this), channel_index, mp)); + } + + void RestoreInitialState() { + io_thread_.PostTaskAndWait( + FROM_HERE, + base::Bind(&RemoteMessagePipeTest::RestoreInitialStateOnIOThread, + base::Unretained(this))); + } + + test::TestIOThread* io_thread() { return &io_thread_; } + + private: + void SetUpOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + embedder::PlatformChannelPair channel_pair; + platform_handles_[0] = channel_pair.PassServerHandle(); + platform_handles_[1] = channel_pair.PassClientHandle(); + } + + void TearDownOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + if (channels_[0]) { + channels_[0]->Shutdown(); + channels_[0] = NULL; + } + if (channels_[1]) { + channels_[1]->Shutdown(); + channels_[1] = NULL; + } + } + + void CreateAndInitChannel(unsigned channel_index) { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + CHECK(channel_index == 0 || channel_index == 1); + CHECK(!channels_[channel_index]); + + channels_[channel_index] = new Channel(); + CHECK(channels_[channel_index]->Init( + RawChannel::Create(platform_handles_[channel_index].Pass()))); + } + + void ConnectMessagePipesOnIOThread(scoped_refptr<MessagePipe> mp0, + scoped_refptr<MessagePipe> mp1) { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + if (!channels_[0]) + CreateAndInitChannel(0); + if (!channels_[1]) + CreateAndInitChannel(1); + + MessageInTransit::EndpointId local_id0 = + channels_[0]->AttachMessagePipeEndpoint(mp0, 1); + MessageInTransit::EndpointId local_id1 = + channels_[1]->AttachMessagePipeEndpoint(mp1, 0); + + CHECK(channels_[0]->RunMessagePipeEndpoint(local_id0, local_id1)); + CHECK(channels_[1]->RunMessagePipeEndpoint(local_id1, local_id0)); + } + + void BootstrapMessagePipeOnIOThread(unsigned channel_index, + scoped_refptr<MessagePipe> mp) { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + CHECK(channel_index == 0 || channel_index == 1); + + unsigned port = channel_index ^ 1u; + + CreateAndInitChannel(channel_index); + MessageInTransit::EndpointId endpoint_id = + channels_[channel_index]->AttachMessagePipeEndpoint(mp, port); + if (endpoint_id == MessageInTransit::kInvalidEndpointId) + return; + + CHECK_EQ(endpoint_id, Channel::kBootstrapEndpointId); + CHECK(channels_[channel_index]->RunMessagePipeEndpoint( + Channel::kBootstrapEndpointId, Channel::kBootstrapEndpointId)); + } + + void RestoreInitialStateOnIOThread() { + CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop()); + + TearDownOnIOThread(); + SetUpOnIOThread(); + } + + test::TestIOThread io_thread_; + embedder::ScopedPlatformHandle platform_handles_[2]; + scoped_refptr<Channel> channels_[2]; + + DISALLOW_COPY_AND_ASSIGN(RemoteMessagePipeTest); +}; + +TEST_F(RemoteMessagePipeTest, Basic) { + static const char kHello[] = "hello"; + static const char kWorld[] = "world!!!1!!!1!"; + char buffer[100] = { 0 }; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + Waiter waiter; + uint32_t context = 0; + + // Connect message pipes. MP 0, port 1 will be attached to channel 0 and + // connected to MP 1, port 0, which will be attached to channel 1. This leaves + // MP 0, port 0 and MP 1, port 1 as the "user-facing" endpoints. + + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + ConnectMessagePipes(mp0, mp1); + + // Write in one direction: MP 0, port 0 -> ... -> MP 1, port 1. + + // Prepare to wait on MP 1, port 1. (Add the waiter now. Otherwise, if we do + // it later, it might already be readable.) + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + // Write to MP 0, port 0. + EXPECT_EQ(MOJO_RESULT_OK, + mp0->WriteMessage(0, + kHello, sizeof(kHello), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(123u, context); + mp1->RemoveWaiter(1, &waiter); + + // Read from MP 1, port 1. + EXPECT_EQ(MOJO_RESULT_OK, + mp1->ReadMessage(1, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(buffer_size)); + EXPECT_STREQ(kHello, buffer); + + // Write in the other direction: MP 1, port 1 -> ... -> MP 0, port 0. + + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp0->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 456)); + + EXPECT_EQ(MOJO_RESULT_OK, + mp1->WriteMessage(1, + kWorld, sizeof(kWorld), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(456u, context); + mp0->RemoveWaiter(0, &waiter); + + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + mp0->ReadMessage(0, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kWorld), static_cast<size_t>(buffer_size)); + EXPECT_STREQ(kWorld, buffer); + + // Close MP 0, port 0. + mp0->Close(0); + + // Try to wait for MP 1, port 1 to become readable. This will eventually fail + // when it realizes that MP 0, port 0 has been closed. (It may also fail + // immediately.) + waiter.Init(); + MojoResult result = + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789); + if (result == MOJO_RESULT_OK) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(789u, context); + mp1->RemoveWaiter(1, &waiter); + } else { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + } + + // And MP 1, port 1. + mp1->Close(1); +} + +TEST_F(RemoteMessagePipeTest, Multiplex) { + static const char kHello[] = "hello"; + static const char kWorld[] = "world!!!1!!!1!"; + char buffer[100] = { 0 }; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + Waiter waiter; + uint32_t context = 0; + + // Connect message pipes as in the |Basic| test. + + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + ConnectMessagePipes(mp0, mp1); + + // Now put another message pipe on the channel. + + scoped_refptr<MessagePipe> mp2(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipe> mp3(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + ConnectMessagePipes(mp2, mp3); + + // Write: MP 2, port 0 -> MP 3, port 1. + + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp3->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789)); + + EXPECT_EQ(MOJO_RESULT_OK, + mp2->WriteMessage(0, + kHello, sizeof(kHello), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(789u, context); + mp3->RemoveWaiter(1, &waiter); + + // Make sure there's nothing on MP 0, port 0 or MP 1, port 1 or MP 2, port 0. + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp0->ReadMessage(0, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp1->ReadMessage(1, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp2->ReadMessage(0, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Read from MP 3, port 1. + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + mp3->ReadMessage(1, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(buffer_size)); + EXPECT_STREQ(kHello, buffer); + + // Write: MP 0, port 0 -> MP 1, port 1 again. + + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + EXPECT_EQ(MOJO_RESULT_OK, + mp0->WriteMessage(0, + kWorld, sizeof(kWorld), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(123u, context); + mp1->RemoveWaiter(1, &waiter); + + // Make sure there's nothing on the other ports. + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp0->ReadMessage(0, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp2->ReadMessage(0, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + mp3->ReadMessage(1, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->ReadMessage(1, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kWorld), static_cast<size_t>(buffer_size)); + EXPECT_STREQ(kWorld, buffer); + + mp0->Close(0); + mp1->Close(1); + mp2->Close(0); + mp3->Close(1); +} + +TEST_F(RemoteMessagePipeTest, CloseBeforeConnect) { + static const char kHello[] = "hello"; + char buffer[100] = { 0 }; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + Waiter waiter; + uint32_t context = 0; + + // Connect message pipes. MP 0, port 1 will be attached to channel 0 and + // connected to MP 1, port 0, which will be attached to channel 1. This leaves + // MP 0, port 0 and MP 1, port 1 as the "user-facing" endpoints. + + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + + // Write to MP 0, port 0. + EXPECT_EQ(MOJO_RESULT_OK, + mp0->WriteMessage(0, + kHello, sizeof(kHello), + NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + BootstrapMessagePipeNoWait(0, mp0); + + + // Close MP 0, port 0 before channel 1 is even connected. + mp0->Close(0); + + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + + // Prepare to wait on MP 1, port 1. (Add the waiter now. Otherwise, if we do + // it later, it might already be readable.) + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + BootstrapMessagePipeNoWait(1, mp1); + + // Wait. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(123u, context); + mp1->RemoveWaiter(1, &waiter); + + // Read from MP 1, port 1. + EXPECT_EQ(MOJO_RESULT_OK, + mp1->ReadMessage(1, + buffer, &buffer_size, + NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(buffer_size)); + EXPECT_STREQ(kHello, buffer); + + // And MP 1, port 1. + mp1->Close(1); +} + +TEST_F(RemoteMessagePipeTest, HandlePassing) { + static const char kHello[] = "hello"; + Waiter waiter; + uint32_t context = 0; + + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + ConnectMessagePipes(mp0, mp1); + + // We'll try to pass this dispatcher. + scoped_refptr<MessagePipeDispatcher> dispatcher(new MessagePipeDispatcher( + MessagePipeDispatcher::kDefaultCreateOptions)); + scoped_refptr<MessagePipe> local_mp(new MessagePipe()); + dispatcher->Init(local_mp, 0); + + // Prepare to wait on MP 1, port 1. (Add the waiter now. Otherwise, if we do + // it later, it might already be readable.) + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + // Write to MP 0, port 0. + { + DispatcherTransport + transport(test::DispatcherTryStartTransport(dispatcher.get())); + EXPECT_TRUE(transport.is_valid()); + + std::vector<DispatcherTransport> transports; + transports.push_back(transport); + EXPECT_EQ(MOJO_RESULT_OK, + mp0->WriteMessage(0, kHello, sizeof(kHello), &transports, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + transport.End(); + + // |dispatcher| should have been closed. This is |DCHECK()|ed when the + // |dispatcher| is destroyed. + EXPECT_TRUE(dispatcher->HasOneRef()); + dispatcher = NULL; + } + + // Wait. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(123u, context); + mp1->RemoveWaiter(1, &waiter); + + // Read from MP 1, port 1. + char read_buffer[100] = { 0 }; + uint32_t read_buffer_size = static_cast<uint32_t>(sizeof(read_buffer)); + DispatcherVector read_dispatchers; + uint32_t read_num_dispatchers = 10; // Maximum to get. + EXPECT_EQ(MOJO_RESULT_OK, + mp1->ReadMessage(1, read_buffer, &read_buffer_size, + &read_dispatchers, &read_num_dispatchers, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(read_buffer_size)); + EXPECT_STREQ(kHello, read_buffer); + EXPECT_EQ(1u, read_dispatchers.size()); + EXPECT_EQ(1u, read_num_dispatchers); + ASSERT_TRUE(read_dispatchers[0]); + EXPECT_TRUE(read_dispatchers[0]->HasOneRef()); + + EXPECT_EQ(Dispatcher::kTypeMessagePipe, read_dispatchers[0]->GetType()); + dispatcher = static_cast<MessagePipeDispatcher*>(read_dispatchers[0].get()); + + // Write to "local_mp", port 1. + EXPECT_EQ(MOJO_RESULT_OK, + local_mp->WriteMessage(1, kHello, sizeof(kHello), NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // TODO(vtl): FIXME -- We (racily) crash if I close |dispatcher| immediately + // here. (We don't crash if I sleep and then close.) + + // Wait for the dispatcher to become readable. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->AddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 456)); + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(456u, context); + dispatcher->RemoveWaiter(&waiter); + + // Read from the dispatcher. + memset(read_buffer, 0, sizeof(read_buffer)); + read_buffer_size = static_cast<uint32_t>(sizeof(read_buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->ReadMessage(read_buffer, &read_buffer_size, 0, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(read_buffer_size)); + EXPECT_STREQ(kHello, read_buffer); + + // Prepare to wait on "local_mp", port 1. + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + local_mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789)); + + // Write to the dispatcher. + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->WriteMessage(kHello, sizeof(kHello), NULL, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(789u, context); + local_mp->RemoveWaiter(1, &waiter); + + // Read from "local_mp", port 1. + memset(read_buffer, 0, sizeof(read_buffer)); + read_buffer_size = static_cast<uint32_t>(sizeof(read_buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + local_mp->ReadMessage(1, read_buffer, &read_buffer_size, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(read_buffer_size)); + EXPECT_STREQ(kHello, read_buffer); + + // TODO(vtl): Also test that messages queued up before the handle was sent are + // delivered properly. + + // Close everything that belongs to us. + mp0->Close(0); + mp1->Close(1); + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + // Note that |local_mp|'s port 0 belong to |dispatcher|, which was closed. + local_mp->Close(1); +} + +#if defined(OS_POSIX) +#define MAYBE_SharedBufferPassing SharedBufferPassing +#else +// Not yet implemented (on Windows). +#define MAYBE_SharedBufferPassing DISABLED_SharedBufferPassing +#endif +TEST_F(RemoteMessagePipeTest, MAYBE_SharedBufferPassing) { + static const char kHello[] = "hello"; + Waiter waiter; + uint32_t context = 0; + + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + ConnectMessagePipes(mp0, mp1); + + // We'll try to pass this dispatcher. + scoped_refptr<SharedBufferDispatcher> dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher)); + ASSERT_TRUE(dispatcher); + + // Make a mapping. + scoped_ptr<RawSharedBufferMapping> mapping0; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, + &mapping0)); + ASSERT_TRUE(mapping0); + ASSERT_TRUE(mapping0->base()); + ASSERT_EQ(100u, mapping0->length()); + static_cast<char*>(mapping0->base())[0] = 'A'; + static_cast<char*>(mapping0->base())[50] = 'B'; + static_cast<char*>(mapping0->base())[99] = 'C'; + + // Prepare to wait on MP 1, port 1. (Add the waiter now. Otherwise, if we do + // it later, it might already be readable.) + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + // Write to MP 0, port 0. + { + DispatcherTransport + transport(test::DispatcherTryStartTransport(dispatcher.get())); + EXPECT_TRUE(transport.is_valid()); + + std::vector<DispatcherTransport> transports; + transports.push_back(transport); + EXPECT_EQ(MOJO_RESULT_OK, + mp0->WriteMessage(0, kHello, sizeof(kHello), &transports, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + transport.End(); + + // |dispatcher| should have been closed. This is |DCHECK()|ed when the + // |dispatcher| is destroyed. + EXPECT_TRUE(dispatcher->HasOneRef()); + dispatcher = NULL; + } + + // Wait. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(123u, context); + mp1->RemoveWaiter(1, &waiter); + + // Read from MP 1, port 1. + char read_buffer[100] = { 0 }; + uint32_t read_buffer_size = static_cast<uint32_t>(sizeof(read_buffer)); + DispatcherVector read_dispatchers; + uint32_t read_num_dispatchers = 10; // Maximum to get. + EXPECT_EQ(MOJO_RESULT_OK, + mp1->ReadMessage(1, read_buffer, &read_buffer_size, + &read_dispatchers, &read_num_dispatchers, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kHello), static_cast<size_t>(read_buffer_size)); + EXPECT_STREQ(kHello, read_buffer); + EXPECT_EQ(1u, read_dispatchers.size()); + EXPECT_EQ(1u, read_num_dispatchers); + ASSERT_TRUE(read_dispatchers[0]); + EXPECT_TRUE(read_dispatchers[0]->HasOneRef()); + + EXPECT_EQ(Dispatcher::kTypeSharedBuffer, read_dispatchers[0]->GetType()); + dispatcher = + static_cast<SharedBufferDispatcher*>(read_dispatchers[0].get()); + + // Make another mapping. + scoped_ptr<RawSharedBufferMapping> mapping1; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, + &mapping1)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->base()); + ASSERT_EQ(100u, mapping1->length()); + EXPECT_NE(mapping1->base(), mapping0->base()); + EXPECT_EQ('A', static_cast<char*>(mapping1->base())[0]); + EXPECT_EQ('B', static_cast<char*>(mapping1->base())[50]); + EXPECT_EQ('C', static_cast<char*>(mapping1->base())[99]); + + // Write stuff either way. + static_cast<char*>(mapping1->base())[1] = 'x'; + EXPECT_EQ('x', static_cast<char*>(mapping0->base())[1]); + static_cast<char*>(mapping0->base())[2] = 'y'; + EXPECT_EQ('y', static_cast<char*>(mapping1->base())[2]); + + // Kill the first mapping; the second should still be valid. + mapping0.reset(); + EXPECT_EQ('A', static_cast<char*>(mapping1->base())[0]); + + // Close everything that belongs to us. + mp0->Close(0); + mp1->Close(1); + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + + // The second mapping should still be good. + EXPECT_EQ('x', static_cast<char*>(mapping1->base())[1]); +} + +#if defined(OS_POSIX) +#define MAYBE_PlatformHandlePassing PlatformHandlePassing +#else +// Not yet implemented (on Windows). +#define MAYBE_PlatformHandlePassing DISABLED_PlatformHandlePassing +#endif +TEST_F(RemoteMessagePipeTest, MAYBE_PlatformHandlePassing) { + static const char kHello[] = "hello"; + static const char kWorld[] = "world"; + Waiter waiter; + uint32_t context = 0; + + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + ConnectMessagePipes(mp0, mp1); + + base::FilePath unused; + base::ScopedFILE fp(CreateAndOpenTemporaryFile(&unused)); + EXPECT_EQ(sizeof(kHello), fwrite(kHello, 1, sizeof(kHello), fp.get())); + // We'll try to pass this dispatcher, which will cause a |PlatformHandle| to + // be passed. + scoped_refptr<PlatformHandleDispatcher> dispatcher( + new PlatformHandleDispatcher( + mojo::test::PlatformHandleFromFILE(fp.Pass()))); + + // Prepare to wait on MP 1, port 1. (Add the waiter now. Otherwise, if we do + // it later, it might already be readable.) + waiter.Init(); + EXPECT_EQ(MOJO_RESULT_OK, + mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123)); + + // Write to MP 0, port 0. + { + DispatcherTransport + transport(test::DispatcherTryStartTransport(dispatcher.get())); + EXPECT_TRUE(transport.is_valid()); + + std::vector<DispatcherTransport> transports; + transports.push_back(transport); + EXPECT_EQ(MOJO_RESULT_OK, + mp0->WriteMessage(0, kWorld, sizeof(kWorld), &transports, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + transport.End(); + + // |dispatcher| should have been closed. This is |DCHECK()|ed when the + // |dispatcher| is destroyed. + EXPECT_TRUE(dispatcher->HasOneRef()); + dispatcher = NULL; + } + + // Wait. + EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_EQ(123u, context); + mp1->RemoveWaiter(1, &waiter); + + // Read from MP 1, port 1. + char read_buffer[100] = { 0 }; + uint32_t read_buffer_size = static_cast<uint32_t>(sizeof(read_buffer)); + DispatcherVector read_dispatchers; + uint32_t read_num_dispatchers = 10; // Maximum to get. + EXPECT_EQ(MOJO_RESULT_OK, + mp1->ReadMessage(1, read_buffer, &read_buffer_size, + &read_dispatchers, &read_num_dispatchers, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(sizeof(kWorld), static_cast<size_t>(read_buffer_size)); + EXPECT_STREQ(kWorld, read_buffer); + EXPECT_EQ(1u, read_dispatchers.size()); + EXPECT_EQ(1u, read_num_dispatchers); + ASSERT_TRUE(read_dispatchers[0]); + EXPECT_TRUE(read_dispatchers[0]->HasOneRef()); + + EXPECT_EQ(Dispatcher::kTypePlatformHandle, read_dispatchers[0]->GetType()); + dispatcher = + static_cast<PlatformHandleDispatcher*>(read_dispatchers[0].get()); + + embedder::ScopedPlatformHandle h = dispatcher->PassPlatformHandle().Pass(); + EXPECT_TRUE(h.is_valid()); + + fp = mojo::test::FILEFromPlatformHandle(h.Pass(), "rb").Pass(); + EXPECT_FALSE(h.is_valid()); + EXPECT_TRUE(fp); + + rewind(fp.get()); + memset(read_buffer, 0, sizeof(read_buffer)); + EXPECT_EQ(sizeof(kHello), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kHello, read_buffer); + + // Close everything that belongs to us. + mp0->Close(0); + mp1->Close(1); + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +// Test racing closes (on each end). +// Note: A flaky failure would almost certainly indicate a problem in the code +// itself (not in the test). Also, any logged warnings/errors would also +// probably be indicative of bugs. +TEST_F(RemoteMessagePipeTest, RacingClosesStress) { + base::TimeDelta delay = base::TimeDelta::FromMilliseconds(5); + + for (unsigned i = 0; i < 256; i++) { + DVLOG(2) << "---------------------------------------- " << i; + scoped_refptr<MessagePipe> mp0(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()))); + BootstrapMessagePipeNoWait(0, mp0); + + scoped_refptr<MessagePipe> mp1(new MessagePipe( + scoped_ptr<MessagePipeEndpoint>(new ProxyMessagePipeEndpoint()), + scoped_ptr<MessagePipeEndpoint>(new LocalMessagePipeEndpoint()))); + BootstrapMessagePipeNoWait(1, mp1); + + if (i & 1u) { + io_thread()->task_runner()->PostTask( + FROM_HERE, base::Bind(&base::PlatformThread::Sleep, delay)); + } + if (i & 2u) + base::PlatformThread::Sleep(delay); + + mp0->Close(0); + + if (i & 4u) { + io_thread()->task_runner()->PostTask( + FROM_HERE, base::Bind(&base::PlatformThread::Sleep, delay)); + } + if (i & 8u) + base::PlatformThread::Sleep(delay); + + mp1->Close(1); + + RestoreInitialState(); + } +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/shared_buffer_dispatcher.cc b/chromium/mojo/system/shared_buffer_dispatcher.cc new file mode 100644 index 00000000000..1181b01b915 --- /dev/null +++ b/chromium/mojo/system/shared_buffer_dispatcher.cc @@ -0,0 +1,272 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/shared_buffer_dispatcher.h" + +#include <limits> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/system/constants.h" +#include "mojo/system/memory.h" +#include "mojo/system/options_validation.h" +#include "mojo/system/raw_shared_buffer.h" + +namespace mojo { +namespace system { + +namespace { + +struct SerializedSharedBufferDispatcher { + size_t num_bytes; + size_t platform_handle_index; +}; + +} // namespace + +// static +const MojoCreateSharedBufferOptions + SharedBufferDispatcher::kDefaultCreateOptions = { + static_cast<uint32_t>(sizeof(MojoCreateSharedBufferOptions)), + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE +}; + +// static +MojoResult SharedBufferDispatcher::ValidateCreateOptions( + const MojoCreateSharedBufferOptions* in_options, + MojoCreateSharedBufferOptions* out_options) { + const MojoCreateSharedBufferOptionsFlags kKnownFlags = + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + *out_options = kDefaultCreateOptions; + if (!in_options) + return MOJO_RESULT_OK; + + MojoResult result = + ValidateOptionsStructPointerSizeAndFlags<MojoCreateSharedBufferOptions>( + in_options, kKnownFlags, out_options); + if (result != MOJO_RESULT_OK) + return result; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +// static +MojoResult SharedBufferDispatcher::Create( + const MojoCreateSharedBufferOptions& /*validated_options*/, + uint64_t num_bytes, + scoped_refptr<SharedBufferDispatcher>* result) { + if (!num_bytes) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > kMaxSharedMemoryNumBytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + scoped_refptr<RawSharedBuffer> shared_buffer( + RawSharedBuffer::Create(static_cast<size_t>(num_bytes))); + if (!shared_buffer) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + *result = new SharedBufferDispatcher(shared_buffer); + return MOJO_RESULT_OK; +} + +Dispatcher::Type SharedBufferDispatcher::GetType() const { + return kTypeSharedBuffer; +} + +// static +scoped_refptr<SharedBufferDispatcher> SharedBufferDispatcher::Deserialize( + Channel* channel, + const void* source, + size_t size, + embedder::PlatformHandleVector* platform_handles) { + if (size != sizeof(SerializedSharedBufferDispatcher)) { + LOG(ERROR) << "Invalid serialized shared buffer dispatcher (bad size)"; + return scoped_refptr<SharedBufferDispatcher>(); + } + + const SerializedSharedBufferDispatcher* serialization = + static_cast<const SerializedSharedBufferDispatcher*>(source); + size_t num_bytes = serialization->num_bytes; + size_t platform_handle_index = serialization->platform_handle_index; + + if (!num_bytes) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (invalid num_bytes)"; + return scoped_refptr<SharedBufferDispatcher>(); + } + + if (!platform_handles || platform_handle_index >= platform_handles->size()) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (missing handles)"; + return scoped_refptr<SharedBufferDispatcher>(); + } + + // Starts off invalid, which is what we want. + embedder::PlatformHandle platform_handle; + // We take ownership of the handle, so we have to invalidate the one in + // |platform_handles|. + std::swap(platform_handle, (*platform_handles)[platform_handle_index]); + + // Wrapping |platform_handle| in a |ScopedPlatformHandle| means that it'll be + // closed even if creation fails. + scoped_refptr<RawSharedBuffer> shared_buffer( + RawSharedBuffer::CreateFromPlatformHandle(num_bytes, + embedder::ScopedPlatformHandle(platform_handle))); + if (!shared_buffer) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (invalid num_bytes?)"; + return scoped_refptr<SharedBufferDispatcher>(); + } + + return scoped_refptr<SharedBufferDispatcher>(new SharedBufferDispatcher( + shared_buffer)); +} + +SharedBufferDispatcher::SharedBufferDispatcher( + scoped_refptr<RawSharedBuffer> shared_buffer) + : shared_buffer_(shared_buffer) { + DCHECK(shared_buffer_); +} + +SharedBufferDispatcher::~SharedBufferDispatcher() { +} + +// static +MojoResult SharedBufferDispatcher::ValidateDuplicateOptions( + const MojoDuplicateBufferHandleOptions* in_options, + MojoDuplicateBufferHandleOptions* out_options) { + const MojoDuplicateBufferHandleOptionsFlags kKnownFlags = + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE; + static const MojoDuplicateBufferHandleOptions kDefaultOptions = { + static_cast<uint32_t>(sizeof(MojoDuplicateBufferHandleOptions)), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE + }; + + *out_options = kDefaultOptions; + if (!in_options) + return MOJO_RESULT_OK; + + MojoResult result = + ValidateOptionsStructPointerSizeAndFlags< + MojoDuplicateBufferHandleOptions>( + in_options, kKnownFlags, out_options); + if (result != MOJO_RESULT_OK) + return result; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +void SharedBufferDispatcher::CloseImplNoLock() { + lock().AssertAcquired(); + DCHECK(shared_buffer_); + shared_buffer_ = NULL; +} + +scoped_refptr<Dispatcher> + SharedBufferDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() { + lock().AssertAcquired(); + DCHECK(shared_buffer_); + scoped_refptr<RawSharedBuffer> shared_buffer; + shared_buffer.swap(shared_buffer_); + return scoped_refptr<Dispatcher>(new SharedBufferDispatcher(shared_buffer)); +} + +MojoResult SharedBufferDispatcher::DuplicateBufferHandleImplNoLock( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr<Dispatcher>* new_dispatcher) { + lock().AssertAcquired(); + + MojoDuplicateBufferHandleOptions validated_options; + MojoResult result = ValidateDuplicateOptions(options, &validated_options); + if (result != MOJO_RESULT_OK) + return result; + + *new_dispatcher = new SharedBufferDispatcher(shared_buffer_); + return MOJO_RESULT_OK; +} + +MojoResult SharedBufferDispatcher::MapBufferImplNoLock( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + scoped_ptr<RawSharedBufferMapping>* mapping) { + lock().AssertAcquired(); + DCHECK(shared_buffer_); + + if (offset > static_cast<uint64_t>(std::numeric_limits<size_t>::max())) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > static_cast<uint64_t>(std::numeric_limits<size_t>::max())) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!shared_buffer_->IsValidMap(static_cast<size_t>(offset), + static_cast<size_t>(num_bytes))) + return MOJO_RESULT_INVALID_ARGUMENT; + + DCHECK(mapping); + *mapping = shared_buffer_->MapNoCheck(static_cast<size_t>(offset), + static_cast<size_t>(num_bytes)); + if (!*mapping) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + return MOJO_RESULT_OK; +} + +void SharedBufferDispatcher::StartSerializeImplNoLock( + Channel* /*channel*/, + size_t* max_size, + size_t* max_platform_handles) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + *max_size = sizeof(SerializedSharedBufferDispatcher); + *max_platform_handles = 1; +} + +bool SharedBufferDispatcher::EndSerializeAndCloseImplNoLock( + Channel* /*channel*/, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) { + DCHECK(HasOneRef()); // Only one ref => no need to take the lock. + DCHECK(shared_buffer_); + + SerializedSharedBufferDispatcher* serialization = + static_cast<SerializedSharedBufferDispatcher*>(destination); + // If there's only one reference to |shared_buffer_|, then it's ours (and no + // one else can make any more references to it), so we can just take its + // handle. + embedder::ScopedPlatformHandle platform_handle( + shared_buffer_->HasOneRef() ? + shared_buffer_->PassPlatformHandle() : + shared_buffer_->DuplicatePlatformHandle()); + if (!platform_handle.is_valid()) { + shared_buffer_ = NULL; + return false; + } + + serialization->num_bytes = shared_buffer_->num_bytes(); + serialization->platform_handle_index = platform_handles->size(); + platform_handles->push_back(platform_handle.release()); + *actual_size = sizeof(SerializedSharedBufferDispatcher); + + shared_buffer_ = NULL; + + return true; +} + +HandleSignalsState SharedBufferDispatcher::GetHandleSignalsStateNoLock() const { + // TODO(vtl): Add transferrable flag. + return HandleSignalsState(); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/shared_buffer_dispatcher.h b/chromium/mojo/system/shared_buffer_dispatcher.h new file mode 100644 index 00000000000..ff251e3305b --- /dev/null +++ b/chromium/mojo/system/shared_buffer_dispatcher.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 MOJO_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ +#define MOJO_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ + +#include "base/macros.h" +#include "mojo/system/raw_shared_buffer.h" +#include "mojo/system/simple_dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// TODO(vtl): We derive from SimpleDispatcher, even though we don't currently +// have anything that's waitable. I want to add a "transferrable" wait flag. +class MOJO_SYSTEM_IMPL_EXPORT SharedBufferDispatcher : public SimpleDispatcher { + public: + // The default options to use for |MojoCreateSharedBuffer()|. (Real uses + // should obtain this via |ValidateCreateOptions()| with a null |in_options|; + // this is exposed directly for testing convenience.) + static const MojoCreateSharedBufferOptions kDefaultCreateOptions; + + // Validates and/or sets default options for |MojoCreateSharedBufferOptions|. + // If non-null, |in_options| must point to a struct of at least + // |in_options->struct_size| bytes. |out_options| must point to a (current) + // |MojoCreateSharedBufferOptions| and will be entirely overwritten on success + // (it may be partly overwritten on failure). + static MojoResult ValidateCreateOptions( + const MojoCreateSharedBufferOptions* in_options, + MojoCreateSharedBufferOptions* out_options); + + // Static factory method: |validated_options| must be validated (obviously). + // On failure, |*result| will be left as-is. + static MojoResult Create( + const MojoCreateSharedBufferOptions& validated_options, + uint64_t num_bytes, + scoped_refptr<SharedBufferDispatcher>* result); + + // |Dispatcher| public methods: + virtual Type GetType() const OVERRIDE; + + // The "opposite" of |SerializeAndClose()|. (Typically this is called by + // |Dispatcher::Deserialize()|.) + static scoped_refptr<SharedBufferDispatcher> Deserialize( + Channel* channel, + const void* source, + size_t size, + embedder::PlatformHandleVector* platform_handles); + + private: + explicit SharedBufferDispatcher( + scoped_refptr<RawSharedBuffer> shared_buffer_); + virtual ~SharedBufferDispatcher(); + + // Validates and/or sets default options for + // |MojoDuplicateBufferHandleOptions|. If non-null, |in_options| must point to + // a struct of at least |in_options->struct_size| bytes. |out_options| must + // point to a (current) |MojoDuplicateBufferHandleOptions| and will be + // entirely overwritten on success (it may be partly overwritten on failure). + static MojoResult ValidateDuplicateOptions( + const MojoDuplicateBufferHandleOptions* in_options, + MojoDuplicateBufferHandleOptions* out_options); + + // |Dispatcher| protected methods: + virtual void CloseImplNoLock() OVERRIDE; + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE; + virtual MojoResult DuplicateBufferHandleImplNoLock( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr<Dispatcher>* new_dispatcher) OVERRIDE; + virtual MojoResult MapBufferImplNoLock( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + scoped_ptr<RawSharedBufferMapping>* mapping) OVERRIDE; + virtual void StartSerializeImplNoLock(Channel* channel, + size_t* max_size, + size_t* max_platform_handles) OVERRIDE; + virtual bool EndSerializeAndCloseImplNoLock( + Channel* channel, + void* destination, + size_t* actual_size, + embedder::PlatformHandleVector* platform_handles) OVERRIDE; + + // |SimpleDispatcher| method: + virtual HandleSignalsState GetHandleSignalsStateNoLock() const OVERRIDE; + + scoped_refptr<RawSharedBuffer> shared_buffer_; + + DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcher); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ diff --git a/chromium/mojo/system/shared_buffer_dispatcher_unittest.cc b/chromium/mojo/system/shared_buffer_dispatcher_unittest.cc new file mode 100644 index 00000000000..8534ae56ace --- /dev/null +++ b/chromium/mojo/system/shared_buffer_dispatcher_unittest.cc @@ -0,0 +1,268 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/shared_buffer_dispatcher.h" + +#include <limits> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/raw_shared_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +// NOTE(vtl): There's currently not much to test for in +// |SharedBufferDispatcher::ValidateCreateOptions()|, but the tests should be +// expanded if/when options are added, so I've kept the general form of the +// tests from data_pipe_unittest.cc. + +const uint32_t kSizeOfCreateOptions = sizeof(MojoCreateSharedBufferOptions); + +// Does a cursory sanity check of |validated_options|. Calls +// |ValidateCreateOptions()| on already-validated options. The validated options +// should be valid, and the revalidated copy should be the same. +void RevalidateCreateOptions( + const MojoCreateSharedBufferOptions& validated_options) { + EXPECT_EQ(kSizeOfCreateOptions, validated_options.struct_size); + // Nothing to check for flags. + + MojoCreateSharedBufferOptions revalidated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + &validated_options, &revalidated_options)); + EXPECT_EQ(validated_options.struct_size, revalidated_options.struct_size); + EXPECT_EQ(validated_options.flags, revalidated_options.flags); +} + +// Tests valid inputs to |ValidateCreateOptions()|. +TEST(SharedBufferDispatcherTest, ValidateCreateOptionsValid) { + // Default options. + { + MojoCreateSharedBufferOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + NULL, &validated_options)); + RevalidateCreateOptions(validated_options); + } + + // Different flags. + MojoCreateSharedBufferOptionsFlags flags_values[] = { + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE + }; + for (size_t i = 0; i < arraysize(flags_values); i++) { + const MojoCreateSharedBufferOptionsFlags flags = flags_values[i]; + + // Different capacities (size 1). + for (uint32_t capacity = 1; capacity <= 100 * 1000 * 1000; capacity *= 10) { + MojoCreateSharedBufferOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags // |flags|. + }; + MojoCreateSharedBufferOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &validated_options)) + << capacity; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + } + } +} + +TEST(SharedBufferDispatcherTest, ValidateCreateOptionsInvalid) { + // Invalid |struct_size|. + { + MojoCreateSharedBufferOptions options = { + 1, // |struct_size|. + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE // |flags|. + }; + MojoCreateSharedBufferOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + SharedBufferDispatcher::ValidateCreateOptions(&options, &unused)); + } + + // Unknown |flags|. + { + MojoCreateSharedBufferOptions options = { + kSizeOfCreateOptions, // |struct_size|. + ~0u // |flags|. + }; + MojoCreateSharedBufferOptions unused; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + SharedBufferDispatcher::ValidateCreateOptions(&options, &unused)); + } +} + +TEST(SharedBufferDispatcherTest, CreateAndMapBuffer) { + scoped_refptr<SharedBufferDispatcher> dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher)); + ASSERT_TRUE(dispatcher); + EXPECT_EQ(Dispatcher::kTypeSharedBuffer, dispatcher->GetType()); + + // Make a couple of mappings. + scoped_ptr<RawSharedBufferMapping> mapping1; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, + &mapping1)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->base()); + EXPECT_EQ(100u, mapping1->length()); + // Write something. + static_cast<char*>(mapping1->base())[50] = 'x'; + + scoped_ptr<RawSharedBufferMapping> mapping2; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher->MapBuffer(50, 50, MOJO_MAP_BUFFER_FLAG_NONE, + &mapping2)); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->base()); + EXPECT_EQ(50u, mapping2->length()); + EXPECT_EQ('x', static_cast<char*>(mapping2->base())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + + // Check that we can still read/write to mappings after the dispatcher has + // gone away. + static_cast<char*>(mapping2->base())[1] = 'y'; + EXPECT_EQ('y', static_cast<char*>(mapping1->base())[51]); +} + +TEST(SharedBufferDispatcher, DuplicateBufferHandle) { + scoped_refptr<SharedBufferDispatcher> dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher1)); + + // Map and write something. + scoped_ptr<RawSharedBufferMapping> mapping; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher1->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, + &mapping)); + static_cast<char*>(mapping->base())[0] = 'x'; + mapping.reset(); + + // Duplicate |dispatcher1| and then close it. + scoped_refptr<Dispatcher> dispatcher2; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher1->DuplicateBufferHandle(NULL, &dispatcher2)); + ASSERT_TRUE(dispatcher2); + EXPECT_EQ(Dispatcher::kTypeSharedBuffer, dispatcher2->GetType()); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); + + // Map |dispatcher2| and read something. + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher2->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, + &mapping)); + EXPECT_EQ('x', static_cast<char*>(mapping->base())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close()); +} + +TEST(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) { + scoped_refptr<SharedBufferDispatcher> dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher1)); + + MojoDuplicateBufferHandleOptions options[] = { + {sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}, + {sizeof(MojoDuplicateBufferHandleOptionsFlags), ~0u} + }; + for (size_t i = 0; i < arraysize(options); i++) { + scoped_refptr<Dispatcher> dispatcher2; + EXPECT_EQ(MOJO_RESULT_OK, + dispatcher1->DuplicateBufferHandle(&options[i], &dispatcher2)); + ASSERT_TRUE(dispatcher2); + EXPECT_EQ(Dispatcher::kTypeSharedBuffer, dispatcher2->GetType()); + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close()); + } + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); +} + +TEST(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsInvalid) { + scoped_refptr<SharedBufferDispatcher> dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher1)); + + // Invalid |struct_size|. + { + MojoDuplicateBufferHandleOptions options = { + 1u, MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE + }; + scoped_refptr<Dispatcher> dispatcher2; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher1->DuplicateBufferHandle(&options, &dispatcher2)); + EXPECT_FALSE(dispatcher2); + } + + // Unknown |flags|. + { + MojoDuplicateBufferHandleOptions options = { + sizeof(MojoDuplicateBufferHandleOptions), ~0u + }; + scoped_refptr<Dispatcher> dispatcher2; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + dispatcher1->DuplicateBufferHandle(&options, &dispatcher2)); + EXPECT_FALSE(dispatcher2); + } + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); +} + +TEST(SharedBufferDispatcherTest, CreateInvalidNumBytes) { + // Size too big. + scoped_refptr<SharedBufferDispatcher> dispatcher; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + std::numeric_limits<uint64_t>::max(), &dispatcher)); + EXPECT_FALSE(dispatcher); + + // Zero size. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 0, &dispatcher)); + EXPECT_FALSE(dispatcher); +} + +TEST(SharedBufferDispatcherTest, MapBufferInvalidArguments) { + scoped_refptr<SharedBufferDispatcher> dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, 100, + &dispatcher)); + + scoped_ptr<RawSharedBufferMapping> mapping; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(0, 101, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(1, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(0, 0, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/simple_dispatcher.cc b/chromium/mojo/system/simple_dispatcher.cc new file mode 100644 index 00000000000..3595815c571 --- /dev/null +++ b/chromium/mojo/system/simple_dispatcher.cc @@ -0,0 +1,49 @@ +// 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 "mojo/system/simple_dispatcher.h" + +#include "base/logging.h" + +namespace mojo { +namespace system { + +SimpleDispatcher::SimpleDispatcher() { +} + +SimpleDispatcher::~SimpleDispatcher() { +} + +void SimpleDispatcher::HandleSignalsStateChangedNoLock() { + lock().AssertAcquired(); + waiter_list_.AwakeWaitersForStateChange(GetHandleSignalsStateNoLock()); +} + +void SimpleDispatcher::CancelAllWaitersNoLock() { + lock().AssertAcquired(); + waiter_list_.CancelAllWaiters(); +} + +MojoResult SimpleDispatcher::AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + lock().AssertAcquired(); + + HandleSignalsState state(GetHandleSignalsStateNoLock()); + if (state.satisfies(signals)) + return MOJO_RESULT_ALREADY_EXISTS; + if (!state.can_satisfy(signals)) + return MOJO_RESULT_FAILED_PRECONDITION; + + waiter_list_.AddWaiter(waiter, signals, context); + return MOJO_RESULT_OK; +} + +void SimpleDispatcher::RemoveWaiterImplNoLock(Waiter* waiter) { + lock().AssertAcquired(); + waiter_list_.RemoveWaiter(waiter); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/simple_dispatcher.h b/chromium/mojo/system/simple_dispatcher.h new file mode 100644 index 00000000000..65d52fed5ab --- /dev/null +++ b/chromium/mojo/system/simple_dispatcher.h @@ -0,0 +1,53 @@ +// 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 MOJO_SYSTEM_SIMPLE_DISPATCHER_H_ +#define MOJO_SYSTEM_SIMPLE_DISPATCHER_H_ + +#include <list> + +#include "base/basictypes.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/handle_signals_state.h" +#include "mojo/system/system_impl_export.h" +#include "mojo/system/waiter_list.h" + +namespace mojo { +namespace system { + +// A base class for simple dispatchers. "Simple" means that there's a one-to-one +// correspondence between handles and dispatchers (see the explanatory comment +// in core.cc). This class implements the standard waiter-signalling mechanism +// in that case. +class MOJO_SYSTEM_IMPL_EXPORT SimpleDispatcher : public Dispatcher { + protected: + SimpleDispatcher(); + virtual ~SimpleDispatcher(); + + // To be called by subclasses when the state changes (so + // |GetHandleSignalsStateNoLock()| should be checked again). Must be called + // under lock. + void HandleSignalsStateChangedNoLock(); + + // Never called after the dispatcher has been closed; called under |lock_|. + virtual HandleSignalsState GetHandleSignalsStateNoLock() const = 0; + + // |Dispatcher| protected methods: + virtual void CancelAllWaitersNoLock() OVERRIDE; + virtual MojoResult AddWaiterImplNoLock(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) OVERRIDE; + virtual void RemoveWaiterImplNoLock(Waiter* waiter) OVERRIDE; + + private: + // Protected by |lock()|: + WaiterList waiter_list_; + + DISALLOW_COPY_AND_ASSIGN(SimpleDispatcher); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_SIMPLE_DISPATCHER_H_ diff --git a/chromium/mojo/system/simple_dispatcher_unittest.cc b/chromium/mojo/system/simple_dispatcher_unittest.cc new file mode 100644 index 00000000000..00c39654ef5 --- /dev/null +++ b/chromium/mojo/system/simple_dispatcher_unittest.cc @@ -0,0 +1,541 @@ +// 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. + +// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a +// heavily-loaded system). Sorry. |test::EpsilonTimeout()| may be increased to +// increase tolerance and reduce observed flakiness (though doing so reduces the +// meaningfulness of the test). + +#include "mojo/system/simple_dispatcher.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_vector.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "base/time/time.h" +#include "mojo/system/test_utils.h" +#include "mojo/system/waiter.h" +#include "mojo/system/waiter_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +class MockSimpleDispatcher : public SimpleDispatcher { + public: + MockSimpleDispatcher() + : state_(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE) {} + + void SetSatisfiedSignals(MojoHandleSignals new_satisfied_signals) { + base::AutoLock locker(lock()); + + // Any new signals that are set should be satisfiable. + CHECK_EQ(new_satisfied_signals & ~state_.satisfied_signals, + new_satisfied_signals & ~state_.satisfied_signals & + state_.satisfiable_signals); + + if (new_satisfied_signals == state_.satisfied_signals) + return; + + state_.satisfied_signals = new_satisfied_signals; + HandleSignalsStateChangedNoLock(); + } + + void SetSatisfiableSignals(MojoHandleSignals new_satisfiable_signals) { + base::AutoLock locker(lock()); + + // Satisfied implies satisfiable. + CHECK_EQ(new_satisfiable_signals & state_.satisfied_signals, + state_.satisfied_signals); + + if (new_satisfiable_signals == state_.satisfiable_signals) + return; + + state_.satisfiable_signals = new_satisfiable_signals; + HandleSignalsStateChangedNoLock(); + } + + virtual Type GetType() const OVERRIDE { + return kTypeUnknown; + } + + private: + friend class base::RefCountedThreadSafe<MockSimpleDispatcher>; + virtual ~MockSimpleDispatcher() {} + + virtual scoped_refptr<Dispatcher> + CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE { + scoped_refptr<MockSimpleDispatcher> rv(new MockSimpleDispatcher()); + rv->state_ = state_; + return scoped_refptr<Dispatcher>(rv.get()); + } + + // |SimpleDispatcher| implementation: + virtual HandleSignalsState GetHandleSignalsStateNoLock() const OVERRIDE { + lock().AssertAcquired(); + return state_; + } + + // Protected by |lock()|: + HandleSignalsState state_; + + DISALLOW_COPY_AND_ASSIGN(MockSimpleDispatcher); +}; + +TEST(SimpleDispatcherTest, Basic) { + test::Stopwatch stopwatch; + + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + Waiter w; + uint32_t context = 0; + + // Try adding a readable waiter when already readable. + w.Init(); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 0)); + // Shouldn't need to remove the waiter (it was not added). + + // Wait (forever) for writable when already writable. + w.Init(); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1)); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(1u, context); + d->RemoveWaiter(&w); + + // Wait for zero time for writable when already writable. + w.Init(); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2)); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_OK, w.Wait(0, &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(2u, context); + d->RemoveWaiter(&w); + + // Wait for non-zero, finite time for writable when already writable. + w.Init(); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3)); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_OK, + w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(3u, context); + d->RemoveWaiter(&w); + + // Wait for zero time for writable when not writable (will time out). + w.Init(); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4)); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, NULL)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + d->RemoveWaiter(&w); + + // Wait for non-zero, finite time for writable when not writable (will time + // out). + w.Init(); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 5)); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), NULL)); + base::TimeDelta elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + d->RemoveWaiter(&w); + + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); +} + +TEST(SimpleDispatcherTest, BasicUnsatisfiable) { + test::Stopwatch stopwatch; + + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + Waiter w; + uint32_t context = 0; + + // Try adding a writable waiter when it can never be writable. + w.Init(); + d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE); + d->SetSatisfiedSignals(0); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1)); + // Shouldn't need to remove the waiter (it was not added). + + // Wait (forever) for writable and then it becomes never writable. + w.Init(); + d->SetSatisfiableSignals( + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2)); + d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + w.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(2u, context); + d->RemoveWaiter(&w); + + // Wait for zero time for writable and then it becomes never writable. + w.Init(); + d->SetSatisfiableSignals( + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3)); + d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, w.Wait(0, &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(3u, context); + d->RemoveWaiter(&w); + + // Wait for non-zero, finite time for writable and then it becomes never + // writable. + w.Init(); + d->SetSatisfiableSignals( + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4)); + d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(4u, context); + d->RemoveWaiter(&w); + + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); +} + +TEST(SimpleDispatcherTest, BasicClosed) { + test::Stopwatch stopwatch; + + scoped_refptr<MockSimpleDispatcher> d; + Waiter w; + uint32_t context = 0; + + // Try adding a writable waiter when the dispatcher has been closed. + d = new MockSimpleDispatcher(); + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1)); + // Shouldn't need to remove the waiter (it was not added). + + // Wait (forever) for writable and then the dispatcher is closed. + d = new MockSimpleDispatcher(); + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2)); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_CANCELLED, w.Wait(MOJO_DEADLINE_INDEFINITE, &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(2u, context); + // Don't need to remove waiters from closed dispatchers. + + // Wait for zero time for writable and then the dispatcher is closed. + d = new MockSimpleDispatcher(); + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3)); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_CANCELLED, w.Wait(0, &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(3u, context); + // Don't need to remove waiters from closed dispatchers. + + // Wait for non-zero, finite time for writable and then the dispatcher is + // closed. + d = new MockSimpleDispatcher(); + w.Init(); + EXPECT_EQ(MOJO_RESULT_OK, d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4)); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_CANCELLED, + w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), &context)); + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_EQ(4u, context); + // Don't need to remove waiters from closed dispatchers. +} + +TEST(SimpleDispatcherTest, BasicThreaded) { + test::Stopwatch stopwatch; + bool did_wait; + MojoResult result; + uint32_t context; + + // Wait for readable (already readable). + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + { + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + test::WaiterThread thread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 1, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + } // Joins the thread. + // If we closed earlier, then probably we'd get a |MOJO_RESULT_CANCELLED|. + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } + EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout()); + EXPECT_FALSE(did_wait); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, result); + + // Wait for readable and becomes readable after some time. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + test::WaiterThread thread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 2, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the thread. + base::TimeDelta elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(2u, context); + + // Wait for readable and becomes never-readable after some time. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + test::WaiterThread thread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 3, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_NONE); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the thread. + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(3u, context); + + // Wait for readable and dispatcher gets closed. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + test::WaiterThread thread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + 4, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the thread. + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(4u, context); + + // Wait for readable and times out. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + { + test::WaiterThread thread(d, + MOJO_HANDLE_SIGNAL_READABLE, + 2 * test::EpsilonTimeout().InMicroseconds(), + 5, + &did_wait, &result, &context); + stopwatch.Start(); + thread.Start(); + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + // Not what we're waiting for. + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE); + } // Joins the thread (after its wait times out). + // If we closed earlier, then probably we'd get a |MOJO_RESULT_CANCELLED|. + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_TRUE(did_wait); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result); +} + +TEST(SimpleDispatcherTest, MultipleWaiters) { + static const uint32_t kNumWaiters = 20; + + bool did_wait[kNumWaiters]; + MojoResult result[kNumWaiters]; + uint32_t context[kNumWaiters]; + + // All wait for readable and becomes readable after some time. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + ScopedVector<test::WaiterThread> threads; + for (uint32_t i = 0; i < kNumWaiters; i++) { + threads.push_back(new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + i, + &did_wait[i], + &result[i], + &context[i])); + threads.back()->Start(); + } + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the threads. + for (uint32_t i = 0; i < kNumWaiters; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_OK, result[i]); + EXPECT_EQ(i, context[i]); + } + + // Some wait for readable, some for writable, and becomes readable after some + // time. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + ScopedVector<test::WaiterThread> threads; + for (uint32_t i = 0; i < kNumWaiters / 2; i++) { + threads.push_back(new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + i, + &did_wait[i], + &result[i], + &context[i])); + threads.back()->Start(); + } + for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) { + threads.push_back(new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_DEADLINE_INDEFINITE, + i, + &did_wait[i], + &result[i], + &context[i])); + threads.back()->Start(); + } + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + // This will wake up the ones waiting to write. + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the threads. + for (uint32_t i = 0; i < kNumWaiters / 2; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_OK, result[i]); + EXPECT_EQ(i, context[i]); + } + for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result[i]); + EXPECT_EQ(i, context[i]); + } + + // Some wait for readable, some for writable, and becomes readable and + // never-writable after some time. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + ScopedVector<test::WaiterThread> threads; + for (uint32_t i = 0; i < kNumWaiters / 2; i++) { + threads.push_back(new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_DEADLINE_INDEFINITE, + i, + &did_wait[i], + &result[i], + &context[i])); + threads.back()->Start(); + } + for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) { + threads.push_back(new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_DEADLINE_INDEFINITE, + i, + &did_wait[i], + &result[i], + &context[i])); + threads.back()->Start(); + } + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE); + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the threads. + for (uint32_t i = 0; i < kNumWaiters / 2; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_OK, result[i]); + EXPECT_EQ(i, context[i]); + } + for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result[i]); + EXPECT_EQ(i, context[i]); + } + + // Some wait for readable, some for writable, and becomes readable after some + // time. + { + scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher()); + ScopedVector<test::WaiterThread> threads; + for (uint32_t i = 0; i < kNumWaiters / 2; i++) { + threads.push_back( + new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_READABLE, + 3 * test::EpsilonTimeout().InMicroseconds(), + i, + &did_wait[i], &result[i], &context[i])); + threads.back()->Start(); + } + for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) { + threads.push_back( + new test::WaiterThread(d, + MOJO_HANDLE_SIGNAL_WRITABLE, + 1 * test::EpsilonTimeout().InMicroseconds(), + i, + &did_wait[i], &result[i], &context[i])); + threads.back()->Start(); + } + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE); + // All those waiting for writable should have timed out. + EXPECT_EQ(MOJO_RESULT_OK, d->Close()); + } // Joins the threads. + for (uint32_t i = 0; i < kNumWaiters / 2; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_OK, result[i]); + EXPECT_EQ(i, context[i]); + } + for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) { + EXPECT_TRUE(did_wait[i]); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result[i]); + } +} + +// TODO(vtl): Stress test? + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/system_impl_export.h b/chromium/mojo/system/system_impl_export.h new file mode 100644 index 00000000000..a35eacfe5a5 --- /dev/null +++ b/chromium/mojo/system/system_impl_export.h @@ -0,0 +1,29 @@ +// 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 MOJO_SYSTEM_SYSTEM_IMPL_EXPORT_H_ +#define MOJO_SYSTEM_SYSTEM_IMPL_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) +#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllexport) +#else +#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllimport) +#endif // defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) +#define MOJO_SYSTEM_IMPL_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SYSTEM_IMPL_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_SYSTEM_IMPL_EXPORT +#endif + +#endif // MOJO_SYSTEM_SYSTEM_IMPL_EXPORT_H_ diff --git a/chromium/mojo/system/test_utils.cc b/chromium/mojo/system/test_utils.cc new file mode 100644 index 00000000000..48f109138f0 --- /dev/null +++ b/chromium/mojo/system/test_utils.cc @@ -0,0 +1,96 @@ +// 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 "mojo/system/test_utils.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/test_timeouts.h" +#include "build/build_config.h" + +namespace mojo { +namespace system { +namespace test { + +namespace { + +void PostTaskAndWaitHelper(base::WaitableEvent* event, + const base::Closure& task) { + task.Run(); + event->Signal(); +} + +} // namespace + +void PostTaskAndWait(scoped_refptr<base::TaskRunner> task_runner, + const tracked_objects::Location& from_here, + const base::Closure& task) { + base::WaitableEvent event(false, false); + task_runner->PostTask(from_here, + base::Bind(&PostTaskAndWaitHelper, &event, task)); + event.Wait(); +} + +base::TimeDelta EpsilonTimeout() { + // Originally, our epsilon timeout was 10 ms, which was mostly fine but flaky + // on some Windows bots. I don't recall ever seeing flakes on other bots. At + // 30 ms tests seem reliable on Windows bots, but not at 25 ms. We'd like this + // timeout to be as small as possible (see the description in the .h file). + // + // Currently, |tiny_timeout()| is usually 100 ms (possibly scaled under ASAN, + // etc.). Based on this, set it to (usually be) 30 ms on Windows and 20 ms + // elsewhere. +#if defined(OS_WIN) + return (TestTimeouts::tiny_timeout() * 3) / 10; +#else + return (TestTimeouts::tiny_timeout() * 2) / 10; +#endif +} + +// TestIOThread ---------------------------------------------------------------- + +TestIOThread::TestIOThread(Mode mode) + : io_thread_("test_io_thread"), + io_thread_started_(false) { + switch (mode) { + case kAutoStart: + Start(); + return; + case kManualStart: + return; + } + CHECK(false) << "Invalid mode"; +} + +TestIOThread::~TestIOThread() { + Stop(); +} + +void TestIOThread::Start() { + CHECK(!io_thread_started_); + io_thread_started_ = true; + CHECK(io_thread_.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO, 0))); +} + +void TestIOThread::Stop() { + // Note: It's okay to call |Stop()| even if the thread isn't running. + io_thread_.Stop(); + io_thread_started_ = false; +} + +void TestIOThread::PostTask(const tracked_objects::Location& from_here, + const base::Closure& task) { + task_runner()->PostTask(from_here, task); +} + +void TestIOThread::PostTaskAndWait(const tracked_objects::Location& from_here, + const base::Closure& task) { + ::mojo::system::test::PostTaskAndWait(task_runner(), from_here, task); +} + +} // namespace test +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/test_utils.h b/chromium/mojo/system/test_utils.h new file mode 100644 index 00000000000..c1a1a1e0b47 --- /dev/null +++ b/chromium/mojo/system/test_utils.h @@ -0,0 +1,100 @@ +// 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 MOJO_SYSTEM_TEST_UTILS_H_ +#define MOJO_SYSTEM_TEST_UTILS_H_ + +#include <stdint.h> + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/task_runner.h" +#include "base/threading/thread.h" +#include "base/time/time.h" + +namespace tracked_objects { +class Location; +} + +namespace mojo { +namespace system { +namespace test { + +// Posts the given task (to the given task runner) and waits for it to complete. +// (Note: Doesn't spin the current thread's message loop, so if you're careless +// this could easily deadlock.) +void PostTaskAndWait(scoped_refptr<base::TaskRunner> task_runner, + const tracked_objects::Location& from_here, + const base::Closure& task); + +// A timeout smaller than |TestTimeouts::tiny_timeout()|. Warning: This may lead +// to flakiness, but this is unavoidable if, e.g., you're trying to ensure that +// functions with timeouts are reasonably accurate. We want this to be as small +// as possible without causing too much flakiness. +base::TimeDelta EpsilonTimeout(); + +// Stopwatch ------------------------------------------------------------------- + +// A simple "stopwatch" for measuring time elapsed from a given starting point. +class Stopwatch { + public: + Stopwatch() {} + ~Stopwatch() {} + + void Start() { + start_time_ = base::TimeTicks::HighResNow(); + } + + base::TimeDelta Elapsed() { + return base::TimeTicks::HighResNow() - start_time_; + } + + private: + base::TimeTicks start_time_; + + DISALLOW_COPY_AND_ASSIGN(Stopwatch); +}; + +// TestIOThread ---------------------------------------------------------------- + +class TestIOThread { + public: + enum Mode { kAutoStart, kManualStart }; + explicit TestIOThread(Mode mode); + // Stops the I/O thread if necessary. + ~TestIOThread(); + + // |Start()|/|Stop()| should only be called from the main (creation) thread. + // After |Stop()|, |Start()| may be called again to start a new I/O thread. + // |Stop()| may be called even when the I/O thread is not started. + void Start(); + void Stop(); + + void PostTask(const tracked_objects::Location& from_here, + const base::Closure& task); + void PostTaskAndWait(const tracked_objects::Location& from_here, + const base::Closure& task); + + base::MessageLoopForIO* message_loop() { + return static_cast<base::MessageLoopForIO*>(io_thread_.message_loop()); + } + + scoped_refptr<base::TaskRunner> task_runner() { + return message_loop()->message_loop_proxy(); + } + + private: + base::Thread io_thread_; + bool io_thread_started_; + + DISALLOW_COPY_AND_ASSIGN(TestIOThread); +}; + +} // namespace test +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_TEST_UTILS_H_ diff --git a/chromium/mojo/system/transport_data.cc b/chromium/mojo/system/transport_data.cc new file mode 100644 index 00000000000..10a3228ecf8 --- /dev/null +++ b/chromium/mojo/system/transport_data.cc @@ -0,0 +1,345 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/system/transport_data.h" + +#include <string.h> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/system/channel.h" +#include "mojo/system/constants.h" +#include "mojo/system/message_in_transit.h" + +namespace mojo { +namespace system { + +// The maximum amount of space needed per platform handle. +// (|{Channel,RawChannel}::GetSerializedPlatformHandleSize()| should always +// return a value which is at most this. This is only used to calculate +// |TransportData::kMaxBufferSize|. This value should be a multiple of the +// alignment in order to simplify calculations, even though the actual amount of +// space needed need not be a multiple of the alignment. +const size_t kMaxSizePerPlatformHandle = 8; +COMPILE_ASSERT(kMaxSizePerPlatformHandle % + MessageInTransit::kMessageAlignment == 0, + kMaxSizePerPlatformHandle_not_a_multiple_of_alignment); + +STATIC_CONST_MEMBER_DEFINITION const size_t + TransportData::kMaxSerializedDispatcherSize; +STATIC_CONST_MEMBER_DEFINITION const size_t + TransportData::kMaxSerializedDispatcherPlatformHandles; + +// static +const size_t TransportData::kMaxPlatformHandles = + kMaxMessageNumHandles * kMaxSerializedDispatcherPlatformHandles; + +// In additional to the header, for each attached (Mojo) handle there'll be a +// handle table entry and serialized dispatcher data. +// Note: This definition must follow the one for |kMaxPlatformHandles|; +// otherwise, we get a static initializer with gcc (but not clang). +// static +const size_t TransportData::kMaxBufferSize = + sizeof(Header) + + kMaxMessageNumHandles * (sizeof(HandleTableEntry) + + kMaxSerializedDispatcherSize) + + kMaxPlatformHandles * kMaxSizePerPlatformHandle; + +struct TransportData::PrivateStructForCompileAsserts { + // The size of |Header| must be a multiple of the alignment. + COMPILE_ASSERT(sizeof(Header) % MessageInTransit::kMessageAlignment == 0, + sizeof_MessageInTransit_Header_invalid); + + // The maximum serialized dispatcher size must be a multiple of the alignment. + COMPILE_ASSERT(kMaxSerializedDispatcherSize % + MessageInTransit::kMessageAlignment == 0, + kMaxSerializedDispatcherSize_not_a_multiple_of_alignment); + + // The size of |HandleTableEntry| must be a multiple of the alignment. + COMPILE_ASSERT(sizeof(HandleTableEntry) % + MessageInTransit::kMessageAlignment == 0, + sizeof_MessageInTransit_HandleTableEntry_invalid); +}; + +TransportData::TransportData(scoped_ptr<DispatcherVector> dispatchers, + Channel* channel) { + DCHECK(dispatchers); + DCHECK(channel); + + const size_t num_handles = dispatchers->size(); + DCHECK_GT(num_handles, 0u); + + // The offset to the start of the (Mojo) handle table. + const size_t handle_table_start_offset = sizeof(Header); + // The offset to the start of the serialized dispatcher data. + const size_t serialized_dispatcher_start_offset = + handle_table_start_offset + num_handles * sizeof(HandleTableEntry); + // The estimated size of the secondary buffer. We compute this estimate below. + // It must be at least as big as the (eventual) actual size. + size_t estimated_size = serialized_dispatcher_start_offset; + size_t estimated_num_platform_handles = 0; +#if DCHECK_IS_ON + std::vector<size_t> all_max_sizes(num_handles); + std::vector<size_t> all_max_platform_handles(num_handles); +#endif + for (size_t i = 0; i < num_handles; i++) { + if (Dispatcher* dispatcher = (*dispatchers)[i].get()) { + size_t max_size = 0; + size_t max_platform_handles = 0; + Dispatcher::TransportDataAccess::StartSerialize( + dispatcher, channel, &max_size, &max_platform_handles); + + DCHECK_LE(max_size, kMaxSerializedDispatcherSize); + estimated_size += MessageInTransit::RoundUpMessageAlignment(max_size); + DCHECK_LE(estimated_size, kMaxBufferSize); + + DCHECK_LE(max_platform_handles, + kMaxSerializedDispatcherPlatformHandles); + estimated_num_platform_handles += max_platform_handles; + DCHECK_LE(estimated_num_platform_handles, kMaxPlatformHandles); + +#if DCHECK_IS_ON + all_max_sizes[i] = max_size; + all_max_platform_handles[i] = max_platform_handles; +#endif + } + } + + size_t size_per_platform_handle = 0; + if (estimated_num_platform_handles > 0) { + size_per_platform_handle = channel->GetSerializedPlatformHandleSize(); + DCHECK_LE(size_per_platform_handle, kMaxSizePerPlatformHandle); + estimated_size += estimated_num_platform_handles * size_per_platform_handle; + estimated_size = MessageInTransit::RoundUpMessageAlignment(estimated_size); + DCHECK_LE(estimated_size, kMaxBufferSize); + } + + buffer_.reset(static_cast<char*>( + base::AlignedAlloc(estimated_size, MessageInTransit::kMessageAlignment))); + // Entirely clear out the secondary buffer, since then we won't have to worry + // about clearing padding or unused space (e.g., if a dispatcher fails to + // serialize). + memset(buffer_.get(), 0, estimated_size); + + if (estimated_num_platform_handles > 0) { + DCHECK(!platform_handles_); + platform_handles_.reset(new embedder::PlatformHandleVector()); + } + + Header* header = reinterpret_cast<Header*>(buffer_.get()); + header->num_handles = static_cast<uint32_t>(num_handles); + // (Okay to leave |platform_handle_table_offset|, |num_platform_handles|, and + // |unused| be zero; we'll set the former two later if necessary.) + + HandleTableEntry* handle_table = reinterpret_cast<HandleTableEntry*>( + buffer_.get() + handle_table_start_offset); + size_t current_offset = serialized_dispatcher_start_offset; + for (size_t i = 0; i < num_handles; i++) { + Dispatcher* dispatcher = (*dispatchers)[i].get(); + if (!dispatcher) { + COMPILE_ASSERT(Dispatcher::kTypeUnknown == 0, + value_of_Dispatcher_kTypeUnknown_must_be_zero); + continue; + } + +#if DCHECK_IS_ON + size_t old_platform_handles_size = + platform_handles_ ? platform_handles_->size() : 0; +#endif + + void* destination = buffer_.get() + current_offset; + size_t actual_size = 0; + if (Dispatcher::TransportDataAccess::EndSerializeAndClose( + dispatcher, channel, destination, &actual_size, + platform_handles_.get())) { + handle_table[i].type = static_cast<int32_t>(dispatcher->GetType()); + handle_table[i].offset = static_cast<uint32_t>(current_offset); + handle_table[i].size = static_cast<uint32_t>(actual_size); + // (Okay to not set |unused| since we cleared the entire buffer.) + +#if DCHECK_IS_ON + DCHECK_LE(actual_size, all_max_sizes[i]); + DCHECK_LE(platform_handles_ ? (platform_handles_->size() - + old_platform_handles_size) : 0, + all_max_platform_handles[i]); +#endif + } else { + // Nothing to do on failure, since |buffer_| was cleared, and + // |kTypeUnknown| is zero. The handle was simply closed. + LOG(ERROR) << "Failed to serialize handle to remote message pipe"; + } + + current_offset += MessageInTransit::RoundUpMessageAlignment(actual_size); + DCHECK_LE(current_offset, estimated_size); + DCHECK_LE(platform_handles_ ? platform_handles_->size() : 0, + estimated_num_platform_handles); + } + + if (platform_handles_ && platform_handles_->size() > 0) { + header->platform_handle_table_offset = + static_cast<uint32_t>(current_offset); + header->num_platform_handles = + static_cast<uint32_t>(platform_handles_->size()); + current_offset += platform_handles_->size() * size_per_platform_handle; + current_offset = MessageInTransit::RoundUpMessageAlignment(current_offset); + } + + // There's no aligned realloc, so it's no good way to release unused space (if + // we overshot our estimated space requirements). + buffer_size_ = current_offset; + + // |dispatchers_| will be destroyed as it goes out of scope. +} + +#if defined(OS_POSIX) +TransportData::TransportData( + embedder::ScopedPlatformHandleVectorPtr platform_handles) + : buffer_size_(sizeof(Header)), + platform_handles_(platform_handles.Pass()) { + buffer_.reset(static_cast<char*>( + base::AlignedAlloc(buffer_size_, MessageInTransit::kMessageAlignment))); + memset(buffer_.get(), 0, buffer_size_); +} +#endif // defined(OS_POSIX) + +TransportData::~TransportData() { +} + +// static +const char* TransportData::ValidateBuffer( + size_t serialized_platform_handle_size, + const void* buffer, + size_t buffer_size) { + DCHECK(buffer); + DCHECK_GT(buffer_size, 0u); + + // Always make sure that the buffer size is sane; if it's not, someone's + // messing with us. + if (buffer_size < sizeof(Header) || buffer_size > kMaxBufferSize || + buffer_size % MessageInTransit::kMessageAlignment != 0) + return "Invalid message secondary buffer size"; + + const Header* header = static_cast<const Header*>(buffer); + const size_t num_handles = header->num_handles; + +#if !defined(OS_POSIX) + // On POSIX, we send control messages with platform handles (but no handles) + // attached (see the comments for + // |TransportData(embedder::ScopedPlatformHandleVectorPtr)|. (This check isn't + // important security-wise anyway.) + if (num_handles == 0) + return "Message has no handles attached, but secondary buffer present"; +#endif + + // Sanity-check |num_handles| (before multiplying it against anything). + if (num_handles > kMaxMessageNumHandles) + return "Message handle payload too large"; + + if (buffer_size < sizeof(Header) + num_handles * sizeof(HandleTableEntry)) + return "Message secondary buffer too small"; + + if (header->num_platform_handles == 0) { + // Then |platform_handle_table_offset| should also be zero. + if (header->platform_handle_table_offset != 0) { + return + "Message has no handles attached, but platform handle table present"; + } + } else { + // |num_handles| has already been validated, so the multiplication is okay. + if (header->num_platform_handles > + num_handles * kMaxSerializedDispatcherPlatformHandles) + return "Message has too many platform handles attached"; + + static const char kInvalidPlatformHandleTableOffset[] = + "Message has invalid platform handle table offset"; + // This doesn't check that the platform handle table doesn't alias other + // stuff, but it doesn't matter, since it's all read-only. + if (header->platform_handle_table_offset % + MessageInTransit::kMessageAlignment != 0) + return kInvalidPlatformHandleTableOffset; + + // ">" instead of ">=" since the size per handle may be zero. + if (header->platform_handle_table_offset > buffer_size) + return kInvalidPlatformHandleTableOffset; + + // We already checked |platform_handle_table_offset| and + // |num_platform_handles|, so the addition and multiplication are okay. + if (header->platform_handle_table_offset + + header->num_platform_handles * serialized_platform_handle_size > + buffer_size) + return kInvalidPlatformHandleTableOffset; + } + + const HandleTableEntry* handle_table = + reinterpret_cast<const HandleTableEntry*>( + static_cast<const char*>(buffer) + sizeof(Header)); + static const char kInvalidSerializedDispatcher[] = + "Message contains invalid serialized dispatcher"; + for (size_t i = 0; i < num_handles; i++) { + size_t offset = handle_table[i].offset; + if (offset % MessageInTransit::kMessageAlignment != 0) + return kInvalidSerializedDispatcher; + + size_t size = handle_table[i].size; + if (size > kMaxSerializedDispatcherSize || size > buffer_size) + return kInvalidSerializedDispatcher; + + // Note: This is an overflow-safe check for |offset + size > buffer_size| + // (we know that |size <= buffer_size| from the previous check). + if (offset > buffer_size - size) + return kInvalidSerializedDispatcher; + } + + return NULL; +} + +// static +void TransportData::GetPlatformHandleTable(const void* transport_data_buffer, + size_t* num_platform_handles, + const void** platform_handle_table) { + DCHECK(transport_data_buffer); + DCHECK(num_platform_handles); + DCHECK(platform_handle_table); + + const Header* header = static_cast<const Header*>(transport_data_buffer); + *num_platform_handles = header->num_platform_handles; + *platform_handle_table = static_cast<const char*>(transport_data_buffer) + + header->platform_handle_table_offset; +} + +// static +scoped_ptr<DispatcherVector> TransportData::DeserializeDispatchers( + const void* buffer, + size_t buffer_size, + embedder::ScopedPlatformHandleVectorPtr platform_handles, + Channel* channel) { + DCHECK(buffer); + DCHECK_GT(buffer_size, 0u); + DCHECK(channel); + + const Header* header = static_cast<const Header*>(buffer); + const size_t num_handles = header->num_handles; + scoped_ptr<DispatcherVector> dispatchers(new DispatcherVector(num_handles)); + + const HandleTableEntry* handle_table = + reinterpret_cast<const HandleTableEntry*>( + static_cast<const char*>(buffer) + sizeof(Header)); + for (size_t i = 0; i < num_handles; i++) { + size_t offset = handle_table[i].offset; + size_t size = handle_table[i].size; + // Should already have been checked by |ValidateBuffer()|: + DCHECK_EQ(offset % MessageInTransit::kMessageAlignment, 0u); + DCHECK_LE(offset, buffer_size); + DCHECK_LE(offset + size, buffer_size); + + const void* source = static_cast<const char*>(buffer) + offset; + (*dispatchers)[i] = Dispatcher::TransportDataAccess::Deserialize( + channel, handle_table[i].type, source, size, platform_handles.get()); + } + + return dispatchers.Pass(); +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/transport_data.h b/chromium/mojo/system/transport_data.h new file mode 100644 index 00000000000..ddaa20b616b --- /dev/null +++ b/chromium/mojo/system/transport_data.h @@ -0,0 +1,192 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_SYSTEM_TRANSPORT_DATA_H_ +#define MOJO_SYSTEM_TRANSPORT_DATA_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "base/memory/aligned_memory.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "mojo/embedder/platform_handle.h" +#include "mojo/embedder/platform_handle_vector.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Channel; + +// This class is used by |MessageInTransit| to represent handles (|Dispatcher|s) +// in various stages of serialization. +// +// The stages are: +// - Before reaching |TransportData|: Turn |DispatcherTransport|s into +// |Dispatcher|s that are "owned" by (and attached to) a |MessageInTransit|. +// This invalidates the handles in the space of the sending application +// (and, e.g., if another thread is waiting on such a handle, it'll be +// notified of this invalidation). +// - Serialize these dispatchers into the |TransportData|: First, for each +// attached dispatcher, there's an entry in the |TransportData|'s "handle +// table", which points to a segment of (dispatcher-type-dependent) data. +// - During the serialization of the dispatchers, |PlatformHandle|s may be +// detached from the dispatchers and attached to the |TransportData|. +// - Before sending the |MessageInTransit|, including its main buffer and the +// |TransportData|'s buffer, the |Channel| sends any |PlatformHandle|s (in a +// platform-, and possibly sandbox-situation-, specific way) first. In doing +// so, it appends a "platform handle table" to the |TransportData| +// containing information about how to deserialize these |PlatformHandle|s. +// - Finally, at this point, to send the |MessageInTransit|, there only +// remains "inert" data: the |MessageInTransit|'s main buffer and data from +// the |TransportData|, consisting of the "handle table" (one entry for each +// attached dispatcher), dispatcher-type-specific data (one segment for each +// entry in the "handle table"), and the "platform handle table" (one entry +// for each attached |PlatformHandle|). +// +// To receive a message (|MessageInTransit|), the "reverse" happens: +// - On POSIX, receive and buffer |PlatformHandle|s (i.e., FDs), which were +// sent before the "inert" data. +// - Receive the "inert" data from the |MessageInTransit|. Examine its +// "platform handle table". On POSIX, match its entries with the buffered +// |PlatformHandle|s, which were previously received. On Windows, do what's +// necessary to obtain |PlatformHandle|s (e.g.: i. if the sender is fully +// trusted and able to duplicate handle into the receiver, then just pick +// out the |HANDLE| value; ii. if the receiver is fully trusted and able to +// duplicate handles from the receiver, do the |DuplicateHandle()|; iii. +// otherwise, talk to a broker to get handles). Reattach all the +// |PlatformHandle|s to the |MessageInTransit|. +// - For each entry in the "handle table", use serialized dispatcher data to +// reconstitute a dispatcher, taking ownership of associated +// |PlatformHandle|s (and detaching them). Attach these dispatchers to the +// |MessageInTransit|. +// - At this point, the |MessageInTransit| consists of its main buffer +// (primarily the data payload) and the attached dispatchers; the +// |TransportData| can be discarded. +// - When |MojoReadMessage()| is to give data to the application, attach the +// dispatchers to the (global, "core") handle table, getting handles; give +// the application the data payload and these handles. +// +// TODO(vtl): Everything above involving |PlatformHandle|s. +class MOJO_SYSTEM_IMPL_EXPORT TransportData { + public: + // The maximum size of a single serialized dispatcher. This must be a multiple + // of |kMessageAlignment|. + static const size_t kMaxSerializedDispatcherSize = 10000; + + // The maximum number of platform handles to attach for a single serialized + // dispatcher. + static const size_t kMaxSerializedDispatcherPlatformHandles = 2; + + // The maximum possible size of a valid transport data buffer. + static const size_t kMaxBufferSize; + + // The maximum total number of platform handles that may be attached. + static const size_t kMaxPlatformHandles; + + TransportData(scoped_ptr<DispatcherVector> dispatchers, Channel* channel); + +#if defined(OS_POSIX) + // This is a hacky POSIX-only constructor to directly attach only platform + // handles to a message, used by |RawChannelPosix| to split messages with too + // many platform handles into multiple messages. |Header| will be present, but + // be zero. (No other information will be attached, and + // |RawChannel::GetSerializedPlatformHandleSize()| should return zero.) + explicit TransportData( + embedder::ScopedPlatformHandleVectorPtr platform_handles); +#endif + + ~TransportData(); + + const void* buffer() const { return buffer_.get(); } + void* buffer() { return buffer_.get(); } + size_t buffer_size() const { return buffer_size_; } + + uint32_t platform_handle_table_offset() const { + return header()->platform_handle_table_offset; + } + + // Gets attached platform-specific handles; this may return null if there are + // none. Note that the caller may mutate the set of platform-specific handles. + const embedder::PlatformHandleVector* platform_handles() const { + return platform_handles_.get(); + } + embedder::PlatformHandleVector* platform_handles() { + return platform_handles_.get(); + } + + // Receive-side functions: + + // Checks if the given buffer (from the "wire") looks like a valid + // |TransportData| buffer. (Should only be called if |buffer_size| is + // nonzero.) Returns null if valid, and a pointer to a human-readable error + // message (for debug/logging purposes) on error. Note: This checks the + // validity of the handle table entries (i.e., does range checking), but does + // not check that the validity of the actual serialized dispatcher + // information. + static const char* ValidateBuffer(size_t serialized_platform_handle_size, + const void* buffer, + size_t buffer_size); + + // Gets the platform handle table from a (valid) |TransportData| buffer (which + // should have been validated using |ValidateBuffer()| first). + static void GetPlatformHandleTable(const void* transport_data_buffer, + size_t* num_platform_handles, + const void** platform_handle_table); + + // Deserializes dispatchers from the given (serialized) transport data buffer + // (typically from a |MessageInTransit::View|) and vector of platform handles. + // |buffer| should be non-null and |buffer_size| should be nonzero. + static scoped_ptr<DispatcherVector> DeserializeDispatchers( + const void* buffer, + size_t buffer_size, + embedder::ScopedPlatformHandleVectorPtr platform_handles, + Channel* channel); + + private: + // To allow us to make compile-assertions about |Header|, etc. in the .cc + // file. + struct PrivateStructForCompileAsserts; + + // Header for the "secondary buffer"/"transport data". Must be a multiple of + // |MessageInTransit::kMessageAlignment| in size. Must be POD. + struct Header { + uint32_t num_handles; + // TODO(vtl): Not used yet: + uint32_t platform_handle_table_offset; + uint32_t num_platform_handles; + uint32_t unused; + }; + + struct HandleTableEntry { + int32_t type; // From |Dispatcher::Type| (|kTypeUnknown| for "invalid"). + uint32_t offset; // Relative to the start of the "secondary buffer". + uint32_t size; // (Not including any padding.) + uint32_t unused; + }; + + const Header* header() const { + return reinterpret_cast<const Header*>(buffer_.get()); + } + + size_t buffer_size_; + scoped_ptr<char, base::AlignedFreeDeleter> buffer_; // Never null. + + // Any platform-specific handles attached to this message (for inter-process + // transport). The vector (if any) owns the handles that it contains (and is + // responsible for closing them). + // TODO(vtl): With C++11, change it to a vector of |ScopedPlatformHandles|. + embedder::ScopedPlatformHandleVectorPtr platform_handles_; + + DISALLOW_COPY_AND_ASSIGN(TransportData); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_TRANSPORT_DATA_H_ diff --git a/chromium/mojo/system/waiter.cc b/chromium/mojo/system/waiter.cc new file mode 100644 index 00000000000..5a3150d3bce --- /dev/null +++ b/chromium/mojo/system/waiter.cc @@ -0,0 +1,98 @@ +// 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 "mojo/system/waiter.h" + +#include <limits> + +#include "base/logging.h" +#include "base/time/time.h" + +namespace mojo { +namespace system { + +Waiter::Waiter() + : cv_(&lock_), +#ifndef NDEBUG + initialized_(false), +#endif + awoken_(false), + awake_result_(MOJO_RESULT_INTERNAL), + awake_context_(static_cast<uint32_t>(-1)) { +} + +Waiter::~Waiter() { +} + +void Waiter::Init() { +#ifndef NDEBUG + initialized_ = true; +#endif + awoken_ = false; + // NOTE(vtl): If performance ever becomes an issue, we can disable the setting + // of |awake_result_| (except the first one in |Awake()|) in Release builds. + awake_result_ = MOJO_RESULT_INTERNAL; +} + +// TODO(vtl): Fast-path the |deadline == 0| case? +MojoResult Waiter::Wait(MojoDeadline deadline, uint32_t* context) { + base::AutoLock locker(lock_); + +#ifndef NDEBUG + DCHECK(initialized_); + // It'll need to be re-initialized after this. + initialized_ = false; +#endif + + // Fast-path the already-awoken case: + if (awoken_) { + DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL); + if (context) + *context = awake_context_; + return awake_result_; + } + + // |MojoDeadline| is actually a |uint64_t|, but we need a signed quantity. + // Treat any out-of-range deadline as "forever" (which is wrong, but okay + // since 2^63 microseconds is ~300000 years). Note that this also takes care + // of the |MOJO_DEADLINE_INDEFINITE| (= 2^64 - 1) case. + if (deadline > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { + do { + cv_.Wait(); + } while (!awoken_); + } else { + // NOTE(vtl): This is very inefficient on POSIX, since pthreads condition + // variables take an absolute deadline. + const base::TimeTicks end_time = base::TimeTicks::HighResNow() + + base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline)); + do { + base::TimeTicks now_time = base::TimeTicks::HighResNow(); + if (now_time >= end_time) + return MOJO_RESULT_DEADLINE_EXCEEDED; + + cv_.TimedWait(end_time - now_time); + } while (!awoken_); + } + + DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL); + if (context) + *context = awake_context_; + return awake_result_; +} + +void Waiter::Awake(MojoResult result, uint32_t context) { + base::AutoLock locker(lock_); + + if (awoken_) + return; + + awoken_ = true; + awake_result_ = result; + awake_context_ = context; + cv_.Signal(); + // |cv_.Wait()|/|cv_.TimedWait()| will return after |lock_| is released. +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/waiter.h b/chromium/mojo/system/waiter.h new file mode 100644 index 00000000000..66fe6e7ee88 --- /dev/null +++ b/chromium/mojo/system/waiter.h @@ -0,0 +1,81 @@ +// 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 MOJO_SYSTEM_WAITER_H_ +#define MOJO_SYSTEM_WAITER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +// IMPORTANT (all-caps gets your attention, right?): |Waiter| methods are called +// under other locks, in particular, |Dispatcher::lock_|s, so |Waiter| methods +// must never call out to other objects (in particular, |Dispatcher|s). This +// class is thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT Waiter { + public: + Waiter(); + ~Waiter(); + + // A |Waiter| can be used multiple times; |Init()| should be called before + // each time it's used. + void Init(); + + // Waits until a suitable |Awake()| is called. (|context| may be null, in + // which case, obviously no context is ever returned.) + // Returns: + // - The result given to the first call to |Awake()| (possibly before this + // call to |Wait()|); in this case, |*context| is set to the value passed + // to that call to |Awake()|. + // - |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline was exceeded; in this + // case |*context| is not modified. + // + // Usually, the context passed to |Awake()| will be the value passed to + // |Dispatcher::AddWaiter()|, which is usually the index to the array of + // handles passed to |MojoWaitMany()| (or 0 for |MojoWait()|). + // + // Typical |Awake()| results are: + // - |MOJO_RESULT_OK| if one of the flags passed to + // |MojoWait()|/|MojoWaitMany()| (hence |Dispatcher::AddWaiter()|) was + // satisfied; + // - |MOJO_RESULT_CANCELLED| if a handle (on which + // |MojoWait()|/|MojoWaitMany()| was called) was closed (hence the + // dispatcher closed); and + // - |MOJO_RESULT_FAILED_PRECONDITION| if one of the set of flags passed to + // |MojoWait()|/|MojoWaitMany()| cannot or can no longer be satisfied by + // the corresponding handle (e.g., if the other end of a message or data + // pipe is closed). + MojoResult Wait(MojoDeadline deadline, uint32_t* context); + + // Wake the waiter up with the given result and context (or no-op if it's been + // woken up already). + void Awake(MojoResult result, uint32_t context); + + private: + base::ConditionVariable cv_; // Associated to |lock_|. + base::Lock lock_; // Protects the following members. +#ifndef NDEBUG + bool initialized_; +#endif + bool awoken_; + MojoResult awake_result_; + // This is a |uint32_t| because we really only need to store an index (for + // |MojoWaitMany()|). But in tests, it's convenient to use this for other + // purposes (e.g., to distinguish between different wake-up reasons). + uint32_t awake_context_; + + DISALLOW_COPY_AND_ASSIGN(Waiter); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_WAITER_H_ diff --git a/chromium/mojo/system/waiter_list.cc b/chromium/mojo/system/waiter_list.cc new file mode 100644 index 00000000000..6ee90100aa8 --- /dev/null +++ b/chromium/mojo/system/waiter_list.cc @@ -0,0 +1,57 @@ +// 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 "mojo/system/waiter_list.h" + +#include "base/logging.h" +#include "mojo/system/handle_signals_state.h" +#include "mojo/system/waiter.h" + +namespace mojo { +namespace system { + +WaiterList::WaiterList() { +} + +WaiterList::~WaiterList() { + DCHECK(waiters_.empty()); +} + +void WaiterList::AwakeWaitersForStateChange(const HandleSignalsState& state) { + for (WaiterInfoList::iterator it = waiters_.begin(); it != waiters_.end(); + ++it) { + if (state.satisfies(it->signals)) + it->waiter->Awake(MOJO_RESULT_OK, it->context); + else if (!state.can_satisfy(it->signals)) + it->waiter->Awake(MOJO_RESULT_FAILED_PRECONDITION, it->context); + } +} + +void WaiterList::CancelAllWaiters() { + for (WaiterInfoList::iterator it = waiters_.begin(); it != waiters_.end(); + ++it) { + it->waiter->Awake(MOJO_RESULT_CANCELLED, it->context); + } + waiters_.clear(); +} + +void WaiterList::AddWaiter(Waiter* waiter, + MojoHandleSignals signals, + uint32_t context) { + waiters_.push_back(WaiterInfo(waiter, signals, context)); +} + +void WaiterList::RemoveWaiter(Waiter* waiter) { + // We allow a thread to wait on the same handle multiple times simultaneously, + // so we need to scan the entire list and remove all occurrences of |waiter|. + for (WaiterInfoList::iterator it = waiters_.begin(); it != waiters_.end();) { + WaiterInfoList::iterator maybe_delete = it; + ++it; + if (maybe_delete->waiter == waiter) + waiters_.erase(maybe_delete); + } +} + +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/waiter_list.h b/chromium/mojo/system/waiter_list.h new file mode 100644 index 00000000000..39ac7b00100 --- /dev/null +++ b/chromium/mojo/system/waiter_list.h @@ -0,0 +1,58 @@ +// 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 MOJO_SYSTEM_WAITER_LIST_H_ +#define MOJO_SYSTEM_WAITER_LIST_H_ + +#include <stdint.h> + +#include <list> + +#include "base/macros.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/system_impl_export.h" + +namespace mojo { +namespace system { + +class Waiter; +struct HandleSignalsState; + +// |WaiterList| tracks all the |Waiter|s that are waiting on a given +// handle/|Dispatcher|. There should be a |WaiterList| for each handle that can +// be waited on (in any way). In the simple case, the |WaiterList| is owned by +// the |Dispatcher|, whereas in more complex cases it is owned by the secondary +// object (see simple_dispatcher.* and the explanatory comment in core.cc). This +// class is thread-unsafe (all concurrent access must be protected by some +// lock). +class MOJO_SYSTEM_IMPL_EXPORT WaiterList { + public: + WaiterList(); + ~WaiterList(); + + void AwakeWaitersForStateChange(const HandleSignalsState& state); + void CancelAllWaiters(); + void AddWaiter(Waiter* waiter, MojoHandleSignals signals, uint32_t context); + void RemoveWaiter(Waiter* waiter); + + private: + struct WaiterInfo { + WaiterInfo(Waiter* waiter, MojoHandleSignals signals, uint32_t context) + : waiter(waiter), signals(signals), context(context) {} + + Waiter* waiter; + MojoHandleSignals signals; + uint32_t context; + }; + typedef std::list<WaiterInfo> WaiterInfoList; + + WaiterInfoList waiters_; + + DISALLOW_COPY_AND_ASSIGN(WaiterList); +}; + +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_WAITER_LIST_H_ diff --git a/chromium/mojo/system/waiter_list_unittest.cc b/chromium/mojo/system/waiter_list_unittest.cc new file mode 100644 index 00000000000..1abe5e7eb4b --- /dev/null +++ b/chromium/mojo/system/waiter_list_unittest.cc @@ -0,0 +1,297 @@ +// 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. + +// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a +// heavily-loaded system). Sorry. |test::EpsilonTimeout()| may be increased to +// increase tolerance and reduce observed flakiness (though doing so reduces the +// meaningfulness of the test). + +#include "mojo/system/waiter_list.h" + +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "base/time/time.h" +#include "mojo/system/handle_signals_state.h" +#include "mojo/system/test_utils.h" +#include "mojo/system/waiter.h" +#include "mojo/system/waiter_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +TEST(WaiterListTest, BasicCancel) { + MojoResult result; + uint32_t context; + + // Cancel immediately after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); + thread.Start(); + waiter_list.CancelAllWaiters(); + waiter_list.RemoveWaiter(thread.waiter()); // Double-remove okay. + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(1u, context); + + // Cancel before after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); + waiter_list.CancelAllWaiters(); + thread.Start(); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(2u, context); + + // Cancel some time after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + waiter_list.CancelAllWaiters(); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(3u, context); +} + +TEST(WaiterListTest, BasicAwakeSatisfied) { + MojoResult result; + uint32_t context; + + // Awake immediately after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); + thread.Start(); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread.waiter()); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(1u, context); + + // Awake before after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread.waiter()); + waiter_list.RemoveWaiter(thread.waiter()); // Double-remove okay. + thread.Start(); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(2u, context); + + // Awake some time after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread.waiter()); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(3u, context); +} + +TEST(WaiterListTest, BasicAwakeUnsatisfiable) { + MojoResult result; + uint32_t context; + + // Awake (for unsatisfiability) immediately after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); + thread.Start(); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread.waiter()); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(1u, context); + + // Awake (for unsatisfiability) before after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE)); + waiter_list.RemoveWaiter(thread.waiter()); + thread.Start(); + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(2u, context); + + // Awake (for unsatisfiability) some time after thread start. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread(&result, &context); + waiter_list.AddWaiter(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread.waiter()); + waiter_list.RemoveWaiter(thread.waiter()); // Double-remove okay. + } // Join |thread|. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(3u, context); +} + +TEST(WaiterListTest, MultipleWaiters) { + MojoResult result1; + MojoResult result2; + MojoResult result3; + MojoResult result4; + uint32_t context1; + uint32_t context2; + uint32_t context3; + uint32_t context4; + + // Cancel two waiters. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread1(&result1, &context1); + waiter_list.AddWaiter(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1); + thread1.Start(); + test::SimpleWaiterThread thread2(&result2, &context2); + waiter_list.AddWaiter(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2); + thread2.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + waiter_list.CancelAllWaiters(); + } // Join threads. + EXPECT_EQ(MOJO_RESULT_CANCELLED, result1); + EXPECT_EQ(1u, context1); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result2); + EXPECT_EQ(2u, context2); + + // Awake one waiter, cancel other. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread1(&result1, &context1); + waiter_list.AddWaiter(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3); + thread1.Start(); + test::SimpleWaiterThread thread2(&result2, &context2); + waiter_list.AddWaiter(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 4); + thread2.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread1.waiter()); + waiter_list.CancelAllWaiters(); + } // Join threads. + EXPECT_EQ(MOJO_RESULT_OK, result1); + EXPECT_EQ(3u, context1); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result2); + EXPECT_EQ(4u, context2); + + // Cancel one waiter, awake other for unsatisfiability. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread1(&result1, &context1); + waiter_list.AddWaiter(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 5); + thread1.Start(); + test::SimpleWaiterThread thread2(&result2, &context2); + waiter_list.AddWaiter(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 6); + thread2.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_READABLE)); + waiter_list.RemoveWaiter(thread2.waiter()); + waiter_list.CancelAllWaiters(); + } // Join threads. + EXPECT_EQ(MOJO_RESULT_CANCELLED, result1); + EXPECT_EQ(5u, context1); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2); + EXPECT_EQ(6u, context2); + + // Cancel one waiter, awake other for unsatisfiability. + { + WaiterList waiter_list; + test::SimpleWaiterThread thread1(&result1, &context1); + waiter_list.AddWaiter(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 7); + thread1.Start(); + + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + + // Should do nothing. + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE)); + + test::SimpleWaiterThread thread2(&result2, &context2); + waiter_list.AddWaiter(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 8); + thread2.Start(); + + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + + // Awake #1. + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE)); + waiter_list.RemoveWaiter(thread1.waiter()); + + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + + test::SimpleWaiterThread thread3(&result3, &context3); + waiter_list.AddWaiter(thread3.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 9); + thread3.Start(); + + test::SimpleWaiterThread thread4(&result4, &context4); + waiter_list.AddWaiter(thread4.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 10); + thread4.Start(); + + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + + // Awake #2 and #3 for unsatisfiability. + waiter_list.AwakeWaitersForStateChange( + HandleSignalsState(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_READABLE)); + waiter_list.RemoveWaiter(thread2.waiter()); + waiter_list.RemoveWaiter(thread3.waiter()); + + // Cancel #4. + waiter_list.CancelAllWaiters(); + } // Join threads. + EXPECT_EQ(MOJO_RESULT_OK, result1); + EXPECT_EQ(7u, context1); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2); + EXPECT_EQ(8u, context2); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result3); + EXPECT_EQ(9u, context3); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result4); + EXPECT_EQ(10u, context4); +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/waiter_test_utils.cc b/chromium/mojo/system/waiter_test_utils.cc new file mode 100644 index 00000000000..f762e985f90 --- /dev/null +++ b/chromium/mojo/system/waiter_test_utils.cc @@ -0,0 +1,66 @@ +// 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 "mojo/system/waiter_test_utils.h" + +namespace mojo { +namespace system { +namespace test { + +SimpleWaiterThread::SimpleWaiterThread(MojoResult* result, uint32_t* context) + : base::SimpleThread("waiter_thread"), + result_(result), + context_(context) { + waiter_.Init(); + *result_ = -5420734; // Totally invalid result. + *context_ = 23489023; // "Random". +} + +SimpleWaiterThread::~SimpleWaiterThread() { + Join(); +} + +void SimpleWaiterThread::Run() { + *result_ = waiter_.Wait(MOJO_DEADLINE_INDEFINITE, context_); +} + +WaiterThread::WaiterThread(scoped_refptr<Dispatcher> dispatcher, + MojoHandleSignals handle_signals, + MojoDeadline deadline, + uint32_t context, + bool* did_wait_out, + MojoResult* result_out, + uint32_t* context_out) + : base::SimpleThread("waiter_thread"), + dispatcher_(dispatcher), + handle_signals_(handle_signals), + deadline_(deadline), + context_(context), + did_wait_out_(did_wait_out), + result_out_(result_out), + context_out_(context_out) { + *did_wait_out_ = false; + *result_out_ = -8542346; // Totally invalid result. + *context_out_ = 89023444; // "Random". +} + +WaiterThread::~WaiterThread() { + Join(); +} + +void WaiterThread::Run() { + waiter_.Init(); + + *result_out_ = dispatcher_->AddWaiter(&waiter_, handle_signals_, context_); + if (*result_out_ != MOJO_RESULT_OK) + return; + + *did_wait_out_ = true; + *result_out_ = waiter_.Wait(deadline_, context_out_); + dispatcher_->RemoveWaiter(&waiter_); +} + +} // namespace test +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/system/waiter_test_utils.h b/chromium/mojo/system/waiter_test_utils.h new file mode 100644 index 00000000000..95e0f51f90f --- /dev/null +++ b/chromium/mojo/system/waiter_test_utils.h @@ -0,0 +1,101 @@ +// 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 MOJO_SYSTEM_WAITER_TEST_UTILS_H_ +#define MOJO_SYSTEM_WAITER_TEST_UTILS_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/simple_thread.h" +#include "mojo/public/c/system/types.h" +#include "mojo/system/dispatcher.h" +#include "mojo/system/waiter.h" + +namespace mojo { +namespace system { +namespace test { + +// This is a very simple thread that has a |Waiter|, on which it waits +// indefinitely (and records the result). It will create and initialize the +// |Waiter| on creation, but the caller must start the thread with |Start()|. It +// will join the thread on destruction. +// +// One usually uses it like: +// +// MojoResult result; +// { +// WaiterList waiter_list; +// test::SimpleWaiterThread thread(&result); +// waiter_list.AddWaiter(thread.waiter(), ...); +// thread.Start(); +// ... some stuff to wake the waiter ... +// waiter_list.RemoveWaiter(thread.waiter()); +// } // Join |thread|. +// EXPECT_EQ(..., result); +// +// There's a bit of unrealism in its use: In this sort of usage, calls such as +// |Waiter::Init()|, |AddWaiter()|, and |RemoveWaiter()| are done in the main +// (test) thread, not the waiter thread (as would actually happen in real code). +// (We accept this unrealism for simplicity, since |WaiterList| is +// thread-unsafe so making it more realistic would require adding nontrivial +// synchronization machinery.) +class SimpleWaiterThread : public base::SimpleThread { + public: + // For the duration of the lifetime of this object, |*result| belongs to it + // (in the sense that it will write to it whenever it wants). + SimpleWaiterThread(MojoResult* result, uint32_t* context); + virtual ~SimpleWaiterThread(); // Joins the thread. + + Waiter* waiter() { return &waiter_; } + + private: + virtual void Run() OVERRIDE; + + MojoResult* const result_; + uint32_t* const context_; + Waiter waiter_; + + DISALLOW_COPY_AND_ASSIGN(SimpleWaiterThread); +}; + +// This is a more complex and realistic thread that has a |Waiter|, on which it +// waits for the given deadline (with the given flags). Unlike +// |SimpleWaiterThread|, it requires the machinery of |Dispatcher|. +class WaiterThread : public base::SimpleThread { + public: + // Note: |*did_wait_out|, |*result_out|, and |*context_out| "belong" to this + // object (i.e., may be modified by, on some other thread) while it's alive. + WaiterThread(scoped_refptr<Dispatcher> dispatcher, + MojoHandleSignals handle_signals, + MojoDeadline deadline, + uint32_t context, + bool* did_wait_out, + MojoResult* result_out, + uint32_t* context_out); + virtual ~WaiterThread(); + + private: + virtual void Run() OVERRIDE; + + const scoped_refptr<Dispatcher> dispatcher_; + const MojoHandleSignals handle_signals_; + const MojoDeadline deadline_; + const uint32_t context_; + bool* const did_wait_out_; + MojoResult* const result_out_; + uint32_t* const context_out_; + + Waiter waiter_; + + DISALLOW_COPY_AND_ASSIGN(WaiterThread); +}; + +} // namespace test +} // namespace system +} // namespace mojo + +#endif // MOJO_SYSTEM_WAITER_TEST_UTILS_H_ diff --git a/chromium/mojo/system/waiter_unittest.cc b/chromium/mojo/system/waiter_unittest.cc new file mode 100644 index 00000000000..3afef28aa20 --- /dev/null +++ b/chromium/mojo/system/waiter_unittest.cc @@ -0,0 +1,303 @@ +// 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. + +// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a +// heavily-loaded system). Sorry. |test::EpsilonTimeout()| may be increased to +// increase tolerance and reduce observed flakiness (though doing so reduces the +// meaningfulness of the test). + +#include "mojo/system/waiter.h" + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/synchronization/lock.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "mojo/system/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace system { +namespace { + +const int64_t kMicrosPerMs = 1000; +const int64_t kPollTimeMicros = 10 * kMicrosPerMs; // 10 ms. + +class WaitingThread : public base::SimpleThread { + public: + explicit WaitingThread(MojoDeadline deadline) + : base::SimpleThread("waiting_thread"), + deadline_(deadline), + done_(false), + result_(MOJO_RESULT_UNKNOWN), + context_(static_cast<uint32_t>(-1)) { + waiter_.Init(); + } + + virtual ~WaitingThread() { + Join(); + } + + void WaitUntilDone(MojoResult* result, + uint32_t* context, + base::TimeDelta* elapsed) { + for (;;) { + { + base::AutoLock locker(lock_); + if (done_) { + *result = result_; + *context = context_; + *elapsed = elapsed_; + break; + } + } + + base::PlatformThread::Sleep( + base::TimeDelta::FromMicroseconds(kPollTimeMicros)); + } + } + + Waiter* waiter() { return &waiter_; } + + private: + virtual void Run() OVERRIDE { + test::Stopwatch stopwatch; + MojoResult result; + uint32_t context = static_cast<uint32_t>(-1); + base::TimeDelta elapsed; + + stopwatch.Start(); + result = waiter_.Wait(deadline_, &context); + elapsed = stopwatch.Elapsed(); + + { + base::AutoLock locker(lock_); + done_ = true; + result_ = result; + context_ = context; + elapsed_ = elapsed; + } + } + + const MojoDeadline deadline_; + Waiter waiter_; // Thread-safe. + + base::Lock lock_; // Protects the following members. + bool done_; + MojoResult result_; + uint32_t context_; + base::TimeDelta elapsed_; + + DISALLOW_COPY_AND_ASSIGN(WaitingThread); +}; + +TEST(WaiterTest, Basic) { + MojoResult result; + uint32_t context; + base::TimeDelta elapsed; + + // Finite deadline. + + // Awake immediately after thread start. + { + WaitingThread thread(10 * test::EpsilonTimeout().InMicroseconds()); + thread.Start(); + thread.waiter()->Awake(MOJO_RESULT_OK, 1); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(1u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + // Awake before after thread start. + { + WaitingThread thread(10 * test::EpsilonTimeout().InMicroseconds()); + thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 2); + thread.Start(); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(2u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + // Awake some time after thread start. + { + WaitingThread thread(10 * test::EpsilonTimeout().InMicroseconds()); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + thread.waiter()->Awake(1, 3); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(1, result); + EXPECT_EQ(3u, context); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + } + + // Awake some longer time after thread start. + { + WaitingThread thread(10 * test::EpsilonTimeout().InMicroseconds()); + thread.Start(); + base::PlatformThread::Sleep(5 * test::EpsilonTimeout()); + thread.waiter()->Awake(2, 4); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(2, result); + EXPECT_EQ(4u, context); + EXPECT_GT(elapsed, (5-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (5+1) * test::EpsilonTimeout()); + } + + // Don't awake -- time out (on another thread). + { + WaitingThread thread(2 * test::EpsilonTimeout().InMicroseconds()); + thread.Start(); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result); + EXPECT_EQ(static_cast<uint32_t>(-1), context); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + } + + // No (indefinite) deadline. + + // Awake immediately after thread start. + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.Start(); + thread.waiter()->Awake(MOJO_RESULT_OK, 5); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(5u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + // Awake before after thread start. + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 6); + thread.Start(); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + EXPECT_EQ(6u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + // Awake some time after thread start. + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.Start(); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + thread.waiter()->Awake(1, 7); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(1, result); + EXPECT_EQ(7u, context); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + } + + // Awake some longer time after thread start. + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.Start(); + base::PlatformThread::Sleep(5 * test::EpsilonTimeout()); + thread.waiter()->Awake(2, 8); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(2, result); + EXPECT_EQ(8u, context); + EXPECT_GT(elapsed, (5-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (5+1) * test::EpsilonTimeout()); + } +} + +TEST(WaiterTest, TimeOut) { + test::Stopwatch stopwatch; + base::TimeDelta elapsed; + + Waiter waiter; + uint32_t context = 123; + + waiter.Init(); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, &context)); + elapsed = stopwatch.Elapsed(); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + EXPECT_EQ(123u, context); + + waiter.Init(); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + waiter.Wait(2 * test::EpsilonTimeout().InMicroseconds(), &context)); + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (2-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (2+1) * test::EpsilonTimeout()); + EXPECT_EQ(123u, context); + + waiter.Init(); + stopwatch.Start(); + EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, + waiter.Wait(5 * test::EpsilonTimeout().InMicroseconds(), &context)); + elapsed = stopwatch.Elapsed(); + EXPECT_GT(elapsed, (5-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (5+1) * test::EpsilonTimeout()); + EXPECT_EQ(123u, context); +} + +// The first |Awake()| should always win. +TEST(WaiterTest, MultipleAwakes) { + MojoResult result; + uint32_t context; + base::TimeDelta elapsed; + + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.Start(); + thread.waiter()->Awake(MOJO_RESULT_OK, 1); + thread.waiter()->Awake(1, 2); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(1u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.waiter()->Awake(1, 3); + thread.Start(); + thread.waiter()->Awake(MOJO_RESULT_OK, 4); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(1, result); + EXPECT_EQ(3u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + { + WaitingThread thread(MOJO_DEADLINE_INDEFINITE); + thread.Start(); + thread.waiter()->Awake(10, 5); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + thread.waiter()->Awake(20, 6); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(10, result); + EXPECT_EQ(5u, context); + EXPECT_LT(elapsed, test::EpsilonTimeout()); + } + + { + WaitingThread thread(10 * test::EpsilonTimeout().InMicroseconds()); + thread.Start(); + base::PlatformThread::Sleep(1 * test::EpsilonTimeout()); + thread.waiter()->Awake(MOJO_RESULT_FAILED_PRECONDITION, 7); + base::PlatformThread::Sleep(2 * test::EpsilonTimeout()); + thread.waiter()->Awake(MOJO_RESULT_OK, 8); + thread.WaitUntilDone(&result, &context, &elapsed); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + EXPECT_EQ(7u, context); + EXPECT_GT(elapsed, (1-1) * test::EpsilonTimeout()); + EXPECT_LT(elapsed, (1+1) * test::EpsilonTimeout()); + } +} + +} // namespace +} // namespace system +} // namespace mojo diff --git a/chromium/mojo/tools/check_mojom_golden_files.py b/chromium/mojo/tools/check_mojom_golden_files.py new file mode 100755 index 00000000000..6c2e187c8ae --- /dev/null +++ b/chromium/mojo/tools/check_mojom_golden_files.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# 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 argparse +import os.path +import sys +from filecmp import dircmp +from shutil import rmtree +from tempfile import mkdtemp + +_script_dir = os.path.dirname(os.path.abspath(__file__)) +_mojo_dir = os.path.join(_script_dir, os.pardir) +sys.path.insert(0, os.path.join(_mojo_dir, "public", "tools", "bindings", + "pylib")) +from mojom_tests.support.find_files import FindFiles +from mojom_tests.support.run_bindings_generator import RunBindingsGenerator + + +def _ProcessDircmpResults(results, verbose=False): + """Prints results of directory comparison and returns true if they are + identical (note: the "left" directory should be the golden directory).""" + rv = not (bool(results.left_only) or bool(results.right_only) or \ + bool(results.common_funny) or bool(results.funny_files) or \ + bool(results.diff_files)) + if verbose: + for f in results.left_only: + print "%s exists in golden directory but not in current output" % f + for f in results.right_only: + print "%s exists in current output but not in golden directory" % f + for f in results.common_funny + results.funny_files: + print "Unable to compare %s between golden directory and current output" \ + % f + for f in results.diff_files: + print "%s differs between golden directory and current output" % f + for r in results.subdirs.values(): + # If we're being verbose, check subdirectories even if we know that there + # are differences. Note that it's "... and rv" to avoid the short-circuit. + if rv or verbose: + rv = _ProcessDircmpResults(r, verbose=verbose) and rv + return rv + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--generate_golden_files", action="store_true", + help=("generate golden files (does not obliterate " + "directory")) + parser.add_argument("--keep_temp_dir", action="store_true", + help="don't delete the temporary directory") + parser.add_argument("--verbose", action="store_true", + help="spew excess verbiage") + parser.add_argument("golden_dir", metavar="GOLDEN_DIR", + help="directory with the golden files") + args = parser.parse_args() + + if args.generate_golden_files: + if os.path.exists(args.golden_dir): + print "WARNING: golden directory %s already exists" % args.golden_dir + out_dir = args.golden_dir + else: + if not os.path.exists(args.golden_dir): + print "ERROR: golden directory %s does not exist" % args.golden_dir + return 1 + out_dir = mkdtemp() + if args.verbose: + print "Generating files to %s ..." % out_dir + + mojom_files = FindFiles(_mojo_dir, "*.mojom") + for mojom_file in mojom_files: + if args.verbose: + print " Processing %s ..." % os.path.relpath(mojom_file, _mojo_dir) + RunBindingsGenerator(out_dir, _mojo_dir, mojom_file) + + if args.generate_golden_files: + return 0 + + identical = _ProcessDircmpResults(dircmp(args.golden_dir, out_dir, ignore=[]), + verbose=args.verbose) + + if args.keep_temp_dir: + if args.verbose: + print "Not removing %s ..." % out_dir + else: + if args.verbose: + print "Removing %s ..." % out_dir + rmtree(out_dir) + + if not identical: + print "FAILURE: current output differs from golden files" + return 1 + + print "SUCCESS: current output identical to golden files" + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/mojo/tools/data/unittests b/chromium/mojo/tools/data/unittests new file mode 100644 index 00000000000..7939ae24970 --- /dev/null +++ b/chromium/mojo/tools/data/unittests @@ -0,0 +1,26 @@ +# This file contains a list of Mojo gtest unit tests. +# Prepend * to indicate that results shouldn't be cached (e.g., if the test has +# other data dependencies). +# TODO(vtl): Add a way of specifying data dependencies instead. + +# System tests: +mojo_system_unittests + +# Public tests: +*mojo_public_bindings_unittests +mojo_public_environment_unittests +mojo_public_system_unittests +mojo_public_utility_unittests + +# Non-system, non-public tests: +mojo_common_unittests +mojo_service_manager_unittests +mojo_view_manager_lib_unittests +mojo_view_manager_unittests + +# JavaScript tests: +*mojo_apps_js_unittests +*mojo_js_unittests + +# Shell integration tests: +*mojo_shell_tests diff --git a/chromium/mojo/tools/generate_java_callback_interfaces.py b/chromium/mojo/tools/generate_java_callback_interfaces.py new file mode 100644 index 00000000000..fdf1f74d8a9 --- /dev/null +++ b/chromium/mojo/tools/generate_java_callback_interfaces.py @@ -0,0 +1,59 @@ +"""Generate the org.chromium.mojo.bindings.Callbacks interface""" + +import argparse +import sys + +CALLBACK_TEMPLATE = (""" + /** + * A generic %d-argument callback. + * + * %s + */ + interface Callback%d<%s> { + /** + * Call the callback. + */ + public void call(%s); + } +""") + +INTERFACE_TEMPLATE = ( +"""// 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. + +// This file was generated using +// mojo/tools/generate_java_callback_interfaces.py + +package org.chromium.mojo.bindings; + +/** + * Contains a generic interface for callbacks. + */ +public interface Callbacks { +%s +}""") + +def GenerateCallback(nb_args): + params = '\n * '.join( + ['@param <T%d> the type of argument %d.' % (i+1, i+1) + for i in xrange(nb_args)]) + template_parameters = ', '.join(['T%d' % (i+1) for i in xrange(nb_args)]) + callback_parameters = ', '.join(['T%d arg%d' % ((i+1), (i+1)) + for i in xrange(nb_args)]) + return CALLBACK_TEMPLATE % (nb_args, params, nb_args, template_parameters, + callback_parameters) + +def main(): + parser = argparse.ArgumentParser( + description="Generate org.chromium.mojo.bindings.Callbacks") + parser.add_argument("max_args", nargs=1, type=int, + help="maximal number of arguments to generate callbacks for") + args = parser.parse_args() + max_args = args.max_args[0] + print INTERFACE_TEMPLATE % ''.join([GenerateCallback(i+1) + for i in xrange(max_args)]) + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/chromium/mojo/tools/message_generator.cc b/chromium/mojo/tools/message_generator.cc new file mode 100644 index 00000000000..86fcab21af1 --- /dev/null +++ b/chromium/mojo/tools/message_generator.cc @@ -0,0 +1,63 @@ +// 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/file_util.h" +#include "base/files/file_path.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" +#include "mojo/public/cpp/bindings/message.h" + +// This file is used to generate various files corresponding to mojo +// messages. The various binding implementations can parse these to verify they +// correctly decode messages. +// +// The output consists of each byte of the message encoded in a hex string with +// a newline after it. +namespace mojo { +namespace { + +std::string BinaryToHex(const base::StringPiece& piece) { + std::string result("// File generated by mojo_message_generator.\n");; + result.reserve(result.size() + (piece.size() * 5)); + for (size_t i = 0; i < piece.size(); ++i) + base::StringAppendF(&result, "0X%.2X\n", static_cast<int>(piece.data()[i])); + return result; +} + +void WriteMessageToFile(const Message& message, const base::FilePath& path) { + const std::string hex_message(BinaryToHex( + base::StringPiece(reinterpret_cast<const char*>(message.data()), + message.data_num_bytes()))); + CHECK_EQ(static_cast<int>(hex_message.size()), + base::WriteFile(path, hex_message.data(), + static_cast<int>(hex_message.size()))); +} + +// Generates a message of type MessageData. The message uses the name 21, +// with 4 bytes of payload: 0x9, 0x8, 0x7, 0x6. +void GenerateMessageDataMessage() { + internal::MessageBuilder builder(static_cast<uint32_t>(21), + static_cast<size_t>(4)); + char* data = static_cast<char*>(builder.buffer()->Allocate(4)); + DCHECK(data); + data[0] = 9; + data[1] = 8; + data[2] = 7; + data[3] = 6; + + Message message; + builder.Finish(&message); + WriteMessageToFile(message, + base::FilePath(FILE_PATH_LITERAL("message_data"))); +} + +} // namespace +} // namespace mojo + +int main(int argc, char** argv) { + mojo::GenerateMessageDataMessage(); + return 0; +} diff --git a/chromium/mojo/tools/mojob.sh b/chromium/mojo/tools/mojob.sh new file mode 100755 index 00000000000..b870cd13014 --- /dev/null +++ b/chromium/mojo/tools/mojob.sh @@ -0,0 +1,186 @@ +#!/bin/bash +# 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. + +# This a simple script to make building/testing Mojo components easier (on +# Linux). + +# TODO(vtl): Maybe make the test runner smart and not run unchanged test +# binaries. +# TODO(vtl) Maybe also provide a way to pass command-line arguments to the test +# binaries. + +do_help() { + cat << EOF +Usage: $(basename "$0") [command|option ...] + +command should be one of: + build - Build. + test - Run unit tests (does not build). + perftest - Run perf tests (does not build). + pytest - Run Python unit tests. + gyp - Run gyp for mojo (does not sync), with clang. + sync - Sync using gclient (does not run gyp). + show-bash-alias - Outputs an appropriate bash alias for mojob. In bash do: + \$ eval \`mojo/tools/mojob.sh show-bash-alias\` + +option (which will only apply to following commands) should be one of: + Build/test options (specified before build/test/perftest): + --debug - Build/test in Debug mode. + --release - Build/test in Release mode. + --debug-and-release - Build/test in both Debug and Release modes (default). + Compiler options (specified before gyp): + --clang - Use clang (default). + --gcc - Use gcc. + Component options: + --shared Build components as shared libraries (default). + --static Build components as static libraries. + Mojo in chromium/content (crbug.com/353602): + --use-mojo - Enabled (default). + --no-use-mojo - Disabled. + +Note: It will abort on the first failure (if any). +EOF +} + +do_build() { + echo "Building in out/$1 ..." + ninja -C "out/$1" mojo || exit 1 +} + +do_unittests() { + echo "Running unit tests in out/$1 ..." + mojo/tools/test_runner.py mojo/tools/data/unittests "out/$1" \ + mojob_test_successes || exit 1 +} + +do_perftests() { + echo "Running perf tests in out/$1 ..." + "out/$1/mojo_public_system_perftests" || exit 1 +} + +do_pytests() { + python mojo/tools/run_mojo_python_tests.py || exit 1 +} + +do_gyp() { + local gyp_defines="$(make_gyp_defines)" + echo "Running gyp with GYP_DEFINES=$gyp_defines ..." + GYP_DEFINES="$gyp_defines" build/gyp_chromium mojo/mojo.gyp || exit 1 +} + +do_sync() { + # Note: sync only (with hooks, but no gyp-ing). + GYP_CHROMIUM_NO_ACTION=1 gclient sync || exit 1 +} + +# Valid values: Debug, Release, or Debug_and_Release. +BUILD_TEST_TYPE=Debug_and_Release +should_do_Debug() { + test "$BUILD_TEST_TYPE" = Debug -o "$BUILD_TEST_TYPE" = Debug_and_Release +} +should_do_Release() { + test "$BUILD_TEST_TYPE" = Release -o "$BUILD_TEST_TYPE" = Debug_and_Release +} + +# Valid values: clang or gcc. +COMPILER=clang +# Valid values: shared or static. +COMPONENT=shared +make_gyp_defines() { + local options=() + # Always include these options. + options+=("use_aura=1") + case "$COMPILER" in + clang) + options+=("clang=1") + ;; + gcc) + options+=("clang=0") + ;; + esac + case "$COMPONENT" in + shared) + options+=("component=shared_library") + ;; + static) + options+=("component=static_library") + ;; + esac + echo ${options[*]} +} + +# We're in src/mojo/tools. We want to get to src. +cd "$(realpath "$(dirname "$0")")/../.." + +if [ $# -eq 0 ]; then + do_help + exit 0 +fi + +for arg in "$@"; do + case "$arg" in + # Commands ----------------------------------------------------------------- + help|--help) + do_help + exit 0 + ;; + build) + should_do_Debug && do_build Debug + should_do_Release && do_build Release + ;; + test) + should_do_Debug && do_unittests Debug + should_do_Release && do_unittests Release + ;; + perftest) + should_do_Debug && do_perftests Debug + should_do_Release && do_perftests Release + ;; + pytest) + do_pytests + ;; + gyp) + do_gyp + ;; + sync) + do_sync + ;; + show-bash-alias) + # You want to type something like: + # alias mojob=\ + # '"$(pwd | sed '"'"'s/\(.*\/src\).*/\1/'"'"')/mojo/tools/mojob.sh"' + # This is quoting hell, so we simply escape every non-alphanumeric + # character. + echo alias\ mojob\=\'\"\$\(pwd\ \|\ sed\ \'\"\'\"\'s\/\\\(\.\*\\\/src\\\)\ +\.\*\/\\1\/\'\"\'\"\'\)\/mojo\/tools\/mojob\.sh\"\' + ;; + # Options ------------------------------------------------------------------ + --debug) + BUILD_TEST_TYPE=Debug + ;; + --release) + BUILD_TEST_TYPE=Release + ;; + --debug-and-release) + BUILD_TEST_TYPE=Debug_and_Release + ;; + --clang) + COMPILER=clang + ;; + --gcc) + COMPILER=gcc + ;; + --shared) + COMPONENT=shared + ;; + --static) + COMPONENT=static + ;; + *) + echo "Unknown command \"${arg}\". Try \"$(basename "$0") help\"." + exit 1 + ;; + esac +done diff --git a/chromium/mojo/tools/mojosh.sh b/chromium/mojo/tools/mojosh.sh new file mode 100755 index 00000000000..bb8a2cdca4a --- /dev/null +++ b/chromium/mojo/tools/mojosh.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# 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. + +# This a simple script to make running Mojo shell easier (on Linux). + +DIRECTORY="$(dirname "$0")"/../../out/Debug +PORT=$(($RANDOM % 8192 + 2000)) + +do_help() { + cat << EOF +Usage: $(basename "$0") [-d DIRECTORY] [-|--] <mojo_shell arguments ...> + +DIRECTORY defaults to $DIRECTORY. + +Example: + $(basename "$0") mojo:mojo_sample_app +EOF +} + +kill_http_server() { + echo "Killing SimpleHTTPServer ..." + kill $HTTP_SERVER_PID + wait $HTTP_SERVER_PID +} + +while [ $# -gt 0 ]; do + case "$1" in + -h|--help) + do_help + exit 0 + ;; + -d) + shift + if [ $# -eq 0 ]; then + do_help + exit 1 + fi + DIRECTORY="$1" + ;; + --show-bash-alias) + echo alias\ mojosh\=\'\"\$\(pwd\ \|\ sed\ \'\"\'\"\'s\/\\\(\.\*\\\/src\\\ +\)\.\*\/\\1\/\'\"\'\"\'\)\/mojo\/tools\/mojosh\.sh\"\' + exit 0 + ;; + # Separate arguments to mojo_shell (e.g., in case you want to pass it -d). + -|--) + shift + break + ;; + *) + break + ;; + esac + shift +done + +echo "Base directory: $DIRECTORY" + +echo "Running SimpleHTTPServer in directory $DIRECTORY/lib on port $PORT" +cd $DIRECTORY/lib || exit 1 +python -m SimpleHTTPServer $PORT & +# Kill the HTTP server on exit (even if the user kills everything using ^C). +HTTP_SERVER_PID=$! +trap kill_http_server EXIT +cd .. + +echo "Running:" +echo "./mojo_shell --origin=http://127.0.0.1:$PORT --disable-cache $*" +./mojo_shell --origin=http://127.0.0.1:$PORT --disable-cache $* diff --git a/chromium/mojo/tools/pylib/transitive_hash.py b/chromium/mojo/tools/pylib/transitive_hash.py new file mode 100644 index 00000000000..93e8dc4e75e --- /dev/null +++ b/chromium/mojo/tools/pylib/transitive_hash.py @@ -0,0 +1,89 @@ +# 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 logging +import subprocess +import sys + +from hashlib import sha256 +from os.path import basename, realpath + +_logging = logging.getLogger() + +# Based on/taken from +# http://code.activestate.com/recipes/578231-probably-the-fastest-memoization-decorator-in-the-/ +# (with cosmetic changes). +def _memoize(f): + """Memoization decorator for a function taking a single argument.""" + class Memoize(dict): + def __missing__(self, key): + rv = self[key] = f(key) + return rv + return Memoize().__getitem__ + +@_memoize +def _file_hash(filename): + """Returns a string representing the hash of the given file.""" + _logging.debug("Hashing %s ...", filename) + rv = subprocess.check_output(['sha256sum', '-b', filename]).split(None, 1)[0] + _logging.debug(" => %s", rv) + return rv + +@_memoize +def _get_dependencies(filename): + """Returns a list of filenames for files that the given file depends on.""" + _logging.debug("Getting dependencies for %s ...", filename) + lines = subprocess.check_output(['ldd', filename]).splitlines() + rv = [] + for line in lines: + i = line.find('/') + if i < 0: + _logging.debug(" => no file found in line: %s", line) + continue + rv.append(line[i:].split(None, 1)[0]) + _logging.debug(" => %s", rv) + return rv + +def transitive_hash(filename): + """Returns a string that represents the "transitive" hash of the given + file. The transitive hash is a hash of the file and all the shared libraries + on which it depends (done in an order-independent way).""" + hashes = set() + to_hash = [filename] + while to_hash: + current_filename = realpath(to_hash.pop()) + current_hash = _file_hash(current_filename) + if current_hash in hashes: + _logging.debug("Already seen %s (%s) ...", current_filename, current_hash) + continue + _logging.debug("Haven't seen %s (%s) ...", current_filename, current_hash) + hashes.add(current_hash) + to_hash.extend(_get_dependencies(current_filename)) + return sha256('|'.join(sorted(hashes))).hexdigest() + +def main(argv): + logging.basicConfig() + # Uncomment to debug: + # _logging.setLevel(logging.DEBUG) + + if len(argv) < 2: + print """\ +Usage: %s [file] ... + +Prints the \"transitive\" hash of each (executable) file. The transitive +hash is a hash of the file and all the shared libraries on which it +depends (done in an order-independent way).""" % basename(argv[0]) + return 0 + + rv = 0 + for filename in argv[1:]: + try: + print transitive_hash(filename), filename + except: + print "ERROR", filename + rv = 1 + return rv + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/mojo/tools/run_mojo_python_tests.py b/chromium/mojo/tools/run_mojo_python_tests.py new file mode 100755 index 00000000000..c96aba36d2c --- /dev/null +++ b/chromium/mojo/tools/run_mojo_python_tests.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# 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 optparse +import os +import re +import sys +import unittest + + +def main(): + parser = optparse.OptionParser() + parser.usage = 'run_mojo_python_tests.py [options] [tests...]' + parser.add_option('-v', '--verbose', action='count', default=0) + parser.add_option('--unexpected-failures', metavar='FILENAME', action='store', + help=('path to write a list of any tests that fail ' + 'unexpectedly.')) + parser.epilog = ('If --unexpected-failures is passed, a list of the tests ' + 'that failed (one per line) will be written to the file. ' + 'If no tests failed, the file will be truncated (empty). ' + 'If the test run did not completely properly, or something ' + 'else weird happened, any existing file will be left ' + 'unmodified. ' + 'If --unexpected-failures is *not* passed, any existing ' + 'file will be ignored and left unmodified.') + options, args = parser.parse_args() + + chromium_src_dir = os.path.join(os.path.dirname(__file__), + os.pardir, + os.pardir) + + loader = unittest.loader.TestLoader() + print "Running Python unit tests under mojo/public/tools/bindings/pylib ..." + + pylib_dir = os.path.join(chromium_src_dir, 'mojo', 'public', + 'tools', 'bindings', 'pylib') + if args: + if not pylib_dir in sys.path: + sys.path.append(pylib_dir) + suite = unittest.TestSuite() + for test_name in args: + suite.addTests(loader.loadTestsFromName(test_name)) + else: + suite = loader.discover(pylib_dir, pattern='*_unittest.py') + + runner = unittest.runner.TextTestRunner(verbosity=(options.verbose + 1)) + result = runner.run(suite) + + if options.unexpected_failures: + WriteUnexpectedFailures(result, options.unexpected_failures) + + return 0 if result.wasSuccessful() else 1 + + +def WriteUnexpectedFailures(result, path): + + # This regex and UnitTestName() extracts the test_name in a way + # that can be handed back to the loader successfully. + + test_description = re.compile("(\w+) \(([\w.]+)\)") + + def UnitTestName(test): + m = test_description.match(str(test)) + return "%s.%s" % (m.group(2), m.group(1)) + + with open(path, 'w') as fp: + for (test, _) in result.failures + result.errors: + fp.write(UnitTestName(test) + '\n') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/mojo/tools/test_runner.py b/chromium/mojo/tools/test_runner.py new file mode 100755 index 00000000000..b0bb4d9f94b --- /dev/null +++ b/chromium/mojo/tools/test_runner.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# 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. + +"""A "smart" test runner for gtest unit tests (that caches successes).""" + +import logging +import os +import subprocess +import sys + +_logging = logging.getLogger() + +_script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(_script_dir, "pylib")) + +from transitive_hash import transitive_hash + +def main(argv): + logging.basicConfig() + # Uncomment to debug: + # _logging.setLevel(logging.DEBUG) + + if len(argv) < 3 or len(argv) > 4: + print "Usage: %s gtest_list_file root_dir [successes_cache_file]" % \ + os.path.basename(argv[0]) + return 0 if len(argv) < 2 else 1 + + _logging.debug("Test list file: %s", argv[1]) + with open(argv[1], 'rb') as f: + gtest_list = [y for y in [x.strip() for x in f.readlines()] \ + if y and y[0] != '#'] + _logging.debug("Test list: %s" % gtest_list) + + print "Running tests in directory: %s" % argv[2] + os.chdir(argv[2]) + + if len(argv) == 4 and argv[3]: + successes_cache_filename = argv[3] + print "Successes cache file: %s" % successes_cache_filename + else: + successes_cache_filename = None + print "No successes cache file (will run all tests unconditionally)" + + if successes_cache_filename: + # This file simply contains a list of transitive hashes of tests that + # succeeded. + try: + _logging.debug("Trying to read successes cache file: %s", + successes_cache_filename) + with open(argv[3], 'rb') as f: + successes = set([x.strip() for x in f.readlines()]) + _logging.debug("Successes: %s", successes) + except: + # Just assume that it didn't exist, or whatever. + print "Failed to read successes cache file %s (will create)" % argv[3] + successes = set() + + # Run gtests with color if we're on a TTY (and we're not being told explicitly + # what to do). + if sys.stdout.isatty() and 'GTEST_COLOR' not in os.environ: + _logging.debug("Setting GTEST_COLOR=yes") + os.environ['GTEST_COLOR'] = 'yes' + + # TODO(vtl): We may not close this file on failure. + successes_cache_file = open(successes_cache_filename, 'ab') \ + if successes_cache_filename else None + for gtest in gtest_list: + if gtest[0] == '*': + gtest = gtest[1:] + _logging.debug("%s is marked as non-cacheable" % gtest) + cacheable = False + else: + cacheable = True + + if successes_cache_file and cacheable: + _logging.debug("Getting transitive hash for %s ... " % gtest) + try: + gtest_hash = transitive_hash(gtest) + except: + print "Failed to get transitive hash for %s" % gtest + return 1 + _logging.debug(" Transitive hash: %s" % gtest_hash) + + if gtest_hash in successes: + print "Skipping %s (previously succeeded)" % gtest + continue + + print "Running %s...." % gtest, + sys.stdout.flush() + try: + subprocess.check_output(["./" + gtest], stderr=subprocess.STDOUT) + print "Succeeded" + # Record success. + if successes_cache_filename and cacheable: + successes.add(gtest_hash) + successes_cache_file.write(gtest_hash + '\n') + successes_cache_file.flush() + except subprocess.CalledProcessError as e: + print "Failed with exit code %d and output:" % e.returncode + print 72 * '-' + print e.output + print 72 * '-' + return 1 + except OSError as e: + print " Failed to start test" + return 1 + print "All tests succeeded" + if successes_cache_file: + successes_cache_file.close() + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/mojo/views/DEPS b/chromium/mojo/views/DEPS new file mode 100644 index 00000000000..a182521d711 --- /dev/null +++ b/chromium/mojo/views/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+base", + "+skia", + "+ui/aura", + "+ui/base", + "+ui/events", + "+ui/views", + "+ui/wm", +] diff --git a/chromium/mojo/views/native_widget_view_manager.cc b/chromium/mojo/views/native_widget_view_manager.cc new file mode 100644 index 00000000000..c5d32e1c498 --- /dev/null +++ b/chromium/mojo/views/native_widget_view_manager.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 "mojo/views/native_widget_view_manager.h" + +#include "mojo/aura/window_tree_host_mojo.h" +#include "mojo/services/public/cpp/input_events/input_events_type_converters.h" +#include "mojo/services/public/cpp/view_manager/view.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/base/ime/input_method.h" +#include "ui/base/ime/input_method_delegate.h" +#include "ui/base/ime/input_method_factory.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/wm/core/base_focus_rules.h" +#include "ui/wm/core/focus_controller.h" + +namespace mojo { +namespace { + +// TODO: figure out what this should be. +class FocusRulesImpl : public wm::BaseFocusRules { + public: + FocusRulesImpl() {} + virtual ~FocusRulesImpl() {} + + virtual bool SupportsChildActivation(aura::Window* window) const OVERRIDE { + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusRulesImpl); +}; + +class MinimalInputEventFilter : public ui::internal::InputMethodDelegate, + public ui::EventHandler { + public: + explicit MinimalInputEventFilter(aura::Window* root) + : root_(root), + input_method_( + ui::CreateInputMethod(this, gfx::kNullAcceleratedWidget).Pass()) { + ui::InitializeInputMethodForTesting(); + input_method_->Init(true); + root_->AddPreTargetHandler(this); + root_->SetProperty(aura::client::kRootWindowInputMethodKey, + input_method_.get()); + } + + virtual ~MinimalInputEventFilter() { + root_->RemovePreTargetHandler(this); + root_->SetProperty(aura::client::kRootWindowInputMethodKey, + static_cast<ui::InputMethod*>(NULL)); + } + + private: + // ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE { + // See the comment in InputMethodEventFilter::OnKeyEvent() for details. + if (event->IsTranslated()) { + event->SetTranslated(false); + } else { + if (input_method_->DispatchKeyEvent(*event)) + event->StopPropagation(); + } + } + + // ui::internal::InputMethodDelegate: + virtual bool DispatchKeyEventPostIME(const ui::KeyEvent& event) OVERRIDE { + // See the comment in InputMethodEventFilter::DispatchKeyEventPostIME() for + // details. + ui::KeyEvent aura_event(event); + aura_event.SetTranslated(true); + ui::EventDispatchDetails details = + root_->GetHost()->dispatcher()->OnEventFromSource(&aura_event); + return aura_event.handled() || details.dispatcher_destroyed; + } + + aura::Window* root_; + scoped_ptr<ui::InputMethod> input_method_; + + DISALLOW_COPY_AND_ASSIGN(MinimalInputEventFilter); +}; + +} // namespace + +NativeWidgetViewManager::NativeWidgetViewManager( + views::internal::NativeWidgetDelegate* delegate, view_manager::Node* node) + : NativeWidgetAura(delegate), + node_(node) { + node_->active_view()->AddObserver(this); + window_tree_host_.reset(new WindowTreeHostMojo(node_, this)); + window_tree_host_->InitHost(); + + ime_filter_.reset( + new MinimalInputEventFilter(window_tree_host_->window())); + + focus_client_.reset(new wm::FocusController(new FocusRulesImpl)); + + aura::client::SetFocusClient(window_tree_host_->window(), + focus_client_.get()); + aura::client::SetActivationClient(window_tree_host_->window(), + focus_client_.get()); + window_tree_host_->window()->AddPreTargetHandler(focus_client_.get()); +} + +NativeWidgetViewManager::~NativeWidgetViewManager() { + node_->active_view()->RemoveObserver(this); +} + +void NativeWidgetViewManager::InitNativeWidget( + const views::Widget::InitParams& in_params) { + views::Widget::InitParams params(in_params); + params.parent = window_tree_host_->window(); + NativeWidgetAura::InitNativeWidget(params); +} + +void NativeWidgetViewManager::CompositorContentsChanged( + const SkBitmap& bitmap) { + node_->active_view()->SetContents(bitmap); +} + +void NativeWidgetViewManager::OnViewInputEvent(view_manager::View* view, + const EventPtr& event) { + scoped_ptr<ui::Event> ui_event(event.To<scoped_ptr<ui::Event> >()); + if (ui_event.get()) + window_tree_host_->SendEventToProcessor(ui_event.get()); +} + +} // namespace mojo diff --git a/chromium/mojo/views/native_widget_view_manager.h b/chromium/mojo/views/native_widget_view_manager.h new file mode 100644 index 00000000000..5981200bc20 --- /dev/null +++ b/chromium/mojo/views/native_widget_view_manager.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_VIEWS_NATIVE_WIDGET_VIEW_MANAGER_H_ +#define MOJO_VIEWS_NATIVE_WIDGET_VIEW_MANAGER_H_ + +#include "mojo/aura/window_tree_host_mojo_delegate.h" +#include "mojo/services/public/cpp/view_manager/node_observer.h" +#include "mojo/services/public/cpp/view_manager/view_observer.h" +#include "ui/views/widget/native_widget_aura.h" + +namespace ui { +namespace internal { +class InputMethodDelegate; +} +} + +namespace wm { +class FocusController; +} + +namespace mojo { + +class WindowTreeHostMojo; + +class NativeWidgetViewManager : public views::NativeWidgetAura, + public WindowTreeHostMojoDelegate, + public view_manager::ViewObserver, + public view_manager::NodeObserver { + public: + NativeWidgetViewManager(views::internal::NativeWidgetDelegate* delegate, + view_manager::Node* node); + virtual ~NativeWidgetViewManager(); + + private: + // Overridden from internal::NativeWidgetPrivate: + virtual void InitNativeWidget( + const views::Widget::InitParams& in_params) OVERRIDE; + + // WindowTreeHostMojoDelegate: + virtual void CompositorContentsChanged(const SkBitmap& bitmap) OVERRIDE; + + // view_manager::ViewObserver + virtual void OnViewInputEvent(view_manager::View* view, + const EventPtr& event) OVERRIDE; + + scoped_ptr<WindowTreeHostMojo> window_tree_host_; + + scoped_ptr<wm::FocusController> focus_client_; + + scoped_ptr<ui::internal::InputMethodDelegate> ime_filter_; + + view_manager::Node* node_; + + DISALLOW_COPY_AND_ASSIGN(NativeWidgetViewManager); +}; + +} // namespace mojo + +#endif // MOJO_VIEWS_NATIVE_WIDGET_VIEW_MANAGER_H_ diff --git a/chromium/mojo/views/views_init.cc b/chromium/mojo/views/views_init.cc new file mode 100644 index 00000000000..50651ab1ae8 --- /dev/null +++ b/chromium/mojo/views/views_init.cc @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/views/views_init.h" + +#include "base/i18n/icu_util.h" +#include "base/path_service.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_paths.h" + +namespace mojo { + +ViewsInit::ViewsInit() { + base::i18n::InitializeICU(); + + ui::RegisterPathProvider(); + + base::FilePath ui_test_pak_path; + CHECK(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); + ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); +} + +ViewsInit::~ViewsInit() { +} + +} // namespace mojo diff --git a/chromium/mojo/views/views_init.h b/chromium/mojo/views/views_init.h new file mode 100644 index 00000000000..ad3b95e25cb --- /dev/null +++ b/chromium/mojo/views/views_init.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_VIEWS_VIEWS_INIT_H_ +#define MOJO_VIEWS_VIEWS_INIT_H_ + +#include "mojo/aura/aura_init.h" + +namespace mojo { + +// Sets up necessary state for views when run with the viewmanager. +class ViewsInit { + public: + ViewsInit(); + ~ViewsInit(); + + private: + AuraInit aura_init_; + + DISALLOW_COPY_AND_ASSIGN(ViewsInit); +}; + +} // namespace mojo + +#endif // MOJO_VIEWS_VIEWS_INIT_H_ |