Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
new file mode 100644
index 0000000..9341abb
--- /dev/null
+++ b/mojo/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+group("mojo") {
+ # Meta-target, don't link into production code.
+ testonly = true
+ declare_args() {
+ mojo_use_go = false
+ }
+ deps = [
+ ":tests",
+ "//mojo/apps/js",
+ "//mojo/common",
+ "//mojo/examples",
+ "//mojo/public",
+ "//mojo/services",
+ "//mojo/shell:mojo_shell",
+ "//mojo/tools/package_manager",
+ ]
+
+ if (is_linux) {
+ deps += [
+ "//mojo/python",
+ ]
+ if (mojo_use_go) {
+ deps += [
+ "//mojo/go",
+ ]
+ }
+ }
+}
+
+group("tests") {
+ testonly = true
+ deps = [
+ "//mojo/application_manager:mojo_application_manager_unittests",
+ "//mojo/apps/js/test:mojo_apps_js_unittests",
+ "//mojo/bindings/js/tests:mojo_js_unittests",
+ "//mojo/common:mojo_common_unittests",
+ "//mojo/edk/system:mojo_message_pipe_perftests",
+ "//mojo/edk/system:mojo_system_unittests",
+ "//mojo/public/c/system/tests:perftests",
+ "//mojo/public/cpp/application/tests:mojo_public_application_unittests",
+ "//mojo/public/cpp/bindings/tests:mojo_public_bindings_unittests",
+ "//mojo/public/cpp/environment/tests:mojo_public_environment_unittests",
+ "//mojo/public/cpp/system/tests:mojo_public_system_unittests",
+ "//mojo/public/cpp/utility/tests:mojo_public_utility_unittests",
+ "//mojo/services/clipboard:mojo_clipboard_unittests",
+ "//mojo/services/public/cpp/surfaces/tests:mojo_surfaces_lib_unittests",
+ "//mojo/shell:mojo_external_application_tests",
+ "//mojo/shell:mojo_shell_tests",
+ "//mojo/tools:message_generator",
+ ]
+
+ if (use_aura) {
+ deps += [
+ "//mojo/services/public/cpp/view_manager/tests:mojo_view_manager_lib_unittests",
+ "//mojo/services/view_manager:mojo_view_manager_unittests",
+ "//mojo/services/window_manager:mojo_core_window_manager_unittests",
+ ]
+ }
+}
+
+if (is_android) {
+ import("//build/config/android/rules.gni")
+
+ generate_jni("jni_headers") {
+ sources = [
+ "android/javatests/src/org/chromium/mojo/MojoTestCase.java",
+ "android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java",
+ "android/system/src/org/chromium/mojo/system/impl/CoreImpl.java",
+ "services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java",
+ ]
+
+ jni_package = "mojo"
+ }
+}
diff --git a/mojo/DEPS b/mojo/DEPS
new file mode 100644
index 0000000..c37b8de
--- /dev/null
+++ b/mojo/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+base",
+ "+build",
+ "+mojo",
+ "-mojo/edk/system",
+ "+testing",
+]
diff --git a/mojo/OWNERS b/mojo/OWNERS
new file mode 100644
index 0000000..b864087
--- /dev/null
+++ b/mojo/OWNERS
@@ -0,0 +1,11 @@
+aa@chromium.org
+abarth@chromium.org
+ben@chromium.org
+darin@chromium.org
+davemoore@chromium.org
+jamesr@chromium.org
+mpcomplete@chromium.org
+qsr@chromium.org
+sky@chromium.org
+viettrungluu@chromium.org
+yzshen@chromium.org
diff --git a/mojo/PRESUBMIT.py b/mojo/PRESUBMIT.py
new file mode 100644
index 0000000..a7dc055
--- /dev/null
+++ b/mojo/PRESUBMIT.py
@@ -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.
+
+"""Presubmit script for mojo
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+import os.path
+
+def CheckChangeOnUpload(input_api, output_api):
+ # Additional python module paths (we're in src/mojo/); not everyone needs
+ # them, but it's easiest to add them to everyone's path.
+ # For ply and jinja2:
+ third_party_path = os.path.join(
+ input_api.PresubmitLocalPath(), "..", "third_party")
+ # For the bindings generator:
+ mojo_public_bindings_pylib_path = os.path.join(
+ input_api.PresubmitLocalPath(), "public", "tools", "bindings", "pylib")
+ # TODO(vtl): Don't lint these files until the (many) problems are fixed
+ # (possibly by deleting/rewriting some files).
+ temporary_black_list = input_api.DEFAULT_BLACK_LIST + \
+ (r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]pylib[\\\/]mojom[\\\/]"
+ r"generate[\\\/].+\.py$",
+ r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]generators[\\\/].+\.py$",
+ r".*\bspy[\\\/]ui[\\\/].+\.py$",
+ r".*\btools[\\\/]pylib[\\\/]transitive_hash\.py$",
+ r".*\btools[\\\/]test_runner\.py$")
+
+ results = []
+ pylint_extra_paths = [third_party_path, mojo_public_bindings_pylib_path]
+ results += input_api.canned_checks.RunPylint(
+ input_api, output_api, extra_paths_list=pylint_extra_paths,
+ black_list=temporary_black_list)
+ return results
diff --git a/mojo/README.md b/mojo/README.md
new file mode 100644
index 0000000..5af1dcb
--- /dev/null
+++ b/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/mojo/android/DEPS b/mojo/android/DEPS
new file mode 100644
index 0000000..c80012b
--- /dev/null
+++ b/mojo/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+jni",
+]
diff --git a/mojo/android/javatests/AndroidManifest.xml b/mojo/android/javatests/AndroidManifest.xml
new file mode 100644
index 0000000..290a1de
--- /dev/null
+++ b/mojo/android/javatests/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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="20" />
+ <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.INJECT_EVENTS" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
+</manifest>
diff --git a/mojo/android/javatests/DEPS b/mojo/android/javatests/DEPS
new file mode 100644
index 0000000..78cf465
--- /dev/null
+++ b/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/mojo/android/javatests/apk/.empty b/mojo/android/javatests/apk/.empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/android/javatests/apk/.empty
diff --git a/mojo/android/javatests/init_library.cc b/mojo/android/javatests/init_library.cc
new file mode 100644
index 0000000..c738981
--- /dev/null
+++ b/mojo/android/javatests/init_library.cc
@@ -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.
+
+#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/javatests/validation_test_util.h"
+#include "mojo/android/system/core_impl.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+
+namespace {
+
+base::android::RegistrationMethod kMojoRegisteredMethods[] = {
+ { "CoreImpl", mojo::android::RegisterCoreImpl },
+ { "MojoTestCase", mojo::android::RegisterMojoTestCase },
+ { "ValidationTestUtil", mojo::android::RegisterValidationTestUtil },
+};
+
+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;
+
+ mojo::embedder::Init(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+
+ return JNI_VERSION_1_4;
+}
diff --git a/mojo/android/javatests/mojo_test_case.cc b/mojo/android/javatests/mojo_test_case.cc
new file mode 100644
index 0000000..f14dfd8
--- /dev/null
+++ b/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/mojo/android/javatests/mojo_test_case.h b/mojo/android/javatests/mojo_test_case.h
new file mode 100644
index 0000000..2ce3428
--- /dev/null
+++ b/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/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java
new file mode 100644
index 0000000..9a7b473
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.SharedBufferHandle;
+import org.chromium.mojo.system.UntypedHandle;
+import org.chromium.mojo.system.impl.CoreImpl;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A mock handle, that does nothing.
+ */
+public class HandleMock implements UntypedHandle, MessagePipeHandle,
+ ProducerHandle, ConsumerHandle, SharedBufferHandle {
+
+ /**
+ * @see Handle#close()
+ */
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+
+ /**
+ * @see Handle#wait(Core.HandleSignals, long)
+ */
+ @Override
+ public int wait(Core.HandleSignals signals, long deadline) {
+ // Do nothing.
+ return MojoResult.OK;
+ }
+
+ /**
+ * @see Handle#isValid()
+ */
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ /**
+ * @see Handle#toUntypedHandle()
+ */
+ @Override
+ public UntypedHandle toUntypedHandle() {
+ return this;
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#getCore()
+ */
+ @Override
+ public Core getCore() {
+ return CoreImpl.getInstance();
+ }
+
+ /**
+ * @see org.chromium.mojo.system.UntypedHandle#pass()
+ */
+ @Override
+ public HandleMock pass() {
+ return this;
+ }
+
+ /**
+ * @see Handle#releaseNativeHandle()
+ */
+ @Override
+ public int releaseNativeHandle() {
+ return 0;
+ }
+
+ /**
+ * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags)
+ */
+ @Override
+ public int discardData(int numBytes, DataPipe.ReadFlags flags) {
+ // Do nothing.
+ return 0;
+ }
+
+ /**
+ * @see ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags)
+ */
+ @Override
+ public int readData(ByteBuffer elements,
+ DataPipe.ReadFlags flags) {
+ // Do nothing.
+ return 0;
+ }
+
+ /**
+ * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags)
+ */
+ @Override
+ public ByteBuffer beginReadData(int numBytes,
+ DataPipe.ReadFlags flags) {
+ // Do nothing.
+ return null;
+ }
+
+ /**
+ * @see ConsumerHandle#endReadData(int)
+ */
+ @Override
+ public void endReadData(int numBytesRead) {
+ // Do nothing.
+ }
+
+ /**
+ * @see ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags)
+ */
+ @Override
+ public int writeData(ByteBuffer elements,
+ DataPipe.WriteFlags flags) {
+ // Do nothing.
+ return 0;
+ }
+
+ /**
+ * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags)
+ */
+ @Override
+ public ByteBuffer beginWriteData(int numBytes,
+ DataPipe.WriteFlags flags) {
+ // Do nothing.
+ return null;
+ }
+
+ /**
+ * @see ProducerHandle#endWriteData(int)
+ */
+ @Override
+ public void endWriteData(int numBytesWritten) {
+ // Do nothing.
+ }
+
+ /**
+ * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List,
+ * MessagePipeHandle.WriteFlags)
+ */
+ @Override
+ public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles,
+ WriteFlags flags) {
+ // Do nothing.
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags)
+ */
+ @Override
+ public ReadMessageResult readMessage(ByteBuffer bytes, int maxNumberOfHandles,
+ ReadFlags flags) {
+ // Do nothing.
+ return new ReadMessageResult();
+ }
+
+ /**
+ * @see UntypedHandle#toMessagePipeHandle()
+ */
+ @Override
+ public MessagePipeHandle toMessagePipeHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toDataPipeConsumerHandle()
+ */
+ @Override
+ public ConsumerHandle toDataPipeConsumerHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toDataPipeProducerHandle()
+ */
+ @Override
+ public ProducerHandle toDataPipeProducerHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toSharedBufferHandle()
+ */
+ @Override
+ public SharedBufferHandle toSharedBufferHandle() {
+ return this;
+ }
+
+ /**
+ * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions)
+ */
+ @Override
+ public SharedBufferHandle duplicate(DuplicateOptions options) {
+ // Do nothing.
+ return null;
+ }
+
+ /**
+ * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags)
+ */
+ @Override
+ public ByteBuffer map(long offset, long numBytes, MapFlags flags) {
+ // Do nothing.
+ return null;
+ }
+
+ /**
+ * @see SharedBufferHandle#unmap(java.nio.ByteBuffer)
+ */
+ @Override
+ public void unmap(ByteBuffer buffer) {
+ // Do nothing.
+ }
+
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java
new file mode 100644
index 0000000..3adb2fb
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java
@@ -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.
+
+package org.chromium.mojo;
+
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+import org.chromium.base.JNINamespace;
+import org.chromium.base.library_loader.LibraryLoader;
+
+/**
+ * Base class to test mojo. Setup the environment.
+ */
+@JNINamespace("mojo::android")
+public class MojoTestCase extends InstrumentationTestCase {
+
+ private long mTestEnvironmentPointer;
+
+ /**
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ LibraryLoader.ensureInitialized();
+ nativeInitApplicationContext(getInstrumentation().getTargetContext());
+ mTestEnvironmentPointer = nativeSetupTestEnvironment();
+ }
+
+ /**
+ * @see android.test.InstrumentationTestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ nativeTearDownTestEnvironment(mTestEnvironmentPointer);
+ super.tearDown();
+ }
+
+ private native void nativeInitApplicationContext(Context context);
+
+ private native long nativeSetupTestEnvironment();
+
+ private native void nativeTearDownTestEnvironment(long testEnvironment);
+
+ protected native void nativeRunLoop(long timeoutMS);
+
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java
new file mode 100644
index 0000000..2134a9f
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java
@@ -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.
+
+package org.chromium.mojo;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Random;
+
+/**
+ * Utilities methods for tests.
+ */
+public final class TestUtils {
+
+ private static final Random RANDOM = new Random();
+
+ /**
+ * Returns a new direct ByteBuffer of the given size with random (but reproducible) data.
+ */
+ public static ByteBuffer newRandomBuffer(int size) {
+ byte bytes[] = new byte[size];
+ RANDOM.setSeed(size);
+ RANDOM.nextBytes(bytes);
+ ByteBuffer data = ByteBuffer.allocateDirect(size);
+ data.order(ByteOrder.nativeOrder());
+ data.put(bytes);
+ data.flip();
+ return data;
+ }
+
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java
new file mode 100644
index 0000000..a46a157
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import java.nio.charset.Charset;
+
+/**
+ * Testing {@link BindingsHelper}.
+ */
+public class BindingsHelperTest extends TestCase {
+
+ /**
+ * Testing {@link BindingsHelper#utf8StringSizeInBytes(String)}.
+ */
+ @SmallTest
+ public void testUTF8StringLength() {
+ String[] stringsToTest = {
+ "",
+ "a",
+ "hello world",
+ "éléphant",
+ "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕",
+ "你午饭想吃什么",
+ "你午饭想吃什么\0éléphant",
+ };
+ for (String s : stringsToTest) {
+ assertEquals(s.getBytes(Charset.forName("utf8")).length,
+ BindingsHelper.utf8StringSizeInBytes(s));
+ }
+ assertEquals(1, BindingsHelper.utf8StringSizeInBytes("\0"));
+ String s = new StringBuilder().appendCodePoint(0x0).appendCodePoint(0x80).
+ appendCodePoint(0x800).appendCodePoint(0x10000).toString();
+ assertEquals(10, BindingsHelper.utf8StringSizeInBytes(s));
+ assertEquals(10, s.getBytes(Charset.forName("utf8")).length);
+ }
+
+ /**
+ * Testing {@link BindingsHelper#align(int)}.
+ */
+ @SmallTest
+ public void testAlign() {
+ for (int i = 0; i < 3 * BindingsHelper.ALIGNMENT; ++i) {
+ int j = BindingsHelper.align(i);
+ assertTrue(j >= i);
+ assertTrue(j % BindingsHelper.ALIGNMENT == 0);
+ assertTrue(j - i < BindingsHelper.ALIGNMENT);
+ }
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java
new file mode 100644
index 0000000..e957bfe
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java
@@ -0,0 +1,252 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.chromium.mojo.HandleMock;
+import org.chromium.mojo.bindings.test.mojom.imported.Color;
+import org.chromium.mojo.bindings.test.mojom.imported.Point;
+import org.chromium.mojo.bindings.test.mojom.imported.Shape;
+import org.chromium.mojo.bindings.test.mojom.imported.Thing;
+import org.chromium.mojo.bindings.test.mojom.sample.Bar;
+import org.chromium.mojo.bindings.test.mojom.sample.Bar.Type;
+import org.chromium.mojo.bindings.test.mojom.sample.DefaultsTest;
+import org.chromium.mojo.bindings.test.mojom.sample.Enum;
+import org.chromium.mojo.bindings.test.mojom.sample.Foo;
+import org.chromium.mojo.bindings.test.mojom.sample.InterfaceConstants;
+import org.chromium.mojo.bindings.test.mojom.sample.SampleServiceConstants;
+import org.chromium.mojo.bindings.test.mojom.test_structs.EmptyStruct;
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+/**
+ * Testing generated classes and associated features.
+ */
+public class BindingsTest extends TestCase {
+
+ /**
+ * Create a new typical Bar instance.
+ */
+ private static Bar newBar() {
+ Bar bar = new Bar();
+ bar.alpha = (byte) 0x01;
+ bar.beta = (byte) 0x02;
+ bar.gamma = (byte) 0x03;
+ bar.type = Type.BOTH;
+ return bar;
+ }
+
+ /**
+ * Check that 2 Bar instances are equals.
+ */
+ private static void assertBarEquals(Bar bar, Bar bar2) {
+ if (bar == bar2) {
+ return;
+ }
+ assertTrue(bar != null && bar2 != null);
+ assertEquals(bar.alpha, bar2.alpha);
+ assertEquals(bar.beta, bar2.beta);
+ assertEquals(bar.gamma, bar2.gamma);
+ assertEquals(bar.type, bar2.type);
+ }
+
+ /**
+ * Create a new typical Foo instance.
+ */
+ private static Foo createFoo() {
+ Foo foo = new Foo();
+ foo.name = "HELLO WORLD";
+ foo.arrayOfArrayOfBools = new boolean[][] {
+ { true, false, true }, { }, { }, { false }, { true } };
+ foo.bar = newBar();
+ foo.a = true;
+ foo.c = true;
+ foo.data = new byte[] { 0x01, 0x02, 0x03 };
+ foo.extraBars = new Bar[] { newBar(), newBar() };
+ String[][][] strings = new String[3][2][1];
+ for (int i0 = 0; i0 < strings.length; ++i0) {
+ for (int i1 = 0; i1 < strings[i0].length; ++i1) {
+ for (int i2 = 0; i2 < strings[i0][i1].length; ++i2) {
+ strings[i0][i1][i2] = "Hello(" + i0 + ", " + i1 + ", " + i2 + ")";
+ }
+ }
+ }
+ foo.multiArrayOfStrings = strings;
+ ConsumerHandle[] inputStreams = new ConsumerHandle[5];
+ for (int i = 0; i < inputStreams.length; ++i) {
+ inputStreams[i] = new HandleMock();
+ }
+ foo.inputStreams = inputStreams;
+ ProducerHandle[] outputStreams = new ProducerHandle[3];
+ for (int i = 0; i < outputStreams.length; ++i) {
+ outputStreams[i] = new HandleMock();
+ }
+ foo.outputStreams = outputStreams;
+ foo.source = new HandleMock();
+ return foo;
+ }
+
+ /**
+ * Check that 2 Foo instances are equals.
+ */
+ private static void assertFooEquals(Foo foo1, Foo foo2) {
+ assertEquals(foo1.a, foo2.a);
+ assertEquals(foo1.b, foo2.b);
+ assertEquals(foo1.c, foo2.c);
+ assertEquals(foo1.name, foo2.name);
+ assertEquals(foo1.x, foo2.x);
+ assertEquals(foo1.y, foo2.y);
+ TestCase.assertTrue(Arrays.deepEquals(foo1.arrayOfArrayOfBools, foo2.arrayOfArrayOfBools));
+ assertBarEquals(foo1.bar, foo2.bar);
+ assertTrue(Arrays.equals(foo1.data, foo2.data));
+ TestCase.assertTrue(Arrays.deepEquals(foo1.multiArrayOfStrings, foo2.multiArrayOfStrings));
+ assertEquals(foo1.source, foo2.source);
+ TestCase.assertTrue(Arrays.deepEquals(foo1.inputStreams, foo2.inputStreams));
+ TestCase.assertTrue(Arrays.deepEquals(foo1.outputStreams, foo2.outputStreams));
+ if (foo1.extraBars != foo2.extraBars) {
+ assertEquals(foo1.extraBars.length, foo2.extraBars.length);
+ for (int i = 0; i < foo1.extraBars.length; ++i) {
+ assertBarEquals(foo1.extraBars[i], foo2.extraBars[i]);
+ }
+ }
+ }
+
+ private static <T> void checkConstantField(
+ Field field, Class<T> expectedClass, T value) throws IllegalAccessException {
+ assertEquals(expectedClass, field.getType());
+ assertEquals(Modifier.FINAL, field.getModifiers() & Modifier.FINAL);
+ assertEquals(Modifier.STATIC, field.getModifiers() & Modifier.STATIC);
+ assertEquals(value, field.get(null));
+ }
+
+ private static <T> void checkField(Field field, Class<T> expectedClass,
+ Object object, T value) throws IllegalArgumentException, IllegalAccessException {
+ assertEquals(expectedClass, field.getType());
+ assertEquals(0, field.getModifiers() & Modifier.FINAL);
+ assertEquals(0, field.getModifiers() & Modifier.STATIC);
+ assertEquals(value, field.get(object));
+ }
+
+ /**
+ * Testing constants are correctly generated.
+ */
+ @SmallTest
+ public void testConstants() throws NoSuchFieldException, SecurityException,
+ IllegalAccessException {
+ checkConstantField(SampleServiceConstants.class.getField("TWELVE"), byte.class, (byte) 12);
+ checkConstantField(InterfaceConstants.class.getField("LONG"), long.class, 4405L);
+ }
+
+ /**
+ * Testing enums are correctly generated.
+ */
+ @SmallTest
+ public void testEnums() throws NoSuchFieldException, SecurityException,
+ IllegalAccessException {
+ checkConstantField(Color.class.getField("RED"), int.class, 0);
+ checkConstantField(Color.class.getField("BLACK"), int.class, 1);
+
+ checkConstantField(Enum.class.getField("VALUE"), int.class, 0);
+
+ checkConstantField(Shape.class.getField("RECTANGLE"), int.class, 1);
+ checkConstantField(Shape.class.getField("CIRCLE"), int.class, 2);
+ checkConstantField(Shape.class.getField("TRIANGLE"), int.class, 3);
+ }
+
+ /**
+ * Testing default values on structs.
+ *
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ @SmallTest
+ public void testStructDefaults() throws NoSuchFieldException, SecurityException,
+ IllegalArgumentException, IllegalAccessException {
+ // Check default values.
+ DefaultsTest test = new DefaultsTest();
+
+ checkField(DefaultsTest.class.getField("a0"), byte.class, test, (byte) -12);
+ checkField(DefaultsTest.class.getField("a1"), byte.class, test, (byte) 12);
+ checkField(DefaultsTest.class.getField("a2"), short.class, test, (short) 1234);
+ checkField(DefaultsTest.class.getField("a3"), short.class, test, (short) 34567);
+ checkField(DefaultsTest.class.getField("a4"), int.class, test, 123456);
+ checkField(DefaultsTest.class.getField("a5"), int.class, test, (int) 3456789012L);
+ checkField(DefaultsTest.class.getField("a6"), long.class, test, -111111111111L);
+ // -8446744073709551617 == 9999999999999999999 - 2 ^ 64.
+ checkField(DefaultsTest.class.getField("a7"), long.class, test, -8446744073709551617L);
+ checkField(DefaultsTest.class.getField("a8"), int.class, test, 0x12345);
+ checkField(DefaultsTest.class.getField("a9"), int.class, test, -0x12345);
+ checkField(DefaultsTest.class.getField("a10"), int.class, test, 1234);
+ checkField(DefaultsTest.class.getField("a11"), boolean.class, test, true);
+ checkField(DefaultsTest.class.getField("a12"), boolean.class, test, false);
+ checkField(DefaultsTest.class.getField("a13"), float.class, test, (float) 123.25);
+ checkField(DefaultsTest.class.getField("a14"), double.class, test, 1234567890.123);
+ checkField(DefaultsTest.class.getField("a15"), double.class, test, 1E10);
+ checkField(DefaultsTest.class.getField("a16"), double.class, test, -1.2E+20);
+ checkField(DefaultsTest.class.getField("a17"), double.class, test, +1.23E-20);
+ checkField(DefaultsTest.class.getField("a18"), byte[].class, test, null);
+ checkField(DefaultsTest.class.getField("a19"), String.class, test, null);
+ checkField(DefaultsTest.class.getField("a20"), int.class, test, Bar.Type.BOTH);
+ checkField(DefaultsTest.class.getField("a21"), Point.class, test, null);
+
+ assertNotNull(test.a22);
+ checkField(DefaultsTest.class.getField("a22"), Thing.class, test, test.a22);
+ checkField(DefaultsTest.class.getField("a23"), long.class, test, -1L);
+ checkField(DefaultsTest.class.getField("a24"), long.class, test, 0x123456789L);
+ checkField(DefaultsTest.class.getField("a25"), long.class, test, -0x123456789L);
+ }
+
+ /**
+ * Testing generation of the Foo class.
+ *
+ * @throws IllegalAccessException
+ */
+ @SmallTest
+ public void testFooGeneration() throws NoSuchFieldException, SecurityException,
+ IllegalAccessException {
+ // Checking Foo constants.
+ checkConstantField(Foo.class.getField("FOOBY"), String.class, "Fooby");
+
+ // Checking Foo default values.
+ Foo foo = new Foo();
+ checkField(Foo.class.getField("name"), String.class, foo, Foo.FOOBY);
+
+ assertNotNull(foo.source);
+ assertFalse(foo.source.isValid());
+ checkField(Foo.class.getField("source"), MessagePipeHandle.class, foo, foo.source);
+ }
+
+ /**
+ * Testing serialization of the Foo class.
+ */
+ @SmallTest
+ public void testFooSerialization() {
+ // Checking serialization and deserialization of a Foo object.
+ Foo typicalFoo = createFoo();
+ Message serializedFoo = typicalFoo.serialize(null);
+ Foo deserializedFoo = Foo.deserialize(serializedFoo);
+ assertFooEquals(typicalFoo, deserializedFoo);
+ }
+
+ /**
+ * Testing serialization of the EmptyStruct class.
+ */
+ @SmallTest
+ public void testEmptyStructSerialization() {
+ // Checking serialization and deserialization of a EmptyStruct object.
+ Message serializedStruct = new EmptyStruct().serialize(null);
+ EmptyStruct emptyStruct = EmptyStruct.deserialize(serializedStruct);
+ assertNotNull(emptyStruct);
+ }
+
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java
new file mode 100644
index 0000000..880577a
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.TestUtils;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.Pair;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for bindings tests.
+ */
+public class BindingsTestUtils {
+
+ /**
+ * {@link MessageReceiver} that records any message it receives.
+ */
+ public static class RecordingMessageReceiver extends SideEffectFreeCloseable
+ implements MessageReceiver {
+
+ public final List<Message> messages = new ArrayList<Message>();
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ messages.add(message);
+ return true;
+ }
+ }
+
+ /**
+ * {@link MessageReceiverWithResponder} that records any message it receives.
+ */
+ public static class RecordingMessageReceiverWithResponder extends RecordingMessageReceiver
+ implements MessageReceiverWithResponder {
+
+ public final List<Pair<Message, MessageReceiver>> messagesWithReceivers =
+ new ArrayList<Pair<Message, MessageReceiver>>();
+
+ /**
+ * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver)
+ */
+ @Override
+ public boolean acceptWithResponder(Message message, MessageReceiver responder) {
+ messagesWithReceivers.add(Pair.create(message, responder));
+ return true;
+ }
+ }
+
+ /**
+ * {@link ConnectionErrorHandler} that records any error it received.
+ */
+ public static class CapturingErrorHandler implements ConnectionErrorHandler {
+
+ private MojoException mLastMojoException = null;
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ mLastMojoException = e;
+ }
+
+ /**
+ * Returns the last recorded exception.
+ */
+ public MojoException getLastMojoException() {
+ return mLastMojoException;
+ }
+
+ }
+
+ /**
+ * Creates a new valid {@link Message}. The message will have a valid header.
+ */
+ public static Message newRandomMessage(int size) {
+ assert size > 16;
+ ByteBuffer message = TestUtils.newRandomBuffer(size);
+ int[] headerAsInts = { 16, 2, 0, 0 };
+ for (int i = 0; i < 4; ++i) {
+ message.putInt(4 * i, headerAsInts[i]);
+ }
+ message.position(0);
+ return new Message(message, new ArrayList<Handle>());
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java
new file mode 100644
index 0000000..dfb8e21
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.chromium.mojo.bindings.Callbacks.Callback1;
+import org.chromium.mojo.bindings.Callbacks.Callback7;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Testing generated callbacks
+ */
+public class CallbacksTest extends TestCase {
+
+ /**
+ * Testing {@link Callback1}.
+ */
+ @SmallTest
+ public void testCallback1() {
+ final List<Integer> parameters = new ArrayList<Integer>();
+ new Callback1<Integer>() {
+ @Override
+ public void call(Integer i1) {
+ parameters.add(i1);
+ }
+ }.call(1);
+ assertEquals(Arrays.asList(1), parameters);
+ }
+
+ /**
+ * Testing {@link Callback7}.
+ */
+ @SmallTest
+ public void testCallback7() {
+ final List<Integer> parameters = new ArrayList<Integer>();
+ new Callback7<Integer, Integer, Integer, Integer, Integer, Integer, Integer>() {
+ @Override
+ public void call(Integer i1, Integer i2, Integer i3, Integer i4, Integer i5, Integer i6,
+ Integer i7) {
+ parameters.add(i1);
+ parameters.add(i2);
+ parameters.add(i3);
+ parameters.add(i4);
+ parameters.add(i5);
+ parameters.add(i6);
+ parameters.add(i7);
+ }
+ }.call(1, 2, 3, 4, 5, 6, 7);
+ assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), parameters);
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java
new file mode 100644
index 0000000..2bf733c
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler;
+import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.impl.CoreImpl;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * Testing the {@link Connector} class.
+ */
+public class ConnectorTest extends MojoTestCase {
+
+ private static final long RUN_LOOP_TIMEOUT_MS = 25;
+
+ private static final int DATA_LENGTH = 1024;
+
+ private MessagePipeHandle mHandle;
+ private Connector mConnector;
+ private Message mTestMessage;
+ private RecordingMessageReceiver mReceiver;
+ private CapturingErrorHandler mErrorHandler;
+
+ /**
+ * @see MojoTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(
+ new MessagePipeHandle.CreateOptions());
+ mHandle = handles.first;
+ mConnector = new Connector(handles.second);
+ mReceiver = new RecordingMessageReceiver();
+ mConnector.setIncomingMessageReceiver(mReceiver);
+ mErrorHandler = new CapturingErrorHandler();
+ mConnector.setErrorHandler(mErrorHandler);
+ mConnector.start();
+ mTestMessage = BindingsTestUtils.newRandomMessage(DATA_LENGTH);
+ assertNull(mErrorHandler.getLastMojoException());
+ assertEquals(0, mReceiver.messages.size());
+ }
+
+ /**
+ * @see MojoTestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ mConnector.close();
+ mHandle.close();
+ super.tearDown();
+ }
+
+ /**
+ * Test sending a message through a {@link Connector}.
+ */
+ @SmallTest
+ public void testSendingMessage() {
+ mConnector.accept(mTestMessage);
+ assertNull(mErrorHandler.getLastMojoException());
+ ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH);
+ MessagePipeHandle.ReadMessageResult result = mHandle.readMessage(received, 0,
+ MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(MojoResult.OK, result.getMojoResult());
+ assertEquals(DATA_LENGTH, result.getMessageSize());
+ assertEquals(mTestMessage.getData(), received);
+ }
+
+ /**
+ * Test receiving a message through a {@link Connector}
+ */
+ @SmallTest
+ public void testReceivingMessage() {
+ mHandle.writeMessage(mTestMessage.getData(), new ArrayList<Handle>(),
+ MessagePipeHandle.WriteFlags.NONE);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertNull(mErrorHandler.getLastMojoException());
+ assertEquals(1, mReceiver.messages.size());
+ Message received = mReceiver.messages.get(0);
+ assertEquals(0, received.getHandles().size());
+ assertEquals(mTestMessage.getData(), received.getData());
+ }
+
+ /**
+ * Test receiving an error through a {@link Connector}.
+ */
+ @SmallTest
+ public void testErrors() {
+ mHandle.close();
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertNotNull(mErrorHandler.getLastMojoException());
+ assertEquals(MojoResult.FAILED_PRECONDITION,
+ mErrorHandler.getLastMojoException().getMojoResult());
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java
new file mode 100644
index 0000000..40fa43b
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.system.impl.CoreImpl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Testing the executor factory.
+ */
+public class ExecutorFactoryTest extends MojoTestCase {
+
+ private static final long RUN_LOOP_TIMEOUT_MS = 50;
+ private static final int CONCURRENCY_LEVEL = 5;
+ private static final ExecutorService WORKERS = Executors.newFixedThreadPool(CONCURRENCY_LEVEL);
+
+ private Executor mExecutor;
+ private List<Thread> mThreadContainer;
+
+ /**
+ * @see MojoTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance());
+ mThreadContainer = new ArrayList<Thread>();
+ }
+
+ /**
+ * Testing the {@link Executor} when called from the executor thread.
+ */
+ @SmallTest
+ public void testExecutorOnCurrentThread() {
+ Runnable action = new Runnable() {
+ @Override
+ public void run() {
+ mThreadContainer.add(Thread.currentThread());
+ }
+ };
+ mExecutor.execute(action);
+ mExecutor.execute(action);
+ assertEquals(0, mThreadContainer.size());
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(2, mThreadContainer.size());
+ for (Thread thread : mThreadContainer) {
+ assertEquals(Thread.currentThread(), thread);
+ }
+ }
+
+ /**
+ * Testing the {@link Executor} when called from another thread.
+ */
+ @SmallTest
+ public void testExecutorOnOtherThread() {
+ final CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY_LEVEL + 1);
+ for (int i = 0; i < CONCURRENCY_LEVEL; ++i) {
+ WORKERS.execute(new Runnable() {
+ @Override
+ public void run() {
+ mExecutor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ mThreadContainer.add(Thread.currentThread());
+ }
+ });
+ try {
+ barrier.await();
+ } catch (InterruptedException e) {
+ fail("Unexpected exception: " + e.getMessage());
+ } catch (BrokenBarrierException e) {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ }
+ });
+ }
+ try {
+ barrier.await();
+ } catch (InterruptedException e) {
+ fail("Unexpected exception: " + e.getMessage());
+ } catch (BrokenBarrierException e) {
+ fail("Unexpected exception: " + e.getMessage());
+ }
+ assertEquals(0, mThreadContainer.size());
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(CONCURRENCY_LEVEL, mThreadContainer.size());
+ for (Thread thread : mThreadContainer) {
+ assertEquals(Thread.currentThread(), thread);
+ }
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java
new file mode 100644
index 0000000..253add9
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler;
+import org.chromium.mojo.bindings.test.mojom.imported.ImportedInterface;
+import org.chromium.mojo.bindings.test.mojom.sample.Factory;
+import org.chromium.mojo.bindings.test.mojom.sample.FactoryClient;
+import org.chromium.mojo.bindings.test.mojom.sample.NamedObject;
+import org.chromium.mojo.bindings.test.mojom.sample.NamedObject.GetNameResponse;
+import org.chromium.mojo.bindings.test.mojom.sample.Request;
+import org.chromium.mojo.bindings.test.mojom.sample.Response;
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.impl.CoreImpl;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for interfaces / proxies / stubs generated for sample_factory.mojom.
+ */
+public class InterfacesTest extends MojoTestCase {
+
+ private static final long RUN_LOOP_TIMEOUT_MS = 25;
+
+ private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>();
+
+ /**
+ * Basic implementation of {@link NamedObject}.
+ */
+ public static class MockNamedObjectImpl extends CapturingErrorHandler implements NamedObject {
+
+ private String mName = "";
+
+ /**
+ * @see org.chromium.mojo.bindings.Interface#close()
+ */
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void setName(String name) {
+ mName = name;
+ }
+
+ @Override
+ public void getName(GetNameResponse callback) {
+ callback.call(mName);
+ }
+
+ public String getNameSynchronously() {
+ return mName;
+ }
+ }
+
+ /**
+ * Implementation of {@link GetNameResponse} keeping track of usage.
+ */
+ public static class RecordingGetNameResponse implements GetNameResponse {
+ private String mName;
+ private boolean mCalled;
+
+ public RecordingGetNameResponse() {
+ reset();
+ }
+
+ @Override
+ public void call(String name) {
+ mName = name;
+ mCalled = true;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public boolean wasCalled() {
+ return mCalled;
+ }
+
+ public void reset() {
+ mName = null;
+ mCalled = false;
+ }
+ }
+
+ /**
+ * Basic implementation of {@link Factory}.
+ */
+ public class MockFactoryImpl extends CapturingErrorHandler implements Factory {
+
+ private boolean mClosed = false;
+ private FactoryClient mFactoryClient;
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ @Override
+ public void setClient(FactoryClient client) {
+ mFactoryClient = client;
+ mCloseablesToClose.add(client);
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.Interface#close()
+ */
+ @Override
+ public void close() {
+ mClosed = true;
+ }
+
+ @Override
+ public void doStuff(Request request, MessagePipeHandle pipe) {
+ if (pipe != null) {
+ pipe.close();
+ }
+ Response response = new Response();
+ response.x = 42;
+ mFactoryClient.didStuff(response, "Hello");
+ }
+
+ @Override
+ public void doStuff2(ConsumerHandle pipe) {
+ }
+
+ @Override
+ public void createNamedObject(InterfaceRequest<NamedObject> obj) {
+ NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj);
+ }
+
+ @Override
+ public void requestImportedInterface(InterfaceRequest<ImportedInterface> obj,
+ RequestImportedInterfaceResponse callback) {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+
+ @Override
+ public void takeImportedInterface(ImportedInterface obj,
+ TakeImportedInterfaceResponse callback) {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+ }
+
+ /**
+ * Basic implementation of {@link FactoryClient}.
+ */
+ public static class MockFactoryClientImpl implements FactoryClient {
+
+ private boolean mClosed = false;
+ private boolean mDidStuffCalled = false;
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public boolean wasDidStuffCalled() {
+ return mDidStuffCalled;
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.Interface#close()
+ */
+ @Override
+ public void close() {
+ mClosed = true;
+ }
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ }
+
+ /**
+ * @see FactoryClient#didStuff(Response, java.lang.String)
+ */
+ @Override
+ public void didStuff(Response response, String text) {
+ mDidStuffCalled = true;
+ }
+
+ /**
+ * @see FactoryClient#didStuff2(String)
+ */
+ @Override
+ public void didStuff2(String text) {
+ }
+
+ }
+
+ /**
+ * @see MojoTestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ // Close the elements in the reverse order they were added. This is needed because it is an
+ // error to close the handle of a proxy without closing the proxy first.
+ Collections.reverse(mCloseablesToClose);
+ for (Closeable c : mCloseablesToClose) {
+ c.close();
+ }
+ super.tearDown();
+ }
+
+ private <I extends Interface, P extends Interface.Proxy> P newProxyOverPipe(
+ Interface.Manager<I, P> manager, I impl) {
+ Pair<MessagePipeHandle, MessagePipeHandle> handles =
+ CoreImpl.getInstance().createMessagePipe(null);
+ P proxy = manager.attachProxy(handles.first);
+ mCloseablesToClose.add(proxy);
+ manager.bind(impl, handles.second);
+ return proxy;
+ }
+
+ private <I extends InterfaceWithClient<C>, P extends InterfaceWithClient.Proxy<C>,
+ C extends Interface> P newProxyOverPipeWithClient(
+ InterfaceWithClient.Manager<I, P, C> manager, I impl, C client) {
+ Pair<MessagePipeHandle, MessagePipeHandle> handles =
+ CoreImpl.getInstance().createMessagePipe(null);
+ P proxy = manager.attachProxy(handles.first, client);
+ mCloseablesToClose.add(proxy);
+ manager.bind(impl, handles.second);
+ return proxy;
+ }
+
+ /**
+ * Check that the given proxy receives the calls. If |impl| is not null, also check that the
+ * calls are forwared to |impl|.
+ */
+ private void checkProxy(NamedObject.Proxy proxy, MockNamedObjectImpl impl) {
+ final String NAME = "hello world";
+ RecordingGetNameResponse callback = new RecordingGetNameResponse();
+ CapturingErrorHandler errorHandler = new CapturingErrorHandler();
+ proxy.setErrorHandler(errorHandler);
+
+ if (impl != null) {
+ assertNull(impl.getLastMojoException());
+ assertEquals("", impl.getNameSynchronously());
+ }
+
+ proxy.getName(callback);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertNull(errorHandler.getLastMojoException());
+ assertTrue(callback.wasCalled());
+ assertEquals("", callback.getName());
+
+ callback.reset();
+ proxy.setName(NAME);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertNull(errorHandler.getLastMojoException());
+ if (impl != null) {
+ assertNull(impl.getLastMojoException());
+ assertEquals(NAME, impl.getNameSynchronously());
+ }
+
+ proxy.getName(callback);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertNull(errorHandler.getLastMojoException());
+ assertTrue(callback.wasCalled());
+ assertEquals(NAME, callback.getName());
+ }
+
+ @SmallTest
+ public void testName() {
+ assertEquals("sample::NamedObject", NamedObject.MANAGER.getName());
+ }
+
+ @SmallTest
+ public void testProxyAndStub() {
+ MockNamedObjectImpl impl = new MockNamedObjectImpl();
+ NamedObject.Proxy proxy =
+ NamedObject.MANAGER.buildProxy(null, NamedObject.MANAGER.buildStub(null, impl));
+
+ checkProxy(proxy, impl);
+ }
+
+ @SmallTest
+ public void testProxyAndStubOverPipe() {
+ MockNamedObjectImpl impl = new MockNamedObjectImpl();
+ NamedObject.Proxy proxy = newProxyOverPipe(NamedObject.MANAGER, impl);
+
+ checkProxy(proxy, impl);
+ }
+
+ @SmallTest
+ public void testFactoryOverPipe() {
+ Factory.Proxy proxy = newProxyOverPipe(Factory.MANAGER, new MockFactoryImpl());
+ Pair<NamedObject.Proxy, InterfaceRequest<NamedObject>> request =
+ NamedObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
+ mCloseablesToClose.add(request.first);
+ proxy.createNamedObject(request.second);
+
+ checkProxy(request.first, null);
+ }
+
+ @SmallTest
+ public void testInterfaceClosing() {
+ MockFactoryImpl impl = new MockFactoryImpl();
+ MockFactoryClientImpl client = new MockFactoryClientImpl();
+ Factory.Proxy proxy = newProxyOverPipeWithClient(
+ Factory.MANAGER, impl, client);
+
+ assertFalse(impl.isClosed());
+ assertFalse(client.isClosed());
+
+ proxy.close();
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertTrue(impl.isClosed());
+ assertTrue(client.isClosed());
+ }
+
+ @SmallTest
+ public void testClient() {
+ MockFactoryImpl impl = new MockFactoryImpl();
+ MockFactoryClientImpl client = new MockFactoryClientImpl();
+ Factory.Proxy proxy = newProxyOverPipeWithClient(
+ Factory.MANAGER, impl, client);
+ Request request = new Request();
+ request.x = 42;
+ proxy.doStuff(request, null);
+
+ assertFalse(client.wasDidStuffCalled());
+
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertTrue(client.wasDidStuffCalled());
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java
new file mode 100644
index 0000000..8af8be1
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.chromium.mojo.bindings.test.mojom.imported.Point;
+
+/**
+ * Testing internal classes of interfaces.
+ */
+public class MessageHeaderTest extends TestCase {
+
+ /**
+ * Testing that headers are identical after being serialized/deserialized.
+ */
+ @SmallTest
+ public void testSimpleMessageHeader() {
+ final int xValue = 1;
+ final int yValue = 2;
+ final int type = 6;
+ Point p = new Point();
+ p.x = xValue;
+ p.y = yValue;
+ ServiceMessage message = p.serializeWithHeader(null, new MessageHeader(type));
+
+ MessageHeader header = message.getHeader();
+ assertTrue(header.validateHeader(type, 0));
+ assertEquals(type, header.getType());
+ assertEquals(0, header.getFlags());
+
+ Point p2 = Point.deserialize(message.getPayload());
+ assertNotNull(p2);
+ assertEquals(p.x, p2.x);
+ assertEquals(p.y, p2.y);
+ }
+
+ /**
+ * Testing that headers are identical after being serialized/deserialized.
+ */
+ @SmallTest
+ public void testMessageWithRequestIdHeader() {
+ final int xValue = 1;
+ final int yValue = 2;
+ final int type = 6;
+ final long requestId = 0x1deadbeafL;
+ Point p = new Point();
+ p.x = xValue;
+ p.y = yValue;
+ ServiceMessage message = p.serializeWithHeader(null,
+ new MessageHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0));
+ message.setRequestId(requestId);
+
+ MessageHeader header = message.getHeader();
+ assertTrue(header.validateHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG));
+ assertEquals(type, header.getType());
+ assertEquals(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, header.getFlags());
+ assertEquals(requestId, header.getRequestId());
+
+ Point p2 = Point.deserialize(message.getPayload());
+ assertNotNull(p2);
+ assertEquals(p.x, p2.x);
+ assertEquals(p.y, p2.y);
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java
new file mode 100644
index 0000000..54f0155
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.impl.CoreImpl;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Testing {@link Connector#readAndDispatchMessage}.
+ */
+public class ReadAndDispatchMessageTest extends MojoTestCase {
+
+ private static final int DATA_SIZE = 1024;
+
+ private ByteBuffer mData;
+ private Pair<MessagePipeHandle, MessagePipeHandle> mHandles;
+ private List<Handle> mHandlesToSend = new ArrayList<Handle>();
+ private List<Handle> mHandlesToClose = new ArrayList<Handle>();
+ private RecordingMessageReceiver mMessageReceiver;
+
+ /**
+ * @see org.chromium.mojo.MojoTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Core core = CoreImpl.getInstance();
+ mData = BindingsTestUtils.newRandomMessage(DATA_SIZE).getData();
+ mMessageReceiver = new RecordingMessageReceiver();
+ mHandles = core.createMessagePipe(new MessagePipeHandle.CreateOptions());
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> datapipe = core.createDataPipe(null);
+ mHandlesToSend.addAll(Arrays.asList(datapipe.first, datapipe.second));
+ mHandlesToClose.addAll(Arrays.asList(mHandles.first, mHandles.second));
+ mHandlesToClose.addAll(mHandlesToSend);
+ }
+
+ /**
+ * @see org.chromium.mojo.MojoTestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ for (Handle handle : mHandlesToClose) {
+ handle.close();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)}
+ */
+ @SmallTest
+ public void testReadAndDispatchMessage() {
+ mHandles.first.writeMessage(mData, mHandlesToSend, MessagePipeHandle.WriteFlags.NONE);
+ assertEquals(MojoResult.OK,
+ Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver));
+ assertEquals(1, mMessageReceiver.messages.size());
+ Message message = mMessageReceiver.messages.get(0);
+ mHandlesToClose.addAll(message.getHandles());
+ assertEquals(mData, message.getData());
+ assertEquals(2, message.getHandles().size());
+ for (Handle handle : message.getHandles()) {
+ assertTrue(handle.isValid());
+ }
+ }
+
+ /**
+ * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)}
+ * with no message available.
+ */
+ @SmallTest
+ public void testReadAndDispatchMessageOnEmptyHandle() {
+ assertEquals(MojoResult.SHOULD_WAIT,
+ Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver));
+ assertEquals(0, mMessageReceiver.messages.size());
+ }
+
+ /**
+ * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)}
+ * on closed handle.
+ */
+ @SmallTest
+ public void testReadAndDispatchMessageOnClosedHandle() {
+ mHandles.first.close();
+ try {
+ Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver);
+ fail("MojoException should have been thrown");
+ } catch (MojoException expected) {
+ assertEquals(MojoResult.FAILED_PRECONDITION, expected.getMojoResult());
+ }
+ assertEquals(0, mMessageReceiver.messages.size());
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java
new file mode 100644
index 0000000..e90bcd2
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler;
+import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.impl.CoreImpl;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * Testing {@link Router}
+ */
+public class RouterTest extends MojoTestCase {
+
+ private static final long RUN_LOOP_TIMEOUT_MS = 25;
+
+ private MessagePipeHandle mHandle;
+ private Router mRouter;
+ private RecordingMessageReceiverWithResponder mReceiver;
+ private CapturingErrorHandler mErrorHandler;
+
+ /**
+ * @see MojoTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ mHandle = handles.first;
+ mRouter = new RouterImpl(handles.second);
+ mReceiver = new RecordingMessageReceiverWithResponder();
+ mRouter.setIncomingMessageReceiver(mReceiver);
+ mErrorHandler = new CapturingErrorHandler();
+ mRouter.setErrorHandler(mErrorHandler);
+ mRouter.start();
+ }
+
+ /**
+ * Testing sending a message via the router that expected a response.
+ */
+ @SmallTest
+ public void testSendingToRouterWithResponse() {
+ final int requestMessageType = 0xdead;
+ final int responseMessageType = 0xbeaf;
+
+ // Sending a message expecting a response.
+ MessageHeader header = new MessageHeader(requestMessageType,
+ MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0);
+ Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize());
+ header.encode(encoder);
+ mRouter.acceptWithResponder(encoder.getMessage(), mReceiver);
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(header.getSize());
+ MessagePipeHandle.ReadMessageResult result = mHandle.readMessage(receiveBuffer, 0,
+ MessagePipeHandle.ReadFlags.NONE);
+
+ assertEquals(MojoResult.OK, result.getMojoResult());
+ MessageHeader receivedHeader = new Message(
+ receiveBuffer, new ArrayList<Handle>()).asServiceMessage().getHeader();
+
+ assertEquals(header.getType(), receivedHeader.getType());
+ assertEquals(header.getFlags(), receivedHeader.getFlags());
+ assertTrue(receivedHeader.getRequestId() != 0);
+
+ // Sending the response.
+ MessageHeader responseHeader = new MessageHeader(responseMessageType,
+ MessageHeader.MESSAGE_IS_RESPONSE_FLAG, receivedHeader.getRequestId());
+ encoder = new Encoder(CoreImpl.getInstance(), header.getSize());
+ responseHeader.encode(encoder);
+ Message responseMessage = encoder.getMessage();
+ mHandle.writeMessage(responseMessage.getData(), new ArrayList<Handle>(),
+ MessagePipeHandle.WriteFlags.NONE);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertEquals(1, mReceiver.messages.size());
+ ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage();
+ assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+ receivedResponseMessage.getHeader().getFlags());
+ assertEquals(responseMessage.getData(), receivedResponseMessage.getData());
+ }
+
+ /**
+ * Testing receiving a message via the router that expected a response.
+ */
+ @SmallTest
+ public void testReceivingViaRouterWithResponse() {
+ final int requestMessageType = 0xdead;
+ final int responseMessageType = 0xbeef;
+ final int requestId = 0xdeadbeaf;
+
+ // Sending a message expecting a response.
+ MessageHeader header = new MessageHeader(requestMessageType,
+ MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId);
+ Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize());
+ header.encode(encoder);
+ Message headerMessage = encoder.getMessage();
+ mHandle.writeMessage(headerMessage.getData(), new ArrayList<Handle>(),
+ MessagePipeHandle.WriteFlags.NONE);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+
+ assertEquals(1, mReceiver.messagesWithReceivers.size());
+ Pair<Message, MessageReceiver> receivedMessage =
+ mReceiver.messagesWithReceivers.get(0);
+ assertEquals(headerMessage.getData(), receivedMessage.first.getData());
+
+ // Sending the response.
+ MessageHeader responseHeader = new MessageHeader(responseMessageType,
+ MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId);
+ encoder = new Encoder(CoreImpl.getInstance(), header.getSize());
+ responseHeader.encode(encoder);
+ Message message = encoder.getMessage();
+ receivedMessage.second.accept(message);
+ ByteBuffer receivedResponseMessage = ByteBuffer.allocateDirect(responseHeader.getSize());
+ MessagePipeHandle.ReadMessageResult result = mHandle.readMessage(receivedResponseMessage, 0,
+ MessagePipeHandle.ReadFlags.NONE);
+
+ assertEquals(MojoResult.OK, result.getMojoResult());
+ assertEquals(message.getData(), receivedResponseMessage);
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java
new file mode 100644
index 0000000..a7e213c
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.chromium.mojo.HandleMock;
+import org.chromium.mojo.bindings.test.mojom.mojo.Struct1;
+import org.chromium.mojo.bindings.test.mojom.mojo.Struct2;
+import org.chromium.mojo.bindings.test.mojom.mojo.Struct3;
+import org.chromium.mojo.bindings.test.mojom.mojo.Struct4;
+import org.chromium.mojo.bindings.test.mojom.mojo.Struct5;
+import org.chromium.mojo.bindings.test.mojom.mojo.Struct6;
+import org.chromium.mojo.bindings.test.mojom.mojo.StructOfNullables;
+
+/**
+ * Tests for the serialization logic of the generated structs, using structs defined in
+ * mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom .
+ */
+public class SerializationTest extends TestCase {
+
+ private static void assertThrowsSerializationException(Struct struct) {
+ try {
+ struct.serialize(null);
+ fail("Serialization of invalid struct should have thrown an exception.");
+ } catch (SerializationException ex) {
+ // Expected.
+ }
+ }
+
+ /**
+ * Verifies that serializing a struct with an invalid handle of a non-nullable type throws an
+ * exception.
+ */
+ @SmallTest
+ public void testHandle() {
+ Struct2 struct = new Struct2();
+ assertFalse(struct.hdl.isValid());
+ assertThrowsSerializationException(struct);
+
+ // Make the struct valid and verify that it serializes without an exception.
+ struct.hdl = new HandleMock();
+ struct.serialize(null);
+ }
+
+ /**
+ * Verifies that serializing a struct with a null struct pointer throws an exception.
+ */
+ @SmallTest
+ public void testStructPointer() {
+ Struct3 struct = new Struct3();
+ assertNull(struct.struct1);
+ assertThrowsSerializationException(struct);
+
+ // Make the struct valid and verify that it serializes without an exception.
+ struct.struct1 = new Struct1();
+ struct.serialize(null);
+ }
+
+ /**
+ * Verifies that serializing a struct with an array of structs throws an exception when the
+ * struct is invalid.
+ */
+ @SmallTest
+ public void testStructArray() {
+ Struct4 struct = new Struct4();
+ assertNull(struct.data);
+ assertThrowsSerializationException(struct);
+
+ // Create the (1-element) array but have the element null.
+ struct.data = new Struct1[1];
+ assertThrowsSerializationException(struct);
+
+ // Create the array element, struct should serialize now.
+ struct.data[0] = new Struct1();
+ struct.serialize(null);
+ }
+
+ /**
+ * Verifies that serializing a struct with a fixed-size array of incorrect length throws an
+ * exception.
+ */
+ @SmallTest
+ public void testFixedSizeArray() {
+ Struct5 struct = new Struct5();
+ assertNull(struct.pair);
+ assertThrowsSerializationException(struct);
+
+ // Create the (1-element) array, 2-element array is required.
+ struct.pair = new Struct1[1];
+ struct.pair[0] = new Struct1();
+ assertThrowsSerializationException(struct);
+
+ // Create the array of a correct size, struct should serialize now.
+ struct.pair = new Struct1[2];
+ struct.pair[0] = new Struct1();
+ struct.pair[1] = new Struct1();
+ struct.serialize(null);
+ }
+
+ /**
+ * Verifies that serializing a struct with a null string throws an exception.
+ */
+ @SmallTest
+ public void testString() {
+ Struct6 struct = new Struct6();
+ assertNull(struct.str);
+ assertThrowsSerializationException(struct);
+
+ // Make the struct valid and verify that it serializes without an exception.
+ struct.str = "";
+ struct.serialize(null);
+ }
+
+ /**
+ * Verifies that a struct with an invalid nullable handle, null nullable struct pointer and null
+ * nullable string serializes without an exception.
+ */
+ @SmallTest
+ public void testNullableFields() {
+ StructOfNullables struct = new StructOfNullables();
+ assertFalse(struct.hdl.isValid());
+ assertNull(struct.struct1);
+ assertNull(struct.str);
+ struct.serialize(null);
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java
new file mode 100644
index 0000000..d0d63bb
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java
@@ -0,0 +1,195 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.mojo.HandleMock;
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.bindings.test.mojom.mojo.ConformanceTestInterface;
+import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface1;
+import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface2TestHelper;
+import org.chromium.mojo.system.Handle;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * Testing validation upon deserialization using the interfaces defined in the
+ * mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom file.
+ * <p>
+ * One needs to pass '--test_data=bindings:{path to mojo/public/interfaces/bindings/tests/data}' to
+ * the test_runner script for this test to find the validation data it needs.
+ */
+public class ValidationTest extends MojoTestCase {
+
+ /**
+ * The path where validation test data is.
+ */
+ private static final File VALIDATION_TEST_DATA_PATH =
+ new File(UrlUtils.getTestFilePath("bindings/validation"));
+
+ /**
+ * The data needed for a validation test.
+ */
+ private static class TestData {
+ public File dataFile;
+ public ValidationTestUtil.Data inputData;
+ public String expectedResult;
+ }
+
+ private static class DataFileFilter implements FileFilter {
+ private final String mPrefix;
+
+ public DataFileFilter(String prefix) {
+ this.mPrefix = prefix;
+ }
+
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isFile() && pathname.getName().startsWith(mPrefix)
+ && pathname.getName().endsWith(".data");
+ }
+ }
+
+ private static String getStringContent(File f) throws FileNotFoundException {
+ Scanner scanner = new Scanner(f).useDelimiter("\\Z");
+ if (scanner.hasNext()) {
+ return scanner.next();
+ }
+ return "";
+ }
+
+ private static List<TestData> getTestData(String prefix)
+ throws FileNotFoundException {
+ List<TestData> results = new ArrayList<TestData>();
+
+ // Do not fail if the test data is not present.
+ if (!VALIDATION_TEST_DATA_PATH.isDirectory()) {
+ Log.w("ValidationTest", "No test found.");
+ return results;
+ }
+
+ for (File dataFile : VALIDATION_TEST_DATA_PATH.listFiles(new DataFileFilter(prefix))) {
+ File resultFile = new File(dataFile.getParent(),
+ dataFile.getName().replaceFirst("\\.data$", ".expected"));
+ TestData testData = new TestData();
+ testData.dataFile = dataFile;
+ testData.inputData = ValidationTestUtil.parseData(getStringContent(dataFile));
+ testData.expectedResult = getStringContent(resultFile);
+ results.add(testData);
+ }
+ return results;
+ }
+
+ /**
+ * Runs all the test with the given prefix on the given {@link MessageReceiver}.
+ */
+ private static void runTest(String prefix, MessageReceiver messageReceiver)
+ throws FileNotFoundException {
+ List<TestData> testData = getTestData(prefix);
+ for (TestData test : testData) {
+ assertNull(test.inputData.getErrorMessage());
+ List<Handle> handles = new ArrayList<Handle>();
+ for (int i = 0; i < test.inputData.getHandlesCount(); ++i) {
+ handles.add(new HandleMock());
+ }
+ Message message = new Message(test.inputData.getData(), handles);
+ boolean passed = messageReceiver.accept(message);
+ if (passed && !test.expectedResult.equals("PASS")) {
+ fail("Input: " + test.dataFile.getName() +
+ ": The message should have been refused. Expected error: " +
+ test.expectedResult);
+ }
+ if (!passed && test.expectedResult.equals("PASS")) {
+ fail("Input: " + test.dataFile.getName() +
+ ": The message should have been accepted.");
+ }
+ }
+ }
+
+ private static class RoutingMessageReceiver implements MessageReceiver {
+ private final MessageReceiver mRequest;
+ private final MessageReceiver mResponse;
+
+ private RoutingMessageReceiver(MessageReceiver request, MessageReceiver response) {
+ this.mRequest = request;
+ this.mResponse = response;
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ try {
+ MessageHeader header = message.asServiceMessage().getHeader();
+ if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+ return mResponse.accept(message);
+ } else {
+ return mRequest.accept(message);
+ }
+ } catch (DeserializationException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @see MessageReceiver#close()
+ */
+ @Override
+ public void close() {
+ }
+
+ }
+
+ /**
+ * A trivial message receiver that refuses all messages it receives.
+ */
+ private static class SinkMessageReceiver implements MessageReceiverWithResponder {
+
+ @Override
+ public boolean accept(Message message) {
+ return false;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public boolean acceptWithResponder(Message message, MessageReceiver responder) {
+ return false;
+ }
+ }
+
+ /**
+ * Testing the conformance suite.
+ */
+ @SmallTest
+ public void testConformance() throws FileNotFoundException {
+ runTest("conformance_", ConformanceTestInterface.MANAGER.buildStub(null,
+ ConformanceTestInterface.MANAGER.buildProxy(null, new SinkMessageReceiver())));
+ }
+
+ /**
+ * Testing the integration suite.
+ */
+ @SmallTest
+ public void testIntegration() throws FileNotFoundException {
+ runTest("integration_",
+ new RoutingMessageReceiver(IntegrationTestInterface1.MANAGER.buildStub(null,
+ IntegrationTestInterface1.MANAGER.buildProxy(null,
+ new SinkMessageReceiver())),
+ IntegrationTestInterface2TestHelper.
+ newIntegrationTestInterface2MethodCallback()));
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java
new file mode 100644
index 0000000..7d78c19
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Utility class for testing message validation. The file format used to describe a message is
+ * described in The format is described in
+ * mojo/public/cpp/bindings/tests/validation_test_input_parser.h
+ */
+@JNINamespace("mojo::android")
+public class ValidationTestUtil {
+
+ /**
+ * Content of a '.data' file.
+ */
+ public static class Data {
+ private final ByteBuffer mData;
+ private final int mHandlesCount;
+ private final String mErrorMessage;
+
+ public ByteBuffer getData() {
+ return mData;
+ }
+
+ public int getHandlesCount() {
+ return mHandlesCount;
+ }
+
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ private Data(ByteBuffer data, int handlesCount, String errorMessage) {
+ this.mData = data;
+ this.mHandlesCount = handlesCount;
+ this.mErrorMessage = errorMessage;
+ }
+ }
+
+ /**
+ * Parse a '.data' file.
+ */
+ public static Data parseData(String dataAsString) {
+ return nativeParseData(dataAsString);
+ }
+
+ private static native Data nativeParseData(String dataAsString);
+
+ @CalledByNative
+ private static Data buildData(ByteBuffer data, int handlesCount, String errorMessage) {
+ ByteBuffer copiedData = null;
+ if (data != null) {
+ copiedData = ByteBuffer.allocateDirect(data.limit());
+ copiedData.order(ByteOrder.nativeOrder());
+ copiedData.put(data);
+ copiedData.flip();
+ }
+ return new Data(copiedData, handlesCount, errorMessage);
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java
new file mode 100644
index 0000000..b0c5a54
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Testing {@link ValidationTestUtil}.
+ */
+public class ValidationTestUtilTest extends MojoTestCase {
+
+ /**
+ * Check that the input parser is correct on a given input.
+ */
+ public static void checkInputParser(
+ String input, boolean isInputValid, ByteBuffer expectedData, int expectedHandlesCount) {
+ ValidationTestUtil.Data data = ValidationTestUtil.parseData(input);
+ if (isInputValid) {
+ assertNull(data.getErrorMessage());
+ assertEquals(expectedData, data.getData());
+ assertEquals(expectedHandlesCount, data.getHandlesCount());
+ } else {
+ assertNotNull(data.getErrorMessage());
+ assertNull(data.getData());
+ }
+ }
+
+ /**
+ * Testing {@link ValidationTestUtil#parseData(String)}.
+ */
+ @SmallTest
+ public void testCorrectMessageParsing() {
+ {
+ // Test empty input.
+ String input = "";
+ ByteBuffer expected = ByteBuffer.allocateDirect(0);
+ expected.order(ByteOrder.nativeOrder());
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ // Test input that only consists of comments and whitespaces.
+ String input = " \t // hello world \n\r \t// the answer is 42 ";
+ ByteBuffer expected = ByteBuffer.allocateDirect(0);
+ expected.order(ByteOrder.nativeOrder());
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ String input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" +
+ "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff";
+ ByteBuffer expected = ByteBuffer.allocateDirect(17);
+ expected.order(ByteOrder.nativeOrder());
+ expected.put((byte) 0x10);
+ expected.putShort((short) 65535);
+ expected.putInt(65536);
+ expected.putLong(-1);
+ expected.put((byte) 0);
+ expected.put((byte) 0xff);
+ expected.flip();
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ String input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40";
+ ByteBuffer expected = ByteBuffer.allocateDirect(15);
+ expected.order(ByteOrder.nativeOrder());
+ expected.putLong(-0x800);
+ expected.put((byte) -128);
+ expected.putShort((short) 0);
+ expected.putInt(-40);
+ expected.flip();
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ String input = "[b]00001011 [b]10000000 // hello world\r [b]00000000";
+ ByteBuffer expected = ByteBuffer.allocateDirect(3);
+ expected.order(ByteOrder.nativeOrder());
+ expected.put((byte) 11);
+ expected.put((byte) 128);
+ expected.put((byte) 0);
+ expected.flip();
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ String input = "[f]+.3e9 [d]-10.03";
+ ByteBuffer expected = ByteBuffer.allocateDirect(12);
+ expected.order(ByteOrder.nativeOrder());
+ expected.putFloat(+.3e9f);
+ expected.putDouble(-10.03);
+ expected.flip();
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ String input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar";
+ ByteBuffer expected = ByteBuffer.allocateDirect(14);
+ expected.order(ByteOrder.nativeOrder());
+ expected.putInt(14);
+ expected.put((byte) 0);
+ expected.putLong(9);
+ expected.put((byte) 0);
+ expected.flip();
+
+ checkInputParser(input, true, expected, 0);
+ }
+ {
+ String input = "// This message has handles! \n[handles]50 [u8]2";
+ ByteBuffer expected = ByteBuffer.allocateDirect(8);
+ expected.order(ByteOrder.nativeOrder());
+ expected.putLong(2);
+ expected.flip();
+
+ checkInputParser(input, true, expected, 50);
+ }
+
+ // Test some failure cases.
+ {
+ String error_inputs[] = {
+ "/ hello world",
+ "[u1]x",
+ "[u2]-1000",
+ "[u1]0x100",
+ "[s2]-0x8001",
+ "[b]1",
+ "[b]1111111k",
+ "[dist4]unmatched",
+ "[anchr]hello [dist8]hello",
+ "[dist4]a [dist4]a [anchr]a",
+ "[dist4]a [anchr]a [dist4]a [anchr]a",
+ "0 [handles]50"
+ };
+
+ for (String input : error_inputs) {
+ ByteBuffer expected = ByteBuffer.allocateDirect(0);
+ expected.order(ByteOrder.nativeOrder());
+ checkInputParser(input, false, expected, 0);
+ }
+ }
+
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterface2TestHelper.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterface2TestHelper.java
new file mode 100644
index 0000000..2a2b6b8
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterface2TestHelper.java
@@ -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.
+
+package org.chromium.mojo.bindings.test.mojom.mojo;
+
+import org.chromium.mojo.bindings.MessageReceiver;
+import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface2.Method0Response;
+import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface2_Internal.IntegrationTestInterface2Method0ResponseParamsForwardToCallback;
+
+/**
+ * Helper class to access {@link IntegrationTestInterface2_Internal} package protected method for
+ * tests.
+ */
+public class IntegrationTestInterface2TestHelper {
+
+ private static final class SinkMethod0Response implements Method0Response {
+ @Override
+ public void call(byte[] arg1) {
+ }
+ }
+
+ /**
+ * Creates a new {@link MessageReceiver} to use for the callback of
+ * |IntegrationTestInterface2#method0(Method0Response)|.
+ */
+ public static MessageReceiver newIntegrationTestInterface2MethodCallback() {
+ return new IntegrationTestInterface2Method0ResponseParamsForwardToCallback(
+ new SinkMethod0Response());
+ }
+}
diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
new file mode 100644
index 0000000..355156e
--- /dev/null
+++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
@@ -0,0 +1,838 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.system.impl;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.mojo.MojoTestCase;
+import org.chromium.mojo.system.AsyncWaiter;
+import org.chromium.mojo.system.AsyncWaiter.Callback;
+import org.chromium.mojo.system.AsyncWaiter.Cancellable;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Core.WaitManyResult;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.InvalidHandle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.SharedBufferHandle;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Testing the core API.
+ */
+public class CoreImplTest extends MojoTestCase {
+
+ private static final long RUN_LOOP_TIMEOUT_MS = 5;
+
+ private static final ScheduledExecutorService WORKER =
+ Executors.newSingleThreadScheduledExecutor();
+
+ private List<Handle> mHandlesToClose = new ArrayList<Handle>();
+
+ /**
+ * @see MojoTestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ MojoException toThrow = null;
+ for (Handle handle : mHandlesToClose) {
+ try {
+ handle.close();
+ } catch (MojoException e) {
+ if (toThrow == null) {
+ toThrow = e;
+ }
+ }
+ }
+ if (toThrow != null) {
+ throw toThrow;
+ }
+ super.tearDown();
+ }
+
+ private void addHandleToClose(Handle handle) {
+ mHandlesToClose.add(handle);
+ }
+
+ private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> handles) {
+ mHandlesToClose.add(handles.first);
+ mHandlesToClose.add(handles.second);
+ }
+
+ /**
+ * Runnable that will close the given handle.
+ */
+ private static class CloseHandle implements Runnable {
+ private Handle mHandle;
+
+ CloseHandle(Handle handle) {
+ mHandle = handle;
+ }
+
+ @Override
+ public void run() {
+ mHandle.close();
+ }
+ }
+
+ private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) {
+ Random random = new Random();
+
+ // Writing a random 8 bytes message.
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
+ buffer.put(bytes);
+ in.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE);
+
+ // Try to read into a small buffer.
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length / 2);
+ MessagePipeHandle.ReadMessageResult result = out.readMessage(
+ receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult());
+ assertEquals(bytes.length, result.getMessageSize());
+ assertEquals(0, result.getHandlesCount());
+
+ // Read into a correct buffer.
+ receiveBuffer = ByteBuffer.allocateDirect(bytes.length);
+ result = out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(MojoResult.OK, result.getMojoResult());
+ assertEquals(bytes.length, result.getMessageSize());
+ assertEquals(0, result.getHandlesCount());
+ assertEquals(0, receiveBuffer.position());
+ assertEquals(result.getMessageSize(), receiveBuffer.limit());
+ byte[] receivedBytes = new byte[result.getMessageSize()];
+ receiveBuffer.get(receivedBytes);
+ assertTrue(Arrays.equals(bytes, receivedBytes));
+
+ }
+
+ private static void checkSendingData(DataPipe.ProducerHandle in, DataPipe.ConsumerHandle out) {
+ Random random = new Random();
+
+ // Writing a random 8 bytes message.
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
+ buffer.put(bytes);
+ int result = in.writeData(buffer, DataPipe.WriteFlags.NONE);
+ assertEquals(bytes.length, result);
+
+ // Query number of bytes available.
+ result = out.readData(null, DataPipe.ReadFlags.none().query(true));
+ assertEquals(bytes.length, result);
+
+ // Read into a buffer.
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length);
+ result = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE);
+ assertEquals(bytes.length, result);
+ assertEquals(0, receiveBuffer.position());
+ assertEquals(bytes.length, receiveBuffer.limit());
+ byte[] receivedBytes = new byte[bytes.length];
+ receiveBuffer.get(receivedBytes);
+ assertTrue(Arrays.equals(bytes, receivedBytes));
+ }
+
+ private static void checkSharing(SharedBufferHandle in, SharedBufferHandle out) {
+ Random random = new Random();
+
+ ByteBuffer buffer1 = in.map(0, 8, SharedBufferHandle.MapFlags.NONE);
+ assertEquals(8, buffer1.capacity());
+ ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE);
+ assertEquals(8, buffer2.capacity());
+
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ buffer1.put(bytes);
+
+ byte[] receivedBytes = new byte[bytes.length];
+ buffer2.get(receivedBytes);
+
+ assertTrue(Arrays.equals(bytes, receivedBytes));
+
+ in.unmap(buffer1);
+ out.unmap(buffer2);
+ }
+
+ /**
+ * Testing {@link Core#waitMany(List, long)}.
+ */
+ @SmallTest
+ public void testWaitMany() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ List<Pair<Handle, Core.HandleSignals>> handlesToWaitOn = new ArrayList<
+ Pair<Handle, Core.HandleSignals>>();
+ handlesToWaitOn.add(
+ new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE));
+ handlesToWaitOn.add(
+ new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE));
+ WaitManyResult result = core.waitMany(handlesToWaitOn, 0);
+ assertEquals(MojoResult.OK, result.getMojoResult());
+ assertEquals(1, result.getHandleIndex());
+
+ handlesToWaitOn.clear();
+ handlesToWaitOn.add(
+ new Pair<Handle, Core.HandleSignals>(handles.first, Core.HandleSignals.WRITABLE));
+ handlesToWaitOn.add(
+ new Pair<Handle, Core.HandleSignals>(handles.second, Core.HandleSignals.READABLE));
+ result = core.waitMany(handlesToWaitOn, 0);
+ assertEquals(MojoResult.OK, result.getMojoResult());
+ assertEquals(0, result.getHandleIndex());
+ }
+
+ /**
+ * Testing that Core can be retrieved from a handle.
+ */
+ @SmallTest
+ public void testGetCore() {
+ Core core = CoreImpl.getInstance();
+
+ Pair<? extends Handle, ? extends Handle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ assertEquals(core, handles.first.getCore());
+ assertEquals(core, handles.second.getCore());
+
+ handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+ assertEquals(core, handles.first.getCore());
+ assertEquals(core, handles.second.getCore());
+
+ SharedBufferHandle handle = core.createSharedBuffer(null, 100);
+ SharedBufferHandle handle2 = handle.duplicate(null);
+ addHandleToClose(handle);
+ addHandleToClose(handle2);
+ assertEquals(core, handle.getCore());
+ assertEquals(core, handle2.getCore());
+ }
+
+ private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(options);
+ handles.first.close();
+ handles.second.close();
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle} creation.
+ */
+ @SmallTest
+ public void testMessagePipeCreation() {
+ // Test creation with null options.
+ createAndCloseMessagePipe(null);
+ // Test creation with default options.
+ createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions());
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @SmallTest
+ public void testMessagePipeEmpty() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ // Testing wait.
+ assertEquals(MojoResult.OK,
+ handles.first.wait(Core.HandleSignals.none().setReadable(true).setWritable(true),
+ 0));
+ assertEquals(MojoResult.OK, handles.first.wait(Core.HandleSignals.WRITABLE, 0));
+ assertEquals(MojoResult.DEADLINE_EXCEEDED,
+ handles.first.wait(Core.HandleSignals.READABLE, 0));
+
+ // Testing read on an empty pipe.
+ MessagePipeHandle.ReadMessageResult result = handles.first.readMessage(null, 0,
+ MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(MojoResult.SHOULD_WAIT, result.getMojoResult());
+
+ // Closing a pipe while waiting.
+ WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS);
+ assertEquals(MojoResult.CANCELLED,
+ handles.first.wait(Core.HandleSignals.READABLE, 1000000L));
+
+ handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ // Closing the other pipe while waiting.
+ WORKER.schedule(new CloseHandle(handles.first), 10, TimeUnit.MILLISECONDS);
+ assertEquals(MojoResult.FAILED_PRECONDITION,
+ handles.second.wait(Core.HandleSignals.READABLE, 1000000L));
+
+ // Waiting on a closed pipe.
+ assertEquals(MojoResult.FAILED_PRECONDITION,
+ handles.second.wait(Core.HandleSignals.READABLE, 0));
+ assertEquals(MojoResult.FAILED_PRECONDITION,
+ handles.second.wait(Core.HandleSignals.WRITABLE, 0));
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @SmallTest
+ public void testMessagePipeSend() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ checkSendingMessage(handles.first, handles.second);
+ checkSendingMessage(handles.second, handles.first);
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @SmallTest
+ public void testMessagePipeReceiveOnSmallBuffer() {
+ Random random = new Random();
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ // Writing a random 8 bytes message.
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
+ buffer.put(bytes);
+ handles.first.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE);
+
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(1);
+ MessagePipeHandle.ReadMessageResult result = handles.second
+ .readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult());
+ assertEquals(bytes.length, result.getMessageSize());
+ assertEquals(0, result.getHandlesCount());
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @SmallTest
+ public void testMessagePipeSendHandles() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ Pair<MessagePipeHandle, MessagePipeHandle> handlesToShare = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ addHandlePairToClose(handlesToShare);
+
+ handles.first.writeMessage(null,
+ Collections.<Handle> singletonList(handlesToShare.second),
+ MessagePipeHandle.WriteFlags.NONE);
+ assertFalse(handlesToShare.second.isValid());
+ MessagePipeHandle.ReadMessageResult readMessageResult =
+ handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(1, readMessageResult.getHandlesCount());
+ MessagePipeHandle newHandle = readMessageResult.getHandles().get(0)
+ .toMessagePipeHandle();
+ addHandleToClose(newHandle);
+ assertTrue(newHandle.isValid());
+ checkSendingMessage(handlesToShare.first, newHandle);
+ checkSendingMessage(newHandle, handlesToShare.first);
+ }
+
+ private static void createAndCloseDataPipe(DataPipe.CreateOptions options) {
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(
+ options);
+ handles.first.close();
+ handles.second.close();
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @SmallTest
+ public void testDataPipeCreation() {
+ // Create datapipe with null options.
+ createAndCloseDataPipe(null);
+ DataPipe.CreateOptions options = new DataPipe.CreateOptions();
+ // Create datapipe with element size set.
+ options.setElementNumBytes(24);
+ createAndCloseDataPipe(options);
+ // Create datapipe with a flag set.
+ options.getFlags().setMayDiscard(true);
+ createAndCloseDataPipe(options);
+ // Create datapipe with capacity set.
+ options.setCapacityNumBytes(1024 * options.getElementNumBytes());
+ createAndCloseDataPipe(options);
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @SmallTest
+ public void testDataPipeSend() {
+ Core core = CoreImpl.getInstance();
+
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+
+ checkSendingData(handles.first, handles.second);
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @SmallTest
+ public void testDataPipeTwoPhaseSend() {
+ Random random = new Random();
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+
+ // Writing a random 8 bytes message.
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ ByteBuffer buffer = handles.first.beginWriteData(bytes.length,
+ DataPipe.WriteFlags.NONE);
+ assertTrue(buffer.capacity() >= bytes.length);
+ buffer.put(bytes);
+ handles.first.endWriteData(bytes.length);
+
+ // Read into a buffer.
+ ByteBuffer receiveBuffer = handles.second.beginReadData(bytes.length,
+ DataPipe.ReadFlags.NONE);
+ assertEquals(0, receiveBuffer.position());
+ assertEquals(bytes.length, receiveBuffer.limit());
+ byte[] receivedBytes = new byte[bytes.length];
+ receiveBuffer.get(receivedBytes);
+ assertTrue(Arrays.equals(bytes, receivedBytes));
+ handles.second.endReadData(bytes.length);
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @SmallTest
+ public void testDataPipeDiscard() {
+ Random random = new Random();
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+
+ // Writing a random 8 bytes message.
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
+ buffer.put(bytes);
+ int result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE);
+ assertEquals(bytes.length, result);
+
+ // Discard bytes.
+ final int nbBytesToDiscard = 4;
+ assertEquals(nbBytesToDiscard,
+ handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE));
+
+ // Read into a buffer.
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard);
+ result = handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE);
+ assertEquals(bytes.length - nbBytesToDiscard, result);
+ assertEquals(0, receiveBuffer.position());
+ assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit());
+ byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard];
+ receiveBuffer.get(receivedBytes);
+ assertTrue(Arrays.equals(Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length),
+ receivedBytes));
+ }
+
+ /**
+ * Testing {@link SharedBufferHandle}.
+ */
+ @SmallTest
+ public void testSharedBufferCreation() {
+ Core core = CoreImpl.getInstance();
+ // Test creation with empty options.
+ core.createSharedBuffer(null, 8).close();
+ // Test creation with default options.
+ core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), 8).close();
+ }
+
+ /**
+ * Testing {@link SharedBufferHandle}.
+ */
+ @SmallTest
+ public void testSharedBufferDuplication() {
+ Core core = CoreImpl.getInstance();
+ SharedBufferHandle handle = core.createSharedBuffer(null, 8);
+ addHandleToClose(handle);
+
+ // Test duplication with empty options.
+ handle.duplicate(null).close();
+ // Test creation with default options.
+ handle.duplicate(new SharedBufferHandle.DuplicateOptions()).close();
+ }
+
+ /**
+ * Testing {@link SharedBufferHandle}.
+ */
+ @SmallTest
+ public void testSharedBufferSending() {
+ Core core = CoreImpl.getInstance();
+ SharedBufferHandle handle = core.createSharedBuffer(null, 8);
+ addHandleToClose(handle);
+ SharedBufferHandle newHandle = handle.duplicate(null);
+ addHandleToClose(newHandle);
+
+ checkSharing(handle, newHandle);
+ checkSharing(newHandle, handle);
+ }
+
+ /**
+ * Testing that invalid handle can be used with this implementation.
+ */
+ @SmallTest
+ public void testInvalidHandle() {
+ Core core = CoreImpl.getInstance();
+ Handle handle = InvalidHandle.INSTANCE;
+
+ // Checking wait.
+ boolean exception = false;
+ try {
+ core.wait(handle, Core.HandleSignals.WRITABLE, 0);
+ } catch (MojoException e) {
+ assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
+ exception = true;
+ }
+ assertTrue(exception);
+
+ // Checking waitMany.
+ exception = false;
+ try {
+ List<Pair<Handle, Core.HandleSignals>> handles = new ArrayList<
+ Pair<Handle, Core.HandleSignals>>();
+ handles.add(Pair.create(handle, Core.HandleSignals.WRITABLE));
+ core.waitMany(handles, 0);
+ } catch (MojoException e) {
+ assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
+ exception = true;
+ }
+ assertTrue(exception);
+
+ // Checking sending an invalid handle.
+ // Until the behavior is changed on the C++ side, handle gracefully 2 different use case:
+ // - Receive a INVALID_ARGUMENT exception
+ // - Receive an invalid handle on the other side.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ try {
+ handles.first.writeMessage(null, Collections.<Handle> singletonList(handle),
+ MessagePipeHandle.WriteFlags.NONE);
+ MessagePipeHandle.ReadMessageResult readMessageResult =
+ handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE);
+ assertEquals(1, readMessageResult.getHandlesCount());
+ assertFalse(readMessageResult.getHandles().get(0).isValid());
+ } catch (MojoException e) {
+ assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult());
+ }
+ }
+
+ private static class AsyncWaiterResult implements Callback {
+ private int mResult = Integer.MIN_VALUE;
+ private MojoException mException = null;
+
+ /**
+ * @see Callback#onResult(int)
+ */
+ @Override
+ public void onResult(int result) {
+ this.mResult = result;
+ }
+
+ /**
+ * @see Callback#onError(MojoException)
+ */
+ @Override
+ public void onError(MojoException exception) {
+ this.mException = exception;
+ }
+
+ /**
+ * @return the result
+ */
+ public int getResult() {
+ return mResult;
+ }
+
+ /**
+ * @return the exception
+ */
+ public MojoException getException() {
+ return mException;
+ }
+
+ }
+
+ /**
+ * Testing core {@link AsyncWaiter} implementation.
+ */
+ @SmallTest
+ public void testAsyncWaiterCorrectResult() {
+ Core core = CoreImpl.getInstance();
+
+ // Checking a correct result.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ core.getDefaultAsyncWaiter().asyncWait(handles.first, Core.HandleSignals.READABLE,
+ Core.DEADLINE_INFINITE, asyncWaiterResult);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ handles.second.writeMessage(ByteBuffer.allocateDirect(1), null,
+ MessagePipeHandle.WriteFlags.NONE);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertNull(asyncWaiterResult.getException());
+ assertEquals(MojoResult.OK, asyncWaiterResult.getResult());
+ }
+
+ /**
+ * Testing core {@link AsyncWaiter} implementation.
+ */
+ @SmallTest
+ public void testAsyncWaiterClosingPeerHandle() {
+ Core core = CoreImpl.getInstance();
+
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ core.getDefaultAsyncWaiter().asyncWait(handles.first, Core.HandleSignals.READABLE,
+ Core.DEADLINE_INFINITE, asyncWaiterResult);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ handles.second.close();
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertNull(asyncWaiterResult.getException());
+ assertEquals(MojoResult.FAILED_PRECONDITION, asyncWaiterResult.getResult());
+ }
+
+ /**
+ * Testing core {@link AsyncWaiter} implementation.
+ */
+ @SmallTest
+ public void testAsyncWaiterClosingWaitingHandle() {
+ Core core = CoreImpl.getInstance();
+
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ Cancellable cancellable = core.getDefaultAsyncWaiter().asyncWait(handles.first,
+ Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE, asyncWaiterResult);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ cancellable.cancel();
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ // TODO(qsr) Re-enable when MojoWaitMany handles it correctly.
+ // assertNull(asyncWaiterResult.getException());
+ // assertEquals(MojoResult.CANCELLED, asyncWaiterResult.getResult());
+ }
+
+ /**
+ * Testing core {@link AsyncWaiter} implementation.
+ */
+ @SmallTest
+ public void testAsyncWaiterWaitingWithTimeout() {
+ Core core = CoreImpl.getInstance();
+
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ core.getDefaultAsyncWaiter().asyncWait(handles.first, Core.HandleSignals.READABLE,
+ RUN_LOOP_TIMEOUT_MS, asyncWaiterResult);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ nativeRunLoop(10 * RUN_LOOP_TIMEOUT_MS);
+ assertNull(asyncWaiterResult.getException());
+ assertEquals(MojoResult.DEADLINE_EXCEEDED, asyncWaiterResult.getResult());
+ }
+
+ /**
+ * Testing core {@link AsyncWaiter} implementation.
+ */
+ @SmallTest
+ public void testAsyncWaiterCancelWaiting() {
+ Core core = CoreImpl.getInstance();
+
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ Cancellable cancellable = core.getDefaultAsyncWaiter().asyncWait(handles.first,
+ Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE, asyncWaiterResult);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ cancellable.cancel();
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ handles.second.writeMessage(ByteBuffer.allocateDirect(1), null,
+ MessagePipeHandle.WriteFlags.NONE);
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+ }
+
+ /**
+ * Testing core {@link AsyncWaiter} implementation.
+ */
+ @SmallTest
+ public void testAsyncWaiterImmediateCancelOnInvalidHandle() {
+ Core core = CoreImpl.getInstance();
+
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final AsyncWaiterResult asyncWaiterResult = new AsyncWaiterResult();
+ handles.first.close();
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+
+ Cancellable cancellable = core.getDefaultAsyncWaiter().asyncWait(handles.first,
+ Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE, asyncWaiterResult);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+ cancellable.cancel();
+
+ nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
+ assertEquals(Integer.MIN_VALUE, asyncWaiterResult.getResult());
+ assertEquals(null, asyncWaiterResult.getException());
+ }
+
+ /**
+ * Testing the pass method on message pipes.
+ */
+ @SmallTest
+ public void testMessagePipeHandlePass() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ assertTrue(handles.first.isValid());
+ MessagePipeHandle handleClone = handles.first.pass();
+
+ addHandleToClose(handleClone);
+
+ assertFalse(handles.first.isValid());
+ assertTrue(handleClone.isValid());
+ checkSendingMessage(handleClone, handles.second);
+ checkSendingMessage(handles.second, handleClone);
+ }
+
+ /**
+ * Testing the pass method on data pipes.
+ */
+ @SmallTest
+ public void testDataPipeHandlePass() {
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+
+ DataPipe.ProducerHandle producerClone = handles.first.pass();
+ DataPipe.ConsumerHandle consumerClone = handles.second.pass();
+
+ addHandleToClose(producerClone);
+ addHandleToClose(consumerClone);
+
+ assertFalse(handles.first.isValid());
+ assertFalse(handles.second.isValid());
+ assertTrue(producerClone.isValid());
+ assertTrue(consumerClone.isValid());
+ checkSendingData(producerClone, consumerClone);
+ }
+
+ /**
+ * Testing the pass method on shared buffers.
+ */
+ @SmallTest
+ public void testSharedBufferPass() {
+ Core core = CoreImpl.getInstance();
+ SharedBufferHandle handle = core.createSharedBuffer(null, 8);
+ addHandleToClose(handle);
+ SharedBufferHandle newHandle = handle.duplicate(null);
+ addHandleToClose(newHandle);
+
+ SharedBufferHandle handleClone = handle.pass();
+ SharedBufferHandle newHandleClone = newHandle.pass();
+
+ addHandleToClose(handleClone);
+ addHandleToClose(newHandleClone);
+
+ assertFalse(handle.isValid());
+ assertTrue(handleClone.isValid());
+ checkSharing(handleClone, newHandleClone);
+ checkSharing(newHandleClone, handleClone);
+ }
+
+ /**
+ * esting handle conversion to native and back.
+ */
+ @SmallTest
+ public void testHandleConversion() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ MessagePipeHandle converted =
+ core.acquireNativeHandle(handles.first.releaseNativeHandle()).toMessagePipeHandle();
+ addHandleToClose(converted);
+
+ assertFalse(handles.first.isValid());
+
+ checkSendingMessage(converted, handles.second);
+ checkSendingMessage(handles.second, converted);
+ }
+}
diff --git a/mojo/android/javatests/validation_test_util.cc b/mojo/android/javatests/validation_test_util.cc
new file mode 100644
index 0000000..2289e02
--- /dev/null
+++ b/mojo/android/javatests/validation_test_util.cc
@@ -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.
+
+#include "mojo/android/javatests/validation_test_util.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/test/test_support_android.h"
+#include "jni/ValidationTestUtil_jni.h"
+#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
+
+namespace mojo {
+namespace android {
+
+bool RegisterValidationTestUtil(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+jobject ParseData(JNIEnv* env, jclass jcaller, jstring data_as_string) {
+ std::string input =
+ base::android::ConvertJavaStringToUTF8(env, data_as_string);
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ std::string error_message;
+ if (!test::ParseValidationTestInput(
+ input, &data, &num_handles, &error_message)) {
+ ScopedJavaLocalRef<jstring> j_error_message =
+ base::android::ConvertUTF8ToJavaString(env, error_message);
+ return Java_ValidationTestUtil_buildData(
+ env, NULL, 0, j_error_message.obj()).Release();
+ }
+ void* data_ptr = &data[0];
+ if (!data_ptr) {
+ DCHECK(!data.size());
+ data_ptr = &data;
+ }
+ jobject byte_buffer =
+ env->NewDirectByteBuffer(data_ptr, data.size());
+ return Java_ValidationTestUtil_buildData(env, byte_buffer, num_handles, NULL)
+ .Release();
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/android/javatests/validation_test_util.h b/mojo/android/javatests/validation_test_util.h
new file mode 100644
index 0000000..f58dc07
--- /dev/null
+++ b/mojo/android/javatests/validation_test_util.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_VALIDATION_TEST_UTIL_H_
+#define MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+
+namespace mojo {
+namespace android {
+
+JNI_EXPORT bool RegisterValidationTestUtil(JNIEnv* env);
+
+} // namespace android
+} // namespace mojo
+
+#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_
diff --git a/mojo/android/system/core_impl.cc b/mojo/android/system/core_impl.cc
new file mode 100644
index 0000000..b862130
--- /dev/null
+++ b/mojo/android/system/core_impl.cc
@@ -0,0 +1,378 @@
+// Copyright 2014 The Chromium 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/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "jni/CoreImpl_jni.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 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 signals.
+ const void* buffer_start = env->GetDirectBufferAddress(buffer);
+ DCHECK(buffer_start);
+ DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
+ 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,
+ jobject options_buffer) {
+ const MojoCreateMessagePipeOptions* options = NULL;
+ if (options_buffer) {
+ const void* buffer_start = env->GetDirectBufferAddress(options_buffer);
+ DCHECK(buffer_start);
+ DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
+ const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
+ DCHECK_EQ(buffer_size, sizeof(MojoCreateMessagePipeOptions));
+ options = static_cast<const MojoCreateMessagePipeOptions*>(buffer_start);
+ DCHECK_EQ(options->struct_size, buffer_size);
+ }
+ MojoHandle handle1;
+ MojoHandle handle2;
+ MojoResult result = MojoCreateMessagePipe(options, &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);
+ DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
+ 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);
+ DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
+ 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 = 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));
+ Environment::GetDefaultAsyncWaiter()->CancelWait(id);
+}
+
+bool RegisterCoreImpl(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/android/system/core_impl.h b/mojo/android/system/core_impl.h
new file mode 100644
index 0000000..c624999
--- /dev/null
+++ b/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/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java
new file mode 100644
index 0000000..1fd8076
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java
@@ -0,0 +1,671 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.system.impl;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.mojo.system.AsyncWaiter;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojo.system.SharedBufferHandle;
+import org.chromium.mojo.system.SharedBufferHandle.DuplicateOptions;
+import org.chromium.mojo.system.SharedBufferHandle.MapFlags;
+import org.chromium.mojo.system.UntypedHandle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of {@link Core}.
+ */
+@JNINamespace("mojo::android")
+public class CoreImpl implements Core, AsyncWaiter {
+
+ /**
+ * Discard flag for the |MojoReadData| operation.
+ */
+ private static final int MOJO_READ_DATA_FLAG_DISCARD = 1 << 1;
+
+ /**
+ * the size of a handle, in bytes.
+ */
+ private static final int HANDLE_SIZE = 4;
+
+ /**
+ * the size of a flag, in bytes.
+ */
+ private static final int FLAG_SIZE = 4;
+
+ /**
+ * The mojo handle for an invalid handle.
+ */
+ static final int INVALID_HANDLE = 0;
+
+ private static class LazyHolder {
+ private static final Core INSTANCE = new CoreImpl();
+ }
+
+ /**
+ * @return the instance.
+ */
+ public static Core getInstance() {
+ return LazyHolder.INSTANCE;
+ }
+
+ private CoreImpl() {
+ }
+
+ /**
+ * @see Core#getTimeTicksNow()
+ */
+ @Override
+ public long getTimeTicksNow() {
+ return nativeGetTimeTicksNow();
+ }
+
+ /**
+ * @see Core#waitMany(List, long)
+ */
+ @Override
+ public WaitManyResult waitMany(List<Pair<Handle, HandleSignals>> handles, long deadline) {
+ // Allocate a direct buffer to allow native code not to reach back to java. Buffer will
+ // contain all mojo handles, followed by all flags values.
+ ByteBuffer buffer = allocateDirectBuffer(handles.size() * 8);
+ int index = 0;
+ for (Pair<Handle, HandleSignals> handle : handles) {
+ buffer.putInt(HANDLE_SIZE * index, getMojoHandle(handle.first));
+ buffer.putInt(HANDLE_SIZE * handles.size() + FLAG_SIZE * index,
+ handle.second.getFlags());
+ index++;
+ }
+ int code = nativeWaitMany(buffer, deadline);
+ WaitManyResult result = new WaitManyResult();
+ // If result is greater than 0, result is the indexed of the available handle. To make sure
+ // it cannot be misinterpreted, set handleIndex to a negative number in case of error.
+ result.setHandleIndex(code);
+ result.setMojoResult(filterMojoResultForWait(code));
+ return result;
+ }
+
+ /**
+ * @see Core#wait(Handle, HandleSignals, long)
+ */
+ @Override
+ public int wait(Handle handle, HandleSignals signals, long deadline) {
+ return filterMojoResultForWait(nativeWait(getMojoHandle(handle),
+ signals.getFlags(), deadline));
+ }
+
+ /**
+ * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions)
+ */
+ @Override
+ public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe(
+ MessagePipeHandle.CreateOptions options) {
+ ByteBuffer optionsBuffer = null;
+ if (options != null) {
+ optionsBuffer = allocateDirectBuffer(8);
+ optionsBuffer.putInt(0, 8);
+ optionsBuffer.putInt(4, options.getFlags().getFlags());
+ }
+ NativeCreationResult result = nativeCreateMessagePipe(optionsBuffer);
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return Pair.<MessagePipeHandle, MessagePipeHandle> create(
+ new MessagePipeHandleImpl(this, result.getMojoHandle1()),
+ new MessagePipeHandleImpl(this, result.getMojoHandle2()));
+ }
+
+ /**
+ * @see Core#createDataPipe(DataPipe.CreateOptions)
+ */
+ @Override
+ public Pair<ProducerHandle, ConsumerHandle> createDataPipe(DataPipe.CreateOptions options) {
+ ByteBuffer optionsBuffer = null;
+ if (options != null) {
+ optionsBuffer = allocateDirectBuffer(16);
+ optionsBuffer.putInt(0, 16);
+ optionsBuffer.putInt(4, options.getFlags().getFlags());
+ optionsBuffer.putInt(8, options.getElementNumBytes());
+ optionsBuffer.putInt(12, options.getCapacityNumBytes());
+ }
+ NativeCreationResult result = nativeCreateDataPipe(optionsBuffer);
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return Pair.<ProducerHandle, ConsumerHandle> create(
+ new DataPipeProducerHandleImpl(this, result.getMojoHandle1()),
+ new DataPipeConsumerHandleImpl(this, result.getMojoHandle2()));
+ }
+
+ /**
+ * @see Core#createSharedBuffer(SharedBufferHandle.CreateOptions, long)
+ */
+ @Override
+ public SharedBufferHandle createSharedBuffer(
+ SharedBufferHandle.CreateOptions options, long numBytes) {
+ ByteBuffer optionsBuffer = null;
+ if (options != null) {
+ optionsBuffer = allocateDirectBuffer(8);
+ optionsBuffer.putInt(0, 8);
+ optionsBuffer.putInt(4, options.getFlags().getFlags());
+ }
+ NativeCreationResult result = nativeCreateSharedBuffer(optionsBuffer, numBytes);
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ assert result.getMojoHandle2() == 0;
+ return new SharedBufferHandleImpl(this, result.getMojoHandle1());
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Core#acquireNativeHandle(int)
+ */
+ @Override
+ public UntypedHandle acquireNativeHandle(int handle) {
+ return new UntypedHandleImpl(this, handle);
+ }
+
+ /**
+ * @see Core#getDefaultAsyncWaiter()
+ */
+ @Override
+ public AsyncWaiter getDefaultAsyncWaiter() {
+ return this;
+ }
+
+ /**
+ * @see AsyncWaiter#asyncWait(Handle, Core.HandleSignals, long, Callback)
+ */
+ @Override
+ public Cancellable asyncWait(Handle handle, HandleSignals signals, long deadline,
+ Callback callback) {
+ return nativeAsyncWait(getMojoHandle(handle), signals.getFlags(), deadline, callback);
+ }
+
+ int closeWithResult(int mojoHandle) {
+ return nativeClose(mojoHandle);
+ }
+
+ void close(int mojoHandle) {
+ int mojoResult = nativeClose(mojoHandle);
+ if (mojoResult != MojoResult.OK) {
+ throw new MojoException(mojoResult);
+ }
+ }
+
+ /**
+ * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags)
+ */
+ void writeMessage(MessagePipeHandleImpl pipeHandle, ByteBuffer bytes,
+ List<? extends Handle> handles, MessagePipeHandle.WriteFlags flags) {
+ ByteBuffer handlesBuffer = null;
+ if (handles != null && !handles.isEmpty()) {
+ handlesBuffer = allocateDirectBuffer(handles.size() * HANDLE_SIZE);
+ for (Handle handle : handles) {
+ handlesBuffer.putInt(getMojoHandle(handle));
+ }
+ handlesBuffer.position(0);
+ }
+ int mojoResult = nativeWriteMessage(pipeHandle.getMojoHandle(), bytes,
+ bytes == null ? 0 : bytes.limit(), handlesBuffer,
+ flags.getFlags());
+ if (mojoResult != MojoResult.OK) {
+ throw new MojoException(mojoResult);
+ }
+ // Success means the handles have been invalidated.
+ if (handles != null) {
+ for (Handle handle : handles) {
+ if (handle.isValid()) {
+ ((HandleBase) handle).invalidateHandle();
+ }
+ }
+ }
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(ByteBuffer, int, MessagePipeHandle.ReadFlags)
+ */
+ MessagePipeHandle.ReadMessageResult readMessage(MessagePipeHandleImpl handle,
+ ByteBuffer bytes, int maxNumberOfHandles,
+ MessagePipeHandle.ReadFlags flags) {
+ ByteBuffer handlesBuffer = null;
+ if (maxNumberOfHandles > 0) {
+ handlesBuffer = allocateDirectBuffer(maxNumberOfHandles * HANDLE_SIZE);
+ }
+ MessagePipeHandle.ReadMessageResult result = nativeReadMessage(
+ handle.getMojoHandle(), bytes, handlesBuffer, flags.getFlags());
+ if (result.getMojoResult() != MojoResult.OK &&
+ result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED &&
+ result.getMojoResult() != MojoResult.SHOULD_WAIT) {
+ throw new MojoException(result.getMojoResult());
+ }
+
+ if (result.getMojoResult() == MojoResult.OK) {
+ if (bytes != null) {
+ bytes.position(0);
+ bytes.limit(result.getMessageSize());
+ }
+
+ List<UntypedHandle> handles = new ArrayList<UntypedHandle>(
+ result.getHandlesCount());
+ for (int i = 0; i < result.getHandlesCount(); ++i) {
+ int mojoHandle = handlesBuffer.getInt(HANDLE_SIZE * i);
+ handles.add(new UntypedHandleImpl(this, mojoHandle));
+ }
+ result.setHandles(handles);
+ }
+ return result;
+ }
+
+ /**
+ * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags)
+ */
+ int discardData(DataPipeConsumerHandleImpl handle, int numBytes,
+ DataPipe.ReadFlags flags) {
+ int result = nativeReadData(handle.getMojoHandle(), null, numBytes,
+ flags.getFlags() | MOJO_READ_DATA_FLAG_DISCARD);
+ if (result < 0) {
+ throw new MojoException(result);
+ }
+ return result;
+ }
+
+ /**
+ * @see ConsumerHandle#readData(ByteBuffer, DataPipe.ReadFlags)
+ */
+ int readData(DataPipeConsumerHandleImpl handle, ByteBuffer elements,
+ DataPipe.ReadFlags flags) {
+ int result = nativeReadData(handle.getMojoHandle(), elements,
+ elements == null ? 0 : elements.capacity(),
+ flags.getFlags());
+ if (result < 0) {
+ throw new MojoException(result);
+ }
+ if (elements != null) {
+ elements.limit(result);
+ }
+ return result;
+ }
+
+ /**
+ * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags)
+ */
+ ByteBuffer beginReadData(DataPipeConsumerHandleImpl handle,
+ int numBytes, DataPipe.ReadFlags flags) {
+ NativeCodeAndBufferResult result = nativeBeginReadData(
+ handle.getMojoHandle(),
+ numBytes,
+ flags.getFlags());
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return result.getBuffer().asReadOnlyBuffer();
+ }
+
+ /**
+ * @see ConsumerHandle#endReadData(int)
+ */
+ void endReadData(DataPipeConsumerHandleImpl handle,
+ int numBytesRead) {
+ int result = nativeEndReadData(handle.getMojoHandle(), numBytesRead);
+ if (result != MojoResult.OK) {
+ throw new MojoException(result);
+ }
+ }
+
+ /**
+ * @see ProducerHandle#writeData(ByteBuffer, DataPipe.WriteFlags)
+ */
+ int writeData(DataPipeProducerHandleImpl handle, ByteBuffer elements,
+ DataPipe.WriteFlags flags) {
+ return nativeWriteData(handle.getMojoHandle(), elements, elements.limit(),
+ flags.getFlags());
+ }
+
+ /**
+ * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags)
+ */
+ ByteBuffer beginWriteData(DataPipeProducerHandleImpl handle,
+ int numBytes, DataPipe.WriteFlags flags) {
+ NativeCodeAndBufferResult result = nativeBeginWriteData(
+ handle.getMojoHandle(),
+ numBytes,
+ flags.getFlags());
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return result.getBuffer();
+ }
+
+ /**
+ * @see ProducerHandle#endWriteData(int)
+ */
+ void endWriteData(DataPipeProducerHandleImpl handle,
+ int numBytesWritten) {
+ int result = nativeEndWriteData(handle.getMojoHandle(), numBytesWritten);
+ if (result != MojoResult.OK) {
+ throw new MojoException(result);
+ }
+ }
+
+ /**
+ * @see SharedBufferHandle#duplicate(DuplicateOptions)
+ */
+ SharedBufferHandle duplicate(SharedBufferHandleImpl handle,
+ DuplicateOptions options) {
+ ByteBuffer optionsBuffer = null;
+ if (options != null) {
+ optionsBuffer = allocateDirectBuffer(8);
+ optionsBuffer.putInt(0, 8);
+ optionsBuffer.putInt(4, options.getFlags().getFlags());
+ }
+ NativeCreationResult result = nativeDuplicate(handle.getMojoHandle(),
+ optionsBuffer);
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ assert result.getMojoHandle2() == 0;
+ return new SharedBufferHandleImpl(this, result.getMojoHandle1());
+ }
+
+ /**
+ * @see SharedBufferHandle#map(long, long, MapFlags)
+ */
+ ByteBuffer map(SharedBufferHandleImpl handle, long offset, long numBytes,
+ MapFlags flags) {
+ NativeCodeAndBufferResult result = nativeMap(handle.getMojoHandle(), offset, numBytes,
+ flags.getFlags());
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return result.getBuffer();
+ }
+
+ /**
+ * @see SharedBufferHandle#unmap(ByteBuffer)
+ */
+ void unmap(ByteBuffer buffer) {
+ int result = nativeUnmap(buffer);
+ if (result != MojoResult.OK) {
+ throw new MojoException(result);
+ }
+ }
+
+ /**
+ * @return the mojo handle associated to the given handle, considering invalid handles.
+ */
+ private int getMojoHandle(Handle handle) {
+ if (handle.isValid()) {
+ return ((HandleBase) handle).getMojoHandle();
+ }
+ return 0;
+ }
+
+ private static boolean isUnrecoverableError(int code) {
+ switch (code) {
+ case MojoResult.OK:
+ case MojoResult.DEADLINE_EXCEEDED:
+ case MojoResult.CANCELLED:
+ case MojoResult.FAILED_PRECONDITION:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ private static int filterMojoResult(int code) {
+ if (code >= 0) {
+ return MojoResult.OK;
+ }
+ return code;
+ }
+
+ private static int filterMojoResultForWait(int code) {
+ int finalCode = filterMojoResult(code);
+ if (isUnrecoverableError(finalCode)) {
+ throw new MojoException(finalCode);
+ }
+ return finalCode;
+ }
+
+ private static ByteBuffer allocateDirectBuffer(int capacity) {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
+ buffer.order(ByteOrder.nativeOrder());
+ return buffer;
+ }
+
+ private static class NativeCodeAndBufferResult {
+ private int mMojoResult;
+ private ByteBuffer mBuffer;
+
+ /**
+ * @return the mojoResult
+ */
+ public int getMojoResult() {
+ return mMojoResult;
+ }
+
+ /**
+ * @param mojoResult the mojoResult to set
+ */
+ public void setMojoResult(int mojoResult) {
+ mMojoResult = mojoResult;
+ }
+
+ /**
+ * @return the buffer
+ */
+ public ByteBuffer getBuffer() {
+ return mBuffer;
+ }
+
+ /**
+ * @param buffer the buffer to set
+ */
+ public void setBuffer(ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ }
+
+ /**
+ * Implementation of {@link org.chromium.mojo.system.AsyncWaiter.Cancellable}.
+ */
+ private class AsyncWaiterCancellableImpl implements AsyncWaiter.Cancellable {
+
+ private final long mId;
+ private final long mDataPtr;
+ private boolean mActive = true;
+
+ private AsyncWaiterCancellableImpl(long id, long dataPtr) {
+ this.mId = id;
+ this.mDataPtr = dataPtr;
+ }
+
+ /**
+ * @see org.chromium.mojo.system.AsyncWaiter.Cancellable#cancel()
+ */
+ @Override
+ public void cancel() {
+ if (mActive) {
+ mActive = false;
+ nativeCancelAsyncWait(mId, mDataPtr);
+ }
+ }
+
+ private boolean isActive() {
+ return mActive;
+ }
+
+ private void deactivate() {
+ mActive = false;
+ }
+ }
+
+ @CalledByNative
+ private AsyncWaiterCancellableImpl newAsyncWaiterCancellableImpl(long id, long dataPtr) {
+ return new AsyncWaiterCancellableImpl(id, dataPtr);
+ }
+
+ @CalledByNative
+ private void onAsyncWaitResult(int mojoResult,
+ AsyncWaiter.Callback callback,
+ AsyncWaiterCancellableImpl cancellable) {
+ if (!cancellable.isActive()) {
+ // If cancellable is not active, the user cancelled the wait.
+ return;
+ }
+ cancellable.deactivate();
+ int finalCode = filterMojoResult(mojoResult);
+ if (isUnrecoverableError(finalCode)) {
+ callback.onError(new MojoException(finalCode));
+ return;
+ }
+ callback.onResult(finalCode);
+ }
+
+ @CalledByNative
+ private static NativeCodeAndBufferResult newNativeCodeAndBufferResult(int mojoResult,
+ ByteBuffer buffer) {
+ NativeCodeAndBufferResult result = new NativeCodeAndBufferResult();
+ result.setMojoResult(mojoResult);
+ result.setBuffer(buffer);
+ return result;
+ }
+
+ @CalledByNative
+ private static MessagePipeHandle.ReadMessageResult newReadMessageResult(int mojoResult,
+ int messageSize,
+ int handlesCount) {
+ MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult();
+ if (mojoResult >= 0) {
+ result.setMojoResult(MojoResult.OK);
+ } else {
+ result.setMojoResult(mojoResult);
+ }
+ result.setMessageSize(messageSize);
+ result.setHandlesCount(handlesCount);
+ return result;
+ }
+
+ private static class NativeCreationResult {
+ private int mMojoResult;
+ private int mMojoHandle1;
+ private int mMojoHandle2;
+
+ /**
+ * @return the mojoResult
+ */
+ public int getMojoResult() {
+ return mMojoResult;
+ }
+
+ /**
+ * @param mojoResult the mojoResult to set
+ */
+ public void setMojoResult(int mojoResult) {
+ mMojoResult = mojoResult;
+ }
+
+ /**
+ * @return the mojoHandle1
+ */
+ public int getMojoHandle1() {
+ return mMojoHandle1;
+ }
+
+ /**
+ * @param mojoHandle1 the mojoHandle1 to set
+ */
+ public void setMojoHandle1(int mojoHandle1) {
+ mMojoHandle1 = mojoHandle1;
+ }
+
+ /**
+ * @return the mojoHandle2
+ */
+ public int getMojoHandle2() {
+ return mMojoHandle2;
+ }
+
+ /**
+ * @param mojoHandle2 the mojoHandle2 to set
+ */
+ public void setMojoHandle2(int mojoHandle2) {
+ mMojoHandle2 = mojoHandle2;
+ }
+ }
+
+ @CalledByNative
+ private static NativeCreationResult newNativeCreationResult(int mojoResult,
+ int mojoHandle1, int mojoHandle2) {
+ NativeCreationResult result = new NativeCreationResult();
+ result.setMojoResult(mojoResult);
+ result.setMojoHandle1(mojoHandle1);
+ result.setMojoHandle2(mojoHandle2);
+ return result;
+ }
+
+ private native long nativeGetTimeTicksNow();
+
+ private native int nativeWaitMany(ByteBuffer buffer, long deadline);
+
+ private native NativeCreationResult nativeCreateMessagePipe(ByteBuffer optionsBuffer);
+
+ private native NativeCreationResult nativeCreateDataPipe(ByteBuffer optionsBuffer);
+
+ private native NativeCreationResult nativeCreateSharedBuffer(ByteBuffer optionsBuffer,
+ long numBytes);
+
+ private native int nativeClose(int mojoHandle);
+
+ private native int nativeWait(int mojoHandle, int signals, long deadline);
+
+ private native int nativeWriteMessage(int mojoHandle, ByteBuffer bytes, int numBytes,
+ ByteBuffer handlesBuffer, int flags);
+
+ private native MessagePipeHandle.ReadMessageResult nativeReadMessage(int mojoHandle,
+ ByteBuffer bytes,
+ ByteBuffer handlesBuffer,
+ int flags);
+
+ private native int nativeReadData(int mojoHandle, ByteBuffer elements, int elementsSize,
+ int flags);
+
+ private native NativeCodeAndBufferResult nativeBeginReadData(int mojoHandle, int numBytes,
+ int flags);
+
+ private native int nativeEndReadData(int mojoHandle, int numBytesRead);
+
+ private native int nativeWriteData(int mojoHandle, ByteBuffer elements, int limit, int flags);
+
+ private native NativeCodeAndBufferResult nativeBeginWriteData(int mojoHandle, int numBytes,
+ int flags);
+
+ private native int nativeEndWriteData(int mojoHandle, int numBytesWritten);
+
+ private native NativeCreationResult nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer);
+
+ private native NativeCodeAndBufferResult nativeMap(int mojoHandle, long offset, long numBytes,
+ int flags);
+
+ private native int nativeUnmap(ByteBuffer buffer);
+
+ private native AsyncWaiterCancellableImpl nativeAsyncWait(int mojoHandle, int signals,
+ long deadline, AsyncWaiter.Callback callback);
+
+ private native void nativeCancelAsyncWait(long mId, long dataPtr);
+}
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java
new file mode 100644
index 0000000..ab6f1ea
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java
@@ -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.
+
+package org.chromium.mojo.system.impl;
+
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ReadFlags;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implementation of {@link ConsumerHandle}.
+ */
+class DataPipeConsumerHandleImpl extends HandleBase implements ConsumerHandle {
+
+ /**
+ * @see HandleBase#HandleBase(CoreImpl, int)
+ */
+ DataPipeConsumerHandleImpl(CoreImpl core, int mojoHandle) {
+ super(core, mojoHandle);
+ }
+
+ /**
+ * @see HandleBase#HandleBase(HandleBase)
+ */
+ DataPipeConsumerHandleImpl(HandleBase other) {
+ super(other);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public ConsumerHandle pass() {
+ return new DataPipeConsumerHandleImpl(this);
+ }
+
+ /**
+ * @see ConsumerHandle#discardData(int, ReadFlags)
+ */
+ @Override
+ public int discardData(int numBytes, ReadFlags flags) {
+ return mCore.discardData(this, numBytes, flags);
+ }
+
+ /**
+ * @see ConsumerHandle#readData(ByteBuffer, ReadFlags)
+ */
+ @Override
+ public int readData(ByteBuffer elements, ReadFlags flags) {
+ return mCore.readData(this, elements, flags);
+ }
+
+ /**
+ * @see ConsumerHandle#beginReadData(int, ReadFlags)
+ */
+ @Override
+ public ByteBuffer beginReadData(int numBytes, ReadFlags flags) {
+ return mCore.beginReadData(this, numBytes, flags);
+ }
+
+ /**
+ * @see ConsumerHandle#endReadData(int)
+ */
+ @Override
+ public void endReadData(int numBytesRead) {
+ mCore.endReadData(this, numBytesRead);
+ }
+
+}
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java
new file mode 100644
index 0000000..1087a59
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java
@@ -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.
+
+package org.chromium.mojo.system.impl;
+
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+import org.chromium.mojo.system.DataPipe.WriteFlags;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implementation of {@link ProducerHandle}.
+ */
+class DataPipeProducerHandleImpl extends HandleBase implements ProducerHandle {
+
+ /**
+ * @see HandleBase#HandleBase(CoreImpl, int)
+ */
+ DataPipeProducerHandleImpl(CoreImpl core, int mojoHandle) {
+ super(core, mojoHandle);
+ }
+
+ /**
+ * @see HandleBase#HandleBase(HandleBase)
+ */
+ DataPipeProducerHandleImpl(HandleBase handle) {
+ super(handle);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.DataPipe.ProducerHandle#pass()
+ */
+ @Override
+ public ProducerHandle pass() {
+ return new DataPipeProducerHandleImpl(this);
+ }
+
+ /**
+ * @see ProducerHandle#writeData(ByteBuffer, WriteFlags)
+ */
+ @Override
+ public int writeData(ByteBuffer elements, WriteFlags flags) {
+ return mCore.writeData(this, elements, flags);
+ }
+
+ /**
+ * @see ProducerHandle#beginWriteData(int, WriteFlags)
+ */
+ @Override
+ public ByteBuffer beginWriteData(int numBytes, WriteFlags flags) {
+ return mCore.beginWriteData(this, numBytes, flags);
+ }
+
+ /**
+ * @see ProducerHandle#endWriteData(int)
+ */
+ @Override
+ public void endWriteData(int numBytesWritten) {
+ mCore.endWriteData(this, numBytesWritten);
+ }
+
+}
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java
new file mode 100644
index 0000000..81bfac7
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java
@@ -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.
+
+package org.chromium.mojo.system.impl;
+
+import android.util.Log;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Core.HandleSignals;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.UntypedHandle;
+
+/**
+ * Implementation of {@link Handle}.
+ */
+abstract class HandleBase implements Handle {
+
+ private static final String TAG = "HandleImpl";
+
+ /**
+ * The pointer to the scoped handle owned by this object.
+ */
+ private int mMojoHandle;
+
+ /**
+ * The core implementation. Will be used to delegate all behavior.
+ */
+ protected CoreImpl mCore;
+
+ /**
+ * Base constructor. Takes ownership of the passed handle.
+ */
+ HandleBase(CoreImpl core, int mojoHandle) {
+ mCore = core;
+ mMojoHandle = mojoHandle;
+ }
+
+ /**
+ * Constructor for transforming {@link HandleBase} into a specific one. It is used to transform
+ * an {@link UntypedHandle} into a typed one, or any handle into an {@link UntypedHandle}.
+ */
+ protected HandleBase(HandleBase other) {
+ mCore = other.mCore;
+ HandleBase otherAsHandleImpl = other;
+ int mojoHandle = otherAsHandleImpl.mMojoHandle;
+ otherAsHandleImpl.mMojoHandle = CoreImpl.INVALID_HANDLE;
+ mMojoHandle = mojoHandle;
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#close()
+ */
+ @Override
+ public void close() {
+ if (mMojoHandle != CoreImpl.INVALID_HANDLE) {
+ // After a close, the handle is invalid whether the close succeed or not.
+ int handle = mMojoHandle;
+ mMojoHandle = CoreImpl.INVALID_HANDLE;
+ mCore.close(handle);
+ }
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#wait(HandleSignals, long)
+ */
+ @Override
+ public int wait(HandleSignals signals, long deadline) {
+ return mCore.wait(this, signals, deadline);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#isValid()
+ */
+ @Override
+ public boolean isValid() {
+ return mMojoHandle != CoreImpl.INVALID_HANDLE;
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#toUntypedHandle()
+ */
+ @Override
+ public UntypedHandle toUntypedHandle() {
+ return new UntypedHandleImpl(this);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#getCore()
+ */
+ @Override
+ public Core getCore() {
+ return mCore;
+ }
+
+ /**
+ * @see Handle#releaseNativeHandle()
+ */
+ @Override
+ public int releaseNativeHandle() {
+ int result = mMojoHandle;
+ mMojoHandle = CoreImpl.INVALID_HANDLE;
+ return result;
+ }
+
+ /**
+ * Getter for the native scoped handle.
+ *
+ * @return the native scoped handle.
+ */
+ int getMojoHandle() {
+ return mMojoHandle;
+ }
+
+ /**
+ * invalidate the handle. The caller must ensures that the handle does not leak.
+ */
+ void invalidateHandle() {
+ mMojoHandle = CoreImpl.INVALID_HANDLE;
+ }
+
+ /**
+ * Close the handle if it is valid. Necessary because we cannot let handle leak, and we cannot
+ * ensure that every handle will be manually closed.
+ *
+ * @see java.lang.Object#finalize()
+ */
+ @Override
+ protected final void finalize() throws Throwable {
+ if (isValid()) {
+ // This should not happen, as the user of this class should close the handle. Adding a
+ // warning.
+ Log.w(TAG, "Handle was not closed.");
+ // Ignore result at this point.
+ mCore.closeWithResult(mMojoHandle);
+ }
+ super.finalize();
+ }
+
+}
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java
new file mode 100644
index 0000000..345a558
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java
@@ -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.
+
+package org.chromium.mojo.system.impl;
+
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Implementation of {@link MessagePipeHandle}.
+ */
+class MessagePipeHandleImpl extends HandleBase implements MessagePipeHandle {
+
+ /**
+ * @see HandleBase#HandleBase(CoreImpl, int)
+ */
+ MessagePipeHandleImpl(CoreImpl core, int mojoHandle) {
+ super(core, mojoHandle);
+ }
+
+ /**
+ * @see HandleBase#HandleBase(HandleBase)
+ */
+ MessagePipeHandleImpl(HandleBase handle) {
+ super(handle);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.MessagePipeHandle#pass()
+ */
+ @Override
+ public MessagePipeHandle pass() {
+ return new MessagePipeHandleImpl(this);
+ }
+
+ /**
+ * @see MessagePipeHandle#writeMessage(ByteBuffer, List, WriteFlags)
+ */
+ @Override
+ public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) {
+ mCore.writeMessage(this, bytes, handles, flags);
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(ByteBuffer, int, ReadFlags)
+ */
+ @Override
+ public ReadMessageResult readMessage(ByteBuffer bytes,
+ int maxNumberOfHandles,
+ ReadFlags flags) {
+ return mCore.readMessage(this, bytes, maxNumberOfHandles, flags);
+ }
+
+}
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java
new file mode 100644
index 0000000..76ef739
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java
@@ -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.
+
+package org.chromium.mojo.system.impl;
+
+import org.chromium.mojo.system.SharedBufferHandle;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implementation of {@link SharedBufferHandle}.
+ */
+class SharedBufferHandleImpl extends HandleBase implements SharedBufferHandle {
+
+ /**
+ * @see HandleBase#HandleBase(CoreImpl, int)
+ */
+ SharedBufferHandleImpl(CoreImpl core, int mojoHandle) {
+ super(core, mojoHandle);
+ }
+
+ /**
+ * @see HandleBase#HandleBase(HandleBase)
+ */
+ SharedBufferHandleImpl(HandleBase handle) {
+ super(handle);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.SharedBufferHandle#pass()
+ */
+ @Override
+ public SharedBufferHandle pass() {
+ return new SharedBufferHandleImpl(this);
+ }
+
+ /**
+ * @see SharedBufferHandle#duplicate(DuplicateOptions)
+ */
+ @Override
+ public SharedBufferHandle duplicate(DuplicateOptions options) {
+ return mCore.duplicate(this, options);
+ }
+
+ /**
+ * @see SharedBufferHandle#map(long, long, MapFlags)
+ */
+ @Override
+ public ByteBuffer map(long offset, long numBytes, MapFlags flags) {
+ return mCore.map(this, offset, numBytes, flags);
+ }
+
+ /**
+ * @see SharedBufferHandle#unmap(ByteBuffer)
+ */
+ @Override
+ public void unmap(ByteBuffer buffer) {
+ mCore.unmap(buffer);
+ }
+
+}
diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java
new file mode 100644
index 0000000..4774ab8
--- /dev/null
+++ b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java
@@ -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.
+
+package org.chromium.mojo.system.impl;
+
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.SharedBufferHandle;
+import org.chromium.mojo.system.UntypedHandle;
+
+/**
+ * Implementation of {@link UntypedHandle}.
+ */
+class UntypedHandleImpl extends HandleBase implements UntypedHandle {
+
+ /**
+ * @see HandleBase#HandleBase(CoreImpl, int)
+ */
+ UntypedHandleImpl(CoreImpl core, int mojoHandle) {
+ super(core, mojoHandle);
+ }
+
+ /**
+ * @see HandleBase#HandleBase(HandleBase)
+ */
+ UntypedHandleImpl(HandleBase handle) {
+ super(handle);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.UntypedHandle#pass()
+ */
+ @Override
+ public UntypedHandle pass() {
+ return new UntypedHandleImpl(this);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.UntypedHandle#toMessagePipeHandle()
+ */
+ @Override
+ public MessagePipeHandle toMessagePipeHandle() {
+ return new MessagePipeHandleImpl(this);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.UntypedHandle#toDataPipeConsumerHandle()
+ */
+ @Override
+ public ConsumerHandle toDataPipeConsumerHandle() {
+ return new DataPipeConsumerHandleImpl(this);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.UntypedHandle#toDataPipeProducerHandle()
+ */
+ @Override
+ public ProducerHandle toDataPipeProducerHandle() {
+ return new DataPipeProducerHandleImpl(this);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.UntypedHandle#toSharedBufferHandle()
+ */
+ @Override
+ public SharedBufferHandle toSharedBufferHandle() {
+ return new SharedBufferHandleImpl(this);
+ }
+
+}
diff --git a/mojo/application/BUILD.gn b/mojo/application/BUILD.gn
new file mode 100644
index 0000000..746f778
--- /dev/null
+++ b/mojo/application/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_base.gyp:mojo_application_chromium
+source_set("application") {
+ sources = [
+ "application_runner_chromium.cc",
+ "application_runner_chromium.h",
+ ]
+
+ public_deps = [
+ "//mojo/public/cpp/application",
+ ]
+ deps = [
+ "//base",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ ]
+}
diff --git a/mojo/application/application_runner_chromium.cc b/mojo/application/application_runner_chromium.cc
new file mode 100644
index 0000000..fa139c7
--- /dev/null
+++ b/mojo/application/application_runner_chromium.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/application/application_runner_chromium.h"
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/common/message_pump_mojo.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+
+namespace mojo {
+
+// static
+void ApplicationImpl::Terminate() {
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+}
+
+ApplicationRunnerChromium::ApplicationRunnerChromium(
+ ApplicationDelegate* delegate)
+ : delegate_(scoped_ptr<ApplicationDelegate>(delegate)),
+ message_loop_type_(base::MessageLoop::TYPE_CUSTOM),
+ has_run_(false) {}
+
+ApplicationRunnerChromium::~ApplicationRunnerChromium() {}
+
+void ApplicationRunnerChromium::set_message_loop_type(
+ base::MessageLoop::Type type) {
+ DCHECK_NE(base::MessageLoop::TYPE_CUSTOM, type);
+ DCHECK(!has_run_);
+
+ message_loop_type_ = type;
+}
+
+MojoResult ApplicationRunnerChromium::Run(MojoHandle shell_handle) {
+ DCHECK(!has_run_);
+ has_run_ = true;
+
+ base::CommandLine::Init(0, NULL);
+#if !defined(COMPONENT_BUILD)
+ base::AtExitManager at_exit;
+#endif
+
+ {
+ scoped_ptr<base::MessageLoop> loop;
+ if (message_loop_type_ == base::MessageLoop::TYPE_CUSTOM)
+ loop.reset(new base::MessageLoop(common::MessagePumpMojo::Create()));
+ else
+ loop.reset(new base::MessageLoop(message_loop_type_));
+
+ ApplicationImpl impl(delegate_.get(),
+ MakeScopedHandle(MessagePipeHandle(shell_handle)));
+ loop->Run();
+ }
+ delegate_.reset();
+ return MOJO_RESULT_OK;
+}
+
+} // namespace mojo
diff --git a/mojo/application/application_runner_chromium.h b/mojo/application/application_runner_chromium.h
new file mode 100644
index 0000000..8671342
--- /dev/null
+++ b/mojo/application/application_runner_chromium.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_APPLICATION_APPLICATION_RUNNER_CHROMIUM_H_
+#define MOJO_APPLICATION_APPLICATION_RUNNER_CHROMIUM_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+class ApplicationDelegate;
+
+// A utility for running a chromium based mojo Application. The typical use
+// case is to use when writing your MojoMain:
+//
+// MojoResult MojoMain(MojoHandle shell_handle) {
+// mojo::ApplicationRunnerChromium runner(new MyDelegate());
+// return runner.Run(shell_handle);
+// }
+//
+// ApplicationRunnerChromium takes care of chromium environment initialization
+// and shutdown, and starting a MessageLoop from which your application can run
+// and ultimately Quit().
+class ApplicationRunnerChromium {
+ public:
+ // Takes ownership of |delegate|.
+ explicit ApplicationRunnerChromium(ApplicationDelegate* delegate);
+ ~ApplicationRunnerChromium();
+
+ void set_message_loop_type(base::MessageLoop::Type type);
+
+ // Once the various parameters have been set above, use Run to initialize an
+ // ApplicationImpl wired to the provided delegate, and run a MessageLoop until
+ // the application exits.
+ MojoResult Run(MojoHandle shell_handle);
+
+ private:
+ scoped_ptr<ApplicationDelegate> delegate_;
+
+ // MessageLoop type. TYPE_CUSTOM is default (MessagePumpMojo will be used as
+ // the underlying message pump).
+ base::MessageLoop::Type message_loop_type_;
+ // Whether Run() has been called.
+ bool has_run_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ApplicationRunnerChromium);
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_APPLICATION_RUNNER_CHROMIUM_H_
diff --git a/mojo/application_manager/BUILD.gn b/mojo/application_manager/BUILD.gn
new file mode 100644
index 0000000..22c0edd
--- /dev/null
+++ b/mojo/application_manager/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo.gyp:mojo_application_manager
+component("application_manager") {
+ output_name = "mojo_application_manager"
+ sources = [
+ "application_loader.cc",
+ "application_loader.h",
+ "application_manager.cc",
+ "application_manager.h",
+ "application_manager_export.h",
+ "background_shell_application_loader.cc",
+ "background_shell_application_loader.h",
+ ]
+
+ defines = [
+ "MOJO_APPLICATION_MANAGER_IMPLEMENTATION",
+ ]
+
+ public_deps = [
+ "//base",
+ "//mojo/common",
+ "//mojo/public/interfaces/application:application",
+ "//mojo/services/public/interfaces/network:network",
+ "//url",
+ ]
+ deps = [
+ "//base/third_party/dynamic_annotations",
+ "//net",
+ "//url",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/interfaces/content_handler:content_handler",
+ ]
+}
+
+# GYP version: mojo.gyp:mojo_application_manager_unittests
+test("mojo_application_manager_unittests") {
+ sources = [
+ "application_manager_unittest.cc",
+ "background_shell_application_loader_unittest.cc",
+ ]
+
+ deps = [
+ ":application_manager",
+ ":test_bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/bindings",
+ "//testing/gtest",
+ "//url",
+ ]
+}
+
+mojom("test_bindings") {
+ sources = [ "test.mojom" ]
+}
diff --git a/mojo/application_manager/application_loader.cc b/mojo/application_manager/application_loader.cc
new file mode 100644
index 0000000..60dec66
--- /dev/null
+++ b/mojo/application_manager/application_loader.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 "mojo/application_manager/application_loader.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+
+ApplicationLoader::SimpleLoadCallbacks::SimpleLoadCallbacks(
+ ScopedMessagePipeHandle shell_handle)
+ : shell_handle_(shell_handle.Pass()) {
+}
+
+ApplicationLoader::SimpleLoadCallbacks::~SimpleLoadCallbacks() {
+}
+
+ScopedMessagePipeHandle
+ApplicationLoader::SimpleLoadCallbacks::RegisterApplication() {
+ return shell_handle_.Pass();
+}
+
+void ApplicationLoader::SimpleLoadCallbacks::LoadWithContentHandler(
+ const GURL& content_handle_url,
+ URLResponsePtr url_response) {
+ NOTREACHED();
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/application_loader.h b/mojo/application_manager/application_loader.h
new file mode 100644
index 0000000..b7d9f19
--- /dev/null
+++ b/mojo/application_manager/application_loader.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.
+
+#ifndef MOJO_APPLICATION_MANAGER_APPLICATION_LOADER_H_
+#define MOJO_APPLICATION_MANAGER_APPLICATION_LOADER_H_
+
+#include "base/memory/ref_counted.h"
+#include "mojo/application_manager/application_manager_export.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/public/interfaces/network/url_loader.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class ApplicationManager;
+
+// Interface to allowing loading behavior to be established for schemes,
+// specific urls or as the default.
+// A ApplicationLoader is responsible to using whatever mechanism is appropriate
+// to load the application at url.
+// The handle to the shell is passed to that application so it can bind it to
+// a Shell instance. This will give the Application a way to connect to other
+// apps and services.
+class MOJO_APPLICATION_MANAGER_EXPORT ApplicationLoader {
+ public:
+ class MOJO_APPLICATION_MANAGER_EXPORT LoadCallbacks
+ : public base::RefCounted<LoadCallbacks> {
+ public:
+ // Register the requested application with ApplicationManager. If the
+ // returned handle is valid, it should be used to implement the
+ // mojo::Application interface.
+ virtual ScopedMessagePipeHandle RegisterApplication() = 0;
+
+ // Load the requested application with a content handler.
+ virtual void LoadWithContentHandler(const GURL& content_handler_url,
+ URLResponsePtr url_response) = 0;
+
+ protected:
+ friend base::RefCounted<LoadCallbacks>;
+ virtual ~LoadCallbacks() {}
+ };
+
+ // Implements RegisterApplication() by returning a handle that was specified
+ // at construction time. LoadWithContentHandler() is not supported.
+ class MOJO_APPLICATION_MANAGER_EXPORT SimpleLoadCallbacks
+ : public LoadCallbacks {
+ public:
+ SimpleLoadCallbacks(ScopedMessagePipeHandle shell_handle);
+ virtual ScopedMessagePipeHandle RegisterApplication() override;
+ virtual void LoadWithContentHandler(const GURL& content_handler_url,
+ URLResponsePtr response) override;
+
+ private:
+ ScopedMessagePipeHandle shell_handle_;
+ virtual ~SimpleLoadCallbacks();
+ };
+
+ virtual ~ApplicationLoader() {}
+
+ // Load the application named |url|. Applications can be loaded two ways:
+ //
+ // 1. |url| can refer directly to a Mojo application. In this case, call
+ // callbacks->RegisterApplication(). The returned handle should be used to
+ // implement the mojo.Application interface. Note that the returned handle
+ // can be invalid in the case where the application has already been
+ // loaded.
+ //
+ // 2. |url| can refer to some content that can be handled by some other Mojo
+ // application. In this case, call callbacks->LoadWithContentHandler() and
+ // specify the URL of the application that should handle the content.
+ // The specified application must implement the mojo.ContentHandler
+ // interface.
+ virtual void Load(ApplicationManager* application_manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) = 0;
+
+ // Called when the Application exits.
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) = 0;
+
+ protected:
+ ApplicationLoader() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_MANAGER_APPLICATION_LOADER_H_
diff --git a/mojo/application_manager/application_manager.cc b/mojo/application_manager/application_manager.cc
new file mode 100644
index 0000000..b183d9f
--- /dev/null
+++ b/mojo/application_manager/application_manager.cc
@@ -0,0 +1,328 @@
+// Copyright 2014 The Chromium 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/application_manager/application_manager.h"
+
+#include <stdio.h>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace mojo {
+
+namespace {
+// Used by TestAPI.
+bool has_created_instance = false;
+
+class StubServiceProvider : public InterfaceImpl<ServiceProvider> {
+ public:
+ ServiceProvider* GetRemoteServiceProvider() { return client(); }
+
+ private:
+ virtual void ConnectToService(
+ const String& service_name,
+ ScopedMessagePipeHandle client_handle) override {}
+};
+
+} // namespace
+
+ApplicationManager::Delegate::~Delegate() {}
+
+class ApplicationManager::LoadCallbacksImpl
+ : public ApplicationLoader::LoadCallbacks {
+ public:
+ LoadCallbacksImpl(base::WeakPtr<ApplicationManager> manager,
+ const GURL& requested_url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider)
+ : manager_(manager),
+ requested_url_(requested_url),
+ requestor_url_(requestor_url),
+ service_provider_(service_provider.Pass()) {}
+
+ private:
+ virtual ~LoadCallbacksImpl() {}
+
+ // LoadCallbacks implementation
+ virtual ScopedMessagePipeHandle RegisterApplication() override {
+ ScopedMessagePipeHandle shell_handle;
+ if (manager_) {
+ manager_->RegisterLoadedApplication(requested_url_,
+ requestor_url_,
+ service_provider_.Pass(),
+ &shell_handle);
+ }
+ return shell_handle.Pass();
+ }
+
+ virtual void LoadWithContentHandler(const GURL& content_handler_url,
+ URLResponsePtr url_response) override {
+ if (manager_) {
+ manager_->LoadWithContentHandler(requested_url_,
+ requestor_url_,
+ content_handler_url,
+ url_response.Pass(),
+ service_provider_.Pass());
+ }
+ }
+
+ base::WeakPtr<ApplicationManager> manager_;
+ GURL requested_url_;
+ GURL requestor_url_;
+ ServiceProviderPtr service_provider_;
+};
+
+class ApplicationManager::ShellImpl : public InterfaceImpl<Shell> {
+ public:
+ ShellImpl(ApplicationManager* manager, const GURL& url)
+ : manager_(manager), url_(url) {}
+
+ virtual ~ShellImpl() {}
+
+ void ConnectToClient(const GURL& requestor_url,
+ ServiceProviderPtr service_provider) {
+ client()->AcceptConnection(String::From(requestor_url),
+ service_provider.Pass());
+ }
+
+ // ServiceProvider implementation:
+ virtual void ConnectToApplication(
+ const String& app_url,
+ InterfaceRequest<ServiceProvider> in_service_provider) override {
+ ServiceProviderPtr out_service_provider;
+ out_service_provider.Bind(in_service_provider.PassMessagePipe());
+ manager_->ConnectToApplication(
+ app_url.To<GURL>(), url_, out_service_provider.Pass());
+ }
+
+ const GURL& url() const { return url_; }
+
+ private:
+ virtual void OnConnectionError() override {
+ manager_->OnShellImplError(this);
+ }
+
+ ApplicationManager* const manager_;
+ const GURL url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellImpl);
+};
+
+struct ApplicationManager::ContentHandlerConnection {
+ ContentHandlerConnection(ApplicationManager* manager,
+ const GURL& content_handler_url) {
+ ServiceProviderPtr service_provider;
+ BindToProxy(&service_provider_impl, &service_provider);
+ manager->ConnectToApplication(
+ content_handler_url, GURL(), service_provider.Pass());
+ mojo::ConnectToService(service_provider_impl.client(), &content_handler);
+ }
+
+ StubServiceProvider service_provider_impl;
+ ContentHandlerPtr content_handler;
+};
+
+// static
+ApplicationManager::TestAPI::TestAPI(ApplicationManager* manager)
+ : manager_(manager) {
+}
+
+ApplicationManager::TestAPI::~TestAPI() {
+}
+
+bool ApplicationManager::TestAPI::HasCreatedInstance() {
+ return has_created_instance;
+}
+
+bool ApplicationManager::TestAPI::HasFactoryForURL(const GURL& url) const {
+ return manager_->url_to_shell_impl_.find(url) !=
+ manager_->url_to_shell_impl_.end();
+}
+
+ApplicationManager::ApplicationManager()
+ : delegate_(NULL),
+ interceptor_(NULL),
+ weak_ptr_factory_(this) {
+}
+
+ApplicationManager::~ApplicationManager() {
+ STLDeleteValues(&url_to_content_handler_);
+ TerminateShellConnections();
+ STLDeleteValues(&url_to_loader_);
+ STLDeleteValues(&scheme_to_loader_);
+}
+
+void ApplicationManager::TerminateShellConnections() {
+ STLDeleteValues(&url_to_shell_impl_);
+}
+
+// static
+ApplicationManager* ApplicationManager::GetInstance() {
+ static base::LazyInstance<ApplicationManager> instance =
+ LAZY_INSTANCE_INITIALIZER;
+ has_created_instance = true;
+ return &instance.Get();
+}
+
+void ApplicationManager::ConnectToApplication(
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider) {
+ URLToShellImplMap::const_iterator shell_it = url_to_shell_impl_.find(url);
+ if (shell_it != url_to_shell_impl_.end()) {
+ ConnectToClient(
+ shell_it->second, url, requestor_url, service_provider.Pass());
+ return;
+ }
+
+ scoped_refptr<LoadCallbacksImpl> callbacks(
+ new LoadCallbacksImpl(weak_ptr_factory_.GetWeakPtr(),
+ url,
+ requestor_url,
+ service_provider.Pass()));
+ GetLoaderForURL(url)->Load(this, url, callbacks);
+}
+
+void ApplicationManager::ConnectToClient(ShellImpl* shell_impl,
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider) {
+ if (interceptor_) {
+ shell_impl->ConnectToClient(
+ requestor_url,
+ interceptor_->OnConnectToClient(url, service_provider.Pass()));
+ } else {
+ shell_impl->ConnectToClient(requestor_url, service_provider.Pass());
+ }
+}
+
+void ApplicationManager::RegisterExternalApplication(
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle) {
+ url_to_shell_impl_[url] =
+ WeakBindToPipe(new ShellImpl(this, url), shell_handle.Pass());
+}
+
+void ApplicationManager::RegisterLoadedApplication(
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider,
+ ScopedMessagePipeHandle* shell_handle) {
+ ShellImpl* shell_impl = NULL;
+ URLToShellImplMap::iterator iter = url_to_shell_impl_.find(url);
+ if (iter != url_to_shell_impl_.end()) {
+ // This can happen because services are loaded asynchronously. So if we get
+ // two requests for the same service close to each other, we might get here
+ // and find that we already have it.
+ shell_impl = iter->second;
+ } else {
+ MessagePipe pipe;
+ URLToArgsMap::const_iterator args_it = url_to_args_.find(url);
+ Array<String> args;
+ if (args_it != url_to_args_.end())
+ args = Array<String>::From(args_it->second);
+ shell_impl = WeakBindToPipe(new ShellImpl(this, url), pipe.handle1.Pass());
+ url_to_shell_impl_[url] = shell_impl;
+ *shell_handle = pipe.handle0.Pass();
+ shell_impl->client()->Initialize(args.Pass());
+ }
+
+ ConnectToClient(shell_impl, url, requestor_url, service_provider.Pass());
+}
+
+void ApplicationManager::LoadWithContentHandler(
+ const GURL& content_url,
+ const GURL& requestor_url,
+ const GURL& content_handler_url,
+ URLResponsePtr url_response,
+ ServiceProviderPtr service_provider) {
+ ContentHandlerConnection* connection = NULL;
+ URLToContentHandlerMap::iterator iter =
+ url_to_content_handler_.find(content_handler_url);
+ if (iter != url_to_content_handler_.end()) {
+ connection = iter->second;
+ } else {
+ connection = new ContentHandlerConnection(this, content_handler_url);
+ url_to_content_handler_[content_handler_url] = connection;
+ }
+
+ InterfaceRequest<ServiceProvider> spir;
+ spir.Bind(service_provider.PassMessagePipe());
+ connection->content_handler->OnConnect(
+ content_url.spec(), url_response.Pass(), spir.Pass());
+}
+
+void ApplicationManager::SetLoaderForURL(scoped_ptr<ApplicationLoader> 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 ApplicationManager::SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader> 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 ApplicationManager::SetArgsForURL(const std::vector<std::string>& args,
+ const GURL& url) {
+ url_to_args_[url] = args;
+}
+
+void ApplicationManager::SetInterceptor(Interceptor* interceptor) {
+ interceptor_ = interceptor;
+}
+
+ApplicationLoader* ApplicationManager::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;
+ return default_loader_.get();
+}
+
+void ApplicationManager::OnShellImplError(ShellImpl* shell_impl) {
+ // Called from ~ShellImpl, so we do not need to call Destroy here.
+ const GURL url = shell_impl->url();
+ URLToShellImplMap::iterator it = url_to_shell_impl_.find(url);
+ DCHECK(it != url_to_shell_impl_.end());
+ delete it->second;
+ url_to_shell_impl_.erase(it);
+ ApplicationLoader* loader = GetLoaderForURL(url);
+ if (loader)
+ loader->OnApplicationError(this, url);
+ if (delegate_)
+ delegate_->OnApplicationError(url);
+}
+
+ScopedMessagePipeHandle ApplicationManager::ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& interface_name) {
+ StubServiceProvider* stub_sp = new StubServiceProvider;
+ ServiceProviderPtr spp;
+ BindToProxy(stub_sp, &spp);
+ ConnectToApplication(application_url, GURL(), spp.Pass());
+ MessagePipe pipe;
+ stub_sp->GetRemoteServiceProvider()->ConnectToService(interface_name,
+ pipe.handle1.Pass());
+ return pipe.handle0.Pass();
+}
+} // namespace mojo
diff --git a/mojo/application_manager/application_manager.h b/mojo/application_manager/application_manager.h
new file mode 100644
index 0000000..af74d8b
--- /dev/null
+++ b/mojo/application_manager/application_manager.h
@@ -0,0 +1,163 @@
+// Copyright 2014 The Chromium 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_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
+#define MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager_export.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class MOJO_APPLICATION_MANAGER_EXPORT ApplicationManager {
+ public:
+ class MOJO_APPLICATION_MANAGER_EXPORT Delegate {
+ public:
+ virtual ~Delegate();
+ // Send when the Application holding the handle on the other end of the
+ // Shell pipe goes away.
+ virtual void OnApplicationError(const GURL& url) = 0;
+ };
+
+ // API for testing.
+ class MOJO_APPLICATION_MANAGER_EXPORT TestAPI {
+ public:
+ explicit TestAPI(ApplicationManager* manager);
+ ~TestAPI();
+
+ // Returns true if the shared instance has been created.
+ static bool HasCreatedInstance();
+ // Returns true if there is a ShellImpl for this URL.
+ bool HasFactoryForURL(const GURL& url) const;
+
+ private:
+ ApplicationManager* manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestAPI);
+ };
+
+ // Interface class for debugging only.
+ class Interceptor {
+ public:
+ virtual ~Interceptor() {}
+ // Called when ApplicationManager::Connect is called.
+ virtual ServiceProviderPtr OnConnectToClient(
+ const GURL& url,
+ ServiceProviderPtr service_provider) = 0;
+ };
+
+ ApplicationManager();
+ ~ApplicationManager();
+
+ // Returns a shared instance, creating it if necessary.
+ static ApplicationManager* GetInstance();
+
+ void SetDelegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // Loads a service if necessary and establishes a new client connection.
+ void ConnectToApplication(const GURL& application_url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider);
+
+ template <typename Interface>
+ inline void ConnectToService(const GURL& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ScopedMessagePipeHandle service_handle =
+ ConnectToServiceByName(application_url, Interface::Name_);
+ ptr->Bind(service_handle.Pass());
+ }
+
+ ScopedMessagePipeHandle ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& interface_name);
+
+ void RegisterExternalApplication(const GURL& application_url,
+ ScopedMessagePipeHandle shell);
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // Sets the default Loader to be used if not overridden by SetLoaderForURL()
+ // or SetLoaderForScheme().
+ void set_default_loader(scoped_ptr<ApplicationLoader> loader) {
+ default_loader_ = loader.Pass();
+ }
+ // Sets a Loader to be used for a specific url.
+ void SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, const GURL& url);
+ // Sets a Loader to be used for a specific url scheme.
+ void SetLoaderForScheme(scoped_ptr<ApplicationLoader> loader,
+ const std::string& scheme);
+ // These strings will be passed to the Initialize() method when an
+ // Application is instantiated.
+ void SetArgsForURL(const std::vector<std::string>& args, const GURL& url);
+
+ // Allows to interpose a debugger to service connections.
+ void SetInterceptor(Interceptor* interceptor);
+
+ // Destroys all Shell-ends of connections established with Applications.
+ // Applications connected by this ApplicationManager will observe pipe errors
+ // and have a chance to shutdown.
+ void TerminateShellConnections();
+
+ private:
+ struct ContentHandlerConnection;
+ class LoadCallbacksImpl;
+ class ShellImpl;
+
+ typedef std::map<std::string, ApplicationLoader*> SchemeToLoaderMap;
+ typedef std::map<GURL, ApplicationLoader*> URLToLoaderMap;
+ typedef std::map<GURL, ShellImpl*> URLToShellImplMap;
+ typedef std::map<GURL, ContentHandlerConnection*> URLToContentHandlerMap;
+ typedef std::map<GURL, std::vector<std::string> > URLToArgsMap;
+
+ void ConnectToClient(ShellImpl* shell_impl,
+ const GURL& url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider);
+
+ void RegisterLoadedApplication(const GURL& service_url,
+ const GURL& requestor_url,
+ ServiceProviderPtr service_provider,
+ ScopedMessagePipeHandle* shell_handle);
+
+ void LoadWithContentHandler(const GURL& content_url,
+ const GURL& requestor_url,
+ const GURL& content_handler_url,
+ URLResponsePtr url_response,
+ ServiceProviderPtr service_provider);
+
+ // 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.
+ ApplicationLoader* GetLoaderForURL(const GURL& url);
+
+ // Removes a ShellImpl when it encounters an error.
+ void OnShellImplError(ShellImpl* shell_impl);
+
+ Delegate* delegate_;
+ // Loader management.
+ URLToLoaderMap url_to_loader_;
+ SchemeToLoaderMap scheme_to_loader_;
+ scoped_ptr<ApplicationLoader> default_loader_;
+ Interceptor* interceptor_;
+
+ URLToShellImplMap url_to_shell_impl_;
+ URLToContentHandlerMap url_to_content_handler_;
+ URLToArgsMap url_to_args_;
+
+ base::WeakPtrFactory<ApplicationManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApplicationManager);
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
diff --git a/mojo/application_manager/application_manager_export.h b/mojo/application_manager/application_manager_export.h
new file mode 100644
index 0000000..9af43cf
--- /dev/null
+++ b/mojo/application_manager/application_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_APPLICATION_MANAGER_APPLICATION_MANAGER_EXPORT_H_
+#define MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(MOJO_APPLICATION_MANAGER_IMPLEMENTATION)
+#define MOJO_APPLICATION_MANAGER_EXPORT __declspec(dllexport)
+#else
+#define MOJO_APPLICATION_MANAGER_EXPORT __declspec(dllimport)
+#endif
+
+#else // !defined(WIN32)
+
+#if defined(MOJO_APPLICATION_MANAGER_IMPLEMENTATION)
+#define MOJO_APPLICATION_MANAGER_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_APPLICATION_MANAGER_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+#else // !defined(COMPONENT_BUILD)
+#define MOJO_APPLICATION_MANAGER_EXPORT
+#endif
+
+#endif // MOJO_APPLICATION_MANAGER_APPLICATION_MANAGER_EXPORT_H_
diff --git a/mojo/application_manager/application_manager_unittest.cc b/mojo/application_manager/application_manager_unittest.cc
new file mode 100644
index 0000000..5763265
--- /dev/null
+++ b/mojo/application_manager/application_manager_unittest.cc
@@ -0,0 +1,686 @@
+// Copyright 2014 The Chromium 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/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/application_manager/background_shell_application_loader.h"
+#include "mojo/application_manager/test.mojom.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/interfaces/application/service_provider.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 {
+ if (!base::MessageLoop::current()->is_running())
+ return;
+ 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() { service_.reset(); }
+
+ 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 TestApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<TestService> {
+ public:
+ TestApplicationLoader() : context_(NULL), num_loads_(0) {}
+
+ virtual ~TestApplicationLoader() {
+ if (context_)
+ ++context_->num_loader_deletes;
+ test_app_.reset(NULL);
+ }
+
+ void set_context(TestContext* context) { context_ = context; }
+ int num_loads() const { return num_loads_; }
+ std::vector<std::string> GetArgs() {
+ return test_app_->args().To<std::vector<std::string> >();
+ }
+
+ private:
+ // ApplicationLoader implementation.
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ ++num_loads_;
+ test_app_.reset(
+ new ApplicationImpl(this, callbacks->RegisterApplication().Pass()));
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) override {
+ BindToRequest(new TestServiceImpl(context_), &request);
+ }
+
+ scoped_ptr<ApplicationImpl> test_app_;
+ TestContext* context_;
+ int num_loads_;
+ DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader);
+};
+
+class TesterContext {
+ public:
+ explicit TesterContext(base::MessageLoop* loop)
+ : num_b_calls_(0),
+ num_c_calls_(0),
+ num_a_deletes_(0),
+ num_b_deletes_(0),
+ num_c_deletes_(0),
+ tester_called_quit_(false),
+ a_called_quit_(false),
+ loop_(loop) {}
+
+ void IncrementNumBCalls() {
+ base::AutoLock lock(lock_);
+ num_b_calls_++;
+ }
+
+ void IncrementNumCCalls() {
+ base::AutoLock lock(lock_);
+ num_c_calls_++;
+ }
+
+ void IncrementNumADeletes() {
+ base::AutoLock lock(lock_);
+ num_a_deletes_++;
+ }
+
+ void IncrementNumBDeletes() {
+ base::AutoLock lock(lock_);
+ num_b_deletes_++;
+ }
+
+ void IncrementNumCDeletes() {
+ base::AutoLock lock(lock_);
+ num_c_deletes_++;
+ }
+
+ void set_tester_called_quit() {
+ base::AutoLock lock(lock_);
+ tester_called_quit_ = true;
+ }
+
+ void set_a_called_quit() {
+ base::AutoLock lock(lock_);
+ a_called_quit_ = true;
+ }
+
+ int num_b_calls() {
+ base::AutoLock lock(lock_);
+ return num_b_calls_;
+ }
+ int num_c_calls() {
+ base::AutoLock lock(lock_);
+ return num_c_calls_;
+ }
+ int num_a_deletes() {
+ base::AutoLock lock(lock_);
+ return num_a_deletes_;
+ }
+ int num_b_deletes() {
+ base::AutoLock lock(lock_);
+ return num_b_deletes_;
+ }
+ int num_c_deletes() {
+ base::AutoLock lock(lock_);
+ return num_c_deletes_;
+ }
+ bool tester_called_quit() {
+ base::AutoLock lock(lock_);
+ return tester_called_quit_;
+ }
+ bool a_called_quit() {
+ base::AutoLock lock(lock_);
+ return a_called_quit_;
+ }
+
+ void QuitSoon() {
+ loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+ }
+
+ private:
+ // lock_ protects all members except for loop_ which must be unchanged for the
+ // lifetime of this class.
+ base::Lock lock_;
+ int num_b_calls_;
+ int num_c_calls_;
+ int num_a_deletes_;
+ int num_b_deletes_;
+ int num_c_deletes_;
+ bool tester_called_quit_;
+ bool a_called_quit_;
+
+ base::MessageLoop* loop_;
+};
+
+// Used to test that the requestor url will be correctly passed.
+class TestAImpl : public InterfaceImpl<TestA> {
+ public:
+ TestAImpl(ApplicationConnection* connection, TesterContext* test_context)
+ : test_context_(test_context) {
+ connection->ConnectToApplication(kTestBURLString)->ConnectToService(&b_);
+ }
+ virtual ~TestAImpl() {
+ test_context_->IncrementNumADeletes();
+ if (base::MessageLoop::current()->is_running())
+ Quit();
+ }
+
+ private:
+ virtual void CallB() override {
+ b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
+ }
+
+ virtual void CallCFromB() override {
+ b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
+ }
+
+ void Quit() {
+ base::MessageLoop::current()->Quit();
+ test_context_->set_a_called_quit();
+ test_context_->QuitSoon();
+ }
+
+ TesterContext* test_context_;
+ TestBPtr b_;
+};
+
+class TestBImpl : public InterfaceImpl<TestB> {
+ public:
+ TestBImpl(ApplicationConnection* connection, TesterContext* test_context)
+ : test_context_(test_context) {
+ connection->ConnectToService(&c_);
+ }
+
+ virtual ~TestBImpl() {
+ test_context_->IncrementNumBDeletes();
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+ test_context_->QuitSoon();
+ }
+
+ private:
+ virtual void B(const mojo::Callback<void()>& callback) override {
+ test_context_->IncrementNumBCalls();
+ callback.Run();
+ }
+
+ virtual void CallC(const mojo::Callback<void()>& callback) override {
+ test_context_->IncrementNumBCalls();
+ c_->C(callback);
+ }
+
+ TesterContext* test_context_;
+ TestCPtr c_;
+};
+
+class TestCImpl : public InterfaceImpl<TestC> {
+ public:
+ TestCImpl(ApplicationConnection* connection, TesterContext* test_context)
+ : test_context_(test_context) {}
+
+ virtual ~TestCImpl() { test_context_->IncrementNumCDeletes(); }
+
+ private:
+ virtual void C(const mojo::Callback<void()>& callback) override {
+ test_context_->IncrementNumCCalls();
+ callback.Run();
+ }
+ TesterContext* test_context_;
+};
+
+class Tester : public ApplicationDelegate,
+ public ApplicationLoader,
+ public InterfaceFactory<TestA>,
+ public InterfaceFactory<TestB>,
+ public InterfaceFactory<TestC> {
+ public:
+ Tester(TesterContext* context, const std::string& requestor_url)
+ : context_(context), requestor_url_(requestor_url) {}
+ virtual ~Tester() {}
+
+ private:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ app_.reset(
+ new ApplicationImpl(this, callbacks->RegisterApplication().Pass()));
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ if (!requestor_url_.empty() &&
+ requestor_url_ != connection->GetRemoteApplicationURL()) {
+ context_->set_tester_called_quit();
+ context_->QuitSoon();
+ base::MessageLoop::current()->Quit();
+ return false;
+ }
+ // If we're coming from A, then add B, otherwise A.
+ if (connection->GetRemoteApplicationURL() == kTestAURLString)
+ connection->AddService<TestB>(this);
+ else
+ connection->AddService<TestA>(this);
+ return true;
+ }
+
+ virtual bool ConfigureOutgoingConnection(
+ ApplicationConnection* connection) override {
+ // If we're connecting to B, then add C.
+ if (connection->GetRemoteApplicationURL() == kTestBURLString)
+ connection->AddService<TestC>(this);
+ return true;
+ }
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestA> request) override {
+ BindToRequest(new TestAImpl(connection, context_), &request);
+ }
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestB> request) override {
+ BindToRequest(new TestBImpl(connection, context_), &request);
+ }
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestC> request) override {
+ BindToRequest(new TestCImpl(connection, context_), &request);
+ }
+
+ TesterContext* context_;
+ scoped_ptr<ApplicationImpl> app_;
+ std::string requestor_url_;
+};
+
+class TestServiceInterceptor : public ApplicationManager::Interceptor {
+ public:
+ TestServiceInterceptor() : call_count_(0) {}
+
+ virtual ServiceProviderPtr OnConnectToClient(
+ const GURL& url,
+ ServiceProviderPtr service_provider) override {
+ ++call_count_;
+ url_ = url;
+ return service_provider.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 ApplicationManagerTest : public testing::Test {
+ public:
+ ApplicationManagerTest() : tester_context_(&loop_) {}
+
+ virtual ~ApplicationManagerTest() {}
+
+ virtual void SetUp() override {
+ application_manager_.reset(new ApplicationManager);
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ default_loader->set_context(&context_);
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+
+ TestServicePtr service_proxy;
+ application_manager_->ConnectToService(GURL(kTestURLString),
+ &service_proxy);
+ test_client_.reset(new TestClientImpl(service_proxy.Pass()));
+ }
+
+ virtual void TearDown() override {
+ test_client_.reset(NULL);
+ application_manager_.reset(NULL);
+ }
+
+ scoped_ptr<BackgroundShellApplicationLoader> MakeLoader(
+ const std::string& requestor_url) {
+ scoped_ptr<ApplicationLoader> real_loader(
+ new Tester(&tester_context_, requestor_url));
+ scoped_ptr<BackgroundShellApplicationLoader> loader(
+ new BackgroundShellApplicationLoader(real_loader.Pass(),
+ std::string(),
+ base::MessageLoop::TYPE_DEFAULT));
+ return loader.Pass();
+ }
+
+ void AddLoaderForURL(const GURL& url, const std::string& requestor_url) {
+ application_manager_->SetLoaderForURL(
+ MakeLoader(requestor_url).PassAs<ApplicationLoader>(), url);
+ }
+
+ bool HasFactoryForTestURL() {
+ ApplicationManager::TestAPI manager_test_api(application_manager_.get());
+ return manager_test_api.HasFactoryForURL(GURL(kTestURLString));
+ }
+
+ protected:
+ base::ShadowingAtExitManager at_exit_;
+ TesterContext tester_context_;
+ TestContext context_;
+ base::MessageLoop loop_;
+ scoped_ptr<TestClientImpl> test_client_;
+ scoped_ptr<ApplicationManager> application_manager_;
+ DISALLOW_COPY_AND_ASSIGN(ApplicationManagerTest);
+};
+
+TEST_F(ApplicationManagerTest, Basic) {
+ test_client_->Test("test");
+ loop_.Run();
+ EXPECT_EQ(std::string("test"), context_.last_test_string);
+}
+
+// Confirm that no arguments are sent to an application by default.
+TEST_F(ApplicationManagerTest, NoArgs) {
+ ApplicationManager am;
+ GURL test_url("test:test");
+ TestContext context;
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClientImpl test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ EXPECT_EQ(0U, app_args.size());
+}
+
+// Confirm that arguments are sent to an application.
+TEST_F(ApplicationManagerTest, Args) {
+ ApplicationManager am;
+ GURL test_url("test:test");
+ std::vector<std::string> args;
+ args.push_back("test_arg1");
+ args.push_back("test_arg2");
+ am.SetArgsForURL(args, test_url);
+ TestContext context;
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClientImpl test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ ASSERT_EQ(args.size(), app_args.size());
+ EXPECT_EQ(args[0], app_args[0]);
+ EXPECT_EQ(args[1], app_args[1]);
+}
+
+TEST_F(ApplicationManagerTest, 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(ApplicationManagerTest, Deletes) {
+ {
+ ApplicationManager am;
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ default_loader->set_context(&context_);
+ TestApplicationLoader* url_loader1 = new TestApplicationLoader;
+ TestApplicationLoader* url_loader2 = new TestApplicationLoader;
+ url_loader1->set_context(&context_);
+ url_loader2->set_context(&context_);
+ TestApplicationLoader* scheme_loader1 = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader2 = new TestApplicationLoader;
+ scheme_loader1->set_context(&context_);
+ scheme_loader2->set_context(&context_);
+ am.set_default_loader(scoped_ptr<ApplicationLoader>(default_loader));
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader1),
+ GURL("test:test1"));
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader2),
+ GURL("test:test1"));
+ am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader1),
+ "test");
+ am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader2),
+ "test");
+ }
+ EXPECT_EQ(5, context_.num_loader_deletes);
+}
+
+// Confirm that both urls and schemes can have their loaders explicitly set.
+TEST_F(ApplicationManagerTest, SetLoaders) {
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ TestApplicationLoader* url_loader = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader = new TestApplicationLoader;
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+ application_manager_->SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1"));
+ application_manager_->SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader>(scheme_loader), "test");
+
+ // test::test1 should go to url_loader.
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL("test:test1"), &test_service);
+ 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.
+ application_manager_->ConnectToService(GURL("test:test2"), &test_service);
+ 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.
+ application_manager_->ConnectToService(GURL("http:test1"), &test_service);
+ 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(ApplicationManagerTest, ACallB) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only a can load b.
+ AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallB();
+ loop_.Run();
+ EXPECT_EQ(1, tester_context_.num_b_calls());
+ EXPECT_TRUE(tester_context_.a_called_quit());
+}
+
+// A calls B which calls C.
+TEST_F(ApplicationManagerTest, BCallC) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only a can load b.
+ AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallCFromB();
+ loop_.Run();
+
+ EXPECT_EQ(1, tester_context_.num_b_calls());
+ EXPECT_EQ(1, tester_context_.num_c_calls());
+ EXPECT_TRUE(tester_context_.a_called_quit());
+}
+
+// Confirm that a service impl will be deleted if the app that connected to
+// it goes away.
+TEST_F(ApplicationManagerTest, BDeleted) {
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+ AddLoaderForURL(GURL(kTestBURLString), std::string());
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+
+ a->CallB();
+ loop_.Run();
+
+ // Kills the a app.
+ application_manager_->SetLoaderForURL(scoped_ptr<ApplicationLoader>(),
+ GURL(kTestAURLString));
+ loop_.Run();
+
+ EXPECT_EQ(1, tester_context_.num_b_deletes());
+}
+
+// Confirm that the url of a service is correctly passed to another service that
+// it loads, and that it can be rejected.
+TEST_F(ApplicationManagerTest, ANoLoadB) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only c can load b, so this will fail.
+ AddLoaderForURL(GURL(kTestBURLString), "test:TestC");
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallB();
+ loop_.Run();
+ EXPECT_EQ(0, tester_context_.num_b_calls());
+
+ EXPECT_FALSE(tester_context_.a_called_quit());
+ EXPECT_TRUE(tester_context_.tester_called_quit());
+}
+
+TEST_F(ApplicationManagerTest, NoServiceNoLoad) {
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // There is no TestC service implementation registered with
+ // ApplicationManager, so this cannot succeed (but also shouldn't crash).
+ TestCPtr c;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &c);
+ QuitMessageLoopErrorHandler quitter;
+ c.set_error_handler(&quitter);
+
+ loop_.Run();
+ EXPECT_TRUE(c.encountered_error());
+}
+
+TEST_F(ApplicationManagerTest, Interceptor) {
+ TestServiceInterceptor interceptor;
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+ application_manager_->SetInterceptor(&interceptor);
+
+ std::string url("test:test3");
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL(url), &test_service);
+
+ EXPECT_EQ(1, interceptor.call_count());
+ EXPECT_EQ(url, interceptor.url_spec());
+ EXPECT_EQ(1, default_loader->num_loads());
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/background_shell_application_loader.cc b/mojo/application_manager/background_shell_application_loader.cc
new file mode 100644
index 0000000..e6664db
--- /dev/null
+++ b/mojo/application_manager/background_shell_application_loader.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/application_manager/background_shell_application_loader.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "mojo/application_manager/application_manager.h"
+
+namespace mojo {
+
+class BackgroundShellApplicationLoader::BackgroundLoader {
+ public:
+ explicit BackgroundLoader(ApplicationLoader* loader) : loader_(loader) {}
+ ~BackgroundLoader() {}
+
+ void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle) {
+ scoped_refptr<LoadCallbacks> callbacks(
+ new ApplicationLoader::SimpleLoadCallbacks(shell_handle.Pass()));
+ loader_->Load(manager, url, callbacks);
+ }
+
+ void OnApplicationError(ApplicationManager* manager, const GURL& url) {
+ loader_->OnApplicationError(manager, url);
+ }
+
+ private:
+ ApplicationLoader* loader_; // Owned by BackgroundShellApplicationLoader
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundLoader);
+};
+
+BackgroundShellApplicationLoader::BackgroundShellApplicationLoader(
+ scoped_ptr<ApplicationLoader> real_loader,
+ const std::string& thread_name,
+ base::MessageLoop::Type message_loop_type)
+ : loader_(real_loader.Pass()),
+ message_loop_type_(message_loop_type),
+ thread_name_(thread_name),
+ message_loop_created_(true, false),
+ background_loader_(NULL) {
+}
+
+BackgroundShellApplicationLoader::~BackgroundShellApplicationLoader() {
+ if (thread_)
+ thread_->Join();
+}
+
+void BackgroundShellApplicationLoader::Load(
+ ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+
+ if (!thread_) {
+ // TODO(tim): It'd be nice if we could just have each Load call
+ // result in a new thread like DynamicService{Loader, Runner}. But some
+ // loaders are creating multiple ApplicationImpls (NetworkApplicationLoader)
+ // sharing a delegate (etc). So we have to keep it single threaded, wait
+ // for the thread to initialize, and post to the TaskRunner for subsequent
+ // Load calls for now.
+ thread_.reset(new base::DelegateSimpleThread(this, thread_name_));
+ thread_->Start();
+ message_loop_created_.Wait();
+ DCHECK(task_runner_.get());
+ }
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &BackgroundShellApplicationLoader::LoadOnBackgroundThread,
+ base::Unretained(this),
+ manager,
+ url,
+ base::Owned(new ScopedMessagePipeHandle(shell_handle.Pass()))));
+}
+
+void BackgroundShellApplicationLoader::OnApplicationError(
+ ApplicationManager* manager,
+ const GURL& url) {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&BackgroundShellApplicationLoader::
+ OnApplicationErrorOnBackgroundThread,
+ base::Unretained(this),
+ manager,
+ url));
+}
+
+void BackgroundShellApplicationLoader::Run() {
+ base::MessageLoop message_loop(message_loop_type_);
+ base::RunLoop loop;
+ task_runner_ = message_loop.task_runner();
+ quit_closure_ = loop.QuitClosure();
+ message_loop_created_.Signal();
+ loop.Run();
+
+ delete background_loader_;
+ background_loader_ = NULL;
+ // Destroy |loader_| on the thread it's actually used on.
+ loader_.reset();
+}
+
+void BackgroundShellApplicationLoader::LoadOnBackgroundThread(
+ ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle* shell_handle) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ if (!background_loader_)
+ background_loader_ = new BackgroundLoader(loader_.get());
+ background_loader_->Load(manager, url, shell_handle->Pass());
+}
+
+void BackgroundShellApplicationLoader::OnApplicationErrorOnBackgroundThread(
+ ApplicationManager* manager,
+ const GURL& url) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ if (!background_loader_)
+ background_loader_ = new BackgroundLoader(loader_.get());
+ background_loader_->OnApplicationError(manager, url);
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/background_shell_application_loader.h b/mojo/application_manager/background_shell_application_loader.h
new file mode 100644
index 0000000..3c56290
--- /dev/null
+++ b/mojo/application_manager/background_shell_application_loader.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_APPLICATION_MANAGER_BACKGROUND_SHELL_APPLICATION_LOADER_H_
+#define MOJO_APPLICATION_MANAGER_BACKGROUND_SHELL_APPLICATION_LOADER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/application_manager/application_loader.h"
+
+namespace mojo {
+
+// TODO(tim): Eventually this should be Android-only to support services
+// that we need to bundle with the shell (such as NetworkService). Perhaps
+// we should move it to shell/ as well.
+class MOJO_APPLICATION_MANAGER_EXPORT BackgroundShellApplicationLoader
+ : public ApplicationLoader,
+ public base::DelegateSimpleThread::Delegate {
+ public:
+ BackgroundShellApplicationLoader(scoped_ptr<ApplicationLoader> real_loader,
+ const std::string& thread_name,
+ base::MessageLoop::Type message_loop_type);
+ virtual ~BackgroundShellApplicationLoader();
+
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override;
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ private:
+ class BackgroundLoader;
+
+ // |base::DelegateSimpleThread::Delegate| method:
+ virtual void Run() override;
+
+ // 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 ApplicationManager
+ // isn't thread safe).
+ void LoadOnBackgroundThread(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle* shell_handle);
+ void OnApplicationErrorOnBackgroundThread(ApplicationManager* manager,
+ const GURL& url);
+ bool quit_on_shutdown_;
+ scoped_ptr<ApplicationLoader> loader_;
+
+ const base::MessageLoop::Type message_loop_type_;
+ const std::string thread_name_;
+
+ // Created on |thread_| during construction of |this|. Protected against
+ // uninitialized use by |message_loop_created_|, and protected against
+ // use-after-free by holding a reference to the thread-safe object. Note
+ // that holding a reference won't hold |thread_| from exiting.
+ scoped_refptr<base::TaskRunner> task_runner_;
+ base::WaitableEvent message_loop_created_;
+
+ // Lives on |thread_|.
+ base::Closure quit_closure_;
+
+ scoped_ptr<base::DelegateSimpleThread> thread_;
+
+ // Lives on |thread_|. Trivial interface that calls through to |loader_|.
+ BackgroundLoader* background_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundShellApplicationLoader);
+};
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_MANAGER_BACKGROUND_SHELL_APPLICATION_LOADER_H_
diff --git a/mojo/application_manager/background_shell_application_loader_unittest.cc b/mojo/application_manager/background_shell_application_loader_unittest.cc
new file mode 100644
index 0000000..acf1a01
--- /dev/null
+++ b/mojo/application_manager/background_shell_application_loader_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.
+
+#include "mojo/application_manager/background_shell_application_loader.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+namespace {
+
+class DummyLoader : public ApplicationLoader {
+ public:
+ DummyLoader() : simulate_app_quit_(true) {}
+ virtual ~DummyLoader() {}
+
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ if (simulate_app_quit_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ void DontSimulateAppQuit() { simulate_app_quit_ = false; }
+
+ private:
+ bool simulate_app_quit_;
+};
+
+} // namespace
+
+// Tests that the loader can start and stop gracefully.
+TEST(BackgroundShellApplicationLoaderTest, StartStop) {
+ scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
+ BackgroundShellApplicationLoader loader(
+ real_loader.Pass(), "test", base::MessageLoop::TYPE_DEFAULT);
+}
+
+// Tests that the loader can load a service that is well behaved (quits
+// itself).
+TEST(BackgroundShellApplicationLoaderTest, Load) {
+ scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
+ BackgroundShellApplicationLoader loader(
+ real_loader.Pass(), "test", base::MessageLoop::TYPE_DEFAULT);
+ MessagePipe dummy;
+ scoped_refptr<ApplicationLoader::SimpleLoadCallbacks> callbacks(
+ new ApplicationLoader::SimpleLoadCallbacks(dummy.handle0.Pass()));
+ loader.Load(NULL, GURL(), callbacks);
+}
+
+} // namespace mojo
diff --git a/mojo/application_manager/test.mojom b/mojo/application_manager/test.mojom
new file mode 100644
index 0000000..6a03581
--- /dev/null
+++ b/mojo/application_manager/test.mojom
@@ -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 {
+
+[Client=TestClient]
+interface TestService {
+ Test(string? test_string);
+};
+
+interface TestClient {
+ AckTest();
+};
+
+interface TestA {
+ CallB();
+ CallCFromB();
+};
+
+interface TestB {
+ B() => ();
+ CallC() => ();
+};
+
+interface TestC {
+ C() => ();
+};
+
+}
diff --git a/mojo/apps/js/BUILD.gn b/mojo/apps/js/BUILD.gn
new file mode 100644
index 0000000..16f1907
--- /dev/null
+++ b/mojo/apps/js/BUILD.gn
@@ -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.
+
+# GYP version: part of mojo/mojo_apps.gypi:mojo_js_lib
+source_set("js") {
+ sources = [
+ "mojo_runner_delegate.cc",
+ "mojo_runner_delegate.h",
+ ]
+
+ public_deps = [
+ "//mojo/bindings/js",
+ ]
+ deps = [
+ "//base",
+ "//gin",
+ "//mojo/apps/js/bindings",
+ "//mojo/apps/js/bindings/gl",
+ "//v8",
+ ]
+}
+
+# GYP version: mojo/mojo_apps.gypi:mojo_js_apps_lib
+source_set("js_apps") {
+ sources = [
+ "application_delegate_impl.cc",
+ "js_app.cc",
+ "mojo_module.cc",
+ ]
+
+ public_deps = [
+ "//mojo/bindings/js",
+ ]
+ deps = [
+ ":js",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/utility",
+ ]
+}
+
+shared_library("mojo_js_content_handler") {
+ sources = [
+ "content_handler_impl.cc",
+ "content_handler_main.cc",
+ ]
+
+ deps = [
+ ":js_apps",
+ "//base:i18n",
+ "//mojo/application:application",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/interfaces/content_handler",
+ ]
+}
+
+shared_library("mojo_js_standalone") {
+ sources = [
+ "standalone_main.cc"
+ ]
+
+ deps = [
+ ":js_apps",
+ "//base:i18n",
+ "//mojo/application:application",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/application",
+ ]
+}
+
+
+
diff --git a/mojo/apps/js/DEPS b/mojo/apps/js/DEPS
new file mode 100644
index 0000000..d974b68
--- /dev/null
+++ b/mojo/apps/js/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+gin",
+ "+v8",
+]
diff --git a/mojo/apps/js/application_delegate_impl.cc b/mojo/apps/js/application_delegate_impl.cc
new file mode 100644
index 0000000..eb784a9
--- /dev/null
+++ b/mojo/apps/js/application_delegate_impl.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/apps/js/application_delegate_impl.h"
+
+#include "gin/array_buffer.h"
+#include "gin/public/isolate_holder.h"
+#include "mojo/apps/js/js_app.h"
+#include "mojo/public/cpp/application/application_impl.h"
+
+namespace mojo {
+namespace apps {
+
+ApplicationDelegateImpl::ApplicationDelegateImpl()
+ : application_impl_(nullptr) {
+}
+
+void ApplicationDelegateImpl::Initialize(ApplicationImpl* app) {
+ application_impl_ = app;
+ gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode,
+ gin::ArrayBufferAllocator::SharedInstance());
+}
+
+ApplicationDelegateImpl::~ApplicationDelegateImpl() {
+}
+
+void ApplicationDelegateImpl::StartJSApp(scoped_ptr<JSApp> app_ptr) {
+ JSApp *app = app_ptr.release();
+ app_vector_.push_back(app);
+ // TODO(hansmuller): deal with the Start() return value.
+ app->Start();
+}
+
+void ApplicationDelegateImpl::QuitJSApp(JSApp* app) {
+ AppVector::iterator itr =
+ std::find(app_vector_.begin(), app_vector_.end(), app);
+ if (itr != app_vector_.end())
+ app_vector_.erase(itr);
+}
+
+void ApplicationDelegateImpl::ConnectToService(
+ ScopedMessagePipeHandle pipe_handle,
+ const std::string& application_url,
+ const std::string& interface_name) {
+ CHECK(application_impl_);
+ ServiceProvider* service_provider =
+ application_impl_->ConnectToApplication(application_url)
+ ->GetServiceProvider();
+ service_provider->ConnectToService(interface_name, pipe_handle.Pass());
+}
+
+} // namespace apps
+} // namespace mojo
diff --git a/mojo/apps/js/application_delegate_impl.h b/mojo/apps/js/application_delegate_impl.h
new file mode 100644
index 0000000..4c2f2e2
--- /dev/null
+++ b/mojo/apps/js/application_delegate_impl.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_APPS_JS_CONTENT_HANDLER_H_
+#define MOJO_APPS_JS_CONTENT_HANDLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+class ApplcationImpl;
+
+namespace apps {
+
+class ApplicationDelegateImpl;
+class JSApp;
+
+// Manages the JSApps started by this content handler. This class owns the one
+// reference to the Mojo shell. JSApps post a task to the content handler's
+// thread to connect to a service or to quit.
+//
+// The lifetime each JSApp is defined by its entry in AppVector. When the entry
+// is removed ("erased") by QuitJSApp(), the JSApp is destroyed.
+
+class ApplicationDelegateImpl : public ApplicationDelegate {
+ public:
+ ApplicationDelegateImpl();
+ virtual ~ApplicationDelegateImpl();
+
+ // Add app to the AppVector and call its Start() method.
+ void StartJSApp(scoped_ptr<JSApp> app);
+
+ // Remove app from the AppVector; destroys the app.
+ void QuitJSApp(JSApp *app);
+
+ void ConnectToService(ScopedMessagePipeHandle pipe_handle,
+ const std::string& application_url,
+ const std::string& interface_name);
+
+ protected:
+ // ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override;
+
+ private:
+ typedef ScopedVector<JSApp> AppVector;
+ ApplicationImpl* application_impl_;
+ AppVector app_vector_;
+};
+
+} // namespace apps
+} // namespace mojo
+
+#endif // MOJO_APPS_JS_CONTENT_HANDLER_H_
diff --git a/mojo/apps/js/bindings/BUILD.gn b/mojo/apps/js/bindings/BUILD.gn
new file mode 100644
index 0000000..bc72747
--- /dev/null
+++ b/mojo/apps/js/bindings/BUILD.gn
@@ -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.
+
+# GYP version: part of mojo/mojo_apps.gypi:mojo_js_lib
+source_set("bindings") {
+ deps = [
+ "//base",
+ "//gin",
+ "//v8",
+ "//mojo/bindings/js",
+ ]
+
+ sources = [
+ "threading.cc",
+ "threading.h",
+ "monotonic_clock.cc",
+ "monotonic_clock.h",
+ ]
+}
diff --git a/mojo/apps/js/bindings/connection_unittests.js b/mojo/apps/js/bindings/connection_unittests.js
new file mode 100644
index 0000000..0b0a71b
--- /dev/null
+++ b/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/mojo/apps/js/bindings/gl/BUILD.gn b/mojo/apps/js/bindings/gl/BUILD.gn
new file mode 100644
index 0000000..0ee7e13
--- /dev/null
+++ b/mojo/apps/js/bindings/gl/BUILD.gn
@@ -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.
+
+# GYP version: part of mojo/mojo_apps.gypi:mojo_js_lib
+source_set("gl") {
+ sources = [
+ "context.cc",
+ "context.h",
+ "module.cc",
+ "module.h",
+ ]
+
+ deps = [
+ "//base",
+ "//gin",
+ "//v8",
+ "//mojo/bindings/js",
+ "//mojo/environment:chromium",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/gles2:bindings",
+ ]
+}
diff --git a/mojo/apps/js/bindings/gl/context.cc b/mojo/apps/js/bindings/gl/context.cc
new file mode 100644
index 0000000..4754d4e
--- /dev/null
+++ b/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"
+#include "mojo/public/cpp/environment/environment.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,
+ this,
+ Environment::GetDefaultAsyncWaiter());
+ 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/mojo/apps/js/bindings/gl/context.h b/mojo/apps/js/bindings/gl/context.h
new file mode 100644
index 0000000..2166860
--- /dev/null
+++ b/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/mojo/apps/js/bindings/gl/module.cc b/mojo/apps/js/bindings/gl/module.cc
new file mode 100644
index 0000000..413f22e
--- /dev/null
+++ b/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/mojo/apps/js/bindings/gl/module.h b/mojo/apps/js/bindings/gl/module.h
new file mode 100644
index 0000000..0ae0d14
--- /dev/null
+++ b/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/mojo/apps/js/bindings/monotonic_clock.cc b/mojo/apps/js/bindings/monotonic_clock.cc
new file mode 100644
index 0000000..733af8a
--- /dev/null
+++ b/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/mojo/apps/js/bindings/monotonic_clock.h b/mojo/apps/js/bindings/monotonic_clock.h
new file mode 100644
index 0000000..6278821
--- /dev/null
+++ b/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/mojo/apps/js/bindings/monotonic_clock_unittests.js b/mojo/apps/js/bindings/monotonic_clock_unittests.js
new file mode 100644
index 0000000..a1a253e
--- /dev/null
+++ b/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/mojo/apps/js/bindings/sample_service_unittests.js b/mojo/apps/js/bindings/sample_service_unittests.js
new file mode 100644
index 0000000..2b8c31c
--- /dev/null
+++ b/mojo/apps/js/bindings/sample_service_unittests.js
@@ -0,0 +1,168 @@
+// 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.VERTICAL;
+
+ var extra_bars = new Array(3);
+ for (var i = 0; i < extra_bars.length; ++i) {
+ var base = i * 100;
+ var type = i % 2 ?
+ sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
+ extra_bars[i] = new sample.Bar();
+ extra_bars[i].alpha = base;
+ extra_bars[i].beta = base + 20;
+ extra_bars[i].gamma = base + 40;
+ extra_bars[i].type = type;
+ }
+
+ var data = new Array(10);
+ for (var i = 0; i < data.length; ++i) {
+ data[i] = data.length - i;
+ }
+
+ var source = 0xFFFF; // Invent a dummy handle.
+
+ var foo = new sample.Foo();
+ foo.name = "foopy";
+ foo.x = 1;
+ foo.y = 2;
+ foo.a = false;
+ foo.b = true;
+ foo.c = false;
+ foo.bar = bar;
+ foo.extra_bars = extra_bars;
+ foo.data = data;
+ foo.source = source;
+ return foo;
+ }
+
+ // Check that the given |Foo| is identical to the one made by |MakeFoo()|.
+ function checkFoo(foo) {
+ expect(foo.name).toBe("foopy");
+ expect(foo.x).toBe(1);
+ expect(foo.y).toBe(2);
+ expect(foo.a).toBeFalsy();
+ expect(foo.b).toBeTruthy();
+ expect(foo.c).toBeFalsy();
+ expect(foo.bar.alpha).toBe(20);
+ expect(foo.bar.beta).toBe(40);
+ expect(foo.bar.gamma).toBe(60);
+ expect(foo.bar.type).toBe(sample.Bar.Type.VERTICAL);
+
+ expect(foo.extra_bars.length).toBe(3);
+ for (var i = 0; i < foo.extra_bars.length; ++i) {
+ var base = i * 100;
+ var type = i % 2 ?
+ sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
+ expect(foo.extra_bars[i].alpha).toBe(base);
+ expect(foo.extra_bars[i].beta).toBe(base + 20);
+ expect(foo.extra_bars[i].gamma).toBe(base + 40);
+ expect(foo.extra_bars[i].type).toBe(type);
+ }
+
+ expect(foo.data.length).toBe(10);
+ for (var i = 0; i < foo.data.length; ++i)
+ expect(foo.data[i]).toBe(foo.data.length - i);
+
+ expect(foo.source).toBe(0xFFFF);
+ }
+
+ // Check that values are set to the defaults if we don't override them.
+ function checkDefaultValues() {
+ var bar = new sample.Bar();
+ expect(bar.alpha).toBe(255);
+ expect(bar.type).toBe(sample.Bar.Type.VERTICAL);
+
+ var foo = new sample.Foo();
+ expect(foo.name).toBe("Fooby");
+ expect(foo.a).toBeTruthy();
+ expect(foo.data).toBeNull();
+
+ var defaults = new sample.DefaultsTest();
+ expect(defaults.a0).toBe(-12);
+ expect(defaults.a1).toBe(sample.kTwelve);
+ expect(defaults.a2).toBe(1234);
+ expect(defaults.a3).toBe(34567);
+ expect(defaults.a4).toBe(123456);
+ expect(defaults.a5).toBe(3456789012);
+ expect(defaults.a6).toBe(-111111111111);
+ // JS doesn't have a 64 bit integer type so this is just checking that the
+ // expected and actual values have the same closest double value.
+ expect(defaults.a7).toBe(9999999999999999999);
+ expect(defaults.a8).toBe(0x12345);
+ expect(defaults.a9).toBe(-0x12345);
+ expect(defaults.a10).toBe(1234);
+ expect(defaults.a11).toBe(true);
+ expect(defaults.a12).toBe(false);
+ expect(defaults.a13).toBe(123.25);
+ expect(defaults.a14).toBe(1234567890.123);
+ expect(defaults.a15).toBe(1E10);
+ expect(defaults.a16).toBe(-1.2E+20);
+ expect(defaults.a17).toBe(1.23E-20);
+ expect(defaults.a20).toBe(sample.Bar.Type.BOTH);
+ expect(defaults.a21).toBeNull();
+ expect(defaults.a22).toBeTruthy();
+ expect(defaults.a22.shape).toBe(imported.Shape.RECTANGLE);
+ expect(defaults.a22.color).toBe(imported2.Color.BLACK);
+ expect(defaults.a21).toBeNull();
+ expect(defaults.a23).toBe(0xFFFFFFFFFFFFFFFF);
+ expect(defaults.a24).toBe(0x123456789);
+ expect(defaults.a25).toBe(-0x123456789);
+ }
+
+ function ServiceImpl() {
+ }
+
+ ServiceImpl.prototype = Object.create(sample.ServiceStub.prototype);
+
+ ServiceImpl.prototype.frobinate = function(foo, baz, port) {
+ checkFoo(foo);
+ expect(baz).toBe(sample.ServiceStub.BazOptions.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.EXTRA, port);
+});
diff --git a/mojo/apps/js/bindings/threading.cc b/mojo/apps/js/bindings/threading.cc
new file mode 100644
index 0000000..64e32fc
--- /dev/null
+++ b/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/mojo/apps/js/bindings/threading.h b/mojo/apps/js/bindings/threading.h
new file mode 100644
index 0000000..ac8e221
--- /dev/null
+++ b/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/mojo/apps/js/content_handler_impl.cc b/mojo/apps/js/content_handler_impl.cc
new file mode 100644
index 0000000..f3dffa7
--- /dev/null
+++ b/mojo/apps/js/content_handler_impl.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.
+
+#include "mojo/apps/js/content_handler_impl.h"
+
+#include "mojo/apps/js/application_delegate_impl.h"
+#include "mojo/apps/js/js_app.h"
+#include "mojo/common/data_pipe_utils.h"
+
+namespace mojo {
+namespace apps {
+
+class ContentHandlerJSApp : public JSApp {
+ public:
+ ContentHandlerJSApp(ApplicationDelegateImpl* app_delegate_impl,
+ const std::string& url,
+ URLResponsePtr content)
+ : JSApp(app_delegate_impl),
+ url_(url),
+ content_(content.Pass()) {
+ }
+
+ virtual bool Load(std::string* source, std::string* file_name) override {
+ *file_name = url_;
+ if (content_.is_null())
+ return false;
+ return common::BlockingCopyToString(content_->body.Pass(), source);
+ }
+
+ private:
+ std::string url_;
+ URLResponsePtr content_;
+};
+
+
+ContentHandlerImpl::ContentHandlerImpl(ApplicationDelegateImpl* app)
+ : app_delegate_impl_(app) {
+}
+
+ContentHandlerImpl::~ContentHandlerImpl() {
+}
+
+void ContentHandlerImpl::OnConnect(
+ const mojo::String& url,
+ URLResponsePtr content,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ scoped_ptr<JSApp> js_app(
+ new ContentHandlerJSApp(app_delegate_impl_,
+ url.To<std::string>(),
+ content.Pass()));
+ app_delegate_impl_->StartJSApp(js_app.Pass());
+}
+
+} // namespace apps
+} // namespace mojo
diff --git a/mojo/apps/js/content_handler_impl.h b/mojo/apps/js/content_handler_impl.h
new file mode 100644
index 0000000..04c2603
--- /dev/null
+++ b/mojo/apps/js/content_handler_impl.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_APPS_JS_CONTENT_HANDLER_IMPL_H_
+#define MOJO_APPS_JS_CONTENT_HANDLER_IMPL_H_
+
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace mojo {
+namespace apps {
+
+class ApplicationDelegateImpl;
+
+// Starts a new JSApp for each OnConnect call().
+class ContentHandlerImpl : public InterfaceImpl<ContentHandler> {
+ public:
+ ContentHandlerImpl(ApplicationDelegateImpl* app_delegate_impl);
+
+ private:
+ virtual ~ContentHandlerImpl();
+ virtual void OnConnect(
+ const mojo::String& url,
+ URLResponsePtr content,
+ InterfaceRequest<ServiceProvider> service_provider) override;
+
+ ApplicationDelegateImpl* app_delegate_impl_;
+};
+
+} // namespace apps
+} // namespace mojo
+
+#endif // MOJO_APPS_JS_CONTENT_HANDLER_IMPL_H_
diff --git a/mojo/apps/js/content_handler_main.cc b/mojo/apps/js/content_handler_main.cc
new file mode 100644
index 0000000..3ea9a3e
--- /dev/null
+++ b/mojo/apps/js/content_handler_main.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 "base/i18n/icu_util.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/apps/js/application_delegate_impl.h"
+#include "mojo/apps/js/content_handler_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace mojo {
+namespace apps {
+
+class ContentHandlerApplicationDelegateImpl : public ApplicationDelegateImpl {
+ public:
+ ContentHandlerApplicationDelegateImpl() : content_handler_factory_(this) {
+ }
+
+ private:
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(&content_handler_factory_);
+ return true;
+ }
+
+ InterfaceFactoryImplWithContext<ContentHandlerImpl, ApplicationDelegateImpl>
+ content_handler_factory_;
+};
+
+} // namespace apps
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ base::i18n::InitializeICU();
+ mojo::ApplicationRunnerChromium runner(
+ new mojo::apps::ContentHandlerApplicationDelegateImpl());
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/apps/js/js_app.cc b/mojo/apps/js/js_app.cc
new file mode 100644
index 0000000..7b400b2
--- /dev/null
+++ b/mojo/apps/js/js_app.cc
@@ -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.
+
+#include "mojo/apps/js/js_app.h"
+
+#include "base/bind.h"
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "mojo/apps/js/application_delegate_impl.h"
+#include "mojo/apps/js/mojo_module.h"
+
+namespace mojo {
+namespace apps {
+
+JSApp::JSApp(ApplicationDelegateImpl* app_delegate_impl)
+ : app_delegate_impl_(app_delegate_impl),
+ thread_("Mojo JS"),
+ app_delegate_impl_task_runner_(
+ base::MessageLoop::current()->task_runner()) {
+ CHECK(on_app_delegate_impl_thread());
+ runner_delegate_.AddBuiltinModule(Mojo::kModuleName,
+ base::Bind(Mojo::GetModule, this));
+}
+
+JSApp::~JSApp() {
+}
+
+bool JSApp::Start() {
+ CHECK(!js_app_task_runner_.get() && on_app_delegate_impl_thread());
+ base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
+ thread_.StartWithOptions(thread_options);
+
+ // TODO(hansmuller): check thread_.StartWithOptions() return value.
+ // TODO(hansmuller): need to funnel Run() failures back to the caller.
+
+ thread_.message_loop()->PostTask(
+ FROM_HERE, base::Bind(&JSApp::Run, base::Unretained(this)));
+ return true;
+}
+
+void JSApp::Quit() {
+ CHECK(on_js_app_thread());
+
+ // The terminate operation is posted to the message_loop so that
+ // the shell_runner isn't destroyed before this JS function returns.
+ thread_.message_loop()->PostTask(
+ FROM_HERE, base::Bind(&JSApp::Terminate, base::Unretained(this)));
+}
+
+Handle JSApp::ConnectToService(const std::string& application_url,
+ const std::string& interface_name) {
+ CHECK(on_js_app_thread());
+ MessagePipe pipe;
+
+ app_delegate_impl_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ApplicationDelegateImpl::ConnectToService,
+ base::Unretained(app_delegate_impl_),
+ base::Passed(pipe.handle1.Pass()),
+ application_url,
+ interface_name));
+
+ return pipe.handle0.release();
+}
+
+void JSApp::Run() {
+ CHECK(!js_app_task_runner_.get() && !on_app_delegate_impl_thread());
+ js_app_task_runner_ = base::MessageLoop::current()->task_runner();
+
+ std::string source;
+ std::string file_name;
+ Load(&source, &file_name); // TODO(hansmuller): handle Load() failure.
+
+ isolate_holder_.reset(new gin::IsolateHolder());
+ isolate_holder_->AddRunMicrotasksObserver();
+
+ shell_runner_.reset(
+ new gin::ShellRunner(&runner_delegate_, isolate_holder_->isolate()));
+
+ gin::Runner::Scope scope(shell_runner_.get());
+ shell_runner_->Run(source.c_str(), file_name.c_str());
+}
+
+void JSApp::Terminate() {
+ isolate_holder_->RemoveRunMicrotasksObserver();
+ shell_runner_.reset(nullptr);
+
+ // This JSApp's thread must be stopped on the thread that started it. Ask the
+ // app_delegate_impl_ to erase its AppVector entry for this app, which
+ // implicitly destroys this JSApp and stops its thread.
+ app_delegate_impl_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ApplicationDelegateImpl::QuitJSApp,
+ base::Unretained(app_delegate_impl_),
+ base::Unretained(this)));
+}
+
+bool JSApp::on_app_delegate_impl_thread() const {
+ return app_delegate_impl_task_runner_.get() &&
+ app_delegate_impl_task_runner_.get() ==
+ base::MessageLoop::current()->task_runner().get();
+}
+
+bool JSApp::on_js_app_thread() const {
+ return js_app_task_runner_.get() &&
+ js_app_task_runner_.get() ==
+ base::MessageLoop::current()->task_runner().get();
+}
+
+} // namespace apps
+} // namespace mojo
diff --git a/mojo/apps/js/js_app.h b/mojo/apps/js/js_app.h
new file mode 100644
index 0000000..6bbaed4
--- /dev/null
+++ b/mojo/apps/js/js_app.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_APPS_JS_JS_APP_H_
+#define MOJO_APPS_JS_JS_APP_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+#include "gin/public/isolate_holder.h"
+#include "gin/shell_runner.h"
+#include "mojo/apps/js/mojo_runner_delegate.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace mojo {
+namespace apps {
+
+class JSApp;
+class ApplicationDelegateImpl;
+
+// Each JavaScript app started by content handler runs on its own thread and
+// in its own V8 isolate. This class represents one running JS app.
+
+class JSApp {
+ public:
+ JSApp(ApplicationDelegateImpl* app_delegate_impl);
+ virtual ~JSApp();
+
+ // Launch this app on a new thread. This method can be called on any thread.
+ // This method causes Load() and then Run() to run on a new thread.
+ bool Start();
+
+
+ // Subclasses must return the JS source code for this app's main script and
+ // the filename or URL that identifies the script's origin. This method will
+ // be called from this app's thread.
+ virtual bool Load(std::string* source, std::string* file_name) = 0;
+
+ // Called by the JS mojo module to quit this JS app. See mojo_module.cc.
+ void Quit();
+
+ // Called by the JS mojo module to connect to a Mojo service.
+ Handle ConnectToService(const std::string& application_url,
+ const std::string& interface_name);
+
+ private:
+ void Run();
+ void Terminate();
+
+ // Used to CHECK that we're running on the correct thread.
+ bool on_app_delegate_impl_thread() const;
+ bool on_js_app_thread() const;
+
+ ApplicationDelegateImpl* app_delegate_impl_;
+ base::Thread thread_;
+ scoped_refptr<base::SingleThreadTaskRunner> app_delegate_impl_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> js_app_task_runner_;
+ MojoRunnerDelegate runner_delegate_;
+ scoped_ptr<gin::IsolateHolder> isolate_holder_;
+ scoped_ptr<gin::ShellRunner> shell_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(JSApp);
+};
+
+} // namespace apps
+} // namespace mojo
+
+#endif // MOJO_APPS_JS_JS_APP_H_
diff --git a/mojo/apps/js/main.js b/mojo/apps/js/main.js
new file mode 100644
index 0000000..c8eaac5
--- /dev/null
+++ b/mojo/apps/js/main.js
@@ -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.
+
+// This trivial app just loads "cnn.com" using the Mojo Network and URLLoader
+// services and then prints a brief summary of the response. It's intended to
+// be run using the mojo_js content handler and assumes that this source file
+// is specified as a URL. For example:
+//
+// (cd YOUR_DIR/mojo/apps/js; python -m SimpleHTTPServer ) &
+// mojo_shell --content-handlers=application/javascript,mojo://mojo_js \
+// http://localhost:8000/test.js
+
+define("test", [
+ "mojo/public/js/bindings/core",
+ "mojo/public/js/bindings/connection",
+ "mojo/public/js/bindings/support",
+ "mojo/services/public/interfaces/network/network_service.mojom",
+ "mojo/services/public/interfaces/network/url_loader.mojom",
+ "mojo",
+ "console"
+], function(core, connection, support, net, loader, mojo, console) {
+
+ var netServiceHandle = mojo.connectToService(
+ "mojo:mojo_network_service", "mojo::NetworkService");
+ var netConnection = new connection.Connection(
+ netServiceHandle, net.NetworkServiceStub, net.NetworkServiceProxy);
+
+ var urlLoaderPipe = new core.createMessagePipe();
+ netConnection.remote.createURLLoader(urlLoaderPipe.handle1);
+ var urlLoaderConnection = new connection.Connection(
+ urlLoaderPipe.handle0, loader.URLLoaderStub, loader.URLLoaderProxy);
+
+ var urlRequest = new loader.URLRequest();
+ urlRequest.url = "http://www.cnn.com";
+ urlRequest.method = "GET";
+ urlRequest.auto_follow_redirects = true;
+
+ var urlRequestPromise = urlLoaderConnection.remote.start(urlRequest);
+ urlRequestPromise.then(function(result) {
+ for(var key in result.response)
+ console.log(key + " => " + result.response[key]);
+ var drainDataPromise = core.drainData(result.response.body);
+ drainDataPromise.then(function(result) {
+ console.log("read " + result.buffer.byteLength + " bytes");
+ }).then(function() {
+ mojo.quit();
+ });
+ });
+});
diff --git a/mojo/apps/js/mojo_module.cc b/mojo/apps/js/mojo_module.cc
new file mode 100644
index 0000000..d3c0f76
--- /dev/null
+++ b/mojo/apps/js/mojo_module.cc
@@ -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.
+
+#include "mojo/apps/js/mojo_module.h"
+
+#include "gin/arguments.h"
+#include "gin/converter.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "mojo/apps/js/js_app.h"
+#include "mojo/apps/js/mojo_module.h"
+#include "mojo/bindings/js/handle.h"
+#include "mojo/common/data_pipe_utils.h"
+
+namespace mojo {
+namespace apps {
+
+namespace {
+
+gin::WrapperInfo g_wrapper_info = {gin::kEmbedderNativeGin};
+
+} // namespace
+
+const char Mojo::kModuleName[] = "mojo";
+
+v8::Local<v8::Value> Mojo::GetModule(JSApp* js_app, 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("connectToService",
+ base::Bind(&JSApp::ConnectToService,
+ base::Unretained(js_app)))
+ .SetMethod("quit",
+ base::Bind(&JSApp::Quit, base::Unretained(js_app)))
+ .Build();
+ data->SetObjectTemplate(&g_wrapper_info, templ);
+ }
+
+ return templ->NewInstance();
+}
+
+} // namespace apps
+} // namespace mojo
diff --git a/mojo/apps/js/mojo_module.h b/mojo/apps/js/mojo_module.h
new file mode 100644
index 0000000..cb4a73a
--- /dev/null
+++ b/mojo/apps/js/mojo_module.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_APPS_JS_MOJO_MODULE_H_
+#define MOJO_APPS_JS_MOJO_MODULE_H_
+
+#include "gin/gin_export.h"
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace apps {
+
+class JSApp;
+
+class Mojo {
+ public:
+ static const char kModuleName[];
+ static v8::Local<v8::Value> GetModule(JSApp* js_app, v8::Isolate* isolate);
+};
+
+} // namespace apps
+} // namespace mojo
+
+#endif // MOJO_APPS_JS_MOJO_MODULE_H_
diff --git a/mojo/apps/js/mojo_runner_delegate.cc b/mojo/apps/js/mojo_runner_delegate.cc
new file mode 100644
index 0000000..8bec392
--- /dev/null
+++ b/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/mojo/apps/js/mojo_runner_delegate.h b/mojo/apps/js/mojo_runner_delegate.h
new file mode 100644
index 0000000..8a7b448
--- /dev/null
+++ b/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/macros.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/mojo/apps/js/standalone_main.cc b/mojo/apps/js/standalone_main.cc
new file mode 100644
index 0000000..efdfcb8
--- /dev/null
+++ b/mojo/apps/js/standalone_main.cc
@@ -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.
+
+#include "base/files/file_util.h"
+#include "base/i18n/icu_util.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/apps/js/application_delegate_impl.h"
+#include "mojo/apps/js/js_app.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+
+namespace mojo {
+namespace apps {
+
+class StandaloneJSApp : public JSApp {
+ public:
+ StandaloneJSApp(ApplicationDelegateImpl* app_delegate_impl,
+ const base::FilePath& path)
+ : JSApp(app_delegate_impl),
+ path_(path) {
+ }
+
+ virtual bool Load(std::string* source, std::string* file_name) override {
+ *file_name = path_.AsUTF8Unsafe();
+ return ReadFileToString(path_, source);
+ }
+
+ private:
+ base::FilePath path_;
+};
+
+class StandaloneApplicationDelegateImpl : public ApplicationDelegateImpl {
+ private:
+ virtual void Initialize(ApplicationImpl* app) override {
+ ApplicationDelegateImpl::Initialize(app);
+
+ for (size_t i = 1; i < app->args().size(); i++) {
+ base::FilePath path(base::FilePath::FromUTF8Unsafe(app->args()[i]));
+ scoped_ptr<JSApp> js_app(new StandaloneJSApp(this, path));
+ StartJSApp(js_app.Pass());
+ }
+ }
+};
+
+} // namespace apps
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ base::i18n::InitializeICU();
+ mojo::ApplicationRunnerChromium runner(
+ new mojo::apps::StandaloneApplicationDelegateImpl());
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/apps/js/test/BUILD.gn b/mojo/apps/js/test/BUILD.gn
new file mode 100644
index 0000000..2b09e71
--- /dev/null
+++ b/mojo/apps/js/test/BUILD.gn
@@ -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("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_apps.gypi:mojo_apps_js_unittests
+test("mojo_apps_js_unittests") {
+ deps = [
+ ":js_to_cpp_bindings",
+ "//gin:gin_test",
+ "//mojo/apps/js",
+ "//mojo/common",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/edk/test:test_support",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ ]
+
+ sources = [
+ "handle_unittest.cc",
+ "js_to_cpp_unittest.cc",
+ "run_apps_js_tests.cc",
+ ]
+}
+
+mojom("js_to_cpp_bindings") {
+ sources = [ "js_to_cpp.mojom" ]
+}
diff --git a/mojo/apps/js/test/handle_unittest.cc b/mojo/apps/js/test/handle_unittest.cc
new file mode 100644
index 0000000..ec089c9
--- /dev/null
+++ b/mojo/apps/js/test/handle_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "mojo/bindings/js/handle.h"
+#include "mojo/bindings/js/handle_close_observer.h"
+#include "mojo/public/cpp/system/core.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+
+class HandleWrapperTest : public testing::Test,
+ public gin::HandleCloseObserver {
+ public:
+ HandleWrapperTest() : closes_observed_(0) {}
+
+ virtual void OnWillCloseHandle() override { closes_observed_++; }
+
+ protected:
+ int closes_observed_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HandleWrapperTest);
+};
+
+class TestHandleWrapper : public gin::HandleWrapper {
+ public:
+ explicit TestHandleWrapper(MojoHandle handle) : HandleWrapper(handle) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestHandleWrapper);
+};
+
+// Test that calling Close() on a HandleWrapper for an invalid handle does not
+// notify observers.
+TEST_F(HandleWrapperTest, CloseWithInvalidHandle) {
+ {
+ TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ wrapper.Close();
+ EXPECT_EQ(0, closes_observed_);
+ }
+ EXPECT_EQ(0, closes_observed_);
+}
+
+// Test that destroying a HandleWrapper for an invalid handle does not notify
+// observers.
+TEST_F(HandleWrapperTest, DestroyWithInvalidHandle) {
+ {
+ TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ }
+ EXPECT_EQ(0, closes_observed_);
+}
+
+// Test that calling Close on a HandleWrapper for a valid handle notifies
+// observers once.
+TEST_F(HandleWrapperTest, CloseWithValidHandle) {
+ {
+ mojo::MessagePipe pipe;
+ TestHandleWrapper wrapper(pipe.handle0.release().value());
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ wrapper.Close();
+ EXPECT_EQ(1, closes_observed_);
+ // Check that calling close again doesn't notify observers.
+ wrapper.Close();
+ EXPECT_EQ(1, closes_observed_);
+ }
+ // Check that destroying a closed HandleWrapper doesn't notify observers.
+ EXPECT_EQ(1, closes_observed_);
+}
+
+// Test that destroying a HandleWrapper for a valid handle notifies observers.
+TEST_F(HandleWrapperTest, DestroyWithValidHandle) {
+ {
+ mojo::MessagePipe pipe;
+ TestHandleWrapper wrapper(pipe.handle0.release().value());
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ }
+ EXPECT_EQ(1, closes_observed_);
+}
+
+} // namespace js
+} // namespace mojo
diff --git a/mojo/apps/js/test/hexdump.js b/mojo/apps/js/test/hexdump.js
new file mode 100644
index 0000000..b36c47f
--- /dev/null
+++ b/mojo/apps/js/test/hexdump.js
@@ -0,0 +1,34 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define(function() {
+ function hexify(value, length) {
+ var hex = value.toString(16);
+ while (hex.length < length)
+ hex = "0" + hex;
+ return hex;
+ }
+
+ function dumpArray(bytes) {
+ var dumped = "";
+ for (var i = 0; i < bytes.length; ++i) {
+ dumped += hexify(bytes[i], 2);
+
+ if (i % 16 == 15) {
+ dumped += "\n";
+ continue;
+ }
+
+ if (i % 2 == 1)
+ dumped += " ";
+ if (i % 8 == 7)
+ dumped += " ";
+ }
+ return dumped;
+ }
+
+ var exports = {};
+ exports.dumpArray = dumpArray;
+ return exports;
+});
diff --git a/mojo/apps/js/test/js_to_cpp.mojom b/mojo/apps/js/test/js_to_cpp.mojom
new file mode 100644
index 0000000..9914e9b
--- /dev/null
+++ b/mojo/apps/js/test/js_to_cpp.mojom
@@ -0,0 +1,55 @@
+module js_to_cpp {
+
+// This struct encompasses all of the basic types, so that they
+// may be sent from C++ to JS and back for validation.
+struct EchoArgs {
+ int64 si64;
+ int32 si32;
+ int16 si16;
+ int8 si8;
+ uint64 ui64;
+ uint32 ui32;
+ uint16 ui16;
+ uint8 ui8;
+ float float_val;
+ float float_inf;
+ float float_nan;
+ double double_val;
+ double double_inf;
+ double double_nan;
+ string? name;
+ array<string>? string_array;
+ handle<message_pipe>? message_handle;
+ handle<data_pipe_consumer>? data_handle;
+};
+
+struct EchoArgsList {
+ EchoArgsList? next;
+ EchoArgs? item;
+};
+
+// Note: For messages which control test flow, pick numbers that are unlikely
+// to be hit as a result of our deliberate corruption of response messages.
+interface CppSide {
+ // Sent for all tests to notify that the JS side is now ready.
+ StartTest@88888888();
+
+ // Indicates end for echo, bit-flip, and back-pointer tests.
+ TestFinished@99999999();
+
+ // Responses from specific tests.
+ PingResponse();
+ EchoResponse(EchoArgsList list);
+ BitFlipResponse(EchoArgsList arg);
+ BackPointerResponse(EchoArgsList arg);
+};
+
+[Client=CppSide]
+interface JsSide {
+ Ping();
+ Echo(int32 numIterations, EchoArgs arg);
+ BitFlip(EchoArgs arg);
+ BackPointer(EchoArgs arg);
+};
+
+}
diff --git a/mojo/apps/js/test/js_to_cpp_unittest.cc b/mojo/apps/js/test/js_to_cpp_unittest.cc
new file mode 100644
index 0000000..44526a4
--- /dev/null
+++ b/mojo/apps/js/test/js_to_cpp_unittest.cc
@@ -0,0 +1,458 @@
+// Copyright 2014 The Chromium 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/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gin/array_buffer.h"
+#include "gin/public/isolate_holder.h"
+#include "mojo/apps/js/mojo_runner_delegate.h"
+#include "mojo/apps/js/test/js_to_cpp.mojom.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+
+// Global value updated by some checks to prevent compilers from optimizing
+// reads out of existence.
+uint32 g_waste_accumulator = 0;
+
+namespace {
+
+// Negative numbers with different values in each byte, the last of
+// which can survive promotion to double and back.
+const int8 kExpectedInt8Value = -65;
+const int16 kExpectedInt16Value = -16961;
+const int32 kExpectedInt32Value = -1145258561;
+const int64 kExpectedInt64Value = -77263311946305LL;
+
+// Positive numbers with different values in each byte, the last of
+// which can survive promotion to double and back.
+const uint8 kExpectedUInt8Value = 65;
+const uint16 kExpectedUInt16Value = 16961;
+const uint32 kExpectedUInt32Value = 1145258561;
+const uint64 kExpectedUInt64Value = 77263311946305LL;
+
+// Double/float values, including special case constants.
+const double kExpectedDoubleVal = 3.14159265358979323846;
+const double kExpectedDoubleInf = std::numeric_limits<double>::infinity();
+const double kExpectedDoubleNan = std::numeric_limits<double>::quiet_NaN();
+const float kExpectedFloatVal = static_cast<float>(kExpectedDoubleVal);
+const float kExpectedFloatInf = std::numeric_limits<float>::infinity();
+const float kExpectedFloatNan = std::numeric_limits<float>::quiet_NaN();
+
+// NaN has the property that it is not equal to itself.
+#define EXPECT_NAN(x) EXPECT_NE(x, x)
+
+bool IsRunningOnIsolatedBot() {
+ // 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_interfaces.mojom"));
+ if (!base::PathExists(test_file_path)) {
+ LOG(WARNING) << "Mojom binding files don't exist. Skipping the test.";
+ return true;
+ }
+ return false;
+}
+
+void CheckDataPipe(MojoHandle data_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadData(
+ data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(64u, buffer_size);
+ for (int i = 0; i < 64; ++i) {
+ EXPECT_EQ(i, buffer[i]);
+ }
+}
+
+void CheckMessagePipe(MojoHandle message_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadMessage(
+ message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(64u, buffer_size);
+ for (int i = 0; i < 64; ++i) {
+ EXPECT_EQ(255 - i, buffer[i]);
+ }
+}
+
+js_to_cpp::EchoArgsPtr BuildSampleEchoArgs() {
+ js_to_cpp::EchoArgsPtr args(js_to_cpp::EchoArgs::New());
+ args->si64 = kExpectedInt64Value;
+ args->si32 = kExpectedInt32Value;
+ args->si16 = kExpectedInt16Value;
+ args->si8 = kExpectedInt8Value;
+ args->ui64 = kExpectedUInt64Value;
+ args->ui32 = kExpectedUInt32Value;
+ args->ui16 = kExpectedUInt16Value;
+ args->ui8 = kExpectedUInt8Value;
+ args->float_val = kExpectedFloatVal;
+ args->float_inf = kExpectedFloatInf;
+ args->float_nan = kExpectedFloatNan;
+ args->double_val = kExpectedDoubleVal;
+ args->double_inf = kExpectedDoubleInf;
+ args->double_nan = kExpectedDoubleNan;
+ args->name = "coming";
+ Array<String> string_array(3);
+ string_array[0] = "one";
+ string_array[1] = "two";
+ string_array[2] = "three";
+ args->string_array = string_array.Pass();
+ return args.Pass();
+}
+
+void CheckSampleEchoArgs(const js_to_cpp::EchoArgs& arg) {
+ EXPECT_EQ(kExpectedInt64Value, arg.si64);
+ EXPECT_EQ(kExpectedInt32Value, arg.si32);
+ EXPECT_EQ(kExpectedInt16Value, arg.si16);
+ EXPECT_EQ(kExpectedInt8Value, arg.si8);
+ EXPECT_EQ(kExpectedUInt64Value, arg.ui64);
+ EXPECT_EQ(kExpectedUInt32Value, arg.ui32);
+ EXPECT_EQ(kExpectedUInt16Value, arg.ui16);
+ EXPECT_EQ(kExpectedUInt8Value, arg.ui8);
+ EXPECT_EQ(kExpectedFloatVal, arg.float_val);
+ EXPECT_EQ(kExpectedFloatInf, arg.float_inf);
+ EXPECT_NAN(arg.float_nan);
+ EXPECT_EQ(kExpectedDoubleVal, arg.double_val);
+ EXPECT_EQ(kExpectedDoubleInf, arg.double_inf);
+ EXPECT_NAN(arg.double_nan);
+ EXPECT_EQ(std::string("coming"), arg.name.get());
+ EXPECT_EQ(std::string("one"), arg.string_array[0].get());
+ EXPECT_EQ(std::string("two"), arg.string_array[1].get());
+ EXPECT_EQ(std::string("three"), arg.string_array[2].get());
+ CheckDataPipe(arg.data_handle.get().value());
+ CheckMessagePipe(arg.message_handle.get().value());
+}
+
+void CheckSampleEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) {
+ if (list.is_null())
+ return;
+ CheckSampleEchoArgs(*list->item);
+ CheckSampleEchoArgsList(list->next);
+}
+
+// More forgiving checks are needed in the face of potentially corrupt
+// messages. The values don't matter so long as all accesses are within
+// bounds.
+void CheckCorruptedString(const String& arg) {
+ if (arg.is_null())
+ return;
+ for (size_t i = 0; i < arg.size(); ++i)
+ g_waste_accumulator += arg[i];
+}
+
+void CheckCorruptedStringArray(const Array<String>& string_array) {
+ if (string_array.is_null())
+ return;
+ for (size_t i = 0; i < string_array.size(); ++i)
+ CheckCorruptedString(string_array[i]);
+}
+
+void CheckCorruptedDataPipe(MojoHandle data_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadData(
+ data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE);
+ if (result != MOJO_RESULT_OK)
+ return;
+ for (uint32_t i = 0; i < buffer_size; ++i)
+ g_waste_accumulator += buffer[i];
+}
+
+void CheckCorruptedMessagePipe(MojoHandle message_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadMessage(
+ message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
+ if (result != MOJO_RESULT_OK)
+ return;
+ for (uint32_t i = 0; i < buffer_size; ++i)
+ g_waste_accumulator += buffer[i];
+}
+
+void CheckCorruptedEchoArgs(const js_to_cpp::EchoArgsPtr& arg) {
+ if (arg.is_null())
+ return;
+ CheckCorruptedString(arg->name);
+ CheckCorruptedStringArray(arg->string_array);
+ if (arg->data_handle.is_valid())
+ CheckCorruptedDataPipe(arg->data_handle.get().value());
+ if (arg->message_handle.is_valid())
+ CheckCorruptedMessagePipe(arg->message_handle.get().value());
+}
+
+void CheckCorruptedEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) {
+ if (list.is_null())
+ return;
+ CheckCorruptedEchoArgs(list->item);
+ CheckCorruptedEchoArgsList(list->next);
+}
+
+// Base Provider implementation class. It's expected that tests subclass and
+// override the appropriate Provider functions. When test is done quit the
+// run_loop().
+class CppSideConnection : public js_to_cpp::CppSide {
+ public:
+ CppSideConnection() :
+ run_loop_(NULL),
+ js_side_(NULL),
+ mishandled_messages_(0) {
+ }
+ virtual ~CppSideConnection() {}
+
+ void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
+ base::RunLoop* run_loop() { return run_loop_; }
+
+ void set_js_side(js_to_cpp::JsSide* js_side) { js_side_ = js_side; }
+ js_to_cpp::JsSide* js_side() { return js_side_; }
+
+ // js_to_cpp::CppSide:
+ virtual void StartTest() override {
+ NOTREACHED();
+ }
+
+ virtual void TestFinished() override {
+ NOTREACHED();
+ }
+
+ virtual void PingResponse() override {
+ mishandled_messages_ += 1;
+ }
+
+ virtual void EchoResponse(js_to_cpp::EchoArgsListPtr list) override {
+ mishandled_messages_ += 1;
+ }
+
+ virtual void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override {
+ mishandled_messages_ += 1;
+ }
+
+ virtual void BackPointerResponse(
+ js_to_cpp::EchoArgsListPtr list) override {
+ mishandled_messages_ += 1;
+ }
+
+ protected:
+ base::RunLoop* run_loop_;
+ js_to_cpp::JsSide* js_side_;
+ int mishandled_messages_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CppSideConnection);
+};
+
+// Trivial test to verify a message sent from JS is received.
+class PingCppSideConnection : public CppSideConnection {
+ public:
+ PingCppSideConnection() : got_message_(false) {}
+ virtual ~PingCppSideConnection() {}
+
+ // js_to_cpp::CppSide:
+ virtual void StartTest() override {
+ js_side_->Ping();
+ }
+
+ virtual void PingResponse() override {
+ got_message_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return got_message_ && !mishandled_messages_;
+ }
+
+ private:
+ bool got_message_;
+ DISALLOW_COPY_AND_ASSIGN(PingCppSideConnection);
+};
+
+// Test that parameters are passed with correct values.
+class EchoCppSideConnection : public CppSideConnection {
+ public:
+ EchoCppSideConnection() :
+ message_count_(0),
+ termination_seen_(false) {
+ }
+ virtual ~EchoCppSideConnection() {}
+
+ // js_to_cpp::CppSide:
+ virtual void StartTest() override {
+ js_side_->Echo(kExpectedMessageCount, BuildSampleEchoArgs());
+ }
+
+ virtual void EchoResponse(js_to_cpp::EchoArgsListPtr list) override {
+ const js_to_cpp::EchoArgsPtr& special_arg = list->item;
+ message_count_ += 1;
+ EXPECT_EQ(-1, special_arg->si64);
+ EXPECT_EQ(-1, special_arg->si32);
+ EXPECT_EQ(-1, special_arg->si16);
+ EXPECT_EQ(-1, special_arg->si8);
+ EXPECT_EQ(std::string("going"), special_arg->name.To<std::string>());
+ CheckSampleEchoArgsList(list->next);
+ }
+
+ virtual void TestFinished() override {
+ termination_seen_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return termination_seen_ &&
+ !mishandled_messages_ &&
+ message_count_ == kExpectedMessageCount;
+ }
+
+ private:
+ static const int kExpectedMessageCount = 10;
+ int message_count_;
+ bool termination_seen_;
+ DISALLOW_COPY_AND_ASSIGN(EchoCppSideConnection);
+};
+
+// Test that corrupted messages don't wreak havoc.
+class BitFlipCppSideConnection : public CppSideConnection {
+ public:
+ BitFlipCppSideConnection() : termination_seen_(false) {}
+ virtual ~BitFlipCppSideConnection() {}
+
+ // js_to_cpp::CppSide:
+ virtual void StartTest() override {
+ js_side_->BitFlip(BuildSampleEchoArgs());
+ }
+
+ virtual void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override {
+ CheckCorruptedEchoArgsList(list);
+ }
+
+ virtual void TestFinished() override {
+ termination_seen_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return termination_seen_;
+ }
+
+ private:
+ bool termination_seen_;
+ DISALLOW_COPY_AND_ASSIGN(BitFlipCppSideConnection);
+};
+
+// Test that severely random messages don't wreak havoc.
+class BackPointerCppSideConnection : public CppSideConnection {
+ public:
+ BackPointerCppSideConnection() : termination_seen_(false) {}
+ virtual ~BackPointerCppSideConnection() {}
+
+ // js_to_cpp::CppSide:
+ virtual void StartTest() override {
+ js_side_->BackPointer(BuildSampleEchoArgs());
+ }
+
+ virtual void BackPointerResponse(
+ js_to_cpp::EchoArgsListPtr list) override {
+ CheckCorruptedEchoArgsList(list);
+ }
+
+ virtual void TestFinished() override {
+ termination_seen_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return termination_seen_;
+ }
+
+ private:
+ bool termination_seen_;
+ DISALLOW_COPY_AND_ASSIGN(BackPointerCppSideConnection);
+};
+
+} // namespace
+
+class JsToCppTest : public testing::Test {
+ public:
+ JsToCppTest() {}
+
+ void RunTest(const std::string& test, CppSideConnection* cpp_side) {
+ cpp_side->set_run_loop(&run_loop_);
+
+ MessagePipe pipe;
+ js_to_cpp::JsSidePtr js_side =
+ MakeProxy<js_to_cpp::JsSide>(pipe.handle0.Pass());
+ js_side.set_client(cpp_side);
+
+ js_side.internal_state()->router_for_testing()->EnableTestingMode();
+
+ cpp_side->set_js_side(js_side.get());
+
+ gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode,
+ gin::ArrayBufferAllocator::SharedInstance());
+ gin::IsolateHolder instance;
+ apps::MojoRunnerDelegate delegate;
+ gin::ShellRunner runner(&delegate, instance.isolate());
+ delegate.Start(&runner, pipe.handle1.release().value(), test);
+
+ run_loop_.Run();
+ }
+
+ private:
+ base::ShadowingAtExitManager at_exit_;
+ base::MessageLoop loop;
+ base::RunLoop run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsToCppTest);
+};
+
+TEST_F(JsToCppTest, Ping) {
+ if (IsRunningOnIsolatedBot())
+ return;
+
+ PingCppSideConnection cpp_side_connection;
+ RunTest("mojo/apps/js/test/js_to_cpp_unittest", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, Echo) {
+ if (IsRunningOnIsolatedBot())
+ return;
+
+ EchoCppSideConnection cpp_side_connection;
+ RunTest("mojo/apps/js/test/js_to_cpp_unittest", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, BitFlip) {
+ if (IsRunningOnIsolatedBot())
+ return;
+
+ BitFlipCppSideConnection cpp_side_connection;
+ RunTest("mojo/apps/js/test/js_to_cpp_unittest", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, BackPointer) {
+ if (IsRunningOnIsolatedBot())
+ return;
+
+ BackPointerCppSideConnection cpp_side_connection;
+ RunTest("mojo/apps/js/test/js_to_cpp_unittest", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+} // namespace js
+} // namespace mojo
diff --git a/mojo/apps/js/test/js_to_cpp_unittest.js b/mojo/apps/js/test/js_to_cpp_unittest.js
new file mode 100644
index 0000000..b1073c8
--- /dev/null
+++ b/mojo/apps/js/test/js_to_cpp_unittest.js
@@ -0,0 +1,220 @@
+// Copyright 2014 The Chromium 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/apps/js/test/js_to_cpp_unittest', [
+ 'console',
+ 'mojo/apps/js/test/js_to_cpp.mojom',
+ 'mojo/public/js/bindings/connection',
+ 'mojo/public/js/bindings/connector',
+ 'mojo/public/js/bindings/core',
+], function (console, jsToCpp, connection, connector, core) {
+ var retainedConnection;
+ var sampleData;
+ var sampleMessage;
+ var BAD_VALUE = 13;
+ var DATA_PIPE_PARAMS = {
+ flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ elementNumBytes: 1,
+ capacityNumBytes: 64
+ };
+
+ function JsSideConnection(cppSide) {
+ this.cppSide_ = cppSide;
+ cppSide.startTest();
+ }
+
+ JsSideConnection.prototype = Object.create(jsToCpp.JsSideStub.prototype);
+
+ JsSideConnection.prototype.ping = function (arg) {
+ this.cppSide_.pingResponse();
+ };
+
+ JsSideConnection.prototype.echo = function (numIterations, arg) {
+ var dataPipe1;
+ var dataPipe2;
+ var i;
+ var messagePipe1;
+ var messagePipe2;
+ var specialArg;
+
+ // Ensure expected negative values are negative.
+ if (arg.si64 > 0)
+ arg.si64 = BAD_VALUE;
+
+ if (arg.si32 > 0)
+ arg.si32 = BAD_VALUE;
+
+ if (arg.si16 > 0)
+ arg.si16 = BAD_VALUE;
+
+ if (arg.si8 > 0)
+ arg.si8 = BAD_VALUE;
+
+ for (i = 0; i < numIterations; ++i) {
+ dataPipe1 = core.createDataPipe(DATA_PIPE_PARAMS);
+ dataPipe2 = core.createDataPipe(DATA_PIPE_PARAMS);
+ messagePipe1 = core.createMessagePipe();
+ messagePipe2 = core.createMessagePipe();
+
+ arg.data_handle = dataPipe1.consumerHandle;
+ arg.message_handle = messagePipe1.handle1;
+
+ specialArg = new jsToCpp.EchoArgs();
+ specialArg.si64 = -1;
+ specialArg.si32 = -1;
+ specialArg.si16 = -1;
+ specialArg.si8 = -1;
+ specialArg.name = 'going';
+ specialArg.data_handle = dataPipe2.consumerHandle;
+ specialArg.message_handle = messagePipe2.handle1;
+
+ writeDataPipe(dataPipe1, sampleData);
+ writeDataPipe(dataPipe2, sampleData);
+ writeMessagePipe(messagePipe1, sampleMessage);
+ writeMessagePipe(messagePipe2, sampleMessage);
+
+ this.cppSide_.echoResponse(createEchoArgsList(specialArg, arg));
+
+ core.close(dataPipe1.producerHandle);
+ core.close(dataPipe2.producerHandle);
+ core.close(messagePipe1.handle0);
+ core.close(messagePipe2.handle0);
+ }
+ this.cppSide_.testFinished();
+ };
+
+ JsSideConnection.prototype.bitFlip = function (arg) {
+ var iteration = 0;
+ var dataPipe;
+ var messagePipe;
+ var proto = connector.Connector.prototype;
+ var stopSignalled = false;
+
+ proto.realAccept = proto.accept;
+ proto.accept = function (message) {
+ var offset = iteration / 8;
+ var mask;
+ var value;
+ if (offset < message.buffer.arrayBuffer.byteLength) {
+ mask = 1 << (iteration % 8);
+ value = message.buffer.getUint8(offset) ^ mask;
+ message.buffer.setUint8(offset, value);
+ return this.realAccept(message);
+ }
+ stopSignalled = true;
+ return false;
+ };
+
+ while (!stopSignalled) {
+ dataPipe = core.createDataPipe(DATA_PIPE_PARAMS);
+ messagePipe = core.createMessagePipe();
+ writeDataPipe(dataPipe, sampleData);
+ writeMessagePipe(messagePipe, sampleMessage);
+ arg.data_handle = dataPipe.consumerHandle;
+ arg.message_handle = messagePipe.handle1;
+
+ this.cppSide_.bitFlipResponse(createEchoArgsList(arg));
+
+ core.close(dataPipe.producerHandle);
+ core.close(messagePipe.handle0);
+ iteration += 1;
+ }
+
+ proto.accept = proto.realAccept;
+ proto.realAccept = null;
+ this.cppSide_.testFinished();
+ };
+
+ JsSideConnection.prototype.backPointer = function (arg) {
+ var iteration = 0;
+ var dataPipe;
+ var messagePipe;
+ var proto = connector.Connector.prototype;
+ var stopSignalled = false;
+
+ proto.realAccept = proto.accept;
+ proto.accept = function (message) {
+ var delta = 8 * (1 + iteration % 32);
+ var offset = 8 * ((iteration / 32) | 0);
+ if (offset < message.buffer.arrayBuffer.byteLength - 4) {
+ message.buffer.dataView.setUint32(offset, 0x100000000 - delta, true);
+ message.buffer.dataView.setUint32(offset + 4, 0xffffffff, true);
+ return this.realAccept(message);
+ }
+ stopSignalled = true;
+ return false;
+ };
+
+ while (!stopSignalled) {
+ dataPipe = core.createDataPipe(DATA_PIPE_PARAMS);
+ messagePipe = core.createMessagePipe();
+ writeDataPipe(dataPipe, sampleData);
+ writeMessagePipe(messagePipe, sampleMessage);
+ arg.data_handle = dataPipe.consumerHandle;
+ arg.message_handle = messagePipe.handle1;
+
+ this.cppSide_.backPointerResponse(createEchoArgsList(arg));
+
+ core.close(dataPipe.producerHandle);
+ core.close(messagePipe.handle0);
+ iteration += 1;
+ }
+
+ proto.accept = proto.realAccept;
+ proto.realAccept = null;
+ this.cppSide_.testFinished();
+ };
+
+ function writeDataPipe(pipe, data) {
+ var writeResult = core.writeData(
+ pipe.producerHandle, data, core.WRITE_DATA_FLAG_ALL_OR_NONE);
+
+ if (writeResult.result != core.RESULT_OK) {
+ console.log('ERROR: Data pipe write result was ' + writeResult.result);
+ return false;
+ }
+ if (writeResult.numBytes != data.length) {
+ console.log('ERROR: Data pipe write length was ' + writeResult.numBytes);
+ return false;
+ }
+ return true;
+ }
+
+ function writeMessagePipe(pipe, arrayBuffer) {
+ var result = core.writeMessage(pipe.handle0, arrayBuffer, [], 0);
+ if (result != core.RESULT_OK) {
+ console.log('ERROR: Message pipe write result was ' + result);
+ return false;
+ }
+ return true;
+ }
+
+ function createEchoArgsListElement(item, next) {
+ var list = new jsToCpp.EchoArgsList();
+ list.item = item;
+ list.next = next;
+ return list;
+ }
+
+ function createEchoArgsList() {
+ var genuineArray = Array.prototype.slice.call(arguments);
+ return genuineArray.reduceRight(function (previous, current) {
+ return createEchoArgsListElement(current, previous);
+ }, null);
+ }
+
+ return function(handle) {
+ var i;
+ sampleData = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes);
+ for (i = 0; i < sampleData.length; ++i) {
+ sampleData[i] = i;
+ }
+ sampleMessage = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes);
+ for (i = 0; i < sampleMessage.length; ++i) {
+ sampleMessage[i] = 255 - i;
+ }
+ retainedConnection = new connection.Connection(handle, JsSideConnection,
+ jsToCpp.CppSideProxy);
+ };
+});
diff --git a/mojo/apps/js/test/run_apps_js_tests.cc b/mojo/apps/js/test/run_apps_js_tests.cc
new file mode 100644
index 0000000..36b185b
--- /dev/null
+++ b/mojo/apps/js/test/run_apps_js_tests.cc
@@ -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.
+
+#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/apps/js/bindings/monotonic_clock.h"
+#include "mojo/apps/js/bindings/threading.h"
+#include "mojo/bindings/js/core.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+namespace {
+
+class TestRunnerDelegate : public gin::FileRunnerDelegate {
+ public:
+ TestRunnerDelegate() {
+ AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+ AddBuiltinModule(Core::kModuleName, Core::GetModule);
+ AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule);
+ AddBuiltinModule(apps::MonotonicClock::kModuleName,
+ apps::MonotonicClock::GetModule);
+ AddBuiltinModule(apps::Threading::kModuleName, apps::Threading::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("apps")
+ .AppendASCII("js")
+ .AppendASCII("bindings")
+ .AppendASCII(test);
+ TestRunnerDelegate delegate;
+ gin::RunTestFromFile(path, &delegate, run_until_idle);
+}
+
+// TODO(abarth): Should we autogenerate these stubs from GYP?
+
+TEST(JSTest, sample_test) {
+ RunTest("sample_service_unittests.js", true);
+}
+
+TEST(JSTest, connection) {
+ RunTest("connection_unittests.js", false);
+}
+
+TEST(JSTest, monotonic_clock) {
+ RunTest("monotonic_clock_unittests.js", false);
+}
+
+} // namespace
+} // namespace js
+} // namespace mojo
diff --git a/mojo/aura/BUILD.gn b/mojo/aura/BUILD.gn
new file mode 100644
index 0000000..f6d6450
--- /dev/null
+++ b/mojo/aura/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo.gyp:mojo_aura_support
+source_set("aura") {
+ sources = [
+ "aura_init.cc",
+ "aura_init.h",
+ "context_factory_mojo.cc",
+ "context_factory_mojo.h",
+ "screen_mojo.cc",
+ "screen_mojo.h",
+ "window_tree_host_mojo.cc",
+ "window_tree_host_mojo.h",
+ "window_tree_host_mojo_delegate.h",
+ ]
+
+ public_deps = [
+ "//mojo/services/public/cpp/view_manager",
+ ]
+ deps = [
+ "//cc",
+ "//skia",
+ "//ui/aura",
+ "//ui/compositor",
+ "//ui/events",
+ "//ui/events:events_base",
+ "//ui/gl",
+ "//mojo/cc",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/interfaces/native_viewport",
+ ]
+}
diff --git a/mojo/aura/DEPS b/mojo/aura/DEPS
new file mode 100644
index 0000000..660a477
--- /dev/null
+++ b/mojo/aura/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+skia",
+ "+ui/aura",
+ "+ui/compositor",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/gl",
+]
diff --git a/mojo/aura/aura_init.cc b/mojo/aura/aura_init.cc
new file mode 100644
index 0000000..df17ab7
--- /dev/null
+++ b/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(false);
+
+ 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/mojo/aura/aura_init.h b/mojo/aura/aura_init.h
new file mode 100644
index 0000000..dea1684
--- /dev/null
+++ b/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/mojo/aura/context_factory_mojo.cc b/mojo/aura/context_factory_mojo.cc
new file mode 100644
index 0000000..5b55cba
--- /dev/null
+++ b/mojo/aura/context_factory_mojo.cc
@@ -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.
+
+#include "mojo/aura/context_factory_mojo.h"
+
+#include "base/bind.h"
+#include "base/macros.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/mojo/aura/context_factory_mojo.h b/mojo/aura/context_factory_mojo.h
new file mode 100644
index 0000000..437359d
--- /dev/null
+++ b/mojo/aura/context_factory_mojo.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_AURA_CONTEXT_FACTORY_MOJO_H_
+#define MOJO_AURA_CONTEXT_FACTORY_MOJO_H_
+
+#include "base/macros.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/mojo/aura/screen_mojo.cc b/mojo/aura/screen_mojo.cc
new file mode 100644
index 0000000..b07c320
--- /dev/null
+++ b/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/mojo/aura/screen_mojo.h b/mojo/aura/screen_mojo.h
new file mode 100644
index 0000000..184ccff
--- /dev/null
+++ b/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/macros.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/mojo/aura/window_tree_host_mojo.cc b/mojo/aura/window_tree_host_mojo.cc
new file mode 100644
index 0000000..8ef87c6
--- /dev/null
+++ b/mojo/aura/window_tree_host_mojo.cc
@@ -0,0 +1,173 @@
+// Copyright 2014 The Chromium 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* view,
+ WindowTreeHostMojoDelegate* delegate)
+ : view_(view),
+ bounds_(view->bounds()),
+ delegate_(delegate) {
+ view_->AddObserver(this);
+ CreateCompositor(GetAcceleratedWidget());
+
+ TreeHosts::Get()->Add(this);
+}
+
+WindowTreeHostMojo::~WindowTreeHostMojo() {
+ view_->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::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, ViewObserver implementation:
+
+void WindowTreeHostMojo::OnViewBoundsChanged(
+ View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ 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/mojo/aura/window_tree_host_mojo.h b/mojo/aura/window_tree_host_mojo.h
new file mode 100644
index 0000000..af00203
--- /dev/null
+++ b/mojo/aura/window_tree_host_mojo.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_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_H_
+#define MOJO_EXAMPLES_AURA_DEMO_WINDOW_TREE_HOST_VIEW_MANAGER_H_
+
+#include "base/macros.h"
+#include "mojo/services/public/cpp/view_manager/view_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 ViewObserver {
+ public:
+ WindowTreeHostMojo(View* view, 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 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;
+
+ // ViewObserver:
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+
+ View* view_;
+
+ 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/mojo/aura/window_tree_host_mojo_delegate.h b/mojo/aura/window_tree_host_mojo_delegate.h
new file mode 100644
index 0000000..9ab13b2
--- /dev/null
+++ b/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/mojo/bindings/js/BUILD.gn b/mojo/bindings/js/BUILD.gn
new file mode 100644
index 0000000..7e64c0f
--- /dev/null
+++ b/mojo/bindings/js/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo.gyp:mojo_js_bindings
+source_set("js") {
+ sources = [
+ "core.cc",
+ "core.h",
+ "drain_data.cc",
+ "drain_data.h",
+ "handle.cc",
+ "handle.h",
+ "handle_close_observer.h",
+ "support.cc",
+ "support.h",
+ "waiting_callback.cc",
+ "waiting_callback.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//gin",
+ "//mojo/common",
+ "//v8",
+ ]
+}
diff --git a/mojo/bindings/js/DEPS b/mojo/bindings/js/DEPS
new file mode 100644
index 0000000..d974b68
--- /dev/null
+++ b/mojo/bindings/js/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+gin",
+ "+v8",
+]
diff --git a/mojo/bindings/js/core.cc b/mojo/bindings/js/core.cc
new file mode 100644
index 0000000..9ab2f19
--- /dev/null
+++ b/mojo/bindings/js/core.cc
@@ -0,0 +1,320 @@
+// Copyright 2014 The Chromium 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/drain_data.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) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+
+ MojoHandle handle0 = MOJO_HANDLE_INVALID;
+ MojoHandle handle1 = MOJO_HANDLE_INVALID;
+ MojoResult result = MOJO_RESULT_OK;
+
+ v8::Handle<v8::Value> options_value = args.PeekNext();
+ if (options_value.IsEmpty() || options_value->IsNull() ||
+ options_value->IsUndefined()) {
+ result = MojoCreateMessagePipe(NULL, &handle0, &handle1);
+ } else if (options_value->IsObject()) {
+ gin::Dictionary options_dict(args.isolate(), options_value->ToObject());
+ MojoCreateMessagePipeOptions options;
+ // For future struct_size, we can probably infer that from the presence of
+ // properties in options_dict. For now, it's always 8.
+ options.struct_size = 8;
+ // Ideally these would be optional. But the interface makes it hard to
+ // typecheck them then.
+ if (!options_dict.Get("flags", &options.flags)) {
+ return dictionary;
+ }
+
+ result = MojoCreateMessagePipe(&options, &handle0, &handle1);
+ } else {
+ return dictionary;
+ }
+
+ CHECK_EQ(MOJO_RESULT_OK, result);
+
+ dictionary.Set("result", result);
+ dictionary.Set("handle0", mojo::Handle(handle0));
+ dictionary.Set("handle1", mojo::Handle(handle1));
+ return dictionary;
+}
+
+MojoResult WriteMessage(
+ mojo::Handle handle,
+ const gin::ArrayBufferView& buffer,
+ const std::vector<gin::Handle<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) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+
+ MojoHandle producer_handle = MOJO_HANDLE_INVALID;
+ MojoHandle consumer_handle = MOJO_HANDLE_INVALID;
+ MojoResult result = MOJO_RESULT_OK;
+
+ v8::Handle<v8::Value> options_value = args.PeekNext();
+ if (options_value.IsEmpty() || options_value->IsNull() ||
+ options_value->IsUndefined()) {
+ result = MojoCreateDataPipe(NULL, &producer_handle, &consumer_handle);
+ } else if (options_value->IsObject()) {
+ gin::Dictionary options_dict(args.isolate(), options_value->ToObject());
+ MojoCreateDataPipeOptions options;
+ // For future struct_size, we can probably infer that from the presence of
+ // properties in options_dict. For now, it's always 16.
+ options.struct_size = 16;
+ // Ideally these would be optional. But the interface makes it hard to
+ // typecheck them then.
+ if (!options_dict.Get("flags", &options.flags) ||
+ !options_dict.Get("elementNumBytes", &options.element_num_bytes) ||
+ !options_dict.Get("capacityNumBytes", &options.capacity_num_bytes)) {
+ return dictionary;
+ }
+
+ result = MojoCreateDataPipe(&options, &producer_handle, &consumer_handle);
+ } else {
+ return dictionary;
+ }
+
+ CHECK_EQ(MOJO_RESULT_OK, result);
+
+ dictionary.Set("result", result);
+ dictionary.Set("producerHandle", mojo::Handle(producer_handle));
+ dictionary.Set("consumerHandle", mojo::Handle(consumer_handle));
+ return dictionary;
+}
+
+gin::Dictionary WriteData(const gin::Arguments& args,
+ mojo::Handle handle,
+ const gin::ArrayBufferView& buffer,
+ MojoWriteDataFlags flags) {
+ uint32_t num_bytes = static_cast<uint32_t>(buffer.num_bytes());
+ MojoResult result =
+ MojoWriteData(handle.value(), buffer.bytes(), &num_bytes, flags);
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ dictionary.Set("numBytes", num_bytes);
+ return dictionary;
+}
+
+gin::Dictionary ReadData(const gin::Arguments& args,
+ mojo::Handle handle,
+ MojoReadDataFlags flags) {
+ uint32_t num_bytes = 0;
+ MojoResult result = MojoReadData(
+ handle.value(), NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY);
+ if (result != MOJO_RESULT_OK) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ return dictionary;
+ }
+
+ v8::Handle<v8::ArrayBuffer> array_buffer =
+ v8::ArrayBuffer::New(args.isolate(), num_bytes);
+ gin::ArrayBuffer buffer;
+ ConvertFromV8(args.isolate(), array_buffer, &buffer);
+ CHECK_EQ(num_bytes, buffer.num_bytes());
+
+ result = MojoReadData(handle.value(), buffer.bytes(), &num_bytes, flags);
+ CHECK_EQ(num_bytes, buffer.num_bytes());
+
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ dictionary.Set("buffer", array_buffer);
+ return dictionary;
+}
+
+// Asynchronously read all of the data available for the specified data pipe
+// consumer handle until the remote handle is closed or an error occurs. A
+// Promise is returned whose settled value is an object like this:
+// {result: core.RESULT_OK, buffer: dataArrayBuffer}. If the read failed,
+// then the Promise is rejected, the result will be the actual error code,
+// and the buffer will contain whatever was read before the error occurred.
+// The drainData data pipe handle argument is closed automatically.
+
+v8::Handle<v8::Value> DoDrainData(gin::Arguments* args, mojo::Handle handle) {
+ return (new DrainData(args->isolate(), handle))->GetPromise();
+}
+
+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)
+ .SetMethod("drainData", DoDrainData)
+
+ .SetValue("RESULT_OK", MOJO_RESULT_OK)
+ .SetValue("RESULT_CANCELLED", MOJO_RESULT_CANCELLED)
+ .SetValue("RESULT_UNKNOWN", MOJO_RESULT_UNKNOWN)
+ .SetValue("RESULT_INVALID_ARGUMENT", MOJO_RESULT_INVALID_ARGUMENT)
+ .SetValue("RESULT_DEADLINE_EXCEEDED", MOJO_RESULT_DEADLINE_EXCEEDED)
+ .SetValue("RESULT_NOT_FOUND", MOJO_RESULT_NOT_FOUND)
+ .SetValue("RESULT_ALREADY_EXISTS", MOJO_RESULT_ALREADY_EXISTS)
+ .SetValue("RESULT_PERMISSION_DENIED", MOJO_RESULT_PERMISSION_DENIED)
+ .SetValue("RESULT_RESOURCE_EXHAUSTED", MOJO_RESULT_RESOURCE_EXHAUSTED)
+ .SetValue("RESULT_FAILED_PRECONDITION", MOJO_RESULT_FAILED_PRECONDITION)
+ .SetValue("RESULT_ABORTED", MOJO_RESULT_ABORTED)
+ .SetValue("RESULT_OUT_OF_RANGE", MOJO_RESULT_OUT_OF_RANGE)
+ .SetValue("RESULT_UNIMPLEMENTED", MOJO_RESULT_UNIMPLEMENTED)
+ .SetValue("RESULT_INTERNAL", MOJO_RESULT_INTERNAL)
+ .SetValue("RESULT_UNAVAILABLE", MOJO_RESULT_UNAVAILABLE)
+ .SetValue("RESULT_DATA_LOSS", MOJO_RESULT_DATA_LOSS)
+ .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY)
+ .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT)
+
+ .SetValue("DEADLINE_INDEFINITE", MOJO_DEADLINE_INDEFINITE)
+
+ .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE)
+ .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE)
+ .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE)
+
+ .SetValue("CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE",
+ MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE)
+
+ .SetValue("WRITE_MESSAGE_FLAG_NONE", MOJO_WRITE_MESSAGE_FLAG_NONE)
+
+ .SetValue("READ_MESSAGE_FLAG_NONE", MOJO_READ_MESSAGE_FLAG_NONE)
+ .SetValue("READ_MESSAGE_FLAG_MAY_DISCARD",
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)
+
+ .SetValue("CREATE_DATA_PIPE_OPTIONS_FLAG_NONE",
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE)
+ .SetValue("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/mojo/bindings/js/core.h b/mojo/bindings/js/core.h
new file mode 100644
index 0000000..bde327c
--- /dev/null
+++ b/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/mojo/bindings/js/drain_data.cc b/mojo/bindings/js/drain_data.cc
new file mode 100644
index 0000000..a615cd6
--- /dev/null
+++ b/mojo/bindings/js/drain_data.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/bindings/js/drain_data.h"
+
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "gin/per_context_data.h"
+#include "gin/per_isolate_data.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace js {
+
+DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle)
+ : isolate_(isolate),
+ handle_(DataPipeConsumerHandle(handle.value())),
+ wait_id_(0) {
+
+ v8::Handle<v8::Context> context(isolate_->GetCurrentContext());
+ runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
+
+ WaitForData();
+}
+
+v8::Handle<v8::Value> DrainData::GetPromise() {
+ CHECK(resolver_.IsEmpty());
+ v8::Handle<v8::Promise::Resolver> resolver(
+ v8::Promise::Resolver::New(isolate_));
+ resolver_.Reset(isolate_, resolver);
+ return resolver->GetPromise();
+}
+
+DrainData::~DrainData() {
+ if (wait_id_)
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+ resolver_.Reset();
+}
+
+void DrainData::WaitForData() {
+ wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
+ handle_.get().value(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ &DrainData::WaitCompleted,
+ this);
+}
+
+void DrainData::DataReady(MojoResult result) {
+ wait_id_ = 0;
+ if (result != MOJO_RESULT_OK) {
+ DeliverData(result);
+ return;
+ }
+ while (result == MOJO_RESULT_OK) {
+ result = ReadData();
+ if (result == MOJO_RESULT_SHOULD_WAIT)
+ WaitForData();
+ else if (result != MOJO_RESULT_OK)
+ DeliverData(result);
+ }
+}
+
+MojoResult DrainData::ReadData() {
+ const void* buffer;
+ uint32_t num_bytes = 0;
+ MojoResult result = BeginReadDataRaw(
+ handle_.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+ if (result != MOJO_RESULT_OK)
+ return result;
+ const char* p = static_cast<const char*>(buffer);
+ DataBuffer* data_buffer = new DataBuffer(p, p + num_bytes);
+ data_buffers_.push_back(data_buffer);
+ return EndReadDataRaw(handle_.get(), num_bytes);
+}
+
+void DrainData::DeliverData(MojoResult result) {
+ if (!runner_) {
+ delete this;
+ return;
+ }
+
+ size_t total_bytes = 0;
+ for (unsigned i = 0; i < data_buffers_.size(); i++)
+ total_bytes += data_buffers_[i]->size();
+
+ // Create a total_bytes length ArrayBuffer return value.
+ gin::Runner::Scope scope(runner_.get());
+ v8::Handle<v8::ArrayBuffer> array_buffer =
+ v8::ArrayBuffer::New(isolate_, total_bytes);
+ gin::ArrayBuffer buffer;
+ ConvertFromV8(isolate_, array_buffer, &buffer);
+ CHECK_EQ(total_bytes, buffer.num_bytes());
+
+ // Copy the data_buffers into the ArrayBuffer.
+ char* array_buffer_ptr = static_cast<char*>(buffer.bytes());
+ size_t offset = 0;
+ for (size_t i = 0; i < data_buffers_.size(); i++) {
+ size_t num_bytes = data_buffers_[i]->size();
+ if (num_bytes == 0)
+ continue;
+ const char* data_buffer_ptr = &((*data_buffers_[i])[0]);
+ memcpy(array_buffer_ptr + offset, data_buffer_ptr, num_bytes);
+ offset += num_bytes;
+ }
+
+ // The "settled" value of the promise always includes all of the data
+ // that was read before either an error occurred or the remote pipe handle
+ // was closed. The latter is indicated by MOJO_RESULT_FAILED_PRECONDITION.
+
+ v8::Handle<v8::Promise::Resolver> resolver(
+ v8::Local<v8::Promise::Resolver>::New(isolate_, resolver_));
+
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate_);
+ dictionary.Set("result", result);
+ dictionary.Set("buffer", array_buffer);
+ v8::Handle<v8::Value> settled_value(ConvertToV8(isolate_, dictionary));
+
+ if (result == MOJO_RESULT_FAILED_PRECONDITION)
+ resolver->Resolve(settled_value);
+ else
+ resolver->Reject(settled_value);
+
+ delete this;
+}
+
+} // namespace js
+} // namespace mojo
diff --git a/mojo/bindings/js/drain_data.h b/mojo/bindings/js/drain_data.h
new file mode 100644
index 0000000..27d8f52
--- /dev/null
+++ b/mojo/bindings/js/drain_data.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_BINDINGS_JS_DRAIN_DATA_H_
+#define MOJO_BINDINGS_JS_DRAIN_DATA_H_
+
+#include "base/memory/scoped_vector.h"
+#include "gin/runner.h"
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/cpp/system/core.h"
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace js {
+
+// This class is the implementation of the Mojo JavaScript core module's
+// drainData() method. It is not intended to be used directly. The caller
+// allocates a DrainData on the heap and returns GetPromise() to JS. The
+// implementation deletes itself after reading as much data as possible
+// and rejecting or resolving the Promise.
+
+class DrainData {
+ public:
+ // Starts waiting for data on the specified data pipe consumer handle.
+ // See WaitForData(). The constructor does not block.
+ DrainData(v8::Isolate* isolate, mojo::Handle handle);
+
+ // Returns a Promise that will be settled when no more data can be read.
+ // Should be called just once on a newly allocated DrainData object.
+ v8::Handle<v8::Value> GetPromise();
+
+ private:
+ ~DrainData();
+
+ // Registers an "async waiter" that calls DataReady() via WaitCompleted().
+ void WaitForData();
+ static void WaitCompleted(void* self, MojoResult result) {
+ static_cast<DrainData*>(self)->DataReady(result);
+ }
+
+ // Use ReadData() to read whatever is availble now on handle_ and save
+ // it in data_buffers_.
+ void DataReady(MojoResult result);
+ MojoResult ReadData();
+
+ // When the remote data pipe handle is closed, or an error occurs, deliver
+ // all of the buffered data to the JS Promise and then delete this.
+ void DeliverData(MojoResult result);
+
+ typedef std::vector<char> DataBuffer;
+
+ v8::Isolate* isolate_;
+ ScopedDataPipeConsumerHandle handle_;
+ MojoAsyncWaitID wait_id_;
+ base::WeakPtr<gin::Runner> runner_;
+ v8::UniquePersistent<v8::Promise::Resolver> resolver_;
+ ScopedVector<DataBuffer> data_buffers_;
+};
+
+} // namespace js
+} // namespace mojo
+
+#endif // MOJO_BINDINGS_JS_DRAIN_DATA_H_
diff --git a/mojo/bindings/js/handle.cc b/mojo/bindings/js/handle.cc
new file mode 100644
index 0000000..a25a911
--- /dev/null
+++ b/mojo/bindings/js/handle.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/bindings/js/handle.h"
+
+#include "mojo/bindings/js/handle_close_observer.h"
+
+namespace gin {
+
+gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+HandleWrapper::HandleWrapper(MojoHandle handle)
+ : handle_(mojo::Handle(handle)) {
+}
+
+HandleWrapper::~HandleWrapper() {
+ NotifyCloseObservers();
+}
+
+void HandleWrapper::Close() {
+ NotifyCloseObservers();
+ handle_.reset();
+}
+
+void HandleWrapper::AddCloseObserver(HandleCloseObserver* observer) {
+ close_observers_.AddObserver(observer);
+}
+
+void HandleWrapper::RemoveCloseObserver(HandleCloseObserver* observer) {
+ close_observers_.RemoveObserver(observer);
+}
+
+void HandleWrapper::NotifyCloseObservers() {
+ if (!handle_.is_valid())
+ return;
+
+ FOR_EACH_OBSERVER(HandleCloseObserver, close_observers_, OnWillCloseHandle());
+}
+
+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/mojo/bindings/js/handle.h b/mojo/bindings/js/handle.h
new file mode 100644
index 0000000..e0f00dd
--- /dev/null
+++ b/mojo/bindings/js/handle.h
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_BINDINGS_JS_HANDLE_H_
+#define MOJO_BINDINGS_JS_HANDLE_H_
+
+#include "base/observer_list.h"
+#include "gin/converter.h"
+#include "gin/handle.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace gin {
+class HandleCloseObserver;
+
+// Wrapper for mojo Handles exposed to JavaScript. This ensures the Handle
+// is Closed when its JS object is garbage collected.
+class HandleWrapper : public gin::Wrappable<HandleWrapper> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ static gin::Handle<HandleWrapper> Create(v8::Isolate* isolate,
+ MojoHandle handle) {
+ return gin::CreateHandle(isolate, new HandleWrapper(handle));
+ }
+
+ mojo::Handle get() const { return handle_.get(); }
+ mojo::Handle release() { return handle_.release(); }
+ void Close();
+
+ void AddCloseObserver(HandleCloseObserver* observer);
+ void RemoveCloseObserver(HandleCloseObserver* observer);
+
+ protected:
+ HandleWrapper(MojoHandle handle);
+ virtual ~HandleWrapper();
+ void NotifyCloseObservers();
+
+ mojo::ScopedHandle handle_;
+ ObserverList<HandleCloseObserver> close_observers_;
+};
+
+// 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/mojo/bindings/js/handle_close_observer.h b/mojo/bindings/js/handle_close_observer.h
new file mode 100644
index 0000000..854603e
--- /dev/null
+++ b/mojo/bindings/js/handle_close_observer.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_BINDINGS_JS_HANDLE_CLOSE_OBSERVER_H_
+#define MOJO_BINDINGS_JS_HANDLE_CLOSE_OBSERVER_H_
+
+namespace gin {
+
+class HandleCloseObserver {
+ public:
+ virtual void OnWillCloseHandle() = 0;
+
+ protected:
+ virtual ~HandleCloseObserver() {}
+};
+
+} // namespace gin
+
+#endif // MOJO_BINDINGS_JS_HANDLE_CLOSE_OBSERVER_H_
diff --git a/mojo/bindings/js/support.cc b/mojo/bindings/js/support.cc
new file mode 100644
index 0000000..1c82f17
--- /dev/null
+++ b/mojo/bindings/js/support.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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,
+ gin::Handle<gin::HandleWrapper> handle,
+ MojoHandleSignals signals,
+ v8::Handle<v8::Function> callback) {
+ return WaitingCallback::Create(args.isolate(), callback, handle, signals)
+ .get();
+}
+
+void CancelWait(WaitingCallback* waiting_callback) {
+ waiting_callback->Cancel();
+}
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+} // namespace
+
+const char Support::kModuleName[] = "mojo/public/js/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/mojo/bindings/js/support.h b/mojo/bindings/js/support.h
new file mode 100644
index 0000000..0f6eb07
--- /dev/null
+++ b/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/mojo/bindings/js/tests/BUILD.gn b/mojo/bindings/js/tests/BUILD.gn
new file mode 100644
index 0000000..c85e68f
--- /dev/null
+++ b/mojo/bindings/js/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_js_unittests
+test("mojo_js_unittests") {
+ deps = [
+ "//gin:gin_test",
+ "//mojo/bindings/js",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/edk/test:test_support",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ ]
+
+ sources = [ "run_js_tests.cc" ]
+}
diff --git a/mojo/bindings/js/tests/DEPS b/mojo/bindings/js/tests/DEPS
new file mode 100644
index 0000000..2424ea1
--- /dev/null
+++ b/mojo/bindings/js/tests/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+base",
+ "+gin",
+ "+v8",
+ "+mojo/bindings/js/core.h",
+ "+mojo/bindings/js/support.h",
+]
diff --git a/mojo/bindings/js/tests/run_js_tests.cc b/mojo/bindings/js/tests/run_js_tests.cc
new file mode 100644
index 0000000..54d9806
--- /dev/null
+++ b/mojo/bindings/js/tests/run_js_tests.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/modules/timer.h"
+#include "gin/test/file_runner.h"
+#include "gin/test/gtest.h"
+#include "mojo/bindings/js/core.h"
+#include "mojo/bindings/js/support.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+namespace {
+
+class TestRunnerDelegate : public gin::FileRunnerDelegate {
+ public:
+ TestRunnerDelegate() {
+ AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+ AddBuiltinModule(Core::kModuleName, Core::GetModule);
+ AddBuiltinModule(Support::kModuleName, Support::GetModule);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate);
+};
+
+void RunTest(std::string test, bool run_until_idle) {
+ Environment env;
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("mojo")
+ .AppendASCII("public")
+ .AppendASCII("js")
+ .AppendASCII("bindings")
+ .AppendASCII(test);
+ TestRunnerDelegate delegate;
+ gin::RunTestFromFile(path, &delegate, run_until_idle);
+}
+
+// TODO(abarth): Should we autogenerate these stubs from GYP?
+TEST(JSTest, core) {
+ RunTest("core_unittests.js", true);
+}
+
+TEST(JSTest, codec) {
+ RunTest("codec_unittests.js", true);
+}
+
+TEST(JSTest, struct) {
+ RunTest("struct_unittests.js", true);
+}
+
+TEST(JSTest, validation) {
+ RunTest("validation_unittests.js", true);
+}
+
+} // namespace
+} // namespace js
+} // namespace mojo
diff --git a/mojo/bindings/js/waiting_callback.cc b/mojo/bindings/js/waiting_callback.cc
new file mode 100644
index 0000000..a88f956
--- /dev/null
+++ b/mojo/bindings/js/waiting_callback.cc
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium 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,
+ gin::Handle<gin::HandleWrapper> handle_wrapper,
+ MojoHandleSignals signals) {
+ gin::Handle<WaitingCallback> waiting_callback = gin::CreateHandle(
+ isolate, new WaitingCallback(isolate, callback, handle_wrapper));
+ waiting_callback->wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
+ handle_wrapper->get().value(),
+ signals,
+ MOJO_DEADLINE_INDEFINITE,
+ &WaitingCallback::CallOnHandleReady,
+ waiting_callback.get());
+ return waiting_callback;
+}
+
+void WaitingCallback::Cancel() {
+ if (!wait_id_)
+ return;
+
+ handle_wrapper_->RemoveCloseObserver(this);
+ handle_wrapper_ = NULL;
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+ wait_id_ = 0;
+}
+
+WaitingCallback::WaitingCallback(v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<gin::HandleWrapper> handle_wrapper)
+ : wait_id_(0), handle_wrapper_(handle_wrapper.get()) {
+ handle_wrapper_->AddCloseObserver(this);
+ v8::Handle<v8::Context> context = isolate->GetCurrentContext();
+ runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
+ GetWrapper(isolate)->SetHiddenValue(GetHiddenPropertyName(isolate), callback);
+}
+
+WaitingCallback::~WaitingCallback() {
+ Cancel();
+}
+
+// static
+void WaitingCallback::CallOnHandleReady(void* closure, MojoResult result) {
+ static_cast<WaitingCallback*>(closure)->OnHandleReady(result);
+}
+
+void WaitingCallback::OnHandleReady(MojoResult result) {
+ wait_id_ = 0;
+ handle_wrapper_->RemoveCloseObserver(this);
+ handle_wrapper_ = NULL;
+
+ if (!runner_)
+ return;
+
+ gin::Runner::Scope scope(runner_.get());
+ v8::Isolate* isolate = runner_->GetContextHolder()->isolate();
+
+ v8::Handle<v8::Value> hidden_value =
+ GetWrapper(isolate)->GetHiddenValue(GetHiddenPropertyName(isolate));
+ v8::Handle<v8::Function> callback;
+ CHECK(gin::ConvertFromV8(isolate, hidden_value, &callback));
+
+ v8::Handle<v8::Value> args[] = { gin::ConvertToV8(isolate, result) };
+ runner_->Call(callback, runner_->global(), 1, args);
+}
+
+void WaitingCallback::OnWillCloseHandle() {
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+ OnHandleReady(MOJO_RESULT_INVALID_ARGUMENT);
+}
+
+} // namespace js
+} // namespace mojo
diff --git a/mojo/bindings/js/waiting_callback.h b/mojo/bindings/js/waiting_callback.h
new file mode 100644
index 0000000..b3a4fda
--- /dev/null
+++ b/mojo/bindings/js/waiting_callback.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_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/bindings/js/handle.h"
+#include "mojo/bindings/js/handle_close_observer.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 gin::HandleCloseObserver {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ // Creates a new WaitingCallback.
+ static gin::Handle<WaitingCallback> Create(
+ v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<gin::HandleWrapper> handle_wrapper,
+ MojoHandleSignals signals);
+
+ // Cancels the callback. Does nothing if a callback is not pending. This is
+ // implicitly invoked from the destructor but can be explicitly invoked as
+ // necessary.
+ void Cancel();
+
+ private:
+ WaitingCallback(v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<gin::HandleWrapper> handle_wrapper);
+ 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);
+
+ // Invoked by the HandleWrapper if the handle is closed while this wait is
+ // still in progress.
+ virtual void OnWillCloseHandle() override;
+
+ base::WeakPtr<gin::Runner> runner_;
+ MojoAsyncWaitID wait_id_;
+
+ gin::HandleWrapper* handle_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(WaitingCallback);
+};
+
+} // namespace js
+} // namespace mojo
+
+#endif // MOJO_BINDINGS_JS_WAITING_CALLBACK_H_
diff --git a/mojo/build/package_app.gypi b/mojo/build/package_app.gypi
new file mode 100644
index 0000000..00c6785
--- /dev/null
+++ b/mojo/build/package_app.gypi
@@ -0,0 +1,31 @@
+# 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.
+
+{
+ 'type': 'none',
+ 'dependencies': [
+ '<(app_name)',
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'variables': {
+ 'mojo_app_dir': '<(PRODUCT_DIR)/apps',
+ 'source_binary': '<(PRODUCT_DIR)/<(SHARED_LIB_PREFIX)<(app_name)<(SHARED_LIB_SUFFIX)',
+ 'target_binary': '<(mojo_app_dir)/<(SHARED_LIB_PREFIX)<(app_name)<(SHARED_LIB_SUFFIX)',
+ },
+ 'actions': [{
+ 'action_name': 'strip',
+ 'inputs': [ '<(source_binary)', ],
+ 'outputs': [ '<(target_binary)', ],
+ 'action': [
+ '<(android_strip)',
+ '<@(_inputs)',
+ '--strip-unneeded',
+ '-o',
+ '<@(_outputs)',
+ ],
+ }],
+ }],
+ ],
+}
diff --git a/mojo/cc/BUILD.gn b/mojo/cc/BUILD.gn
new file mode 100644
index 0000000..af2a8e5
--- /dev/null
+++ b/mojo/cc/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo.gyp:mojo_cc_support
+source_set("cc") {
+ deps = [
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//skia",
+ "//gpu/command_buffer/client:gles2_implementation",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/cpp/surfaces",
+ "//mojo/services/public/interfaces/surfaces",
+ ]
+
+ sources = [
+ "context_provider_mojo.cc",
+ "context_provider_mojo.h",
+ "output_surface_mojo.cc",
+ "output_surface_mojo.h",
+ ]
+}
diff --git a/mojo/cc/DEPS b/mojo/cc/DEPS
new file mode 100644
index 0000000..f789994
--- /dev/null
+++ b/mojo/cc/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+]
diff --git a/mojo/cc/context_provider_mojo.cc b/mojo/cc/context_provider_mojo.cc
new file mode 100644
index 0000000..16c6f8c
--- /dev/null
+++ b/mojo/cc/context_provider_mojo.cc
@@ -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.
+
+#include "mojo/cc/context_provider_mojo.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/environment/environment.h"
+
+namespace mojo {
+
+ContextProviderMojo::ContextProviderMojo(
+ ScopedMessagePipeHandle command_buffer_handle)
+ : command_buffer_handle_(command_buffer_handle.Pass()),
+ context_lost_(false) {
+}
+
+bool ContextProviderMojo::BindToCurrentThread() {
+ DCHECK(command_buffer_handle_.is_valid());
+ context_ = MojoGLES2CreateContext(command_buffer_handle_.release().value(),
+ &ContextLostThunk,
+ this,
+ Environment::GetDefaultAsyncWaiter());
+ 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_lost_;
+}
+bool ContextProviderMojo::DestroyedOnMainThread() { return !context_; }
+
+ContextProviderMojo::~ContextProviderMojo() {
+ if (context_)
+ MojoGLES2DestroyContext(context_);
+}
+
+void ContextProviderMojo::ContextLost() {
+ context_lost_ = true;
+}
+
+} // namespace mojo
diff --git a/mojo/cc/context_provider_mojo.h b/mojo/cc/context_provider_mojo.h
new file mode 100644
index 0000000..294bd5d
--- /dev/null
+++ b/mojo/cc/context_provider_mojo.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_CC_CONTEXT_PROVIDER_MOJO_H_
+#define MOJO_CC_CONTEXT_PROVIDER_MOJO_H_
+
+#include "base/macros.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_;
+ bool context_lost_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextProviderMojo);
+};
+
+} // namespace mojo
+
+#endif // MOJO_CC_CONTEXT_PROVIDER_MOJO_H_
diff --git a/mojo/cc/output_surface_mojo.cc b/mojo/cc/output_surface_mojo.cc
new file mode 100644
index 0000000..fbd2c9e
--- /dev/null
+++ b/mojo/cc/output_surface_mojo.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/cc/output_surface_mojo.h"
+
+#include "cc/output/compositor_frame.h"
+#include "cc/output/output_surface_client.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+
+namespace mojo {
+
+OutputSurfaceMojo::OutputSurfaceMojo(
+ OutputSurfaceMojoClient* client,
+ const scoped_refptr<cc::ContextProvider>& context_provider,
+ SurfacePtr surface,
+ uint32_t id_namespace)
+ : cc::OutputSurface(context_provider),
+ output_surface_mojo_client_(client),
+ surface_(surface.Pass()),
+ id_allocator_(id_namespace) {
+ capabilities_.delegated_rendering = true;
+ capabilities_.max_frames_pending = 1;
+}
+
+OutputSurfaceMojo::~OutputSurfaceMojo() {
+}
+
+void OutputSurfaceMojo::ReturnResources(Array<ReturnedResourcePtr> resources) {
+}
+
+bool OutputSurfaceMojo::BindToClient(cc::OutputSurfaceClient* client) {
+ surface_.set_client(this);
+ return cc::OutputSurface::BindToClient(client);
+}
+
+void OutputSurfaceMojo::SwapBuffers(cc::CompositorFrame* frame) {
+ gfx::Size frame_size =
+ frame->delegated_frame_data->render_pass_list.back()->output_rect.size();
+ if (frame_size != surface_size_) {
+ if (!surface_id_.is_null()) {
+ surface_->DestroySurface(SurfaceId::From(surface_id_));
+ }
+ surface_id_ = id_allocator_.GenerateId();
+ surface_->CreateSurface(SurfaceId::From(surface_id_),
+ Size::From(frame_size));
+ output_surface_mojo_client_->DidCreateSurface(surface_id_);
+ surface_size_ = frame_size;
+ }
+
+ surface_->SubmitFrame(SurfaceId::From(surface_id_), Frame::From(*frame));
+
+ client_->DidSwapBuffers();
+ client_->DidSwapBuffersComplete();
+}
+
+} // namespace mojo
diff --git a/mojo/cc/output_surface_mojo.h b/mojo/cc/output_surface_mojo.h
new file mode 100644
index 0000000..b8f2bab
--- /dev/null
+++ b/mojo/cc/output_surface_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_OUTPUT_SURFACE_MOJO_H_
+#define MOJO_CC_OUTPUT_SURFACE_MOJO_H_
+
+#include "base/macros.h"
+#include "cc/output/output_surface.h"
+#include "cc/surfaces/surface_id.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+
+namespace mojo {
+
+class OutputSurfaceMojoClient {
+ public:
+ virtual ~OutputSurfaceMojoClient() {}
+
+ virtual void DidCreateSurface(cc::SurfaceId id) = 0;
+};
+
+class OutputSurfaceMojo : public cc::OutputSurface, public SurfaceClient {
+ public:
+ OutputSurfaceMojo(OutputSurfaceMojoClient* client,
+ const scoped_refptr<cc::ContextProvider>& context_provider,
+ SurfacePtr surface,
+ uint32_t id_namespace);
+
+ // SurfaceClient implementation.
+ virtual void ReturnResources(Array<ReturnedResourcePtr> resources) override;
+
+ // cc::OutputSurface implementation.
+ virtual void SwapBuffers(cc::CompositorFrame* frame) override;
+ virtual bool BindToClient(cc::OutputSurfaceClient* client) override;
+
+ protected:
+ virtual ~OutputSurfaceMojo();
+
+ private:
+ OutputSurfaceMojoClient* output_surface_mojo_client_;
+ SurfacePtr surface_;
+ cc::SurfaceIdAllocator id_allocator_;
+ cc::SurfaceId surface_id_;
+ gfx::Size surface_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutputSurfaceMojo);
+};
+}
+
+#endif // MOJO_CC_OUTPUT_SURFACE_MOJO_H_
diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn
new file mode 100644
index 0000000..a825189
--- /dev/null
+++ b/mojo/common/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.
+
+# GYP version: mojo/mojo_base.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/public/c/system:for_component",
+ "//url",
+ ]
+}
+
+# GYP version: mojo/mojo_base.gyp:mojo_common_unittests
+test("mojo_common_unittests") {
+ deps = [
+ ":common",
+ "//base",
+ "//base:message_loop_tests",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/edk/test:test_support",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//testing/gtest",
+ "//url",
+ ]
+
+ sources = [
+ "common_type_converters_unittest.cc",
+ "handle_watcher_unittest.cc",
+ "message_pump_mojo_unittest.cc",
+ ]
+}
diff --git a/mojo/common/DEPS b/mojo/common/DEPS
new file mode 100644
index 0000000..e8ac428
--- /dev/null
+++ b/mojo/common/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ # common must not depend on embedder.
+ "-mojo",
+ "+mojo/common",
+ "+mojo/public",
+]
diff --git a/mojo/common/common_type_converters.cc b/mojo/common/common_type_converters.cc
new file mode 100644
index 0000000..114b409
--- /dev/null
+++ b/mojo/common/common_type_converters.cc
@@ -0,0 +1,65 @@
+// 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"
+#include "url/gurl.h"
+
+namespace mojo {
+
+// static
+String TypeConverter<String, base::StringPiece>::Convert(
+ const base::StringPiece& input) {
+ if (input.empty()) {
+ char c = 0;
+ return String(&c, 0);
+ }
+ return String(input.data(), input.size());
+}
+// static
+base::StringPiece TypeConverter<base::StringPiece, String>::Convert(
+ const String& input) {
+ return input.get();
+}
+
+// static
+String TypeConverter<String, base::string16>::Convert(
+ const base::string16& input) {
+ return TypeConverter<String, base::StringPiece>::Convert(
+ base::UTF16ToUTF8(input));
+}
+// static
+base::string16 TypeConverter<base::string16, String>::Convert(
+ const String& input) {
+ return base::UTF8ToUTF16(input.To<base::StringPiece>());
+}
+
+String TypeConverter<String, GURL>::Convert(const GURL& input) {
+ return String(input.spec());
+}
+
+GURL TypeConverter<GURL, String>::Convert(const String& input) {
+ return GURL(input.get());
+}
+
+std::string TypeConverter<std::string, Array<uint8_t> >::Convert(
+ const Array<uint8_t>& input) {
+ if (input.is_null())
+ return std::string();
+
+ return std::string(reinterpret_cast<const char*>(&input.front()),
+ input.size());
+}
+
+Array<uint8_t> TypeConverter<Array<uint8_t>, std::string>::Convert(
+ const std::string& input) {
+ Array<uint8_t> result(input.size());
+ memcpy(&result.front(), input.c_str(), input.size());
+ return result.Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/common/common_type_converters.h b/mojo/common/common_type_converters.h
new file mode 100644
index 0000000..7b0260a
--- /dev/null
+++ b/mojo/common/common_type_converters.h
@@ -0,0 +1,65 @@
+// 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/array.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/public/cpp/bindings/type_converter.h"
+
+class GURL;
+
+namespace mojo {
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<String, base::StringPiece> {
+ static String Convert(const base::StringPiece& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<base::StringPiece, String> {
+ static base::StringPiece Convert(const String& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<String, base::string16> {
+ static String Convert(const base::string16& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<base::string16, String> {
+ static base::string16 Convert(const String& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<String, GURL> {
+ static String Convert(const GURL& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<GURL, String> {
+ static GURL Convert(const String& input);
+};
+
+// TODO(erg): In the very long term, we will want to remove conversion between
+// std::strings and arrays of unsigned bytes. However, there is too much code
+// across chrome which uses std::string as a bag of bytes that we probably
+// don't want to roll this function at each callsite.
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<std::string, Array<uint8_t> > {
+ static std::string Convert(const Array<uint8_t>& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<Array<uint8_t>, std::string> {
+ static Array<uint8_t> Convert(const std::string& input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_COMMON_COMMON_TYPE_CONVERTERS_H_
diff --git a/mojo/common/common_type_converters_unittest.cc b/mojo/common/common_type_converters_unittest.cc
new file mode 100644
index 0000000..f97be0a
--- /dev/null
+++ b/mojo/common/common_type_converters_unittest.cc
@@ -0,0 +1,110 @@
+// 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"
+#include "url/gurl.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()));
+}
+
+TEST(CommonTypeConvertersTest, URL) {
+ GURL url("mojo:foo");
+ String mojo_string(String::From(url));
+
+ ASSERT_EQ(url.spec(), mojo_string);
+ EXPECT_EQ(url.spec(), mojo_string.To<GURL>().spec());
+ EXPECT_EQ(url.spec(), String::From(url));
+
+ GURL invalid = String().To<GURL>();
+ ASSERT_TRUE(invalid.spec().empty());
+
+ String string_from_invalid = String::From(invalid);
+ EXPECT_FALSE(string_from_invalid.is_null());
+ ASSERT_EQ(0U, string_from_invalid.size());
+}
+
+TEST(CommonTypeConvertersTest, ArrayUint8ToStdString) {
+ Array<uint8_t> data(4);
+ data[0] = 'd';
+ data[1] = 'a';
+ data[2] = 't';
+ data[3] = 'a';
+
+ EXPECT_EQ("data", data.To<std::string>());
+}
+
+TEST(CommonTypeConvertersTest, StdStringToArrayUint8) {
+ std::string input("data");
+ Array<uint8_t> data = Array<uint8_t>::From(input);
+
+ ASSERT_EQ(4ul, data.size());
+ EXPECT_EQ('d', data[0]);
+ EXPECT_EQ('a', data[1]);
+ EXPECT_EQ('t', data[2]);
+ EXPECT_EQ('a', data[3]);
+}
+
+} // namespace test
+} // namespace common
+} // namespace mojo
diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc
new file mode 100644
index 0000000..428a589
--- /dev/null
+++ b/mojo/common/data_pipe_utils.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/common/data_pipe_utils.h"
+
+#include <stdio.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/message_loop/message_loop.h"
+#include "base/task_runner_util.h"
+
+namespace mojo {
+namespace common {
+namespace {
+
+bool BlockingCopyHelper(ScopedDataPipeConsumerHandle source,
+ const base::Callback<size_t(const void*, uint32_t)>& write_bytes) {
+ 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 = write_bytes.Run(buffer, num_bytes);
+ 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;
+}
+
+size_t CopyToStringHelper(
+ std::string* result, const void* buffer, uint32_t num_bytes) {
+ result->append(static_cast<const char*>(buffer), num_bytes);
+ return num_bytes;
+}
+
+size_t CopyToFileHelper(FILE* fp, const void* buffer, uint32_t num_bytes) {
+ return fwrite(buffer, 1, num_bytes, fp);
+}
+
+} // namespace
+
+
+// TODO(hansmuller): Add a max_size parameter.
+bool BlockingCopyToString(ScopedDataPipeConsumerHandle source,
+ std::string* result) {
+ CHECK(result);
+ result->clear();
+ return BlockingCopyHelper(
+ source.Pass(), base::Bind(&CopyToStringHelper, result));
+}
+
+bool BlockingCopyToFile(ScopedDataPipeConsumerHandle source,
+ const base::FilePath& destination) {
+ base::ScopedFILE fp(base::OpenFile(destination, "wb"));
+ if (!fp)
+ return false;
+ return BlockingCopyHelper(
+ source.Pass(), base::Bind(&CopyToFileHelper, fp.get()));
+}
+
+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/mojo/common/data_pipe_utils.h b/mojo/common/data_pipe_utils.h
new file mode 100644
index 0000000..dc2197b
--- /dev/null
+++ b/mojo/common/data_pipe_utils.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_DATA_PIPE_UTILS_H_
+#define MOJO_SHELL_DATA_PIPE_UTILS_H_
+
+#include <string>
+
+#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);
+
+// Copies the data from |source| into |contents| and returns true on success and
+// false on error. In case of I/O error, |contents| holds the data that could
+// be read from source before the error occurred.
+bool MOJO_COMMON_EXPORT BlockingCopyToString(
+ ScopedDataPipeConsumerHandle source,
+ std::string* contents);
+
+} // namespace common
+} // namespace mojo
+
+#endif // MOJO_SHELL_DATA_PIPE_UTILS_H_
diff --git a/mojo/common/handle_watcher.cc b/mojo/common/handle_watcher.cc
new file mode 100644
index 0000000..e8df765
--- /dev/null
+++ b/mojo/common/handle_watcher.cc
@@ -0,0 +1,475 @@
+// 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/logging.h"
+#include "base/macros.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/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.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";
+
+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);
+
+ // Cancels a previously scheduled request to start a watch.
+ 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;
+ MessagePumpMojo::current()->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)) {
+ handle_to_data_.erase(handle);
+ MessagePumpMojo::current()->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);
+ MessagePumpMojo::current()->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.
+} // namespace
+
+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:
+ enum RequestType {
+ REQUEST_START,
+ REQUEST_STOP,
+ };
+
+ // See description of |requests_| for details.
+ struct RequestData {
+ RequestData() : type(REQUEST_START), stop_id(0), stop_event(NULL) {}
+
+ RequestType type;
+ WatchData start_data;
+ WatcherID stop_id;
+ base::WaitableEvent* stop_event;
+ };
+
+ typedef std::vector<RequestData> Requests;
+
+ friend struct DefaultSingletonTraits<WatcherThreadManager>;
+
+ WatcherThreadManager();
+
+ // Schedules a request on the background thread. See |requests_| for details.
+ void AddRequest(const RequestData& data);
+
+ // Processes requests added to |requests_|. This is invoked on the backend
+ // thread.
+ void ProcessRequestsOnBackendThread();
+
+ base::Thread thread_;
+
+ base::AtomicSequenceNumber watcher_id_generator_;
+
+ WatcherBackend backend_;
+
+ // Protects |requests_|.
+ base::Lock lock_;
+
+ // Start/Stop result in adding a RequestData to |requests_| (protected by
+ // |lock_|). When the background thread wakes up it processes the requests.
+ Requests requests_;
+
+ 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) {
+ RequestData request_data;
+ request_data.type = REQUEST_START;
+ request_data.start_data.id = watcher_id_generator_.GetNext();
+ request_data.start_data.handle = handle;
+ request_data.start_data.callback = callback;
+ request_data.start_data.handle_signals = handle_signals;
+ request_data.start_data.deadline = deadline;
+ request_data.start_data.message_loop = base::MessageLoopProxy::current();
+ DCHECK_NE(static_cast<base::MessageLoopProxy*>(NULL),
+ request_data.start_data.message_loop.get());
+ AddRequest(request_data);
+ return request_data.start_data.id;
+}
+
+void WatcherThreadManager::StopWatching(WatcherID watcher_id) {
+ // Handle the case of StartWatching() followed by StopWatching() before
+ // |thread_| woke up.
+ {
+ base::AutoLock auto_lock(lock_);
+ for (Requests::iterator i = requests_.begin(); i != requests_.end(); ++i) {
+ if (i->type == REQUEST_START && i->start_data.id == watcher_id) {
+ // Watcher ids are not reused, so if we find it we can stop.
+ requests_.erase(i);
+ return;
+ }
+ }
+ }
+
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ base::WaitableEvent event(true, false);
+ RequestData request_data;
+ request_data.type = REQUEST_STOP;
+ request_data.stop_id = watcher_id;
+ request_data.stop_event = &event;
+ AddRequest(request_data);
+
+ // We need to block until the handle is actually removed.
+ event.Wait();
+}
+
+void WatcherThreadManager::AddRequest(const RequestData& data) {
+ {
+ base::AutoLock auto_lock(lock_);
+ const bool was_empty = requests_.empty();
+ requests_.push_back(data);
+ if (!was_empty)
+ return;
+ }
+ // We own |thread_|, so it's safe to use Unretained() here.
+ thread_.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&WatcherThreadManager::ProcessRequestsOnBackendThread,
+ base::Unretained(this)));
+}
+
+void WatcherThreadManager::ProcessRequestsOnBackendThread() {
+ DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
+
+ Requests requests;
+ {
+ base::AutoLock auto_lock(lock_);
+ requests_.swap(requests);
+ }
+ for (size_t i = 0; i < requests.size(); ++i) {
+ if (requests[i].type == REQUEST_START) {
+ backend_.StartWatching(requests[i].start_data);
+ } else {
+ backend_.StopWatching(requests[i].stop_id);
+ requests[i].stop_event->Signal();
+ }
+ }
+}
+
+WatcherThreadManager::WatcherThreadManager()
+ : thread_(kWatcherThreadName) {
+ base::Thread::Options thread_options;
+ thread_options.message_pump_factory = base::Bind(&MessagePumpMojo::Create);
+ thread_.StartWithOptions(thread_options);
+}
+
+// HandleWatcher::StateBase and subclasses -------------------------------------
+
+// The base class of HandleWatcher's state. 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::StateBase : public base::MessageLoop::DestructionObserver {
+ public:
+ StateBase(HandleWatcher* watcher,
+ const base::Callback<void(MojoResult)>& callback)
+ : watcher_(watcher),
+ callback_(callback),
+ got_ready_(false) {
+ base::MessageLoop::current()->AddDestructionObserver(this);
+ }
+
+ virtual ~StateBase() {
+ base::MessageLoop::current()->RemoveDestructionObserver(this);
+ }
+
+ protected:
+ void NotifyHandleReady(MojoResult result) {
+ got_ready_ = true;
+ NotifyAndDestroy(result);
+ }
+
+ bool got_ready() const { return got_ready_; }
+
+ private:
+ virtual void WillDestroyCurrentMessageLoop() override {
+ // The current thread is exiting. Simulate a watch error.
+ NotifyAndDestroy(MOJO_RESULT_ABORTED);
+ }
+
+ void NotifyAndDestroy(MojoResult result) {
+ base::Callback<void(MojoResult)> callback = callback_;
+ watcher_->Stop(); // Destroys |this|.
+
+ callback.Run(result);
+ }
+
+ HandleWatcher* watcher_;
+ base::Callback<void(MojoResult)> callback_;
+
+ // Have we been notified that the handle is ready?
+ bool got_ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(StateBase);
+};
+
+// If the thread on which HandleWatcher is used runs MessagePumpMojo,
+// SameThreadWatchingState is used to directly watch the handle on the same
+// thread.
+class HandleWatcher::SameThreadWatchingState : public StateBase,
+ public MessagePumpMojoHandler {
+ public:
+ SameThreadWatchingState(HandleWatcher* watcher,
+ const Handle& handle,
+ MojoHandleSignals handle_signals,
+ MojoDeadline deadline,
+ const base::Callback<void(MojoResult)>& callback)
+ : StateBase(watcher, callback),
+ handle_(handle) {
+ DCHECK(MessagePumpMojo::IsCurrent());
+
+ MessagePumpMojo::current()->AddHandler(
+ this, handle, handle_signals, MojoDeadlineToTimeTicks(deadline));
+ }
+
+ virtual ~SameThreadWatchingState() {
+ if (!got_ready())
+ MessagePumpMojo::current()->RemoveHandler(handle_);
+ }
+
+ private:
+ // MessagePumpMojoHandler overrides:
+ virtual void OnHandleReady(const Handle& handle) override {
+ StopWatchingAndNotifyReady(handle, MOJO_RESULT_OK);
+ }
+
+ virtual void OnHandleError(const Handle& handle, MojoResult result) override {
+ StopWatchingAndNotifyReady(handle, result);
+ }
+
+ void StopWatchingAndNotifyReady(const Handle& handle, MojoResult result) {
+ DCHECK_EQ(handle.value(), handle_.value());
+ MessagePumpMojo::current()->RemoveHandler(handle_);
+ NotifyHandleReady(result);
+ }
+
+ Handle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(SameThreadWatchingState);
+};
+
+// If the thread on which HandleWatcher is used runs a message pump different
+// from MessagePumpMojo, SecondaryThreadWatchingState is used to watch the
+// handle on the handle watcher thread.
+class HandleWatcher::SecondaryThreadWatchingState : public StateBase {
+ public:
+ SecondaryThreadWatchingState(HandleWatcher* watcher,
+ const Handle& handle,
+ MojoHandleSignals handle_signals,
+ MojoDeadline deadline,
+ const base::Callback<void(MojoResult)>& callback)
+ : StateBase(watcher, callback),
+ weak_factory_(this) {
+ watcher_id_ = WatcherThreadManager::GetInstance()->StartWatching(
+ handle,
+ handle_signals,
+ MojoDeadlineToTimeTicks(deadline),
+ base::Bind(&SecondaryThreadWatchingState::NotifyHandleReady,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ virtual ~SecondaryThreadWatchingState() {
+ // If we've been notified the handle is ready (|got_ready()| is true) then
+ // the watch has been implicitly removed by
+ // WatcherThreadManager/MessagePumpMojo and we don't have to call
+ // StopWatching(). To do so would needlessly entail posting a task and
+ // blocking until the background thread services it.
+ if (!got_ready())
+ WatcherThreadManager::GetInstance()->StopWatching(watcher_id_);
+ }
+
+ private:
+ WatcherID watcher_id_;
+
+ // Used to weakly bind |this| to the WatcherThreadManager.
+ base::WeakPtrFactory<SecondaryThreadWatchingState> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SecondaryThreadWatchingState);
+};
+
+// 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);
+
+ if (MessagePumpMojo::IsCurrent()) {
+ state_.reset(new SameThreadWatchingState(
+ this, handle, handle_signals, deadline, callback));
+ } else {
+ state_.reset(new SecondaryThreadWatchingState(
+ this, handle, handle_signals, deadline, callback));
+ }
+}
+
+void HandleWatcher::Stop() {
+ state_.reset();
+}
+
+} // namespace common
+} // namespace mojo
diff --git a/mojo/common/handle_watcher.h b/mojo/common/handle_watcher.h
new file mode 100644
index 0000000..dfcb145
--- /dev/null
+++ b/mojo/common/handle_watcher.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_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();
+
+ // The destructor implicitly stops listening. See Stop() for details.
+ ~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. Blocks
+ // until no longer listening on the handle.
+ void Stop();
+
+ private:
+ class StateBase;
+ class SameThreadWatchingState;
+ class SecondaryThreadWatchingState;
+
+ // If non-NULL Start() has been invoked.
+ scoped_ptr<StateBase> state_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandleWatcher);
+};
+
+} // namespace common
+} // namespace mojo
+
+#endif // MOJO_COMMON_HANDLE_WATCHER_H_
diff --git a/mojo/common/handle_watcher_unittest.cc b/mojo/common/handle_watcher_unittest.cc
new file mode 100644
index 0000000..61913cd
--- /dev/null
+++ b/mojo/common/handle_watcher_unittest.cc
@@ -0,0 +1,467 @@
+// 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/memory/scoped_vector.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/threading/thread.h"
+#include "mojo/common/message_pump_mojo.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 {
+
+enum MessageLoopConfig {
+ MESSAGE_LOOP_CONFIG_DEFAULT = 0,
+ MESSAGE_LOOP_CONFIG_MOJO = 1
+};
+
+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);
+}
+
+scoped_ptr<base::MessageLoop> CreateMessageLoop(MessageLoopConfig config) {
+ scoped_ptr<base::MessageLoop> loop;
+ if (config == MESSAGE_LOOP_CONFIG_DEFAULT)
+ loop.reset(new base::MessageLoop());
+ else
+ loop.reset(new base::MessageLoop(MessagePumpMojo::Create()));
+ return loop.Pass();
+}
+
+// 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::TestWithParam<MessageLoopConfig> {
+ public:
+ HandleWatcherTest() : message_loop_(CreateMessageLoop(GetParam())) {}
+ virtual ~HandleWatcherTest() {
+ test::SetTickClockForTest(NULL);
+ }
+
+ protected:
+ void TearDownMessageLoop() {
+ message_loop_.reset();
+ }
+
+ void InstallTickClock() {
+ test::SetTickClockForTest(&tick_clock_);
+ }
+
+ base::SimpleTestTickClock tick_clock_;
+
+ private:
+ base::ShadowingAtExitManager at_exit_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandleWatcherTest);
+};
+
+INSTANTIATE_TEST_CASE_P(
+ MultipleMessageLoopConfigs, HandleWatcherTest,
+ testing::Values(MESSAGE_LOOP_CONFIG_DEFAULT, MESSAGE_LOOP_CONFIG_MOJO));
+
+// Trivial test case with a single handle to watch.
+TEST_P(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_P(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_P(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_P(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_P(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_P(HandleWatcherTest, AbortedOnMessageLoopDestruction) {
+ bool was_signaled = false;
+ MojoResult result = MOJO_RESULT_OK;
+
+ MessagePipe pipe;
+ HandleWatcher watcher;
+ 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.
+ TearDownMessageLoop();
+
+ EXPECT_TRUE(was_signaled);
+ EXPECT_EQ(MOJO_RESULT_ABORTED, result);
+}
+
+void NeverReached(MojoResult result) {
+ FAIL() << "Callback should never be invoked " << result;
+}
+
+// Called on the main thread when a thread is done. Decrements |active_count|
+// and if |active_count| is zero quits |run_loop|.
+void StressThreadDone(base::RunLoop* run_loop, int* active_count) {
+ (*active_count)--;
+ EXPECT_GE(*active_count, 0);
+ if (*active_count == 0)
+ run_loop->Quit();
+}
+
+// See description of StressTest. This is called on the background thread.
+// |count| is the number of HandleWatchers to create. |active_count| is the
+// number of outstanding threads, |task_runner| the task runner for the main
+// thread and |run_loop| the run loop that should be quit when there are no more
+// threads running. When done StressThreadDone() is invoked on the main thread.
+// |active_count| and |run_loop| should only be used on the main thread.
+void RunStressTest(int count,
+ scoped_refptr<base::TaskRunner> task_runner,
+ base::RunLoop* run_loop,
+ int* active_count) {
+ struct TestData {
+ MessagePipe pipe;
+ HandleWatcher watcher;
+ };
+ ScopedVector<TestData> data_vector;
+ for (int i = 0; i < count; ++i) {
+ if (i % 20 == 0) {
+ // Every so often we wait. This results in some level of thread balancing
+ // as well as making sure HandleWatcher has time to actually start some
+ // watches.
+ 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()));
+ base::MessageLoop::ScopedNestableTaskAllower scoper(
+ base::MessageLoop::current());
+ callback_helper.RunUntilGotCallback();
+ EXPECT_TRUE(callback_helper.got_callback());
+ } else {
+ scoped_ptr<TestData> test_data(new TestData);
+ ASSERT_TRUE(test_data->pipe.handle0.is_valid());
+ test_data->watcher.Start(test_data->pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ base::Bind(&NeverReached));
+ data_vector.push_back(test_data.release());
+ }
+ if (i % 15 == 0)
+ data_vector.clear();
+ }
+ task_runner->PostTask(FROM_HERE,
+ base::Bind(&StressThreadDone, run_loop,
+ active_count));
+}
+
+// This test is meant to stress HandleWatcher. It uses from various threads
+// repeatedly starting and stopping watches. It spins up kThreadCount
+// threads. Each thread creates kWatchCount watches. Every so often each thread
+// writes to a pipe and waits for the response.
+TEST(HandleWatcherCleanEnvironmentTest, StressTest) {
+#if defined(NDEBUG)
+ const int kThreadCount = 15;
+ const int kWatchCount = 400;
+#else
+ const int kThreadCount = 10;
+ const int kWatchCount = 250;
+#endif
+
+ base::ShadowingAtExitManager at_exit;
+ base::MessageLoop message_loop;
+ base::RunLoop run_loop;
+ ScopedVector<base::Thread> threads;
+ int threads_active_counter = kThreadCount;
+ // Starts the threads first and then post the task in hopes of having more
+ // threads running at once.
+ for (int i = 0; i < kThreadCount; ++i) {
+ scoped_ptr<base::Thread> thread(new base::Thread("test thread"));
+ if (i % 2) {
+ base::Thread::Options thread_options;
+ thread_options.message_pump_factory =
+ base::Bind(&MessagePumpMojo::Create);
+ thread->StartWithOptions(thread_options);
+ } else {
+ thread->Start();
+ }
+ threads.push_back(thread.release());
+ }
+ for (int i = 0; i < kThreadCount; ++i) {
+ threads[i]->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&RunStressTest, kWatchCount,
+ message_loop.task_runner(),
+ &run_loop, &threads_active_counter));
+ }
+ run_loop.Run();
+ ASSERT_EQ(0, threads_active_counter);
+}
+
+} // namespace test
+} // namespace common
+} // namespace mojo
diff --git a/mojo/common/message_pump_mojo.cc b/mojo/common/message_pump_mojo.cc
new file mode 100644
index 0000000..91b78d1
--- /dev/null
+++ b/mojo/common/message_pump_mojo.cc
@@ -0,0 +1,278 @@
+// 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/lazy_instance.h"
+#include "base/logging.h"
+#include "base/threading/thread_local.h"
+#include "base/time/time.h"
+#include "mojo/common/message_pump_mojo_handler.h"
+#include "mojo/common/time_helper.h"
+
+namespace mojo {
+namespace common {
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<MessagePumpMojo> >::Leaky
+ g_tls_current_pump = LAZY_INSTANCE_INITIALIZER;
+
+MojoDeadline TimeTicksToMojoDeadline(base::TimeTicks time_ticks,
+ base::TimeTicks now) {
+ // The is_null() check matches that of HandleWatcher as well as how
+ // |delayed_work_time| is used.
+ if (time_ticks.is_null())
+ return MOJO_DEADLINE_INDEFINITE;
+ const int64_t delta = (time_ticks - now).InMicroseconds();
+ return delta < 0 ? static_cast<MojoDeadline>(0) :
+ static_cast<MojoDeadline>(delta);
+}
+
+} // namespace
+
+// 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) {
+ DCHECK(!current())
+ << "There is already a MessagePumpMojo instance on this thread.";
+ g_tls_current_pump.Pointer()->Set(this);
+}
+
+MessagePumpMojo::~MessagePumpMojo() {
+ DCHECK_EQ(this, current());
+ g_tls_current_pump.Pointer()->Set(NULL);
+}
+
+// static
+scoped_ptr<base::MessagePump> MessagePumpMojo::Create() {
+ return scoped_ptr<MessagePump>(new MessagePumpMojo());
+}
+
+// static
+MessagePumpMojo* MessagePumpMojo::current() {
+ return g_tls_current_pump.Pointer()->Get();
+}
+
+void MessagePumpMojo::AddHandler(MessagePumpMojoHandler* handler,
+ const Handle& handle,
+ MojoHandleSignals wait_signals,
+ base::TimeTicks deadline) {
+ CHECK(handler);
+ DCHECK(handle.is_valid());
+ // Assume it's an error if someone tries to reregister an existing handle.
+ CHECK_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;
+}
+
+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:
+ 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 = 0; 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) {
+ // We should never have an invalid argument. If we do it indicates
+ // RemoveHandler() was not invoked and is likely to cause problems else
+ // where in the stack if we ignore it.
+ CHECK(false);
+ } else if (result == MOJO_RESULT_FAILED_PRECONDITION ||
+ result == MOJO_RESULT_CANCELLED) {
+ CHECK_NE(i, 0u); // Indicates the control pipe went bad.
+
+ // Remove the handle first, this way if OnHandleError() tries to remove
+ // the handle our iterator isn't invalidated.
+ CHECK(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) {
+ const MojoResult result =
+ WriteMessageRaw(run_state.write_handle.get(), NULL, 0, NULL, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ // If we can't write we likely won't wake up the thread and there is a strong
+ // chance we'll deadlock.
+ CHECK_EQ(MOJO_RESULT_OK, result);
+}
+
+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 {
+ const base::TimeTicks now(internal::NowTicks());
+ MojoDeadline deadline = TimeTicksToMojoDeadline(run_state.delayed_work_time,
+ now);
+ for (HandleToHandler::const_iterator i = handlers_.begin();
+ i != handlers_.end(); ++i) {
+ deadline = std::min(
+ TimeTicksToMojoDeadline(i->second.deadline, now), deadline);
+ }
+ return deadline;
+}
+
+} // namespace common
+} // namespace mojo
diff --git a/mojo/common/message_pump_mojo.h b/mojo/common/message_pump_mojo.h
new file mode 100644
index 0000000..3ec3457
--- /dev/null
+++ b/mojo/common/message_pump_mojo.h
@@ -0,0 +1,114 @@
+// 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/macros.h"
+#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();
+
+ // Returns the MessagePumpMojo instance of the current thread, if it exists.
+ static MessagePumpMojo* current();
+
+ static bool IsCurrent() { return !!current(); }
+
+ // Registers a MessagePumpMojoHandler for the specified handle. Only one
+ // handler can be registered for a specified handle.
+ // NOTE: a value of 0 for |deadline| indicates an indefinite timeout.
+ 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/mojo/common/message_pump_mojo_handler.h b/mojo/common/message_pump_mojo_handler.h
new file mode 100644
index 0000000..5809051
--- /dev/null
+++ b/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/mojo/common/message_pump_mojo_unittest.cc b/mojo/common/message_pump_mojo_unittest.cc
new file mode 100644
index 0000000..fb5ae24
--- /dev/null
+++ b/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/mojo/common/mojo_common_export.h b/mojo/common/mojo_common_export.h
new file mode 100644
index 0000000..48d21d0
--- /dev/null
+++ b/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/mojo/common/test/BUILD.gn b/mojo/common/test/BUILD.gn
new file mode 100644
index 0000000..ebad97e
--- /dev/null
+++ b/mojo/common/test/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_base.gyp:mojo_run_all_unittests
+source_set("run_all_unittests") {
+ testonly = true
+ deps = [
+ ":test_support_impl",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//mojo/public/c/test_support",
+ "//testing/gtest",
+ ]
+
+ sources = [ "run_all_unittests.cc" ]
+}
+
+# GYP version: mojo/mojo_base.gyp:mojo_run_all_perftests
+source_set("run_all_perftests") {
+ testonly = true
+ deps = [
+ ":test_support_impl",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//mojo/public/c/test_support",
+ ]
+
+ sources = [ "run_all_perftests.cc" ]
+}
+
+
+# GYP version: mojo/mojo_base.gyp:mojo_test_support_impl
+source_set("test_support_impl") {
+ testonly = true
+ deps = [
+ "//base",
+ ]
+
+ sources = [
+ "test_support_impl.cc",
+ "test_support_impl.h",
+ ]
+}
diff --git a/mojo/common/test/DEPS b/mojo/common/test/DEPS
new file mode 100644
index 0000000..61ef5fe
--- /dev/null
+++ b/mojo/common/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/edk/embedder",
+]
diff --git a/mojo/common/test/run_all_perftests.cc b/mojo/common/test/run_all_perftests.cc
new file mode 100644
index 0000000..e3f41aa
--- /dev/null
+++ b/mojo/common/test/run_all_perftests.cc
@@ -0,0 +1,14 @@
+// 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/test/perf_test_suite.h"
+#include "mojo/common/test/test_support_impl.h"
+#include "mojo/edk/embedder/test_embedder.h"
+#include "mojo/public/tests/test_support_private.h"
+
+int main(int argc, char** argv) {
+ mojo::embedder::test::InitWithSimplePlatformSupport();
+ mojo::test::TestSupport::Init(new mojo::test::TestSupportImpl());
+ return base::PerfTestSuite(argc, argv).Run();
+}
diff --git a/mojo/common/test/run_all_unittests.cc b/mojo/common/test/run_all_unittests.cc
new file mode 100644
index 0000000..48c054e
--- /dev/null
+++ b/mojo/common/test/run_all_unittests.cc
@@ -0,0 +1,26 @@
+// 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/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "mojo/common/test/test_support_impl.h"
+#include "mojo/edk/embedder/test_embedder.h"
+#include "mojo/public/tests/test_support_private.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ // Silence death test thread warnings on Linux. We can afford to run our death
+ // tests a little more slowly (< 10 ms per death test on a Z620).
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+
+ base::TestSuite test_suite(argc, argv);
+
+ mojo::embedder::test::InitWithSimplePlatformSupport();
+ mojo::test::TestSupport::Init(new mojo::test::TestSupportImpl());
+
+ return base::LaunchUnitTests(
+ argc, argv, base::Bind(&base::TestSuite::Run,
+ base::Unretained(&test_suite)));
+}
diff --git a/mojo/common/test/test_support_impl.cc b/mojo/common/test/test_support_impl.cc
new file mode 100644
index 0000000..89e6de1
--- /dev/null
+++ b/mojo/common/test/test_support_impl.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/test/test_support_impl.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/test/perf_log.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+base::FilePath ResolveSourceRootRelativePath(const char* relative_path) {
+ base::FilePath path;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &path))
+ return base::FilePath();
+
+ std::vector<std::string> components;
+ base::SplitString(relative_path, '/', &components);
+
+ for (size_t i = 0; i < components.size(); ++i) {
+ if (!components[i].empty())
+ path = path.AppendASCII(components[i]);
+ }
+
+ return path;
+}
+
+} // namespace
+
+TestSupportImpl::TestSupportImpl() {
+}
+
+TestSupportImpl::~TestSupportImpl() {
+}
+
+void TestSupportImpl::LogPerfResult(const char* test_name,
+ double value,
+ const char* units) {
+ base::LogPerfResult(test_name, value, units);
+}
+
+FILE* TestSupportImpl::OpenSourceRootRelativeFile(const char* relative_path) {
+ return base::OpenFile(ResolveSourceRootRelativePath(relative_path), "rb");
+}
+
+char** TestSupportImpl::EnumerateSourceRootRelativeDirectory(
+ const char* relative_path) {
+ std::vector<std::string> names;
+ base::FileEnumerator e(ResolveSourceRootRelativePath(relative_path), false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath name = e.Next(); !name.empty(); name = e.Next())
+ names.push_back(name.BaseName().AsUTF8Unsafe());
+
+ // |names.size() + 1| for null terminator.
+ char** rv = static_cast<char**>(calloc(names.size() + 1, sizeof(char*)));
+ for (size_t i = 0; i < names.size(); ++i)
+ rv[i] = base::strdup(names[i].c_str());
+ return rv;
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/common/test/test_support_impl.h b/mojo/common/test/test_support_impl.h
new file mode 100644
index 0000000..9f69bfe
--- /dev/null
+++ b/mojo/common/test/test_support_impl.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_COMMON_TEST_TEST_SUPPORT_IMPL_H_
+#define MOJO_COMMON_TEST_TEST_SUPPORT_IMPL_H_
+
+#include "base/macros.h"
+#include "mojo/public/tests/test_support_private.h"
+
+namespace mojo {
+namespace test {
+
+class TestSupportImpl : public TestSupport {
+ public:
+ TestSupportImpl();
+ virtual ~TestSupportImpl();
+
+ virtual void LogPerfResult(const char* test_name,
+ double value,
+ const char* units) override;
+ virtual FILE* OpenSourceRootRelativeFile(const char* relative_path) override;
+ virtual char** EnumerateSourceRootRelativeDirectory(const char* relative_path)
+ override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestSupportImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_COMMON_TEST_TEST_SUPPORT_IMPL_H_
diff --git a/mojo/common/time_helper.cc b/mojo/common/time_helper.cc
new file mode 100644
index 0000000..36fd087
--- /dev/null
+++ b/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/mojo/common/time_helper.h b/mojo/common/time_helper.h
new file mode 100644
index 0000000..365ae04
--- /dev/null
+++ b/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/mojo/edk/DEPS b/mojo/edk/DEPS
new file mode 100644
index 0000000..5fe1410
--- /dev/null
+++ b/mojo/edk/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "-mojo",
+ "+mojo/edk",
+ "+mojo/public",
+]
diff --git a/mojo/edk/embedder/BUILD.gn b/mojo/edk/embedder/BUILD.gn
new file mode 100644
index 0000000..9c2f39f
--- /dev/null
+++ b/mojo/edk/embedder/BUILD.gn
@@ -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.
+
+source_set("embedder") {
+ # This isn't really a standalone target, it must be linked into the
+ # mojo_system_impl component.
+ visibility = [ "//mojo/edk/system" ]
+
+ deps = [ "//base", ]
+
+ defines = [
+ "MOJO_SYSTEM_IMPL_IMPLEMENTATION",
+ "MOJO_SYSTEM_IMPLEMENTATION",
+ ]
+
+ configs += [ "//mojo/edk/system:system_config" ]
+
+ sources = [
+ "channel_init.cc",
+ "channel_init.h",
+ "embedder.cc",
+ "embedder.h",
+ "platform_channel_pair.cc",
+ "platform_channel_pair.h",
+ "platform_channel_pair_posix.cc",
+ "platform_channel_pair_win.cc",
+ "platform_channel_utils_posix.cc",
+ "platform_channel_utils_posix.h",
+ "platform_handle.cc",
+ "platform_handle.h",
+ "platform_handle_utils.h",
+ "platform_handle_utils_posix.cc",
+ "platform_handle_utils_win.cc",
+ "platform_handle_vector.h",
+ "platform_shared_buffer.h",
+ "platform_support.h",
+ "scoped_platform_handle.h",
+ "simple_platform_shared_buffer.cc",
+ "simple_platform_shared_buffer.h",
+ "simple_platform_shared_buffer_posix.cc",
+ "simple_platform_shared_buffer_win.cc",
+ "simple_platform_support.cc",
+ "simple_platform_support.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.
+ "test_embedder.cc",
+ "test_embedder.h",
+ ]
+}
+
+source_set("embedder_unittests") {
+ testonly = true
+ visibility = [ "//mojo/edk/system:mojo_system_unittests" ]
+ testonly = true
+
+ deps = [
+ "//base",
+ "//mojo/edk/test:test_support",
+ "//mojo/edk/system",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "embedder_unittest.cc",
+ "platform_channel_pair_posix_unittest.cc",
+ "simple_platform_shared_buffer_unittest.cc",
+ ]
+}
diff --git a/mojo/edk/embedder/DEPS b/mojo/edk/embedder/DEPS
new file mode 100644
index 0000000..c3a0d22
--- /dev/null
+++ b/mojo/edk/embedder/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ "+mojo/edk/system/system_impl_export.h",
+]
+
+specific_include_rules = {
+ # Implementation files may freely access mojo/edk/system, but we don't want to
+ # leak implementation details through the headers.
+ ".*\.cc": [
+ "+mojo/edk/system",
+ ]
+}
diff --git a/mojo/edk/embedder/PRESUBMIT.py b/mojo/edk/embedder/PRESUBMIT.py
new file mode 100644
index 0000000..9cd39bc
--- /dev/null
+++ b/mojo/edk/embedder/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Presubmit script for mojo/edk/embedder.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += input_api.canned_checks.CheckChangeHasOnlyOneEol(input_api,
+ output_api)
+ results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
+ return results
diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md
new file mode 100644
index 0000000..f976fcb
--- /dev/null
+++ b/mojo/edk/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/edk/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/mojo/edk/embedder/channel_init.cc b/mojo/edk/embedder/channel_init.cc
new file mode 100644
index 0000000..9ea984b
--- /dev/null
+++ b/mojo/edk/embedder/channel_init.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.
+
+#include "mojo/edk/embedder/channel_init.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder.h"
+
+namespace mojo {
+namespace embedder {
+
+ChannelInit::ChannelInit() : channel_info_(nullptr), weak_factory_(this) {
+}
+
+ChannelInit::~ChannelInit() {
+ if (channel_info_)
+ DestroyChannel(channel_info_);
+}
+
+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;
+ ScopedMessagePipeHandle message_pipe =
+ CreateChannel(ScopedPlatformHandle(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();
+}
+
+void ChannelInit::WillDestroySoon() {
+ if (channel_info_)
+ WillDestroyChannelSoon(channel_info_);
+}
+
+// static
+void ChannelInit::OnCreatedChannel(base::WeakPtr<ChannelInit> self,
+ scoped_refptr<base::TaskRunner> io_thread,
+ ChannelInfo* channel) {
+ // If |self| was already destroyed, shut the channel down.
+ if (!self) {
+ DestroyChannel(channel);
+ return;
+ }
+
+ self->channel_info_ = channel;
+}
+
+} // namespace embedder
+} // namespace mojo
diff --git a/mojo/edk/embedder/channel_init.h b/mojo/edk/embedder/channel_init.h
new file mode 100644
index 0000000..797cc6d
--- /dev/null
+++ b/mojo/edk/embedder/channel_init.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_EDK_EMBEDDER_CHANNEL_INIT_H_
+#define MOJO_EDK_EMBEDDER_CHANNEL_INIT_H_
+
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace base {
+class MessageLoopProxy;
+class TaskRunner;
+}
+
+namespace mojo {
+namespace embedder {
+struct ChannelInfo;
+}
+
+namespace embedder {
+
+// |ChannelInit| handles creation (and destruction) of the Mojo channel. It is
+// not thread-safe, but may be used on any single thread (with a |MessageLoop|).
+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);
+
+ // Notifies the channel that we (hence it) will soon be destroyed.
+ void WillDestroySoon();
+
+ private:
+ // Invoked on the thread on which this object lives once the channel has been
+ // established. (This is a static method that takes a weak pointer to self,
+ // since we want to destroy the channel even if we're destroyed.)
+ static void OnCreatedChannel(base::WeakPtr<ChannelInit> self,
+ scoped_refptr<base::TaskRunner> io_thread,
+ ChannelInfo* channel);
+
+ scoped_refptr<base::TaskRunner> io_thread_task_runner_;
+
+ // If non-null the channel has been established.
+ ChannelInfo* channel_info_;
+
+ base::WeakPtrFactory<ChannelInit> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelInit);
+};
+
+} // namespace embedder
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_CHANNEL_INIT_H_
diff --git a/mojo/edk/embedder/embedder.cc b/mojo/edk/embedder/embedder.cc
new file mode 100644
index 0000000..b3033c4
--- /dev/null
+++ b/mojo/edk/embedder/embedder.cc
@@ -0,0 +1,235 @@
+// Copyright 2014 The Chromium 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/edk/embedder/embedder.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/entrypoints.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/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 {
+ ChannelInfo() {}
+ ~ChannelInfo() {}
+
+ scoped_refptr<system::Channel> channel;
+
+ // May be null, in which case |DestroyChannelOnIOThread()| must be used (from
+ // the IO thread), instead of |DestroyChannel()|.
+ scoped_refptr<base::TaskRunner> io_thread_task_runner;
+};
+
+namespace {
+
+// Helper for |CreateChannel...()|. (Note: May return null for some failures.)
+scoped_refptr<system::Channel> MakeChannel(
+ system::Core* core,
+ ScopedPlatformHandle platform_handle,
+ scoped_refptr<system::ChannelEndpoint> channel_endpoint) {
+ DCHECK(platform_handle.is_valid());
+
+ // Create and initialize a |system::Channel|.
+ scoped_refptr<system::Channel> channel =
+ new system::Channel(core->platform_support());
+ 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 endpoint.
+ system::MessageInTransit::EndpointId endpoint_id =
+ channel->AttachEndpoint(channel_endpoint);
+ 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::AttachEndpoint() failed";
+ return channel;
+ }
+ CHECK_EQ(endpoint_id, system::Channel::kBootstrapEndpointId);
+
+ channel->RunEndpoint(channel_endpoint, system::Channel::kBootstrapEndpointId);
+
+ return channel;
+}
+
+void CreateChannelHelper(
+ system::Core* core,
+ ScopedPlatformHandle platform_handle,
+ scoped_ptr<ChannelInfo> channel_info,
+ scoped_refptr<system::ChannelEndpoint> channel_endpoint,
+ DidCreateChannelCallback callback,
+ scoped_refptr<base::TaskRunner> callback_thread_task_runner) {
+ channel_info->channel =
+ MakeChannel(core, platform_handle.Pass(), channel_endpoint);
+
+ // Hand the channel back to the embedder.
+ if (callback_thread_task_runner.get()) {
+ callback_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(callback, channel_info.release()));
+ } else {
+ callback.Run(channel_info.release());
+ }
+}
+
+} // namespace
+
+void Init(scoped_ptr<PlatformSupport> platform_support) {
+ system::entrypoints::SetCore(new system::Core(platform_support.Pass()));
+}
+
+// TODO(vtl): Write tests for this.
+ScopedMessagePipeHandle CreateChannelOnIOThread(
+ ScopedPlatformHandle platform_handle,
+ ChannelInfo** channel_info) {
+ DCHECK(platform_handle.is_valid());
+ DCHECK(channel_info);
+
+ scoped_refptr<system::ChannelEndpoint> channel_endpoint;
+ scoped_refptr<system::MessagePipeDispatcher> dispatcher =
+ system::MessagePipeDispatcher::CreateRemoteMessagePipe(&channel_endpoint);
+
+ system::Core* core = system::entrypoints::GetCore();
+ DCHECK(core);
+ ScopedMessagePipeHandle rv(
+ MessagePipeHandle(core->AddDispatcher(dispatcher)));
+
+ *channel_info = new ChannelInfo();
+ (*channel_info)->channel =
+ MakeChannel(core, platform_handle.Pass(), channel_endpoint);
+
+ return rv.Pass();
+}
+
+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());
+
+ scoped_refptr<system::ChannelEndpoint> channel_endpoint;
+ scoped_refptr<system::MessagePipeDispatcher> dispatcher =
+ system::MessagePipeDispatcher::CreateRemoteMessagePipe(&channel_endpoint);
+
+ system::Core* core = system::entrypoints::GetCore();
+ DCHECK(core);
+ ScopedMessagePipeHandle rv(
+ MessagePipeHandle(core->AddDispatcher(dispatcher)));
+
+ scoped_ptr<ChannelInfo> channel_info(new ChannelInfo());
+ channel_info->io_thread_task_runner = io_thread_task_runner;
+
+ if (rv.is_valid()) {
+ io_thread_task_runner->PostTask(FROM_HERE,
+ base::Bind(&CreateChannelHelper,
+ base::Unretained(core),
+ base::Passed(&platform_handle),
+ base::Passed(&channel_info),
+ channel_endpoint,
+ callback,
+ callback_thread_task_runner));
+ } else {
+ (callback_thread_task_runner.get() ? callback_thread_task_runner
+ : io_thread_task_runner)
+ ->PostTask(FROM_HERE, base::Bind(callback, channel_info.release()));
+ }
+
+ return rv.Pass();
+}
+
+void DestroyChannelOnIOThread(ChannelInfo* channel_info) {
+ DCHECK(channel_info);
+ if (!channel_info->channel.get()) {
+ // Presumably, |Init()| on the channel failed.
+ return;
+ }
+
+ channel_info->channel->Shutdown();
+ delete channel_info;
+}
+
+// TODO(vtl): Write tests for this.
+void DestroyChannel(ChannelInfo* channel_info) {
+ DCHECK(channel_info);
+ DCHECK(channel_info->io_thread_task_runner.get());
+
+ if (!channel_info->channel.get()) {
+ // Presumably, |Init()| on the channel failed.
+ return;
+ }
+
+ channel_info->channel->WillShutdownSoon();
+ channel_info->io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&DestroyChannelOnIOThread, channel_info));
+}
+
+void WillDestroyChannelSoon(ChannelInfo* channel_info) {
+ DCHECK(channel_info);
+ channel_info->channel->WillShutdownSoon();
+}
+
+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.get())
+ 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/mojo/edk/embedder/embedder.h b/mojo/edk/embedder/embedder.h
new file mode 100644
index 0000000..ce949d0
--- /dev/null
+++ b/mojo/edk/embedder/embedder.h
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium 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_EDK_EMBEDDER_EMBEDDER_H_
+#define MOJO_EDK_EMBEDDER_EMBEDDER_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task_runner.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace embedder {
+
+class PlatformSupport;
+
+// Must be called first to initialize the (global, singleton) system.
+MOJO_SYSTEM_IMPL_EXPORT void Init(scoped_ptr<PlatformSupport> platform_support);
+
+// A "channel" is a connection on top of an OS "pipe", on top of which Mojo
+// message pipes (etc.) can be multiplexed. It must "live" on some I/O thread.
+//
+// There are two "channel" creation/destruction APIs: the synchronous
+// |CreateChannelOnIOThread()|/|DestroyChannelOnIOThread()|, which must be
+// called from the I/O thread, and the asynchronous
+// |CreateChannel()|/|DestroyChannel()|, which may be called from any thread.
+//
+// Both creation functions have a |platform_handle| argument, which 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).
+//
+// Both (synchronously) return a handle to the bootstrap message pipe on the
+// channel that was (or is to be) created, or |MOJO_HANDLE_INVALID| on error
+// (but note that this will happen only if, e.g., the handle table is full).
+// This message pipe may be used immediately, but since channel operation
+// actually begins asynchronously, other errors may still occur (e.g., if the
+// other end of the "pipe" is closed) and be reported in the usual way to the
+// returned handle.
+//
+// (E.g., a message written immediately to the returned handle will be queued
+// and the handle immediately closed, before the channel begins operation. In
+// this case, the channel should connect as usual, send the queued message, and
+// report that the handle was closed to the other side. The message sent may
+// have other handles, so there may still be message pipes "on" this channel.)
+//
+// Both also produce a |ChannelInfo*| (a pointer to an opaque object) -- the
+// first synchronously and second asynchronously.
+//
+// The destruction functions are similarly synchronous and asynchronous,
+// respectively, and take the |ChannelInfo*| produced by the creation function.
+// (Note: One may call |DestroyChannelOnIOThread()| with the result of
+// |CreateChannel()|, but not |DestroyChannel()| with the result of
+// |CreateChannelOnIOThread()|.)
+//
+// TODO(vtl): Figure out channel teardown.
+struct ChannelInfo;
+
+// Creates a channel; must only be called from the I/O thread. |platform_handle|
+// should be a handle to a connected OS "pipe". Eventually (even on failure),
+// the "out" value |*channel_info| should be passed to
+// |DestroyChannelOnIOThread()| to tear down the channel. Returns a handle to
+// the bootstrap message pipe.
+MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle
+ CreateChannelOnIOThread(ScopedPlatformHandle platform_handle,
+ ChannelInfo** channel_info);
+
+typedef base::Callback<void(ChannelInfo*)> DidCreateChannelCallback;
+// Creates a channel asynchronously; may be called from any thread.
+// |platform_handle| should be a handle to a connected OS "pipe".
+// |io_thread_task_runner| should be the |TaskRunner| for the I/O thread.
+// |callback| should be the callback to call with the |ChannelInfo*|, which
+// should eventually be passed to |DestroyChannel()| (or
+// |DestroyChannelOnIOThread()|) to tear down the channel; the callback will be
+// called using |callback_thread_task_runner| if that is non-null, or otherwise
+// it will be called using |io_thread_task_runner|. Returns a handle to the
+// bootstrap message pipe.
+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);
+
+// Destroys a channel that was created using either |CreateChannelOnIOThread()|
+// or |CreateChannel()|; must only be called from the I/O thread. |channel_info|
+// should be the "out" value from |CreateChannelOnIOThread()| or the value
+// provided to the callback to |CreateChannel()|.
+MOJO_SYSTEM_IMPL_EXPORT void DestroyChannelOnIOThread(
+ ChannelInfo* channel_info);
+
+// Destroys a channel (asynchronously) that was created using |CreateChannel()|
+// (note: NOT |CreateChannelOnIOThread()|); may be called from any thread.
+// |channel_info| should be the value provided to the callback to
+// |CreateChannel()|.
+MOJO_SYSTEM_IMPL_EXPORT void DestroyChannel(ChannelInfo* channel_info);
+
+// Inform the channel that it will soon be destroyed (doing so is optional).
+// This may be called from any thread, but the caller must ensure that this is
+// called before |DestroyChannel()| or |DestroyChannelOnIOThread()|.
+MOJO_SYSTEM_IMPL_EXPORT void WillDestroyChannelSoon(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_EDK_EMBEDDER_EMBEDDER_H_
diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc
new file mode 100644
index 0000000..551ea27
--- /dev/null
+++ b/mojo/edk/embedder/embedder_unittest.cc
@@ -0,0 +1,602 @@
+// Copyright 2014 The Chromium 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/edk/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 "base/test/test_io_thread.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/test_embedder.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/multiprocess_test_helper.h"
+#include "mojo/public/c/system/core.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_(nullptr) {
+ bootstrap_message_pipe_ =
+ CreateChannel(platform_handle.Pass(),
+ io_thread_task_runner_,
+ base::Bind(&ScopedTestChannel::DidCreateChannel,
+ base::Unretained(this)),
+ nullptr)
+ .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_ = nullptr;
+ }
+
+ 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_(base::TestIOThread::kAutoStart) {}
+ virtual ~EmbedderTest() {}
+
+ protected:
+ base::TestIOThread* test_io_thread() { return &test_io_thread_; }
+
+ private:
+ base::TestIOThread test_io_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(EmbedderTest);
+};
+
+TEST_F(EmbedderTest, ChannelsBasic) {
+ mojo::embedder::test::InitWithSimplePlatformSupport();
+
+ {
+ 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)),
+ nullptr,
+ 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,
+ nullptr,
+ nullptr,
+ 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());
+ EXPECT_TRUE(client_channel.channel_info());
+ }
+
+ EXPECT_TRUE(test::Shutdown());
+}
+
+TEST_F(EmbedderTest, ChannelsHandlePassing) {
+ mojo::embedder::test::InitWithSimplePlatformSupport();
+
+ {
+ 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(nullptr, &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)),
+ nullptr,
+ 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)),
+ nullptr,
+ 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,
+ nullptr,
+ nullptr,
+ 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)),
+ nullptr,
+ 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,
+ nullptr,
+ nullptr,
+ 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());
+ EXPECT_TRUE(client_channel.channel_info());
+ }
+
+ 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) {
+ mojo::embedder::test::InitWithSimplePlatformSupport();
+ 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());
+
+ // 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)),
+ nullptr,
+ 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,
+ nullptr,
+ nullptr,
+ 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(nullptr, &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)),
+ nullptr,
+ 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,
+ nullptr,
+ nullptr,
+ 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) {
+ ScopedPlatformHandle client_platform_handle =
+ mojo::test::MultiprocessTestHelper::client_platform_handle.Pass();
+ EXPECT_TRUE(client_platform_handle.is_valid());
+
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ mojo::embedder::test::InitWithSimplePlatformSupport();
+
+ {
+ 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() != nullptr);
+
+ // 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,
+ nullptr,
+ nullptr,
+ 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)),
+ nullptr,
+ 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(nullptr, &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)),
+ nullptr,
+ 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,
+ nullptr,
+ nullptr,
+ 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/mojo/edk/embedder/platform_channel_pair.cc b/mojo/edk/embedder/platform_channel_pair.cc
new file mode 100644
index 0000000..4e88bc9
--- /dev/null
+++ b/mojo/edk/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/edk/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/mojo/edk/embedder/platform_channel_pair.h b/mojo/edk/embedder/platform_channel_pair.h
new file mode 100644
index 0000000..d77c100
--- /dev/null
+++ b/mojo/edk/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_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
+#define MOJO_EDK_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/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/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_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
diff --git a/mojo/edk/embedder/platform_channel_pair_posix.cc b/mojo/edk/embedder/platform_channel_pair_posix.cc
new file mode 100644
index 0000000..f9886b5
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair_posix.cc
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/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/edk/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/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc
new file mode 100644
index 0000000..355e573
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc
@@ -0,0 +1,255 @@
+// Copyright 2014 The Chromium 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/edk/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/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/edk/embedder/platform_channel_utils_posix.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/test/test_utils.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_, nullptr));
+ }
+
+ 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) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ 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 unused;
+ base::ScopedFILE fp(
+ base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ ASSERT_TRUE(fp);
+ ASSERT_EQ(j, 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) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ 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 unused;
+ base::ScopedFILE fp(
+ base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ ASSERT_TRUE(fp);
+ ASSERT_EQ(file_contents.size(),
+ 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/mojo/edk/embedder/platform_channel_pair_win.cc b/mojo/edk/embedder/platform_channel_pair_win.cc
new file mode 100644
index 0000000..1cafc4f
--- /dev/null
+++ b/mojo/edk/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/edk/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/edk/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.
+ nullptr))); // 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),
+ nullptr, TRUE};
+ client_handle_.reset(
+ PlatformHandle(CreateFileW(pipe_name.c_str(),
+ kDesiredAccess,
+ 0, // No sharing.
+ &security_attributes,
+ OPEN_EXISTING,
+ kFlags,
+ nullptr))); // 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, nullptr));
+ 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/mojo/edk/embedder/platform_channel_utils_posix.cc b/mojo/edk/embedder/platform_channel_utils_posix.cc
new file mode 100644
index 0000000..61b573b
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_utils_posix.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/edk/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/mojo/edk/embedder/platform_channel_utils_posix.h b/mojo/edk/embedder/platform_channel_utils_posix.h
new file mode 100644
index 0000000..87176fb
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_utils_posix.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_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
+#define MOJO_EDK_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/edk/embedder/platform_handle.h"
+#include "mojo/edk/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_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
diff --git a/mojo/edk/embedder/platform_handle.cc b/mojo/edk/embedder/platform_handle.cc
new file mode 100644
index 0000000..4675714
--- /dev/null
+++ b/mojo/edk/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/edk/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/mojo/edk/embedder/platform_handle.h b/mojo/edk/embedder/platform_handle.h
new file mode 100644
index 0000000..346301a
--- /dev/null
+++ b/mojo/edk/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_EDK_EMBEDDER_PLATFORM_HANDLE_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_
+
+#include "build/build_config.h"
+#include "mojo/edk/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_EDK_EMBEDDER_PLATFORM_HANDLE_H_
diff --git a/mojo/edk/embedder/platform_handle_utils.h b/mojo/edk/embedder/platform_handle_utils.h
new file mode 100644
index 0000000..f594e21
--- /dev/null
+++ b/mojo/edk/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_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_
+
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/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_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_
diff --git a/mojo/edk/embedder/platform_handle_utils_posix.cc b/mojo/edk/embedder/platform_handle_utils_posix.cc
new file mode 100644
index 0000000..b3be93f
--- /dev/null
+++ b/mojo/edk/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/edk/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/mojo/edk/embedder/platform_handle_utils_win.cc b/mojo/edk/embedder/platform_handle_utils_win.cc
new file mode 100644
index 0000000..12745d0
--- /dev/null
+++ b/mojo/edk/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/edk/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/mojo/edk/embedder/platform_handle_vector.h b/mojo/edk/embedder/platform_handle_vector.h
new file mode 100644
index 0000000..96356b7
--- /dev/null
+++ b/mojo/edk/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_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_utils.h"
+#include "mojo/edk/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_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
diff --git a/mojo/edk/embedder/platform_shared_buffer.h b/mojo/edk/embedder/platform_shared_buffer.h
new file mode 100644
index 0000000..07f8b90
--- /dev/null
+++ b/mojo/edk/embedder/platform_shared_buffer.h
@@ -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.
+
+#ifndef MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace embedder {
+
+class PlatformSharedBufferMapping;
+
+// |PlatformSharedBuffer| is an interface for a thread-safe, ref-counted wrapper
+// around OS-specific shared memory. It has the following features:
+// - A |PlatformSharedBuffer| simply represents a piece of shared memory that
+// *may* be mapped and *may* be shared to another process.
+// - A single |PlatformSharedBuffer| may be mapped multiple times. The
+// lifetime of the mapping (owned by |PlatformSharedBufferMapping|) is
+// separate from the lifetime of the |PlatformSharedBuffer|.
+// - 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 PlatformSharedBuffer
+ : public base::RefCountedThreadSafe<PlatformSharedBuffer> {
+ public:
+ // Gets the size of shared buffer (in number of bytes).
+ virtual size_t GetNumBytes() const = 0;
+
+ // 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.
+ virtual scoped_ptr<PlatformSharedBufferMapping> Map(size_t offset,
+ size_t length) = 0;
+
+ // Checks if |offset| and |length| are valid arguments.
+ virtual bool IsValidMap(size_t offset, size_t length) = 0;
+
+ // Like |Map()|, but doesn't check its arguments (which should have been
+ // preflighted using |IsValidMap()|).
+ virtual scoped_ptr<PlatformSharedBufferMapping> MapNoCheck(size_t offset,
+ size_t length) = 0;
+
+ // Duplicates the underlying platform handle and passes it to the caller.
+ // TODO(vtl): On POSIX, we'll need two FDs to support sharing read-only.
+ virtual ScopedPlatformHandle DuplicatePlatformHandle() = 0;
+
+ // 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.
+ virtual ScopedPlatformHandle PassPlatformHandle() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<PlatformSharedBuffer>;
+
+ PlatformSharedBuffer() {}
+ virtual ~PlatformSharedBuffer() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PlatformSharedBuffer);
+};
+
+// An interface for a mapping of a |PlatformSharedBuffer| (compararable to a
+// "file view" in Windows); see above. Created by (implementations of)
+// |PlatformSharedBuffer::Map()|. Automatically unmaps memory on destruction.
+//
+// Mappings are NOT thread-safe.
+//
+// Note: This is an entirely separate class (instead of
+// |PlatformSharedBuffer::Mapping|) so that it can be forward-declared.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBufferMapping {
+ public:
+ // IMPORTANT: Implementations must implement a destructor that unmaps memory.
+ virtual ~PlatformSharedBufferMapping() {}
+
+ virtual void* GetBase() const = 0;
+ virtual size_t GetLength() const = 0;
+
+ protected:
+ PlatformSharedBufferMapping() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PlatformSharedBufferMapping);
+};
+
+} // namespace embedder
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
diff --git a/mojo/edk/embedder/platform_support.h b/mojo/edk/embedder/platform_support.h
new file mode 100644
index 0000000..4556ee3
--- /dev/null
+++ b/mojo/edk/embedder/platform_support.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_EDK_EMBEDDER_PLATFORM_SUPPORT_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_SUPPORT_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace embedder {
+
+class PlatformSharedBuffer;
+
+// This class is provided by the embedder to implement (typically
+// platform-dependent) things needed by the Mojo system implementation.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformSupport {
+ public:
+ virtual ~PlatformSupport() {}
+
+ virtual PlatformSharedBuffer* CreateSharedBuffer(size_t num_bytes) = 0;
+ virtual PlatformSharedBuffer* CreateSharedBufferFromHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) = 0;
+
+ protected:
+ PlatformSupport() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PlatformSupport);
+};
+
+} // namespace embedder
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_SUPPORT_H_
diff --git a/mojo/edk/embedder/scoped_platform_handle.h b/mojo/edk/embedder/scoped_platform_handle.h
new file mode 100644
index 0000000..2919b04
--- /dev/null
+++ b/mojo/edk/embedder/scoped_platform_handle.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_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_
+#define MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_
+
+#include "base/compiler_specific.h"
+#include "base/move.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/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_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer.cc b/mojo/edk/embedder/simple_platform_shared_buffer.cc
new file mode 100644
index 0000000..866250c
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer.cc
@@ -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.
+
+#include "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_handle_utils.h"
+
+namespace mojo {
+namespace embedder {
+
+// static
+SimplePlatformSharedBuffer* SimplePlatformSharedBuffer::Create(
+ size_t num_bytes) {
+ DCHECK_GT(num_bytes, 0u);
+
+ SimplePlatformSharedBuffer* rv = new SimplePlatformSharedBuffer(num_bytes);
+ if (!rv->Init()) {
+ // We can't just delete it directly, due to the "in destructor" (debug)
+ // check.
+ scoped_refptr<SimplePlatformSharedBuffer> deleter(rv);
+ return nullptr;
+ }
+
+ return rv;
+}
+
+// static
+SimplePlatformSharedBuffer*
+SimplePlatformSharedBuffer::CreateFromPlatformHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) {
+ DCHECK_GT(num_bytes, 0u);
+
+ SimplePlatformSharedBuffer* rv = new SimplePlatformSharedBuffer(num_bytes);
+ if (!rv->InitFromPlatformHandle(platform_handle.Pass())) {
+ // We can't just delete it directly, due to the "in destructor" (debug)
+ // check.
+ scoped_refptr<SimplePlatformSharedBuffer> deleter(rv);
+ return nullptr;
+ }
+
+ return rv;
+}
+
+size_t SimplePlatformSharedBuffer::GetNumBytes() const {
+ return num_bytes_;
+}
+
+scoped_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::Map(
+ size_t offset,
+ size_t length) {
+ if (!IsValidMap(offset, length))
+ return nullptr;
+
+ return MapNoCheck(offset, length);
+}
+
+bool SimplePlatformSharedBuffer::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<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::MapNoCheck(
+ size_t offset,
+ size_t length) {
+ DCHECK(IsValidMap(offset, length));
+ return MapImpl(offset, length);
+}
+
+ScopedPlatformHandle SimplePlatformSharedBuffer::DuplicatePlatformHandle() {
+ return mojo::embedder::DuplicatePlatformHandle(handle_.get());
+}
+
+ScopedPlatformHandle SimplePlatformSharedBuffer::PassPlatformHandle() {
+ DCHECK(HasOneRef());
+ return handle_.Pass();
+}
+
+SimplePlatformSharedBuffer::SimplePlatformSharedBuffer(size_t num_bytes)
+ : num_bytes_(num_bytes) {
+}
+
+SimplePlatformSharedBuffer::~SimplePlatformSharedBuffer() {
+}
+
+SimplePlatformSharedBufferMapping::~SimplePlatformSharedBufferMapping() {
+ Unmap();
+}
+
+void* SimplePlatformSharedBufferMapping::GetBase() const {
+ return base_;
+}
+
+size_t SimplePlatformSharedBufferMapping::GetLength() const {
+ return length_;
+}
+
+} // namespace embedder
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer.h b/mojo/edk/embedder/simple_platform_shared_buffer.h
new file mode 100644
index 0000000..110637e
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer.h
@@ -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.
+
+#ifndef MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SHARED_BUFFER_H_
+#define MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SHARED_BUFFER_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace embedder {
+
+// A simple implementation of |PlatformSharedBuffer|.
+class MOJO_SYSTEM_IMPL_EXPORT SimplePlatformSharedBuffer
+ : public PlatformSharedBuffer {
+ public:
+ // Creates a shared buffer of size |num_bytes| bytes (initially zero-filled).
+ // |num_bytes| must be nonzero. Returns null on failure.
+ static SimplePlatformSharedBuffer* Create(size_t num_bytes);
+
+ static SimplePlatformSharedBuffer* CreateFromPlatformHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle);
+
+ // |PlatformSharedBuffer| implementation:
+ virtual size_t GetNumBytes() const override;
+ virtual scoped_ptr<PlatformSharedBufferMapping> Map(size_t offset,
+ size_t length) override;
+ virtual bool IsValidMap(size_t offset, size_t length) override;
+ virtual scoped_ptr<PlatformSharedBufferMapping> MapNoCheck(
+ size_t offset,
+ size_t length) override;
+ virtual ScopedPlatformHandle DuplicatePlatformHandle() override;
+ virtual ScopedPlatformHandle PassPlatformHandle() override;
+
+ private:
+ explicit SimplePlatformSharedBuffer(size_t num_bytes);
+ virtual ~SimplePlatformSharedBuffer();
+
+ // Implemented in simple_platform_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(ScopedPlatformHandle platform_handle);
+
+ // The platform-dependent part of |Map()|; doesn't check arguments.
+ scoped_ptr<PlatformSharedBufferMapping> 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.
+ ScopedPlatformHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimplePlatformSharedBuffer);
+};
+
+// An implementation of |PlatformSharedBufferMapping|, produced by
+// |SimplePlatformSharedBuffer|.
+class MOJO_SYSTEM_IMPL_EXPORT SimplePlatformSharedBufferMapping
+ : public PlatformSharedBufferMapping {
+ public:
+ virtual ~SimplePlatformSharedBufferMapping();
+
+ virtual void* GetBase() const override;
+ virtual size_t GetLength() const override;
+
+ private:
+ friend class SimplePlatformSharedBuffer;
+
+ SimplePlatformSharedBufferMapping(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(SimplePlatformSharedBufferMapping);
+};
+
+} // namespace embedder
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SHARED_BUFFER_H_
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_posix.cc b/mojo/edk/embedder/simple_platform_shared_buffer_posix.cc
new file mode 100644
index 0000000..ebb55ee
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_posix.cc
@@ -0,0 +1,156 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/embedder/simple_platform_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/files/file_path.h"
+#include "base/files/file_util.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/edk/embedder/platform_handle.h"
+
+// We assume that |size_t| and |off_t| (type for |ftruncate()|) fits in a
+// |uint64_t|.
+static_assert(sizeof(size_t) <= sizeof(uint64_t), "size_t too big");
+static_assert(sizeof(off_t) <= sizeof(uint64_t), "off_t too big");
+
+namespace mojo {
+namespace embedder {
+
+// SimplePlatformSharedBuffer --------------------------------------------------
+
+bool SimplePlatformSharedBuffer::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(PlatformHandle(fd.release()));
+ return true;
+}
+
+bool SimplePlatformSharedBuffer::InitFromPlatformHandle(
+ 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<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::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(nullptr,
+ 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 nullptr;
+ }
+
+ void* base = static_cast<char*>(real_base) + offset_rounding;
+ return make_scoped_ptr(new SimplePlatformSharedBufferMapping(
+ base, length, real_base, real_length));
+}
+
+// SimplePlatformSharedBufferMapping -------------------------------------------
+
+void SimplePlatformSharedBufferMapping::Unmap() {
+ int result = munmap(real_base_, real_length_);
+ PLOG_IF(ERROR, result != 0) << "munmap";
+}
+
+} // namespace embedder
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_unittest.cc b/mojo/edk/embedder/simple_platform_shared_buffer_unittest.cc
new file mode 100644
index 0000000..45d27b9
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_unittest.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/edk/embedder/simple_platform_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 embedder {
+namespace {
+
+TEST(SimplePlatformSharedBufferTest, 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<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(kNumBytes));
+ ASSERT_TRUE(buffer.get());
+
+ // Map it all, scribble some stuff, and then unmap it.
+ {
+ EXPECT_TRUE(buffer->IsValidMap(0, kNumBytes));
+ scoped_ptr<PlatformSharedBufferMapping> mapping(buffer->Map(0, kNumBytes));
+ ASSERT_TRUE(mapping);
+ ASSERT_TRUE(mapping->GetBase());
+ int* stuff = static_cast<int*>(mapping->GetBase());
+ 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<PlatformSharedBufferMapping> mapping1(
+ buffer->MapNoCheck(0, kNumBytes));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ int* stuff1 = static_cast<int*>(mapping1->GetBase());
+ for (size_t i = 0; i < kNumInts; i++)
+ EXPECT_EQ(static_cast<int>(i) + kFudge, stuff1[i]) << i;
+
+ scoped_ptr<PlatformSharedBufferMapping> mapping2(
+ buffer->Map((kNumInts / 2) * sizeof(int), 2 * sizeof(int)));
+ ASSERT_TRUE(mapping2);
+ ASSERT_TRUE(mapping2->GetBase());
+ int* stuff2 = static_cast<int*>(mapping2->GetBase());
+ 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<PlatformSharedBufferMapping> mapping(
+ buffer->Map(sizeof(int), kNumBytes - sizeof(int)));
+ ASSERT_TRUE(mapping);
+ ASSERT_TRUE(mapping->GetBase());
+ int* stuff = static_cast<int*>(mapping->GetBase());
+
+ 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(SimplePlatformSharedBufferTest, InvalidMappings) {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(100));
+ ASSERT_TRUE(buffer.get());
+
+ // 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(SimplePlatformSharedBufferTest, 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<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(kMaxSizeT));
+ // But, assuming |sizeof(size_t) == sizeof(void*)|, mapping all of it should
+ // always fail.
+ if (buffer.get())
+ 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(SimplePlatformSharedBufferTest, MappingsDistinct) {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(100));
+ scoped_ptr<PlatformSharedBufferMapping> mapping1(buffer->Map(0, 100));
+ scoped_ptr<PlatformSharedBufferMapping> mapping2(buffer->Map(0, 100));
+ EXPECT_NE(mapping1->GetBase(), mapping2->GetBase());
+}
+
+TEST(SimplePlatformSharedBufferTest, BufferZeroInitialized) {
+ static const size_t kSizes[] = {10, 100, 1000, 10000, 100000};
+ for (size_t i = 0; i < arraysize(kSizes); i++) {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(kSizes[i]));
+ scoped_ptr<PlatformSharedBufferMapping> 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->GetBase())[j])
+ << "size " << kSizes[i] << ", offset " << j;
+ }
+ }
+}
+
+TEST(SimplePlatformSharedBufferTest, MappingsOutliveBuffer) {
+ scoped_ptr<PlatformSharedBufferMapping> mapping1;
+ scoped_ptr<PlatformSharedBufferMapping> mapping2;
+
+ {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(100));
+ mapping1 = buffer->Map(0, 100).Pass();
+ mapping2 = buffer->Map(50, 50).Pass();
+ static_cast<char*>(mapping1->GetBase())[50] = 'x';
+ }
+
+ EXPECT_EQ('x', static_cast<char*>(mapping2->GetBase())[0]);
+
+ static_cast<char*>(mapping2->GetBase())[1] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
+}
+
+} // namespace
+} // namespace embedder
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_win.cc b/mojo/edk/embedder/simple_platform_shared_buffer_win.cc
new file mode 100644
index 0000000..fd3de83
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_win.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/edk/embedder/simple_platform_shared_buffer.h"
+
+#include <windows.h>
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+
+namespace mojo {
+namespace embedder {
+
+// SimplePlatformSharedBuffer --------------------------------------------------
+
+bool SimplePlatformSharedBuffer::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(PlatformHandle(CreateFileMapping(INVALID_HANDLE_VALUE,
+ nullptr,
+ PAGE_READWRITE,
+ 0,
+ static_cast<DWORD>(num_bytes_),
+ nullptr)));
+ if (!handle_.is_valid()) {
+ PLOG(ERROR) << "CreateFileMapping";
+ return false;
+ }
+
+ return true;
+}
+
+bool SimplePlatformSharedBuffer::InitFromPlatformHandle(
+ ScopedPlatformHandle platform_handle) {
+ DCHECK(!handle_.is_valid());
+
+ // TODO(vtl): Implement.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+scoped_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::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 nullptr;
+ }
+
+ void* base = static_cast<char*>(real_base) + offset_rounding;
+ return make_scoped_ptr(new SimplePlatformSharedBufferMapping(
+ base, length, real_base, real_length));
+}
+
+// SimplePlatformSharedBufferMapping -------------------------------------------
+
+void SimplePlatformSharedBufferMapping::Unmap() {
+ BOOL result = UnmapViewOfFile(real_base_);
+ PLOG_IF(ERROR, !result) << "UnmapViewOfFile";
+}
+
+} // namespace embedder
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_support.cc b/mojo/edk/embedder/simple_platform_support.cc
new file mode 100644
index 0000000..c59cfb3
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_support.cc
@@ -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.
+
+#include "mojo/edk/embedder/simple_platform_support.h"
+
+#include "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+namespace mojo {
+namespace embedder {
+
+PlatformSharedBuffer* SimplePlatformSupport::CreateSharedBuffer(
+ size_t num_bytes) {
+ return SimplePlatformSharedBuffer::Create(num_bytes);
+}
+
+PlatformSharedBuffer* SimplePlatformSupport::CreateSharedBufferFromHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) {
+ return SimplePlatformSharedBuffer::CreateFromPlatformHandle(
+ num_bytes, platform_handle.Pass());
+}
+
+} // namespace embedder
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_support.h b/mojo/edk/embedder/simple_platform_support.h
new file mode 100644
index 0000000..fc5371d
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_support.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_EDK_EMBEDDER_SIMPLE_PLATFORM_SUPPORT_H_
+#define MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SUPPORT_H_
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace embedder {
+
+// A simple implementation of |PlatformSupport|, when sandboxing and
+// multiprocess support are not issues (e.g., in most tests). Note: This class
+// has no state, and different instances of |SimplePlatformSupport| are mutually
+// compatible (i.e., you don't need to use a single instance of it everywhere --
+// you may simply create one whenever/wherever you need it).
+class MOJO_SYSTEM_IMPL_EXPORT SimplePlatformSupport : public PlatformSupport {
+ public:
+ SimplePlatformSupport() {}
+ virtual ~SimplePlatformSupport() {}
+
+ virtual PlatformSharedBuffer* CreateSharedBuffer(size_t num_bytes) override;
+ virtual PlatformSharedBuffer* CreateSharedBufferFromHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SimplePlatformSupport);
+};
+
+} // namespace embedder
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SUPPORT_H_
diff --git a/mojo/edk/embedder/test_embedder.cc b/mojo/edk/embedder/test_embedder.cc
new file mode 100644
index 0000000..95d3be6
--- /dev/null
+++ b/mojo/edk/embedder/test_embedder.cc
@@ -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.
+
+#include "mojo/edk/embedder/test_embedder.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/entrypoints.h"
+#include "mojo/edk/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 {
+
+void InitWithSimplePlatformSupport() {
+ Init(make_scoped_ptr(new SimplePlatformSupport()));
+}
+
+bool Shutdown() {
+ system::Core* core = system::entrypoints::GetCore();
+ CHECK(core);
+ system::entrypoints::SetCore(nullptr);
+
+ bool rv = system::internal::ShutdownCheckNoLeaks(core);
+ delete core;
+ return rv;
+}
+
+} // namespace test
+} // namespace embedder
+
+} // namespace mojo
diff --git a/mojo/edk/embedder/test_embedder.h b/mojo/edk/embedder/test_embedder.h
new file mode 100644
index 0000000..b6203b4
--- /dev/null
+++ b/mojo/edk/embedder/test_embedder.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_EDK_EMBEDDER_TEST_EMBEDDER_H_
+#define MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_
+
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace embedder {
+namespace test {
+
+// Calls |Init()| with a |SimplePlatformSupport| (use this in tests if, e.g.,
+// you don't care about sandboxing, etc.).
+MOJO_SYSTEM_IMPL_EXPORT void InitWithSimplePlatformSupport();
+
+// 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_EDK_EMBEDDER_TEST_EMBEDDER_H_
diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn
new file mode 100644
index 0000000..549af7d
--- /dev/null
+++ b/mojo/edk/system/BUILD.gn
@@ -0,0 +1,147 @@
+# Copyright 2014 The Chromium 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"
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//mojo/edk/embedder",
+ ]
+
+ defines = [
+ "MOJO_SYSTEM_IMPL_IMPLEMENTATION",
+ "MOJO_SYSTEM_IMPLEMENTATION",
+ ]
+
+ all_dependent_configs = [ ":system_config" ]
+
+ sources = [
+ "channel.cc",
+ "channel.h",
+ "channel_endpoint.cc",
+ "channel_endpoint.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",
+ "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",
+ ]
+}
+
+# GYP version: mojo/mojo_base.gyp:mojo_system_unittests
+test("mojo_system_unittests") {
+ deps = [
+ ":system",
+ "//base",
+ "//mojo/edk/embedder:embedder_unittests",
+ "//mojo/edk/test:test_support",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "../test/multiprocess_test_helper_unittest.cc",
+ "channel_unittest.cc",
+ "core_test_base.cc",
+ "core_test_base.h",
+ "core_unittest.cc",
+ "data_pipe_unittest.cc",
+ "dispatcher_unittest.cc",
+ "local_data_pipe_unittest.cc",
+ "memory_unittest.cc",
+ "message_pipe_dispatcher_unittest.cc",
+ "message_pipe_test_utils.cc",
+ "message_pipe_test_utils.h",
+ "message_pipe_unittest.cc",
+ "multiprocess_message_pipe_unittest.cc",
+ "options_validation_unittest.cc",
+ "platform_handle_dispatcher_unittest.cc",
+ "raw_channel_unittest.cc",
+ "remote_message_pipe_unittest.cc",
+ "run_all_unittests.cc",
+ "shared_buffer_dispatcher_unittest.cc",
+ "simple_dispatcher_unittest.cc",
+ "test_utils.cc",
+ "test_utils.h",
+ "waiter_list_unittest.cc",
+ "waiter_test_utils.cc",
+ "waiter_test_utils.h",
+ "waiter_unittest.cc",
+ ]
+}
+
+# GYP version: mojo/mojo_base.gyp:mojo_message_pipe_perftests
+test("mojo_message_pipe_perftests") {
+ deps = [
+ ":system",
+ "//base",
+ "//base/test:test_support",
+ "//base/test:test_support_perf",
+ "//mojo/edk/test:test_support",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "message_pipe_perftest.cc",
+ "message_pipe_test_utils.h",
+ "message_pipe_test_utils.cc",
+ "test_utils.cc",
+ "test_utils.h",
+ ]
+}
diff --git a/mojo/edk/system/PRESUBMIT.py b/mojo/edk/system/PRESUBMIT.py
new file mode 100644
index 0000000..2827571
--- /dev/null
+++ b/mojo/edk/system/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Presubmit script for mojo/edk/system.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += input_api.canned_checks.CheckChangeHasOnlyOneEol(input_api,
+ output_api)
+ results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
+ return results
diff --git a/mojo/edk/system/channel.cc b/mojo/edk/system/channel.cc
new file mode 100644
index 0000000..2cc99b6
--- /dev/null
+++ b/mojo/edk/system/channel.cc
@@ -0,0 +1,531 @@
+// 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/edk/system/channel.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/message_pipe_endpoint.h"
+#include "mojo/edk/system/transport_data.h"
+
+namespace mojo {
+namespace system {
+
+static_assert(Channel::kBootstrapEndpointId !=
+ MessageInTransit::kInvalidEndpointId,
+ "kBootstrapEndpointId is invalid");
+
+STATIC_CONST_MEMBER_DEFINITION const MessageInTransit::EndpointId
+ Channel::kBootstrapEndpointId;
+
+Channel::Channel(embedder::PlatformSupport* platform_support)
+ : platform_support_(platform_support),
+ is_running_(false),
+ is_shutting_down_(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_);
+ 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());
+
+ IdToEndpointMap to_destroy;
+ {
+ base::AutoLock locker(lock_);
+ if (!is_running_)
+ return;
+
+ // Note: Don't reset |raw_channel_|, in case we're being called from within
+ // |OnReadMessage()| or |OnError()|.
+ raw_channel_->Shutdown();
+ is_running_ = false;
+
+ // We need to deal with it outside the lock.
+ std::swap(to_destroy, local_id_to_endpoint_map_);
+ }
+
+ size_t num_live = 0;
+ size_t num_zombies = 0;
+ for (IdToEndpointMap::iterator it = to_destroy.begin();
+ it != to_destroy.end();
+ ++it) {
+ if (it->second->state_ == ChannelEndpoint::STATE_NORMAL) {
+ it->second->OnDisconnect();
+ num_live++;
+ } else {
+ num_zombies++;
+ }
+ it->second->DetachFromChannel();
+ }
+ DVLOG_IF(2, num_live || num_zombies) << "Shut down Channel with " << num_live
+ << " live endpoints and " << num_zombies
+ << " zombies";
+}
+
+void Channel::WillShutdownSoon() {
+ base::AutoLock locker(lock_);
+ is_shutting_down_ = true;
+}
+
+// Note: |endpoint| being a |scoped_refptr| makes this function safe, since it
+// keeps the endpoint alive even after the lock is released. Otherwise, there's
+// the temptation to simply pass the result of |new ChannelEndpoint(...)|
+// directly to this function, which wouldn't be sufficient for safety.
+MessageInTransit::EndpointId Channel::AttachEndpoint(
+ scoped_refptr<ChannelEndpoint> endpoint) {
+ DCHECK(endpoint.get());
+
+ MessageInTransit::EndpointId local_id;
+ {
+ base::AutoLock locker(lock_);
+
+ DLOG_IF(WARNING, is_shutting_down_)
+ << "AttachEndpoint() while shutting down";
+
+ while (next_local_id_ == MessageInTransit::kInvalidEndpointId ||
+ local_id_to_endpoint_map_.find(next_local_id_) !=
+ local_id_to_endpoint_map_.end())
+ next_local_id_++;
+
+ local_id = next_local_id_;
+ next_local_id_++;
+ local_id_to_endpoint_map_[local_id] = endpoint;
+ }
+
+ endpoint->AttachToChannel(this, local_id);
+ return local_id;
+}
+
+// TODO(vtl): This function is currently slightly absurd, but we'll eventually
+// get rid of it and merge it with |AttachEndpoint()|.
+void Channel::RunEndpoint(scoped_refptr<ChannelEndpoint> endpoint,
+ MessageInTransit::EndpointId remote_id) {
+ {
+ base::AutoLock locker(lock_);
+
+ DLOG_IF(WARNING, is_shutting_down_)
+ << "RunMessagePipeEndpoint() while shutting down";
+
+ // Absurdity: |endpoint->state_| is protected by our lock.
+ if (endpoint->state_ != ChannelEndpoint::STATE_NORMAL) {
+ DVLOG(2) << "Ignoring run message pipe endpoint for zombie endpoint";
+ return;
+ }
+ }
+
+ // TODO(vtl): FIXME -- We need to handle the case that message pipe is already
+ // running when we're here due to |kSubtypeChannelRunMessagePipeEndpoint|).
+ endpoint->Run(remote_id);
+}
+
+void Channel::RunRemoteMessagePipeEndpoint(
+ MessageInTransit::EndpointId local_id,
+ MessageInTransit::EndpointId remote_id) {
+#if DCHECK_IS_ON
+ {
+ base::AutoLock locker(lock_);
+ DCHECK(local_id_to_endpoint_map_.find(local_id) !=
+ local_id_to_endpoint_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_) {
+ // 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;
+ }
+
+ DLOG_IF(WARNING, is_shutting_down_) << "WriteMessage() while shutting down";
+ return raw_channel_->WriteMessage(message.Pass());
+}
+
+bool Channel::IsWriteBufferEmpty() {
+ base::AutoLock locker(lock_);
+ if (!is_running_)
+ return true;
+ return raw_channel_->IsWriteBufferEmpty();
+}
+
+void Channel::DetachMessagePipeEndpoint(
+ MessageInTransit::EndpointId local_id,
+ MessageInTransit::EndpointId remote_id) {
+ DCHECK_NE(local_id, MessageInTransit::kInvalidEndpointId);
+
+ // If this is non-null after the locked block, the endpoint should be detached
+ // (and no remove message sent).
+ scoped_refptr<ChannelEndpoint> endpoint_to_detach;
+ {
+ base::AutoLock locker_(lock_);
+ if (!is_running_)
+ return;
+
+ IdToEndpointMap::iterator it = local_id_to_endpoint_map_.find(local_id);
+ DCHECK(it != local_id_to_endpoint_map_.end());
+
+ switch (it->second->state_) {
+ case ChannelEndpoint::STATE_NORMAL:
+ it->second->state_ = ChannelEndpoint::STATE_WAIT_REMOTE_REMOVE_ACK;
+ if (remote_id == MessageInTransit::kInvalidEndpointId)
+ return;
+ // We have to send a remove message (outside the lock).
+ break;
+ case ChannelEndpoint::STATE_WAIT_LOCAL_DETACH:
+ endpoint_to_detach = it->second;
+ local_id_to_endpoint_map_.erase(it);
+ // We have to detach (outside the lock).
+ break;
+ case ChannelEndpoint::STATE_WAIT_REMOTE_REMOVE_ACK:
+ NOTREACHED();
+ return;
+ }
+ }
+ if (endpoint_to_detach.get()) {
+ endpoint_to_detach->DetachFromChannel();
+ 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_);
+}
+
+void Channel::OnReadMessage(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles) {
+ DCHECK(creation_thread_checker_.CalledOnValidThread());
+
+ 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::OnError(Error error) {
+ DCHECK(creation_thread_checker_.CalledOnValidThread());
+
+ switch (error) {
+ case ERROR_READ_SHUTDOWN:
+ // The other side was cleanly closed, so this isn't actually an error.
+ DVLOG(1) << "RawChannel read error (shutdown)";
+ break;
+ case ERROR_READ_BROKEN: {
+ base::AutoLock locker(lock_);
+ LOG_IF(ERROR, !is_shutting_down_)
+ << "RawChannel read error (connection broken)";
+ break;
+ }
+ case ERROR_READ_BAD_MESSAGE:
+ // Receiving a bad message means either a bug, data corruption, or
+ // malicious attack (probably due to some other bug).
+ LOG(ERROR) << "RawChannel read error (received bad message)";
+ break;
+ case ERROR_READ_UNKNOWN:
+ LOG(ERROR) << "RawChannel read error (unknown)";
+ break;
+ case ERROR_WRITE:
+ // Write errors are slightly notable: they probably shouldn't happen under
+ // normal operation (but maybe the other side crashed).
+ LOG(WARNING) << "RawChannel write error";
+ break;
+ }
+ Shutdown();
+}
+
+void Channel::OnReadMessageForDownstream(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles) {
+ DCHECK(creation_thread_checker_.CalledOnValidThread());
+ 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;
+ }
+
+ scoped_refptr<ChannelEndpoint> endpoint;
+ ChannelEndpoint::State state = ChannelEndpoint::STATE_NORMAL;
+ {
+ 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_);
+
+ IdToEndpointMap::const_iterator it =
+ local_id_to_endpoint_map_.find(local_id);
+ if (it != local_id_to_endpoint_map_.end()) {
+ endpoint = it->second;
+ state = it->second->state_;
+ }
+ }
+ if (!endpoint.get()) {
+ 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 buggy (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;
+ }
+
+ // Ignore messages for zombie endpoints (not an error).
+ if (state != ChannelEndpoint::STATE_NORMAL) {
+ DVLOG(2) << "Ignoring downstream message for zombie endpoint (local ID = "
+ << local_id << ", remote ID = " << message_view.source_id() << ")";
+ return;
+ }
+
+ if (!endpoint->OnReadMessage(message_view, platform_handles.Pass())) {
+ HandleLocalError(
+ base::StringPrintf("Failed to enqueue message to local ID %u",
+ static_cast<unsigned>(local_id)));
+ return;
+ }
+}
+
+void Channel::OnReadMessageForChannel(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles) {
+ DCHECK(creation_thread_checker_.CalledOnValidThread());
+ 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 (!OnRunMessagePipeEndpoint(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 (!OnRemoveMessagePipeEndpoint(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 (!OnRemoveMessagePipeEndpointAck(message_view.destination_id())) {
+ HandleRemoteError(
+ "Received invalid channel message to ack remove message pipe");
+ }
+ break;
+ default:
+ HandleRemoteError("Received invalid channel message");
+ NOTREACHED();
+ break;
+ }
+}
+
+bool Channel::OnRunMessagePipeEndpoint(MessageInTransit::EndpointId local_id,
+ MessageInTransit::EndpointId remote_id) {
+ scoped_refptr<ChannelEndpoint> endpoint;
+ {
+ base::AutoLock locker(lock_);
+
+ IdToEndpointMap::iterator it = local_id_to_endpoint_map_.find(local_id);
+ if (it == local_id_to_endpoint_map_.end())
+ return false;
+
+ endpoint = it->second;
+ }
+
+ RunEndpoint(endpoint, remote_id);
+ return true;
+}
+
+bool Channel::OnRemoveMessagePipeEndpoint(
+ MessageInTransit::EndpointId local_id,
+ MessageInTransit::EndpointId remote_id) {
+ DCHECK(creation_thread_checker_.CalledOnValidThread());
+
+ scoped_refptr<ChannelEndpoint> endpoint;
+ {
+ base::AutoLock locker(lock_);
+
+ IdToEndpointMap::iterator it = local_id_to_endpoint_map_.find(local_id);
+ if (it == local_id_to_endpoint_map_.end()) {
+ DVLOG(2) << "Remove message pipe endpoint error: not found";
+ return false;
+ }
+
+ switch (it->second->state_) {
+ case ChannelEndpoint::STATE_NORMAL:
+ // This is the normal case; we'll proceed on to "wait local detach".
+ break;
+
+ case ChannelEndpoint::STATE_WAIT_LOCAL_DETACH:
+ // We can only be in this state because we got a "remove" already, so
+ // getting another such message is invalid.
+ DVLOG(2) << "Remove message pipe endpoint error: wrong state";
+ return false;
+
+ case ChannelEndpoint::STATE_WAIT_REMOTE_REMOVE_ACK:
+ // Remove messages "crossed"; we have to wait for the ack.
+ return true;
+ }
+
+ it->second->state_ = ChannelEndpoint::STATE_WAIT_LOCAL_DETACH;
+ endpoint = it->second;
+ // Send the remove ack message outside the lock.
+ }
+
+ 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->OnDisconnect();
+ return true;
+}
+
+bool Channel::OnRemoveMessagePipeEndpointAck(
+ MessageInTransit::EndpointId local_id) {
+ DCHECK(creation_thread_checker_.CalledOnValidThread());
+
+ scoped_refptr<ChannelEndpoint> endpoint;
+ {
+ base::AutoLock locker(lock_);
+
+ IdToEndpointMap::iterator it = local_id_to_endpoint_map_.find(local_id);
+ if (it == local_id_to_endpoint_map_.end()) {
+ DVLOG(2) << "Remove message pipe endpoint ack error: not found";
+ return false;
+ }
+
+ if (it->second->state_ != ChannelEndpoint::STATE_WAIT_REMOTE_REMOVE_ACK) {
+ DVLOG(2) << "Remove message pipe endpoint ack error: wrong state";
+ return false;
+ }
+
+ endpoint = it->second;
+ local_id_to_endpoint_map_.erase(it);
+ // Detach the endpoint outside the lock.
+ }
+
+ endpoint->DetachFromChannel();
+ 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, nullptr));
+ 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/mojo/edk/system/channel.h b/mojo/edk/system/channel.h
new file mode 100644
index 0000000..7088cbd
--- /dev/null
+++ b/mojo/edk/system/channel.h
@@ -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.
+
+#ifndef MOJO_EDK_SYSTEM_CHANNEL_H_
+#define MOJO_EDK_SYSTEM_CHANNEL_H_
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.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/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+
+namespace embedder {
+class PlatformSupport;
+}
+
+namespace system {
+
+class ChannelEndpoint;
+
+// 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. (Many private methods are restricted to the creation
+// thread.) 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 the lock order (in order of allowable acquisition): |MessagePipe|,
+// |ChannelEndpoint|, |Channel|. Thus |Channel| may not call into
+// |ChannelEndpoint| with |Channel|'s lock held.
+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;
+
+ // |platform_support| (typically owned by |Core|) must remain alive until
+ // after |Shutdown()| is called.
+ explicit Channel(embedder::PlatformSupport* platform_support);
+
+ // 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();
+
+ // Signals that |Shutdown()| will be called soon (this may be called from any
+ // thread, unlike |Shutdown()|). Warnings will be issued if, e.g., messages
+ // are written after this is called; other warnings may be suppressed. (This
+ // may be called multiple times, or not at all.)
+ void WillShutdownSoon();
+
+ // Attaches the given endpoint to this channel. This assigns it a local ID,
+ // which it returns. The first 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): This should be combined with "run", and it should take a
+ // |ChannelEndpoint| instead.
+ // TODO(vtl): Maybe limit the number of attached message pipes.
+ MessageInTransit::EndpointId AttachEndpoint(
+ scoped_refptr<ChannelEndpoint> endpoint);
+
+ // Runs the given endpoint (which must have been attached to this |Channel|,
+ // and not detached), assigning it the specified |remote_id|.
+ void RunEndpoint(scoped_refptr<ChannelEndpoint> endpoint,
+ 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 |AttachEndpoint()| 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;
+
+ embedder::PlatformSupport* platform_support() const {
+ return platform_support_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Channel>;
+ virtual ~Channel();
+
+ // |RawChannel::Delegate| implementation (only called on the creation thread):
+ virtual void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles) override;
+ virtual void OnError(Error error) override;
+
+ // Helpers for |OnReadMessage| (only called on the creation thread):
+ void OnReadMessageForDownstream(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles);
+ void OnReadMessageForChannel(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles);
+
+ // Handles "run message pipe endpoint" messages.
+ bool OnRunMessagePipeEndpoint(MessageInTransit::EndpointId local_id,
+ MessageInTransit::EndpointId remote_id);
+ // Handles "remove message pipe endpoint" messages.
+ bool OnRemoveMessagePipeEndpoint(MessageInTransit::EndpointId local_id,
+ MessageInTransit::EndpointId remote_id);
+ // Handles "remove message pipe endpoint ack" messages.
+ bool OnRemoveMessagePipeEndpointAck(MessageInTransit::EndpointId local_id);
+
+ // Handles errors (e.g., invalid messages) from the remote side. Callable from
+ // any thread.
+ void HandleRemoteError(const base::StringPiece& error_message);
+ // Handles internal errors/failures from the local side. Callable from any
+ // thread.
+ void HandleLocalError(const base::StringPiece& error_message);
+
+ // Helper to send channel control messages. Returns true on success. Should be
+ // called *without* |lock_| held. Callable from any thread.
+ bool SendControlMessage(MessageInTransit::Subtype subtype,
+ MessageInTransit::EndpointId source_id,
+ MessageInTransit::EndpointId destination_id);
+
+ base::ThreadChecker creation_thread_checker_;
+
+ embedder::PlatformSupport* const platform_support_;
+
+ // 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 to the |MessagePipe| should be acquired from
+ // |local_id_to_endpoint_map_| under |lock_| and then the lock released.
+ base::Lock lock_; // Protects the members below.
+
+ scoped_ptr<RawChannel> raw_channel_;
+ bool is_running_;
+ // Set when |WillShutdownSoon()| is called.
+ bool is_shutting_down_;
+
+ typedef base::hash_map<MessageInTransit::EndpointId,
+ scoped_refptr<ChannelEndpoint>> IdToEndpointMap;
+ IdToEndpointMap local_id_to_endpoint_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_EDK_SYSTEM_CHANNEL_H_
diff --git a/mojo/edk/system/channel_endpoint.cc b/mojo/edk/system/channel_endpoint.cc
new file mode 100644
index 0000000..40cb184
--- /dev/null
+++ b/mojo/edk/system/channel_endpoint.cc
@@ -0,0 +1,183 @@
+// Copyright 2014 The Chromium 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/edk/system/channel_endpoint.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/transport_data.h"
+
+namespace mojo {
+namespace system {
+
+ChannelEndpoint::ChannelEndpoint(MessagePipe* message_pipe, unsigned port)
+ : state_(STATE_NORMAL),
+ message_pipe_(message_pipe),
+ port_(port),
+ channel_(),
+ local_id_(MessageInTransit::kInvalidEndpointId),
+ remote_id_(MessageInTransit::kInvalidEndpointId) {
+ DCHECK(message_pipe_.get());
+ DCHECK(port_ == 0 || port_ == 1);
+}
+
+void ChannelEndpoint::TakeMessages(MessageInTransitQueue* message_queue) {
+ DCHECK(paused_message_queue_.IsEmpty());
+ paused_message_queue_.Swap(message_queue);
+}
+
+bool ChannelEndpoint::EnqueueMessage(scoped_ptr<MessageInTransit> message) {
+ DCHECK(message);
+
+ base::AutoLock locker(lock_);
+
+ if (!channel_ || remote_id_ == MessageInTransit::kInvalidEndpointId) {
+ // We may reach here if we haven't been attached or run yet.
+ // TODO(vtl): We may also reach here if the channel is shut down early for
+ // some reason (with live message pipes on it). We can't check |state_| yet,
+ // until it's protected under lock, but in this case we should return false
+ // (and not enqueue any messages).
+ paused_message_queue_.AddMessage(message.Pass());
+ return true;
+ }
+
+ // TODO(vtl): Currently, this only works in the "running" case.
+ DCHECK_NE(remote_id_, MessageInTransit::kInvalidEndpointId);
+
+ return WriteMessageNoLock(message.Pass());
+}
+
+void ChannelEndpoint::DetachFromMessagePipe() {
+ // TODO(vtl): Once |message_pipe_| is under |lock_|, we should null it out
+ // here. For now, get the channel to do so for us.
+
+ scoped_refptr<Channel> channel;
+ {
+ base::AutoLock locker(lock_);
+ DCHECK(message_pipe_.get());
+ message_pipe_ = nullptr;
+
+ if (!channel_)
+ return;
+ DCHECK_NE(local_id_, MessageInTransit::kInvalidEndpointId);
+ // TODO(vtl): Once we combine "run" into "attach", |remote_id_| should valid
+ // here as well.
+ channel = channel_;
+ }
+ // Don't call this under |lock_|, since it'll call us back.
+ // TODO(vtl): This seems pretty suboptimal.
+ channel->DetachMessagePipeEndpoint(local_id_, remote_id_);
+}
+
+void ChannelEndpoint::AttachToChannel(Channel* channel,
+ MessageInTransit::EndpointId local_id) {
+ DCHECK(channel);
+ DCHECK_NE(local_id, MessageInTransit::kInvalidEndpointId);
+
+ base::AutoLock locker(lock_);
+ DCHECK(!channel_);
+ DCHECK_EQ(local_id_, MessageInTransit::kInvalidEndpointId);
+ channel_ = channel;
+ local_id_ = local_id;
+}
+
+void ChannelEndpoint::Run(MessageInTransit::EndpointId remote_id) {
+ DCHECK_NE(remote_id, MessageInTransit::kInvalidEndpointId);
+
+ base::AutoLock locker(lock_);
+ DCHECK(channel_);
+ DCHECK_EQ(remote_id_, MessageInTransit::kInvalidEndpointId);
+ remote_id_ = remote_id;
+
+ while (!paused_message_queue_.IsEmpty()) {
+ LOG_IF(WARNING, !WriteMessageNoLock(paused_message_queue_.GetMessage()))
+ << "Failed to write enqueue message to channel";
+ }
+}
+
+bool ChannelEndpoint::OnReadMessage(
+ const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles) {
+ scoped_ptr<MessageInTransit> message(new MessageInTransit(message_view));
+ scoped_refptr<MessagePipe> message_pipe;
+ unsigned port;
+ {
+ base::AutoLock locker(lock_);
+ DCHECK(channel_);
+ if (!message_pipe_.get()) {
+ // This isn't a failure per se. (It just means that, e.g., the other end
+ // of the message point closed first.)
+ return true;
+ }
+
+ 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(),
+ channel_));
+ }
+
+ // Take a ref, and call |EnqueueMessage()| outside the lock.
+ message_pipe = message_pipe_;
+ port = port_;
+ }
+
+ MojoResult result = message_pipe->EnqueueMessage(
+ MessagePipe::GetPeerPort(port), message.Pass());
+ return (result == MOJO_RESULT_OK);
+}
+
+void ChannelEndpoint::OnDisconnect() {
+ scoped_refptr<MessagePipe> message_pipe;
+ unsigned port;
+ {
+ base::AutoLock locker(lock_);
+ if (!message_pipe_.get())
+ return;
+
+ // Take a ref, and call |Close()| outside the lock.
+ message_pipe = message_pipe_;
+ port = port_;
+ }
+ message_pipe->Close(port);
+}
+
+void ChannelEndpoint::DetachFromChannel() {
+ base::AutoLock locker(lock_);
+ DCHECK(channel_);
+ DCHECK_NE(local_id_, MessageInTransit::kInvalidEndpointId);
+ // TODO(vtl): Once we combine "run" into "attach", |remote_id_| should valid
+ // here as well.
+ channel_ = nullptr;
+ local_id_ = MessageInTransit::kInvalidEndpointId;
+ remote_id_ = MessageInTransit::kInvalidEndpointId;
+}
+
+ChannelEndpoint::~ChannelEndpoint() {
+ DCHECK(!message_pipe_.get());
+ DCHECK(!channel_);
+ DCHECK_EQ(local_id_, MessageInTransit::kInvalidEndpointId);
+ DCHECK_EQ(remote_id_, MessageInTransit::kInvalidEndpointId);
+}
+
+bool ChannelEndpoint::WriteMessageNoLock(scoped_ptr<MessageInTransit> message) {
+ DCHECK(message);
+
+ lock_.AssertAcquired();
+
+ DCHECK(channel_);
+ DCHECK_NE(local_id_, MessageInTransit::kInvalidEndpointId);
+ DCHECK_NE(remote_id_, MessageInTransit::kInvalidEndpointId);
+
+ message->SerializeAndCloseDispatchers(channel_);
+ message->set_source_id(local_id_);
+ message->set_destination_id(remote_id_);
+ return channel_->WriteMessage(message.Pass());
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/channel_endpoint.h b/mojo/edk/system/channel_endpoint.h
new file mode 100644
index 0000000..0ba7aaf
--- /dev/null
+++ b/mojo/edk/system/channel_endpoint.h
@@ -0,0 +1,210 @@
+// Copyright 2014 The Chromium 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_EDK_SYSTEM_CHANNEL_ENDPOINT_H_
+#define MOJO_EDK_SYSTEM_CHANNEL_ENDPOINT_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_in_transit_queue.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace system {
+
+class Channel;
+class MessagePipe;
+
+// TODO(vtl): The plan:
+// - (Done.) Move |Channel::Endpoint| to |ChannelEndpoint|. Make it
+// refcounted, and not copyable. Make |Channel| a friend. Make things work.
+// - (Done.) Give |ChannelEndpoint| a lock. The lock order (in order of
+// allowable acquisition) is: |MessagePipe|, |ChannelEndpoint|, |Channel|.
+// - Stop having |Channel| as a friend.
+// - Move logic from |ProxyMessagePipeEndpoint| into |ChannelEndpoint|. Right
+// now, we have to go through lots of contortions to manipulate state owned
+// by |ProxyMessagePipeEndpoint| (in particular, |Channel::Endpoint| doesn't
+// know about the remote ID; the local ID is duplicated in two places).
+// Hollow out |ProxyMessagePipeEndpoint|, and have it just own a reference
+// to |ChannelEndpoint| (hence the refcounting).
+// - In essence, |ChannelEndpoint| becomes the thing that knows about
+// channel-specific aspects of an endpoint (notably local and remote IDs,
+// and knowledge about handshaking), and mediates between the |Channel| and
+// the |MessagePipe|.
+// - In the end state, |Channel| should no longer need to know about
+// |MessagePipe| and ports (but only |ChannelEndpoint|) and
+// |ProxyMessagePipeEndpoint| should no longer need to know about |Channel|
+// (ditto).
+//
+// Things as they are now, before I change everything (TODO(vtl): update this
+// comment appropriately):
+//
+// Terminology:
+// - "Message pipe endpoint": In the implementation, a |MessagePipe| owns
+// two |MessagePipeEndpoint| objects, one for each port. The
+// |MessagePipeEndpoint| objects are only accessed via the |MessagePipe|
+// (which has the lock), with the additional information of the port
+// number. So as far as the channel is concerned, a message pipe endpoint
+// is a pointer to a |MessagePipe| together with the port number.
+// - The value of |port| in |EndpointInfo| refers to the
+// |ProxyMessagePipeEndpoint| (i.e., the endpoint that is logically on
+// the other side). Messages received by a channel for a message pipe
+// are thus written to the *peer* of this port.
+// - "Attached"/"detached": A message pipe endpoint is attached to a channel
+// if it has a pointer to it. It must be detached before the channel gives
+// up its pointer to it in order to break a reference cycle. (This cycle
+// is needed to allow a channel to be shut down cleanly, without shutting
+// down everything else first.)
+// - "Running" (message pipe endpoint): A message pipe endpoint is running
+// if messages written to it (via some |MessagePipeDispatcher|, to which
+// some |MojoHandle| is assigned) are being transmitted through the
+// channel.
+// - Before a message pipe endpoint is run, it will queue messages.
+// - When a message pipe endpoint is detached from a channel, it is also
+// taken out of the running state. After that point, messages should
+// no longer be written to it.
+// - "Normal" message pipe endpoint (state): The channel itself does not
+// have knowledge of whether a message pipe endpoint has started running
+// yet. It will *receive* messages for a message pipe in either state (but
+// the message pipe endpoint won't *send* messages to the channel if it
+// has not started running).
+// - "Zombie" message pipe endpoint (state): A message pipe endpoint is a
+// zombie if it is still in |local_id_to_endpoint_info_map_|, but the
+// channel is no longer forwarding messages to it (even if it may still be
+// receiving messages for it).
+// - There are various types of zombies, depending on the reason the
+// message pipe endpoint cannot yet be removed.
+// - If the remote side is closed, it will send a "remove" control
+// message. After the channel receives that message (to which it
+// responds with a "remove ack" control message), it knows that it
+// shouldn't receive any more messages for that message pipe endpoint
+// (local ID), but it must wait for the endpoint to detach. (It can't
+// do so without a race, since it can't call into the message pipe
+// under |lock_|.) [TODO(vtl): When I add remotely-allocated IDs,
+// we'll have to remove the |EndpointInfo| from
+// |local_id_to_endpoint_info_map_| -- i.e., remove the local ID,
+// since it's no longer valid and may be reused by the remote side --
+// and keep the |EndpointInfo| alive in some other way.]
+// - If the local side is closed and the message pipe endpoint was
+// already running (so there are no queued messages left to send), it
+// will detach the endpoint, and send a "remove" control message.
+// However, the channel may still receive messages for that endpoint
+// until it receives a "remove ack" control message.
+// - If the local side is closed but the message pipe endpoint was not
+// yet running , the detaching is delayed until after it is run and
+// all the queued messages are sent to the channel. On being detached,
+// things proceed as in one of the above cases. The endpoint is *not*
+// a zombie until it is detached (or a "remove" message is received).
+// [TODO(vtl): Maybe we can get rid of this case? It'd only not yet be
+// running since under the current scheme it wouldn't have a remote ID
+// yet.]
+// - Note that even if the local side is closed, it may still receive a
+// "remove" message from the other side (if the other side is closed
+// simultaneously, and both sides send "remove" messages). In that
+// case, it must still remain alive until it receives the "remove
+// ack" (and it must ack the "remove" message that it received).
+class MOJO_SYSTEM_IMPL_EXPORT ChannelEndpoint
+ : public base::RefCountedThreadSafe<ChannelEndpoint> {
+ public:
+ // TODO(vtl): More comments....
+ ChannelEndpoint(MessagePipe* message_pipe, unsigned port);
+
+ // Takes messages from the given |MessageInTransitQueue|. This must be called
+ // before this object is attached to a channel, and before anyone has a chance
+ // to enqueue any messages.
+ void TakeMessages(MessageInTransitQueue* message_queue);
+
+ // Methods called by |MessagePipe| (via |ProxyMessagePipeEndpoint|):
+
+ // TODO(vtl): This currently only works if we're "running". We'll move the
+ // "paused message queue" here (will this be needed when we have
+ // locally-allocated remote IDs?).
+ bool EnqueueMessage(scoped_ptr<MessageInTransit> message);
+
+ void DetachFromMessagePipe();
+
+ // Methods called by |Channel|:
+
+ // Called by |Channel| when it takes a reference to this object.
+ void AttachToChannel(Channel* channel, MessageInTransit::EndpointId local_id);
+
+ // TODO(vtl): Combine this with |AttachToChannel()|.
+ void Run(MessageInTransit::EndpointId remote_id);
+
+ // Called by |Channel| when it receives a message for the message pipe.
+ bool OnReadMessage(const MessageInTransit::View& message_view,
+ embedder::ScopedPlatformHandleVectorPtr platform_handles);
+
+ // Called by |Channel| to notify that it'll no longer receive messages for the
+ // message pipe (i.e., |OnReadMessage()| will no longer be called).
+ // TODO(vtl): After more simplification, we might be able to get rid of this
+ // (and merge it with |DetachFromChannel()|).
+ void OnDisconnect();
+
+ // Called by |Channel| before it gives up its reference to this object.
+ void DetachFromChannel();
+
+ private:
+ 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,
+ };
+
+ // TODO(vtl): This is totally temporary, until this becomes a proper
+ // self-contained class. See the plan above.
+ friend class Channel;
+
+ friend class base::RefCountedThreadSafe<ChannelEndpoint>;
+ ~ChannelEndpoint();
+
+ // Must be called with |lock_| held.
+ bool WriteMessageNoLock(scoped_ptr<MessageInTransit> message);
+
+ // TODO(vtl): Move these under lock.
+ State state_;
+
+ // TODO(vtl): Move the things above under lock.
+ // Protects the members below.
+ base::Lock lock_;
+
+ // |message_pipe_| must be valid whenever it is non-null. Before
+ // |*message_pipe_| gives up its reference to this object, it must call
+ // |DetachFromMessagePipe()|.
+ // NOTE: This is a |scoped_refptr<>|, rather than a raw pointer, since the
+ // |Channel| needs to keep the |MessagePipe| alive for the "proxy-proxy" case.
+ // Possibly we'll be able to eliminate that case when we have full
+ // multiprocess support.
+ // WARNING: |MessagePipe| methods must not be called under |lock_|. Thus to
+ // make such a call, a reference must first be taken under |lock_| and the
+ // lock released.
+ scoped_refptr<MessagePipe> message_pipe_;
+ unsigned port_;
+
+ // |channel_| must be valid whenever it is non-null. Before |*channel_| gives
+ // up its reference to this object, it must call |DetachFromChannel()|.
+ Channel* channel_;
+ MessageInTransit::EndpointId local_id_;
+ MessageInTransit::EndpointId remote_id_;
+
+ // This queue is used before we're running on a channel and ready to send
+ // messages.
+ MessageInTransitQueue paused_message_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelEndpoint);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_CHANNEL_ENDPOINT_H_
diff --git a/mojo/edk/system/channel_unittest.cc b/mojo/edk/system/channel_unittest.cc
new file mode 100644
index 0000000..0c5d596
--- /dev/null
+++ b/mojo/edk/system/channel_unittest.cc
@@ -0,0 +1,320 @@
+// Copyright 2014 The Chromium 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/edk/system/channel.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/test_io_thread.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/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_(base::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(&platform_support_);
+ }
+
+ void InitChannelOnIOThread() {
+ CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop());
+
+ CHECK(raw_channel_);
+ CHECK(channel_.get());
+ 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_.get());
+ channel_->Shutdown();
+ }
+
+ base::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();
+ }
+
+ embedder::SimplePlatformSupport platform_support_;
+ base::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() = nullptr;
+}
+
+// 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_UNKNOWN;
+ }
+ virtual IOResult ScheduleRead() override {
+ CHECK(false);
+ return IO_FAILED_UNKNOWN;
+ }
+ 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_UNKNOWN;
+ }
+ virtual IOResult ScheduleWriteNoLock() override {
+ CHECK(false);
+ return IO_FAILED_UNKNOWN;
+ }
+ 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() = nullptr;
+}
+
+// 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<ChannelEndpoint> channel_endpoint;
+ scoped_refptr<MessagePipe> mp(
+ MessagePipe::CreateLocalProxy(&channel_endpoint));
+
+ MessageInTransit::EndpointId local_id =
+ channel()->AttachEndpoint(channel_endpoint);
+ 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
+ // |AttachEndpoint()| to indicate whether |Run...()| will necessarily be
+ // called or not. (Then, in the case that it may not be called, this will
+ // return false.)
+ channel()->RunEndpoint(channel_endpoint, 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<ChannelEndpoint> channel_endpoint;
+ scoped_refptr<MessagePipe> mp(
+ MessagePipe::CreateLocalProxy(&channel_endpoint));
+
+ MessageInTransit::EndpointId local_id =
+ channel()->AttachEndpoint(channel_endpoint);
+ EXPECT_EQ(Channel::kBootstrapEndpointId, local_id);
+
+ // TODO(vtl): Currently, we always "expect" a |RunMessagePipeEndpoint()| after
+ // an |AttachEndpoint()| (which is actually incorrect). We need to refactor
+ // |AttachEndpoint()| 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...()|.)
+ channel()->RunEndpoint(channel_endpoint, Channel::kBootstrapEndpointId);
+
+ Waiter waiter;
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ // 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, nullptr));
+ HandleSignalsState hss;
+ mp->RemoveWaiter(0, &waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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<ChannelEndpoint> channel_endpoint;
+ scoped_refptr<MessagePipe> mp(
+ MessagePipe::CreateLocalProxy(&channel_endpoint));
+
+ MessageInTransit::EndpointId local_id =
+ channel()->AttachEndpoint(channel_endpoint);
+ EXPECT_EQ(Channel::kBootstrapEndpointId, local_id);
+
+ channel()->RunEndpoint(channel_endpoint, Channel::kBootstrapEndpointId);
+
+ io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&ChannelTest::ShutdownChannelOnIOThread,
+ base::Unretained(this)));
+
+ Waiter waiter;
+ waiter.Init();
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ mp->Close(0);
+
+ EXPECT_TRUE(channel()->HasOneRef());
+}
+
+// TODO(vtl): More. ------------------------------------------------------------
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/constants.h b/mojo/edk/system/constants.h
new file mode 100644
index 0000000..23f35d8
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_CONSTANTS_H_
+#define MOJO_EDK_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_EDK_SYSTEM_CONSTANTS_H_
diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc
new file mode 100644
index 0000000..c67a626
--- /dev/null
+++ b/mojo/edk/system/core.cc
@@ -0,0 +1,606 @@
+// 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/edk/system/core.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/data_pipe.h"
+#include "mojo/edk/system/data_pipe_consumer_dispatcher.h"
+#include "mojo/edk/system/data_pipe_producer_dispatcher.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/local_data_pipe.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/public/c/system/macros.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.
+
+// TODO(vtl): This should take a |scoped_ptr<PlatformSupport>| as a parameter.
+Core::Core(scoped_ptr<embedder::PlatformSupport> platform_support)
+ : platform_support_(platform_support.Pass()) {
+}
+
+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 nullptr;
+
+ 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,
+ UserPointer<MojoHandleSignalsState> signals_state) {
+ uint32_t unused = static_cast<uint32_t>(-1);
+ HandleSignalsState hss;
+ MojoResult rv = WaitManyInternal(&handle,
+ &signals,
+ 1,
+ deadline,
+ &unused,
+ signals_state.IsNull() ? nullptr : &hss);
+ if (rv != MOJO_RESULT_INVALID_ARGUMENT && !signals_state.IsNull())
+ signals_state.Put(hss);
+ return rv;
+}
+
+MojoResult Core::WaitMany(UserPointer<const MojoHandle> handles,
+ UserPointer<const MojoHandleSignals> signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ UserPointer<uint32_t> result_index,
+ UserPointer<MojoHandleSignalsState> signals_states) {
+ if (num_handles < 1)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (num_handles > kMaxWaitManyNumHandles)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ UserPointer<const MojoHandle>::Reader handles_reader(handles, num_handles);
+ UserPointer<const MojoHandleSignals>::Reader signals_reader(signals,
+ num_handles);
+ uint32_t index = static_cast<uint32_t>(-1);
+ MojoResult rv;
+ if (signals_states.IsNull()) {
+ rv = WaitManyInternal(handles_reader.GetPointer(),
+ signals_reader.GetPointer(),
+ num_handles,
+ deadline,
+ &index,
+ nullptr);
+ } else {
+ UserPointer<MojoHandleSignalsState>::Writer signals_states_writer(
+ signals_states, num_handles);
+ // Note: The |reinterpret_cast| is safe, since |HandleSignalsState| is a
+ // subclass of |MojoHandleSignalsState| that doesn't add any data members.
+ rv = WaitManyInternal(handles_reader.GetPointer(),
+ signals_reader.GetPointer(),
+ num_handles,
+ deadline,
+ &index,
+ reinterpret_cast<HandleSignalsState*>(
+ signals_states_writer.GetPointer()));
+ if (rv != MOJO_RESULT_INVALID_ARGUMENT)
+ signals_states_writer.Commit();
+ }
+ if (index != static_cast<uint32_t>(-1) && !result_index.IsNull())
+ result_index.Put(index);
+ return rv;
+}
+
+MojoResult Core::CreateMessagePipe(
+ UserPointer<const MojoCreateMessagePipeOptions> options,
+ UserPointer<MojoHandle> message_pipe_handle0,
+ UserPointer<MojoHandle> message_pipe_handle1) {
+ MojoCreateMessagePipeOptions validated_options = {};
+ MojoResult result =
+ MessagePipeDispatcher::ValidateCreateOptions(options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ 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(MessagePipe::CreateLocalLocal());
+ dispatcher0->Init(message_pipe, 0);
+ dispatcher1->Init(message_pipe, 1);
+
+ message_pipe_handle0.Put(handle_pair.first);
+ message_pipe_handle1.Put(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,
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ UserPointer<const MojoHandle> handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(message_pipe_handle));
+ if (!dispatcher.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ // Easy case: not sending any handles.
+ if (num_handles == 0)
+ return dispatcher->WriteMessage(bytes, num_bytes, nullptr, 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 (num_handles > kMaxMessageNumHandles)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ UserPointer<const MojoHandle>::Reader handles_reader(handles, num_handles);
+
+ // 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_reader.GetPointer(),
+ 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_reader.GetPointer(), num_handles);
+ } else {
+ handle_table_.RestoreBusyHandles(handles_reader.GetPointer(),
+ num_handles);
+ }
+ }
+
+ return rv;
+}
+
+MojoResult Core::ReadMessage(MojoHandle message_pipe_handle,
+ UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ UserPointer<MojoHandle> handles,
+ UserPointer<uint32_t> num_handles,
+ MojoReadMessageFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(message_pipe_handle));
+ if (!dispatcher.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ uint32_t num_handles_value = num_handles.IsNull() ? 0 : num_handles.Get();
+
+ MojoResult rv;
+ if (num_handles_value == 0) {
+ // Easy case: won't receive any handles.
+ rv = dispatcher->ReadMessage(
+ bytes, num_bytes, nullptr, &num_handles_value, flags);
+ } else {
+ DispatcherVector dispatchers;
+ rv = dispatcher->ReadMessage(
+ bytes, num_bytes, &dispatchers, &num_handles_value, flags);
+ if (!dispatchers.empty()) {
+ DCHECK_EQ(rv, MOJO_RESULT_OK);
+ DCHECK(!num_handles.IsNull());
+ DCHECK_LE(dispatchers.size(), static_cast<size_t>(num_handles_value));
+
+ bool success;
+ UserPointer<MojoHandle>::Writer handles_writer(handles,
+ dispatchers.size());
+ {
+ base::AutoLock locker(handle_table_lock_);
+ success = handle_table_.AddDispatcherVector(
+ dispatchers, handles_writer.GetPointer());
+ }
+ if (success) {
+ handles_writer.Commit();
+ } else {
+ 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].get())
+ dispatchers[i]->Close();
+ }
+ if (rv == MOJO_RESULT_OK)
+ rv = MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+ }
+ }
+
+ if (!num_handles.IsNull())
+ num_handles.Put(num_handles_value);
+ return rv;
+}
+
+MojoResult Core::CreateDataPipe(
+ UserPointer<const MojoCreateDataPipeOptions> options,
+ UserPointer<MojoHandle> data_pipe_producer_handle,
+ UserPointer<MojoHandle> data_pipe_consumer_handle) {
+ MojoCreateDataPipeOptions validated_options = {};
+ MojoResult result =
+ DataPipe::ValidateCreateOptions(options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ 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.Put(handle_pair.first);
+ data_pipe_consumer_handle.Put(handle_pair.second);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::WriteData(MojoHandle data_pipe_producer_handle,
+ UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoWriteDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->WriteData(elements, num_bytes, flags);
+}
+
+MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle,
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoWriteDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher.get())
+ 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.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->EndWriteData(num_bytes_written);
+}
+
+MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle,
+ UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoReadDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->ReadData(elements, num_bytes, flags);
+}
+
+MojoResult Core::BeginReadData(MojoHandle data_pipe_consumer_handle,
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoReadDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher.get())
+ 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.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->EndReadData(num_bytes_read);
+}
+
+MojoResult Core::CreateSharedBuffer(
+ UserPointer<const MojoCreateSharedBufferOptions> options,
+ uint64_t num_bytes,
+ UserPointer<MojoHandle> shared_buffer_handle) {
+ MojoCreateSharedBufferOptions validated_options = {};
+ MojoResult result = SharedBufferDispatcher::ValidateCreateOptions(
+ options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ result = SharedBufferDispatcher::Create(
+ platform_support(), validated_options, num_bytes, &dispatcher);
+ if (result != MOJO_RESULT_OK) {
+ DCHECK(!dispatcher.get());
+ 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.Put(h);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::DuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ UserPointer<const MojoDuplicateBufferHandleOptions> options,
+ UserPointer<MojoHandle> new_buffer_handle) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ // Don't verify |options| here; that's the dispatcher's job.
+ 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.Put(new_handle);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::MapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ UserPointer<void*> buffer,
+ MojoMapBufferFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher.get())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_ptr<embedder::PlatformSharedBufferMapping> mapping;
+ MojoResult result = dispatcher->MapBuffer(offset, num_bytes, flags, &mapping);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ DCHECK(mapping);
+ void* address = mapping->GetBase();
+ {
+ base::AutoLock locker(mapping_table_lock_);
+ result = mapping_table_.AddMapping(mapping.Pass());
+ }
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ buffer.Put(address);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::UnmapBuffer(UserPointer<void> buffer) {
+ base::AutoLock locker(mapping_table_lock_);
+ return mapping_table_.RemoveMapping(buffer.GetPointerValue());
+}
+
+// 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,
+ uint32_t* result_index,
+ HandleSignalsState* signals_states) {
+ DCHECK_GT(num_handles, 0u);
+ DCHECK_EQ(*result_index, static_cast<uint32_t>(-1));
+
+ DispatcherVector dispatchers;
+ dispatchers.reserve(num_handles);
+ for (uint32_t i = 0; i < num_handles; i++) {
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handles[i]);
+ if (!dispatcher.get()) {
+ *result_index = i;
+ 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, signals_states ? &signals_states[i] : nullptr);
+ if (rv != MOJO_RESULT_OK) {
+ *result_index = i;
+ break;
+ }
+ }
+ uint32_t num_added = i;
+
+ if (rv == MOJO_RESULT_ALREADY_EXISTS)
+ rv = MOJO_RESULT_OK; // The i-th one is already "triggered".
+ else if (rv == MOJO_RESULT_OK)
+ rv = waiter.Wait(deadline, result_index);
+
+ // 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,
+ signals_states ? &signals_states[i] : nullptr);
+ }
+ if (signals_states) {
+ for (; i < num_handles; i++)
+ signals_states[i] = dispatchers[i]->GetHandleSignalsState();
+ }
+
+ return rv;
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h
new file mode 100644
index 0000000..5a3c27b
--- /dev/null
+++ b/mojo/edk/system/core.h
@@ -0,0 +1,156 @@
+// 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_EDK_SYSTEM_CORE_H_
+#define MOJO_EDK_SYSTEM_CORE_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/edk/system/handle_table.h"
+#include "mojo/edk/system/mapping_table.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/system_impl_export.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"
+
+namespace mojo {
+
+namespace embedder {
+class PlatformSupport;
+}
+
+namespace system {
+
+class Dispatcher;
+struct HandleSignalsState;
+
+// |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):
+ explicit Core(scoped_ptr<embedder::PlatformSupport> platform_support);
+ 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);
+
+ embedder::PlatformSupport* platform_support() const {
+ return platform_support_.get();
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // System calls implementation:
+ MojoTimeTicks GetTimeTicksNow();
+ MojoResult Close(MojoHandle handle);
+ MojoResult Wait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ UserPointer<MojoHandleSignalsState> signals_state);
+ MojoResult WaitMany(UserPointer<const MojoHandle> handles,
+ UserPointer<const MojoHandleSignals> signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ UserPointer<uint32_t> result_index,
+ UserPointer<MojoHandleSignalsState> signals_states);
+ MojoResult CreateMessagePipe(
+ UserPointer<const MojoCreateMessagePipeOptions> options,
+ UserPointer<MojoHandle> message_pipe_handle0,
+ UserPointer<MojoHandle> message_pipe_handle1);
+ MojoResult WriteMessage(MojoHandle message_pipe_handle,
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ UserPointer<const MojoHandle> handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags);
+ MojoResult ReadMessage(MojoHandle message_pipe_handle,
+ UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ UserPointer<MojoHandle> handles,
+ UserPointer<uint32_t> num_handles,
+ MojoReadMessageFlags flags);
+ MojoResult CreateDataPipe(
+ UserPointer<const MojoCreateDataPipeOptions> options,
+ UserPointer<MojoHandle> data_pipe_producer_handle,
+ UserPointer<MojoHandle> data_pipe_consumer_handle);
+ MojoResult WriteData(MojoHandle data_pipe_producer_handle,
+ UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult BeginWriteData(MojoHandle data_pipe_producer_handle,
+ UserPointer<void*> buffer,
+ UserPointer<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,
+ UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult BeginReadData(MojoHandle data_pipe_consumer_handle,
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult EndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read);
+ MojoResult CreateSharedBuffer(
+ UserPointer<const MojoCreateSharedBufferOptions> options,
+ uint64_t num_bytes,
+ UserPointer<MojoHandle> shared_buffer_handle);
+ MojoResult DuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ UserPointer<const MojoDuplicateBufferHandleOptions> options,
+ UserPointer<MojoHandle> new_buffer_handle);
+ MojoResult MapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ UserPointer<void*> buffer,
+ MojoMapBufferFlags flags);
+ MojoResult UnmapBuffer(UserPointer<void> buffer);
+
+ private:
+ friend bool internal::ShutdownCheckNoLeaks(Core*);
+
+ // Internal implementation of |Wait()| and |WaitMany()|; doesn't do basic
+ // validation of arguments. |*result_index| is only set if the result (whether
+ // success or failure) applies to a specific handle, so its value should be
+ // preinitialized to |static_cast<uint32_t>(-1)|.
+ MojoResult WaitManyInternal(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ HandleSignalsState* signals_states);
+
+ const scoped_ptr<embedder::PlatformSupport> platform_support_;
+
+ // 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_EDK_SYSTEM_CORE_H_
diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc
new file mode 100644
index 0000000..0f2c7cc
--- /dev/null
+++ b/mojo/edk/system/core_test_base.cc
@@ -0,0 +1,356 @@
+// 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/edk/system/core_test_base.h"
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/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(
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags /*flags*/) override {
+ info_->IncrementWriteMessageCallCount();
+ lock().AssertAcquired();
+
+ if (num_bytes > kMaxMessageNumBytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ if (transports)
+ return MOJO_RESULT_UNIMPLEMENTED;
+
+ return MOJO_RESULT_OK;
+ }
+
+ virtual MojoResult ReadMessageImplNoLock(
+ UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags /*flags*/) override {
+ info_->IncrementReadMessageCallCount();
+ lock().AssertAcquired();
+
+ if (num_dispatchers) {
+ *num_dispatchers = 1;
+ if (dispatchers) {
+ // Okay to leave an invalid dispatcher.
+ dispatchers->resize(1);
+ }
+ }
+
+ return MOJO_RESULT_OK;
+ }
+
+ virtual MojoResult WriteDataImplNoLock(
+ UserPointer<const void> /*elements*/,
+ UserPointer<uint32_t> /*num_bytes*/,
+ MojoWriteDataFlags /*flags*/) override {
+ info_->IncrementWriteDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ virtual MojoResult BeginWriteDataImplNoLock(
+ UserPointer<void*> /*buffer*/,
+ UserPointer<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(UserPointer<void> /*elements*/,
+ UserPointer<uint32_t> /*num_bytes*/,
+ MojoReadDataFlags /*flags*/) override {
+ info_->IncrementReadDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ virtual MojoResult BeginReadDataImplNoLock(
+ UserPointer<const void*> /*buffer*/,
+ UserPointer<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*/,
+ HandleSignalsState* signals_state) override {
+ info_->IncrementAddWaiterCallCount();
+ lock().AssertAcquired();
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ virtual void RemoveWaiterImplNoLock(
+ Waiter* /*waiter*/,
+ HandleSignalsState* signals_state) override {
+ info_->IncrementRemoveWaiterCallCount();
+ lock().AssertAcquired();
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ }
+
+ 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(make_scoped_ptr(new embedder::SimplePlatformSupport()));
+}
+
+void CoreTestBase::TearDown() {
+ delete core_;
+ core_ = nullptr;
+}
+
+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/mojo/edk/system/core_test_base.h b/mojo/edk/system/core_test_base.h
new file mode 100644
index 0000000..409f7e1
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_CORE_TEST_BASE_H_
+#define MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.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_EDK_SYSTEM_CORE_TEST_BASE_H_
diff --git a/mojo/edk/system/core_unittest.cc b/mojo/edk/system/core_unittest.cc
new file mode 100644
index 0000000..9adde19
--- /dev/null
+++ b/mojo/edk/system/core_unittest.cc
@@ -0,0 +1,1486 @@
+// 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/edk/system/core.h"
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "mojo/edk/system/core_test_base.h"
+
+namespace mojo {
+namespace system {
+namespace {
+
+const MojoHandleSignalsState kEmptyMojoHandleSignalsState = {0u, 0u};
+const MojoHandleSignalsState kFullMojoHandleSignalsState = {~0u, ~0u};
+
+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,
+ NullUserPointer(),
+ 0,
+ NullUserPointer(),
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteMessageCallCount());
+
+ EXPECT_EQ(0u, info.GetReadMessageCallCount());
+ uint32_t num_bytes = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h,
+ NullUserPointer(),
+ MakeUserPointer(&num_bytes),
+ NullUserPointer(),
+ NullUserPointer(),
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetReadMessageCallCount());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h,
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(2u, info.GetReadMessageCallCount());
+
+ EXPECT_EQ(0u, info.GetWriteDataCallCount());
+ EXPECT_EQ(
+ MOJO_RESULT_UNIMPLEMENTED,
+ core()->WriteData(
+ h, NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteDataCallCount());
+
+ EXPECT_EQ(0u, info.GetBeginWriteDataCallCount());
+ EXPECT_EQ(
+ MOJO_RESULT_UNIMPLEMENTED,
+ core()->BeginWriteData(
+ h, NullUserPointer(), NullUserPointer(), 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, NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetReadDataCallCount());
+
+ EXPECT_EQ(0u, info.GetBeginReadDataCallCount());
+ EXPECT_EQ(
+ MOJO_RESULT_UNIMPLEMENTED,
+ core()->BeginReadData(
+ h, NullUserPointer(), NullUserPointer(), 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,
+ NullUserPointer()));
+ EXPECT_EQ(1u, info.GetAddWaiterCallCount());
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 0, NullUserPointer()));
+ EXPECT_EQ(2u, info.GetAddWaiterCallCount());
+ MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h,
+ ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(3u, info.GetAddWaiterCallCount());
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, NullUserPointer()));
+ EXPECT_EQ(4u, info.GetAddWaiterCallCount());
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(
+ h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, MakeUserPointer(&hss)));
+ EXPECT_EQ(5u, info.GetAddWaiterCallCount());
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ MojoHandleSignals handle_signals = ~MOJO_HANDLE_SIGNAL_NONE;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(MakeUserPointer(&h),
+ MakeUserPointer(&handle_signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+ EXPECT_EQ(6u, info.GetAddWaiterCallCount());
+ uint32_t result_index = static_cast<uint32_t>(-1);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(MakeUserPointer(&h),
+ MakeUserPointer(&handle_signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ NullUserPointer()));
+ EXPECT_EQ(7u, info.GetAddWaiterCallCount());
+ EXPECT_EQ(0u, result_index);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(MakeUserPointer(&h),
+ MakeUserPointer(&handle_signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(8u, info.GetAddWaiterCallCount());
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(MakeUserPointer(&h),
+ MakeUserPointer(&handle_signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(9u, info.GetAddWaiterCallCount());
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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,
+ NullUserPointer()));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(10,
+ ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer()));
+
+ MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(MOJO_HANDLE_INVALID,
+ ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&hss)));
+ // On invalid argument, it shouldn't modify the handle signals state.
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(10,
+ ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&hss)));
+ // On invalid argument, it shouldn't modify the handle signals state.
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+ }
+
+ // |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(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 0,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(NullUserPointer(),
+ MakeUserPointer(signals),
+ 0,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+ // If |num_handles| is invalid, it should leave |result_index| and
+ // |signals_states| alone.
+ // (We use -1 internally; make sure that doesn't leak.)
+ uint32_t result_index = 123;
+ MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(NullUserPointer(),
+ MakeUserPointer(signals),
+ 0,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(123u, result_index);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(MakeUserPointer(handles),
+ NullUserPointer(),
+ 0,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+ // But if a handle is bad, then it should set |result_index| but still leave
+ // |signals_states| alone.
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+
+ MockHandleInfo info[2];
+ handles[0] = CreateMockHandle(&info[0]);
+
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // On invalid argument, it'll leave |signals_states| alone.
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 2,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+ handles[1] = handles[0] + 1; // Invalid handle.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 2,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+ handles[1] = CreateMockHandle(&info[1]);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ 2,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()));
+
+ // TODO(vtl): Test one where we get "failed precondition" only for the
+ // second handle (and the first one is valid to wait on).
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(handles[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(handles[1]));
+ }
+
+ // |CreateMessagePipe()|: Nothing to check (apart from things that cause
+ // death).
+
+ // |WriteMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(MOJO_HANDLE_INVALID,
+ NullUserPointer(),
+ 0,
+ NullUserPointer(),
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+ MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID};
+
+ // 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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ 0,
+ MakeUserPointer(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,
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ // Okay.
+ uint32_t handle_count = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h,
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ MakeUserPointer(&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));
+ }
+}
+
+// These test invalid arguments that should cause death if we're being paranoid
+// about checking arguments (which we would want to do if, e.g., we were in a
+// true "kernel" situation, but we might not want to do otherwise for
+// performance reasons). Probably blatant errors like passing in null pointers
+// (for required pointer arguments) will still cause death, but perhaps not
+// predictably.
+TEST_F(CoreTest, InvalidArgumentsDeath) {
+ const char kMemoryCheckFailedRegex[] = "Check failed";
+
+ // |WaitMany()|:
+ {
+ MojoHandle handle = MOJO_HANDLE_INVALID;
+ MojoHandleSignals signals = ~MOJO_HANDLE_SIGNAL_NONE;
+ EXPECT_DEATH_IF_SUPPORTED(core()->WaitMany(NullUserPointer(),
+ MakeUserPointer(&signals),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(core()->WaitMany(MakeUserPointer(&handle),
+ NullUserPointer(),
+ 1,
+ MOJO_DEADLINE_INDEFINITE,
+ NullUserPointer(),
+ NullUserPointer()),
+ kMemoryCheckFailedRegex);
+ // TODO(vtl): |result_index| and |signals_states| are optional. Test them
+ // with non-null invalid pointers?
+ }
+
+ // |CreateMessagePipe()|:
+ {
+ MojoHandle h;
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->CreateMessagePipe(
+ NullUserPointer(), NullUserPointer(), NullUserPointer()),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->CreateMessagePipe(
+ NullUserPointer(), MakeUserPointer(&h), NullUserPointer()),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->CreateMessagePipe(
+ NullUserPointer(), NullUserPointer(), MakeUserPointer(&h)),
+ kMemoryCheckFailedRegex);
+ }
+
+ // |WriteMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ // Null |handles| with nonzero |num_handles|.
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->WriteMessage(h,
+ NullUserPointer(),
+ 0,
+ NullUserPointer(),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ kMemoryCheckFailedRegex);
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ }
+
+ // |ReadMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ uint32_t handle_count = 1;
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->ReadMessage(h,
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ MakeUserPointer(&handle_count),
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ kMemoryCheckFailedRegex);
+
+ 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];
+ MojoHandleSignalsState hss[2];
+ uint32_t result_index;
+
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->CreateMessagePipe(
+ NullUserPointer(), MakeUserPointer(&h[0]), MakeUserPointer(&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};
+ result_index = static_cast<uint32_t>(-1);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ hss[1] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->WaitMany(MakeUserPointer(h),
+ MakeUserPointer(signals),
+ 2,
+ 0,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(hss)));
+ EXPECT_EQ(static_cast<uint32_t>(-1), result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[1].satisfiable_signals);
+
+ // Try to read anyway.
+ char buffer[1] = {'a'};
+ uint32_t buffer_size = 1;
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ core()->ReadMessage(h[0],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ NullUserPointer(),
+ NullUserPointer(),
+ 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.
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h[0],
+ MOJO_HANDLE_SIGNAL_WRITABLE,
+ 1000000000,
+ MakeUserPointer(&hss[0])));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfiable_signals);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h[1],
+ MOJO_HANDLE_SIGNAL_WRITABLE,
+ 1000000000,
+ MakeUserPointer(&hss[0])));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfiable_signals);
+
+ // Also check that |h[1]| is writable using |WaitMany()|.
+ signals[0] = MOJO_HANDLE_SIGNAL_READABLE;
+ signals[1] = MOJO_HANDLE_SIGNAL_WRITABLE;
+ result_index = static_cast<uint32_t>(-1);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ hss[1] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WaitMany(MakeUserPointer(h),
+ MakeUserPointer(signals),
+ 2,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(hss)));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[1].satisfiable_signals);
+
+ // Write to |h[1]|.
+ buffer[0] = 'b';
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h[1],
+ UserPointer<const void>(buffer),
+ 1,
+ NullUserPointer(),
+ 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;
+ result_index = static_cast<uint32_t>(-1);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ hss[1] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WaitMany(MakeUserPointer(h),
+ MakeUserPointer(signals),
+ 2,
+ MOJO_DEADLINE_INDEFINITE,
+ MakeUserPointer(&result_index),
+ MakeUserPointer(hss)));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[1].satisfiable_signals);
+
+ // Read from |h[0]|.
+ // First, get only the size.
+ buffer_size = 0;
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ core()->ReadMessage(h[0],
+ NullUserPointer(),
+ MakeUserPointer(&buffer_size),
+ NullUserPointer(),
+ NullUserPointer(),
+ 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],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ NullUserPointer(),
+ NullUserPointer(),
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ('b', buffer[0]);
+ EXPECT_EQ(1u, buffer_size);
+
+ // |h[0]| should no longer be readable.
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->Wait(
+ h[0], MOJO_HANDLE_SIGNAL_READABLE, 0, MakeUserPointer(&hss[0])));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfiable_signals);
+
+ // Write to |h[0]|.
+ buffer[0] = 'd';
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h[0],
+ UserPointer<const void>(buffer),
+ 1,
+ NullUserPointer(),
+ 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).
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h[1],
+ MOJO_HANDLE_SIGNAL_WRITABLE,
+ 1000000000,
+ MakeUserPointer(&hss[0])));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss[0].satisfiable_signals);
+
+ // Check that |h[1]| is still readable (for the moment).
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss[0])));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss[0].satisfiable_signals);
+
+ // Discard a message from |h[1]|.
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ core()->ReadMessage(h[1],
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ NullUserPointer(),
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD));
+
+ // |h[1]| is no longer readable (and will never be).
+ hss[0] = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss[0])));
+ EXPECT_EQ(0u, hss[0].satisfied_signals);
+ EXPECT_EQ(0u, hss[0].satisfiable_signals);
+
+ // Try writing to |h[1]|.
+ buffer[0] = 'e';
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WriteMessage(h[1],
+ UserPointer<const void>(buffer),
+ 1,
+ NullUserPointer(),
+ 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;
+ MojoHandleSignalsState hss;
+ MojoHandle h_received;
+
+ MojoHandle h_passing[2];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateMessagePipe(NullUserPointer(),
+ MakeUserPointer(&h_passing[0]),
+ MakeUserPointer(&h_passing[1])));
+
+ // Make sure that |h_passing[]| work properly.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ NullUserPointer(),
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ MakeUserPointer(&h_passing[0]),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(h_passing[0],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ MakeUserPointer(&h_passing[1]),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MojoHandle h_passed[2];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateMessagePipe(NullUserPointer(),
+ MakeUserPointer(&h_passed[0]),
+ MakeUserPointer(&h_passed[1])));
+
+ // Make sure that |h_passed[]| work properly.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passed[0],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ NullUserPointer(),
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passed[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passed[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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],
+ UserPointer<const void>(kWorld),
+ kWorldSize,
+ MakeUserPointer(&h_passed[1]),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ NullUserPointer(),
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_received,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_received,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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.
+ MojoHandleSignalsState hss;
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateDataPipe(
+ NullUserPointer(), MakeUserPointer(&ph), MakeUserPointer(&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.
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(ph, MOJO_HANDLE_SIGNAL_READABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->Wait(ph, MOJO_HANDLE_SIGNAL_WRITABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Consumer should be never-writable, and not yet readable.
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_WRITABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Write.
+ char elements[2] = {'A', 'B'};
+ uint32_t num_bytes = 2u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteData(ph,
+ UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(2u, num_bytes);
+
+ // Consumer should now be readable.
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read one character.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = 1u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch,
+ UserPointer<void>(elements),
+ MakeUserPointer(&num_bytes),
+ MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ('A', elements[0]);
+ EXPECT_EQ(-1, elements[1]);
+
+ // Two-phase write.
+ void* write_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginWriteData(ph,
+ MakeUserPointer(&write_ptr),
+ MakeUserPointer(&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,
+ UserPointer<const void>(elements),
+ MakeUserPointer(&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,
+ NullUserPointer(),
+ MakeUserPointer(&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,
+ NullUserPointer(),
+ MakeUserPointer(&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,
+ NullUserPointer(),
+ MakeUserPointer(&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 = nullptr;
+ num_bytes = 2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginReadData(ch,
+ MakeUserPointer(&read_ptr),
+ MakeUserPointer(&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,
+ NullUserPointer(),
+ MakeUserPointer(&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.
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // TODO(vtl): More.
+
+ // Close the producer.
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ph));
+
+ // The consumer should now be never-readable.
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, MakeUserPointer(&hss)));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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;
+ MojoHandleSignalsState hss;
+
+ MojoHandle h_passing[2];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateMessagePipe(NullUserPointer(),
+ MakeUserPointer(&h_passing[0]),
+ MakeUserPointer(&h_passing[1])));
+
+ MojoHandle ph, ch;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateDataPipe(
+ NullUserPointer(), MakeUserPointer(&ph), MakeUserPointer(&ch)));
+
+ // Send |ch| from |h_passing[0]| to |h_passing[1]|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ MakeUserPointer(&ch),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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,
+ UserPointer<const void>(kWorld),
+ MakeUserPointer(&num_bytes),
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch_received,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch_received,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&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],
+ UserPointer<const void>(kWorld),
+ kWorldSize,
+ MakeUserPointer(&ph),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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,
+ UserPointer<const void>(kHello),
+ MakeUserPointer(&num_bytes),
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch_received,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch_received,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&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 = nullptr;
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginWriteData(ph,
+ MakeUserPointer(&write_ptr),
+ MakeUserPointer(&num_bytes),
+ MOJO_WRITE_DATA_FLAG_NONE));
+ ASSERT_GE(num_bytes, 1u);
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->WriteMessage(h_passing[0],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ MakeUserPointer(&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],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ MakeUserPointer(&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,
+ NullUserPointer()));
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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.
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->Wait(
+ ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Make sure that |ch| can't be sent if it's in a two-phase read.
+ const void* read_ptr = nullptr;
+ num_bytes = 1;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginReadData(ch,
+ MakeUserPointer(&read_ptr),
+ MakeUserPointer(&num_bytes),
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->WriteMessage(h_passing[0],
+ UserPointer<const void>(kHello),
+ kHelloSize,
+ MakeUserPointer(&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],
+ UserPointer<const void>(kWorld),
+ kWorldSize,
+ MakeUserPointer(&ph),
+ 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ ph = MOJO_HANDLE_INVALID;
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1],
+ MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000,
+ MakeUserPointer(&hss)));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = arraysize(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1],
+ UserPointer<void>(buffer),
+ MakeUserPointer(&num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(&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/mojo/edk/system/data_pipe.cc b/mojo/edk/system/data_pipe.cc
new file mode 100644
index 0000000..3aca67a
--- /dev/null
+++ b/mojo/edk/system/data_pipe.cc
@@ -0,0 +1,473 @@
+// 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/edk/system/data_pipe.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/options_validation.h"
+#include "mojo/edk/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(
+ UserPointer<const MojoCreateDataPipeOptions> in_options,
+ MojoCreateDataPipeOptions* out_options) {
+ const MojoCreateDataPipeOptionsFlags kKnownFlags =
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD;
+
+ *out_options = kDefaultCreateOptions;
+ if (in_options.IsNull())
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateDataPipeOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateDataPipeOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(
+ MojoCreateDataPipeOptions, element_num_bytes, reader))
+ return MOJO_RESULT_OK;
+ if (reader.options().element_num_bytes == 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ out_options->element_num_bytes = reader.options().element_num_bytes;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(
+ MojoCreateDataPipeOptions, capacity_num_bytes, reader) ||
+ reader.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 (reader.options().capacity_num_bytes % out_options->element_num_bytes != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (reader.options().capacity_num_bytes > kMaxDataPipeCapacityBytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ out_options->capacity_num_bytes = reader.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(
+ ConsumerGetHandleSignalsStateImplNoLock());
+}
+
+MojoResult DataPipe::ProducerWriteData(UserPointer<const void> elements,
+ UserPointer<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".
+ uint32_t max_num_bytes_to_write = num_bytes.Get();
+ if (max_num_bytes_to_write % element_num_bytes_ != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (max_num_bytes_to_write == 0)
+ return MOJO_RESULT_OK; // Nothing to do.
+
+ uint32_t min_num_bytes_to_write = all_or_none ? max_num_bytes_to_write : 0;
+
+ HandleSignalsState old_consumer_state =
+ ConsumerGetHandleSignalsStateImplNoLock();
+ MojoResult rv = ProducerWriteDataImplNoLock(
+ elements, num_bytes, max_num_bytes_to_write, min_num_bytes_to_write);
+ HandleSignalsState new_consumer_state =
+ ConsumerGetHandleSignalsStateImplNoLock();
+ if (!new_consumer_state.equals(old_consumer_state))
+ AwakeConsumerWaitersForStateChangeNoLock(new_consumer_state);
+ return rv;
+}
+
+MojoResult DataPipe::ProducerBeginWriteData(
+ UserPointer<void*> buffer,
+ UserPointer<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;
+
+ uint32_t min_num_bytes_to_write = 0;
+ if (all_or_none) {
+ min_num_bytes_to_write = buffer_num_bytes.Get();
+ if (min_num_bytes_to_write % element_num_bytes_ != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ MojoResult rv = ProducerBeginWriteDataImplNoLock(
+ buffer, buffer_num_bytes, min_num_bytes_to_write);
+ 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 =
+ ConsumerGetHandleSignalsStateImplNoLock();
+ 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 =
+ ProducerGetHandleSignalsStateImplNoLock();
+ if (new_producer_state.satisfies(MOJO_HANDLE_SIGNAL_WRITABLE))
+ AwakeProducerWaitersForStateChangeNoLock(new_producer_state);
+ HandleSignalsState new_consumer_state =
+ ConsumerGetHandleSignalsStateImplNoLock();
+ if (!new_consumer_state.equals(old_consumer_state))
+ AwakeConsumerWaitersForStateChangeNoLock(new_consumer_state);
+ return rv;
+}
+
+HandleSignalsState DataPipe::ProducerGetHandleSignalsState() {
+ base::AutoLock locker(lock_);
+ DCHECK(has_local_producer_no_lock());
+ return ProducerGetHandleSignalsStateImplNoLock();
+}
+
+MojoResult DataPipe::ProducerAddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ base::AutoLock locker(lock_);
+ DCHECK(has_local_producer_no_lock());
+
+ HandleSignalsState producer_state = ProducerGetHandleSignalsStateImplNoLock();
+ if (producer_state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = producer_state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!producer_state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = producer_state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ producer_waiter_list_->AddWaiter(waiter, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void DataPipe::ProducerRemoveWaiter(Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ base::AutoLock locker(lock_);
+ DCHECK(has_local_producer_no_lock());
+ producer_waiter_list_->RemoveWaiter(waiter);
+ if (signals_state)
+ *signals_state = ProducerGetHandleSignalsStateImplNoLock();
+}
+
+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(
+ ProducerGetHandleSignalsStateImplNoLock());
+}
+
+MojoResult DataPipe::ConsumerReadData(UserPointer<void> elements,
+ UserPointer<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;
+
+ uint32_t max_num_bytes_to_read = num_bytes.Get();
+ if (max_num_bytes_to_read % element_num_bytes_ != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (max_num_bytes_to_read == 0)
+ return MOJO_RESULT_OK; // Nothing to do.
+
+ uint32_t min_num_bytes_to_read = all_or_none ? max_num_bytes_to_read : 0;
+
+ HandleSignalsState old_producer_state =
+ ProducerGetHandleSignalsStateImplNoLock();
+ MojoResult rv = ConsumerReadDataImplNoLock(
+ elements, num_bytes, max_num_bytes_to_read, min_num_bytes_to_read);
+ HandleSignalsState new_producer_state =
+ ProducerGetHandleSignalsStateImplNoLock();
+ if (!new_producer_state.equals(old_producer_state))
+ AwakeProducerWaitersForStateChangeNoLock(new_producer_state);
+ return rv;
+}
+
+MojoResult DataPipe::ConsumerDiscardData(UserPointer<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;
+
+ uint32_t max_num_bytes_to_discard = num_bytes.Get();
+ if (max_num_bytes_to_discard % element_num_bytes_ != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (max_num_bytes_to_discard == 0)
+ return MOJO_RESULT_OK; // Nothing to do.
+
+ uint32_t min_num_bytes_to_discard =
+ all_or_none ? max_num_bytes_to_discard : 0;
+
+ HandleSignalsState old_producer_state =
+ ProducerGetHandleSignalsStateImplNoLock();
+ MojoResult rv = ConsumerDiscardDataImplNoLock(
+ num_bytes, max_num_bytes_to_discard, min_num_bytes_to_discard);
+ HandleSignalsState new_producer_state =
+ ProducerGetHandleSignalsStateImplNoLock();
+ if (!new_producer_state.equals(old_producer_state))
+ AwakeProducerWaitersForStateChangeNoLock(new_producer_state);
+ return rv;
+}
+
+MojoResult DataPipe::ConsumerQueryData(UserPointer<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(
+ UserPointer<const void*> buffer,
+ UserPointer<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;
+
+ uint32_t min_num_bytes_to_read = 0;
+ if (all_or_none) {
+ min_num_bytes_to_read = buffer_num_bytes.Get();
+ if (min_num_bytes_to_read % element_num_bytes_ != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ MojoResult rv = ConsumerBeginReadDataImplNoLock(
+ buffer, buffer_num_bytes, min_num_bytes_to_read);
+ 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 =
+ ProducerGetHandleSignalsStateImplNoLock();
+ 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 =
+ ConsumerGetHandleSignalsStateImplNoLock();
+ if (new_consumer_state.satisfies(MOJO_HANDLE_SIGNAL_READABLE))
+ AwakeConsumerWaitersForStateChangeNoLock(new_consumer_state);
+ HandleSignalsState new_producer_state =
+ ProducerGetHandleSignalsStateImplNoLock();
+ if (!new_producer_state.equals(old_producer_state))
+ AwakeProducerWaitersForStateChangeNoLock(new_producer_state);
+ return rv;
+}
+
+HandleSignalsState DataPipe::ConsumerGetHandleSignalsState() {
+ base::AutoLock locker(lock_);
+ DCHECK(has_local_consumer_no_lock());
+ return ConsumerGetHandleSignalsStateImplNoLock();
+}
+
+MojoResult DataPipe::ConsumerAddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ base::AutoLock locker(lock_);
+ DCHECK(has_local_consumer_no_lock());
+
+ HandleSignalsState consumer_state = ConsumerGetHandleSignalsStateImplNoLock();
+ if (consumer_state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = consumer_state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!consumer_state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = consumer_state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ consumer_waiter_list_->AddWaiter(waiter, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void DataPipe::ConsumerRemoveWaiter(Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ base::AutoLock locker(lock_);
+ DCHECK(has_local_consumer_no_lock());
+ consumer_waiter_list_->RemoveWaiter(waiter);
+ if (signals_state)
+ *signals_state = ConsumerGetHandleSignalsStateImplNoLock();
+}
+
+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() : nullptr),
+ consumer_waiter_list_(has_local_consumer ? new WaiterList() : nullptr),
+ 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(MakeUserPointer(&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/mojo/edk/system/data_pipe.h b/mojo/edk/system/data_pipe.h
new file mode 100644
index 0000000..64b18e2
--- /dev/null
+++ b/mojo/edk/system/data_pipe.h
@@ -0,0 +1,216 @@
+// 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_EDK_SYSTEM_DATA_PIPE_H_
+#define MOJO_EDK_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/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/types.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(
+ UserPointer<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();
+ MojoResult ProducerWriteData(UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ bool all_or_none);
+ MojoResult ProducerBeginWriteData(UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ bool all_or_none);
+ MojoResult ProducerEndWriteData(uint32_t num_bytes_written);
+ HandleSignalsState ProducerGetHandleSignalsState();
+ MojoResult ProducerAddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ void ProducerRemoveWaiter(Waiter* waiter, HandleSignalsState* signals_state);
+ 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(UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ bool all_or_none);
+ MojoResult ConsumerDiscardData(UserPointer<uint32_t> num_bytes,
+ bool all_or_none);
+ MojoResult ConsumerQueryData(UserPointer<uint32_t> num_bytes);
+ MojoResult ConsumerBeginReadData(UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ bool all_or_none);
+ MojoResult ConsumerEndReadData(uint32_t num_bytes_read);
+ HandleSignalsState ConsumerGetHandleSignalsState();
+ MojoResult ConsumerAddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ void ConsumerRemoveWaiter(Waiter* waiter, HandleSignalsState* signals_state);
+ 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.Get()| will be a nonzero multiple of |element_num_bytes_|.
+ virtual MojoResult ProducerWriteDataImplNoLock(
+ UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_write,
+ uint32_t min_num_bytes_to_write) = 0;
+ virtual MojoResult ProducerBeginWriteDataImplNoLock(
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ uint32_t min_num_bytes_to_write) = 0;
+ virtual MojoResult ProducerEndWriteDataImplNoLock(
+ uint32_t num_bytes_written) = 0;
+ // Note: A producer should not be writable during a two-phase write.
+ virtual HandleSignalsState ProducerGetHandleSignalsStateImplNoLock()
+ const = 0;
+
+ virtual void ConsumerCloseImplNoLock() = 0;
+ // |*num_bytes| will be a nonzero multiple of |element_num_bytes_|.
+ virtual MojoResult ConsumerReadDataImplNoLock(
+ UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_read,
+ uint32_t min_num_bytes_to_read) = 0;
+ virtual MojoResult ConsumerDiscardDataImplNoLock(
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_discard,
+ uint32_t min_num_bytes_to_discard) = 0;
+ // |*num_bytes| will be a nonzero multiple of |element_num_bytes_|.
+ virtual MojoResult ConsumerQueryDataImplNoLock(
+ UserPointer<uint32_t> num_bytes) = 0;
+ virtual MojoResult ConsumerBeginReadDataImplNoLock(
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ uint32_t min_num_bytes_to_read) = 0;
+ virtual MojoResult ConsumerEndReadDataImplNoLock(uint32_t num_bytes_read) = 0;
+ // Note: A consumer should not be writable during a two-phase read.
+ virtual HandleSignalsState ConsumerGetHandleSignalsStateImplNoLock()
+ 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_EDK_SYSTEM_DATA_PIPE_H_
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
new file mode 100644
index 0000000..2b3f514
--- /dev/null
+++ b/mojo/edk/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/edk/system/data_pipe_consumer_dispatcher.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/data_pipe.h"
+#include "mojo/edk/system/memory.h"
+
+namespace mojo {
+namespace system {
+
+DataPipeConsumerDispatcher::DataPipeConsumerDispatcher() {
+}
+
+void DataPipeConsumerDispatcher::Init(scoped_refptr<DataPipe> data_pipe) {
+ DCHECK(data_pipe.get());
+ 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_.get());
+}
+
+void DataPipeConsumerDispatcher::CancelAllWaitersNoLock() {
+ lock().AssertAcquired();
+ data_pipe_->ConsumerCancelAllWaiters();
+}
+
+void DataPipeConsumerDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ data_pipe_->ConsumerClose();
+ data_pipe_ = nullptr;
+}
+
+scoped_refptr<Dispatcher>
+DataPipeConsumerDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+
+ scoped_refptr<DataPipeConsumerDispatcher> rv =
+ new DataPipeConsumerDispatcher();
+ rv->Init(data_pipe_);
+ data_pipe_ = nullptr;
+ return scoped_refptr<Dispatcher>(rv.get());
+}
+
+MojoResult DataPipeConsumerDispatcher::ReadDataImplNoLock(
+ UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoReadDataFlags flags) {
+ lock().AssertAcquired();
+
+ 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.IsNull())
+ << "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.IsNull())
+ << "Query mode: ignoring non-null |elements|";
+ return data_pipe_->ConsumerQueryData(num_bytes);
+ }
+
+ return data_pipe_->ConsumerReadData(
+ elements, num_bytes, (flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+}
+
+MojoResult DataPipeConsumerDispatcher::BeginReadDataImplNoLock(
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoReadDataFlags flags) {
+ lock().AssertAcquired();
+
+ // 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);
+}
+
+HandleSignalsState DataPipeConsumerDispatcher::GetHandleSignalsStateImplNoLock()
+ const {
+ lock().AssertAcquired();
+ return data_pipe_->ConsumerGetHandleSignalsState();
+}
+
+MojoResult DataPipeConsumerDispatcher::AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ return data_pipe_->ConsumerAddWaiter(waiter, signals, context, signals_state);
+}
+
+void DataPipeConsumerDispatcher::RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ data_pipe_->ConsumerRemoveWaiter(waiter, signals_state);
+}
+
+bool DataPipeConsumerDispatcher::IsBusyNoLock() const {
+ lock().AssertAcquired();
+ return data_pipe_->ConsumerIsBusy();
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h
new file mode 100644
index 0000000..59c1239
--- /dev/null
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h
@@ -0,0 +1,67 @@
+// 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_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/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(UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoReadDataFlags flags) override;
+ virtual MojoResult BeginReadDataImplNoLock(
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoReadDataFlags flags) override;
+ virtual MojoResult EndReadDataImplNoLock(uint32_t num_bytes_read) override;
+ virtual HandleSignalsState GetHandleSignalsStateImplNoLock() const override;
+ virtual MojoResult AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ virtual void RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) 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_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc
new file mode 100644
index 0000000..0a82c9a
--- /dev/null
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc
@@ -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.
+
+#include "mojo/edk/system/data_pipe_producer_dispatcher.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/data_pipe.h"
+#include "mojo/edk/system/memory.h"
+
+namespace mojo {
+namespace system {
+
+DataPipeProducerDispatcher::DataPipeProducerDispatcher() {
+}
+
+void DataPipeProducerDispatcher::Init(scoped_refptr<DataPipe> data_pipe) {
+ DCHECK(data_pipe.get());
+ 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_.get());
+}
+
+void DataPipeProducerDispatcher::CancelAllWaitersNoLock() {
+ lock().AssertAcquired();
+ data_pipe_->ProducerCancelAllWaiters();
+}
+
+void DataPipeProducerDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ data_pipe_->ProducerClose();
+ data_pipe_ = nullptr;
+}
+
+scoped_refptr<Dispatcher>
+DataPipeProducerDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+
+ scoped_refptr<DataPipeProducerDispatcher> rv =
+ new DataPipeProducerDispatcher();
+ rv->Init(data_pipe_);
+ data_pipe_ = nullptr;
+ return scoped_refptr<Dispatcher>(rv.get());
+}
+
+MojoResult DataPipeProducerDispatcher::WriteDataImplNoLock(
+ UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoWriteDataFlags flags) {
+ lock().AssertAcquired();
+ return data_pipe_->ProducerWriteData(
+ elements, num_bytes, (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+}
+
+MojoResult DataPipeProducerDispatcher::BeginWriteDataImplNoLock(
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoWriteDataFlags flags) {
+ lock().AssertAcquired();
+
+ 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);
+}
+
+HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsStateImplNoLock()
+ const {
+ lock().AssertAcquired();
+ return data_pipe_->ProducerGetHandleSignalsState();
+}
+
+MojoResult DataPipeProducerDispatcher::AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ return data_pipe_->ProducerAddWaiter(waiter, signals, context, signals_state);
+}
+
+void DataPipeProducerDispatcher::RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ data_pipe_->ProducerRemoveWaiter(waiter, signals_state);
+}
+
+bool DataPipeProducerDispatcher::IsBusyNoLock() const {
+ lock().AssertAcquired();
+ return data_pipe_->ProducerIsBusy();
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h
new file mode 100644
index 0000000..a53357a
--- /dev/null
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.h
@@ -0,0 +1,68 @@
+// 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_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/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(UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoWriteDataFlags flags) override;
+ virtual MojoResult BeginWriteDataImplNoLock(
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoWriteDataFlags flags) override;
+ virtual MojoResult EndWriteDataImplNoLock(
+ uint32_t num_bytes_written) override;
+ virtual HandleSignalsState GetHandleSignalsStateImplNoLock() const override;
+ virtual MojoResult AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ virtual void RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) 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_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
diff --git a/mojo/edk/system/data_pipe_unittest.cc b/mojo/edk/system/data_pipe_unittest.cc
new file mode 100644
index 0000000..8d2e4eb
--- /dev/null
+++ b/mojo/edk/system/data_pipe_unittest.cc
@@ -0,0 +1,377 @@
+// 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/edk/system/data_pipe.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "mojo/edk/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(MakeUserPointer(&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(NullUserPointer(), &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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&options), &unused));
+ }
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc
new file mode 100644
index 0000000..5607125
--- /dev/null
+++ b/mojo/edk/system/dispatcher.cc
@@ -0,0 +1,489 @@
+// 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/edk/system/dispatcher.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/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(
+ UserPointer<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(UserPointer<void> bytes,
+ UserPointer<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(UserPointer<const void> elements,
+ UserPointer<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(UserPointer<void*> buffer,
+ UserPointer<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(UserPointer<void> elements,
+ UserPointer<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(UserPointer<const void*> buffer,
+ UserPointer<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(
+ UserPointer<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<embedder::PlatformSharedBufferMapping>* mapping) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return MapBufferImplNoLock(offset, num_bytes, flags, mapping);
+}
+
+HandleSignalsState Dispatcher::GetHandleSignalsState() const {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return HandleSignalsState();
+
+ return GetHandleSignalsStateImplNoLock();
+}
+
+MojoResult Dispatcher::AddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ base::AutoLock locker(lock_);
+ if (is_closed_) {
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ return AddWaiterImplNoLock(waiter, signals, context, signals_state);
+}
+
+void Dispatcher::RemoveWaiter(Waiter* waiter,
+ HandleSignalsState* handle_signals_state) {
+ base::AutoLock locker(lock_);
+ if (is_closed_) {
+ if (handle_signals_state)
+ *handle_signals_state = HandleSignalsState();
+ return;
+ }
+ RemoveWaiterImplNoLock(waiter, handle_signals_state);
+}
+
+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(
+ UserPointer<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(
+ UserPointer<void> /*bytes*/,
+ UserPointer<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(UserPointer<const void> /*elements*/,
+ UserPointer<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(
+ UserPointer<void*> /*buffer*/,
+ UserPointer<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(UserPointer<void> /*elements*/,
+ UserPointer<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(
+ UserPointer<const void*> /*buffer*/,
+ UserPointer<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(
+ UserPointer<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<embedder::PlatformSharedBufferMapping>* /*mapping*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for buffer dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+HandleSignalsState Dispatcher::GetHandleSignalsStateImplNoLock() const {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+ return HandleSignalsState();
+}
+
+MojoResult Dispatcher::AddWaiterImplNoLock(Waiter* /*waiter*/,
+ MojoHandleSignals /*signals*/,
+ uint32_t /*context*/,
+ HandleSignalsState* signals_state) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ return MOJO_RESULT_FAILED_PRECONDITION;
+}
+
+void Dispatcher::RemoveWaiterImplNoLock(Waiter* /*waiter*/,
+ HandleSignalsState* signals_state) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+}
+
+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. But there's no need to cancel waiters: we shouldn't have any (and
+ // shouldn't be in |Core|'s handle table.
+ is_closed_ = true;
+
+#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_ = nullptr;
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h
new file mode 100644
index 0000000..3e2230a
--- /dev/null
+++ b/mojo/edk/system/dispatcher.h
@@ -0,0 +1,405 @@
+// 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_EDK_SYSTEM_DISPATCHER_H_
+#define MOJO_EDK_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/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/system_impl_export.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"
+
+namespace mojo {
+
+namespace embedder {
+class PlatformSharedBufferMapping;
+}
+
+namespace system {
+
+class Channel;
+class Core;
+class Dispatcher;
+class DispatcherTransport;
+class HandleTable;
+class LocalMessagePipeEndpoint;
+class ProxyMessagePipeEndpoint;
+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(UserPointer<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(UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags);
+ MojoResult WriteData(UserPointer<const void> elements,
+ UserPointer<uint32_t> elements_num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult BeginWriteData(UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult EndWriteData(uint32_t num_bytes_written);
+ MojoResult ReadData(UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult BeginReadData(UserPointer<const void*> buffer,
+ UserPointer<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(
+ UserPointer<const MojoDuplicateBufferHandleOptions> options,
+ scoped_refptr<Dispatcher>* new_dispatcher);
+ MojoResult MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<embedder::PlatformSharedBufferMapping>* mapping);
+
+ // Gets the current handle signals state. (The default implementation simply
+ // returns a default-constructed |HandleSignalsState|, i.e., no signals
+ // satisfied or satisfiable.) Note: The state is subject to change from other
+ // threads.
+ HandleSignalsState GetHandleSignalsState() const;
+
+ // 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.
+ //
+ // If |signals_state| is non-null, on *failure* |*signals_state| will be set
+ // to the current handle signals state (on success, it is left untouched).
+ //
+ // 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,
+ HandleSignalsState* signals_state);
+ // Removes a waiter from this dispatcher. (It is valid to call this multiple
+ // times for the same |waiter| on the same object, so long as |AddWaiter()|
+ // was called at most once.) If |signals_state| is non-null, |*signals_state|
+ // will be set to the current handle signals state.
+ void RemoveWaiter(Waiter* waiter, HandleSignalsState* signals_state);
+
+ // 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(
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags);
+ virtual MojoResult ReadMessageImplNoLock(UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags);
+ virtual MojoResult WriteDataImplNoLock(UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoWriteDataFlags flags);
+ virtual MojoResult BeginWriteDataImplNoLock(
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoWriteDataFlags flags);
+ virtual MojoResult EndWriteDataImplNoLock(uint32_t num_bytes_written);
+ virtual MojoResult ReadDataImplNoLock(UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ MojoReadDataFlags flags);
+ virtual MojoResult BeginReadDataImplNoLock(
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ MojoReadDataFlags flags);
+ virtual MojoResult EndReadDataImplNoLock(uint32_t num_bytes_read);
+ virtual MojoResult DuplicateBufferHandleImplNoLock(
+ UserPointer<const MojoDuplicateBufferHandleOptions> options,
+ scoped_refptr<Dispatcher>* new_dispatcher);
+ virtual MojoResult MapBufferImplNoLock(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<embedder::PlatformSharedBufferMapping>* mapping);
+ virtual HandleSignalsState GetHandleSignalsStateImplNoLock() const;
+ virtual MojoResult AddWaiterImplNoLock(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ virtual void RemoveWaiterImplNoLock(Waiter* waiter,
+ HandleSignalsState* signals_state);
+
+ // 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_(nullptr) {}
+
+ 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_EDK_SYSTEM_DISPATCHER_H_
diff --git a/mojo/edk/system/dispatcher_unittest.cc b/mojo/edk/system/dispatcher_unittest.cc
new file mode 100644
index 0000000..d9d526d
--- /dev/null
+++ b/mojo/edk/system/dispatcher_unittest.cc
@@ -0,0 +1,326 @@
+// 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/edk/system/dispatcher.h"
+
+#include "base/macros.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/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/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(
+ NullUserPointer(), 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadMessage(NullUserPointer(),
+ NullUserPointer(),
+ nullptr,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteData(
+ NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginWriteData(
+ NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndWriteData(0));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadData(
+ NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginReadData(
+ NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndReadData(0));
+ Waiter w;
+ w.Init();
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ d->AddWaiter(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ // Okay to remove even if it wasn't added (or was already removed).
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteMessage(
+ NullUserPointer(), 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadMessage(NullUserPointer(),
+ NullUserPointer(),
+ nullptr,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteData(
+ NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginWriteData(
+ NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndWriteData(0));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadData(
+ NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginReadData(
+ NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndReadData(0));
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->AddWaiter(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+}
+
+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(
+ NullUserPointer(), 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ break;
+ case READ_MESSAGE:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->ReadMessage(NullUserPointer(),
+ NullUserPointer(),
+ nullptr,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ break;
+ case WRITE_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->WriteData(NullUserPointer(),
+ NullUserPointer(),
+ MOJO_WRITE_DATA_FLAG_NONE));
+ break;
+ case BEGIN_WRITE_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->BeginWriteData(NullUserPointer(),
+ NullUserPointer(),
+ 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(NullUserPointer(),
+ NullUserPointer(),
+ MOJO_READ_DATA_FLAG_NONE));
+ break;
+ case BEGIN_READ_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->BeginReadData(NullUserPointer(),
+ NullUserPointer(),
+ 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(NullUserPointer(), &unused));
+ break;
+ }
+ case MAP_BUFFER: {
+ scoped_ptr<embedder::PlatformSharedBufferMapping> unused;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->MapBuffer(0u, 0u, MOJO_MAP_BUFFER_FLAG_NONE, &unused));
+ break;
+ }
+ case ADD_WAITER: {
+ HandleSignalsState hss;
+ MojoResult r =
+ dispatcher_->AddWaiter(&waiter_, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss);
+ EXPECT_TRUE(r == MOJO_RESULT_FAILED_PRECONDITION ||
+ r == MOJO_RESULT_INVALID_ARGUMENT);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ break;
+ }
+ case REMOVE_WAITER: {
+ HandleSignalsState hss;
+ dispatcher_->RemoveWaiter(&waiter_, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // Always try to remove the waiter, in case we added it.
+ HandleSignalsState hss;
+ dispatcher_->RemoveWaiter(&waiter_, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ }
+
+ 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();
+ }
+ // Kicks off real work on the threads:
+ event.Signal();
+ } // 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();
+ }
+ // Kicks off real work on the threads:
+ event.Signal();
+ } // Joins all the threads.
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/entrypoints.cc b/mojo/edk/system/entrypoints.cc
new file mode 100644
index 0000000..7ec5bb4
--- /dev/null
+++ b/mojo/edk/system/entrypoints.cc
@@ -0,0 +1,191 @@
+// Copyright 2014 The Chromium 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/edk/system/entrypoints.h"
+
+#include "base/logging.h"
+#include "mojo/edk/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/message_pipe.h"
+
+static mojo::system::Core* g_core = nullptr;
+
+using mojo::system::MakeUserPointer;
+
+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, mojo::system::NullUserPointer());
+}
+
+MojoResult MojoWaitMany(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline) {
+ uint32_t result_index = static_cast<uint32_t>(-1);
+ MojoResult result = g_core->WaitMany(MakeUserPointer(handles),
+ MakeUserPointer(signals),
+ num_handles,
+ deadline,
+ MakeUserPointer(&result_index),
+ mojo::system::NullUserPointer());
+ return (result == MOJO_RESULT_OK) ? static_cast<MojoResult>(result_index)
+ : result;
+}
+
+MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1) {
+ return g_core->CreateMessagePipe(MakeUserPointer(options),
+ MakeUserPointer(message_pipe_handle0),
+ MakeUserPointer(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,
+ MakeUserPointer(bytes),
+ num_bytes,
+ MakeUserPointer(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,
+ MakeUserPointer(bytes),
+ MakeUserPointer(num_bytes),
+ MakeUserPointer(handles),
+ MakeUserPointer(num_handles),
+ flags);
+}
+
+MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle) {
+ return g_core->CreateDataPipe(MakeUserPointer(options),
+ MakeUserPointer(data_pipe_producer_handle),
+ MakeUserPointer(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,
+ MakeUserPointer(elements),
+ MakeUserPointer(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,
+ MakeUserPointer(buffer),
+ MakeUserPointer(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,
+ MakeUserPointer(elements),
+ MakeUserPointer(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,
+ MakeUserPointer(buffer),
+ MakeUserPointer(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(MakeUserPointer(options),
+ num_bytes,
+ MakeUserPointer(shared_buffer_handle));
+}
+
+MojoResult MojoDuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const struct MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle) {
+ return g_core->DuplicateBufferHandle(buffer_handle,
+ MakeUserPointer(options),
+ MakeUserPointer(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, MakeUserPointer(buffer), flags);
+}
+
+MojoResult MojoUnmapBuffer(void* buffer) {
+ return g_core->UnmapBuffer(MakeUserPointer(buffer));
+}
+
+} // extern "C"
diff --git a/mojo/edk/system/entrypoints.h b/mojo/edk/system/entrypoints.h
new file mode 100644
index 0000000..46c41b4
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_ENTRYPOINTS_H_
+#define MOJO_EDK_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_EDK_SYSTEM_ENTRYPOINTS_H_
diff --git a/mojo/edk/system/handle_signals_state.h b/mojo/edk/system/handle_signals_state.h
new file mode 100644
index 0000000..3391ea0
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
+#define MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
+
+#include "base/macros.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.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.)
+};
+static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState),
+ "HandleSignalsState should add no overhead");
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
diff --git a/mojo/edk/system/handle_table.cc b/mojo/edk/system/handle_table.cc
new file mode 100644
index 0000000..9c01230
--- /dev/null
+++ b/mojo/edk/system/handle_table.cc
@@ -0,0 +1,240 @@
+// Copyright 2014 The Chromium 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/edk/system/handle_table.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/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 nullptr;
+ return it->second.dispatcher.get();
+}
+
+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.
+ static_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].get()) {
+ 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()) {
+ // Only log for Debug builds, since this is not a problem with the system
+ // code, but with user code.
+ DLOG(WARNING) << "Likely race condition in user code detected: attempt "
+ "to transfer handle " << handles[i]
+ << " while it is in use on a different thread";
+
+ // 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.get());
+ 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/mojo/edk/system/handle_table.h b/mojo/edk/system/handle_table.h
new file mode 100644
index 0000000..9385643
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_HANDLE_TABLE_H_
+#define MOJO_EDK_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/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.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_EDK_SYSTEM_HANDLE_TABLE_H_
diff --git a/mojo/edk/system/local_data_pipe.cc b/mojo/edk/system/local_data_pipe.cc
new file mode 100644
index 0000000..8ee696c
--- /dev/null
+++ b/mojo/edk/system/local_data_pipe.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.
+
+// 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/edk/system/local_data_pipe.h"
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "mojo/edk/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(
+ UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_write,
+ uint32_t min_num_bytes_to_write) {
+ DCHECK_EQ(max_num_bytes_to_write % element_num_bytes(), 0u);
+ DCHECK_EQ(min_num_bytes_to_write % element_num_bytes(), 0u);
+ DCHECK_GT(max_num_bytes_to_write, 0u);
+ DCHECK(consumer_open_no_lock());
+
+ size_t num_bytes_to_write = 0;
+ if (may_discard()) {
+ if (min_num_bytes_to_write > capacity_num_bytes())
+ return MOJO_RESULT_OUT_OF_RANGE;
+
+ num_bytes_to_write = std::min(static_cast<size_t>(max_num_bytes_to_write),
+ 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 (min_num_bytes_to_write > 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>(max_num_bytes_to_write),
+ 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();
+ elements.GetArray(buffer_.get() + first_write_index,
+ num_bytes_to_write_first);
+
+ if (num_bytes_to_write_first < num_bytes_to_write) {
+ // The "second write index" is zero.
+ elements.At(num_bytes_to_write_first)
+ .GetArray(buffer_.get(), 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.Put(static_cast<uint32_t>(num_bytes_to_write));
+ return MOJO_RESULT_OK;
+}
+
+MojoResult LocalDataPipe::ProducerBeginWriteDataImplNoLock(
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ uint32_t min_num_bytes_to_write) {
+ 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 (min_num_bytes_to_write > 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() &&
+ min_num_bytes_to_write <= 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(min_num_bytes_to_write - max_num_bytes_to_write, 0u);
+ MarkDataAsConsumedNoLock(min_num_bytes_to_write - max_num_bytes_to_write);
+ max_num_bytes_to_write = min_num_bytes_to_write;
+ } 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.Put(buffer_.get() + write_index);
+ buffer_num_bytes.Put(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::ProducerGetHandleSignalsStateImplNoLock()
+ 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(
+ UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_read,
+ uint32_t min_num_bytes_to_read) {
+ DCHECK_EQ(max_num_bytes_to_read % element_num_bytes(), 0u);
+ DCHECK_EQ(min_num_bytes_to_read % element_num_bytes(), 0u);
+ DCHECK_GT(max_num_bytes_to_read, 0u);
+
+ if (min_num_bytes_to_read > 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>(max_num_bytes_to_read), 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());
+ elements.PutArray(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.
+ elements.At(num_bytes_to_read_first)
+ .PutArray(buffer_.get(), num_bytes_to_read - num_bytes_to_read_first);
+ }
+
+ MarkDataAsConsumedNoLock(num_bytes_to_read);
+ num_bytes.Put(static_cast<uint32_t>(num_bytes_to_read));
+ return MOJO_RESULT_OK;
+}
+
+MojoResult LocalDataPipe::ConsumerDiscardDataImplNoLock(
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_discard,
+ uint32_t min_num_bytes_to_discard) {
+ DCHECK_EQ(max_num_bytes_to_discard % element_num_bytes(), 0u);
+ DCHECK_EQ(min_num_bytes_to_discard % element_num_bytes(), 0u);
+ DCHECK_GT(max_num_bytes_to_discard, 0u);
+
+ if (min_num_bytes_to_discard > 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>(max_num_bytes_to_discard), current_num_bytes_);
+ MarkDataAsConsumedNoLock(num_bytes_to_discard);
+ num_bytes.Put(static_cast<uint32_t>(num_bytes_to_discard));
+ return MOJO_RESULT_OK;
+}
+
+MojoResult LocalDataPipe::ConsumerQueryDataImplNoLock(
+ UserPointer<uint32_t> num_bytes) {
+ // Note: This cast is safe, since the capacity fits into a |uint32_t|.
+ num_bytes.Put(static_cast<uint32_t>(current_num_bytes_));
+ return MOJO_RESULT_OK;
+}
+
+MojoResult LocalDataPipe::ConsumerBeginReadDataImplNoLock(
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ uint32_t min_num_bytes_to_read) {
+ size_t max_num_bytes_to_read = GetMaxNumBytesToReadNoLock();
+ if (min_num_bytes_to_read > 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.Put(buffer_.get() + start_index_);
+ buffer_num_bytes.Put(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::ConsumerGetHandleSignalsStateImplNoLock()
+ 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_)
+ 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_)
+ 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/mojo/edk/system/local_data_pipe.h b/mojo/edk/system/local_data_pipe.h
new file mode 100644
index 0000000..8d3b75b
--- /dev/null
+++ b/mojo/edk/system/local_data_pipe.h
@@ -0,0 +1,92 @@
+// 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_EDK_SYSTEM_LOCAL_DATA_PIPE_H_
+#define MOJO_EDK_SYSTEM_LOCAL_DATA_PIPE_H_
+
+#include "base/macros.h"
+#include "base/memory/aligned_memory.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/data_pipe.h"
+#include "mojo/edk/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(
+ UserPointer<const void> elements,
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_write,
+ uint32_t min_num_bytes_to_write) override;
+ virtual MojoResult ProducerBeginWriteDataImplNoLock(
+ UserPointer<void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ uint32_t min_num_bytes_to_write) override;
+ virtual MojoResult ProducerEndWriteDataImplNoLock(
+ uint32_t num_bytes_written) override;
+ virtual HandleSignalsState ProducerGetHandleSignalsStateImplNoLock()
+ const override;
+ virtual void ConsumerCloseImplNoLock() override;
+ virtual MojoResult ConsumerReadDataImplNoLock(
+ UserPointer<void> elements,
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_read,
+ uint32_t min_num_bytes_to_read) override;
+ virtual MojoResult ConsumerDiscardDataImplNoLock(
+ UserPointer<uint32_t> num_bytes,
+ uint32_t max_num_bytes_to_discard,
+ uint32_t min_num_bytes_to_discard) override;
+ virtual MojoResult ConsumerQueryDataImplNoLock(
+ UserPointer<uint32_t> num_bytes) override;
+ virtual MojoResult ConsumerBeginReadDataImplNoLock(
+ UserPointer<const void*> buffer,
+ UserPointer<uint32_t> buffer_num_bytes,
+ uint32_t min_num_bytes_to_read) override;
+ virtual MojoResult ConsumerEndReadDataImplNoLock(
+ uint32_t num_bytes_read) override;
+ virtual HandleSignalsState ConsumerGetHandleSignalsStateImplNoLock()
+ 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_EDK_SYSTEM_LOCAL_DATA_PIPE_H_
diff --git a/mojo/edk/system/local_data_pipe_unittest.cc b/mojo/edk/system/local_data_pipe_unittest.cc
new file mode 100644
index 0000000..979b4d2
--- /dev/null
+++ b/mojo/edk/system/local_data_pipe_unittest.cc
@@ -0,0 +1,2003 @@
+// 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/edk/system/local_data_pipe.h"
+
+#include <string.h>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/data_pipe.h"
+#include "mojo/edk/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(NullUserPointer(), &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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(
+ UserPointer<void>(elements), MakeUserPointer(&num_bytes), false));
+
+ // Query; nothing there yet.
+ num_bytes = 0;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(MakeUserPointer(&num_bytes), false));
+
+ // Read with invalid |num_bytes|.
+ num_bytes = sizeof(elements[0]) + 1;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dp->ConsumerReadData(
+ UserPointer<void>(elements), MakeUserPointer(&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(UserPointer<const void>(elements),
+ MakeUserPointer(&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(MakeUserPointer(&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(
+ UserPointer<void>(elements), MakeUserPointer(&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(MakeUserPointer(&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(
+ UserPointer<void>(elements), MakeUserPointer(&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(
+ UserPointer<void>(elements), MakeUserPointer(&num_bytes), false));
+ EXPECT_EQ(456, elements[0]);
+ EXPECT_EQ(-1, elements[1]);
+
+ // Query.
+ num_bytes = 0;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(MakeUserPointer(&options),
+ &validated_options));
+
+ scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options));
+ Waiter waiter;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Never readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 12, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Already writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 34, &hss));
+
+ // 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(UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ true));
+ EXPECT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes);
+
+ // Adding a waiter should now succeed.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 56, nullptr));
+ // And it shouldn't be writable yet.
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ProducerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Do it again.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 78, nullptr));
+
+ // 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(
+ UserPointer<void>(elements), MakeUserPointer(&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);
+ hss = HandleSignalsState();
+ dp->ProducerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Try writing, using a two-phase write.
+ void* buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0]));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&buffer), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(buffer);
+ 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 90, nullptr));
+
+ // Read one element, using a two-phase read.
+ const void* read_buffer = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_buffer), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(read_buffer);
+ // 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);
+ hss = HandleSignalsState();
+ dp->ProducerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Write one element.
+ elements[0] = 123;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerWriteData(UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+
+ // Add a waiter.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 12, nullptr));
+
+ // 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);
+ hss = HandleSignalsState();
+ dp->ProducerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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(MakeUserPointer(&options),
+ &validated_options));
+
+ {
+ scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options));
+ Waiter waiter;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Never writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 12, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Not yet readable.
+ waiter.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(
+ &waiter, MOJO_HANDLE_SIGNAL_READABLE, 34, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // 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(UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ true));
+
+ // Should already be readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 56, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Discard one element.
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerDiscardData(MakeUserPointer(&num_bytes), true));
+ EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+
+ // Should still be readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 78, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // 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(
+ UserPointer<void>(elements), MakeUserPointer(&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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(
+ &waiter, MOJO_HANDLE_SIGNAL_READABLE, 90, nullptr));
+
+ // 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(UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ true));
+
+ // Waiting should now succeed.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(1000, &context));
+ EXPECT_EQ(90u, context);
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Close the producer.
+ dp->ProducerClose();
+
+ // Should still be readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 12, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // 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(
+ UserPointer<void>(elements), MakeUserPointer(&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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 34, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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;
+ HandleSignalsState hss;
+
+ // Write two elements.
+ int32_t* elements = nullptr;
+ void* buffer = nullptr;
+ // 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(
+ MakeUserPointer(&buffer), MakeUserPointer(&num_bytes), true));
+ EXPECT_TRUE(buffer);
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 12, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read one element.
+ // Request two in all-or-none mode, but only read one.
+ const void* read_buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_buffer), MakeUserPointer(&num_bytes), true));
+ EXPECT_TRUE(read_buffer);
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 34, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read one element.
+ // Request three, but not in all-or-none mode.
+ read_buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0]));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_buffer), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(read_buffer);
+ 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(
+ &waiter, MOJO_HANDLE_SIGNAL_READABLE, 56, nullptr));
+
+ // Close the producer.
+ dp->ProducerClose();
+
+ // Should be never-readable.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, waiter.Wait(1000, &context));
+ EXPECT_EQ(56u, context);
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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(MakeUserPointer(&options),
+ &validated_options));
+
+ scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options));
+ Waiter waiter;
+ HandleSignalsState hss;
+
+ // It should be writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ uint32_t num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t));
+ void* write_ptr = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(write_ptr);
+ EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t)));
+
+ // At this point, it shouldn't be writable.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 1, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ProducerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // It shouldn't be readable yet either.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 2, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 3, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // And readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 4, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // 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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(write_ptr);
+ EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t)));
+
+ // It should be readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 5, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // 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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(read_ptr);
+ EXPECT_EQ(static_cast<uint32_t>(1u * sizeof(int32_t)), num_bytes);
+
+ // At this point, it should still be writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 6, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // But not readable.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 7, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // End the two-phase read without reading anything.
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerEndReadData(0u));
+
+ // It should be readable again.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 8, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ 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(MakeUserPointer(&options),
+ &validated_options));
+
+ scoped_refptr<LocalDataPipe> dp(new LocalDataPipe(validated_options));
+ Waiter waiter;
+ HandleSignalsState hss;
+
+ // Writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Not readable.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 1, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(int32_t));
+ int32_t element = 123;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerWriteData(UserPointer<const void>(&element),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_EQ(static_cast<uint32_t>(sizeof(int32_t)), num_bytes);
+
+ // Still writable (even though it's full).
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 2, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // Now readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 3, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Overwrite that element.
+ num_bytes = static_cast<uint32_t>(sizeof(int32_t));
+ element = 456;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerWriteData(UserPointer<const void>(&element),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_EQ(static_cast<uint32_t>(sizeof(int32_t)), num_bytes);
+
+ // Still writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 4, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // And still readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 5, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read that element.
+ num_bytes = static_cast<uint32_t>(sizeof(int32_t));
+ element = 0;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerReadData(
+ UserPointer<void>(&element), MakeUserPointer(&num_bytes), false));
+ EXPECT_EQ(static_cast<uint32_t>(sizeof(int32_t)), num_bytes);
+ EXPECT_EQ(456, element);
+
+ // Still writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ dp->ProducerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 6, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfiable_signals);
+
+ // No longer readable.
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerAddWaiter(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 7, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ dp->ConsumerRemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ 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(MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&num_bytes), false));
+ EXPECT_TRUE(write_ptr);
+ 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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OUT_OF_RANGE,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&num_bytes), true));
+
+ // Should still be empty.
+ num_bytes = ~0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&num_bytes), true));
+ EXPECT_EQ(5u * sizeof(int32_t), num_bytes);
+
+ // Half full.
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&num_bytes), true));
+
+ // Just a little.
+ num_bytes = 2u * sizeof(int32_t);
+ Seq(300, arraysize(buffer), buffer);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerWriteData(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&num_bytes), true));
+ EXPECT_EQ(3u * sizeof(int32_t), num_bytes);
+
+ // Exactly full.
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&num_bytes), true));
+
+ // Discard a little.
+ num_bytes = 2u * sizeof(int32_t);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerDiscardData(MakeUserPointer(&num_bytes), true));
+ EXPECT_EQ(2u * sizeof(int32_t), num_bytes);
+
+ // Three left.
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&num_bytes), true));
+
+ // Read a little.
+ num_bytes = 2u * sizeof(int32_t);
+ memset(buffer, 0xab, sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerReadData(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&num_bytes), true));
+ EXPECT_EQ(1u * sizeof(int32_t), num_bytes);
+
+ // Empty again.
+ num_bytes = ~0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&num_bytes), true));
+
+ // Write some stuff.
+ num_bytes = 5u * sizeof(int32_t);
+ Seq(100, arraysize(buffer), buffer);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerWriteData(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&num_bytes), true));
+
+ // Discard a bunch.
+ num_bytes = 4u * sizeof(int32_t);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerDiscardData(MakeUserPointer(&num_bytes), true));
+
+ // Half full.
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OUT_OF_RANGE,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&num_bytes), true));
+
+ // Try writing an amount which isn't a multiple of the element size
+ // (two-phase).
+ static_assert(sizeof(int32_t) > 1u, "Wow! int32_t's have size 1");
+ num_bytes = 1u;
+ write_ptr = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&num_bytes), true));
+
+ // Try reading way too much (two-phase).
+ num_bytes = 20u * sizeof(int32_t);
+ const void* read_ptr = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&num_bytes), true));
+
+ // Write half (two-phase).
+ num_bytes = 5u * sizeof(int32_t);
+ write_ptr = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&num_bytes), true));
+ // May provide more space than requested.
+ EXPECT_GE(num_bytes, 5u * sizeof(int32_t));
+ EXPECT_TRUE(write_ptr);
+ 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 = nullptr;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&num_bytes), true));
+
+ // Read one (two-phase).
+ num_bytes = 1u * sizeof(int32_t);
+ read_ptr = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OUT_OF_RANGE,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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(
+ UserPointer<const void>(buffer), MakeUserPointer(&num_bytes), true));
+ EXPECT_EQ(6u * sizeof(int32_t), num_bytes);
+
+ // We have ten.
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&num_bytes), true));
+
+ // Close the producer.
+ dp->ProducerClose();
+
+ // A two-phase read of nine should work.
+ num_bytes = 9u * sizeof(int32_t);
+ read_ptr = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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(MakeUserPointer(&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(UserPointer<const void>(&test_data[0]),
+ MakeUserPointer(&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(
+ UserPointer<void>(read_buffer), MakeUserPointer(&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 = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(MakeUserPointer(&write_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(write_buffer_ptr);
+ 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(UserPointer<const void>(&test_data[20]),
+ MakeUserPointer(&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 = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(MakeUserPointer(&read_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(read_buffer_ptr);
+ 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(
+ UserPointer<void>(read_buffer), MakeUserPointer(&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(MakeUserPointer(&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(UserPointer<const void>(kTestData),
+ MakeUserPointer(&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(UserPointer<const void>(kTestData),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_EQ(kTestDataSize, num_bytes);
+
+ // Start two-phase write.
+ void* write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(MakeUserPointer(&write_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(write_buffer_ptr);
+ EXPECT_GT(num_bytes, 0u);
+
+ // Start two-phase read.
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(MakeUserPointer(&read_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(read_buffer_ptr);
+ 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 = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(MakeUserPointer(&read_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(read_buffer_ptr);
+ 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(UserPointer<const void>(kTestData),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_EQ(kTestDataSize, num_bytes);
+
+ // Start two-phase write.
+ void* write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(MakeUserPointer(&write_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(write_buffer_ptr);
+ ASSERT_GT(num_bytes, kTestDataSize);
+
+ // Start two-phase read.
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(MakeUserPointer(&read_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(read_buffer_ptr);
+ 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(UserPointer<const void>(kTestData),
+ MakeUserPointer(&num_bytes),
+ false));
+
+ // As will trying to start another two-phase write.
+ write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ProducerBeginWriteData(MakeUserPointer(&write_buffer_ptr),
+ MakeUserPointer(&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 = nullptr;
+ uint32_t num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(MakeUserPointer(&write_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_TRUE(write_buffer_ptr);
+ 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(UserPointer<const void>(kTestData),
+ MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&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(
+ UserPointer<void>(buffer), MakeUserPointer(&num_bytes), false));
+
+ // A two-phase read should also fail.
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ConsumerBeginReadData(MakeUserPointer(&read_buffer_ptr),
+ MakeUserPointer(&num_bytes),
+ false));
+
+ // Ditto for discard.
+ num_bytes = 10u;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ dp->ConsumerDiscardData(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ProducerBeginWriteData(
+ MakeUserPointer(&write_ptr), MakeUserPointer(&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(MakeUserPointer(&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(UserPointer<const void>(&element),
+ MakeUserPointer(&num_bytes),
+ false));
+
+ // One element available.
+ num_bytes = 0u;
+ EXPECT_EQ(MOJO_RESULT_OK, dp->ConsumerQueryData(MakeUserPointer(&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(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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(MakeUserPointer(&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 = nullptr;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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(MakeUserPointer(&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(MakeUserPointer(&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(UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ false));
+ EXPECT_EQ(2u, num_bytes);
+
+ // Begin reading.
+ const void* read_ptr = nullptr;
+ num_bytes = 2u;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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(UserPointer<const void>(elements),
+ MakeUserPointer(&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(UserPointer<const void>(elements),
+ MakeUserPointer(&num_bytes),
+ false));
+
+ // And if we read, we should get the new values.
+ read_ptr = nullptr;
+ num_bytes = 2u;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dp->ConsumerBeginReadData(
+ MakeUserPointer(&read_ptr), MakeUserPointer(&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/mojo/edk/system/local_message_pipe_endpoint.cc b/mojo/edk/system/local_message_pipe_endpoint.cc
new file mode 100644
index 0000000..124241e
--- /dev/null
+++ b/mojo/edk/system/local_message_pipe_endpoint.cc
@@ -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.
+
+#include "mojo/edk/system/local_message_pipe_endpoint.h"
+
+#include <string.h>
+
+#include "base/logging.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/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(
+ UserPointer<void> bytes,
+ UserPointer<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.IsNull() ? 0 : num_bytes.Get();
+ 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.IsNull())
+ num_bytes.Put(message->num_bytes());
+ if (message->num_bytes() <= max_bytes)
+ bytes.PutArray(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 = nullptr;
+
+ 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;
+}
+
+HandleSignalsState LocalMessagePipeEndpoint::GetHandleSignalsState() const {
+ 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;
+}
+
+MojoResult LocalMessagePipeEndpoint::AddWaiter(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ DCHECK(is_open_);
+
+ HandleSignalsState state = GetHandleSignalsState();
+ if (state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ waiter_list_.AddWaiter(waiter, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void LocalMessagePipeEndpoint::RemoveWaiter(Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ DCHECK(is_open_);
+ waiter_list_.RemoveWaiter(waiter);
+ if (signals_state)
+ *signals_state = GetHandleSignalsState();
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/local_message_pipe_endpoint.h b/mojo/edk/system/local_message_pipe_endpoint.h
new file mode 100644
index 0000000..ccff26d
--- /dev/null
+++ b/mojo/edk/system/local_message_pipe_endpoint.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_EDK_SYSTEM_LOCAL_MESSAGE_PIPE_ENDPOINT_H_
+#define MOJO_EDK_SYSTEM_LOCAL_MESSAGE_PIPE_ENDPOINT_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/message_in_transit_queue.h"
+#include "mojo/edk/system/message_pipe_endpoint.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/edk/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(UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags) override;
+ virtual HandleSignalsState GetHandleSignalsState() const override;
+ virtual MojoResult AddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ virtual void RemoveWaiter(Waiter* waiter,
+ HandleSignalsState* signals_state) override;
+
+ // This is only to be used by |MessagePipe|:
+ MessageInTransitQueue* message_queue() { return &message_queue_; }
+
+ private:
+ 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_EDK_SYSTEM_LOCAL_MESSAGE_PIPE_ENDPOINT_H_
diff --git a/mojo/edk/system/mapping_table.cc b/mojo/edk/system/mapping_table.cc
new file mode 100644
index 0000000..0a28a1d
--- /dev/null
+++ b/mojo/edk/system/mapping_table.cc
@@ -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.
+
+#include "mojo/edk/system/mapping_table.h"
+
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/constants.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<embedder::PlatformSharedBufferMapping> mapping) {
+ DCHECK(mapping);
+
+ if (address_to_mapping_map_.size() >= kMaxMappingTableSize)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ uintptr_t address = reinterpret_cast<uintptr_t>(mapping->GetBase());
+ 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(uintptr_t address) {
+ AddressToMappingMap::iterator it = address_to_mapping_map_.find(address);
+ if (it == address_to_mapping_map_.end())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ embedder::PlatformSharedBufferMapping* 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/mojo/edk/system/mapping_table.h b/mojo/edk/system/mapping_table.h
new file mode 100644
index 0000000..d2812d8
--- /dev/null
+++ b/mojo/edk/system/mapping_table.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_EDK_SYSTEM_MAPPING_TABLE_H_
+#define MOJO_EDK_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/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+
+namespace embedder {
+class PlatformSharedBufferMapping;
+}
+
+namespace system {
+
+class Core;
+
+// 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 |PlatformSharedBufferMapping|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<embedder::PlatformSharedBufferMapping> mapping);
+ MojoResult RemoveMapping(uintptr_t address);
+
+ private:
+ friend bool internal::ShutdownCheckNoLeaks(Core*);
+
+ typedef base::hash_map<uintptr_t, embedder::PlatformSharedBufferMapping*>
+ AddressToMappingMap;
+ AddressToMappingMap address_to_mapping_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(MappingTable);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MAPPING_TABLE_H_
diff --git a/mojo/edk/system/memory.cc b/mojo/edk/system/memory.cc
new file mode 100644
index 0000000..606c08f
--- /dev/null
+++ b/mojo/edk/system/memory.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/edk/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>
+void MOJO_SYSTEM_IMPL_EXPORT CheckUserPointer(const void* pointer) {
+ CHECK(pointer && IsAligned<alignment>(pointer));
+}
+
+// Explicitly instantiate the sizes we need. Add instantiations as needed.
+template void MOJO_SYSTEM_IMPL_EXPORT CheckUserPointer<1, 1>(const void*);
+template void MOJO_SYSTEM_IMPL_EXPORT CheckUserPointer<4, 4>(const void*);
+template void MOJO_SYSTEM_IMPL_EXPORT CheckUserPointer<8, 4>(const void*);
+template void MOJO_SYSTEM_IMPL_EXPORT CheckUserPointer<8, 8>(const void*);
+
+template <size_t size, size_t alignment>
+void MOJO_SYSTEM_IMPL_EXPORT
+CheckUserPointerWithCount(const void* pointer, size_t count) {
+ CHECK_LE(count, std::numeric_limits<size_t>::max() / size);
+ CHECK(count == 0 || (pointer && IsAligned<alignment>(pointer)));
+}
+
+// Explicitly instantiate the sizes we need. Add instantiations as needed.
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithCount<1, 1>(const void*, size_t);
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithCount<4, 4>(const void*, size_t);
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithCount<8, 4>(const void*, size_t);
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithCount<8, 8>(const void*, size_t);
+
+template <size_t alignment>
+void CheckUserPointerWithSize(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.)
+ CHECK(size == 0 || (!!pointer && internal::IsAligned<alignment>(pointer)));
+}
+
+// Explicitly instantiate the sizes we need. Add instantiations as needed.
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithSize<1>(const void*, size_t);
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithSize<4>(const void*, size_t);
+// Whereas the other |Check...()| functions are usually used with integral typs
+// or arrays of integral types, this one is used with Options structs for which
+// alignment has been explicitly been specified (using |MOJO_ALIGNAS()|), which
+// MSVS *does* respect.
+#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS)
+template <>
+void MOJO_SYSTEM_IMPL_EXPORT
+CheckUserPointerWithSize<8>(const void* pointer, size_t size) {
+ CHECK(size == 0 ||
+ (!!pointer && reinterpret_cast<uintptr_t>(pointer) % 8 == 0));
+}
+#else
+template void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithSize<8>(const void*, size_t);
+#endif
+
+} // namespace internal
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/memory.h b/mojo/edk/system/memory.h
new file mode 100644
index 0000000..7b5b724
--- /dev/null
+++ b/mojo/edk/system/memory.h
@@ -0,0 +1,377 @@
+// 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_EDK_SYSTEM_MEMORY_H_
+#define MOJO_EDK_SYSTEM_MEMORY_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h> // For |memcpy()|.
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace system {
+
+namespace internal {
+
+// Removes |const| from |T| (available as |remove_const<T>::type|):
+// TODO(vtl): Remove these once we have the C++11 |remove_const|.
+template <typename T>
+struct remove_const {
+ typedef T type;
+};
+template <typename T>
+struct remove_const<const T> {
+ typedef T type;
+};
+
+// Yields |(const) char| if |T| is |(const) void|, else |T|:
+template <typename T>
+struct VoidToChar {
+ typedef T type;
+};
+template <>
+struct VoidToChar<void> {
+ typedef char type;
+};
+template <>
+struct VoidToChar<const void> {
+ typedef const char type;
+};
+
+// Checks (insofar as appropriate/possible) that |pointer| is a valid pointer to
+// a buffer of the given size and alignment (both in bytes).
+template <size_t size, size_t alignment>
+void MOJO_SYSTEM_IMPL_EXPORT CheckUserPointer(const void* pointer);
+
+// Checks (insofar as appropriate/possible) that |pointer| is a valid pointer to
+// a buffer of |count| elements of the given size and alignment (both in bytes).
+template <size_t size, size_t alignment>
+void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithCount(const void* pointer, size_t count);
+
+// Checks (insofar as appropriate/possible) that |pointer| is a valid pointer to
+// a buffer of the given size and alignment (both in bytes).
+template <size_t alignment>
+void MOJO_SYSTEM_IMPL_EXPORT
+ CheckUserPointerWithSize(const void* pointer, size_t size);
+
+} // namespace internal
+
+// Forward declarations so that they can be friended.
+template <typename Type>
+class UserPointerReader;
+template <typename Type>
+class UserPointerWriter;
+template <typename Type>
+class UserPointerReaderWriter;
+template <class Options>
+class UserOptionsReader;
+
+// Provides a convenient way to implicitly get null |UserPointer<Type>|s.
+struct NullUserPointer {};
+
+// Represents a user pointer to a single |Type| (which must be POD), for Mojo
+// primitive parameters.
+//
+// Use a const |Type| for in parameters, and non-const |Type|s for out and
+// in-out parameters (in which case the |Put()| method is available).
+template <typename Type>
+class UserPointer {
+ private:
+ typedef typename internal::VoidToChar<Type>::type NonVoidType;
+
+ public:
+ // Instead of explicitly using these constructors, you can often use
+ // |MakeUserPointer()| (or |NullUserPointer()| for null pointers). (The common
+ // exception is when you have, e.g., a |char*| and want to get a
+ // |UserPointer<void>|.)
+ UserPointer() : pointer_(nullptr) {}
+ explicit UserPointer(Type* pointer) : pointer_(pointer) {}
+ // Allow implicit conversion from the "null user pointer".
+ UserPointer(NullUserPointer) : pointer_(nullptr) {}
+ ~UserPointer() {}
+
+ // Allow assignment from the "null user pointer".
+ UserPointer<Type>& operator=(NullUserPointer) {
+ pointer_ = nullptr;
+ return *this;
+ }
+
+ // Allow conversion to a "non-const" |UserPointer|.
+ operator UserPointer<const Type>() const {
+ return UserPointer<const Type>(pointer_);
+ }
+
+ bool IsNull() const { return !pointer_; }
+
+ // "Reinterpret casts" to a |UserPointer<ToType>|.
+ template <typename ToType>
+ UserPointer<ToType> ReinterpretCast() const {
+ return UserPointer<ToType>(reinterpret_cast<ToType*>(pointer_));
+ }
+
+ // Checks that this pointer points to a valid |Type| in the same way as
+ // |Get()| and |Put()|.
+ // TODO(vtl): Logically, there should be separate read checks and write
+ // checks.
+ void Check() const {
+ internal::CheckUserPointer<sizeof(NonVoidType), MOJO_ALIGNOF(NonVoidType)>(
+ pointer_);
+ }
+
+ // Checks that this pointer points to a valid array (of type |Type|, or just a
+ // buffer if |Type| is |void| or |const void|) of |count| elements (or bytes
+ // if |Type| is |void| or |const void|) in the same way as |GetArray()| and
+ // |PutArray()|.
+ // TODO(vtl): Logically, there should be separate read checks and write
+ // checks.
+ // TODO(vtl): Switch more things to use this.
+ void CheckArray(size_t count) const {
+ internal::CheckUserPointerWithCount<sizeof(NonVoidType),
+ MOJO_ALIGNOF(NonVoidType)>(pointer_,
+ count);
+ }
+
+ // Gets the value (of type |Type|, or a |char| if |Type| is |void|) pointed to
+ // by this user pointer. Use this when you'd use the rvalue |*user_pointer|,
+ // but be aware that this may be costly -- so if the value will be used
+ // multiple times, you should save it.
+ //
+ // (We want to force a copy here, so return |Type| not |const Type&|.)
+ NonVoidType Get() const {
+ Check();
+ internal::CheckUserPointer<sizeof(NonVoidType), MOJO_ALIGNOF(NonVoidType)>(
+ pointer_);
+ return *pointer_;
+ }
+
+ // Gets an array (of type |Type|, or just a buffer if |Type| is |void| or
+ // |const void|) of |count| elements (or bytes if |Type| is |void| or |const
+ // void|) from the location pointed to by this user pointer. Use this when
+ // you'd do something like |memcpy(destination, user_pointer, count *
+ // sizeof(Type)|.
+ void GetArray(typename internal::remove_const<Type>::type* destination,
+ size_t count) const {
+ CheckArray(count);
+ memcpy(destination, pointer_, count * sizeof(NonVoidType));
+ }
+
+ // Puts a value (of type |Type|, or of type |char| if |Type| is |void|) to the
+ // location pointed to by this user pointer. Use this when you'd use the
+ // lvalue |*user_pointer|. Since this may be costly, you should avoid using
+ // this (for the same user pointer) more than once.
+ //
+ // Note: This |Put()| method is not valid when |T| is const, e.g., |const
+ // uint32_t|, but it's okay to include them so long as this template is only
+ // implicitly instantiated (see 14.7.1 of the C++11 standard) and not
+ // explicitly instantiated. (On implicit instantiation, only the declarations
+ // need be valid, not the definitions.)
+ //
+ // In C++11, we could do something like:
+ // template <typename _Type = Type>
+ // typename enable_if<!is_const<_Type>::value &&
+ // !is_void<_Type>::value>::type Put(
+ // const _Type& value) { ... }
+ // (which obviously be correct), but C++03 doesn't allow default function
+ // template arguments.
+ void Put(const NonVoidType& value) {
+ Check();
+ *pointer_ = value;
+ }
+
+ // Puts an array (of type |Type|, or just a buffer if |Type| is |void|) with
+ // |count| elements (or bytes |Type| is |void|) to the location pointed to by
+ // this user pointer. Use this when you'd do something like
+ // |memcpy(user_pointer, source, count * sizeof(Type))|.
+ //
+ // Note: The same comments about the validity of |Put()| (except for the part
+ // about |void|) apply here.
+ void PutArray(const Type* source, size_t count) {
+ CheckArray(count);
+ memcpy(pointer_, source, count * sizeof(NonVoidType));
+ }
+
+ // Gets a |UserPointer| at offset |i| (in |Type|s) relative to this.
+ UserPointer At(size_t i) const {
+ return UserPointer(
+ static_cast<Type*>(static_cast<NonVoidType*>(pointer_) + i));
+ }
+
+ // Gets the value of the |UserPointer| as a |uintptr_t|. This should not be
+ // casted back to a pointer (and dereferenced), but may be used as a key for
+ // lookup or passed back to the user.
+ uintptr_t GetPointerValue() const {
+ return reinterpret_cast<uintptr_t>(pointer_);
+ }
+
+ // These provides safe (read-only/write-only/read-and-write) access to a
+ // |UserPointer<Type>| (probably pointing to an array) using just an ordinary
+ // pointer (obtained via |GetPointer()|).
+ //
+ // The memory returned by |GetPointer()| may be a copy of the original user
+ // memory, but should be modified only if the user is intended to eventually
+ // see the change.) If any changes are made, |Commit()| should be called to
+ // guarantee that the changes are written back to user memory (it may be
+ // called multiple times).
+ //
+ // Note: These classes are designed to allow fast, unsafe implementations (in
+ // which |GetPointer()| just returns the user pointer) if desired. Thus if
+ // |Commit()| is *not* called, changes may or may not be made visible to the
+ // user.
+ //
+ // Use these classes in the following way:
+ //
+ // MojoResult Core::PutFoos(UserPointer<const uint32_t> foos,
+ // uint32_t num_foos) {
+ // UserPointer<const uint32_t>::Reader foos_reader(foos, num_foos);
+ // return PutFoosImpl(foos_reader.GetPointer(), num_foos);
+ // }
+ //
+ // MojoResult Core::GetFoos(UserPointer<uint32_t> foos,
+ // uint32_t num_foos) {
+ // UserPointer<uint32_t>::Writer foos_writer(foos, num_foos);
+ // MojoResult rv = GetFoosImpl(foos.GetPointer(), num_foos);
+ // foos_writer.Commit();
+ // return rv;
+ // }
+ //
+ // TODO(vtl): Possibly, since we're not really being safe, we should just not
+ // copy for Release builds.
+ typedef UserPointerReader<Type> Reader;
+ typedef UserPointerWriter<Type> Writer;
+ typedef UserPointerReaderWriter<Type> ReaderWriter;
+
+ private:
+ friend class UserPointerReader<Type>;
+ friend class UserPointerReader<const Type>;
+ friend class UserPointerWriter<Type>;
+ friend class UserPointerReaderWriter<Type>;
+ template <class Options>
+ friend class UserOptionsReader;
+
+ Type* pointer_;
+ // Allow copy and assignment.
+};
+
+// Provides a convenient way to make a |UserPointer<Type>|.
+template <typename Type>
+inline UserPointer<Type> MakeUserPointer(Type* pointer) {
+ return UserPointer<Type>(pointer);
+}
+
+// Implementation of |UserPointer<Type>::Reader|.
+template <typename Type>
+class UserPointerReader {
+ private:
+ typedef typename internal::remove_const<Type>::type TypeNoConst;
+
+ public:
+ // Note: If |count| is zero, |GetPointer()| will always return null.
+ UserPointerReader(UserPointer<const Type> user_pointer, size_t count) {
+ Init(user_pointer.pointer_, count, true);
+ }
+ UserPointerReader(UserPointer<TypeNoConst> user_pointer, size_t count) {
+ Init(user_pointer.pointer_, count, true);
+ }
+
+ const Type* GetPointer() const { return buffer_.get(); }
+
+ private:
+ template <class Options>
+ friend class UserOptionsReader;
+
+ struct NoCheck {};
+ UserPointerReader(NoCheck,
+ UserPointer<const Type> user_pointer,
+ size_t count) {
+ Init(user_pointer.pointer_, count, false);
+ }
+
+ void Init(const Type* user_pointer, size_t count, bool check) {
+ if (count == 0)
+ return;
+
+ if (check) {
+ internal::CheckUserPointerWithCount<sizeof(Type), MOJO_ALIGNOF(Type)>(
+ user_pointer, count);
+ }
+ buffer_.reset(new TypeNoConst[count]);
+ memcpy(buffer_.get(), user_pointer, count * sizeof(Type));
+ }
+
+ scoped_ptr<TypeNoConst[]> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserPointerReader);
+};
+
+// Implementation of |UserPointer<Type>::Writer|.
+template <typename Type>
+class UserPointerWriter {
+ public:
+ // Note: If |count| is zero, |GetPointer()| will always return null.
+ UserPointerWriter(UserPointer<Type> user_pointer, size_t count)
+ : user_pointer_(user_pointer), count_(count) {
+ if (count_ > 0) {
+ buffer_.reset(new Type[count_]);
+ memset(buffer_.get(), 0, count_ * sizeof(Type));
+ }
+ }
+
+ Type* GetPointer() const { return buffer_.get(); }
+
+ void Commit() {
+ internal::CheckUserPointerWithCount<sizeof(Type), MOJO_ALIGNOF(Type)>(
+ user_pointer_.pointer_, count_);
+ memcpy(user_pointer_.pointer_, buffer_.get(), count_ * sizeof(Type));
+ }
+
+ private:
+ UserPointer<Type> user_pointer_;
+ size_t count_;
+ scoped_ptr<Type[]> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserPointerWriter);
+};
+
+// Implementation of |UserPointer<Type>::ReaderWriter|.
+template <typename Type>
+class UserPointerReaderWriter {
+ public:
+ // Note: If |count| is zero, |GetPointer()| will always return null.
+ UserPointerReaderWriter(UserPointer<Type> user_pointer, size_t count)
+ : user_pointer_(user_pointer), count_(count) {
+ if (count_ > 0) {
+ internal::CheckUserPointerWithCount<sizeof(Type), MOJO_ALIGNOF(Type)>(
+ user_pointer_.pointer_, count_);
+ buffer_.reset(new Type[count]);
+ memcpy(buffer_.get(), user_pointer.pointer_, count * sizeof(Type));
+ }
+ }
+
+ Type* GetPointer() const { return buffer_.get(); }
+ size_t GetCount() const { return count_; }
+
+ void Commit() {
+ internal::CheckUserPointerWithCount<sizeof(Type), MOJO_ALIGNOF(Type)>(
+ user_pointer_.pointer_, count_);
+ memcpy(user_pointer_.pointer_, buffer_.get(), count_ * sizeof(Type));
+ }
+
+ private:
+ UserPointer<Type> user_pointer_;
+ size_t count_;
+ scoped_ptr<Type[]> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserPointerReaderWriter);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MEMORY_H_
diff --git a/mojo/edk/system/memory_unittest.cc b/mojo/edk/system/memory_unittest.cc
new file mode 100644
index 0000000..46515c0
--- /dev/null
+++ b/mojo/edk/system/memory_unittest.cc
@@ -0,0 +1,294 @@
+// Copyright 2014 The Chromium 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/edk/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_array[5] = {}; // Zero initialize.
+
+ UserPointer<char> my_char_ptr(&my_char);
+ UserPointer<int32_t> my_int32_ptr(&my_int32);
+ UserPointer<int64_t> my_int64_array_ptr(my_int64_array);
+
+ // |UserPointer<>::IsNull()|:
+ EXPECT_FALSE(my_char_ptr.IsNull());
+ EXPECT_FALSE(my_int32_ptr.IsNull());
+ EXPECT_FALSE(my_int64_array_ptr.IsNull());
+
+ // |UserPointer<>::Put()| and |UserPointer<>::Get()|:
+ my_char_ptr.Put('x');
+ EXPECT_EQ('x', my_char);
+ EXPECT_EQ('x', my_char_ptr.Get());
+ my_int32_ptr.Put(123);
+ EXPECT_EQ(123, my_int32);
+ EXPECT_EQ(123, my_int32_ptr.Get());
+ my_int64_array_ptr.Put(456);
+ EXPECT_EQ(456, my_int64_array[0]);
+ EXPECT_EQ(456, my_int64_array_ptr.Get());
+
+ // |UserPointer<>::At()|, etc.:
+ my_int64_array_ptr.At(3).Put(789);
+ EXPECT_EQ(789, my_int64_array[3]);
+ {
+ // Copy construction:
+ UserPointer<int64_t> other(my_int64_array_ptr.At(3));
+ EXPECT_FALSE(other.IsNull());
+ EXPECT_EQ(789, other.Get());
+
+ // Assignment:
+ other = my_int64_array_ptr;
+ EXPECT_FALSE(other.IsNull());
+ EXPECT_EQ(456, other.Get());
+
+ // Assignment to |NullUserPointer()|:
+ other = NullUserPointer();
+ EXPECT_TRUE(other.IsNull());
+
+ // |MakeUserPointer()|:
+ other = MakeUserPointer(&my_int64_array[1]);
+ other.Put(-123);
+ EXPECT_EQ(-123, my_int64_array_ptr.At(1).Get());
+ }
+
+ // "const" |UserPointer<>|:
+ {
+ // Explicit constructor from |NullUserPointer()|:
+ UserPointer<const char> other((NullUserPointer()));
+ EXPECT_TRUE(other.IsNull());
+
+ // Conversion to "const":
+ other = my_char_ptr;
+ EXPECT_EQ('x', other.Get());
+ }
+
+ // Default constructor:
+ {
+ UserPointer<int32_t> other;
+ EXPECT_TRUE(other.IsNull());
+
+ other = my_int32_ptr;
+ other.Put(-456);
+ EXPECT_EQ(-456, my_int32_ptr.Get());
+ }
+
+ // |UserPointer<>::CheckArray()|:
+ my_int64_array_ptr.CheckArray(5);
+
+ // |UserPointer<>::GetArray()|:
+ {
+ // From a "const" |UserPointer<>| (why not?):
+ UserPointer<const int64_t> other(my_int64_array_ptr);
+ int64_t array[3] = {1, 2, 3};
+ other.At(1).GetArray(array, 3);
+ EXPECT_EQ(-123, array[0]);
+ EXPECT_EQ(0, array[1]);
+ EXPECT_EQ(789, array[2]);
+ }
+
+ // |UserPointer<>::PutArray()|:
+ {
+ const int64_t array[2] = {654, 321};
+ my_int64_array_ptr.At(3).PutArray(array, 2);
+ EXPECT_EQ(0, my_int64_array[2]);
+ EXPECT_EQ(654, my_int64_array[3]);
+ EXPECT_EQ(321, my_int64_array[4]);
+ }
+
+ // |UserPointer<>::Reader|:
+ {
+ UserPointer<int64_t>::Reader reader(my_int64_array_ptr, 5);
+ EXPECT_EQ(456, reader.GetPointer()[0]);
+ EXPECT_EQ(321, reader.GetPointer()[4]);
+ }
+
+ // Non-const to const:
+ {
+ UserPointer<const int64_t>::Reader reader(my_int64_array_ptr.At(3), 1);
+ const int64_t* ptr = reader.GetPointer();
+ EXPECT_EQ(654, *ptr);
+ }
+
+ // |UserPointer<>::Writer|:
+ {
+ UserPointer<int64_t>::Writer writer(my_int64_array_ptr.At(2), 1);
+ int64_t* ptr = writer.GetPointer();
+ *ptr = 1234567890123LL;
+ writer.Commit();
+ EXPECT_EQ(1234567890123LL, my_int64_array[2]);
+ }
+
+ // |UserPointer<>::ReaderWriter|:
+ {
+ UserPointer<int32_t>::ReaderWriter reader_writer(my_int32_ptr, 1);
+ int32_t* ptr = reader_writer.GetPointer();
+ EXPECT_EQ(-456, *ptr);
+ *ptr = 42;
+ reader_writer.Commit();
+ EXPECT_EQ(42, my_int32);
+ }
+
+ // |UserPointer<>::ReinterpretCast<>|:
+ // (This assumes little-endian, etc.)
+ {
+ UserPointer<const char> other(my_int32_ptr.ReinterpretCast<char>());
+ EXPECT_EQ(42, other.Get());
+ EXPECT_EQ(0, other.At(1).Get());
+ EXPECT_EQ(0, other.At(2).Get());
+ EXPECT_EQ(0, other.At(3).Get());
+ }
+
+ // |UserPointer<>::GetPointerValue()|:
+ {
+ UserPointer<int32_t> other;
+ EXPECT_EQ(0u, other.GetPointerValue());
+ other = my_int32_ptr;
+ EXPECT_EQ(reinterpret_cast<uintptr_t>(&my_int32), other.GetPointerValue());
+ }
+}
+
+TEST(MemoryTest, InvalidDeath) {
+ const char kMemoryCheckFailedRegex[] = "Check failed";
+
+ // Note: |Check...()| 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.
+ static_assert(MOJO_ALIGNOF(int32_t) != 1,
+ "int32_t does not require nontrivial alignment");
+ static_assert(MOJO_ALIGNOF(int64_t) != 1,
+ "int64_t does not require nontrivial alignment");
+
+ // Null:
+ {
+ UserPointer<char> ptr(nullptr);
+ char array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Put('x'), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(array, 5), kMemoryCheckFailedRegex);
+ }
+ {
+ UserPointer<int32_t> ptr(nullptr);
+ int32_t array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Put(123), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(array, 5), kMemoryCheckFailedRegex);
+ }
+ {
+ UserPointer<int64_t> ptr(nullptr);
+ int64_t array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Put(123), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(array, 5), kMemoryCheckFailedRegex);
+ }
+ // Also check a const pointer:
+ {
+ UserPointer<const int32_t> ptr(nullptr);
+ int32_t array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ }
+
+ // Unaligned:
+ {
+ int32_t x[10];
+ UserPointer<int32_t> ptr(
+ reinterpret_cast<int32_t*>(reinterpret_cast<uintptr_t>(x) + 1));
+ int32_t array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Put(123), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(array, 5), kMemoryCheckFailedRegex);
+ }
+ {
+ int64_t x[10];
+ UserPointer<int64_t> ptr(
+ reinterpret_cast<int64_t*>(reinterpret_cast<uintptr_t>(x) + 1));
+ int64_t array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Put(123), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(array, 5), kMemoryCheckFailedRegex);
+ }
+ // Also check a const pointer:
+ {
+ int32_t x[10];
+ UserPointer<const int32_t> ptr(
+ reinterpret_cast<const int32_t*>(reinterpret_cast<uintptr_t>(x) + 1));
+ int32_t array[5] = {};
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Check(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.Get(), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(5), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(array, 5), kMemoryCheckFailedRegex);
+ }
+
+ // Count too big:
+ {
+ const size_t kTooBig =
+ std::numeric_limits<size_t>::max() / sizeof(int32_t) + 1;
+ int32_t x = 0;
+ UserPointer<int32_t> ptr(&x);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(kTooBig), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(&x, kTooBig),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(&x, kTooBig),
+ kMemoryCheckFailedRegex);
+ }
+ {
+ const size_t kTooBig =
+ std::numeric_limits<size_t>::max() / sizeof(int64_t) + 1;
+ int64_t x = 0;
+ UserPointer<int64_t> ptr(&x);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(kTooBig), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(&x, kTooBig),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.PutArray(&x, kTooBig),
+ kMemoryCheckFailedRegex);
+ }
+ // Also check a const pointer:
+ {
+ const size_t kTooBig =
+ std::numeric_limits<size_t>::max() / sizeof(int32_t) + 1;
+ int32_t x = 0;
+ UserPointer<const int32_t> ptr(&x);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.CheckArray(kTooBig), kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(ptr.GetArray(&x, kTooBig),
+ kMemoryCheckFailedRegex);
+ }
+
+ // TODO(vtl): Tests for |UserPointer{Reader,Writer,ReaderWriter}|.
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit.cc b/mojo/edk/system/message_in_transit.cc
new file mode 100644
index 0000000..f1fb91d
--- /dev/null
+++ b/mojo/edk/system/message_in_transit.cc
@@ -0,0 +1,224 @@
+// 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/edk/system/message_in_transit.h"
+
+#include <string.h>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/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.
+ static_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.
+ static_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.
+ static_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))) {
+ ConstructorHelper(type, subtype, num_bytes);
+ 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(Type type,
+ Subtype subtype,
+ uint32_t num_bytes,
+ UserPointer<const void> bytes)
+ : main_buffer_size_(RoundUpMessageAlignment(sizeof(Header) + num_bytes)),
+ main_buffer_(static_cast<char*>(
+ base::AlignedAlloc(main_buffer_size_, kMessageAlignment))) {
+ ConstructorHelper(type, subtype, num_bytes);
+ bytes.GetArray(MessageInTransit::bytes(), num_bytes);
+}
+
+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].get())
+ 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].get() || (*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::ConstructorHelper(Type type,
+ Subtype subtype,
+ uint32_t num_bytes) {
+ 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();
+}
+
+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/mojo/edk/system/message_in_transit.h b/mojo/edk/system/message_in_transit.h
new file mode 100644
index 0000000..7714fa7
--- /dev/null
+++ b/mojo/edk/system/message_in_transit.h
@@ -0,0 +1,267 @@
+// 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_EDK_SYSTEM_MESSAGE_IN_TRANSIT_H_
+#define MOJO_EDK_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/edk/system/dispatcher.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/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()
+ : nullptr;
+ }
+ 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);
+ // |bytes| should be valid (and non-null), unless |num_bytes| is zero.
+ MessageInTransit(Type type,
+ Subtype subtype,
+ uint32_t num_bytes,
+ UserPointer<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 ConstructorHelper(Type type, Subtype subtype, uint32_t num_bytes);
+ 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_EDK_SYSTEM_MESSAGE_IN_TRANSIT_H_
diff --git a/mojo/edk/system/message_in_transit_queue.cc b/mojo/edk/system/message_in_transit_queue.cc
new file mode 100644
index 0000000..ab5195e
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_queue.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/edk/system/message_in_transit_queue.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace mojo {
+namespace system {
+
+MessageInTransitQueue::MessageInTransitQueue() {
+}
+
+MessageInTransitQueue::~MessageInTransitQueue() {
+ if (!IsEmpty()) {
+ LOG(WARNING) << "Destroying nonempty message queue";
+ Clear();
+ }
+}
+
+void MessageInTransitQueue::Clear() {
+ STLDeleteElements(&queue_);
+}
+
+void MessageInTransitQueue::Swap(MessageInTransitQueue* other) {
+ queue_.swap(other->queue_);
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit_queue.h b/mojo/edk/system/message_in_transit_queue.h
new file mode 100644
index 0000000..d81bd2b
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_queue.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_EDK_SYSTEM_MESSAGE_IN_TRANSIT_QUEUE_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_QUEUE_H_
+
+#include <deque>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/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();
+ ~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();
+
+ // Efficiently swaps contents with |*other|.
+ void Swap(MessageInTransitQueue* other);
+
+ 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_EDK_SYSTEM_MESSAGE_IN_TRANSIT_QUEUE_H_
diff --git a/mojo/edk/system/message_pipe.cc b/mojo/edk/system/message_pipe.cc
new file mode 100644
index 0000000..55f1285
--- /dev/null
+++ b/mojo/edk/system/message_pipe.cc
@@ -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.
+
+#include "mojo/edk/system/message_pipe.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/local_message_pipe_endpoint.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/message_pipe_endpoint.h"
+#include "mojo/edk/system/proxy_message_pipe_endpoint.h"
+
+namespace mojo {
+namespace system {
+
+// static
+MessagePipe* MessagePipe::CreateLocalLocal() {
+ MessagePipe* message_pipe = new MessagePipe();
+ message_pipe->endpoints_[0].reset(new LocalMessagePipeEndpoint());
+ message_pipe->endpoints_[1].reset(new LocalMessagePipeEndpoint());
+ return message_pipe;
+}
+
+// static
+MessagePipe* MessagePipe::CreateLocalProxy(
+ scoped_refptr<ChannelEndpoint>* channel_endpoint) {
+ DCHECK(!channel_endpoint->get()); // Not technically wrong, but unlikely.
+ MessagePipe* message_pipe = new MessagePipe();
+ message_pipe->endpoints_[0].reset(new LocalMessagePipeEndpoint());
+ *channel_endpoint = new ChannelEndpoint(message_pipe, 1);
+ message_pipe->endpoints_[1].reset(
+ new ProxyMessagePipeEndpoint(channel_endpoint->get()));
+ return message_pipe;
+}
+
+// static
+MessagePipe* MessagePipe::CreateProxyLocal(
+ scoped_refptr<ChannelEndpoint>* channel_endpoint) {
+ DCHECK(!channel_endpoint->get()); // Not technically wrong, but unlikely.
+ MessagePipe* message_pipe = new MessagePipe();
+ *channel_endpoint = new ChannelEndpoint(message_pipe, 0);
+ message_pipe->endpoints_[0].reset(
+ new ProxyMessagePipeEndpoint(channel_endpoint->get()));
+ message_pipe->endpoints_[1].reset(new LocalMessagePipeEndpoint());
+ return message_pipe;
+}
+
+// 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_);
+ // The endpoint's |OnPeerClose()| may have been called first and returned
+ // false, which would have resulted in its destruction.
+ if (!endpoints_[port])
+ return;
+
+ 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,
+ UserPointer<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,
+ UserPointer<void> bytes,
+ UserPointer<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);
+}
+
+HandleSignalsState MessagePipe::GetHandleSignalsState(unsigned port) const {
+ DCHECK(port == 0 || port == 1);
+
+ base::AutoLock locker(const_cast<base::Lock&>(lock_));
+ DCHECK(endpoints_[port]);
+
+ return endpoints_[port]->GetHandleSignalsState();
+}
+
+MojoResult MessagePipe::AddWaiter(unsigned port,
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ DCHECK(port == 0 || port == 1);
+
+ base::AutoLock locker(lock_);
+ DCHECK(endpoints_[port]);
+
+ return endpoints_[port]->AddWaiter(waiter, signals, context, signals_state);
+}
+
+void MessagePipe::RemoveWaiter(unsigned port,
+ Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ DCHECK(port == 0 || port == 1);
+
+ base::AutoLock locker(lock_);
+ DCHECK(endpoints_[port]);
+
+ endpoints_[port]->RemoveWaiter(waiter, signals_state);
+}
+
+scoped_refptr<ChannelEndpoint> MessagePipe::ConvertLocalToProxy(unsigned port) {
+ DCHECK(port == 0 || port == 1);
+
+ base::AutoLock locker(lock_);
+ DCHECK(endpoints_[port]);
+ DCHECK_EQ(endpoints_[port]->GetType(), MessagePipeEndpoint::kTypeLocal);
+
+ // TODO(vtl): Allowing this case is a temporary hack. It'll set up a
+ // |MessagePipe| with two proxy endpoints, which will then act as a proxy
+ // (rather than trying to connect the two ends directly).
+ DLOG_IF(WARNING,
+ !!endpoints_[GetPeerPort(port)] &&
+ endpoints_[GetPeerPort(port)]->GetType() !=
+ MessagePipeEndpoint::kTypeLocal)
+ << "Direct message pipe passing across multiple channels not yet "
+ "implemented; will proxy";
+
+ scoped_ptr<MessagePipeEndpoint> old_endpoint(endpoints_[port].Pass());
+ scoped_refptr<ChannelEndpoint> channel_endpoint(
+ new ChannelEndpoint(this, port));
+ endpoints_[port].reset(new ProxyMessagePipeEndpoint(channel_endpoint.get()));
+ channel_endpoint->TakeMessages(static_cast<LocalMessagePipeEndpoint*>(
+ old_endpoint.get())->message_queue());
+ old_endpoint->Close();
+
+ return channel_endpoint;
+}
+
+MojoResult MessagePipe::EnqueueMessage(unsigned port,
+ scoped_ptr<MessageInTransit> message) {
+ return EnqueueMessageInternal(port, message.Pass(), nullptr);
+}
+
+MessagePipe::MessagePipe() {
+}
+
+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/mojo/edk/system/message_pipe.h b/mojo/edk/system/message_pipe.h
new file mode 100644
index 0000000..e1d9f63
--- /dev/null
+++ b/mojo/edk/system/message_pipe.h
@@ -0,0 +1,132 @@
+// 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_EDK_SYSTEM_MESSAGE_PIPE_H_
+#define MOJO_EDK_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/edk/system/dispatcher.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe_endpoint.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace system {
+
+class ChannelEndpoint;
+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:
+ // Creates a |MessagePipe| with two new |LocalMessagePipeEndpoint|s.
+ static MessagePipe* CreateLocalLocal();
+
+ // Creates a |MessagePipe| with a |LocalMessagePipeEndpoint| on port 0 and a
+ // |ProxyMessagePipeEndpoint| on port 1. |*channel_endpoint| is set to the
+ // (newly-created) |ChannelEndpoint| for the latter.
+ static MessagePipe* CreateLocalProxy(
+ scoped_refptr<ChannelEndpoint>* channel_endpoint);
+
+ // Creates a |MessagePipe| with a |ProxyMessagePipeEndpoint| on port 0 and a
+ // |LocalMessagePipeEndpoint| on port 1. |*channel_endpoint| is set to the
+ // (newly-created) |ChannelEndpoint| for the former.
+ // Note: This is really only needed in tests (outside of tests, this
+ // configuration arises from a local message pipe having its port 0
+ // "converted" using |ConvertLocalToProxy()|).
+ static MessagePipe* CreateProxyLocal(
+ scoped_refptr<ChannelEndpoint>* channel_endpoint);
+
+ // 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,
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags);
+ MojoResult ReadMessage(unsigned port,
+ UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags);
+ HandleSignalsState GetHandleSignalsState(unsigned port) const;
+ MojoResult AddWaiter(unsigned port,
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ void RemoveWaiter(unsigned port,
+ Waiter* waiter,
+ HandleSignalsState* signals_state);
+
+ // This is called by the dispatcher to convert a local endpoint to a proxy
+ // endpoint.
+ scoped_refptr<ChannelEndpoint> 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);
+
+ private:
+ MessagePipe();
+
+ 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_EDK_SYSTEM_MESSAGE_PIPE_H_
diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc
new file mode 100644
index 0000000..2b3f028
--- /dev/null
+++ b/mojo/edk/system/message_pipe_dispatcher.cc
@@ -0,0 +1,280 @@
+// 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/edk/system/message_pipe_dispatcher.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/local_message_pipe_endpoint.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/options_validation.h"
+#include "mojo/edk/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(
+ UserPointer<const MojoCreateMessagePipeOptions> in_options,
+ MojoCreateMessagePipeOptions* out_options) {
+ const MojoCreateMessagePipeOptionsFlags kKnownFlags =
+ MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE;
+
+ *out_options = kDefaultCreateOptions;
+ if (in_options.IsNull())
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateMessagePipeOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateMessagePipeOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // 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.get());
+ DCHECK(port == 0 || port == 1);
+
+ message_pipe_ = message_pipe;
+ port_ = port;
+}
+
+Dispatcher::Type MessagePipeDispatcher::GetType() const {
+ return kTypeMessagePipe;
+}
+
+// static
+scoped_refptr<MessagePipeDispatcher>
+MessagePipeDispatcher::CreateRemoteMessagePipe(
+ scoped_refptr<ChannelEndpoint>* channel_endpoint) {
+ scoped_refptr<MessagePipe> message_pipe(
+ MessagePipe::CreateLocalProxy(channel_endpoint));
+ scoped_refptr<MessagePipeDispatcher> dispatcher(
+ new MessagePipeDispatcher(MessagePipeDispatcher::kDefaultCreateOptions));
+ dispatcher->Init(message_pipe, 0);
+ return dispatcher;
+}
+
+// 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>();
+ }
+
+ scoped_refptr<ChannelEndpoint> channel_endpoint;
+ scoped_refptr<MessagePipeDispatcher> dispatcher =
+ CreateRemoteMessagePipe(&channel_endpoint);
+
+ 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->AttachEndpoint(channel_endpoint);
+ 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 << ")";
+
+ channel->RunEndpoint(channel_endpoint, remote_id);
+
+ // TODO(vtl): FIXME -- Need some error handling here.
+ channel->RunRemoteMessagePipeEndpoint(local_id, remote_id);
+ return dispatcher;
+}
+
+MessagePipeDispatcher::~MessagePipeDispatcher() {
+ // |Close()|/|CloseImplNoLock()| should have taken care of the pipe.
+ DCHECK(!message_pipe_.get());
+}
+
+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_ = nullptr;
+ 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_ = nullptr;
+ port_ = kInvalidPort;
+ return scoped_refptr<Dispatcher>(rv.get());
+}
+
+MojoResult MessagePipeDispatcher::WriteMessageImplNoLock(
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags) {
+ DCHECK(!transports || (transports->size() > 0 &&
+ transports->size() <= kMaxMessageNumHandles));
+
+ lock().AssertAcquired();
+
+ if (num_bytes > kMaxMessageNumBytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ return message_pipe_->WriteMessage(
+ port_, bytes, num_bytes, transports, flags);
+}
+
+MojoResult MessagePipeDispatcher::ReadMessageImplNoLock(
+ UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags) {
+ lock().AssertAcquired();
+ return message_pipe_->ReadMessage(
+ port_, bytes, num_bytes, dispatchers, num_dispatchers, flags);
+}
+
+HandleSignalsState MessagePipeDispatcher::GetHandleSignalsStateImplNoLock()
+ const {
+ lock().AssertAcquired();
+ return message_pipe_->GetHandleSignalsState(port_);
+}
+
+MojoResult MessagePipeDispatcher::AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ return message_pipe_->AddWaiter(
+ port_, waiter, signals, context, signals_state);
+}
+
+void MessagePipeDispatcher::RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ message_pipe_->RemoveWaiter(port_, waiter, signals_state);
+}
+
+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)
+ // and attach it to the channel.
+ MessageInTransit::EndpointId endpoint_id =
+ channel->AttachEndpoint(message_pipe_->ConvertLocalToProxy(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_ = nullptr;
+ 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/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h
new file mode 100644
index 0000000..de789ba
--- /dev/null
+++ b/mojo/edk/system/message_pipe_dispatcher.h
@@ -0,0 +1,135 @@
+// 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_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace system {
+
+class ChannelEndpoint;
+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(
+ UserPointer<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 scoped_refptr<MessagePipeDispatcher> CreateRemoteMessagePipe(
+ scoped_refptr<ChannelEndpoint>* channel_endpoint);
+
+ // 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(
+ UserPointer<const void> bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags) override;
+ virtual MojoResult ReadMessageImplNoLock(UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags) override;
+ virtual HandleSignalsState GetHandleSignalsStateImplNoLock() const override;
+ virtual MojoResult AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ virtual void RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) 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_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
diff --git a/mojo/edk/system/message_pipe_dispatcher_unittest.cc b/mojo/edk/system/message_pipe_dispatcher_unittest.cc
new file mode 100644
index 0000000..96ca436
--- /dev/null
+++ b/mojo/edk/system/message_pipe_dispatcher_unittest.cc
@@ -0,0 +1,729 @@
+// 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/edk/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/edk/system/message_pipe.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/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(MessagePipe::CreateLocalLocal());
+ d0->Init(mp, i); // 0, 1.
+ d1->Init(mp, i ^ 1); // 1, 0.
+ }
+ Waiter w;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Try adding a writable waiter when already writable.
+ w.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ // 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 1, nullptr));
+ buffer[0] = 123456789;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d1->WriteMessage(UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ 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());
+ hss = HandleSignalsState();
+ d0->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Try adding a readable waiter when already readable (from above).
+ w.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 2, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ // 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(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 3, nullptr));
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout());
+ hss = HandleSignalsState();
+ d0->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for non-zero, finite time for readability on |d0| (will time out).
+ w.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 3, nullptr));
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), nullptr));
+ base::TimeDelta elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonTimeout());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonTimeout());
+ hss = HandleSignalsState();
+ d0->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ 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(MessagePipe::CreateLocalLocal());
+ d0->Init(mp, 0);
+ d1->Init(mp, 1);
+ }
+
+ // |WriteMessage|:
+ // Huge buffer size.
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ d0->WriteMessage(UserPointer<const void>(buffer),
+ std::numeric_limits<uint32_t>::max(),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, d0->Close());
+ EXPECT_EQ(MOJO_RESULT_OK, d1->Close());
+}
+
+// These test invalid arguments that should cause death if we're being paranoid
+// about checking arguments (which we would want to do if, e.g., we were in a
+// true "kernel" situation, but we might not want to do otherwise for
+// performance reasons). Probably blatant errors like passing in null pointers
+// (for required pointer arguments) will still cause death, but perhaps not
+// predictably.
+TEST(MessagePipeDispatcherTest, InvalidParamsDeath) {
+ const char kMemoryCheckFailedRegex[] = "Check failed";
+
+ scoped_refptr<MessagePipeDispatcher> d0(
+ new MessagePipeDispatcher(MessagePipeDispatcher::kDefaultCreateOptions));
+ scoped_refptr<MessagePipeDispatcher> d1(
+ new MessagePipeDispatcher(MessagePipeDispatcher::kDefaultCreateOptions));
+ {
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalLocal());
+ d0->Init(mp, 0);
+ d1->Init(mp, 1);
+ }
+
+ // |WriteMessage|:
+ // Null buffer with nonzero buffer size.
+ EXPECT_DEATH_IF_SUPPORTED(
+ d0->WriteMessage(
+ NullUserPointer(), 1, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ kMemoryCheckFailedRegex);
+
+ // |ReadMessage|:
+ // Null buffer with nonzero buffer size.
+ // First write something so that we actually have something to read.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d1->WriteMessage(UserPointer<const void>("x"),
+ 1,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ uint32_t buffer_size = 1;
+ EXPECT_DEATH_IF_SUPPORTED(d0->ReadMessage(NullUserPointer(),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ kMemoryCheckFailedRegex);
+
+ 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(MessagePipe::CreateLocalLocal());
+ d0->Init(mp, i); // 0, 1.
+ d1->Init(mp, i ^ 1); // 1, 0.
+ }
+ Waiter w;
+ HandleSignalsState hss;
+
+ // Write (twice) to |d1|.
+ buffer[0] = 123456789;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d1->WriteMessage(UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ buffer[0] = 234567890;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d1->WriteMessage(UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Try waiting for readable on |d0|; should fail (already satisfied).
+ w.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Try reading from |d1|; should fail (nothing to read).
+ buffer[0] = 0;
+ buffer_size = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ d1->ReadMessage(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 1, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read from |d0|.
+ buffer[0] = 0;
+ buffer_size = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d0->ReadMessage(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 2, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read again from |d0|.
+ buffer[0] = 0;
+ buffer_size = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d0->ReadMessage(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 3, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // Try waiting for writable on |d0|; should fail (unsatisfiable).
+ w.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ d0->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // 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(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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(UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, d0->Close());
+ }
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/396386
+#define MAYBE_BasicThreaded DISABLED_BasicThreaded
+#else
+#define MAYBE_BasicThreaded BasicThreaded
+#endif
+TEST(MessagePipeDispatcherTest, MAYBE_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;
+ HandleSignalsState hss;
+
+ // 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(MessagePipe::CreateLocalLocal());
+ 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,
+ &hss);
+ 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(UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ 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);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ &hss);
+ stopwatch.Start();
+ thread.Start();
+ } // Joins the thread.
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout());
+ EXPECT_FALSE(did_wait);
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, result);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Consume what we wrote to |d0|.
+ buffer[0] = 0;
+ buffer_size = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ d1->ReadMessage(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ &hss);
+ 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(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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(MessagePipe::CreateLocalLocal());
+ 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,
+ &hss);
+ 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(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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(UserPointer<const void>(buffer),
+ bytes_to_write,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ *bytes_written_ += bytes_to_write;
+ }
+
+ // Write one last "quit" message.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ write_dispatcher_->WriteMessage(UserPointer<const void>("quit"),
+ 4,
+ nullptr,
+ 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];
+ Waiter w;
+ HandleSignalsState hss;
+ MojoResult result;
+
+ // Read messages.
+ for (;;) {
+ // Wait for it to be readable.
+ w.Init();
+ hss = HandleSignalsState();
+ result =
+ read_dispatcher_->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss);
+ 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, nullptr));
+ read_dispatcher_->RemoveWaiter(&w, &hss);
+ }
+ // We may not actually be readable, since we're racing with other threads.
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ // 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(UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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(MessagePipe::CreateLocalLocal());
+ 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/mojo/edk/system/message_pipe_endpoint.cc b/mojo/edk/system/message_pipe_endpoint.cc
new file mode 100644
index 0000000..df623d4
--- /dev/null
+++ b/mojo/edk/system/message_pipe_endpoint.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/edk/system/message_pipe_endpoint.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace system {
+
+void MessagePipeEndpoint::CancelAllWaiters() {
+ NOTREACHED();
+}
+
+MojoResult MessagePipeEndpoint::ReadMessage(UserPointer<void> /*bytes*/,
+ UserPointer<uint32_t> /*num_bytes*/,
+ DispatcherVector* /*dispatchers*/,
+ uint32_t* /*num_dispatchers*/,
+ MojoReadMessageFlags /*flags*/) {
+ NOTREACHED();
+ return MOJO_RESULT_INTERNAL;
+}
+
+HandleSignalsState MessagePipeEndpoint::GetHandleSignalsState() const {
+ NOTREACHED();
+ return HandleSignalsState();
+}
+
+MojoResult MessagePipeEndpoint::AddWaiter(Waiter* /*waiter*/,
+ MojoHandleSignals /*signals*/,
+ uint32_t /*context*/,
+ HandleSignalsState* signals_state) {
+ NOTREACHED();
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ return MOJO_RESULT_INTERNAL;
+}
+
+void MessagePipeEndpoint::RemoveWaiter(Waiter* /*waiter*/,
+ HandleSignalsState* signals_state) {
+ NOTREACHED();
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+}
+
+void MessagePipeEndpoint::Attach(ChannelEndpoint* /*channel_endpoint*/) {
+ NOTREACHED();
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/message_pipe_endpoint.h b/mojo/edk/system/message_pipe_endpoint.h
new file mode 100644
index 0000000..7bc0a0d
--- /dev/null
+++ b/mojo/edk/system/message_pipe_endpoint.h
@@ -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.
+
+#ifndef MOJO_EDK_SYSTEM_MESSAGE_PIPE_ENDPOINT_H_
+#define MOJO_EDK_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/edk/system/dispatcher.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace system {
+
+class ChannelEndpoint;
+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;
+ virtual void Close() = 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 CancelAllWaiters();
+ virtual MojoResult ReadMessage(UserPointer<void> bytes,
+ UserPointer<uint32_t> num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags);
+ virtual HandleSignalsState GetHandleSignalsState() const;
+ virtual MojoResult AddWaiter(Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ virtual void RemoveWaiter(Waiter* waiter, HandleSignalsState* signals_state);
+
+ // 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(ChannelEndpoint* channel_endpoint);
+
+ protected:
+ MessagePipeEndpoint() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeEndpoint);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_PIPE_ENDPOINT_H_
diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc
new file mode 100644
index 0000000..9861e0b
--- /dev/null
+++ b/mojo/edk/system/message_pipe_perftest.cc
@@ -0,0 +1,180 @@
+// Copyright 2014 The Chromium 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/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/pickle.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/perf_time_logger.h"
+#include "base/time/time.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/local_message_pipe_endpoint.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/message_pipe_test_utils.h"
+#include "mojo/edk/system/proxy_message_pipe_endpoint.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace system {
+namespace {
+
+class MultiprocessMessagePipePerfTest
+ : public test::MultiprocessMessagePipeTestBase {
+ public:
+ MultiprocessMessagePipePerfTest() : message_count_(0), message_size_(0) {}
+
+ void SetUpMeasurement(int message_count, size_t message_size) {
+ message_count_ = message_count;
+ message_size_ = message_size;
+ payload_ = Pickle();
+ payload_.WriteString(std::string(message_size, '*'));
+ read_buffer_.resize(message_size * 2);
+ }
+
+ protected:
+ void WriteWaitThenRead(scoped_refptr<MessagePipe> mp) {
+ CHECK_EQ(mp->WriteMessage(0,
+ UserPointer<const void>(payload_.data()),
+ static_cast<uint32_t>(payload_.size()),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ HandleSignalsState hss;
+ CHECK_EQ(test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss),
+ MOJO_RESULT_OK);
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer_.size());
+ CHECK_EQ(mp->ReadMessage(0,
+ UserPointer<void>(&read_buffer_[0]),
+ MakeUserPointer(&read_buffer_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ CHECK_EQ(read_buffer_size, static_cast<uint32_t>(payload_.size()));
+ }
+
+ void SendQuitMessage(scoped_refptr<MessagePipe> mp) {
+ CHECK_EQ(mp->WriteMessage(0,
+ UserPointer<const void>(""),
+ 0,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ }
+
+ void Measure(scoped_refptr<MessagePipe> mp) {
+ // Have one ping-pong to ensure channel being established.
+ WriteWaitThenRead(mp);
+
+ std::string test_name =
+ base::StringPrintf("IPC_Perf_%dx_%u",
+ message_count_,
+ static_cast<unsigned>(message_size_));
+ base::PerfTimeLogger logger(test_name.c_str());
+
+ for (int i = 0; i < message_count_; ++i)
+ WriteWaitThenRead(mp);
+
+ logger.Done();
+ }
+
+ private:
+ int message_count_;
+ size_t message_size_;
+ Pickle payload_;
+ std::string read_buffer_;
+ scoped_ptr<base::PerfTimeLogger> perf_logger_;
+};
+
+// 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(PingPongClient) {
+ embedder::SimplePlatformSupport platform_support;
+ test::ChannelThread channel_thread(&platform_support);
+ embedder::ScopedPlatformHandle client_platform_handle =
+ mojo::test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ scoped_refptr<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ channel_thread.Start(client_platform_handle.Pass(), ep);
+
+ std::string buffer(1000000, '\0');
+ int rv = 0;
+ while (true) {
+ // Wait for our end of the message pipe to be readable.
+ HandleSignalsState hss;
+ MojoResult result =
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss);
+ if (result != MOJO_RESULT_OK) {
+ rv = result;
+ break;
+ }
+
+ uint32_t read_size = static_cast<uint32_t>(buffer.size());
+ CHECK_EQ(mp->ReadMessage(0,
+ UserPointer<void>(&buffer[0]),
+ MakeUserPointer(&read_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+
+ // Empty message indicates quitting
+ if (0 == read_size)
+ break;
+
+ CHECK_EQ(mp->WriteMessage(0,
+ UserPointer<const void>(&buffer[0]),
+ static_cast<uint32_t>(read_size),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ }
+
+ mp->Close(0);
+ return rv;
+}
+
+// Repeatedly sends messages as previous one got replied by the child.
+// Waits for the child to close its end before quitting once specified
+// number of messages has been sent.
+TEST_F(MultiprocessMessagePipePerfTest, PingPong) {
+ helper()->StartChild("PingPongClient");
+
+ scoped_refptr<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ Init(ep);
+
+ // This values are set to align with one at ipc_pertests.cc for comparison.
+ const size_t kMsgSize[5] = {12, 144, 1728, 20736, 248832};
+ const int kMessageCount[5] = {50000, 50000, 50000, 12000, 1000};
+
+ for (size_t i = 0; i < 5; i++) {
+ SetUpMeasurement(kMessageCount[i], kMsgSize[i]);
+ Measure(mp);
+ }
+
+ SendQuitMessage(mp);
+ mp->Close(0);
+ EXPECT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/message_pipe_test_utils.cc b/mojo/edk/system/message_pipe_test_utils.cc
new file mode 100644
index 0000000..b600945
--- /dev/null
+++ b/mojo/edk/system/message_pipe_test_utils.cc
@@ -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.
+
+#include "mojo/edk/system/message_pipe_test_utils.h"
+
+#include "base/bind.h"
+#include "base/threading/platform_thread.h" // For |Sleep()|.
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/waiter.h"
+
+namespace mojo {
+namespace system {
+namespace test {
+
+MojoResult WaitIfNecessary(scoped_refptr<MessagePipe> mp,
+ MojoHandleSignals signals,
+ HandleSignalsState* signals_state) {
+ Waiter waiter;
+ waiter.Init();
+
+ MojoResult add_result = mp->AddWaiter(0, &waiter, signals, 0, signals_state);
+ 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, nullptr);
+ mp->RemoveWaiter(0, &waiter, signals_state);
+ return wait_result;
+}
+
+ChannelThread::ChannelThread(embedder::PlatformSupport* platform_support)
+ : platform_support_(platform_support),
+ test_io_thread_(base::TestIOThread::kManualStart) {
+}
+
+ChannelThread::~ChannelThread() {
+ Stop();
+}
+
+void ChannelThread::Start(embedder::ScopedPlatformHandle platform_handle,
+ scoped_refptr<ChannelEndpoint> channel_endpoint) {
+ test_io_thread_.Start();
+ test_io_thread_.PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&ChannelThread::InitChannelOnIOThread,
+ base::Unretained(this),
+ base::Passed(&platform_handle),
+ channel_endpoint));
+}
+
+void ChannelThread::Stop() {
+ if (channel_.get()) {
+ // 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();
+}
+
+void ChannelThread::InitChannelOnIOThread(
+ embedder::ScopedPlatformHandle platform_handle,
+ scoped_refptr<ChannelEndpoint> channel_endpoint) {
+ CHECK_EQ(base::MessageLoop::current(), test_io_thread_.message_loop());
+ CHECK(platform_handle.is_valid());
+
+ // Create and initialize |Channel|.
+ channel_ = new Channel(platform_support_);
+ 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_->AttachEndpoint(channel_endpoint),
+ Channel::kBootstrapEndpointId);
+ channel_->RunEndpoint(channel_endpoint, Channel::kBootstrapEndpointId);
+}
+
+void ChannelThread::ShutdownChannelOnIOThread() {
+ CHECK(channel_.get());
+ channel_->Shutdown();
+ channel_ = nullptr;
+}
+
+#if !defined(OS_IOS)
+MultiprocessMessagePipeTestBase::MultiprocessMessagePipeTestBase()
+ : channel_thread_(&platform_support_) {
+}
+
+MultiprocessMessagePipeTestBase::~MultiprocessMessagePipeTestBase() {
+}
+
+void MultiprocessMessagePipeTestBase::Init(scoped_refptr<ChannelEndpoint> ep) {
+ channel_thread_.Start(helper_.server_platform_handle.Pass(), ep);
+}
+#endif
+
+} // namespace test
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/message_pipe_test_utils.h b/mojo/edk/system/message_pipe_test_utils.h
new file mode 100644
index 0000000..9e6a4b7
--- /dev/null
+++ b/mojo/edk/system/message_pipe_test_utils.h
@@ -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.
+
+#ifndef MOJO_EDK_SYSTEM_MESSAGE_PIPE_TEST_UTILS_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_TEST_UTILS_H_
+
+#include "base/test/test_io_thread.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/multiprocess_test_helper.h"
+
+namespace mojo {
+namespace system {
+
+class Channel;
+class ChannelEndpoint;
+class MessagePipe;
+
+namespace test {
+
+MojoResult WaitIfNecessary(scoped_refptr<MessagePipe> mp,
+ MojoHandleSignals signals,
+ HandleSignalsState* signals_state);
+
+class ChannelThread {
+ public:
+ explicit ChannelThread(embedder::PlatformSupport* platform_support);
+ ~ChannelThread();
+
+ void Start(embedder::ScopedPlatformHandle platform_handle,
+ scoped_refptr<ChannelEndpoint> channel_endpoint);
+ void Stop();
+
+ private:
+ void InitChannelOnIOThread(embedder::ScopedPlatformHandle platform_handle,
+ scoped_refptr<ChannelEndpoint> channel_endpoint);
+ void ShutdownChannelOnIOThread();
+
+ embedder::PlatformSupport* const platform_support_;
+ base::TestIOThread test_io_thread_;
+ scoped_refptr<Channel> channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelThread);
+};
+
+#if !defined(OS_IOS)
+class MultiprocessMessagePipeTestBase : public testing::Test {
+ public:
+ MultiprocessMessagePipeTestBase();
+ virtual ~MultiprocessMessagePipeTestBase();
+
+ protected:
+ void Init(scoped_refptr<ChannelEndpoint> ep);
+
+ embedder::PlatformSupport* platform_support() { return &platform_support_; }
+ mojo::test::MultiprocessTestHelper* helper() { return &helper_; }
+
+ private:
+ embedder::SimplePlatformSupport platform_support_;
+ ChannelThread channel_thread_;
+ mojo::test::MultiprocessTestHelper helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiprocessMessagePipeTestBase);
+};
+#endif
+
+} // namespace test
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_PIPE_TEST_UTILS_H_
diff --git a/mojo/edk/system/message_pipe_unittest.cc b/mojo/edk/system/message_pipe_unittest.cc
new file mode 100644
index 0000000..dbffb4c
--- /dev/null
+++ b/mojo/edk/system/message_pipe_unittest.cc
@@ -0,0 +1,626 @@
+// 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/edk/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/edk/system/waiter.h"
+#include "mojo/edk/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(MessagePipe::CreateLocalLocal());
+
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ buffer[0] = 234567890;
+ buffer[1] = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp->WriteMessage(0,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ NullUserPointer(),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ mp->Close(1);
+}
+
+TEST(MessagePipeTest, CloseWithQueuedIncomingMessages) {
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalLocal());
+
+ 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,
+ UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ }
+
+ // Port 0 shouldn't be empty.
+ buffer_size = 0;
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ mp->ReadMessage(0,
+ NullUserPointer(),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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(MessagePipe::CreateLocalLocal());
+
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ NullUserPointer(),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<const void>(buffer),
+ static_cast<uint32_t>(sizeof(buffer[0])),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Discard from port 0.
+ buffer_size = 1;
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ mp->ReadMessage(0,
+ NullUserPointer(),
+ NullUserPointer(),
+ 0,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD));
+
+ mp->Close(0);
+ mp->Close(1);
+}
+
+TEST(MessagePipeTest, BasicWaiting) {
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalLocal());
+ Waiter waiter;
+ HandleSignalsState hss;
+
+ 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();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ mp->AddWaiter(0,
+ &waiter,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ 0,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Not yet readable.
+ waiter.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mp->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 1, nullptr));
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, nullptr));
+ hss = HandleSignalsState();
+ mp->RemoveWaiter(0, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Write from port 0 (to port 1), to make port 1 readable.
+ buffer[0] = 123456789;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp->WriteMessage(0,
+ UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Port 1 should already be readable now.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 2, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ mp->AddWaiter(1,
+ &waiter,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ 0,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ // ... and still writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 3, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Close port 0.
+ mp->Close(0);
+
+ // Now port 1 should not be writable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_WRITABLE, 4, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // But it should still be readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 5, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Read from port 1.
+ buffer[0] = 0;
+ buffer_size = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp->ReadMessage(1,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ 0,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(123456789, buffer[0]);
+
+ // Now port 1 should no longer be readable.
+ waiter.Init();
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ mp->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 6, nullptr));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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(MessagePipe::CreateLocalLocal());
+ test::SimpleWaiterThread thread(&result, &context);
+
+ thread.waiter()->Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mp->AddWaiter(
+ 1, thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1, nullptr));
+ 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,
+ UserPointer<const void>(buffer),
+ kBufferSize,
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ HandleSignalsState hss;
+ mp->RemoveWaiter(1, thread.waiter(), &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ 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(MessagePipe::CreateLocalLocal());
+ test::SimpleWaiterThread thread(&result, &context);
+
+ thread.waiter()->Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mp->AddWaiter(
+ 1, thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 2, nullptr));
+ 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(MessagePipe::CreateLocalLocal());
+ test::SimpleWaiterThread thread(&result, &context);
+
+ thread.waiter()->Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mp->AddWaiter(
+ 1, thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3, nullptr));
+ 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);
+
+ HandleSignalsState hss;
+ mp->RemoveWaiter(1, thread.waiter(), &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ 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/mojo/edk/system/multiprocess_message_pipe_unittest.cc b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
new file mode 100644
index 0000000..2f22d67
--- /dev/null
+++ b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
@@ -0,0 +1,523 @@
+// 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/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "build/build_config.h" // TODO(vtl): Remove this.
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/message_pipe_test_utils.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace system {
+namespace {
+
+class MultiprocessMessagePipeTest
+ : public test::MultiprocessMessagePipeTestBase {};
+
+// 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) {
+ embedder::SimplePlatformSupport platform_support;
+ test::ChannelThread channel_thread(&platform_support);
+ embedder::ScopedPlatformHandle client_platform_handle =
+ mojo::test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ scoped_refptr<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ channel_thread.Start(client_platform_handle.Pass(), ep);
+
+ const std::string quitquitquit("quitquitquit");
+ int rv = 0;
+ for (;; rv = (rv + 1) % 100) {
+ // Wait for our end of the message pipe to be readable.
+ HandleSignalsState hss;
+ MojoResult result =
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss);
+ if (result != MOJO_RESULT_OK) {
+ // It was closed, probably.
+ CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION);
+ CHECK_EQ(hss.satisfied_signals, 0u);
+ CHECK_EQ(hss.satisfiable_signals, 0u);
+ break;
+ } else {
+ CHECK((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ CHECK((hss.satisfiable_signals & 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,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&read_buffer_size),
+ nullptr,
+ nullptr,
+ 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,
+ UserPointer<const void>(write_buffer.data()),
+ static_cast<uint32_t>(write_buffer.size()),
+ nullptr,
+ 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<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ Init(ep);
+
+ std::string hello("hello");
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp->WriteMessage(0,
+ UserPointer<const void>(hello.data()),
+ static_cast<uint32_t>(hello.size()),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ // The child may or may not have closed its end of the message pipe and died
+ // (and we may or may not know it yet), so our end may or may not appear as
+ // writable.
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & 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,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&read_buffer_size),
+ nullptr,
+ nullptr,
+ 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<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ Init(ep);
+
+ 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,
+ UserPointer<const void>(write_buffer.data()),
+ static_cast<uint32_t>(write_buffer.size()),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ }
+
+ const std::string quitquitquit("quitquitquit");
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp->WriteMessage(0,
+ UserPointer<const void>(quitquitquit.data()),
+ static_cast<uint32_t>(quitquitquit.size()),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ for (size_t i = 0; i < kNumMessages; i++) {
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ // The child may or may not have closed its end of the message pipe and died
+ // (and we may or may not know it yet), so our end may or may not appear as
+ // writable.
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & 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,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&read_buffer_size),
+ nullptr,
+ nullptr,
+ 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").
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ mp->Close(0);
+
+ EXPECT_EQ(static_cast<int>(kNumMessages % 100),
+ helper()->WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckSharedBuffer) {
+ embedder::SimplePlatformSupport platform_support;
+ test::ChannelThread channel_thread(&platform_support);
+ embedder::ScopedPlatformHandle client_platform_handle =
+ mojo::test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ scoped_refptr<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ channel_thread.Start(client_platform_handle.Pass(), ep);
+
+ // Wait for the first message from our parent.
+ HandleSignalsState hss;
+ CHECK_EQ(test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss),
+ MOJO_RESULT_OK);
+ // In this test, the parent definitely doesn't close its end of the message
+ // pipe before we do.
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // 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,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&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<embedder::PlatformSharedBufferMapping> mapping;
+ CHECK_EQ(dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping),
+ MOJO_RESULT_OK);
+ CHECK(mapping);
+ CHECK(mapping->GetBase());
+ CHECK_EQ(mapping->GetLength(), 100u);
+
+ // Write some stuff to the shared buffer.
+ static const char kHello[] = "hello";
+ memcpy(mapping->GetBase(), 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,
+ UserPointer<const void>(&go2[0]),
+ static_cast<uint32_t>(go2.size()),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+
+ // Now wait for our parent to send us a message.
+ hss = HandleSignalsState();
+ CHECK_EQ(test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss),
+ MOJO_RESULT_OK);
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ read_buffer = std::string(100, '\0');
+ num_bytes = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(mp->ReadMessage(0,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&num_bytes),
+ nullptr,
+ nullptr,
+ 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->GetBase(), 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<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ Init(ep);
+
+ // Make a shared buffer.
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100,
+ &dispatcher));
+ ASSERT_TRUE(dispatcher.get());
+
+ // Make a mapping.
+ scoped_ptr<embedder::PlatformSharedBufferMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ ASSERT_TRUE(mapping);
+ ASSERT_TRUE(mapping->GetBase());
+ ASSERT_EQ(100u, mapping->GetLength());
+
+ // 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,
+ UserPointer<const void>(&go1[0]),
+ static_cast<uint32_t>(go1.size()),
+ &transports,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ transport.End();
+
+ EXPECT_TRUE(dispatcher->HasOneRef());
+ dispatcher = nullptr;
+
+ // Wait for a message from the child.
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & 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,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&num_bytes),
+ nullptr,
+ nullptr,
+ 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->GetBase(), kHello, sizeof(kHello)));
+
+ // Now we'll write some stuff to the shared buffer.
+ static const char kWorld[] = "world!!!";
+ memcpy(mapping->GetBase(), 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,
+ UserPointer<const void>(&go3[0]),
+ static_cast<uint32_t>(go3.size()),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for |mp| to become readable, which should fail.
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ mp->Close(0);
+
+ EXPECT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckPlatformHandleFile) {
+ embedder::SimplePlatformSupport platform_support;
+ test::ChannelThread channel_thread(&platform_support);
+ embedder::ScopedPlatformHandle client_platform_handle =
+ mojo::test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ scoped_refptr<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ channel_thread.Start(client_platform_handle.Pass(), ep);
+
+ HandleSignalsState hss;
+ CHECK_EQ(test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss),
+ MOJO_RESULT_OK);
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ 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,
+ UserPointer<void>(&read_buffer[0]),
+ MakeUserPointer(&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) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ helper()->StartChild("CheckPlatformHandleFile");
+
+ scoped_refptr<ChannelEndpoint> ep;
+ scoped_refptr<MessagePipe> mp(MessagePipe::CreateLocalProxy(&ep));
+ Init(ep);
+
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &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,
+ UserPointer<const void>(&hello[0]),
+ static_cast<uint32_t>(hello.size()),
+ &transports,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ transport.End();
+
+ EXPECT_TRUE(dispatcher->HasOneRef());
+ dispatcher = nullptr;
+
+ // Wait for it to become readable, which should fail.
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ test::WaitIfNecessary(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ mp->Close(0);
+
+ EXPECT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/options_validation.h b/mojo/edk/system/options_validation.h
new file mode 100644
index 0000000..4772508
--- /dev/null
+++ b/mojo/edk/system/options_validation.h
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium 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_EDK_SYSTEM_OPTIONS_VALIDATION_H_
+#define MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace system {
+
+template <class Options>
+class UserOptionsReader {
+ public:
+ // Constructor from a |UserPointer<const Options>| (which it checks -- this
+ // constructor has side effects!).
+ // Note: We initialize |options_reader_| without checking, since we do a check
+ // in |GetSizeForReader()|.
+ explicit UserOptionsReader(UserPointer<const Options> options)
+ : options_reader_(UserPointer<const char>::Reader::NoCheck(),
+ options.template ReinterpretCast<const char>(),
+ GetSizeForReader(options)) {
+ static_assert(offsetof(Options, struct_size) == 0,
+ "struct_size not first member of Options");
+ // TODO(vtl): Enable when MSVC supports this (C++11 extended sizeof):
+ // static_assert(sizeof(Options::struct_size) == sizeof(uint32_t),
+ // "Options::struct_size not a uint32_t");
+ // (Or maybe assert that its type is uint32_t?)
+ }
+
+ bool is_valid() const { return !!options_reader_.GetPointer(); }
+
+ const Options& options() const {
+ DCHECK(is_valid());
+ return *reinterpret_cast<const Options*>(options_reader_.GetPointer());
+ }
+
+ // Checks that the given (variable-size) |options| passed to the constructor
+ // (plausibly) has a member at the given offset with the given size. You
+ // probably want to use |OPTIONS_STRUCT_HAS_MEMBER()| instead.
+ bool HasMember(size_t offset, size_t size) const {
+ DCHECK(is_valid());
+ // We assume that |offset| and |size| are reasonable, since they should come
+ // from |offsetof(Options, some_member)| and |sizeof(Options::some_member)|,
+ // respectively.
+ return options().struct_size >= offset + size;
+ }
+
+ private:
+ static inline size_t GetSizeForReader(UserPointer<const Options> options) {
+ uint32_t struct_size =
+ options.template ReinterpretCast<const uint32_t>().Get();
+ if (struct_size < sizeof(uint32_t))
+ return 0;
+
+ // Check the full requested size.
+ // Note: Use |MOJO_ALIGNOF()| here to match the exact macro used in the
+ // declaration of Options structs.
+ internal::CheckUserPointerWithSize<MOJO_ALIGNOF(Options)>(options.pointer_,
+ struct_size);
+ options.template ReinterpretCast<const char>().CheckArray(struct_size);
+ // But we'll never look at more than |sizeof(Options)| bytes.
+ return std::min(static_cast<size_t>(struct_size), sizeof(Options));
+ }
+
+ UserPointer<const char>::Reader options_reader_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserOptionsReader);
+};
+
+// Macro to invoke |UserOptionsReader<Options>::HasMember()| parametrized by
+// member name instead of offset and size.
+//
+// (We can't just give |HasMember()| 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 of (the
+// contortion below). We might also be able to pull out the type |Options| from
+// |reader| (using |decltype|) instead of requiring a parameter.
+#define OPTIONS_STRUCT_HAS_MEMBER(Options, member, reader) \
+ reader.HasMember(offsetof(Options, member), sizeof(reader.options().member))
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_
diff --git a/mojo/edk/system/options_validation_unittest.cc b/mojo/edk/system/options_validation_unittest.cc
new file mode 100644
index 0000000..f410f24
--- /dev/null
+++ b/mojo/edk/system/options_validation_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium 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/edk/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 kOptions = {kSizeOfTestOptions};
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(&kOptions));
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+ {
+ const TestOptions kOptions = {static_cast<uint32_t>(
+ offsetof(TestOptions, struct_size) + sizeof(uint32_t))};
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(&kOptions));
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+
+ {
+ const TestOptions kOptions = {
+ static_cast<uint32_t>(offsetof(TestOptions, flags) + sizeof(uint32_t))};
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(&kOptions));
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+ {
+ MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {};
+ TestOptions* options = reinterpret_cast<TestOptions*>(buf);
+ options->struct_size = kSizeOfTestOptions + 1;
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(options));
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+ {
+ MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {};
+ TestOptions* options = reinterpret_cast<TestOptions*>(buf);
+ options->struct_size = kSizeOfTestOptions + 4;
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(options));
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+}
+
+TEST(OptionsValidationTest, Invalid) {
+ // Size too small:
+ for (size_t i = 0; i < sizeof(uint32_t); i++) {
+ TestOptions options = {static_cast<uint32_t>(i)};
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(&options));
+ EXPECT_FALSE(reader.is_valid()) << i;
+ }
+}
+
+// These test invalid arguments that should cause death if we're being paranoid
+// about checking arguments (which we would want to do if, e.g., we were in a
+// true "kernel" situation, but we might not want to do otherwise for
+// performance reasons). Probably blatant errors like passing in null pointers
+// (for required pointer arguments) will still cause death, but perhaps not
+// predictably.
+TEST(OptionsValidationTest, InvalidDeath) {
+ const char kMemoryCheckFailedRegex[] = "Check failed";
+
+ // Null:
+ EXPECT_DEATH_IF_SUPPORTED(
+ { UserOptionsReader<TestOptions> reader((NullUserPointer())); },
+ kMemoryCheckFailedRegex);
+
+ // Unaligned:
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ UserOptionsReader<TestOptions> reader(
+ MakeUserPointer(reinterpret_cast<const TestOptions*>(1)));
+ },
+ kMemoryCheckFailedRegex);
+ // Note: The current implementation checks the size only after checking the
+ // alignment versus that required for the |uint32_t| size, so it won't die in
+ // the expected way if you pass, e.g., 4. So we have to manufacture a valid
+ // pointer at an offset of alignment 4.
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ uint32_t buffer[100] = {};
+ TestOptions* options = (reinterpret_cast<uintptr_t>(buffer) % 8 == 0)
+ ? reinterpret_cast<TestOptions*>(&buffer[1])
+ : reinterpret_cast<TestOptions*>(&buffer[0]);
+ options->struct_size = static_cast<uint32_t>(sizeof(TestOptions));
+ UserOptionsReader<TestOptions> reader(MakeUserPointer(options));
+ },
+ kMemoryCheckFailedRegex);
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/platform_handle_dispatcher.cc b/mojo/edk/system/platform_handle_dispatcher.cc
new file mode 100644
index 0000000..a6072cf
--- /dev/null
+++ b/mojo/edk/system/platform_handle_dispatcher.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/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;
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/platform_handle_dispatcher.h b/mojo/edk/system/platform_handle_dispatcher.h
new file mode 100644
index 0000000..eb5eb5d
--- /dev/null
+++ b/mojo/edk/system/platform_handle_dispatcher.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_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/simple_dispatcher.h"
+#include "mojo/edk/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;
+
+ embedder::ScopedPlatformHandle platform_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformHandleDispatcher);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_
diff --git a/mojo/edk/system/platform_handle_dispatcher_unittest.cc b/mojo/edk/system/platform_handle_dispatcher_unittest.cc
new file mode 100644
index 0000000..1d784a6
--- /dev/null
+++ b/mojo/edk/system/platform_handle_dispatcher_unittest.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/edk/system/platform_handle_dispatcher.h"
+
+#include <stdio.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace system {
+namespace {
+
+TEST(PlatformHandleDispatcherTest, Basic) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kHelloWorld[] = "hello world";
+
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &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) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kFooBar[] = "foo bar";
+
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &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.get());
+
+ transport.End();
+ EXPECT_TRUE(dispatcher->HasOneRef());
+ dispatcher = nullptr;
+
+ 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/mojo/edk/system/proxy_message_pipe_endpoint.cc b/mojo/edk/system/proxy_message_pipe_endpoint.cc
new file mode 100644
index 0000000..85437bb
--- /dev/null
+++ b/mojo/edk/system/proxy_message_pipe_endpoint.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/edk/system/proxy_message_pipe_endpoint.h"
+
+#include <string.h>
+
+#include "base/logging.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/local_message_pipe_endpoint.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+
+namespace mojo {
+namespace system {
+
+ProxyMessagePipeEndpoint::ProxyMessagePipeEndpoint(
+ ChannelEndpoint* channel_endpoint)
+ : channel_endpoint_(channel_endpoint) {
+}
+
+ProxyMessagePipeEndpoint::~ProxyMessagePipeEndpoint() {
+ DCHECK(!channel_endpoint_.get());
+}
+
+MessagePipeEndpoint::Type ProxyMessagePipeEndpoint::GetType() const {
+ return kTypeProxy;
+}
+
+bool ProxyMessagePipeEndpoint::OnPeerClose() {
+ DetachIfNecessary();
+ 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) {
+ DCHECK(channel_endpoint_.get());
+ LOG_IF(WARNING, !channel_endpoint_->EnqueueMessage(message.Pass()))
+ << "Failed to write enqueue message to channel";
+}
+
+void ProxyMessagePipeEndpoint::Close() {
+ DetachIfNecessary();
+}
+
+void ProxyMessagePipeEndpoint::DetachIfNecessary() {
+ if (channel_endpoint_.get()) {
+ channel_endpoint_->DetachFromMessagePipe();
+ channel_endpoint_ = nullptr;
+ }
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/proxy_message_pipe_endpoint.h b/mojo/edk/system/proxy_message_pipe_endpoint.h
new file mode 100644
index 0000000..c884240
--- /dev/null
+++ b/mojo/edk/system/proxy_message_pipe_endpoint.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_EDK_SYSTEM_PROXY_MESSAGE_PIPE_ENDPOINT_H_
+#define MOJO_EDK_SYSTEM_PROXY_MESSAGE_PIPE_ENDPOINT_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_pipe_endpoint.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace system {
+
+class ChannelEndpoint;
+class LocalMessagePipeEndpoint;
+class MessagePipe;
+
+// A |ProxyMessagePipeEndpoint| is an endpoint which delegates everything to a
+// |ChannelEndpoint|, which may be co-owned by a |Channel|. Like any
+// |MessagePipeEndpoint|, a |ProxyMessagePipeEndpoint| is owned by a
+// |MessagePipe|.
+//
+// For example, 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|.
+class MOJO_SYSTEM_IMPL_EXPORT ProxyMessagePipeEndpoint
+ : public MessagePipeEndpoint {
+ public:
+ explicit ProxyMessagePipeEndpoint(ChannelEndpoint* channel_endpoint);
+ virtual ~ProxyMessagePipeEndpoint();
+
+ // |MessagePipeEndpoint| implementation:
+ virtual Type GetType() const override;
+ virtual bool OnPeerClose() override;
+ virtual void EnqueueMessage(scoped_ptr<MessageInTransit> message) override;
+ virtual void Close() override;
+
+ private:
+ void DetachIfNecessary();
+
+ scoped_refptr<ChannelEndpoint> channel_endpoint_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyMessagePipeEndpoint);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_PROXY_MESSAGE_PIPE_ENDPOINT_H_
diff --git a/mojo/edk/system/raw_channel.cc b/mojo/edk/system/raw_channel.cc
new file mode 100644
index 0000000..0bfe104
--- /dev/null
+++ b/mojo/edk/system/raw_channel.cc
@@ -0,0 +1,522 @@
+// Copyright 2014 The Chromium 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/edk/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/edk/system/message_in_transit.h"
+#include "mojo/edk/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_(nullptr),
+ delegate_(nullptr),
+ 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_ = nullptr;
+ message_loop_for_io_ = nullptr;
+ read_buffer_.reset();
+ write_buffer_.reset();
+ return false;
+ }
+
+ IOResult io_result = ScheduleRead();
+ if (io_result != 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(),
+ io_result,
+ 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_ = nullptr;
+ 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, platform_handles_written, bytes_written);
+ if (!result) {
+ // Even if we're on the I/O thread, don't call |OnError()| in the nested
+ // context.
+ message_loop_for_io_->PostTask(FROM_HERE,
+ base::Bind(&RawChannel::CallOnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ Delegate::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(IOResult io_result, size_t bytes_read) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+
+ if (read_stopped_) {
+ NOTREACHED();
+ return;
+ }
+
+ // 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 {
+ switch (io_result) {
+ case IO_SUCCEEDED:
+ break;
+ case IO_FAILED_SHUTDOWN:
+ case IO_FAILED_BROKEN:
+ case IO_FAILED_UNKNOWN:
+ read_stopped_ = true;
+ CallOnError(ReadIOResultToError(io_result));
+ return;
+ case IO_PENDING:
+ NOTREACHED();
+ 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 = nullptr;
+ if (!message_view.IsValid(GetSerializedPlatformHandleSize(),
+ &error_message)) {
+ DCHECK(error_message);
+ LOG(ERROR) << "Received invalid message: " << error_message;
+ read_stopped_ = true;
+ CallOnError(Delegate::ERROR_READ_BAD_MESSAGE);
+ return;
+ }
+
+ if (message_view.type() == MessageInTransit::kTypeRawChannel) {
+ if (!OnReadMessageForRawChannel(message_view)) {
+ read_stopped_ = true;
+ CallOnError(Delegate::ERROR_READ_BAD_MESSAGE);
+ 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(ERROR) << "Invalid number of platform handles received";
+ read_stopped_ = true;
+ CallOnError(Delegate::ERROR_READ_BAD_MESSAGE);
+ 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(IOResult io_result,
+ size_t platform_handles_written,
+ size_t bytes_written) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+ DCHECK_NE(io_result, IO_PENDING);
+
+ 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(
+ io_result, platform_handles_written, bytes_written);
+ }
+
+ if (did_fail)
+ CallOnError(Delegate::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;
+}
+
+// static
+RawChannel::Delegate::Error RawChannel::ReadIOResultToError(
+ IOResult io_result) {
+ switch (io_result) {
+ case IO_FAILED_SHUTDOWN:
+ return Delegate::ERROR_READ_SHUTDOWN;
+ case IO_FAILED_BROKEN:
+ return Delegate::ERROR_READ_BROKEN;
+ case IO_FAILED_UNKNOWN:
+ return Delegate::ERROR_READ_UNKNOWN;
+ case IO_SUCCEEDED:
+ case IO_PENDING:
+ NOTREACHED();
+ break;
+ }
+ return Delegate::ERROR_READ_UNKNOWN;
+}
+
+void RawChannel::CallOnError(Delegate::Error error) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+ // TODO(vtl): Add a "write_lock_.AssertNotAcquired()"?
+ if (delegate_)
+ delegate_->OnError(error);
+}
+
+bool RawChannel::OnWriteCompletedNoLock(IOResult io_result,
+ size_t platform_handles_written,
+ size_t bytes_written) {
+ write_lock_.AssertAcquired();
+
+ DCHECK(!write_stopped_);
+ DCHECK(!write_buffer_->message_queue_.empty());
+
+ if (io_result == IO_SUCCEEDED) {
+ 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.
+ CHECK_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.
+ io_result = ScheduleWriteNoLock();
+ if (io_result == IO_PENDING)
+ return true;
+ DCHECK_NE(io_result, IO_SUCCEEDED);
+ }
+
+ 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/mojo/edk/system/raw_channel.h b/mojo/edk/system/raw_channel.h
new file mode 100644
index 0000000..1a077ef
--- /dev/null
+++ b/mojo/edk/system/raw_channel.h
@@ -0,0 +1,331 @@
+// 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_EDK_SYSTEM_RAW_CHANNEL_H_
+#define MOJO_EDK_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/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/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 Error {
+ // Failed read due to raw channel shutdown (e.g., on the other side).
+ ERROR_READ_SHUTDOWN,
+ // Failed read due to raw channel being broken (e.g., if the other side
+ // died without shutting down).
+ ERROR_READ_BROKEN,
+ // Received a bad message.
+ ERROR_READ_BAD_MESSAGE,
+ // Unknown read error.
+ ERROR_READ_UNKNOWN,
+ // Generic write error.
+ 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. This may call the raw channel's
+ // |Shutdown()|, but must not destroy it.
+ //
+ // For each raw channel, there'll be at most one |ERROR_READ_...| and at
+ // most one |ERROR_WRITE| notification. After |OnError(ERROR_READ_...)|,
+ // |OnReadMessage()| won't be called again.
+ virtual void OnError(Error 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:
+ // Result of I/O operations.
+ enum IOResult {
+ IO_SUCCEEDED,
+ // Failed due to a (probably) clean shutdown (e.g., of the other end).
+ IO_FAILED_SHUTDOWN,
+ // Failed due to the connection being broken (e.g., the other end dying).
+ IO_FAILED_BROKEN,
+ // Failed due to some other (unexpected) reason.
+ IO_FAILED_UNKNOWN,
+ 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();
+
+ // |result| must not be |IO_PENDING|. Must be called on the I/O thread WITHOUT
+ // |write_lock_| held.
+ void OnReadCompleted(IOResult io_result, size_t bytes_read);
+ // |result| must not be |IO_PENDING|. Must be called on the I/O thread WITHOUT
+ // |write_lock_| held.
+ void OnWriteCompleted(IOResult io_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:
+ // Converts an |IO_FAILED_...| for a read to a |Delegate::Error|.
+ static Delegate::Error ReadIOResultToError(IOResult io_result);
+
+ // Calls |delegate_->OnError(error)|. Must be called on the I/O thread WITHOUT
+ // |write_lock_| held.
+ void CallOnError(Delegate::Error error);
+
+ // If |io_result| is |IO_SUCCESS|, updates the write buffer and schedules a
+ // write operation to run later if there is more to write. If |io_result| is
+ // failure or any other error occurs, cancels pending writes and returns
+ // false. Must be called under |write_lock_| and only if |write_stopped_| is
+ // false.
+ bool OnWriteCompletedNoLock(IOResult io_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_EDK_SYSTEM_RAW_CHANNEL_H_
diff --git a/mojo/edk/system/raw_channel_posix.cc b/mojo/edk/system/raw_channel_posix.cc
new file mode 100644
index 0000000..05a01aa
--- /dev/null
+++ b/mojo/edk/system/raw_channel_posix.cc
@@ -0,0 +1,486 @@
+// 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/edk/system/raw_channel.h"
+
+#include <errno.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <deque>
+
+#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/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/platform_channel_utils_posix.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/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;
+
+ // Implements most of |Read()| (except for a bit of clean-up):
+ IOResult ReadImpl(size_t* bytes_read);
+
+ // 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,
+ nullptr));
+ 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_);
+
+ IOResult rv = ReadImpl(bytes_read);
+ if (rv != IO_SUCCEEDED && rv != IO_PENDING) {
+ // Make sure that |OnFileCanReadWithoutBlocking()| won't be called again.
+ read_watcher_.reset();
+ }
+ return rv;
+}
+
+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 == EPIPE)
+ return IO_FAILED_SHUTDOWN;
+
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PLOG(WARNING) << "sendmsg/write/writev";
+ return IO_FAILED_UNKNOWN;
+ }
+
+ 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_UNKNOWN;
+}
+
+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 io_result = Read(&bytes_read);
+ if (io_result != IO_PENDING)
+ OnReadCompleted(io_result, 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 io_result;
+ size_t platform_handles_written = 0;
+ size_t bytes_written = 0;
+ {
+ base::AutoLock locker(write_lock());
+
+ DCHECK(pending_write_);
+
+ pending_write_ = false;
+ io_result = WriteNoLock(&platform_handles_written, &bytes_written);
+ }
+
+ if (io_result != IO_PENDING)
+ OnWriteCompleted(io_result, platform_handles_written, bytes_written);
+}
+
+RawChannel::IOResult RawChannelPosix::ReadImpl(size_t* bytes_read) {
+ char* buffer = nullptr;
+ 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(ERROR) << "Received too many platform handles";
+ embedder::CloseAllPlatformHandles(&read_platform_handles_);
+ read_platform_handles_.clear();
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+
+ 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)
+ return IO_FAILED_SHUTDOWN;
+
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return ScheduleRead();
+
+ if (errno == ECONNRESET)
+ return IO_FAILED_BROKEN;
+
+ PLOG(WARNING) << "recvmsg";
+ return IO_FAILED_UNKNOWN;
+}
+
+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(IO_FAILED_UNKNOWN, 0, 0);
+ }
+}
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+// Static factory method declared in raw_channel.h.
+// static
+scoped_ptr<RawChannel> RawChannel::Create(
+ embedder::ScopedPlatformHandle handle) {
+ return make_scoped_ptr(new RawChannelPosix(handle.Pass()));
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/raw_channel_unittest.cc b/mojo/edk/system/raw_channel_unittest.cc
new file mode 100644
index 0000000..7531cf3
--- /dev/null
+++ b/mojo/edk/system/raw_channel_unittest.cc
@@ -0,0 +1,688 @@
+// Copyright 2014 The Chromium 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/edk/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/test/test_io_thread.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/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/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() ? nullptr : &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_(base::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:
+ base::TestIOThread* io_thread() { return &io_thread_; }
+
+ embedder::ScopedPlatformHandle handles[2];
+
+ private:
+ base::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 OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ 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() ? nullptr : &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 OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ // 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 OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ // 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.OnError ------------------------------------------------------
+
+class ErrorRecordingRawChannelDelegate
+ : public ReadCountdownRawChannelDelegate {
+ public:
+ ErrorRecordingRawChannelDelegate(size_t expected_read_count,
+ bool expect_read_error,
+ bool expect_write_error)
+ : ReadCountdownRawChannelDelegate(expected_read_count),
+ got_read_error_event_(false, false),
+ got_write_error_event_(false, false),
+ expecting_read_error_(expect_read_error),
+ expecting_write_error_(expect_write_error) {}
+
+ virtual ~ErrorRecordingRawChannelDelegate() {}
+
+ virtual void OnError(Error error) override {
+ switch (error) {
+ case ERROR_READ_SHUTDOWN:
+ ASSERT_TRUE(expecting_read_error_);
+ expecting_read_error_ = false;
+ got_read_error_event_.Signal();
+ break;
+ case ERROR_READ_BROKEN:
+ // TODO(vtl): Test broken connections.
+ CHECK(false);
+ break;
+ case ERROR_READ_BAD_MESSAGE:
+ // TODO(vtl): Test reception/detection of bad messages.
+ CHECK(false);
+ break;
+ case ERROR_READ_UNKNOWN:
+ // TODO(vtl): Test however it is we might get here.
+ CHECK(false);
+ break;
+ case ERROR_WRITE:
+ ASSERT_TRUE(expecting_write_error_);
+ expecting_write_error_ = false;
+ got_write_error_event_.Signal();
+ break;
+ }
+ }
+
+ void WaitForReadError() { got_read_error_event_.Wait(); }
+ void WaitForWriteError() { got_write_error_event_.Wait(); }
+
+ private:
+ base::WaitableEvent got_read_error_event_;
+ base::WaitableEvent got_write_error_event_;
+
+ bool expecting_read_error_;
+ bool expecting_write_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(ErrorRecordingRawChannelDelegate);
+};
+
+// Tests (fatal) errors.
+TEST_F(RawChannelTest, OnError) {
+ ErrorRecordingRawChannelDelegate 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 error.
+ delegate.WaitForWriteError();
+
+ // We should also get a read error.
+ delegate.WaitForReadError();
+
+ EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(2)));
+
+ // Sleep a bit, to make sure we don't get another |OnError()|
+ // notification. (If we actually get another one, |OnError()| crashes.)
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+
+ io_thread()->PostTaskAndWait(
+ FROM_HERE, base::Bind(&RawChannel::Shutdown, base::Unretained(rc.get())));
+}
+
+// RawChannelTest.ReadUnaffectedByWriteError -----------------------------------
+
+TEST_F(RawChannelTest, ReadUnaffectedByWriteError) {
+ 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.
+ ErrorRecordingRawChannelDelegate 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 error.
+ delegate.WaitForWriteError();
+
+ // Wait for reading to finish. A writing failure shouldn't affect reading.
+ delegate.Wait();
+
+ // And then we should get a read error.
+ delegate.WaitForReadError();
+
+ 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 OnError(Error /*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.ShutdownOnError{Read, Write} ---------------------------------
+
+class ShutdownOnErrorRawChannelDelegate : public RawChannel::Delegate {
+ public:
+ ShutdownOnErrorRawChannelDelegate(RawChannel* raw_channel,
+ Error shutdown_on_error_type)
+ : raw_channel_(raw_channel),
+ shutdown_on_error_type_(shutdown_on_error_type),
+ done_event_(false, false),
+ did_shutdown_(false) {}
+ virtual ~ShutdownOnErrorRawChannelDelegate() {}
+
+ // |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 OnError(Error error) override {
+ EXPECT_FALSE(did_shutdown_);
+ if (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 Error shutdown_on_error_type_;
+ base::WaitableEvent done_event_;
+ bool did_shutdown_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShutdownOnErrorRawChannelDelegate);
+};
+
+TEST_F(RawChannelTest, ShutdownOnErrorRead) {
+ scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass()));
+ ShutdownOnErrorRawChannelDelegate delegate(
+ rc.get(), RawChannel::Delegate::ERROR_READ_SHUTDOWN);
+ 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, ShutdownOnErrorWrite) {
+ scoped_ptr<RawChannel> rc(RawChannel::Create(handles[0].Pass()));
+ ShutdownOnErrorRawChannelDelegate delegate(rc.get(),
+ RawChannel::Delegate::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/mojo/edk/system/raw_channel_win.cc b/mojo/edk/system/raw_channel_win.cc
new file mode 100644
index 0000000..208d595
--- /dev/null
+++ b/mojo/edk/system/raw_channel_win.cc
@@ -0,0 +1,585 @@
+// 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/edk/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/edk/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_(nullptr),
+ cancel_io_ex_(nullptr) {
+ 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_ = nullptr;
+ 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_GT(bytes_read, 0u);
+ owner_->OnReadCompleted(IO_SUCCEEDED, bytes_read);
+ } else if (error == ERROR_BROKEN_PIPE) {
+ DCHECK_EQ(bytes_read, 0u);
+ owner_->OnReadCompleted(IO_FAILED_SHUTDOWN, 0);
+ } else {
+ DCHECK_EQ(bytes_read, 0u);
+ LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
+ owner_->OnReadCompleted(IO_FAILED_UNKNOWN, 0);
+ }
+}
+
+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) {
+ owner_->OnWriteCompleted(IO_SUCCEEDED, 0, bytes_written);
+ } else if (error == ERROR_BROKEN_PIPE) {
+ owner_->OnWriteCompleted(IO_FAILED_SHUTDOWN, 0, 0);
+ } else {
+ LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
+ owner_->OnWriteCompleted(IO_FAILED_UNKNOWN, 0, 0);
+ }
+}
+
+RawChannelWin::RawChannelWin(embedder::ScopedPlatformHandle handle)
+ : handle_(handle.Pass()),
+ io_handler_(nullptr),
+ 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 = nullptr;
+ size_t bytes_to_read = 0;
+ read_buffer()->GetBuffer(&buffer, &bytes_to_read);
+
+ BOOL result = ReadFile(io_handler_->handle(),
+ buffer,
+ static_cast<DWORD>(bytes_to_read),
+ nullptr,
+ &io_handler_->read_context()->overlapped);
+ if (!result) {
+ DWORD error = GetLastError();
+ if (error == ERROR_BROKEN_PIPE)
+ return IO_FAILED_SHUTDOWN;
+ if (error != ERROR_IO_PENDING) {
+ LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+
+ if (result && skip_completion_port_on_success_) {
+ DWORD bytes_read_dword = 0;
+ BOOL get_size_result =
+ GetOverlappedResult(io_handler_->handle(),
+ &io_handler_->read_context()->overlapped,
+ &bytes_read_dword,
+ FALSE);
+ DPCHECK(get_size_result);
+ *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) {
+ DWORD error = GetLastError();
+ if (error == ERROR_BROKEN_PIPE)
+ return IO_FAILED_SHUTDOWN;
+ if (error != ERROR_IO_PENDING) {
+ LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+
+ 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(),
+ nullptr);
+ } else {
+ CancelIo(io_handler_->handle());
+ }
+ }
+
+ io_handler_->DetachFromOwnerNoLock(read_buffer.Pass(), write_buffer.Pass());
+ io_handler_ = nullptr;
+}
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+// Static factory method declared in raw_channel.h.
+// static
+scoped_ptr<RawChannel> RawChannel::Create(
+ embedder::ScopedPlatformHandle handle) {
+ return make_scoped_ptr(new RawChannelWin(handle.Pass()));
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/remote_message_pipe_unittest.cc b/mojo/edk/system/remote_message_pipe_unittest.cc
new file mode 100644
index 0000000..942d496
--- /dev/null
+++ b/mojo/edk/system/remote_message_pipe_unittest.cc
@@ -0,0 +1,1191 @@
+// Copyright 2014 The Chromium 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/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/test_io_thread.h"
+#include "base/threading/platform_thread.h" // For |Sleep()|.
+#include "build/build_config.h" // TODO(vtl): Remove this.
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/channel_endpoint.h"
+#include "mojo/edk/system/message_pipe.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace system {
+namespace {
+
+class RemoteMessagePipeTest : public testing::Test {
+ public:
+ RemoteMessagePipeTest() : io_thread_(base::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 the two given |ChannelEndpoint|s.
+ void ConnectChannelEndpoints(scoped_refptr<ChannelEndpoint> ep0,
+ scoped_refptr<ChannelEndpoint> ep1) {
+ io_thread_.PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&RemoteMessagePipeTest::ConnectChannelEndpointsOnIOThread,
+ base::Unretained(this),
+ ep0,
+ ep1));
+ }
+
+ // This bootstraps |ep| on |channels_[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 BootstrapChannelEndpointNoWait(unsigned channel_index,
+ scoped_refptr<ChannelEndpoint> ep) {
+ io_thread_.PostTask(
+ FROM_HERE,
+ base::Bind(&RemoteMessagePipeTest::BootstrapChannelEndpointOnIOThread,
+ base::Unretained(this),
+ channel_index,
+ ep));
+ }
+
+ void RestoreInitialState() {
+ io_thread_.PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&RemoteMessagePipeTest::RestoreInitialStateOnIOThread,
+ base::Unretained(this)));
+ }
+
+ embedder::PlatformSupport* platform_support() { return &platform_support_; }
+ base::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].get()) {
+ channels_[0]->Shutdown();
+ channels_[0] = nullptr;
+ }
+ if (channels_[1].get()) {
+ channels_[1]->Shutdown();
+ channels_[1] = nullptr;
+ }
+ }
+
+ 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].get());
+
+ channels_[channel_index] = new Channel(&platform_support_);
+ CHECK(channels_[channel_index]->Init(
+ RawChannel::Create(platform_handles_[channel_index].Pass())));
+ }
+
+ void ConnectChannelEndpointsOnIOThread(scoped_refptr<ChannelEndpoint> ep0,
+ scoped_refptr<ChannelEndpoint> ep1) {
+ CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop());
+
+ if (!channels_[0].get())
+ CreateAndInitChannel(0);
+ if (!channels_[1].get())
+ CreateAndInitChannel(1);
+
+ MessageInTransit::EndpointId local_id0 = channels_[0]->AttachEndpoint(ep0);
+ MessageInTransit::EndpointId local_id1 = channels_[1]->AttachEndpoint(ep1);
+
+ channels_[0]->RunEndpoint(ep0, local_id1);
+ channels_[1]->RunEndpoint(ep1, local_id0);
+ }
+
+ void BootstrapChannelEndpointOnIOThread(unsigned channel_index,
+ scoped_refptr<ChannelEndpoint> ep) {
+ CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop());
+ CHECK(channel_index == 0 || channel_index == 1);
+
+ CreateAndInitChannel(channel_index);
+ MessageInTransit::EndpointId endpoint_id =
+ channels_[channel_index]->AttachEndpoint(ep);
+ if (endpoint_id == MessageInTransit::kInvalidEndpointId)
+ return;
+
+ CHECK_EQ(endpoint_id, Channel::kBootstrapEndpointId);
+ channels_[channel_index]->RunEndpoint(ep, Channel::kBootstrapEndpointId);
+ }
+
+ void RestoreInitialStateOnIOThread() {
+ CHECK_EQ(base::MessageLoop::current(), io_thread()->message_loop());
+
+ TearDownOnIOThread();
+ SetUpOnIOThread();
+ }
+
+ embedder::SimplePlatformSupport platform_support_;
+ base::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;
+ HandleSignalsState hss;
+ 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<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ ConnectChannelEndpoints(ep0, ep1);
+
+ // 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ // Write to MP 0, port 0.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp0->WriteMessage(0,
+ UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Read from MP 1, port 1.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp1->ReadMessage(1,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp0->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 456, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp1->WriteMessage(1,
+ UserPointer<const void>(kWorld),
+ sizeof(kWorld),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(456u, context);
+ hss = HandleSignalsState();
+ mp0->RemoveWaiter(0, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp0->ReadMessage(0,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ 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();
+ hss = HandleSignalsState();
+ MojoResult result =
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789, &hss);
+ if (result == MOJO_RESULT_OK) {
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(789u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ }
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // 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;
+ HandleSignalsState hss;
+ uint32_t context = 0;
+
+ // Connect message pipes as in the |Basic| test.
+
+ scoped_refptr<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ ConnectChannelEndpoints(ep0, ep1);
+
+ // Now put another message pipe on the channel.
+
+ scoped_refptr<ChannelEndpoint> ep2;
+ scoped_refptr<MessagePipe> mp2(MessagePipe::CreateLocalProxy(&ep2));
+ scoped_refptr<ChannelEndpoint> ep3;
+ scoped_refptr<MessagePipe> mp3(MessagePipe::CreateProxyLocal(&ep3));
+ ConnectChannelEndpoints(ep2, ep3);
+
+ // Write: MP 2, port 0 -> MP 3, port 1.
+
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp3->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp2->WriteMessage(0,
+ UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(789u, context);
+ hss = HandleSignalsState();
+ mp3->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ mp1->ReadMessage(1,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ mp2->ReadMessage(0,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp0->WriteMessage(0,
+ UserPointer<const void>(kWorld),
+ sizeof(kWorld),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ mp2->ReadMessage(0,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ mp3->ReadMessage(1,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp1->ReadMessage(1,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ 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;
+ HandleSignalsState hss;
+ 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<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+
+ // Write to MP 0, port 0.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp0->WriteMessage(0,
+ UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ BootstrapChannelEndpointNoWait(0, ep0);
+
+ // Close MP 0, port 0 before channel 1 is even connected.
+ mp0->Close(0);
+
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+
+ // 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ BootstrapChannelEndpointNoWait(1, ep1);
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ // Note: MP 1, port 1 should definitely should be readable, but it may or may
+ // not appear as writable (there's a race, and it may not have noticed that
+ // the other side was closed yet -- e.g., inserting a sleep here would make it
+ // much more likely to notice that it's no longer writable).
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ // Read from MP 1, port 1.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp1->ReadMessage(1,
+ UserPointer<void>(buffer),
+ MakeUserPointer(&buffer_size),
+ nullptr,
+ nullptr,
+ 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;
+ HandleSignalsState hss;
+ uint32_t context = 0;
+
+ scoped_refptr<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ ConnectChannelEndpoints(ep0, ep1);
+
+ // We'll try to pass this dispatcher.
+ scoped_refptr<MessagePipeDispatcher> dispatcher(
+ new MessagePipeDispatcher(MessagePipeDispatcher::kDefaultCreateOptions));
+ scoped_refptr<MessagePipe> local_mp(MessagePipe::CreateLocalLocal());
+ 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ // 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,
+ UserPointer<const void>(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 = nullptr;
+ }
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&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].get());
+ EXPECT_TRUE(read_dispatchers[0]->HasOneRef());
+
+ EXPECT_EQ(Dispatcher::kTypeMessagePipe, read_dispatchers[0]->GetType());
+ dispatcher = static_cast<MessagePipeDispatcher*>(read_dispatchers[0].get());
+
+ // Add the waiter now, before it becomes readable to avoid a race.
+ waiter.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ dispatcher->AddWaiter(
+ &waiter, MOJO_HANDLE_SIGNAL_READABLE, 456, nullptr));
+
+ // Write to "local_mp", port 1.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ local_mp->WriteMessage(1,
+ UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ 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.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(456u, context);
+ hss = HandleSignalsState();
+ dispatcher->RemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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(UserPointer<void>(read_buffer),
+ MakeUserPointer(&read_buffer_size),
+ 0,
+ nullptr,
+ 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ local_mp->AddWaiter(
+ 1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789, nullptr));
+
+ // Write to the dispatcher.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dispatcher->WriteMessage(UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(789u, context);
+ hss = HandleSignalsState();
+ local_mp->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&read_buffer_size),
+ nullptr,
+ nullptr,
+ 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;
+ HandleSignalsState hss;
+ uint32_t context = 0;
+
+ scoped_refptr<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ ConnectChannelEndpoints(ep0, ep1);
+
+ // We'll try to pass this dispatcher.
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100,
+ &dispatcher));
+ ASSERT_TRUE(dispatcher.get());
+
+ // Make a mapping.
+ scoped_ptr<embedder::PlatformSharedBufferMapping> mapping0;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping0));
+ ASSERT_TRUE(mapping0);
+ ASSERT_TRUE(mapping0->GetBase());
+ ASSERT_EQ(100u, mapping0->GetLength());
+ static_cast<char*>(mapping0->GetBase())[0] = 'A';
+ static_cast<char*>(mapping0->GetBase())[50] = 'B';
+ static_cast<char*>(mapping0->GetBase())[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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ // 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,
+ UserPointer<const void>(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 = nullptr;
+ }
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&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].get());
+ 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<embedder::PlatformSharedBufferMapping> mapping1;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ ASSERT_EQ(100u, mapping1->GetLength());
+ EXPECT_NE(mapping1->GetBase(), mapping0->GetBase());
+ EXPECT_EQ('A', static_cast<char*>(mapping1->GetBase())[0]);
+ EXPECT_EQ('B', static_cast<char*>(mapping1->GetBase())[50]);
+ EXPECT_EQ('C', static_cast<char*>(mapping1->GetBase())[99]);
+
+ // Write stuff either way.
+ static_cast<char*>(mapping1->GetBase())[1] = 'x';
+ EXPECT_EQ('x', static_cast<char*>(mapping0->GetBase())[1]);
+ static_cast<char*>(mapping0->GetBase())[2] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[2]);
+
+ // Kill the first mapping; the second should still be valid.
+ mapping0.reset();
+ EXPECT_EQ('A', static_cast<char*>(mapping1->GetBase())[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->GetBase())[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) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kHello[] = "hello";
+ static const char kWorld[] = "world";
+ Waiter waiter;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ scoped_refptr<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ ConnectChannelEndpoints(ep0, ep1);
+
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ // 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,
+ UserPointer<const void>(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 = nullptr;
+ }
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&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].get());
+ 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<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ BootstrapChannelEndpointNoWait(0, ep0);
+
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ BootstrapChannelEndpointNoWait(1, ep1);
+
+ 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();
+ }
+}
+
+// Tests passing an end of a message pipe over a remote message pipe, and then
+// passing that end back.
+// TODO(vtl): Also test passing a message pipe across two remote message pipes.
+TEST_F(RemoteMessagePipeTest, PassMessagePipeHandleAcrossAndBack) {
+ static const char kHello[] = "hello";
+ static const char kWorld[] = "world";
+ Waiter waiter;
+ HandleSignalsState hss;
+ uint32_t context = 0;
+
+ scoped_refptr<ChannelEndpoint> ep0;
+ scoped_refptr<MessagePipe> mp0(MessagePipe::CreateLocalProxy(&ep0));
+ scoped_refptr<ChannelEndpoint> ep1;
+ scoped_refptr<MessagePipe> mp1(MessagePipe::CreateProxyLocal(&ep1));
+ ConnectChannelEndpoints(ep0, ep1);
+
+ // We'll try to pass this dispatcher.
+ scoped_refptr<MessagePipeDispatcher> dispatcher(
+ new MessagePipeDispatcher(MessagePipeDispatcher::kDefaultCreateOptions));
+ scoped_refptr<MessagePipe> local_mp(MessagePipe::CreateLocalLocal());
+ 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();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp1->AddWaiter(1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 123, nullptr));
+
+ // 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,
+ UserPointer<const void>(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 = nullptr;
+ }
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(123u, context);
+ hss = HandleSignalsState();
+ mp1->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&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].get());
+ EXPECT_TRUE(read_dispatchers[0]->HasOneRef());
+
+ EXPECT_EQ(Dispatcher::kTypeMessagePipe, read_dispatchers[0]->GetType());
+ dispatcher = static_cast<MessagePipeDispatcher*>(read_dispatchers[0].get());
+ read_dispatchers.clear();
+
+ // Now pass it back.
+
+ // Prepare to wait on MP 0, port 0. (Add the waiter now. Otherwise, if we do
+ // it later, it might already be readable.)
+ waiter.Init();
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ mp0->AddWaiter(0, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 456, nullptr));
+
+ // Write to MP 1, port 1.
+ {
+ DispatcherTransport transport(
+ test::DispatcherTryStartTransport(dispatcher.get()));
+ EXPECT_TRUE(transport.is_valid());
+
+ std::vector<DispatcherTransport> transports;
+ transports.push_back(transport);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp1->WriteMessage(1,
+ UserPointer<const void>(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 = nullptr;
+ }
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(456u, context);
+ hss = HandleSignalsState();
+ mp0->RemoveWaiter(0, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Read from MP 0, port 0.
+ read_buffer_size = static_cast<uint32_t>(sizeof(read_buffer));
+ read_num_dispatchers = 10; // Maximum to get.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mp0->ReadMessage(0,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&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].get());
+ EXPECT_TRUE(read_dispatchers[0]->HasOneRef());
+
+ EXPECT_EQ(Dispatcher::kTypeMessagePipe, read_dispatchers[0]->GetType());
+ dispatcher = static_cast<MessagePipeDispatcher*>(read_dispatchers[0].get());
+ read_dispatchers.clear();
+
+ // Add the waiter now, before it becomes readable to avoid a race.
+ waiter.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ dispatcher->AddWaiter(
+ &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789, nullptr));
+
+ // Write to "local_mp", port 1.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ local_mp->WriteMessage(1,
+ UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for the dispatcher to become readable.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(789u, context);
+ hss = HandleSignalsState();
+ dispatcher->RemoveWaiter(&waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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(UserPointer<void>(read_buffer),
+ MakeUserPointer(&read_buffer_size),
+ 0,
+ nullptr,
+ 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ local_mp->AddWaiter(
+ 1, &waiter, MOJO_HANDLE_SIGNAL_READABLE, 789, nullptr));
+
+ // Write to the dispatcher.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dispatcher->WriteMessage(UserPointer<const void>(kHello),
+ sizeof(kHello),
+ nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait.
+ EXPECT_EQ(MOJO_RESULT_OK, waiter.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_EQ(789u, context);
+ hss = HandleSignalsState();
+ local_mp->RemoveWaiter(1, &waiter, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ UserPointer<void>(read_buffer),
+ MakeUserPointer(&read_buffer_size),
+ nullptr,
+ nullptr,
+ 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 the cases where messages are written and read (at
+ // various points) on the message pipe being passed around.
+
+ // 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);
+}
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/run_all_unittests.cc b/mojo/edk/system/run_all_unittests.cc
new file mode 100644
index 0000000..2855e96
--- /dev/null
+++ b/mojo/edk/system/run_all_unittests.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ // Silence death test thread warnings on Linux. We can afford to run our death
+ // tests a little more slowly (< 10 ms per death test on a Z620).
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+
+ base::TestSuite test_suite(argc, argv);
+
+ return base::LaunchUnitTests(
+ argc,
+ argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/edk/system/shared_buffer_dispatcher.cc b/mojo/edk/system/shared_buffer_dispatcher.cc
new file mode 100644
index 0000000..7e345d9
--- /dev/null
+++ b/mojo/edk/system/shared_buffer_dispatcher.cc
@@ -0,0 +1,276 @@
+// Copyright 2014 The Chromium 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/edk/system/shared_buffer_dispatcher.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/options_validation.h"
+#include "mojo/public/c/system/macros.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(
+ UserPointer<const MojoCreateSharedBufferOptions> in_options,
+ MojoCreateSharedBufferOptions* out_options) {
+ const MojoCreateSharedBufferOptionsFlags kKnownFlags =
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
+
+ *out_options = kDefaultCreateOptions;
+ if (in_options.IsNull())
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateSharedBufferOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateSharedBufferOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ // (Nothing here yet.)
+
+ return MOJO_RESULT_OK;
+}
+
+// static
+MojoResult SharedBufferDispatcher::Create(
+ embedder::PlatformSupport* platform_support,
+ 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<embedder::PlatformSharedBuffer> shared_buffer(
+ platform_support->CreateSharedBuffer(static_cast<size_t>(num_bytes)));
+ if (!shared_buffer.get())
+ 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) {
+ DCHECK(channel);
+
+ 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<embedder::PlatformSharedBuffer> shared_buffer(
+ channel->platform_support()->CreateSharedBufferFromHandle(
+ num_bytes, embedder::ScopedPlatformHandle(platform_handle)));
+ if (!shared_buffer.get()) {
+ 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<embedder::PlatformSharedBuffer> shared_buffer)
+ : shared_buffer_(shared_buffer) {
+ DCHECK(shared_buffer_.get());
+}
+
+SharedBufferDispatcher::~SharedBufferDispatcher() {
+}
+
+// static
+MojoResult SharedBufferDispatcher::ValidateDuplicateOptions(
+ UserPointer<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.IsNull())
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoDuplicateBufferHandleOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(
+ MojoDuplicateBufferHandleOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ // (Nothing here yet.)
+
+ return MOJO_RESULT_OK;
+}
+
+void SharedBufferDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ DCHECK(shared_buffer_.get());
+ shared_buffer_ = nullptr;
+}
+
+scoped_refptr<Dispatcher>
+SharedBufferDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+ DCHECK(shared_buffer_.get());
+ scoped_refptr<embedder::PlatformSharedBuffer> shared_buffer;
+ shared_buffer.swap(shared_buffer_);
+ return scoped_refptr<Dispatcher>(new SharedBufferDispatcher(shared_buffer));
+}
+
+MojoResult SharedBufferDispatcher::DuplicateBufferHandleImplNoLock(
+ UserPointer<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<embedder::PlatformSharedBufferMapping>* mapping) {
+ lock().AssertAcquired();
+ DCHECK(shared_buffer_.get());
+
+ 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_.get());
+
+ 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_ = nullptr;
+ return false;
+ }
+
+ serialization->num_bytes = shared_buffer_->GetNumBytes();
+ serialization->platform_handle_index = platform_handles->size();
+ platform_handles->push_back(platform_handle.release());
+ *actual_size = sizeof(SerializedSharedBufferDispatcher);
+
+ shared_buffer_ = nullptr;
+
+ return true;
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/shared_buffer_dispatcher.h b/mojo/edk/system/shared_buffer_dispatcher.h
new file mode 100644
index 0000000..3e5ade0
--- /dev/null
+++ b/mojo/edk/system/shared_buffer_dispatcher.h
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium 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_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/memory.h"
+#include "mojo/edk/system/simple_dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+
+namespace embedder {
+class PlatformSupport;
+}
+
+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
+// (which would entail overriding |GetHandleSignalsStateImplNoLock()|, etc.).
+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(
+ UserPointer<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(
+ embedder::PlatformSupport* platform_support,
+ 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<embedder::PlatformSharedBuffer> 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(
+ UserPointer<const MojoDuplicateBufferHandleOptions> in_options,
+ MojoDuplicateBufferHandleOptions* out_options);
+
+ // |Dispatcher| protected methods:
+ virtual void CloseImplNoLock() override;
+ virtual scoped_refptr<Dispatcher>
+ CreateEquivalentDispatcherAndCloseImplNoLock() override;
+ virtual MojoResult DuplicateBufferHandleImplNoLock(
+ UserPointer<const MojoDuplicateBufferHandleOptions> options,
+ scoped_refptr<Dispatcher>* new_dispatcher) override;
+ virtual MojoResult MapBufferImplNoLock(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<embedder::PlatformSharedBufferMapping>* 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;
+
+ scoped_refptr<embedder::PlatformSharedBuffer> shared_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcher);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
diff --git a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
new file mode 100644
index 0000000..d46ffe3
--- /dev/null
+++ b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
@@ -0,0 +1,299 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+
+#include <limits>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/dispatcher.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(
+ MakeUserPointer(&validated_options), &revalidated_options));
+ EXPECT_EQ(validated_options.struct_size, revalidated_options.struct_size);
+ EXPECT_EQ(validated_options.flags, revalidated_options.flags);
+}
+
+class SharedBufferDispatcherTest : public testing::Test {
+ public:
+ SharedBufferDispatcherTest() {}
+ virtual ~SharedBufferDispatcherTest() {}
+
+ embedder::PlatformSupport* platform_support() { return &platform_support_; }
+
+ private:
+ embedder::SimplePlatformSupport platform_support_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcherTest);
+};
+
+// Tests valid inputs to |ValidateCreateOptions()|.
+TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsValid) {
+ // Default options.
+ {
+ MojoCreateSharedBufferOptions validated_options = {};
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::ValidateCreateOptions(
+ NullUserPointer(), &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(
+ MakeUserPointer(&options), &validated_options))
+ << capacity;
+ RevalidateCreateOptions(validated_options);
+ EXPECT_EQ(options.flags, validated_options.flags);
+ }
+ }
+}
+
+TEST_F(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(
+ MakeUserPointer(&options), &unused));
+ }
+
+ // Unknown |flags|.
+ {
+ MojoCreateSharedBufferOptions options = {
+ kSizeOfCreateOptions, // |struct_size|.
+ ~0u // |flags|.
+ };
+ MojoCreateSharedBufferOptions unused;
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ SharedBufferDispatcher::ValidateCreateOptions(
+ MakeUserPointer(&options), &unused));
+ }
+}
+
+TEST_F(SharedBufferDispatcherTest, CreateAndMapBuffer) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100,
+ &dispatcher));
+ ASSERT_TRUE(dispatcher.get());
+ EXPECT_EQ(Dispatcher::kTypeSharedBuffer, dispatcher->GetType());
+
+ // Make a couple of mappings.
+ scoped_ptr<embedder::PlatformSharedBufferMapping> mapping1;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dispatcher->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ EXPECT_EQ(100u, mapping1->GetLength());
+ // Write something.
+ static_cast<char*>(mapping1->GetBase())[50] = 'x';
+
+ scoped_ptr<embedder::PlatformSharedBufferMapping> mapping2;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dispatcher->MapBuffer(50, 50, MOJO_MAP_BUFFER_FLAG_NONE, &mapping2));
+ ASSERT_TRUE(mapping2);
+ ASSERT_TRUE(mapping2->GetBase());
+ EXPECT_EQ(50u, mapping2->GetLength());
+ EXPECT_EQ('x', static_cast<char*>(mapping2->GetBase())[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->GetBase())[1] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100,
+ &dispatcher1));
+
+ // Map and write something.
+ scoped_ptr<embedder::PlatformSharedBufferMapping> mapping;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dispatcher1->MapBuffer(0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ static_cast<char*>(mapping->GetBase())[0] = 'x';
+ mapping.reset();
+
+ // Duplicate |dispatcher1| and then close it.
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ dispatcher1->DuplicateBufferHandle(NullUserPointer(), &dispatcher2));
+ ASSERT_TRUE(dispatcher2.get());
+ 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->GetBase())[0]);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ 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(MakeUserPointer(&options[i]),
+ &dispatcher2));
+ ASSERT_TRUE(dispatcher2.get());
+ EXPECT_EQ(Dispatcher::kTypeSharedBuffer, dispatcher2->GetType());
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
+ }
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsInvalid) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ 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(MakeUserPointer(&options),
+ &dispatcher2));
+ EXPECT_FALSE(dispatcher2.get());
+ }
+
+ // Unknown |flags|.
+ {
+ MojoDuplicateBufferHandleOptions options = {
+ sizeof(MojoDuplicateBufferHandleOptions), ~0u};
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ dispatcher1->DuplicateBufferHandle(MakeUserPointer(&options),
+ &dispatcher2));
+ EXPECT_FALSE(dispatcher2.get());
+ }
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, CreateInvalidNumBytes) {
+ // Size too big.
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ std::numeric_limits<uint64_t>::max(),
+ &dispatcher));
+ EXPECT_FALSE(dispatcher.get());
+
+ // Zero size.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 0,
+ &dispatcher));
+ EXPECT_FALSE(dispatcher.get());
+}
+
+TEST_F(SharedBufferDispatcherTest, MapBufferInvalidArguments) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100,
+ &dispatcher));
+
+ scoped_ptr<embedder::PlatformSharedBufferMapping> 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/mojo/edk/system/simple_dispatcher.cc b/mojo/edk/system/simple_dispatcher.cc
new file mode 100644
index 0000000..3eb7a4f
--- /dev/null
+++ b/mojo/edk/system/simple_dispatcher.cc
@@ -0,0 +1,61 @@
+// 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/edk/system/simple_dispatcher.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace system {
+
+SimpleDispatcher::SimpleDispatcher() {
+}
+
+SimpleDispatcher::~SimpleDispatcher() {
+}
+
+void SimpleDispatcher::HandleSignalsStateChangedNoLock() {
+ lock().AssertAcquired();
+ waiter_list_.AwakeWaitersForStateChange(GetHandleSignalsStateImplNoLock());
+}
+
+void SimpleDispatcher::CancelAllWaitersNoLock() {
+ lock().AssertAcquired();
+ waiter_list_.CancelAllWaiters();
+}
+
+MojoResult SimpleDispatcher::AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+
+ HandleSignalsState state(GetHandleSignalsStateImplNoLock());
+ if (state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ waiter_list_.AddWaiter(waiter, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void SimpleDispatcher::RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ waiter_list_.RemoveWaiter(waiter);
+ if (signals_state)
+ *signals_state = GetHandleSignalsStateImplNoLock();
+}
+
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/simple_dispatcher.h b/mojo/edk/system/simple_dispatcher.h
new file mode 100644
index 0000000..400e302
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_SIMPLE_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_SIMPLE_DISPATCHER_H_
+
+#include <list>
+
+#include "base/macros.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/edk/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
+ // |GetHandleSignalsStateImplNoLock()| should be checked again). Must be
+ // called under lock.
+ void HandleSignalsStateChangedNoLock();
+
+ // |Dispatcher| protected methods:
+ virtual void CancelAllWaitersNoLock() override;
+ virtual MojoResult AddWaiterImplNoLock(
+ Waiter* waiter,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ virtual void RemoveWaiterImplNoLock(
+ Waiter* waiter,
+ HandleSignalsState* signals_state) override;
+
+ private:
+ // Protected by |lock()|:
+ WaiterList waiter_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDispatcher);
+};
+
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_SIMPLE_DISPATCHER_H_
diff --git a/mojo/edk/system/simple_dispatcher_unittest.cc b/mojo/edk/system/simple_dispatcher_unittest.cc
new file mode 100644
index 0000000..d2b7eaa
--- /dev/null
+++ b/mojo/edk/system/simple_dispatcher_unittest.cc
@@ -0,0 +1,669 @@
+// 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/edk/system/simple_dispatcher.h"
+
+#include "base/logging.h"
+#include "base/macros.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/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/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());
+ }
+
+ // |Dispatcher| override:
+ virtual HandleSignalsState GetHandleSignalsStateImplNoLock() const override {
+ lock().AssertAcquired();
+ return state_;
+ }
+
+ // Protected by |lock()|:
+ HandleSignalsState state_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockSimpleDispatcher);
+};
+
+#if defined(OS_WIN)
+// http://crbug.com/396404
+#define MAYBE_Basic DISABLED_Basic
+#else
+#define MAYBE_Basic Basic
+#endif
+TEST(SimpleDispatcherTest, MAYBE_Basic) {
+ test::Stopwatch stopwatch;
+
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ Waiter w;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Try adding a readable waiter when already readable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ // 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);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1, nullptr));
+ 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);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for zero time for writable when already writable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2, nullptr));
+ 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);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for non-zero, finite time for writable when already writable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3, nullptr));
+ 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);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for zero time for writable when not writable (will time out).
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, nullptr));
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonTimeout());
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for non-zero, finite time for writable when not writable (will time
+ // out).
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 5, nullptr));
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ w.Wait(2 * test::EpsilonTimeout().InMicroseconds(), nullptr));
+ base::TimeDelta elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonTimeout());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonTimeout());
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ 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;
+ HandleSignalsState hss;
+
+ // Try adding a writable waiter when it can never be writable.
+ w.Init();
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ d->SetSatisfiedSignals(0);
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+ // 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);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2, nullptr));
+ 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);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Wait for zero time for writable and then it becomes never writable.
+ w.Init();
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3, nullptr));
+ 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);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // 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);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, nullptr));
+ 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);
+ hss = HandleSignalsState();
+ d->RemoveWaiter(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+}
+
+TEST(SimpleDispatcherTest, BasicClosed) {
+ test::Stopwatch stopwatch;
+
+ scoped_refptr<MockSimpleDispatcher> d;
+ Waiter w;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Try adding a writable waiter when the dispatcher has been closed.
+ d = new MockSimpleDispatcher();
+ w.Init();
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ // 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2, nullptr));
+ 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3, nullptr));
+ 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();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddWaiter(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, nullptr));
+ 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.
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/396393
+#define MAYBE_BasicThreaded DISABLED_BasicThreaded
+#else
+#define MAYBE_BasicThreaded BasicThreaded
+#endif
+TEST(SimpleDispatcherTest, MAYBE_BasicThreaded) {
+ test::Stopwatch stopwatch;
+ bool did_wait;
+ MojoResult result;
+ uint32_t context;
+ HandleSignalsState hss;
+
+ // 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,
+ &hss);
+ 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);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ &hss);
+ stopwatch.Start();
+ thread.Start();
+ base::PlatformThread::Sleep(2 * test::EpsilonTimeout());
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ } // Joins the thread.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+ 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);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // 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,
+ &hss);
+ stopwatch.Start();
+ thread.Start();
+ base::PlatformThread::Sleep(2 * test::EpsilonTimeout());
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_NONE);
+ } // Joins the thread.
+ 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_FAILED_PRECONDITION, result);
+ EXPECT_EQ(3u, context);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // 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,
+ &hss);
+ 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);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // 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,
+ &hss);
+ 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);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/387124
+#define MAYBE_MultipleWaiters DISABLED_MultipleWaiters
+#else
+#define MAYBE_MultipleWaiters MultipleWaiters
+#endif
+TEST(SimpleDispatcherTest, MAYBE_MultipleWaiters) {
+ static const uint32_t kNumWaiters = 20;
+
+ bool did_wait[kNumWaiters];
+ MojoResult result[kNumWaiters];
+ uint32_t context[kNumWaiters];
+ HandleSignalsState hss[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],
+ &hss[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]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+
+ // 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],
+ &hss[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],
+ &hss[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]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+
+ // 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],
+ &hss[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],
+ &hss[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]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+
+ // 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],
+ &hss[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],
+ &hss[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]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+}
+
+// TODO(vtl): Stress test?
+
+} // namespace
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/system_impl_export.h b/mojo/edk/system/system_impl_export.h
new file mode 100644
index 0000000..5bbf005
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_
+#define MOJO_EDK_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_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_
diff --git a/mojo/edk/system/test_utils.cc b/mojo/edk/system/test_utils.cc
new file mode 100644
index 0000000..3217c00
--- /dev/null
+++ b/mojo/edk/system/test_utils.cc
@@ -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.
+
+#include "mojo/edk/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
+}
+
+} // namespace test
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/test_utils.h b/mojo/edk/system/test_utils.h
new file mode 100644
index 0000000..3ab1907
--- /dev/null
+++ b/mojo/edk/system/test_utils.h
@@ -0,0 +1,61 @@
+// 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_EDK_SYSTEM_TEST_UTILS_H_
+#define MOJO_EDK_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::Now(); }
+
+ base::TimeDelta Elapsed() { return base::TimeTicks::Now() - start_time_; }
+
+ private:
+ base::TimeTicks start_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(Stopwatch);
+};
+
+} // namespace test
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_TEST_UTILS_H_
diff --git a/mojo/edk/system/transport_data.cc b/mojo/edk/system/transport_data.cc
new file mode 100644
index 0000000..774b744
--- /dev/null
+++ b/mojo/edk/system/transport_data.cc
@@ -0,0 +1,346 @@
+// Copyright 2014 The Chromium 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/edk/system/transport_data.h"
+
+#include <string.h>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "mojo/edk/system/channel.h"
+#include "mojo/edk/system/constants.h"
+#include "mojo/edk/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;
+static_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 {
+ static_assert(sizeof(Header) % MessageInTransit::kMessageAlignment == 0,
+ "sizeof(MessageInTransit::Header) not a multiple of alignment");
+ static_assert(kMaxSerializedDispatcherSize %
+ MessageInTransit::kMessageAlignment ==
+ 0,
+ "kMaxSerializedDispatcherSize not a multiple of alignment");
+ static_assert(sizeof(HandleTableEntry) %
+ MessageInTransit::kMessageAlignment ==
+ 0,
+ "sizeof(MessageInTransit::HandleTableEntry) not a multiple of "
+ "alignment");
+};
+
+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) {
+ static_assert(Dispatcher::kTypeUnknown == 0,
+ "Value of Dispatcher::kTypeUnknown must be 0");
+ 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 nullptr;
+}
+
+// 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/mojo/edk/system/transport_data.h b/mojo/edk/system/transport_data.h
new file mode 100644
index 0000000..395bc73
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_TRANSPORT_DATA_H_
+#define MOJO_EDK_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/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/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_EDK_SYSTEM_TRANSPORT_DATA_H_
diff --git a/mojo/edk/system/waiter.cc b/mojo/edk/system/waiter.cc
new file mode 100644
index 0000000..6dcd713
--- /dev/null
+++ b/mojo/edk/system/waiter.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/edk/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::Now() +
+ base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline));
+ do {
+ base::TimeTicks now_time = base::TimeTicks::Now();
+ 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/mojo/edk/system/waiter.h b/mojo/edk/system/waiter.h
new file mode 100644
index 0000000..03ada16
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_WAITER_H_
+#define MOJO_EDK_SYSTEM_WAITER_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.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_EDK_SYSTEM_WAITER_H_
diff --git a/mojo/edk/system/waiter_list.cc b/mojo/edk/system/waiter_list.cc
new file mode 100644
index 0000000..42dfe15
--- /dev/null
+++ b/mojo/edk/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/edk/system/waiter_list.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/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/mojo/edk/system/waiter_list.h b/mojo/edk/system/waiter_list.h
new file mode 100644
index 0000000..6bbc799
--- /dev/null
+++ b/mojo/edk/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_EDK_SYSTEM_WAITER_LIST_H_
+#define MOJO_EDK_SYSTEM_WAITER_LIST_H_
+
+#include <stdint.h>
+
+#include <list>
+
+#include "base/macros.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.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_EDK_SYSTEM_WAITER_LIST_H_
diff --git a/mojo/edk/system/waiter_list_unittest.cc b/mojo/edk/system/waiter_list_unittest.cc
new file mode 100644
index 0000000..129df71
--- /dev/null
+++ b/mojo/edk/system/waiter_list_unittest.cc
@@ -0,0 +1,289 @@
+// 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/edk/system/waiter_list.h"
+
+#include "base/threading/platform_thread.h" // For |Sleep()|.
+#include "base/time/time.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/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();
+ // Double-remove okay:
+ waiter_list.RemoveWaiter(thread.waiter());
+ } // 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());
+ // Double-remove okay:
+ waiter_list.RemoveWaiter(thread.waiter());
+ 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());
+ // Double-remove okay:
+ waiter_list.RemoveWaiter(thread.waiter());
+ } // 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/mojo/edk/system/waiter_test_utils.cc b/mojo/edk/system/waiter_test_utils.cc
new file mode 100644
index 0000000..06a4033
--- /dev/null
+++ b/mojo/edk/system/waiter_test_utils.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/edk/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,
+ HandleSignalsState* signals_state_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),
+ signals_state_out_(signals_state_out) {
+ *did_wait_out_ = false;
+ // Initialize these with invalid results (so that we'll be sure to catch any
+ // case where they're not set).
+ *result_out_ = -8542346;
+ *context_out_ = 89023444;
+ *signals_state_out_ = HandleSignalsState(~0u, ~0u);
+}
+
+WaiterThread::~WaiterThread() {
+ Join();
+}
+
+void WaiterThread::Run() {
+ waiter_.Init();
+
+ *result_out_ = dispatcher_->AddWaiter(
+ &waiter_, handle_signals_, context_, signals_state_out_);
+ if (*result_out_ != MOJO_RESULT_OK)
+ return;
+
+ *did_wait_out_ = true;
+ *result_out_ = waiter_.Wait(deadline_, context_out_);
+ dispatcher_->RemoveWaiter(&waiter_, signals_state_out_);
+}
+
+} // namespace test
+} // namespace system
+} // namespace mojo
diff --git a/mojo/edk/system/waiter_test_utils.h b/mojo/edk/system/waiter_test_utils.h
new file mode 100644
index 0000000..25e5344
--- /dev/null
+++ b/mojo/edk/system/waiter_test_utils.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_EDK_SYSTEM_WAITER_TEST_UTILS_H_
+#define MOJO_EDK_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/edk/system/dispatcher.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/public/c/system/types.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|, |*context_out| and
+ // |*signals_state_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,
+ HandleSignalsState* signals_state_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_;
+ HandleSignalsState* const signals_state_out_;
+
+ Waiter waiter_;
+
+ DISALLOW_COPY_AND_ASSIGN(WaiterThread);
+};
+
+} // namespace test
+} // namespace system
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
diff --git a/mojo/edk/system/waiter_unittest.cc b/mojo/edk/system/waiter_unittest.cc
new file mode 100644
index 0000000..bbbc5b8
--- /dev/null
+++ b/mojo/edk/system/waiter_unittest.cc
@@ -0,0 +1,302 @@
+// 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/edk/system/waiter.h"
+
+#include <stdint.h>
+
+#include "base/macros.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/edk/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/mojo/edk/test/BUILD.gn b/mojo/edk/test/BUILD.gn
new file mode 100644
index 0000000..6a6f312
--- /dev/null
+++ b/mojo/edk/test/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_base.gyp:mojo_common_test_support
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "multiprocess_test_helper.cc",
+ "multiprocess_test_helper.h",
+ "test_utils.h",
+ "test_utils_posix.cc",
+ "test_utils_win.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc
new file mode 100644
index 0000000..02367d6
--- /dev/null
+++ b/mojo/edk/test/multiprocess_test_helper.cc
@@ -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.
+
+#include "mojo/edk/test/multiprocess_test_helper.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/process/kill.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+
+namespace mojo {
+namespace test {
+
+MultiprocessTestHelper::MultiprocessTestHelper()
+ : test_child_handle_(base::kNullProcessHandle) {
+ platform_channel_pair_.reset(new embedder::PlatformChannelPair());
+ server_platform_handle = platform_channel_pair_->PassServerHandle();
+}
+
+MultiprocessTestHelper::~MultiprocessTestHelper() {
+ CHECK_EQ(test_child_handle_, base::kNullProcessHandle);
+ server_platform_handle.reset();
+ platform_channel_pair_.reset();
+}
+
+void MultiprocessTestHelper::StartChild(const std::string& test_child_name) {
+ CHECK(platform_channel_pair_.get());
+ CHECK(!test_child_name.empty());
+ CHECK_EQ(test_child_handle_, base::kNullProcessHandle);
+
+ std::string test_child_main = test_child_name + "TestChildMain";
+
+ base::CommandLine command_line(
+ base::GetMultiProcessTestChildBaseCommandLine());
+ embedder::HandlePassingInformation handle_passing_info;
+ platform_channel_pair_->PrepareToPassClientHandleToChildProcess(
+ &command_line, &handle_passing_info);
+
+ base::LaunchOptions options;
+#if defined(OS_POSIX)
+ options.fds_to_remap = &handle_passing_info;
+#elif defined(OS_WIN)
+ options.start_hidden = true;
+ options.handles_to_inherit = &handle_passing_info;
+#else
+#error "Not supported yet."
+#endif
+
+ test_child_handle_ =
+ base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
+ platform_channel_pair_->ChildProcessLaunched();
+
+ CHECK_NE(test_child_handle_, base::kNullProcessHandle);
+}
+
+int MultiprocessTestHelper::WaitForChildShutdown() {
+ CHECK_NE(test_child_handle_, base::kNullProcessHandle);
+
+ int rv = -1;
+ CHECK(base::WaitForExitCodeWithTimeout(
+ test_child_handle_, &rv, TestTimeouts::action_timeout()));
+ base::CloseProcessHandle(test_child_handle_);
+ test_child_handle_ = base::kNullProcessHandle;
+ return rv;
+}
+
+bool MultiprocessTestHelper::WaitForChildTestShutdown() {
+ return WaitForChildShutdown() == 0;
+}
+
+// static
+void MultiprocessTestHelper::ChildSetup() {
+ CHECK(base::CommandLine::InitializedForCurrentProcess());
+ client_platform_handle =
+ embedder::PlatformChannelPair::PassClientHandleFromParentProcess(
+ *base::CommandLine::ForCurrentProcess());
+}
+
+// static
+embedder::ScopedPlatformHandle MultiprocessTestHelper::client_platform_handle;
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h
new file mode 100644
index 0000000..ea77544
--- /dev/null
+++ b/mojo/edk/test/multiprocess_test_helper.h
@@ -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.
+
+#ifndef MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
+#define MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/process/process_handle.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace mojo {
+
+namespace embedder {
+class PlatformChannelPair;
+}
+
+namespace test {
+
+class MultiprocessTestHelper {
+ public:
+ MultiprocessTestHelper();
+ ~MultiprocessTestHelper();
+
+ // Start a child process and run the "main" function "named" |test_child_name|
+ // declared using |MOJO_MULTIPROCESS_TEST_CHILD_MAIN()| or
+ // |MOJO_MULTIPROCESS_TEST_CHILD_TEST()| (below).
+ void StartChild(const std::string& test_child_name);
+ // Wait for the child process to terminate.
+ // Returns the exit code of the child process. Note that, though it's declared
+ // to be an |int|, the exit code is subject to mangling by the OS. E.g., we
+ // usually return -1 on error in the child (e.g., if |test_child_name| was not
+ // found), but this is mangled to 255 on Linux. You should only rely on codes
+ // 0-127 being preserved, and -1 being outside the range 0-127.
+ int WaitForChildShutdown();
+
+ // Like |WaitForChildShutdown()|, but returns true on success (exit code of 0)
+ // and false otherwise. You probably want to do something like
+ // |EXPECT_TRUE(WaitForChildTestShutdown());|. Mainly for use with
+ // |MOJO_MULTIPROCESS_TEST_CHILD_TEST()|.
+ bool WaitForChildTestShutdown();
+
+ // For use by |MOJO_MULTIPROCESS_TEST_CHILD_MAIN()| only:
+ static void ChildSetup();
+
+ // For use in the main process:
+ embedder::ScopedPlatformHandle server_platform_handle;
+
+ // For use (and only valid) in the child process:
+ static embedder::ScopedPlatformHandle client_platform_handle;
+
+ private:
+ scoped_ptr<embedder::PlatformChannelPair> platform_channel_pair_;
+
+ // Valid after |StartChild()| and before |WaitForChildShutdown()|.
+ base::ProcessHandle test_child_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiprocessTestHelper);
+};
+
+// Use this to declare the child process's "main()" function for tests using
+// |MultiprocessTestHelper|. It returns an |int|, which will be the process's
+// exit code (but see the comment about |WaitForChildShutdown()|).
+#define MOJO_MULTIPROCESS_TEST_CHILD_MAIN(test_child_name) \
+ MULTIPROCESS_TEST_MAIN_WITH_SETUP( \
+ test_child_name ## TestChildMain, \
+ ::mojo::test::MultiprocessTestHelper::ChildSetup)
+
+// Use this (and |WaitForChildTestShutdown()|) for the child process's "main()",
+// if you want to use |EXPECT_...()| or |ASSERT_...()|; it has a |void| return
+// type. (Note that while an |ASSERT_...()| failure will abort the test in the
+// child, it will not abort the test in the parent.)
+#define MOJO_MULTIPROCESS_TEST_CHILD_TEST(test_child_name) \
+ void test_child_name ## TestChildTest(); \
+ MOJO_MULTIPROCESS_TEST_CHILD_MAIN(test_child_name) { \
+ test_child_name ## TestChildTest(); \
+ return (::testing::Test::HasFatalFailure() || \
+ ::testing::Test::HasNonfatalFailure()) ? 1 : 0; \
+ } \
+ void test_child_name ## TestChildTest()
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
diff --git a/mojo/edk/test/multiprocess_test_helper_unittest.cc b/mojo/edk/test/multiprocess_test_helper_unittest.cc
new file mode 100644
index 0000000..ec2ba22
--- /dev/null
+++ b/mojo/edk/test/multiprocess_test_helper_unittest.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/edk/test/multiprocess_test_helper.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include <fcntl.h>
+#endif
+
+namespace mojo {
+namespace test {
+namespace {
+
+bool IsNonBlocking(const embedder::PlatformHandle& handle) {
+#if defined(OS_WIN)
+ // Haven't figured out a way to query whether a HANDLE was created with
+ // FILE_FLAG_OVERLAPPED.
+ return true;
+#else
+ return fcntl(handle.fd, F_GETFL) & O_NONBLOCK;
+#endif
+}
+
+bool WriteByte(const embedder::PlatformHandle& handle, char c) {
+ size_t bytes_written = 0;
+ BlockingWrite(handle, &c, 1, &bytes_written);
+ return bytes_written == 1;
+}
+
+bool ReadByte(const embedder::PlatformHandle& handle, char* c) {
+ size_t bytes_read = 0;
+ BlockingRead(handle, c, 1, &bytes_read);
+ return bytes_read == 1;
+}
+
+typedef testing::Test MultiprocessTestHelperTest;
+
+TEST_F(MultiprocessTestHelperTest, RunChild) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+
+ helper.StartChild("RunChild");
+ EXPECT_EQ(123, helper.WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(RunChild) {
+ CHECK(MultiprocessTestHelper::client_platform_handle.is_valid());
+ return 123;
+}
+
+TEST_F(MultiprocessTestHelperTest, TestChildMainNotFound) {
+ MultiprocessTestHelper helper;
+ helper.StartChild("NoSuchTestChildMain");
+ int result = helper.WaitForChildShutdown();
+ EXPECT_FALSE(result >= 0 && result <= 127);
+}
+
+TEST_F(MultiprocessTestHelperTest, PassedChannel) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("PassedChannel");
+
+ // Take ownership of the handle.
+ embedder::ScopedPlatformHandle handle = helper.server_platform_handle.Pass();
+
+ // The handle should be non-blocking.
+ EXPECT_TRUE(IsNonBlocking(handle.get()));
+
+ // Write a byte.
+ const char c = 'X';
+ EXPECT_TRUE(WriteByte(handle.get(), c));
+
+ // It'll echo it back to us, incremented.
+ char d = 0;
+ EXPECT_TRUE(ReadByte(handle.get(), &d));
+ EXPECT_EQ(c + 1, d);
+
+ // And return it, incremented again.
+ EXPECT_EQ(c + 2, helper.WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(PassedChannel) {
+ CHECK(MultiprocessTestHelper::client_platform_handle.is_valid());
+
+ // Take ownership of the handle.
+ embedder::ScopedPlatformHandle handle =
+ MultiprocessTestHelper::client_platform_handle.Pass();
+
+ // The handle should be non-blocking.
+ EXPECT_TRUE(IsNonBlocking(handle.get()));
+
+ // Read a byte.
+ char c = 0;
+ EXPECT_TRUE(ReadByte(handle.get(), &c));
+
+ // Write it back, incremented.
+ c++;
+ EXPECT_TRUE(WriteByte(handle.get(), c));
+
+ // And return it, incremented again.
+ c++;
+ return static_cast<int>(c);
+}
+
+TEST_F(MultiprocessTestHelperTest, ChildTestPasses) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("ChildTestPasses");
+ EXPECT_TRUE(helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestPasses) {
+ ASSERT_TRUE(MultiprocessTestHelper::client_platform_handle.is_valid());
+ EXPECT_TRUE(IsNonBlocking(
+ MultiprocessTestHelper::client_platform_handle.get()));
+}
+
+TEST_F(MultiprocessTestHelperTest, ChildTestFailsAssert) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("ChildTestFailsAssert");
+ EXPECT_FALSE(helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsAssert) {
+ ASSERT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid())
+ << "DISREGARD: Expected failure in child process";
+ ASSERT_FALSE(IsNonBlocking(
+ MultiprocessTestHelper::client_platform_handle.get())) << "Not reached";
+ CHECK(false) << "Not reached";
+}
+
+TEST_F(MultiprocessTestHelperTest, ChildTestFailsExpect) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("ChildTestFailsExpect");
+ EXPECT_FALSE(helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsExpect) {
+ EXPECT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid())
+ << "DISREGARD: Expected failure #1 in child process";
+ EXPECT_FALSE(IsNonBlocking(
+ MultiprocessTestHelper::client_platform_handle.get()))
+ << "DISREGARD: Expected failure #2 in child process";
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/edk/test/test_utils.h b/mojo/edk/test/test_utils.h
new file mode 100644
index 0000000..9287457
--- /dev/null
+++ b/mojo/edk/test/test_utils.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_EDK_TEST_TEST_UTILS_H_
+#define MOJO_EDK_TEST_TEST_UTILS_H_
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_file.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+
+namespace mojo {
+namespace test {
+
+// On success, |bytes_written| is updated to the number of bytes written;
+// otherwise it is untouched.
+bool BlockingWrite(const embedder::PlatformHandle& handle,
+ const void* buffer,
+ size_t bytes_to_write,
+ size_t* bytes_written);
+
+// On success, |bytes_read| is updated to the number of bytes read; otherwise it
+// is untouched.
+bool BlockingRead(const embedder::PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read);
+
+// If the read is done successfully or would block, the function returns true
+// and updates |bytes_read| to the number of bytes read (0 if the read would
+// block); otherwise it returns false and leaves |bytes_read| untouched.
+// |handle| must already be in non-blocking mode.
+bool NonBlockingRead(const embedder::PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read);
+
+// Gets a (scoped) |PlatformHandle| from the given (scoped) |FILE|.
+embedder::ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp);
+
+// Gets a (scoped) |FILE| from a (scoped) |PlatformHandle|.
+base::ScopedFILE FILEFromPlatformHandle(embedder::ScopedPlatformHandle h,
+ const char* mode);
+
+// Returns the path to the mojom js bindings file.
+base::FilePath GetFilePathForJSResource(const std::string& path);
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_EDK_TEST_TEST_UTILS_H_
diff --git a/mojo/edk/test/test_utils_posix.cc b/mojo/edk/test/test_utils_posix.cc
new file mode 100644
index 0000000..6491baf
--- /dev/null
+++ b/mojo/edk/test/test_utils_posix.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium 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/edk/test/test_utils.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "base/base_paths.h"
+#include "base/path_service.h"
+#include "base/posix/eintr_wrapper.h"
+
+namespace mojo {
+namespace test {
+
+bool BlockingWrite(const embedder::PlatformHandle& handle,
+ const void* buffer,
+ size_t bytes_to_write,
+ size_t* bytes_written) {
+ int original_flags = fcntl(handle.fd, F_GETFL);
+ if (original_flags == -1 ||
+ fcntl(handle.fd, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) {
+ return false;
+ }
+
+ ssize_t result = HANDLE_EINTR(write(handle.fd, buffer, bytes_to_write));
+
+ fcntl(handle.fd, F_SETFL, original_flags);
+
+ if (result < 0)
+ return false;
+
+ *bytes_written = result;
+ return true;
+}
+
+bool BlockingRead(const embedder::PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ int original_flags = fcntl(handle.fd, F_GETFL);
+ if (original_flags == -1 ||
+ fcntl(handle.fd, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) {
+ return false;
+ }
+
+ ssize_t result = HANDLE_EINTR(read(handle.fd, buffer, buffer_size));
+
+ fcntl(handle.fd, F_SETFL, original_flags);
+
+ if (result < 0)
+ return false;
+
+ *bytes_read = result;
+ return true;
+}
+
+bool NonBlockingRead(const embedder::PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ ssize_t result = HANDLE_EINTR(read(handle.fd, buffer, buffer_size));
+
+ if (result < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ return false;
+
+ *bytes_read = 0;
+ } else {
+ *bytes_read = result;
+ }
+
+ return true;
+}
+
+embedder::ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
+ CHECK(fp);
+ int rv = dup(fileno(fp.get()));
+ PCHECK(rv != -1) << "dup";
+ return embedder::ScopedPlatformHandle(embedder::PlatformHandle(rv));
+}
+
+base::ScopedFILE FILEFromPlatformHandle(embedder::ScopedPlatformHandle h,
+ const char* mode) {
+ CHECK(h.is_valid());
+ base::ScopedFILE rv(fdopen(h.release().fd, mode));
+ PCHECK(rv) << "fdopen";
+ return rv.Pass();
+}
+
+base::FilePath GetFilePathForJSResource(const std::string& path) {
+ std::string binding_path = "gen/" + path + ".js";
+ base::FilePath exe_dir;
+ PathService::Get(base::DIR_EXE, &exe_dir);
+ return exe_dir.AppendASCII(binding_path);
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/edk/test/test_utils_win.cc b/mojo/edk/test/test_utils_win.cc
new file mode 100644
index 0000000..42613a6
--- /dev/null
+++ b/mojo/edk/test/test_utils_win.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 "mojo/edk/test/test_utils.h"
+
+#include <windows.h>
+#include <fcntl.h>
+#include <io.h>
+#include <string.h>
+
+#include "base/base_paths.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+
+namespace mojo {
+namespace test {
+
+bool BlockingWrite(const embedder::PlatformHandle& handle,
+ const void* buffer,
+ size_t bytes_to_write,
+ size_t* bytes_written) {
+ OVERLAPPED overlapped = { 0 };
+ DWORD bytes_written_dword = 0;
+
+ if (!WriteFile(handle.handle, buffer, static_cast<DWORD>(bytes_to_write),
+ &bytes_written_dword, &overlapped)) {
+ if (GetLastError() != ERROR_IO_PENDING ||
+ !GetOverlappedResult(handle.handle, &overlapped, &bytes_written_dword,
+ TRUE)) {
+ return false;
+ }
+ }
+
+ *bytes_written = bytes_written_dword;
+ return true;
+}
+
+bool BlockingRead(const embedder::PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ OVERLAPPED overlapped = { 0 };
+ DWORD bytes_read_dword = 0;
+
+ if (!ReadFile(handle.handle, buffer, static_cast<DWORD>(buffer_size),
+ &bytes_read_dword, &overlapped)) {
+ if (GetLastError() != ERROR_IO_PENDING ||
+ !GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword,
+ TRUE)) {
+ return false;
+ }
+ }
+
+ *bytes_read = bytes_read_dword;
+ return true;
+}
+
+bool NonBlockingRead(const embedder::PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ OVERLAPPED overlapped = { 0 };
+ DWORD bytes_read_dword = 0;
+
+ if (!ReadFile(handle.handle, buffer, static_cast<DWORD>(buffer_size),
+ &bytes_read_dword, &overlapped)) {
+ if (GetLastError() != ERROR_IO_PENDING)
+ return false;
+
+ CancelIo(handle.handle);
+
+ if (!GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword,
+ TRUE)) {
+ *bytes_read = 0;
+ return true;
+ }
+ }
+
+ *bytes_read = bytes_read_dword;
+ return true;
+}
+
+embedder::ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
+ CHECK(fp);
+
+ HANDLE rv = INVALID_HANDLE_VALUE;
+ PCHECK(DuplicateHandle(
+ GetCurrentProcess(),
+ reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp.get()))),
+ GetCurrentProcess(),
+ &rv,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS)) << "DuplicateHandle";
+ return embedder::ScopedPlatformHandle(embedder::PlatformHandle(rv));
+}
+
+base::ScopedFILE FILEFromPlatformHandle(embedder::ScopedPlatformHandle h,
+ const char* mode) {
+ CHECK(h.is_valid());
+ // Microsoft's documentation for |_open_osfhandle()| only discusses these
+ // flags (and |_O_WTEXT|). Hmmm.
+ int flags = 0;
+ if (strchr(mode, 'a'))
+ flags |= _O_APPEND;
+ if (strchr(mode, 'r'))
+ flags |= _O_RDONLY;
+ if (strchr(mode, 't'))
+ flags |= _O_TEXT;
+ base::ScopedFILE rv(
+ _fdopen(_open_osfhandle(reinterpret_cast<intptr_t>(h.release().handle),
+ flags),
+ mode));
+ PCHECK(rv) << "_fdopen";
+ return rv.Pass();
+}
+
+base::FilePath GetFilePathForJSResource(const std::string& path) {
+ std::string binding_path = "gen/" + path + ".js";
+ base::ReplaceChars(binding_path, "//", "\\", &binding_path);
+ base::FilePath exe_dir;
+ PathService::Get(base::DIR_EXE, &exe_dir);
+ return exe_dir.AppendASCII(binding_path);
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/environment/BUILD.gn b/mojo/environment/BUILD.gn
new file mode 100644
index 0000000..c860250
--- /dev/null
+++ b/mojo/environment/BUILD.gn
@@ -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.
+
+# GYP version: mojo_base.gyp:mojo_environment_chromium
+source_set("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",
+ ]
+
+ public_deps = [
+ ":chromium_impl",
+ ]
+}
+
+# GYP version: mojo_base.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/mojo/environment/default_async_waiter_impl.cc b/mojo/environment/default_async_waiter_impl.cc
new file mode 100644
index 0000000..ff7a524
--- /dev/null
+++ b/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/mojo/environment/default_async_waiter_impl.h b/mojo/environment/default_async_waiter_impl.h
new file mode 100644
index 0000000..5140e35
--- /dev/null
+++ b/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/mojo/environment/default_logger_impl.cc b/mojo/environment/default_logger_impl.cc
new file mode 100644
index 0000000..575feb3
--- /dev/null
+++ b/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/mojo/environment/default_logger_impl.h b/mojo/environment/default_logger_impl.h
new file mode 100644
index 0000000..c87a727
--- /dev/null
+++ b/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/mojo/environment/environment.cc b/mojo/environment/environment.cc
new file mode 100644
index 0000000..f861113
--- /dev/null
+++ b/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/mojo/environment/mojo_environment_impl_export.h b/mojo/environment/mojo_environment_impl_export.h
new file mode 100644
index 0000000..0255425
--- /dev/null
+++ b/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/mojo/examples/BUILD.gn b/mojo/examples/BUILD.gn
new file mode 100644
index 0000000..bb42f4d
--- /dev/null
+++ b/mojo/examples/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+group("examples") {
+ testonly = true
+
+ deps = [
+ "//mojo/examples/apptest",
+ "//mojo/examples/compositor_app",
+ "//mojo/examples/content_handler_demo",
+ "//mojo/examples/echo",
+ "//mojo/examples/pepper_container_app",
+ "//mojo/examples/png_viewer",
+ "//mojo/examples/sample_app",
+ "//mojo/examples/surfaces_app",
+ "//mojo/examples/wget",
+ ]
+
+ if (use_aura) {
+ deps += [
+ "//mojo/examples/aura_demo:mojo_aura_demo",
+ "//mojo/examples/browser",
+ "//mojo/examples/demo_launcher",
+ "//mojo/examples/embedded_app",
+ "//mojo/examples/media_viewer",
+ "//mojo/examples/nesting_app",
+ "//mojo/examples/keyboard",
+ "//mojo/examples/window_manager",
+ "//mojo/examples/wm_flow",
+ ]
+ }
+}
diff --git a/mojo/examples/apptest/BUILD.gn b/mojo/examples/apptest/BUILD.gn
new file mode 100644
index 0000000..456f0d4
--- /dev/null
+++ b/mojo/examples/apptest/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+group("apptest") {
+ testonly = true
+
+ deps = [
+ ":apptests",
+ ":service",
+ ]
+}
+
+# GYP version mojo/mojo_examples.gypi:mojo_example_service
+shared_library("service") {
+ output_name = "mojo_example_service"
+
+ sources = [
+ "example_service_application.cc",
+ "example_service_application.h",
+ "example_service_impl.cc",
+ "example_service_impl.h",
+ ]
+
+ deps = [
+ ":bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/utility"
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_example_apptests
+shared_library("apptests") {
+ output_name = "mojo_example_apptests"
+
+ testonly = true
+
+ sources = [
+ "example_apptest.cc",
+ "example_client_application.cc",
+ "example_client_application.h",
+ "example_client_impl.cc",
+ "example_client_impl.h",
+ ]
+
+ deps = [
+ ":bindings",
+ "//testing/gtest",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/utility",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_example_service_bindings
+mojom("bindings") {
+ sources = [ "example_service.mojom" ]
+}
diff --git a/mojo/examples/apptest/example_apptest.cc b/mojo/examples/apptest/example_apptest.cc
new file mode 100644
index 0000000..ca3fda8
--- /dev/null
+++ b/mojo/examples/apptest/example_apptest.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/examples/apptest/example_client_application.h"
+#include "mojo/examples/apptest/example_client_impl.h"
+#include "mojo/examples/apptest/example_service.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// TODO(msw): Remove this once we can get ApplicationImpl from TLS.
+mojo::ApplicationImpl* g_application_impl_hack = NULL;
+
+} // namespace
+
+namespace mojo {
+
+namespace {
+
+class ExampleServiceTest : public testing::Test {
+ public:
+ ExampleServiceTest() {
+ g_application_impl_hack->ConnectToService("mojo:mojo_example_service",
+ &example_service_);
+ example_service_.set_client(&example_client_);
+ }
+
+ virtual ~ExampleServiceTest() override {}
+
+ protected:
+ ExampleServicePtr example_service_;
+ ExampleClientImpl example_client_;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ExampleServiceTest);
+};
+
+TEST_F(ExampleServiceTest, Ping) {
+ EXPECT_EQ(0, example_client_.last_pong_value());
+ example_service_->Ping(1);
+ RunLoop::current()->Run();
+ EXPECT_EQ(1, example_client_.last_pong_value());
+}
+
+template <typename T>
+struct SetAndQuit : public Callback<void()>::Runnable {
+ SetAndQuit(T* val, T result) : val_(val), result_(result) {}
+ virtual ~SetAndQuit() {}
+ virtual void Run() const override {
+ *val_ = result_;
+ RunLoop::current()->Quit();
+ }
+ T* val_;
+ T result_;
+};
+
+TEST_F(ExampleServiceTest, RunCallback) {
+ bool was_run = false;
+ example_service_->RunCallback(SetAndQuit<bool>(&was_run, true));
+ RunLoop::current()->Run();
+ EXPECT_TRUE(was_run);
+}
+
+} // namespace
+
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::Environment env;
+ mojo::RunLoop loop;
+
+ // TODO(tim): Perhaps the delegate should be the thing that provides
+ // the ExampleServiceTest with the ApplicationImpl somehow.
+ mojo::ApplicationDelegate* delegate = new mojo::ExampleClientApplication();
+ mojo::ApplicationImpl app(delegate, shell_handle);
+ g_application_impl_hack = &app;
+
+ // TODO(msw): Get actual commandline arguments.
+ int argc = 0;
+ char** argv = NULL;
+ testing::InitGoogleTest(&argc, argv);
+ mojo_ignore_result(RUN_ALL_TESTS());
+
+ delete delegate;
+ return MOJO_RESULT_OK;
+}
diff --git a/mojo/examples/apptest/example_client_application.cc b/mojo/examples/apptest/example_client_application.cc
new file mode 100644
index 0000000..8ba5739
--- /dev/null
+++ b/mojo/examples/apptest/example_client_application.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/examples/apptest/example_client_application.h"
+
+#include "mojo/examples/apptest/example_client_impl.h"
+
+namespace mojo {
+
+ExampleClientApplication::ExampleClientApplication() {}
+
+ExampleClientApplication::~ExampleClientApplication() {}
+
+void ExampleClientApplication::Initialize(ApplicationImpl* app) {}
+
+} // namespace mojo
diff --git a/mojo/examples/apptest/example_client_application.h b/mojo/examples/apptest/example_client_application.h
new file mode 100644
index 0000000..c54d2da
--- /dev/null
+++ b/mojo/examples/apptest/example_client_application.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_EXAMPLES_TEST_EXAMPLE_CLIENT_APPLICATION_H_
+#define MOJO_EXAMPLES_TEST_EXAMPLE_CLIENT_APPLICATION_H_
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+class ExampleClientApplication : public ApplicationDelegate {
+ public:
+ ExampleClientApplication();
+ virtual ~ExampleClientApplication();
+
+ private:
+ // ApplicationDelegate implementation.
+ virtual void Initialize(ApplicationImpl* app) override;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ExampleClientApplication);
+};
+
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_TEST_EXAMPLE_CLIENT_APPLICATION_H_
diff --git a/mojo/examples/apptest/example_client_impl.cc b/mojo/examples/apptest/example_client_impl.cc
new file mode 100644
index 0000000..9d539be
--- /dev/null
+++ b/mojo/examples/apptest/example_client_impl.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/examples/apptest/example_client_impl.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+
+namespace mojo {
+
+ExampleClientImpl::ExampleClientImpl() : last_pong_value_(0) {}
+ExampleClientImpl::~ExampleClientImpl() {}
+
+void ExampleClientImpl::Pong(uint16_t pong_value) {
+ last_pong_value_ = pong_value;
+ RunLoop::current()->Quit();
+}
+
+} // namespace mojo
diff --git a/mojo/examples/apptest/example_client_impl.h b/mojo/examples/apptest/example_client_impl.h
new file mode 100644
index 0000000..25d817a
--- /dev/null
+++ b/mojo/examples/apptest/example_client_impl.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_EXAMPLES_TEST_EXAMPLE_CLIENT_IMPL_H_
+#define MOJO_EXAMPLES_TEST_EXAMPLE_CLIENT_IMPL_H_
+
+#include "mojo/examples/apptest/example_service.mojom.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+class ExampleClientImpl : public InterfaceImpl<ExampleClient> {
+ public:
+ explicit ExampleClientImpl();
+ virtual ~ExampleClientImpl();
+
+ int16_t last_pong_value() const { return last_pong_value_; }
+
+ private:
+ // InterfaceImpl<ExampleClient> overrides.
+ virtual void Pong(uint16_t pong_value) override;
+
+ int16_t last_pong_value_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ExampleClientImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_TEST_EXAMPLE_CLIENT_IMPL_H_
diff --git a/mojo/examples/apptest/example_service.mojom b/mojo/examples/apptest/example_service.mojom
new file mode 100644
index 0000000..a2226fa
--- /dev/null
+++ b/mojo/examples/apptest/example_service.mojom
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo {
+
+// A simple service used to exemplify application testing within mojo_shell.
+[Client=ExampleClient]
+interface ExampleService {
+ // Calls ExampleClient::Pong with |ping_value|.
+ Ping(uint16 ping_value);
+
+ // Runs the argument callback.
+ RunCallback() => ();
+};
+
+interface ExampleClient {
+ Pong(uint16 pong_value);
+};
+
+}
diff --git a/mojo/examples/apptest/example_service_application.cc b/mojo/examples/apptest/example_service_application.cc
new file mode 100644
index 0000000..a62a510
--- /dev/null
+++ b/mojo/examples/apptest/example_service_application.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/examples/apptest/example_service_application.h"
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_runner.h"
+
+namespace mojo {
+
+ExampleServiceApplication::ExampleServiceApplication() {}
+
+ExampleServiceApplication::~ExampleServiceApplication() {}
+
+bool ExampleServiceApplication::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService(&example_service_factory_);
+ return true;
+}
+
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::ExampleServiceApplication());
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/apptest/example_service_application.h b/mojo/examples/apptest/example_service_application.h
new file mode 100644
index 0000000..96843cc
--- /dev/null
+++ b/mojo/examples/apptest/example_service_application.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_EXAMPLES_TEST_EXAMPLE_SERVICE_APPLICATION_H_
+#define MOJO_EXAMPLES_TEST_EXAMPLE_SERVICE_APPLICATION_H_
+
+#include "mojo/examples/apptest/example_service_impl.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+class ExampleServiceApplication : public ApplicationDelegate {
+ public:
+ ExampleServiceApplication();
+ virtual ~ExampleServiceApplication();
+
+ private:
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override;
+
+ InterfaceFactoryImpl<ExampleServiceImpl> example_service_factory_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ExampleServiceApplication);
+};
+
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_TEST_EXAMPLE_SERVICE_APPLICATION_H_
diff --git a/mojo/examples/apptest/example_service_impl.cc b/mojo/examples/apptest/example_service_impl.cc
new file mode 100644
index 0000000..b656ce0
--- /dev/null
+++ b/mojo/examples/apptest/example_service_impl.cc
@@ -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.
+
+#include "mojo/examples/apptest/example_service_impl.h"
+
+#include "mojo/public/cpp/utility/run_loop.h"
+
+namespace mojo {
+
+ExampleServiceImpl::ExampleServiceImpl() {}
+
+ExampleServiceImpl::~ExampleServiceImpl() {}
+
+void ExampleServiceImpl::Ping(uint16_t ping_value) {
+ client()->Pong(ping_value);
+ RunLoop::current()->Quit();
+}
+
+void ExampleServiceImpl::RunCallback(const Callback<void()>& callback) {
+ callback.Run();
+ RunLoop::current()->Quit();
+}
+
+} // namespace mojo
diff --git a/mojo/examples/apptest/example_service_impl.h b/mojo/examples/apptest/example_service_impl.h
new file mode 100644
index 0000000..18537fa
--- /dev/null
+++ b/mojo/examples/apptest/example_service_impl.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_EXAMPLES_TEST_EXAMPLE_SERVICE_IMPL_H_
+#define MOJO_EXAMPLES_TEST_EXAMPLE_SERVICE_IMPL_H_
+
+#include "mojo/examples/apptest/example_service.mojom.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+class ExampleServiceImpl : public InterfaceImpl<ExampleService> {
+ public:
+ ExampleServiceImpl();
+ virtual ~ExampleServiceImpl();
+
+ private:
+ // InterfaceImpl<ExampleService> overrides.
+ virtual void Ping(uint16_t ping_value) override;
+ virtual void RunCallback(const Callback<void()>& callback) override;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ExampleServiceImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_TEST_EXAMPLE_SERVICE_IMPL_H_
diff --git a/mojo/examples/aura_demo/BUILD.gn b/mojo/examples/aura_demo/BUILD.gn
new file mode 100644
index 0000000..ba2fc37
--- /dev/null
+++ b/mojo/examples/aura_demo/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+assert(use_aura)
+
+# GYP version mojo/mojo_examples.gypi:mojo_aura_demo
+shared_library("mojo_aura_demo") {
+ output_name = "mojo_aura_demo"
+
+ sources = [
+ "aura_demo.cc"
+ ]
+
+ deps = [
+ "//base",
+ "//cc",
+ "//ui/aura",
+ "//ui/base",
+ "//ui/compositor",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/aura",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/view_manager",
+ ]
+
+ datadeps = [ ":init" ]
+}
+
+# GYP version mojo/mojo_examples.gypi:mojo_aura_demo_init
+shared_library("init") {
+ output_name = "mojo_aura_demo_init"
+
+ sources = [
+ "view_manager_init.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/interfaces/view_manager",
+ ]
+}
diff --git a/mojo/examples/aura_demo/DEPS b/mojo/examples/aura_demo/DEPS
new file mode 100644
index 0000000..eb8dd8f
--- /dev/null
+++ b/mojo/examples/aura_demo/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+skia/ext",
+ "+ui/aura",
+ "+ui/base/hit_test.h",
+ "+ui/compositor",
+ "+ui/events",
+ "+ui/gfx",
+]
diff --git a/mojo/examples/aura_demo/aura_demo.cc b/mojo/examples/aura_demo/aura_demo.cc
new file mode 100644
index 0000000..1895761
--- /dev/null
+++ b/mojo/examples/aura_demo/aura_demo.cc
@@ -0,0 +1,209 @@
+// 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 "base/macros.h"
+#include "mojo/application/application_runner_chromium.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/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/system/core.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_client_factory.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 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 mojo::ApplicationDelegate,
+ public mojo::WindowTreeHostMojoDelegate,
+ public mojo::ViewManagerDelegate {
+ public:
+ AuraDemo() : window1_(NULL), window2_(NULL), window21_(NULL) {}
+ virtual ~AuraDemo() {}
+
+ private:
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(
+ mojo::ViewManager* view_manager,
+ mojo::View* root,
+ mojo::ServiceProviderImpl* exported_services,
+ scoped_ptr<mojo::ServiceProvider> imported_services) override {
+ // TODO(beng): this function could be called multiple times!
+ root_ = root;
+
+ window_tree_host_.reset(new mojo::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();
+ }
+ virtual void OnViewManagerDisconnected(
+ mojo::ViewManager* view_manager) override {
+ base::MessageLoop::current()->Quit();
+ }
+
+ // WindowTreeHostMojoDelegate:
+ virtual void CompositorContentsChanged(const SkBitmap& bitmap) override {
+ root_->SetContents(bitmap);
+ }
+
+ virtual void Initialize(mojo::ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new mojo::ViewManagerClientFactory(app->shell(), this));
+ aura::Env::CreateInstance(true);
+ context_factory_.reset(new mojo::ContextFactoryMojo);
+ aura::Env::GetInstance()->set_context_factory(context_factory_.get());
+ screen_.reset(mojo::ScreenMojo::Create());
+ gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get());
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ scoped_ptr<DemoWindowTreeClient> window_tree_client_;
+
+ scoped_ptr<ui::ContextFactory> context_factory_;
+
+ scoped_ptr<mojo::ScreenMojo> screen_;
+
+ scoped_ptr<DemoWindowDelegate> delegate1_;
+ scoped_ptr<DemoWindowDelegate> delegate2_;
+ scoped_ptr<DemoWindowDelegate> delegate21_;
+
+ aura::Window* window1_;
+ aura::Window* window2_;
+ aura::Window* window21_;
+
+ mojo::View* root_;
+
+ scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_;
+
+ scoped_ptr<aura::WindowTreeHost> window_tree_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuraDemo);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new examples::AuraDemo);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/aura_demo/view_manager_init.cc b/mojo/examples/aura_demo/view_manager_init.cc
new file mode 100644
index 0000000..bd07e6d
--- /dev/null
+++ b/mojo/examples/aura_demo/view_manager_init.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/basictypes.h"
+#include "base/bind.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/public/cpp/view_manager/view_manager.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_context.h"
+
+namespace examples {
+
+// ViewManagerInit is responsible for establishing the initial connection to
+// the view manager. When established it loads |mojo_aura_demo|.
+class ViewManagerInit : public mojo::ApplicationDelegate {
+ public:
+ ViewManagerInit() {}
+ virtual ~ViewManagerInit() {}
+
+ virtual void Initialize(mojo::ApplicationImpl* app) override {
+ context_.reset(new mojo::ViewManagerContext(app));
+ context_->Embed("mojo:mojo_aura_demo");
+ }
+
+ private:
+ scoped_ptr<mojo::ViewManagerContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerInit);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new examples::ViewManagerInit);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/browser/BUILD.gn b/mojo/examples/browser/BUILD.gn
new file mode 100644
index 0000000..2a62913
--- /dev/null
+++ b/mojo/examples/browser/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+assert(use_aura)
+
+# GYP version: mojo/mojo_examples.gypi:mojo_browser
+shared_library("browser") {
+ output_name = "mojo_browser"
+
+ sources = [
+ "browser.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//cc",
+ "//mojo/application",
+ "//mojo/aura",
+ "//mojo/common",
+ "//mojo/examples/window_manager:bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/navigation",
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/views",
+ "//third_party/icu",
+ "//ui/aura",
+ "//ui/base",
+ "//ui/compositor",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/resources",
+ "//ui/views",
+ "//url",
+ ]
+}
diff --git a/mojo/examples/browser/DEPS b/mojo/examples/browser/DEPS
new file mode 100644
index 0000000..02dd6ea
--- /dev/null
+++ b/mojo/examples/browser/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+skia/ext",
+ "+ui/aura",
+ "+ui/base/hit_test.h",
+ "+ui/compositor",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/views",
+]
diff --git a/mojo/examples/browser/browser.cc b/mojo/examples/browser/browser.cc
new file mode 100644
index 0000000..2a77f89
--- /dev/null
+++ b/mojo/examples/browser/browser.cc
@@ -0,0 +1,273 @@
+// Copyright 2014 The Chromium 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/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/examples/window_manager/window_manager.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.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_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.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/aura/client/focus_client.h"
+#include "ui/aura/window.h"
+#include "ui/events/event.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/focus/focus_manager.h"
+#include "ui/views/layout/layout_manager.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "ui/views/widget/widget_observer.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);
+};
+
+// KeyboardManager handles notifying the windowmanager when views are focused.
+// To use create one and KeyboardManager will take care of all other details.
+//
+// TODO(sky): it would be nice if this were put in NativeWidgetViewManager, but
+// that requires NativeWidgetViewManager to take an IWindowManager. That may be
+// desirable anyway...
+class KeyboardManager
+ : public views::FocusChangeListener,
+ public ui::EventHandler,
+ public views::WidgetObserver {
+ public:
+ KeyboardManager(views::Widget* widget,
+ IWindowManager* window_manager,
+ View* view)
+ : widget_(widget),
+ window_manager_(window_manager),
+ view_(view),
+ last_view_id_(0),
+ focused_view_(NULL) {
+ widget_->GetFocusManager()->AddFocusChangeListener(this);
+ widget_->AddObserver(this);
+ widget_->GetNativeView()->AddPostTargetHandler(this);
+ }
+
+ private:
+ virtual ~KeyboardManager() {
+ widget_->GetFocusManager()->RemoveFocusChangeListener(this);
+ widget_->GetNativeView()->RemovePostTargetHandler(this);
+ widget_->RemoveObserver(this);
+
+ HideKeyboard();
+ }
+
+ void ShowKeyboard(views::View* view) {
+ if (focused_view_ == view)
+ return;
+
+ const gfx::Rect bounds_in_widget =
+ view->ConvertRectToWidget(gfx::Rect(view->bounds().size()));
+ last_view_id_ = view_->id();
+ window_manager_->ShowKeyboard(last_view_id_,
+ Rect::From(bounds_in_widget));
+ // TODO(sky): listen for view to be removed.
+ focused_view_ = view;
+ }
+
+ void HideKeyboard() {
+ if (!focused_view_)
+ return;
+
+ window_manager_->HideKeyboard(last_view_id_);
+ last_view_id_ = 0;
+ focused_view_ = NULL;
+ }
+
+ // views::FocusChangeListener:
+ virtual void OnWillChangeFocus(views::View* focused_before,
+ views::View* focused_now) override {
+ }
+ virtual void OnDidChangeFocus(views::View* focused_before,
+ views::View* focused_now) override {
+ if (focused_view_ && focused_now != focused_view_)
+ HideKeyboard();
+ }
+
+ // ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) override {
+ views::View* focused_now = widget_->GetFocusManager()->GetFocusedView();
+ if (focused_now &&
+ focused_now->GetClassName() == views::Textfield::kViewClassName &&
+ (event->flags() & ui::EF_FROM_TOUCH) != 0) {
+ ShowKeyboard(focused_now);
+ }
+ }
+
+ // views::WidgetObserver:
+ virtual void OnWidgetDestroying(views::Widget* widget) override {
+ delete this;
+ }
+
+ views::Widget* widget_;
+ IWindowManager* window_manager_;
+ View* view_;
+ Id last_view_id_;
+ views::View* focused_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardManager);
+};
+
+// This is the basics of creating a views widget with a textfield.
+// TODO: cleanup!
+class Browser : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public views::TextfieldController,
+ public ViewObserver {
+ public:
+ Browser()
+ : view_manager_(NULL),
+ root_(NULL),
+ widget_(NULL) {}
+
+ virtual ~Browser() {
+ if (root_)
+ root_->RemoveObserver(this);
+ }
+
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ views_init_.reset(new ViewsInit);
+ app->ConnectToService("mojo:mojo_window_manager", &window_manager_);
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ void CreateWidget(View* view) {
+ views::Textfield* textfield = new views::Textfield;
+ textfield->set_controller(this);
+
+ views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView;
+ widget_delegate->GetContentsView()->set_background(
+ views::Background::CreateSolidBackground(SK_ColorBLUE));
+ widget_delegate->GetContentsView()->AddChildView(textfield);
+ widget_delegate->GetContentsView()->SetLayoutManager(
+ new BrowserLayoutManager);
+
+ widget_ = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.native_widget = new NativeWidgetViewManager(widget_, view);
+ params.delegate = widget_delegate;
+ params.bounds = gfx::Rect(view->bounds().width(), view->bounds().height());
+ widget_->Init(params);
+ // KeyboardManager handles deleting itself when the widget is destroyed.
+ new KeyboardManager(widget_, window_manager_.get(), view);
+ widget_->Show();
+ textfield->RequestFocus();
+ }
+
+ // ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ // TODO: deal with OnEmbed() being invoked multiple times.
+ ConnectToService(imported_services.get(), &navigator_host_);
+ view_manager_ = view_manager;
+ root_ = root;
+ root_->AddObserver(this);
+ root_->SetFocus();
+ CreateWidget(root_);
+ }
+ virtual void OnViewManagerDisconnected(
+ ViewManager* view_manager) override {
+ DCHECK_EQ(view_manager_, view_manager);
+ view_manager_ = NULL;
+ base::MessageLoop::current()->Quit();
+ }
+
+ // 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());
+ URLRequestPtr request(URLRequest::New());
+ request->url = String::From(url);
+ navigator_host_->RequestNavigate(TARGET_NEW_NODE, request.Pass());
+ }
+ return false;
+ }
+
+ // ViewObserver:
+ virtual void OnViewFocusChanged(View* gained_focus,
+ View* lost_focus) override {
+ aura::client::FocusClient* focus_client =
+ aura::client::GetFocusClient(widget_->GetNativeView());
+ if (lost_focus == root_)
+ focus_client->FocusWindow(NULL);
+ else if (gained_focus == root_)
+ focus_client->FocusWindow(widget_->GetNativeView());
+ }
+ virtual void OnViewDestroyed(View* view) override {
+ DCHECK_EQ(root_, view);
+ view->RemoveObserver(this);
+ root_ = NULL;
+ }
+
+ scoped_ptr<ViewsInit> views_init_;
+
+ ViewManager* view_manager_;
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+ View* root_;
+ views::Widget* widget_;
+ NavigatorHostPtr navigator_host_;
+ IWindowManagerPtr window_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(Browser);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::Browser);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/compositor_app/BUILD.gn b/mojo/examples/compositor_app/BUILD.gn
new file mode 100644
index 0000000..8ef8ad7
--- /dev/null
+++ b/mojo/examples/compositor_app/BUILD.gn
@@ -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.
+
+# GYP version mojo/mojo_examples.gypi:mojo_compositor_app
+shared_library("compositor_app") {
+ output_name = "mojo_compositor_app"
+
+ sources = [
+ "compositor_app.cc",
+ "compositor_host.cc",
+ "compositor_host.h",
+ ]
+
+ deps = [
+ "//base",
+ "//cc",
+ "//mojo/application",
+ "//mojo/cc",
+ "//mojo/common",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/gpu",
+ "//mojo/services/public/interfaces/native_viewport",
+ ]
+}
diff --git a/mojo/examples/compositor_app/DEPS b/mojo/examples/compositor_app/DEPS
new file mode 100644
index 0000000..70688d6
--- /dev/null
+++ b/mojo/examples/compositor_app/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+ui/gfx",
+ "+gpu/command_buffer/client",
+]
diff --git a/mojo/examples/compositor_app/compositor_app.cc b/mojo/examples/compositor_app/compositor_app.cc
new file mode 100644
index 0000000..279fe75
--- /dev/null
+++ b/mojo/examples/compositor_app/compositor_app.cc
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/compositor_app/compositor_host.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h"
+#include "ui/gfx/rect.h"
+
+namespace mojo {
+namespace examples {
+
+class SampleApp : public ApplicationDelegate, public NativeViewportClient {
+ public:
+ SampleApp() : weak_factory_(this) {}
+ virtual ~SampleApp() {}
+
+ virtual void Initialize(ApplicationImpl* app) override {
+ app->ConnectToService("mojo:mojo_native_viewport_service", &viewport_);
+ viewport_.set_client(this);
+ viewport_->Create(Size::From(gfx::Size(800, 600)),
+ base::Bind(&SampleApp::OnCreatedNativeViewport,
+ weak_factory_.GetWeakPtr()));
+ viewport_->Show();
+
+ // TODO(jamesr): Should be mojo:mojo_gpu_service
+ app->ConnectToService("mojo:mojo_native_viewport_service", &gpu_service_);
+ }
+
+ virtual void OnDestroyed() override { base::MessageLoop::current()->Quit(); }
+
+ virtual void OnSizeChanged(SizePtr size) override {
+ if (host_)
+ host_->SetSize(size.To<gfx::Size>());
+ }
+
+ virtual void OnEvent(EventPtr event,
+ const mojo::Callback<void()>& callback) override {
+ callback.Run();
+ }
+
+ private:
+ void OnCreatedNativeViewport(uint64_t native_viewport_id) {
+ CommandBufferPtr cb;
+ // TODO(jamesr): Output to a surface instead.
+ gpu_service_->CreateOnscreenGLES2Context(
+ native_viewport_id, Size::From(gfx::Size(800, 600)), Get(&cb));
+ host_.reset(new CompositorHost(cb.PassMessagePipe()));
+ }
+
+
+ NativeViewportPtr viewport_;
+ GpuPtr gpu_service_;
+ scoped_ptr<CompositorHost> host_;
+ base::WeakPtrFactory<SampleApp> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SampleApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::SampleApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/compositor_app/compositor_host.cc b/mojo/examples/compositor_app/compositor_host.cc
new file mode 100644
index 0000000..0cd6fb9
--- /dev/null
+++ b/mojo/examples/compositor_app/compositor_host.cc
@@ -0,0 +1,97 @@
+// 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/begin_frame_args.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,
+ base::MessageLoopProxy::current(),
+ 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::BeginMainFrame(const cc::BeginFrameArgs& args) {
+ // TODO(jamesr): Should use cc's animation system.
+ static const double kDegreesPerSecond = 70.0;
+ double time_in_seconds = (args.frame_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::ApplyViewportDeltas(const gfx::Vector2d& inner_delta,
+ const gfx::Vector2d& outer_delta,
+ float page_scale,
+ float top_controls_delta) {}
+void CompositorHost::ApplyViewportDeltas(const gfx::Vector2d& scroll_delta,
+ float page_scale,
+ float top_controls_delta) {}
+
+void CompositorHost::RequestNewOutputSurface(bool fallback) {
+ tree_->SetOutputSurface(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/mojo/examples/compositor_app/compositor_host.h b/mojo/examples/compositor_app/compositor_host.h
new file mode 100644
index 0000000..3725212
--- /dev/null
+++ b/mojo/examples/compositor_app/compositor_host.h
@@ -0,0 +1,61 @@
+// 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/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 BeginMainFrame(const cc::BeginFrameArgs& args) override;
+ virtual void Layout() override;
+ virtual void ApplyViewportDeltas(const gfx::Vector2d& inner_delta,
+ const gfx::Vector2d& outer_delta,
+ float page_scale,
+ float top_controls_delta) override;
+ virtual void ApplyViewportDeltas(const gfx::Vector2d& scroll_delta,
+ float page_scale,
+ float top_controls_delta) override;
+ virtual void RequestNewOutputSurface(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/mojo/examples/content_handler_demo/BUILD.gn b/mojo/examples/content_handler_demo/BUILD.gn
new file mode 100644
index 0000000..cd8bcbb
--- /dev/null
+++ b/mojo/examples/content_handler_demo/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# GYP version: mojo/mojo_examples.gypi:mojo_content_handler_demo
+shared_library("content_handler_demo") {
+ output_name = "mojo_content_handler_demo"
+
+ sources = [
+ "content_handler_demo.cc"
+ ]
+
+ deps = [
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/services/public/interfaces/content_handler",
+ ]
+}
+
diff --git a/mojo/examples/content_handler_demo/content_handler_demo.cc b/mojo/examples/content_handler_demo/content_handler_demo.cc
new file mode 100644
index 0000000..a98fea9
--- /dev/null
+++ b/mojo/examples/content_handler_demo/content_handler_demo.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 <stdio.h>
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace mojo {
+namespace examples {
+
+class ContentHandlerApp;
+
+class ContentHandlerImpl : public InterfaceImpl<ContentHandler> {
+ public:
+ explicit ContentHandlerImpl(ContentHandlerApp* content_handler_app)
+ : content_handler_app_(content_handler_app) {
+ }
+ virtual ~ContentHandlerImpl() {}
+
+ private:
+ virtual void OnConnect(
+ const mojo::String& url,
+ URLResponsePtr response,
+ InterfaceRequest<ServiceProvider> service_provider) override;
+
+ ContentHandlerApp* content_handler_app_;
+};
+
+class ContentHandlerApp : public ApplicationDelegate {
+ public:
+ ContentHandlerApp() : content_handler_factory_(this) {
+ }
+
+ virtual void Initialize(ApplicationImpl* app) override {}
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(&content_handler_factory_);
+ return true;
+ }
+
+ void PrintResponse(ScopedDataPipeConsumerHandle body) const {
+ for (;;) {
+ char buf[512];
+ uint32_t num_bytes = sizeof(buf);
+ MojoResult result = ReadDataRaw(body.get(), buf, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ Wait(body.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ } else if (result == MOJO_RESULT_OK) {
+ if (fwrite(buf, num_bytes, 1, stdout) != 1) {
+ printf("\nUnexpected error writing to file\n");
+ break;
+ }
+ } else {
+ break;
+ }
+
+ printf("\n>>> EOF <<<\n");
+ }
+ }
+
+ private:
+ InterfaceFactoryImplWithContext<ContentHandlerImpl,
+ ContentHandlerApp> content_handler_factory_;
+};
+
+void ContentHandlerImpl::OnConnect(
+ const mojo::String& url,
+ URLResponsePtr response,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ printf("ContentHandler::OnConnect - url:%s - body follows\n\n",
+ url.To<std::string>().c_str());
+ content_handler_app_->PrintResponse(response->body.Pass());
+}
+
+} // namespace examples
+
+// static
+ApplicationDelegate* ApplicationDelegate::Create() {
+ return new examples::ContentHandlerApp();
+}
+
+} // namespace mojo
diff --git a/mojo/examples/demo_launcher/BUILD.gn b/mojo/examples/demo_launcher/BUILD.gn
new file mode 100644
index 0000000..6ecbbfa
--- /dev/null
+++ b/mojo/examples/demo_launcher/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+assert(use_aura)
+
+# GYP version: mojo/mojo_examples.gypi:mojo_demo_launcher
+shared_library("demo_launcher") {
+ output_name = "mojo_demo_launcher"
+
+ sources = [
+ "demo_launcher.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/view_manager",
+ ]
+}
diff --git a/mojo/examples/demo_launcher/demo_launcher.cc b/mojo/examples/demo_launcher/demo_launcher.cc
new file mode 100644
index 0000000..ec46265
--- /dev/null
+++ b/mojo/examples/demo_launcher/demo_launcher.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 "base/basictypes.h"
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/public/cpp/view_manager/view_manager.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_context.h"
+
+namespace examples {
+
+class DemoLauncher : public mojo::ApplicationDelegate {
+ public:
+ DemoLauncher() {}
+ virtual ~DemoLauncher() {}
+
+ private:
+ virtual void Initialize(mojo::ApplicationImpl* app) override {
+ context_.reset(new mojo::ViewManagerContext(app));
+ context_->Embed("mojo:mojo_window_manager");
+ }
+
+ scoped_ptr<mojo::ViewManagerContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(DemoLauncher);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new examples::DemoLauncher);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/echo/BUILD.gn b/mojo/examples/echo/BUILD.gn
new file mode 100644
index 0000000..0f518b4
--- /dev/null
+++ b/mojo/examples/echo/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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+group("echo") {
+ deps = [
+ ":client",
+ ":service"
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_echo_client
+shared_library("client") {
+ output_name = "mojo_echo_client"
+
+ deps = [
+ ":bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ ]
+
+ sources = [ "echo_client.cc" ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_echo_service
+shared_library("service") {
+ output_name = "mojo_echo_service"
+
+ deps = [
+ ":bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ ]
+
+ sources = [ "echo_service.cc" ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_echo_service_bindings
+mojom("bindings") {
+ sources = [ "echo_service.mojom" ]
+}
diff --git a/mojo/examples/echo/echo_client.cc b/mojo/examples/echo/echo_client.cc
new file mode 100644
index 0000000..2eeae0d
--- /dev/null
+++ b/mojo/examples/echo/echo_client.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 <stdio.h>
+
+#include "mojo/examples/echo/echo_service.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+
+namespace mojo {
+namespace examples {
+
+class ResponsePrinter {
+ public:
+ void Run(const String& value) const {
+ printf("Response: \"%s\"\n", value.get().c_str());
+
+ RunLoop::current()->Quit(); // All done!
+ }
+};
+
+class EchoClientDelegate : public ApplicationDelegate {
+ public:
+ virtual void Initialize(ApplicationImpl* app) override {
+ app->ConnectToService("mojo:mojo_echo_service", &echo_service_);
+
+ echo_service_->EchoString("hello world", ResponsePrinter());
+ }
+
+ private:
+ EchoServicePtr echo_service_;
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::examples::EchoClientDelegate);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/echo/echo_service.cc b/mojo/examples/echo/echo_service.cc
new file mode 100644
index 0000000..5df36ad
--- /dev/null
+++ b/mojo/examples/echo/echo_service.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/examples/echo/echo_service.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+
+namespace mojo {
+namespace examples {
+
+class EchoServiceImpl : public InterfaceImpl<EchoService> {
+ public:
+ virtual void EchoString(const String& value,
+ const Callback<void(String)>& callback) override {
+ callback.Run(value);
+ }
+};
+
+class EchoServiceDelegate : public ApplicationDelegate {
+ public:
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(&echo_service_factory_);
+ return true;
+ }
+
+ private:
+ InterfaceFactoryImpl<EchoServiceImpl> echo_service_factory_;
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::examples::EchoServiceDelegate);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/echo/echo_service.mojom b/mojo/examples/echo/echo_service.mojom
new file mode 100644
index 0000000..6e23c6d
--- /dev/null
+++ b/mojo/examples/echo/echo_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.examples {
+
+interface EchoService {
+ EchoString(string? value) => (string? value);
+};
+
+}
diff --git a/mojo/examples/embedded_app/BUILD.gn b/mojo/examples/embedded_app/BUILD.gn
new file mode 100644
index 0000000..3e82cbb
--- /dev/null
+++ b/mojo/examples/embedded_app/BUILD.gn
@@ -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.
+
+shared_library("embedded_app") {
+ output_name = "mojo_embedded_app"
+
+ sources = [
+ "embedded_app.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/examples/window_manager:bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/navigation",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ "//url",
+ ]
+}
diff --git a/mojo/examples/embedded_app/DEPS b/mojo/examples/embedded_app/DEPS
new file mode 100644
index 0000000..fe1d98e
--- /dev/null
+++ b/mojo/examples/embedded_app/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/events",
+]
diff --git a/mojo/examples/embedded_app/embedded_app.cc b/mojo/examples/embedded_app/embedded_app.cc
new file mode 100644
index 0000000..da51fae
--- /dev/null
+++ b/mojo/examples/embedded_app/embedded_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/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/interface_factory_impl.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_client_factory.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"
+
+namespace mojo {
+namespace examples {
+
+const SkColor kColors[] = {SK_ColorYELLOW, SK_ColorRED, SK_ColorGREEN,
+ SK_ColorMAGENTA};
+
+struct Window {
+ Window(View* root, scoped_ptr<ServiceProvider> embedder_service_provider)
+ : root(root),
+ embedder_service_provider(embedder_service_provider.Pass()) {}
+ View* root;
+ scoped_ptr<ServiceProvider> embedder_service_provider;
+};
+
+class EmbeddedApp
+ : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public ViewObserver {
+ public:
+ EmbeddedApp() { url::AddStandardScheme("mojo"); }
+ virtual ~EmbeddedApp() {}
+
+ private:
+
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ root->AddObserver(this);
+ windows_[root->id()] = new Window(root, imported_services.Pass());
+ root->SetColor(kColors[next_color_++ % arraysize(kColors)]);
+ }
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {
+ base::MessageLoop::current()->Quit();
+ }
+
+ // Overridden from ViewObserver:
+ virtual void OnViewDestroyed(View* view) override {
+ DCHECK(windows_.find(view->id()) != windows_.end());
+ windows_.erase(view->id());
+ }
+ virtual void OnViewInputEvent(View* view, const EventPtr& event) override {
+ if (event->action == EVENT_TYPE_MOUSE_RELEASED) {
+ if (event->flags & EVENT_FLAGS_LEFT_MOUSE_BUTTON) {
+ URLRequestPtr request(URLRequest::New());
+ request->url = "http://www.aaronboodman.com/z_dropbox/test.html";
+ NavigatorHostPtr navigator_host;
+ ConnectToService(windows_[view->id()]->embedder_service_provider.get(),
+ &navigator_host);
+ navigator_host->RequestNavigate(TARGET_SOURCE_NODE, request.Pass());
+ }
+ }
+ }
+
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ typedef std::map<Id, Window*> WindowMap;
+ WindowMap windows_;
+
+ int next_color_;
+
+ DISALLOW_COPY_AND_ASSIGN(EmbeddedApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::EmbeddedApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/keyboard/BUILD.gn b/mojo/examples/keyboard/BUILD.gn
new file mode 100644
index 0000000..fb3e726
--- /dev/null
+++ b/mojo/examples/keyboard/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+assert(use_aura)
+
+# GYP version: mojo/mojo_examples.gypi:mojo_keyboard
+shared_library("keyboard") {
+ output_name = "mojo_keyboard"
+
+ sources = [
+ "keyboard_delegate.h",
+ "keyboard_view.cc",
+ "keyboard_view.h",
+ "keyboard.cc",
+ "keys.cc",
+ "keys.h",
+ ]
+
+ deps = [
+ ":bindings",
+ "//base",
+ "//cc",
+ "//mojo/application",
+ "//mojo/aura",
+ "//mojo/common",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/navigation",
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/views",
+ "//third_party/icu",
+ "//ui/aura",
+ "//ui/base",
+ "//ui/compositor",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/resources",
+ "//ui/resources:ui_test_pak",
+ "//ui/views",
+ "//url",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_keyboard_bindings
+mojom("bindings") {
+ sources = [ "keyboard.mojom" ]
+}
+
diff --git a/mojo/examples/keyboard/DEPS b/mojo/examples/keyboard/DEPS
new file mode 100644
index 0000000..02dd6ea
--- /dev/null
+++ b/mojo/examples/keyboard/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+skia/ext",
+ "+ui/aura",
+ "+ui/base/hit_test.h",
+ "+ui/compositor",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/views",
+]
diff --git a/mojo/examples/keyboard/keyboard.cc b/mojo/examples/keyboard/keyboard.cc
new file mode 100644
index 0000000..8eae684
--- /dev/null
+++ b/mojo/examples/keyboard/keyboard.cc
@@ -0,0 +1,152 @@
+// Copyright 2014 The Chromium 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/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/keyboard/keyboard.mojom.h"
+#include "mojo/examples/keyboard/keyboard_delegate.h"
+#include "mojo/examples/keyboard/keyboard_view.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.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_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.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/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace examples {
+
+class Keyboard;
+
+class KeyboardServiceImpl : public InterfaceImpl<KeyboardService> {
+ public:
+ explicit KeyboardServiceImpl(Keyboard* keyboard);
+ virtual ~KeyboardServiceImpl() {}
+
+ // KeyboardService:
+ virtual void SetTarget(uint32_t view_id) override;
+
+ private:
+ Keyboard* keyboard_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardServiceImpl);
+};
+
+class Keyboard : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public KeyboardDelegate {
+ public:
+ Keyboard()
+ : keyboard_service_factory_(this),
+ view_manager_(NULL),
+ keyboard_service_(NULL),
+ target_(0) {}
+
+ virtual ~Keyboard() {
+ }
+
+ void set_target(Id id) { target_ = id; }
+
+ void set_keyboard_service(KeyboardServiceImpl* keyboard) {
+ keyboard_service_ = keyboard;
+ }
+
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ views_init_.reset(new ViewsInit);
+ connection->AddService(view_manager_client_factory_.get());
+ connection->AddService(&keyboard_service_factory_);
+ return true;
+ }
+
+ void CreateWidget(View* view) {
+ views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView;
+ widget_delegate->GetContentsView()->AddChildView(new KeyboardView(this));
+ widget_delegate->GetContentsView()->SetLayoutManager(new views::FillLayout);
+
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.native_widget = new NativeWidgetViewManager(widget, view);
+ params.delegate = widget_delegate;
+ params.bounds = gfx::Rect(view->bounds().width(), view->bounds().height());
+ widget->Init(params);
+ widget->Show();
+ }
+
+ // ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ // TODO: deal with OnEmbed() being invoked multiple times.
+ view_manager_ = view_manager;
+ CreateWidget(root);
+ }
+ virtual void OnViewManagerDisconnected(
+ ViewManager* view_manager) override {
+ DCHECK_EQ(view_manager_, view_manager);
+ view_manager_ = NULL;
+ base::MessageLoop::current()->Quit();
+ }
+
+ // KeyboardDelegate:
+ virtual void OnKeyPressed(int key_code, int event_flags) override {
+ if (!target_)
+ return;
+ keyboard_service_->client()->OnKeyboardEvent(target_, key_code,
+ event_flags);
+ }
+
+ InterfaceFactoryImplWithContext<KeyboardServiceImpl, Keyboard>
+ keyboard_service_factory_;
+
+ scoped_ptr<ViewsInit> views_init_;
+
+ ViewManager* view_manager_;
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ KeyboardServiceImpl* keyboard_service_;
+
+ Id target_;
+
+ DISALLOW_COPY_AND_ASSIGN(Keyboard);
+};
+
+KeyboardServiceImpl::KeyboardServiceImpl(Keyboard* keyboard)
+ : keyboard_(keyboard) {
+ keyboard_->set_keyboard_service(this);
+}
+
+void KeyboardServiceImpl::SetTarget(uint32_t view_id) {
+ keyboard_->set_target(view_id);
+}
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::Keyboard);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/keyboard/keyboard.mojom b/mojo/examples/keyboard/keyboard.mojom
new file mode 100644
index 0000000..9377898
--- /dev/null
+++ b/mojo/examples/keyboard/keyboard.mojom
@@ -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.
+
+module mojo {
+
+[Client=KeyboardClient]
+interface KeyboardService {
+ // Sets the view keyboard events are to go to.
+ SetTarget(uint32 view_id);
+};
+
+interface KeyboardClient {
+ // Invoked when the user interacts with the keyboard. |code| is a key code
+ // |flags| is a bitmask of ui::EventFlags.
+ OnKeyboardEvent(uint32 view_id, int32 code, int32 flags);
+};
+
+}
diff --git a/mojo/examples/keyboard/keyboard_delegate.h b/mojo/examples/keyboard/keyboard_delegate.h
new file mode 100644
index 0000000..bf35fc6
--- /dev/null
+++ b/mojo/examples/keyboard/keyboard_delegate.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_EXAMPLES_KEYBOARD_KEYBOARD_DELEGATE_H_
+#define MOJO_EXAMPLES_KEYBOARD_KEYBOARD_DELEGATE_H_
+
+#include "base/basictypes.h"
+
+namespace mojo {
+namespace examples {
+
+class KeyboardDelegate {
+ public:
+ KeyboardDelegate() {}
+
+ virtual void OnKeyPressed(int key_code, int event_flags) = 0;
+
+ protected:
+ virtual ~KeyboardDelegate() {}
+};
+
+} // namespace examples
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_KEYBOARD_KEYBOARD_DELEGATE_H_
diff --git a/mojo/examples/keyboard/keyboard_view.cc b/mojo/examples/keyboard/keyboard_view.cc
new file mode 100644
index 0000000..3a0d71a
--- /dev/null
+++ b/mojo/examples/keyboard/keyboard_view.cc
@@ -0,0 +1,256 @@
+// Copyright 2014 The Chromium 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/keyboard/keyboard_view.h"
+
+#include <algorithm>
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/examples/keyboard/keyboard_delegate.h"
+#include "mojo/examples/keyboard/keys.h"
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/button/label_button_border.h"
+
+namespace mojo {
+namespace examples {
+
+namespace {
+
+const int kHorizontalPadding = 6;
+const int kVerticalPadding = 8;
+
+base::string16 GetDisplayString(int key_code, int flags) {
+ return base::string16(1, ui::GetCharacterFromKeyCode(
+ static_cast<ui::KeyboardCode>(key_code), flags));
+}
+
+// Returns a font that fits in the space provided. |text| is used as a basis
+// for determing the size.
+gfx::FontList CalculateFont(int width, int height, const base::string16& text) {
+ gfx::FontList font;
+ gfx::FontList last_font;
+ while (gfx::Canvas::GetStringWidth(text, font) < width &&
+ font.GetHeight() < height) {
+ last_font = font;
+ font = font.DeriveWithSizeDelta(2);
+ }
+ return last_font;
+}
+
+// Returns the total number of keys in |rows|.
+int NumKeys(const std::vector<const Row*>& rows) {
+ int result = 0;
+ for (size_t i = 0; i < rows.size(); ++i)
+ result += static_cast<int>(rows[i]->num_keys);
+ return result;
+}
+
+} // namespace
+
+KeyboardView::KeyboardView(KeyboardDelegate* delegate)
+ : delegate_(delegate),
+ max_keys_in_row_(0),
+ keyboard_layout_(KEYBOARD_LAYOUT_ALPHA) {
+ set_background(views::Background::CreateSolidBackground(SK_ColorBLACK));
+ SetRows(GetQWERTYRows());
+}
+
+KeyboardView::~KeyboardView() {
+}
+
+void KeyboardView::Layout() {
+ if (width() == 0 || height() == 0 || rows_.empty() ||
+ last_layout_size_ == bounds().size())
+ return;
+
+ last_layout_size_ = bounds().size();
+
+ const int button_width =
+ (width() - (max_keys_in_row_ - 1) * kHorizontalPadding) /
+ max_keys_in_row_;
+ const int button_height =
+ (height() - (static_cast<int>(rows_.size() - 1) * kVerticalPadding)) /
+ static_cast<int>(rows_.size());
+ const int initial_x = (width() - button_width * max_keys_in_row_ -
+ kHorizontalPadding * (max_keys_in_row_ - 1)) / 2;
+ for (size_t i = 0; i < rows_.size(); ++i) {
+ LayoutRow(*(rows_[i]), static_cast<int>(i), initial_x, button_width,
+ button_height);
+ }
+
+ views::LabelButtonBorder border(views::Button::STYLE_TEXTBUTTON);
+ gfx::Insets insets(border.GetInsets());
+ gfx::FontList font = CalculateFont(button_width - insets.width(),
+ button_height - insets.height(),
+ base::ASCIIToUTF16("W"));
+ gfx::FontList special_font = CalculateFont(button_width - insets.width(),
+ button_height - insets.height(),
+ base::ASCIIToUTF16("?123"));
+ button_font_ = font;
+ ResetFonts(font, special_font);
+}
+
+void KeyboardView::SetLayout(KeyboardLayout keyboard_layout) {
+ if (keyboard_layout_ == keyboard_layout)
+ return;
+
+ keyboard_layout_ = keyboard_layout;
+ last_layout_size_ = gfx::Size();
+ if (keyboard_layout_ == KEYBOARD_LAYOUT_NUMERIC)
+ SetRows(GetNumericRows());
+ else
+ SetRows(GetQWERTYRows());
+ Layout();
+ SchedulePaint();
+}
+
+void KeyboardView::LayoutRow(const Row& row,
+ int row_index,
+ int initial_x,
+ int button_width,
+ int button_height) {
+ int x = initial_x + row.padding * (button_width + kHorizontalPadding);
+ const int y = row_index * (button_height + kVerticalPadding);
+ for (size_t i = 0; i < row.num_keys; ++i) {
+ views::View* button = GetButton(row_index, static_cast<int>(i));
+ int actual_width = button_width;
+ if (row.keys[i].size > 1) {
+ actual_width = (button_width + kHorizontalPadding) *
+ row.keys[i].size - kHorizontalPadding;
+ }
+ button->SetBounds(x, y, actual_width, button_height);
+ x += actual_width + kHorizontalPadding;
+ }
+}
+
+void KeyboardView::SetRows(const std::vector<const Row*>& rows) {
+ const int num_keys = NumKeys(rows);
+ while (child_count() > num_keys)
+ delete child_at(child_count() - 1);
+ for (int i = child_count(); i < num_keys; ++i)
+ AddChildView(CreateButton());
+
+ last_layout_size_ = gfx::Size();
+
+ rows_ = rows;
+
+ max_keys_in_row_ = 0;
+ for (size_t i = 0; i < rows_.size(); ++i) {
+ max_keys_in_row_ = std::max(max_keys_in_row_,
+ static_cast<int>(rows_[i]->num_keys));
+ ConfigureButtonsInRow(static_cast<int>(i), *rows_[i]);
+ }
+}
+
+void KeyboardView::ConfigureButtonsInRow(int row_index, const Row& row) {
+ for (size_t i = 0; i < row.num_keys; ++i) {
+ views::LabelButton* button = GetButton(row_index, static_cast<int>(i));
+ const Key& key(row.keys[i]);
+ switch (key.display_code) {
+ case SPECIAL_KEY_SHIFT:
+ // TODO: need image.
+ button->SetText(base::string16());
+ break;
+ case SPECIAL_KEY_NUMERIC:
+ button->SetText(base::ASCIIToUTF16("?123"));
+ break;
+ case SPECIAL_KEY_ALPHA:
+ button->SetText(base::ASCIIToUTF16("ABC"));
+ break;
+ default:
+ button->SetText(GetDisplayString(key.display_code,
+ key.event_flags | event_flags()));
+ break;
+ }
+ button->SetState(views::Button::STATE_NORMAL);
+ }
+}
+
+views::View* KeyboardView::CreateButton() {
+ views::LabelButton* button = new views::LabelButton(this, base::string16());
+ button->SetTextColor(views::Button::STATE_NORMAL, SK_ColorWHITE);
+ button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ button->set_background(views::Background::CreateSolidBackground(78, 78, 78));
+ button->SetFontList(button_font_);
+ // button->SetHaloColor(SK_ColorBLACK);
+ // Turn off animations as we reuse buttons in different layouts. If we didn't
+ // do this and you click a button to change the layout then the button you
+ // clicked on would animate the transition even though it may now represent a
+ // different key.
+ button->SetAnimationDuration(0);
+ return button;
+}
+
+views::LabelButton* KeyboardView::GetButton(int row, int column) {
+ int offset = column;
+ for (int i = 0; i < row; ++i)
+ offset += static_cast<int>(rows_[i]->num_keys);
+ return static_cast<views::LabelButton*>(child_at(offset));
+}
+
+const Key& KeyboardView::GetKeyForButton(views::Button* button) const {
+ int index = GetIndexOf(button);
+ DCHECK_NE(-1, index);
+ int row = 0;
+ while (index >= static_cast<int>(rows_[row]->num_keys)) {
+ index -= static_cast<int>(rows_[row]->num_keys);
+ row++;
+ }
+ return rows_[row]->keys[index];
+}
+
+void KeyboardView::ResetFonts(const gfx::FontList& button_font,
+ const gfx::FontList& special_font) {
+ for (size_t i = 0; i < rows_.size(); ++i) {
+ for (size_t j = 0; j < rows_[i]->num_keys; ++j) {
+ views::LabelButton* button = GetButton(static_cast<int>(i),
+ static_cast<int>(j));
+ const Key& key(GetKeyForButton(button));
+ switch (key.display_code) {
+ case SPECIAL_KEY_ALPHA:
+ case SPECIAL_KEY_NUMERIC:
+ button->SetFontList(special_font);
+ break;
+ default:
+ button->SetFontList(button_font);
+ break;
+ }
+ }
+ }
+}
+
+void KeyboardView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ const Key& key(GetKeyForButton(sender));
+ switch (key.display_code) {
+ case SPECIAL_KEY_SHIFT:
+ SetLayout((keyboard_layout_ == KEYBOARD_LAYOUT_SHIFT) ?
+ KEYBOARD_LAYOUT_ALPHA : KEYBOARD_LAYOUT_SHIFT);
+ return;
+ case SPECIAL_KEY_ALPHA:
+ SetLayout(KEYBOARD_LAYOUT_ALPHA);
+ return;
+ case SPECIAL_KEY_NUMERIC:
+ SetLayout(KEYBOARD_LAYOUT_NUMERIC);
+ return;
+ default:
+ break;
+ }
+
+ // Windows isn't happy if we pass in the flags used to get the display string.
+#if defined(OS_WIN)
+ int key_event_flags = 0;
+#else
+ int key_event_flags = key.event_flags;
+#endif
+ delegate_->OnKeyPressed(key.keyboard_code(), key_event_flags | event_flags());
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/keyboard/keyboard_view.h b/mojo/examples/keyboard/keyboard_view.h
new file mode 100644
index 0000000..4086a2d
--- /dev/null
+++ b/mojo/examples/keyboard/keyboard_view.h
@@ -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.
+
+#ifndef MOJO_EXAMPLES_KEYBOARD_KEYBOARD_VIEW_H_
+#define MOJO_EXAMPLES_KEYBOARD_KEYBOARD_VIEW_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "ui/gfx/font_list.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/view.h"
+
+namespace views {
+class LabelButton;
+}
+
+namespace mojo {
+namespace examples {
+
+class KeyboardDelegate;
+struct Key;
+struct Row;
+
+// Shows a keyboard the user can interact with. The delegate is notified any
+// time the user presses a button.
+class KeyboardView : public views::View, public views::ButtonListener {
+ public:
+ explicit KeyboardView(KeyboardDelegate* delegate);
+ virtual ~KeyboardView();
+
+ // views::View:
+ virtual void Layout() override;
+
+ private:
+ // The type of keys that are shown.
+ enum KeyboardLayout {
+ KEYBOARD_LAYOUT_ALPHA,
+
+ // Uppercase characters.
+ KEYBOARD_LAYOUT_SHIFT,
+
+ // Numeric characters.
+ KEYBOARD_LAYOUT_NUMERIC,
+ };
+
+ int event_flags() const {
+ return (keyboard_layout_ == KEYBOARD_LAYOUT_SHIFT) ?
+ ui::EF_SHIFT_DOWN : ui::EF_NONE;
+ }
+
+ void SetLayout(KeyboardLayout layout);
+
+ // Lays out the buttons for the specified row.
+ void LayoutRow(const Row& row,
+ int row_index,
+ int initial_x,
+ int button_width,
+ int button_height);
+
+ // Sets the rows to show.
+ void SetRows(const std::vector<const Row*>& rows);
+
+ // Configures the button in a row.
+ void ConfigureButtonsInRow(int row_index, const Row& row);
+
+ // Creates a new button.
+ views::View* CreateButton();
+
+ // Returns the button corresponding to a key at the specified row/column.
+ views::LabelButton* GetButton(int row, int column);
+
+ const Key& GetKeyForButton(views::Button* button) const;
+
+ // Reset the fonts of all the buttons. |special_font| is used for the buttons
+ // that toggle the layout.
+ void ResetFonts(const gfx::FontList& button_font,
+ const gfx::FontList& special_font);
+
+ // views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) override;
+
+ KeyboardDelegate* delegate_;
+
+ // Maximium number of keys in a row. Determined from |rows_|.
+ int max_keys_in_row_;
+
+ KeyboardLayout keyboard_layout_;
+
+ std::vector<const Row*> rows_;
+
+ gfx::Size last_layout_size_;
+
+ gfx::FontList button_font_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardView);
+};
+
+} // namespace examples
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_KEYBOARD_KEYBOARD_VIEW_H_
diff --git a/mojo/examples/keyboard/keys.cc b/mojo/examples/keyboard/keys.cc
new file mode 100644
index 0000000..adf9094
--- /dev/null
+++ b/mojo/examples/keyboard/keys.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/examples/keyboard/keys.h"
+
+#include "base/macros.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace mojo {
+namespace examples {
+namespace {
+
+const Key kQWERTYKeysRow1[] =
+{
+ { ui::VKEY_Q, 1, 0, 'q' },
+ { ui::VKEY_W, 1, 0, 'w' },
+ { ui::VKEY_E, 1, 0, 'e' },
+ { ui::VKEY_R, 1, 0, 'r' },
+ { ui::VKEY_T, 1, 0, 't' },
+ { ui::VKEY_Y, 1, 0, 'y' },
+ { ui::VKEY_U, 1, 0, 'u' },
+ { ui::VKEY_I, 1, 0, 'i' },
+ { ui::VKEY_O, 1, 0, 'o' },
+ { ui::VKEY_P, 1, 0, 'p' },
+};
+
+const Key kQWERTYKeysRow2[] =
+{
+ { ui::VKEY_A, 1, 0, 'a' },
+ { ui::VKEY_S, 1, 0, 's' },
+ { ui::VKEY_D, 1, 0, 'd' },
+ { ui::VKEY_F, 1, 0, 'f' },
+ { ui::VKEY_G, 1, 0, 'g' },
+ { ui::VKEY_H, 1, 0, 'h' },
+ { ui::VKEY_J, 1, 0, 'j' },
+ { ui::VKEY_K, 1, 0, 'k' },
+ { ui::VKEY_L, 1, 0, 'l' },
+};
+
+const Key kQWERTYKeysRow3[] =
+{
+ { SPECIAL_KEY_SHIFT, 1.5, 0, 0 },
+ { ui::VKEY_Z, 1, 0, 'z' },
+ { ui::VKEY_X, 1, 0, 'x' },
+ { ui::VKEY_C, 1, 0, 'c' },
+ { ui::VKEY_V, 1, 0, 'v' },
+ { ui::VKEY_B, 1, 0, 'b' },
+ { ui::VKEY_N, 1, 0, 'n' },
+ { ui::VKEY_M, 1, 0, 'm' },
+ { ui::VKEY_BACK, 1.5, 0, 0 },
+};
+
+const Key kQWERTYKeysRow4[] =
+{
+ { SPECIAL_KEY_NUMERIC, 1.5, 0, 0 },
+ { ui::VKEY_DIVIDE, 1, 0, '/' },
+ { ui::VKEY_SPACE, 5, 0, ' ' },
+ { ui::VKEY_DECIMAL, 1, 0, '.' },
+ { ui::VKEY_RETURN, 1.5, 0, 0 },
+};
+
+const Row kQWERTYRow1 = {
+ 0,
+ kQWERTYKeysRow1,
+ arraysize(kQWERTYKeysRow1),
+};
+
+const Row kQWERTYRow2 = {
+ .5,
+ kQWERTYKeysRow2,
+ arraysize(kQWERTYKeysRow2),
+};
+
+const Row kQWERTYRow3 = {
+ 0,
+ kQWERTYKeysRow3,
+ arraysize(kQWERTYKeysRow3),
+};
+
+const Row kQWERTYRow4 = {
+ 0,
+ kQWERTYKeysRow4,
+ arraysize(kQWERTYKeysRow4),
+};
+
+const Key kNumericKeysRow1[] =
+{
+ { ui::VKEY_1, 1, 0, 0 },
+ { ui::VKEY_2, 1, 0, 0 },
+ { ui::VKEY_3, 1, 0, 0 },
+ { ui::VKEY_4, 1, 0, 0 },
+ { ui::VKEY_5, 1, 0, 0 },
+ { ui::VKEY_6, 1, 0, 0 },
+ { ui::VKEY_7, 1, 0, 0 },
+ { ui::VKEY_8, 1, 0, 0 },
+ { ui::VKEY_9, 1, 0, 0 },
+ { ui::VKEY_0, 1, 0, 0 },
+};
+
+const Key kNumericKeysRow2[] =
+{
+ // @#$%&-+()
+ { ui::VKEY_2, 1, ui::EF_SHIFT_DOWN, '@' },
+ { ui::VKEY_3, 1, ui::EF_SHIFT_DOWN, '#' },
+ { ui::VKEY_4, 1, ui::EF_SHIFT_DOWN, '$' },
+ { ui::VKEY_5, 1, ui::EF_SHIFT_DOWN, '%' },
+ { ui::VKEY_7, 1, ui::EF_SHIFT_DOWN, '&' },
+ { ui::VKEY_SUBTRACT, 1, 0, '-' },
+ { ui::VKEY_ADD, 1, 0, '+' },
+ { ui::VKEY_9, 1, ui::EF_SHIFT_DOWN, '(' },
+ { ui::VKEY_0, 1, ui::EF_SHIFT_DOWN, ')' },
+};
+
+const Key kNumericKeysRow3[] =
+{
+ // *"':;!? backspace
+ { ui::VKEY_MULTIPLY, 1, 0, '*' },
+ { ui::VKEY_OEM_7, 1, ui::EF_SHIFT_DOWN, '"' },
+ { ui::VKEY_OEM_7, 1, 0, '\'' },
+ { ui::VKEY_OEM_1, 1, ui::EF_SHIFT_DOWN, ':' },
+ { ui::VKEY_OEM_1, 1, 0, ';' },
+ { ui::VKEY_1, 1, ui::EF_SHIFT_DOWN, '!' },
+ { ui::VKEY_OEM_2, 1, ui::EF_SHIFT_DOWN, '?' },
+ { ui::VKEY_BACK, 1.5, 0, 0 },
+};
+
+const Key kNumericKeysRow4[] =
+{
+ // ABC _ / space (3) ,.enter
+ { SPECIAL_KEY_ALPHA, 1.5, 0, 0 },
+ { ui::VKEY_OEM_MINUS, 1, ui::EF_SHIFT_DOWN, '_' },
+ { ui::VKEY_OEM_2, 1, 0, '/' },
+ { ui::VKEY_SPACE, 3, 0, ' ' },
+ { ui::VKEY_OEM_COMMA, 1, 0, ',' },
+ { ui::VKEY_OEM_PERIOD, 1, 0, '.' },
+ { ui::VKEY_RETURN, 1.5, 0, 0 },
+};
+
+const Row kNumericRow1 = {
+ 0,
+ kNumericKeysRow1,
+ arraysize(kNumericKeysRow1),
+};
+
+const Row kNumericRow2 = {
+ .5,
+ kNumericKeysRow2,
+ arraysize(kNumericKeysRow2),
+};
+
+const Row kNumericRow3 = {
+ 1.5,
+ kNumericKeysRow3,
+ arraysize(kNumericKeysRow3),
+};
+
+const Row kNumericRow4 = {
+ 0,
+ kNumericKeysRow4,
+ arraysize(kNumericKeysRow4),
+};
+
+} // namespace
+
+std::vector<const Row*> GetQWERTYRows() {
+ std::vector<const Row*> rows;
+ rows.push_back(&kQWERTYRow1);
+ rows.push_back(&kQWERTYRow2);
+ rows.push_back(&kQWERTYRow3);
+ rows.push_back(&kQWERTYRow4);
+ return rows;
+}
+
+std::vector<const Row*> GetNumericRows() {
+ std::vector<const Row*> rows;
+ rows.push_back(&kNumericRow1);
+ rows.push_back(&kNumericRow2);
+ rows.push_back(&kNumericRow3);
+ rows.push_back(&kNumericRow4);
+ return rows;
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/keyboard/keys.h b/mojo/examples/keyboard/keys.h
new file mode 100644
index 0000000..c09f856
--- /dev/null
+++ b/mojo/examples/keyboard/keys.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_KEYBOARD_KEYS_H_
+#define MOJO_EXAMPLES_KEYBOARD_KEYS_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace mojo {
+namespace examples {
+
+enum SpecialKey {
+ SPECIAL_KEY_SHIFT = -1,
+ SPECIAL_KEY_NUMERIC = -2,
+ SPECIAL_KEY_ALPHA = -3,
+};
+
+struct Key {
+ int keyboard_code() const {
+ // Handling of keycodes differs between in windows and others.
+#if defined(OS_WIN)
+ return generated_code ? generated_code : display_code;
+#else
+ return display_code;
+#endif
+ }
+
+ // Code used to get the value to display in the UI. This is either a
+ // KeyboardCode or a SpecialKey.
+ int display_code;
+
+ // How much space (as a percentage) the key is to take up.
+ float size;
+
+ // Any ui::EventFlags that are required to produce the key.
+ int event_flags;
+
+ // If non-zero KeyboardCode to generate. If 0 use the |display_code|.
+ int generated_code;
+};
+
+struct Row {
+ float padding;
+ const Key* keys;
+ size_t num_keys;
+};
+
+// Returns the rows for a qwerty style keyboard. The returned values are owned
+// by this object and should not be deleted.
+std::vector<const Row*> GetQWERTYRows();
+
+// Returns the rows for a numeric keyboard. The returned values are owned
+// by this object and should not be deleted.
+std::vector<const Row*> GetNumericRows();
+
+} // namespace examples
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_KEYBOARD_KEYS_H_
diff --git a/mojo/examples/media_viewer/BUILD.gn b/mojo/examples/media_viewer/BUILD.gn
new file mode 100644
index 0000000..b17d2d3
--- /dev/null
+++ b/mojo/examples/media_viewer/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+if (use_aura) {
+
+# GYP version: mojo/mojo_examples.gypi:mojo_media_viewer
+shared_library("media_viewer") {
+ output_name = "mojo_media_viewer"
+
+ sources = [
+ "media_viewer.cc"
+ ]
+
+ deps = [
+ ":bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/navigation",
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/views",
+ "//skia",
+ "//ui/gfx/geometry",
+ "//ui/views",
+ ]
+}
+
+} # use_aura
+
+# GYP version: mojo/mojo_examples.gypi:mojo_media_viewer_bindings
+mojom("bindings") {
+ sources = [
+ "media_viewer.mojom",
+ ]
+}
diff --git a/mojo/examples/media_viewer/DEPS b/mojo/examples/media_viewer/DEPS
new file mode 100644
index 0000000..44ca77e
--- /dev/null
+++ b/mojo/examples/media_viewer/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+skia/ext",
+ "+third_party/skia/include",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/views",
+]
diff --git a/mojo/examples/media_viewer/media_viewer.cc b/mojo/examples/media_viewer/media_viewer.cc
new file mode 100644
index 0000000..33cf200
--- /dev/null
+++ b/mojo/examples/media_viewer/media_viewer.cc
@@ -0,0 +1,321 @@
+// Copyright 2014 The Chromium 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 <map>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/media_viewer/media_viewer.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/bindings/interface_impl.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_client_factory.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 "skia/ext/platform_canvas.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/painter.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace mojo {
+namespace examples {
+
+class MediaViewer;
+
+class CustomButtonBorder: public views::Border {
+ public:
+ CustomButtonBorder()
+ : normal_painter_(CreatePainter(SkColorSetRGB(0x80, 0x80, 0x80),
+ SkColorSetRGB(0xC0, 0xC0, 0xC0))),
+ hot_painter_(CreatePainter(SkColorSetRGB(0xA0, 0xA0, 0xA0),
+ SkColorSetRGB(0xD0, 0xD0, 0xD0))),
+ pushed_painter_(CreatePainter(SkColorSetRGB(0x80, 0x80, 0x80),
+ SkColorSetRGB(0x90, 0x90, 0x90))),
+ insets_(2, 6, 2, 6) {
+ }
+ virtual ~CustomButtonBorder() {}
+
+ private:
+ // Overridden from views::Border:
+ virtual void Paint(const views::View& view, gfx::Canvas* canvas) override {
+ const views::LabelButton* button =
+ static_cast<const views::LabelButton*>(&view);
+ views::Button::ButtonState state = button->state();
+
+ views::Painter* painter = normal_painter_.get();
+ if (state == views::Button::STATE_HOVERED) {
+ painter = hot_painter_.get();
+ } else if (state == views::Button::STATE_PRESSED) {
+ painter = pushed_painter_.get();
+ }
+ painter->Paint(canvas, view.size());
+ }
+
+ virtual gfx::Insets GetInsets() const override {
+ return insets_;
+ }
+
+ virtual gfx::Size GetMinimumSize() const override {
+ gfx::Size size;
+ if (normal_painter_)
+ size.SetToMax(normal_painter_->GetMinimumSize());
+ if (hot_painter_)
+ size.SetToMax(hot_painter_->GetMinimumSize());
+ if (pushed_painter_)
+ size.SetToMax(pushed_painter_->GetMinimumSize());
+ return size;
+ }
+
+ scoped_ptr<views::Painter> CreatePainter(SkColor border, SkColor background) {
+ skia::RefPtr<SkCanvas> canvas(skia::AdoptRef(skia::CreatePlatformCanvas(
+ 64, 64, false)));
+ SkPaint paint;
+ paint.setColor(background);
+ canvas->drawRoundRect(SkRect::MakeWH(63, 63), 2, 2, paint);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(border);
+ canvas->drawRoundRect(SkRect::MakeWH(63, 63), 2, 2, paint);
+
+ return scoped_ptr<views::Painter>(
+ views::Painter::CreateImagePainter(
+ gfx::ImageSkia::CreateFrom1xBitmap(
+ skia::GetTopDevice(*canvas)->accessBitmap(true)),
+ gfx::Insets(5, 5, 5, 5)));
+ }
+
+ scoped_ptr<views::Painter> normal_painter_;
+ scoped_ptr<views::Painter> hot_painter_;
+ scoped_ptr<views::Painter> pushed_painter_;
+
+ gfx::Insets insets_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomButtonBorder);
+};
+
+class ControlPanel : public views::ButtonListener {
+ public:
+ enum ControlType {
+ CONTROL_ZOOM_IN,
+ CONTROL_ACTUAL_SIZE,
+ CONTROL_ZOOM_OUT,
+ CONTROL_COUNT,
+ };
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual void ButtonPressed(ControlType type) = 0;
+ };
+
+ ControlPanel(Delegate* delegate) : delegate_(delegate), buttons_() {}
+
+ virtual ~ControlPanel() {}
+
+ void Initialize(View* view) {
+ const char* kNames[] = { "Zoom In", "Actual Size", "Zoom Out" };
+
+ views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView;
+
+ widget_delegate->GetContentsView()->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 5, 2, 5));
+
+ widget_delegate->GetContentsView()->set_background(
+ views::Background::CreateSolidBackground(SK_ColorLTGRAY));
+
+ for (int type = 0; type < CONTROL_COUNT; ++type) {
+ views::Button* button = new views::LabelButton(
+ this, base::ASCIIToUTF16(kNames[type]));
+ button->SetBorder(scoped_ptr<views::Border>(new CustomButtonBorder));
+ buttons_[type] = button;
+ widget_delegate->GetContentsView()->AddChildView(button);
+ }
+
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.native_widget = new NativeWidgetViewManager(widget, view);
+ params.delegate = widget_delegate;
+ params.bounds = gfx::Rect(view->bounds().width(), view->bounds().height());
+ params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
+ widget->Init(params);
+ widget->Show();
+ }
+
+ private:
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) override {
+ for (int i = 0; i < CONTROL_COUNT; ++i) {
+ if (sender == buttons_[i]) {
+ delegate_->ButtonPressed(static_cast<ControlType>(i));
+ return;
+ }
+ }
+ }
+
+ Delegate* delegate_;
+ views::Button* buttons_[CONTROL_COUNT];
+
+ DISALLOW_COPY_AND_ASSIGN(ControlPanel);
+};
+
+class MediaViewer
+ : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public ControlPanel::Delegate,
+ public ViewObserver {
+ public:
+ MediaViewer()
+ : app_(NULL),
+ view_manager_(NULL),
+ root_view_(NULL),
+ control_view_(NULL),
+ content_view_(NULL),
+ control_panel_(this) {
+ handler_map_["image/png"] = "mojo:mojo_png_viewer";
+ }
+
+ virtual ~MediaViewer() {
+ if (root_view_)
+ root_view_->RemoveObserver(this);
+ }
+
+ private:
+ typedef std::map<std::string, std::string> HandlerMap;
+
+
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ app_ = app;
+ views_init_.reset(new ViewsInit);
+ }
+
+ virtual bool ConfigureIncomingConnection(ApplicationConnection* connection)
+ override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ void LayoutViews() {
+ View* root = content_view_->parent();
+ gfx::Rect control_bounds(root->bounds().width(), 28);
+ control_view_->SetBounds(control_bounds);
+ gfx::Rect content_bounds(0, control_bounds.height(), root->bounds().width(),
+ root->bounds().height() - control_bounds.height());
+ content_view_->SetBounds(content_bounds);
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ root_view_ = root;
+ view_manager_ = view_manager;
+
+ control_view_ = View::Create(view_manager_);
+ root_view_->AddChild(control_view_);
+
+ content_view_ = View::Create(view_manager_);
+ root_view_->AddChild(content_view_);
+
+ control_panel_.Initialize(control_view_);
+
+ LayoutViews();
+ root_view_->AddObserver(this);
+
+ content_view_->Embed("TODO");
+ }
+
+ virtual void OnViewManagerDisconnected(
+ ViewManager* view_manager) override {
+ DCHECK_EQ(view_manager_, view_manager);
+ view_manager_ = NULL;
+ base::MessageLoop::current()->Quit();
+ }
+
+ // Overridden from ControlPanel::Delegate:
+ virtual void ButtonPressed(ControlPanel::ControlType type) override {
+ switch (type) {
+ case ControlPanel::CONTROL_ZOOM_IN:
+ zoomable_media_->ZoomIn();
+ break;
+ case ControlPanel::CONTROL_ACTUAL_SIZE:
+ zoomable_media_->ZoomToActualSize();
+ break;
+ case ControlPanel::CONTROL_ZOOM_OUT:
+ zoomable_media_->ZoomOut();
+ break;
+ default:
+ NOTIMPLEMENTED();
+ }
+ }
+
+ // ViewObserver:
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override {
+ LayoutViews();
+ }
+ virtual void OnViewDestroyed(View* view) override {
+ DCHECK_EQ(view, root_view_);
+ view->RemoveObserver(this);
+ root_view_ = NULL;
+ }
+
+ std::string GetHandlerForContentType(const std::string& content_type) {
+ HandlerMap::const_iterator it = handler_map_.find(content_type);
+ return it != handler_map_.end() ? it->second : std::string();
+ }
+
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ ApplicationImpl* app_;
+ scoped_ptr<ViewsInit> views_init_;
+ ViewManager* view_manager_;
+ View* root_view_;
+ View* control_view_;
+ View* content_view_;
+ ControlPanel control_panel_;
+ ZoomableMediaPtr zoomable_media_;
+ HandlerMap handler_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaViewer);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::MediaViewer);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/media_viewer/media_viewer.mojom b/mojo/examples/media_viewer/media_viewer.mojom
new file mode 100644
index 0000000..773d775
--- /dev/null
+++ b/mojo/examples/media_viewer/media_viewer.mojom
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium 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 ZoomableMedia {
+ ZoomIn();
+ ZoomOut();
+ ZoomToActualSize();
+};
+
+}
diff --git a/mojo/examples/nesting_app/BUILD.gn b/mojo/examples/nesting_app/BUILD.gn
new file mode 100644
index 0000000..e25263f
--- /dev/null
+++ b/mojo/examples/nesting_app/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_examples.gypi:mojo_nesting_app
+shared_library("nesting_app") {
+ output_name = "mojo_nesting_app"
+
+ sources = [
+ "nesting_app.cc"
+ ]
+
+ deps = [
+ "//base",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ "//url",
+ "//mojo/application",
+ "//mojo/examples/window_manager:bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/navigation",
+ ]
+}
diff --git a/mojo/examples/nesting_app/DEPS b/mojo/examples/nesting_app/DEPS
new file mode 100644
index 0000000..fe1d98e
--- /dev/null
+++ b/mojo/examples/nesting_app/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/events",
+]
diff --git a/mojo/examples/nesting_app/nesting_app.cc b/mojo/examples/nesting_app/nesting_app.cc
new file mode 100644
index 0000000..a7fb74e
--- /dev/null
+++ b/mojo/examples/nesting_app/nesting_app.cc
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium 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/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/window_manager/window_manager.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.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_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
+#include "mojo/services/public/cpp/view_manager/view_observer.h"
+#include "ui/events/event_constants.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace examples {
+
+namespace {
+const char kEmbeddedAppURL[] = "mojo:mojo_embedded_app";
+}
+
+class NestingApp;
+
+// An app that embeds another app.
+// TODO(davemoore): Is this the right name?
+class NestingApp
+ : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public ViewObserver {
+ public:
+ NestingApp() : nested_(NULL) {}
+ virtual ~NestingApp() {}
+
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ // Overridden from ApplicationImpl:
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->ConnectToService(&window_manager_);
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ root->AddObserver(this);
+ root->SetColor(SK_ColorCYAN);
+
+ nested_ = View::Create(view_manager);
+ root->AddChild(nested_);
+ nested_->SetBounds(gfx::Rect(20, 20, 50, 50));
+ nested_->Embed(kEmbeddedAppURL);
+ }
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {
+ base::MessageLoop::current()->Quit();
+ }
+
+ // Overridden from ViewObserver:
+ virtual void OnViewDestroyed(View* view) override {
+ // TODO(beng): reap views & child Views.
+ nested_ = NULL;
+ }
+ virtual void OnViewInputEvent(View* view, const EventPtr& event) override {
+ if (event->action == EVENT_TYPE_MOUSE_RELEASED)
+ window_manager_->CloseWindow(view->id());
+ }
+
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ View* nested_;
+ IWindowManagerPtr window_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(NestingApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::NestingApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/pepper_container_app/BUILD.gn b/mojo/examples/pepper_container_app/BUILD.gn
new file mode 100644
index 0000000..aa9d7af
--- /dev/null
+++ b/mojo/examples/pepper_container_app/BUILD.gn
@@ -0,0 +1,103 @@
+# Copyright 2014 The Chromium 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_examples.gypi:mojo_pepper_container_app
+shared_library("pepper_container_app") {
+ output_name = "mojo_pepper_container_app"
+
+ sources = [
+ # Source files from ppapi/.
+ # An alternative is to depend on
+ # "//ppapi/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",
+
+ "graphics_3d_resource.cc",
+ "graphics_3d_resource.h",
+ "interface_list.cc",
+ "interface_list.h",
+ "mojo_ppapi_globals.cc",
+ "mojo_ppapi_globals.h",
+ "pepper_container_app.cc",
+ "plugin_instance.cc",
+ "plugin_instance.h",
+ "plugin_module.cc",
+ "plugin_module.h",
+ "ppb_core_thunk.cc",
+ "ppb_opengles2_thunk.cc",
+ "resource_creation_impl.cc",
+ "resource_creation_impl.h",
+ "thunk.h",
+ "type_converters.h",
+ ]
+
+ 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",
+ ]
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//gpu/command_buffer/common",
+ "//ppapi:ppapi_c",
+ # TODO(GYP):
+ # "//ppapi:ppapi_example_gles2_spinning_cube",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/gpu",
+ "//mojo/services/public/interfaces/native_viewport",
+ "//ui/events:events_base",
+ ]
+}
diff --git a/mojo/examples/pepper_container_app/DEPS b/mojo/examples/pepper_container_app/DEPS
new file mode 100644
index 0000000..b1e8ef3
--- /dev/null
+++ b/mojo/examples/pepper_container_app/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+base",
+ "+ppapi/c",
+ "+ppapi/shared_impl",
+ "+ppapi/thunk",
+]
diff --git a/mojo/examples/pepper_container_app/OWNERS b/mojo/examples/pepper_container_app/OWNERS
new file mode 100644
index 0000000..4523b30
--- /dev/null
+++ b/mojo/examples/pepper_container_app/OWNERS
@@ -0,0 +1,5 @@
+bbudge@chromium.org
+dmichael@chromium.org
+raymes@chromium.org
+teravest@chromium.org
+yzshen@chromium.org
diff --git a/mojo/examples/pepper_container_app/graphics_3d_resource.cc b/mojo/examples/pepper_container_app/graphics_3d_resource.cc
new file mode 100644
index 0000000..ef6bd76
--- /dev/null
+++ b/mojo/examples/pepper_container_app/graphics_3d_resource.cc
@@ -0,0 +1,166 @@
+// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/environment.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,
+ this,
+ Environment::GetDefaultAsyncWaiter());
+}
+
+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;
+}
+
+uint32_t Graphics3DResource::InsertFutureSyncPoint() {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+void Graphics3DResource::RetireSyncPoint(uint32_t sync_point) {
+ NOTIMPLEMENTED();
+}
+
+Graphics3DResource::~Graphics3DResource() {
+ MojoGLES2DestroyContext(context_);
+}
+
+void Graphics3DResource::ContextLostThunk(void* closure) {
+ static_cast<Graphics3DResource*>(closure)->ContextLost();
+}
+
+void Graphics3DResource::ContextLost() {
+ PluginInstance* plugin_instance =
+ MojoPpapiGlobals::Get()->GetInstance(pp_instance());
+ if (plugin_instance)
+ plugin_instance->Graphics3DContextLost();
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/pepper_container_app/graphics_3d_resource.h b/mojo/examples/pepper_container_app/graphics_3d_resource.h
new file mode 100644
index 0000000..b6ad3b5
--- /dev/null
+++ b/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/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;
+ virtual uint32_t InsertFutureSyncPoint() override;
+ virtual void RetireSyncPoint(uint32_t sync_point) override;
+
+ private:
+ virtual ~Graphics3DResource();
+
+ static void ContextLostThunk(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/mojo/examples/pepper_container_app/interface_list.cc b/mojo/examples/pepper_container_app/interface_list.cc
new file mode 100644
index 0000000..7f4d2ea
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/interface_list.h b/mojo/examples/pepper_container_app/interface_list.h
new file mode 100644
index 0000000..5cff06c
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/mojo_ppapi_globals.cc b/mojo/examples/pepper_container_app/mojo_ppapi_globals.cc
new file mode 100644
index 0000000..c9e7a2b
--- /dev/null
+++ b/mojo/examples/pepper_container_app/mojo_ppapi_globals.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/examples/pepper_container_app/mojo_ppapi_globals.h"
+
+#include "base/logging.h"
+#include "base/macros.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();
+ }
+
+ virtual bool CurrentlyHandlingBlockingMessage() override {
+ return false;
+ }
+
+ // 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_.get()) {
+ 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/mojo/examples/pepper_container_app/mojo_ppapi_globals.h b/mojo/examples/pepper_container_app/mojo_ppapi_globals.h
new file mode 100644
index 0000000..037d161
--- /dev/null
+++ b/mojo/examples/pepper_container_app/mojo_ppapi_globals.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_EXAMPLES_PEPPER_CONTAINER_APP_MOJO_PPAPI_GLOBALS_H_
+#define MOJO_EXAMPLES_PEPPER_CONTAINER_APP_MOJO_PPAPI_GLOBALS_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/mojo/examples/pepper_container_app/pepper_container_app.cc b/mojo/examples/pepper_container_app/pepper_container_app.cc
new file mode 100644
index 0000000..4b64441
--- /dev/null
+++ b/mojo/examples/pepper_container_app/pepper_container_app.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium 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/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "build/build_config.h"
+#include "mojo/application/application_runner_chromium.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/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/public/interfaces/geometry/geometry.mojom.h"
+#include "mojo/services/public/interfaces/gpu/gpu.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 ApplicationDelegate,
+ public NativeViewportClient,
+ public MojoPpapiGlobals::Delegate {
+ public:
+ PepperContainerApp()
+ : ppapi_globals_(this),
+ plugin_module_(new PluginModule),
+ weak_factory_(this) {}
+
+ virtual ~PepperContainerApp() {}
+
+ virtual void Initialize(ApplicationImpl* app) override {
+ app->ConnectToService("mojo:mojo_native_viewport_service", &viewport_);
+ viewport_.set_client(this);
+
+ // TODO(jamesr): Should be mojo:mojo_gpu_service
+ app->ConnectToService("mojo:mojo_native_viewport_service", &gpu_service_);
+
+ SizePtr size(Size::New());
+ size->width = 800;
+ size->height = 600;
+ viewport_->Create(size.Pass(),
+ base::Bind(&PepperContainerApp::OnCreatedNativeViewport,
+ weak_factory_.GetWeakPtr()));
+ viewport_->Show();
+ }
+
+ // NativeViewportClient implementation.
+ virtual void OnDestroyed() override {
+ ppapi::ProxyAutoLock lock;
+
+ if (plugin_instance_) {
+ plugin_instance_->DidDestroy();
+ plugin_instance_.reset();
+ }
+
+ base::MessageLoop::current()->Quit();
+ }
+
+ virtual void OnSizeChanged(SizePtr size) override {
+ ppapi::ProxyAutoLock lock;
+
+ if (plugin_instance_) {
+ PP_Rect pp_rect = {{0, 0}, {size->width, size->height}};
+ plugin_instance_->DidChangeView(pp_rect);
+ }
+ }
+
+ virtual void OnEvent(EventPtr event,
+ const mojo::Callback<void()>& callback) override {
+ if (!event->location_data.is_null()) {
+ ppapi::ProxyAutoLock lock;
+
+ // TODO(yzshen): Handle events.
+ }
+ callback.Run();
+ }
+
+ // MojoPpapiGlobals::Delegate implementation.
+ virtual ScopedMessagePipeHandle CreateGLES2Context() override {
+ CommandBufferPtr command_buffer;
+ SizePtr size = Size::New();
+ size->width = 800;
+ size->width = 600;
+ // TODO(jamesr): Output a surface to the native viewport instead.
+ gpu_service_->CreateOnscreenGLES2Context(
+ native_viewport_id_, size.Pass(), Get(&command_buffer));
+ return command_buffer.PassMessagePipe();
+ }
+
+ private:
+ void OnCreatedNativeViewport(uint64_t native_viewport_id) {
+ native_viewport_id_ = native_viewport_id;
+ ppapi::ProxyAutoLock lock;
+
+ plugin_instance_ = plugin_module_->CreateInstance().Pass();
+ if (!plugin_instance_->DidCreate())
+ plugin_instance_.reset();
+ }
+
+ MojoPpapiGlobals ppapi_globals_;
+
+ uint64_t native_viewport_id_;
+ NativeViewportPtr viewport_;
+ GpuPtr gpu_service_;
+ scoped_refptr<PluginModule> plugin_module_;
+ scoped_ptr<PluginInstance> plugin_instance_;
+
+ base::WeakPtrFactory<PepperContainerApp> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperContainerApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(
+ new mojo::examples::PepperContainerApp);
+ return runner.Run(shell_handle);
+}
+
diff --git a/mojo/examples/pepper_container_app/plugin_instance.cc b/mojo/examples/pepper_container_app/plugin_instance.cc
new file mode 100644
index 0000000..e0b08ef
--- /dev/null
+++ b/mojo/examples/pepper_container_app/plugin_instance.cc
@@ -0,0 +1,444 @@
+// Copyright 2014 The Chromium 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_2* handler,
+ PP_Resource message_loop) {
+ NOTIMPLEMENTED();
+ return PP_ERROR_FAILED;
+}
+
+// TODO(dmichael): Remove this. crbug.com/414398
+int32_t PluginInstance::RegisterMessageHandler_1_1_Deprecated(
+ PP_Instance instance,
+ void* user_data,
+ const PPP_MessageHandler_0_1_Deprecated* 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::PromiseResolvedWithKeyIds(PP_Instance instance,
+ uint32 promise_id,
+ PP_Var key_ids_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::SessionKeysChange(PP_Instance instance,
+ PP_Var web_session_id_var,
+ PP_Bool has_additional_usable_key) {
+ NOTIMPLEMENTED();
+}
+
+void PluginInstance::SessionExpirationChange(PP_Instance instance,
+ PP_Var web_session_id_var,
+ PP_Time new_expiry_time) {
+ 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/mojo/examples/pepper_container_app/plugin_instance.h b/mojo/examples/pepper_container_app/plugin_instance.h
new file mode 100644
index 0000000..6a3240f
--- /dev/null
+++ b/mojo/examples/pepper_container_app/plugin_instance.h
@@ -0,0 +1,199 @@
+// Copyright 2014 The Chromium 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_2* handler,
+ PP_Resource message_loop) override;
+ virtual int32_t RegisterMessageHandler_1_1_Deprecated(
+ PP_Instance instance,
+ void* user_data,
+ const PPP_MessageHandler_0_1_Deprecated* 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 PromiseResolvedWithKeyIds(PP_Instance instance,
+ uint32 promise_id,
+ PP_Var key_ids_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 SessionKeysChange(PP_Instance instance,
+ PP_Var web_session_id_var,
+ PP_Bool has_additional_usable_key) override;
+ virtual void SessionExpirationChange(PP_Instance instance,
+ PP_Var web_session_id_var,
+ PP_Time new_expiry_time) 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/mojo/examples/pepper_container_app/plugin_module.cc b/mojo/examples/pepper_container_app/plugin_module.cc
new file mode 100644
index 0000000..d09d42d
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/plugin_module.h b/mojo/examples/pepper_container_app/plugin_module.h
new file mode 100644
index 0000000..05aad3b
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/ppb_core_thunk.cc b/mojo/examples/pepper_container_app/ppb_core_thunk.cc
new file mode 100644
index 0000000..a76a976
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/ppb_opengles2_thunk.cc b/mojo/examples/pepper_container_app/ppb_opengles2_thunk.cc
new file mode 100644
index 0000000..c5097bb
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/resource_creation_impl.cc b/mojo/examples/pepper_container_app/resource_creation_impl.cc
new file mode 100644
index 0000000..96cf770
--- /dev/null
+++ b/mojo/examples/pepper_container_app/resource_creation_impl.cc
@@ -0,0 +1,410 @@
+// Copyright 2014 The Chromium 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,
+ base::SharedMemoryHandle* shared_state) {
+ 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/mojo/examples/pepper_container_app/resource_creation_impl.h b/mojo/examples/pepper_container_app/resource_creation_impl.h
new file mode 100644
index 0000000..e150964
--- /dev/null
+++ b/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/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,
+ base::SharedMemoryHandle* shared_state) 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/mojo/examples/pepper_container_app/thunk.h b/mojo/examples/pepper_container_app/thunk.h
new file mode 100644
index 0000000..f3b3d54
--- /dev/null
+++ b/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/mojo/examples/pepper_container_app/type_converters.h b/mojo/examples/pepper_container_app/type_converters.h
new file mode 100644
index 0000000..6384f5b
--- /dev/null
+++ b/mojo/examples/pepper_container_app/type_converters.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_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 <>
+struct TypeConverter<PointPtr, PP_Point> {
+ static PointPtr Convert(const PP_Point& input) {
+ PointPtr point(Point::New());
+ point->x = input.x;
+ point->y = input.y;
+ return point.Pass();
+ }
+};
+
+template <>
+struct TypeConverter<PP_Point, PointPtr> {
+ static PP_Point Convert(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 <>
+struct TypeConverter<SizePtr, PP_Size> {
+ static SizePtr Convert(const PP_Size& input) {
+ SizePtr size(Size::New());
+ size->width = input.width;
+ size->height = input.height;
+ return size.Pass();
+ }
+};
+
+template <>
+struct TypeConverter<PP_Size, SizePtr> {
+ static PP_Size Convert(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 <>
+struct TypeConverter<RectPtr, PP_Rect> {
+ static RectPtr Convert(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();
+ }
+};
+
+template <>
+struct TypeConverter<PP_Rect, SizePtr> {
+ static PP_Rect Convert(const SizePtr& input) {
+ if (!input)
+ return PP_MakeRectFromXYWH(0, 0, 0, 0);
+ return PP_MakeRectFromXYWH(0, 0, input->width, input->height);
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_PEPPER_CONTAINER_APP_TYPE_CONVERTERS_H_
diff --git a/mojo/examples/png_viewer/BUILD.gn b/mojo/examples/png_viewer/BUILD.gn
new file mode 100644
index 0000000..9fc2315
--- /dev/null
+++ b/mojo/examples/png_viewer/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_examples.gypi:mojo_png_viewer
+shared_library("png_viewer") {
+ output_name = "mojo_png_viewer"
+
+ sources = [
+ "png_viewer.cc"
+ ]
+
+ deps = [
+ "//mojo/application",
+ "//mojo/examples/media_viewer:bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/content_handler",
+ "//mojo/services/public/interfaces/network",
+ "//skia",
+ "//ui/gfx",
+ ]
+}
diff --git a/mojo/examples/png_viewer/DEPS b/mojo/examples/png_viewer/DEPS
new file mode 100644
index 0000000..23b2bf6
--- /dev/null
+++ b/mojo/examples/png_viewer/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+skia/ext",
+ "+third_party/skia/include",
+ "+ui/gfx",
+]
diff --git a/mojo/examples/png_viewer/png_viewer.cc b/mojo/examples/png_viewer/png_viewer.cc
new file mode 100644
index 0000000..792879a
--- /dev/null
+++ b/mojo/examples/png_viewer/png_viewer.cc
@@ -0,0 +1,242 @@
+// Copyright 2014 The Chromium 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/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_tokenizer.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/media_viewer/media_viewer.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.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_client_factory.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/content_handler/content_handler.mojom.h"
+#include "skia/ext/platform_canvas.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkScalar.h"
+#include "ui/gfx/codec/png_codec.h"
+
+namespace mojo {
+namespace examples {
+
+class PNGViewer;
+
+// TODO(aa): Hook up ZoomableMedia interface again.
+class PNGView : public ViewManagerDelegate, public ViewObserver {
+ public:
+ static void Spawn(URLResponsePtr response,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services,
+ Shell* shell) {
+ // PNGView deletes itself when its View is destroyed.
+ new PNGView(
+ response.Pass(), exported_services, imported_services.Pass(), shell);
+ }
+
+ private:
+ static const uint16_t kMaxZoomPercentage = 400;
+ static const uint16_t kMinZoomPercentage = 20;
+ static const uint16_t kDefaultZoomPercentage = 100;
+ static const uint16_t kZoomStep = 20;
+
+ PNGView(URLResponsePtr response,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services,
+ Shell* shell)
+ : imported_services_(imported_services.Pass()),
+ root_(NULL),
+ view_manager_client_factory_(shell, this),
+ zoom_percentage_(kDefaultZoomPercentage) {
+ exported_services->AddService(&view_manager_client_factory_);
+ DecodePNG(response.Pass());
+ }
+
+ virtual ~PNGView() {
+ if (root_)
+ root_->RemoveObserver(this);
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ root_ = root;
+ root_->AddObserver(this);
+ root_->SetColor(SK_ColorGRAY);
+ if (!bitmap_.isNull())
+ DrawBitmap();
+ }
+
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {
+ // TODO(aa): Need to figure out how shutdown works.
+ }
+
+ // Overridden from ViewObserver:
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override {
+ DCHECK_EQ(view, root_);
+ DrawBitmap();
+ }
+
+ virtual void OnViewDestroyed(View* view) override {
+ DCHECK_EQ(view, root_);
+ delete this;
+ }
+
+ void DecodePNG(URLResponsePtr response) {
+ int content_length = GetContentLength(response->headers);
+ scoped_ptr<unsigned char[]> data(new unsigned char[content_length]);
+ unsigned char* buf = data.get();
+ uint32_t bytes_remaining = content_length;
+ uint32_t num_bytes = bytes_remaining;
+ while (bytes_remaining > 0) {
+ MojoResult result = ReadDataRaw(
+ response->body.get(), buf, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ Wait(response->body.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;
+ }
+ }
+
+ gfx::PNGCodec::Decode(static_cast<const unsigned char*>(data.get()),
+ content_length,
+ &bitmap_);
+ }
+
+ void DrawBitmap() {
+ if (!root_)
+ return;
+
+ skia::RefPtr<SkCanvas> canvas(skia::AdoptRef(skia::CreatePlatformCanvas(
+ root_->bounds().width(), root_->bounds().height(), true)));
+ canvas->drawColor(SK_ColorGRAY);
+ SkPaint paint;
+ SkScalar scale =
+ SkFloatToScalar(zoom_percentage_ * 1.0f / kDefaultZoomPercentage);
+ canvas->scale(scale, scale);
+ canvas->drawBitmap(bitmap_, 0, 0, &paint);
+ root_->SetContents(skia::GetTopDevice(*canvas)->accessBitmap(true));
+ }
+
+ void ZoomIn() {
+ if (zoom_percentage_ >= kMaxZoomPercentage)
+ return;
+ zoom_percentage_ += kZoomStep;
+ DrawBitmap();
+ }
+
+ void ZoomOut() {
+ if (zoom_percentage_ <= kMinZoomPercentage)
+ return;
+ zoom_percentage_ -= kZoomStep;
+ DrawBitmap();
+ }
+
+ void ZoomToActualSize() {
+ if (zoom_percentage_ == kDefaultZoomPercentage)
+ return;
+ zoom_percentage_ = kDefaultZoomPercentage;
+ DrawBitmap();
+ }
+
+ 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;
+ }
+
+ SkBitmap bitmap_;
+ scoped_ptr<ServiceProvider> imported_services_;
+ View* root_;
+ ViewManagerClientFactory view_manager_client_factory_;
+ uint16_t zoom_percentage_;
+
+ DISALLOW_COPY_AND_ASSIGN(PNGView);
+};
+
+class ContentHandlerImpl : public InterfaceImpl<ContentHandler> {
+ public:
+ explicit ContentHandlerImpl(Shell* shell) : shell_(shell) {}
+ virtual ~ContentHandlerImpl() {}
+
+ private:
+ // Overridden from ContentHandler:
+ virtual void OnConnect(
+ const mojo::String& url,
+ URLResponsePtr response,
+ InterfaceRequest<ServiceProvider> service_provider) override {
+ ServiceProviderImpl* exported_services = new ServiceProviderImpl();
+ BindToRequest(exported_services, &service_provider);
+ scoped_ptr<ServiceProvider> remote(
+ exported_services->CreateRemoteServiceProvider());
+ PNGView::Spawn(response.Pass(), exported_services, remote.Pass(), shell_);
+ }
+
+ Shell* shell_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
+};
+
+class PNGViewer : public ApplicationDelegate {
+ public:
+ PNGViewer() {}
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ content_handler_factory_.reset(
+ new InterfaceFactoryImplWithContext<ContentHandlerImpl, Shell>(
+ app->shell()));
+ }
+
+ // Overridden from ApplicationDelegate:
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(content_handler_factory_.get());
+ return true;
+ }
+
+ scoped_ptr<InterfaceFactoryImplWithContext<ContentHandlerImpl, Shell> >
+ content_handler_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PNGViewer);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::PNGViewer);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/sample_app/BUILD.gn b/mojo/examples/sample_app/BUILD.gn
new file mode 100644
index 0000000..d44e6e4
--- /dev/null
+++ b/mojo/examples/sample_app/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_examples.gypi:mojo_sample_app
+shared_library("sample_app") {
+ output_name = "mojo_sample_app"
+
+ sources = [
+ "gles2_client_impl.cc",
+ "gles2_client_impl.h",
+ "sample_app.cc",
+ ]
+
+ deps = [
+ ":spinning_cube",
+ "//base",
+ "//gpu/command_buffer/client:gles2_interface",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/native_viewport",
+ ]
+}
+
+source_set("spinning_cube") {
+ sources = [
+ "spinning_cube.cc",
+ "spinning_cube.h"
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/public/gles2:for_shared_library",
+ ]
+}
diff --git a/mojo/examples/sample_app/DEPS b/mojo/examples/sample_app/DEPS
new file mode 100644
index 0000000..08992c3
--- /dev/null
+++ b/mojo/examples/sample_app/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+gpu/command_buffer/client",
+]
diff --git a/mojo/examples/sample_app/gles2_client_impl.cc b/mojo/examples/sample_app/gles2_client_impl.cc
new file mode 100644
index 0000000..6cdd87a
--- /dev/null
+++ b/mojo/examples/sample_app/gles2_client_impl.cc
@@ -0,0 +1,140 @@
+// 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 "gpu/command_buffer/client/gles2_interface.h"
+#include "mojo/public/c/gles2/gles2.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+
+namespace examples {
+namespace {
+
+float CalculateDragDistance(const mojo::Point& start, const mojo::Point& end) {
+ return hypot(static_cast<float>(start.x - end.x),
+ static_cast<float>(start.y - end.y));
+}
+
+float GetRandomColor() {
+ return static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
+}
+
+}
+
+GLES2ClientImpl::GLES2ClientImpl(mojo::CommandBufferPtr command_buffer)
+ : last_time_(mojo::GetTimeTicksNow()), waiting_to_draw_(false) {
+ context_ =
+ MojoGLES2CreateContext(command_buffer.PassMessagePipe().release().value(),
+ &ContextLostThunk,
+ this,
+ mojo::Environment::GetDefaultAsyncWaiter());
+ MojoGLES2MakeCurrent(context_);
+}
+
+GLES2ClientImpl::~GLES2ClientImpl() {
+ MojoGLES2DestroyContext(context_);
+}
+
+void GLES2ClientImpl::SetSize(const mojo::Size& size) {
+ size_ = size;
+ if (size_.width == 0 || size_.height == 0)
+ return;
+ static_cast<gpu::gles2::GLES2Interface*>(
+ MojoGLES2GetGLES2Interface(context_))->ResizeCHROMIUM(size_.width,
+ size_.height,
+ 1);
+ cube_.Init(size_.width, size_.height);
+ WantToDraw();
+}
+
+void GLES2ClientImpl::HandleInputEvent(const mojo::Event& event) {
+ switch (event.action) {
+ case mojo::EVENT_TYPE_MOUSE_PRESSED:
+ case mojo::EVENT_TYPE_TOUCH_PRESSED:
+ if (event.flags & mojo::EVENT_FLAGS_RIGHT_MOUSE_BUTTON)
+ break;
+ capture_point_ = *event.location_data->in_view_location;
+ last_drag_point_ = capture_point_;
+ drag_start_time_ = mojo::GetTimeTicksNow();
+ break;
+ case mojo::EVENT_TYPE_MOUSE_DRAGGED:
+ case mojo::EVENT_TYPE_TOUCH_MOVED: {
+ if (event.flags & mojo::EVENT_FLAGS_RIGHT_MOUSE_BUTTON)
+ break;
+ int direction =
+ (event.location_data->in_view_location->y < last_drag_point_.y ||
+ event.location_data->in_view_location->x > last_drag_point_.x)
+ ? 1 : -1;
+ cube_.set_direction(direction);
+ cube_.UpdateForDragDistance(CalculateDragDistance(
+ last_drag_point_, *event.location_data->in_view_location));
+ WantToDraw();
+
+ last_drag_point_ = *event.location_data->in_view_location;
+ break;
+ }
+ case mojo::EVENT_TYPE_MOUSE_RELEASED:
+ case mojo::EVENT_TYPE_TOUCH_RELEASED: {
+ if (event.flags & mojo::EVENT_FLAGS_RIGHT_MOUSE_BUTTON) {
+ cube_.set_color(GetRandomColor(), GetRandomColor(), GetRandomColor());
+ break;
+ }
+ MojoTimeTicks offset = mojo::GetTimeTicksNow() - drag_start_time_;
+ float delta = static_cast<float>(offset) / 1000000.;
+ cube_.SetFlingMultiplier(CalculateDragDistance(
+ capture_point_, *event.location_data->in_view_location), delta);
+
+ capture_point_ = last_drag_point_ = mojo::Point();
+ WantToDraw();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void GLES2ClientImpl::ContextLost() {
+}
+
+void GLES2ClientImpl::ContextLostThunk(void* closure) {
+ static_cast<GLES2ClientImpl*>(closure)->ContextLost();
+}
+
+struct DrawRunnable {
+ explicit DrawRunnable(GLES2ClientImpl* impl) : impl(impl) {}
+ virtual ~DrawRunnable() {}
+
+ void Run() const { impl->Draw(); }
+
+ GLES2ClientImpl* impl;
+};
+
+void GLES2ClientImpl::WantToDraw() {
+ if (waiting_to_draw_)
+ return;
+ waiting_to_draw_ = true;
+ mojo::RunLoop::current()->PostDelayedTask(mojo::Closure(DrawRunnable(this)),
+ MojoTimeTicks(16667));
+}
+
+void GLES2ClientImpl::Draw() {
+ waiting_to_draw_ = false;
+ MojoTimeTicks now = mojo::GetTimeTicksNow();
+ MojoTimeTicks offset = now - last_time_;
+ float delta = static_cast<float>(offset) / 1000000.;
+ last_time_ = now;
+ cube_.UpdateForTimeDelta(delta);
+ cube_.Draw();
+
+ MojoGLES2SwapBuffers();
+ WantToDraw();
+}
+
+} // namespace examples
diff --git a/mojo/examples/sample_app/gles2_client_impl.h b/mojo/examples/sample_app/gles2_client_impl.h
new file mode 100644
index 0000000..1a968a0
--- /dev/null
+++ b/mojo/examples/sample_app/gles2_client_impl.h
@@ -0,0 +1,44 @@
+// 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/geometry/geometry.mojom.h"
+#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h"
+
+namespace examples {
+
+class GLES2ClientImpl {
+ public:
+ explicit GLES2ClientImpl(mojo::CommandBufferPtr command_buffer);
+ virtual ~GLES2ClientImpl();
+
+ void SetSize(const mojo::Size& size);
+ void HandleInputEvent(const mojo::Event& event);
+ void Draw();
+
+ private:
+ void ContextLost();
+ static void ContextLostThunk(void* closure);
+ void WantToDraw();
+
+ MojoTimeTicks last_time_;
+ mojo::Size size_;
+ SpinningCube cube_;
+ mojo::Point capture_point_;
+ mojo::Point last_drag_point_;
+ MojoTimeTicks drag_start_time_;
+ bool waiting_to_draw_;
+
+ MojoGLES2Context context_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(GLES2ClientImpl);
+};
+
+} // namespace examples
+
+#endif // MOJO_EXAMPLES_SAMPLE_APP_GLES2_CLIENT_IMPL_H_
diff --git a/mojo/examples/sample_app/sample_app.cc b/mojo/examples/sample_app/sample_app.cc
new file mode 100644
index 0000000..6ffdb59
--- /dev/null
+++ b/mojo/examples/sample_app/sample_app.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 <stdio.h>
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/examples/sample_app/gles2_client_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_runner.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/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h"
+
+namespace examples {
+
+class SampleApp : public mojo::ApplicationDelegate,
+ public mojo::NativeViewportClient {
+ public:
+ SampleApp() : weak_factory_(this) {}
+
+ 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::ApplicationImpl* app) override {
+ app->ConnectToService("mojo:mojo_native_viewport_service", &viewport_);
+ viewport_.set_client(this);
+
+ // TODO(jamesr): Should be mojo:mojo_gpu_service
+ app->ConnectToService("mojo:mojo_native_viewport_service", &gpu_service_);
+
+ mojo::SizePtr size(mojo::Size::New());
+ size->width = 800;
+ size->height = 600;
+ viewport_->Create(size.Pass(),
+ base::Bind(&SampleApp::OnCreatedNativeViewport,
+ weak_factory_.GetWeakPtr()));
+ viewport_->Show();
+ }
+
+ virtual void OnDestroyed() override { mojo::RunLoop::current()->Quit(); }
+
+ virtual void OnSizeChanged(mojo::SizePtr size) override {
+ assert(size);
+ if (gles2_client_)
+ gles2_client_->SetSize(*size);
+ }
+
+ virtual void OnEvent(mojo::EventPtr event,
+ const mojo::Callback<void()>& callback) override {
+ assert(event);
+ if (event->location_data && event->location_data->in_view_location)
+ gles2_client_->HandleInputEvent(*event);
+ callback.Run();
+ }
+
+ private:
+ void OnCreatedNativeViewport(uint64_t native_viewport_id) {
+ mojo::SizePtr size = mojo::Size::New();
+ size->width = 800;
+ size->height = 600;
+ mojo::CommandBufferPtr command_buffer;
+ // TODO(jamesr): Output to a surface instead.
+ gpu_service_->CreateOnscreenGLES2Context(
+ native_viewport_id, size.Pass(), Get(&command_buffer));
+ gles2_client_.reset(new GLES2ClientImpl(command_buffer.Pass()));
+ }
+
+ scoped_ptr<GLES2ClientImpl> gles2_client_;
+ mojo::NativeViewportPtr viewport_;
+ mojo::GpuPtr gpu_service_;
+ base::WeakPtrFactory<SampleApp> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SampleApp);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new examples::SampleApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/sample_app/spinning_cube.cc b/mojo/examples/sample_app/spinning_cube.cc
new file mode 100644
index 0000000..e417984
--- /dev/null
+++ b/mojo/examples/sample_app/spinning_cube.cc
@@ -0,0 +1,470 @@
+// 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 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
diff --git a/mojo/examples/sample_app/spinning_cube.h b/mojo/examples/sample_app/spinning_cube.h
new file mode 100644
index 0000000..f8d506d
--- /dev/null
+++ b/mojo/examples/sample_app/spinning_cube.h
@@ -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.
+
+#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 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
+
+#endif // MOJO_EXAMPLES_SAMPLE_APP_SPINNING_CUBE_H_
diff --git a/mojo/examples/surfaces_app/BUILD.gn b/mojo/examples/surfaces_app/BUILD.gn
new file mode 100644
index 0000000..dc7df4d
--- /dev/null
+++ b/mojo/examples/surfaces_app/BUILD.gn
@@ -0,0 +1,129 @@
+# Copyright 2014 The Chromium 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")
+
+group("surfaces_app") {
+ deps = [
+ ":child_app",
+ ":child_gl_app",
+ ":parent_app",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_surfaces_app
+shared_library("parent_app") {
+ output_name = "mojo_surfaces_app"
+
+ deps = [
+ ":bindings",
+ ":util",
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/cpp/surfaces",
+ ]
+
+ sources = [
+ "embedder.cc",
+ "embedder.h",
+ "surfaces_app.cc",
+ ]
+}
+
+
+# GYP version: mojo/mojo_examples.gypi:mojo_surfaces_child_app
+shared_library("child_app") {
+ output_name = "mojo_surfaces_child_app"
+
+ deps = [
+ ":bindings",
+ ":util",
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/cpp/surfaces",
+ ]
+
+ sources = [
+ "child_app.cc",
+ "child_impl.cc",
+ "child_impl.h",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_surfaces_child_gl_app
+shared_library("child_gl_app") {
+ output_name = "mojo_surfaces_child_gl_app"
+
+ deps = [
+ ":bindings",
+ ":util",
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//mojo/common",
+ "//mojo/application",
+ "//mojo/environment:chromium",
+ "//mojo/examples/sample_app:spinning_cube",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/cpp/surfaces",
+ ]
+
+ sources = [
+ "child_gl_app.cc",
+ "child_gl_impl.cc",
+ "child_gl_impl.h",
+ ]
+}
+
+source_set("util") {
+ deps = [
+ "//cc",
+ "//skia",
+ ]
+
+ sources = [
+ "surfaces_util.cc",
+ "surfaces_util.h",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_surfaces_app_bindings
+mojom("bindings") {
+ deps = [
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/interfaces/surfaces:surface_id",
+ ]
+
+ sources = [ "child.mojom" ]
+}
diff --git a/mojo/examples/surfaces_app/DEPS b/mojo/examples/surfaces_app/DEPS
new file mode 100644
index 0000000..23379ad
--- /dev/null
+++ b/mojo/examples/surfaces_app/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+gpu/GLES2",
+ "+gpu/command_buffer/client",
+ "+third_party/khronos/GLES2",
+ "+third_party/skia/include",
+ "+ui/gfx",
+]
diff --git a/mojo/examples/surfaces_app/child.mojom b/mojo/examples/surfaces_app/child.mojom
new file mode 100644
index 0000000..2fcb00d
--- /dev/null
+++ b/mojo/examples/surfaces_app/child.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.
+
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+import "mojo/services/public/interfaces/surfaces/quads.mojom"
+import "mojo/services/public/interfaces/surfaces/surface_id.mojom"
+
+interface Child {
+ ProduceFrame(mojo.Color color, mojo.Size size) =>
+ (mojo.SurfaceId id);
+};
diff --git a/mojo/examples/surfaces_app/child_app.cc b/mojo/examples/surfaces_app/child_app.cc
new file mode 100644
index 0000000..04c1fba
--- /dev/null
+++ b/mojo/examples/surfaces_app/child_app.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 "base/macros.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/surfaces_app/child_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/bindings/string.h"
+
+namespace mojo {
+namespace examples {
+
+class ChildApp : public ApplicationDelegate, public InterfaceFactory<Child> {
+ public:
+ ChildApp() {}
+ virtual ~ChildApp() {}
+
+ virtual void Initialize(ApplicationImpl* app) override {
+ surfaces_service_connection_ =
+ app->ConnectToApplication("mojo:mojo_surfaces_service");
+ }
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory<Child> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Child> request) override {
+ BindToRequest(new ChildImpl(surfaces_service_connection_), &request);
+ }
+
+ private:
+ ApplicationConnection* surfaces_service_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::ChildApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/surfaces_app/child_gl_app.cc b/mojo/examples/surfaces_app/child_gl_app.cc
new file mode 100644
index 0000000..9646db4
--- /dev/null
+++ b/mojo/examples/surfaces_app/child_gl_app.cc
@@ -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.
+
+#include "base/macros.h"
+#include "base/threading/platform_thread.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/surfaces_app/child_gl_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+
+namespace mojo {
+namespace examples {
+
+class ChildGLApp : public ApplicationDelegate, public InterfaceFactory<Child> {
+ public:
+ ChildGLApp() {}
+ virtual ~ChildGLApp() {}
+
+ virtual void Initialize(ApplicationImpl* app) override {
+ surfaces_service_connection_ =
+ app->ConnectToApplication("mojo:mojo_surfaces_service");
+ // TODO(jamesr): Should be mojo:mojo_gpu_service
+ app->ConnectToService("mojo:mojo_native_viewport_service", &gpu_service_);
+ }
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory<Child> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Child> request) override {
+ CommandBufferPtr command_buffer;
+ gpu_service_->CreateOffscreenGLES2Context(Get(&command_buffer));
+ BindToRequest(
+ new ChildGLImpl(surfaces_service_connection_, command_buffer.Pass()),
+ &request);
+ }
+
+ private:
+ ApplicationConnection* surfaces_service_connection_;
+ GpuPtr gpu_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildGLApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::ChildGLApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/surfaces_app/child_gl_impl.cc b/mojo/examples/surfaces_app/child_gl_impl.cc
new file mode 100644
index 0000000..72374fb
--- /dev/null
+++ b/mojo/examples/surfaces_app/child_gl_impl.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/examples/surfaces_app/child_gl_impl.h"
+
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "gpu/GLES2/gl2chromium.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "mojo/examples/surfaces_app/surfaces_util.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+namespace examples {
+
+using cc::RenderPass;
+using cc::RenderPassId;
+using cc::DrawQuad;
+using cc::TextureDrawQuad;
+using cc::DelegatedFrameData;
+using cc::CompositorFrame;
+
+static void ContextLostThunk(void*) {
+ LOG(FATAL) << "Context lost";
+}
+
+ChildGLImpl::ChildGLImpl(ApplicationConnection* surfaces_service_connection,
+ CommandBufferPtr command_buffer)
+ : start_time_(base::TimeTicks::Now()),
+ next_resource_id_(1),
+ weak_factory_(this) {
+ surfaces_service_connection->ConnectToService(&surfaces_service_);
+ surfaces_service_->CreateSurfaceConnection(base::Bind(
+ &ChildGLImpl::SurfaceConnectionCreated, weak_factory_.GetWeakPtr()));
+ context_ =
+ MojoGLES2CreateContext(command_buffer.PassMessagePipe().release().value(),
+ &ContextLostThunk,
+ this,
+ Environment::GetDefaultAsyncWaiter());
+ DCHECK(context_);
+ MojoGLES2MakeCurrent(context_);
+}
+
+ChildGLImpl::~ChildGLImpl() {
+ MojoGLES2DestroyContext(context_);
+ surface_->DestroySurface(mojo::SurfaceId::From(id_));
+}
+
+void ChildGLImpl::ProduceFrame(
+ ColorPtr color,
+ SizePtr size,
+ const mojo::Callback<void(SurfaceIdPtr id)>& callback) {
+ color_ = color.To<SkColor>();
+ size_ = size.To<gfx::Size>();
+ cube_.Init(size_.width(), size_.height());
+ cube_.set_color(
+ SkColorGetR(color_), SkColorGetG(color_), SkColorGetB(color_));
+ produce_callback_ = callback;
+ AllocateSurface();
+}
+
+void ChildGLImpl::SurfaceConnectionCreated(SurfacePtr surface,
+ uint32_t id_namespace) {
+ surface_ = surface.Pass();
+ surface_.set_client(this);
+ allocator_.reset(new cc::SurfaceIdAllocator(id_namespace));
+ AllocateSurface();
+}
+
+void ChildGLImpl::ReturnResources(Array<ReturnedResourcePtr> resources) {
+ for (size_t i = 0; i < resources.size(); ++i) {
+ cc::ReturnedResource res = resources[i].To<cc::ReturnedResource>();
+ GLuint returned_texture = id_to_tex_map_[res.id];
+ glDeleteTextures(1, &returned_texture);
+ }
+}
+
+void ChildGLImpl::AllocateSurface() {
+ if (produce_callback_.is_null() || !allocator_)
+ return;
+
+ id_ = allocator_->GenerateId();
+ surface_->CreateSurface(mojo::SurfaceId::From(id_), mojo::Size::From(size_));
+ produce_callback_.Run(SurfaceId::From(id_));
+ Draw();
+}
+
+void ChildGLImpl::Draw() {
+ // First, generate a GL texture and draw the cube into it.
+ GLuint texture = 0u;
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ size_.width(),
+ size_.height(),
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ GLuint fbo = 0u;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(
+ GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+ DCHECK_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
+ glCheckFramebufferStatus(GL_FRAMEBUFFER));
+ glClearColor(1, 0, 0, 0.5);
+ cube_.UpdateForTimeDelta(0.16f);
+ cube_.Draw();
+
+ // Then, put the texture into a mailbox.
+ gpu::Mailbox mailbox = gpu::Mailbox::Generate();
+ glProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ GLuint sync_point = glInsertSyncPointCHROMIUM();
+ gpu::MailboxHolder holder(mailbox, GL_TEXTURE_2D, sync_point);
+
+ // Then, put the mailbox into a TransferableResource
+ cc::TransferableResource resource;
+ resource.id = next_resource_id_++;
+ id_to_tex_map_[resource.id] = texture;
+ resource.format = cc::RGBA_8888;
+ resource.filter = GL_LINEAR;
+ resource.size = size_;
+ resource.mailbox_holder = holder;
+ resource.is_repeated = false;
+ resource.is_software = false;
+
+ gfx::Rect rect(size_);
+ RenderPassId id(1, 1);
+ scoped_ptr<RenderPass> pass = RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendSimpleSharedQuadState(pass.get(), gfx::Transform(), size_);
+
+ TextureDrawQuad* texture_quad =
+ pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
+ float vertex_opacity[4] = {1.0f, 1.0f, 0.2f, 1.0f};
+ texture_quad->SetNew(pass->shared_quad_state_list.back(),
+ rect,
+ rect,
+ rect,
+ resource.id,
+ true,
+ gfx::PointF(),
+ gfx::PointF(1.f, 1.f),
+ SK_ColorBLUE,
+ vertex_opacity,
+ false);
+
+ scoped_ptr<DelegatedFrameData> delegated_frame_data(new DelegatedFrameData);
+ delegated_frame_data->render_pass_list.push_back(pass.Pass());
+ delegated_frame_data->resource_list.push_back(resource);
+
+ scoped_ptr<CompositorFrame> frame(new CompositorFrame);
+ frame->delegated_frame_data = delegated_frame_data.Pass();
+
+ surface_->SubmitFrame(mojo::SurfaceId::From(id_), mojo::Frame::From(*frame));
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ChildGLImpl::Draw, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(50));
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/surfaces_app/child_gl_impl.h b/mojo/examples/surfaces_app/child_gl_impl.h
new file mode 100644
index 0000000..4c73e00
--- /dev/null
+++ b/mojo/examples/surfaces_app/child_gl_impl.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_EXAMPLES_SURFACES_APP_CHILD_GL_IMPL_H_
+#define MOJO_EXAMPLES_SURFACES_APP_CHILD_GL_IMPL_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "cc/surfaces/surface_id.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/examples/sample_app/spinning_cube.h"
+#include "mojo/examples/surfaces_app/child.mojom.h"
+#include "mojo/public/c/gles2/gles2.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+class CompositorFrame;
+}
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace examples {
+
+// Simple example of a child app using surfaces + GL.
+class ChildGLImpl : public InterfaceImpl<Child>, public SurfaceClient {
+ public:
+ ChildGLImpl(ApplicationConnection* surfaces_service_connection,
+ CommandBufferPtr command_buffer);
+ virtual ~ChildGLImpl();
+
+ // SurfaceClient implementation
+ virtual void ReturnResources(Array<ReturnedResourcePtr> resources) override;
+
+ private:
+ // Child implementation.
+ virtual void ProduceFrame(
+ ColorPtr color,
+ SizePtr size,
+ const mojo::Callback<void(SurfaceIdPtr id)>& callback) override;
+
+ void SurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace);
+ void AllocateSurface();
+ void Draw();
+
+ SkColor color_;
+ gfx::Size size_;
+ scoped_ptr<cc::SurfaceIdAllocator> allocator_;
+ SurfacesServicePtr surfaces_service_;
+ SurfacePtr surface_;
+ MojoGLES2Context context_;
+ cc::SurfaceId id_;
+ ::examples::SpinningCube cube_;
+ Callback<void(SurfaceIdPtr id)> produce_callback_;
+ base::TimeTicks start_time_;
+ uint32_t next_resource_id_;
+ base::hash_map<uint32_t, GLuint> id_to_tex_map_;
+ base::WeakPtrFactory<ChildGLImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildGLImpl);
+};
+
+} // namespace examples
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_SURFACES_APP_CHILD_GL_IMPL_H_
diff --git a/mojo/examples/surfaces_app/child_impl.cc b/mojo/examples/surfaces_app/child_impl.cc
new file mode 100644
index 0000000..963afb6
--- /dev/null
+++ b/mojo/examples/surfaces_app/child_impl.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium 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/surfaces_app/child_impl.h"
+
+#include "base/bind.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "mojo/examples/surfaces_app/surfaces_util.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+namespace examples {
+
+using cc::RenderPass;
+using cc::RenderPassId;
+using cc::DrawQuad;
+using cc::SolidColorDrawQuad;
+using cc::DelegatedFrameData;
+using cc::CompositorFrame;
+
+ChildImpl::ChildImpl(ApplicationConnection* surfaces_service_connection)
+ : weak_factory_(this) {
+ surfaces_service_connection->ConnectToService(&surfaces_service_);
+ surfaces_service_->CreateSurfaceConnection(base::Bind(
+ &ChildImpl::SurfaceConnectionCreated, weak_factory_.GetWeakPtr()));
+}
+
+ChildImpl::~ChildImpl() {
+ if (surface_)
+ surface_->DestroySurface(mojo::SurfaceId::From(id_));
+}
+
+void ChildImpl::ProduceFrame(
+ ColorPtr color,
+ SizePtr size,
+ const mojo::Callback<void(SurfaceIdPtr id)>& callback) {
+ color_ = color.To<SkColor>();
+ size_ = size.To<gfx::Size>();
+ produce_callback_ = callback;
+ if (allocator_)
+ Draw();
+}
+
+void ChildImpl::SurfaceConnectionCreated(SurfacePtr surface,
+ uint32_t id_namespace) {
+ surface_ = surface.Pass();
+ surface_.set_client(this);
+ allocator_.reset(new cc::SurfaceIdAllocator(id_namespace));
+ if (!produce_callback_.is_null())
+ Draw();
+}
+
+void ChildImpl::ReturnResources(
+ Array<ReturnedResourcePtr> resources) {
+ DCHECK(!resources.size());
+}
+
+void ChildImpl::Draw() {
+ id_ = allocator_->GenerateId();
+ surface_->CreateSurface(mojo::SurfaceId::From(id_),
+ mojo::Size::From(size_));
+ gfx::Rect rect(size_);
+ RenderPassId id(1, 1);
+ scoped_ptr<RenderPass> pass = RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendSimpleSharedQuadState(pass.get(), gfx::Transform(), size_);
+
+ SolidColorDrawQuad* color_quad =
+ pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ color_quad->SetNew(pass->shared_quad_state_list.back(),
+ rect,
+ rect,
+ color_,
+ force_anti_aliasing_off);
+
+ scoped_ptr<DelegatedFrameData> delegated_frame_data(new DelegatedFrameData);
+ delegated_frame_data->render_pass_list.push_back(pass.Pass());
+
+ scoped_ptr<CompositorFrame> frame(new CompositorFrame);
+ frame->delegated_frame_data = delegated_frame_data.Pass();
+
+ surface_->SubmitFrame(mojo::SurfaceId::From(id_),
+ mojo::Frame::From(*frame));
+ produce_callback_.Run(SurfaceId::From(id_));
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/surfaces_app/child_impl.h b/mojo/examples/surfaces_app/child_impl.h
new file mode 100644
index 0000000..1cb887e
--- /dev/null
+++ b/mojo/examples/surfaces_app/child_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_SURFACES_APP_CHILD_IMPL_H_
+#define MOJO_EXAMPLES_SURFACES_APP_CHILD_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/surfaces/surface_id.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/examples/surfaces_app/child.mojom.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+class CompositorFrame;
+}
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace surfaces {
+class Surface;
+}
+
+namespace examples {
+
+// Simple example of a child app using surfaces.
+class ChildImpl : public InterfaceImpl<Child>, public SurfaceClient {
+ public:
+ class Context {
+ public:
+ virtual ApplicationConnection* ShellConnection(
+ const mojo::String& application_url) = 0;
+ };
+ explicit ChildImpl(ApplicationConnection* surfaces_service_connection);
+ virtual ~ChildImpl();
+
+ // SurfaceClient implementation
+ virtual void ReturnResources(
+ Array<ReturnedResourcePtr> resources) override;
+
+ private:
+ // Child implementation.
+ virtual void ProduceFrame(
+ ColorPtr color,
+ SizePtr size,
+ const mojo::Callback<void(SurfaceIdPtr id)>& callback) override;
+
+ void SurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace);
+ void Draw();
+
+ SkColor color_;
+ gfx::Size size_;
+ scoped_ptr<cc::SurfaceIdAllocator> allocator_;
+ SurfacesServicePtr surfaces_service_;
+ SurfacePtr surface_;
+ cc::SurfaceId id_;
+ mojo::Callback<void(SurfaceIdPtr id)> produce_callback_;
+ base::WeakPtrFactory<ChildImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildImpl);
+};
+
+} // namespace examples
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_SURFACES_APP_CHILD_IMPL_H_
diff --git a/mojo/examples/surfaces_app/embedder.cc b/mojo/examples/surfaces_app/embedder.cc
new file mode 100644
index 0000000..4a5ff27
--- /dev/null
+++ b/mojo/examples/surfaces_app/embedder.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/surfaces_app/embedder.h"
+
+#include "cc/output/compositor_frame.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "mojo/examples/surfaces_app/surfaces_util.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+namespace examples {
+
+using cc::RenderPass;
+using cc::RenderPassId;
+using cc::SurfaceDrawQuad;
+using cc::DrawQuad;
+using cc::SolidColorDrawQuad;
+using cc::DelegatedFrameData;
+using cc::CompositorFrame;
+
+Embedder::Embedder(Surface* surface) : surface_(surface) {
+}
+
+Embedder::~Embedder() {
+}
+
+void Embedder::ProduceFrame(cc::SurfaceId child_one,
+ cc::SurfaceId child_two,
+ const gfx::Size& child_size,
+ const gfx::Size& size,
+ int offset) {
+ gfx::Rect rect(size);
+ RenderPassId pass_id(1, 1);
+ scoped_ptr<RenderPass> pass = RenderPass::Create();
+ pass->SetNew(pass_id, rect, rect, gfx::Transform());
+
+ if (!child_one.is_null()) {
+ gfx::Transform one_transform;
+ one_transform.Translate(10 + child_size.width() / 2,
+ 50 + child_size.height() / 2);
+ one_transform.Translate(0, offset);
+ one_transform.Translate(-child_size.width() / 2, -child_size.height() / 2);
+ CreateAndAppendSimpleSharedQuadState(pass.get(), one_transform, size);
+
+ SurfaceDrawQuad* surface_one_quad =
+ pass->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
+ gfx::Rect one_rect(child_size);
+ surface_one_quad->SetNew(
+ pass->shared_quad_state_list.back(), one_rect, one_rect, child_one);
+ }
+
+ if (!child_two.is_null()) {
+ gfx::Transform two_transform;
+ two_transform.Translate(10 + size.width() / 2 + child_size.width() / 2,
+ 50 + child_size.height() / 2);
+ two_transform.Translate(0, 200 - offset);
+ two_transform.Translate(-child_size.width() / 2, -child_size.height() / 2);
+ CreateAndAppendSimpleSharedQuadState(pass.get(), two_transform, size);
+
+ SurfaceDrawQuad* surface_two_quad =
+ pass->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
+ gfx::Rect two_rect(child_size);
+ surface_two_quad->SetNew(
+ pass->shared_quad_state_list.back(), two_rect, two_rect, child_two);
+ }
+
+ CreateAndAppendSimpleSharedQuadState(pass.get(), gfx::Transform(), size);
+ SolidColorDrawQuad* color_quad =
+ pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ color_quad->SetNew(pass->shared_quad_state_list.back(),
+ rect,
+ rect,
+ SK_ColorYELLOW,
+ force_anti_aliasing_off);
+
+ scoped_ptr<DelegatedFrameData> delegated_frame_data(new DelegatedFrameData);
+ delegated_frame_data->render_pass_list.push_back(pass.Pass());
+
+ scoped_ptr<CompositorFrame> frame(new CompositorFrame);
+ frame->delegated_frame_data = delegated_frame_data.Pass();
+
+ surface_->SubmitFrame(mojo::SurfaceId::From(id_),
+ mojo::Frame::From(*frame));
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/surfaces_app/embedder.h b/mojo/examples/surfaces_app/embedder.h
new file mode 100644
index 0000000..3be6a42
--- /dev/null
+++ b/mojo/examples/surfaces_app/embedder.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_EXAMPLES_SURFACES_APP_EMBEDDER_H_
+#define MOJO_EXAMPLES_SURFACES_APP_EMBEDDER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/surfaces/surface_id.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+class CompositorFrame;
+}
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace examples {
+
+// Simple example of a surface embedder that embeds two other surfaces.
+class Embedder {
+ public:
+ explicit Embedder(Surface* surface);
+ ~Embedder();
+
+ void SetSurfaceId(cc::SurfaceId id) { id_ = id; }
+ void ProduceFrame(cc::SurfaceId child_one,
+ cc::SurfaceId child_two,
+ const gfx::Size& child_size,
+ const gfx::Size& size,
+ int offset);
+
+ private:
+ cc::SurfaceId id_;
+ Surface* surface_;
+
+ DISALLOW_COPY_AND_ASSIGN(Embedder);
+};
+
+} // namespace examples
+} // namespace mojo
+
+#endif // MOJO_EXAMPLES_SURFACES_APP_EMBEDDER_H_
diff --git a/mojo/examples/surfaces_app/surfaces_app.cc b/mojo/examples/surfaces_app/surfaces_app.cc
new file mode 100644
index 0000000..a779c16
--- /dev/null
+++ b/mojo/examples/surfaces_app/surfaces_app.cc
@@ -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.
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/surfaces_app/child.mojom.h"
+#include "mojo/examples/surfaces_app/embedder.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/gles2/command_buffer.mojom.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "ui/gfx/rect.h"
+
+namespace mojo {
+namespace examples {
+
+class SurfacesApp : public ApplicationDelegate,
+ public SurfaceClient,
+ public NativeViewportClient {
+ public:
+ SurfacesApp() : weak_factory_(this) {}
+ virtual ~SurfacesApp() {}
+
+ // ApplicationDelegate implementation
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->ConnectToService("mojo:mojo_native_viewport_service",
+ &viewport_);
+ viewport_.set_client(this);
+
+ connection->ConnectToService("mojo:mojo_surfaces_service",
+ &surfaces_service_);
+ surfaces_service_->CreateSurfaceConnection(base::Bind(
+ &SurfacesApp::SurfaceConnectionCreated, base::Unretained(this)));
+
+ size_ = gfx::Size(800, 600);
+
+ viewport_->Create(Size::From(size_),
+ base::Bind(&SurfacesApp::OnCreatedNativeViewport,
+ weak_factory_.GetWeakPtr()));
+ viewport_->Show();
+
+ child_size_ = gfx::Size(size_.width() / 3, size_.height() / 2);
+ connection->ConnectToService("mojo:mojo_surfaces_child_app", &child_one_);
+ connection->ConnectToService("mojo:mojo_surfaces_child_gl_app",
+ &child_two_);
+ child_one_->ProduceFrame(Color::From(SK_ColorBLUE),
+ Size::From(child_size_),
+ base::Bind(&SurfacesApp::ChildOneProducedFrame,
+ base::Unretained(this)));
+ child_two_->ProduceFrame(Color::From(SK_ColorGREEN),
+ Size::From(child_size_),
+ base::Bind(&SurfacesApp::ChildTwoProducedFrame,
+ base::Unretained(this)));
+ return true;
+ }
+
+ void ChildOneProducedFrame(SurfaceIdPtr id) {
+ child_one_id_ = id.To<cc::SurfaceId>();
+ }
+
+ void ChildTwoProducedFrame(SurfaceIdPtr id) {
+ child_two_id_ = id.To<cc::SurfaceId>();
+ }
+
+ void Draw(int offset) {
+ int bounced_offset = offset;
+ if (offset > 200)
+ bounced_offset = 400 - offset;
+ embedder_->ProduceFrame(
+ child_one_id_, child_two_id_, child_size_, size_, bounced_offset);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(
+ &SurfacesApp::Draw, base::Unretained(this), (offset + 2) % 400),
+ base::TimeDelta::FromMilliseconds(50));
+ }
+
+ void SurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace) {
+ surface_ = surface.Pass();
+ surface_.set_client(this);
+ embedder_.reset(new Embedder(surface_.get()));
+ allocator_.reset(new cc::SurfaceIdAllocator(id_namespace));
+
+ onscreen_id_ = allocator_->GenerateId();
+ embedder_->SetSurfaceId(onscreen_id_);
+ surface_->CreateSurface(SurfaceId::From(onscreen_id_), Size::From(size_));
+ viewport_->SubmittedFrame(SurfaceId::From(onscreen_id_));
+ Draw(10);
+ }
+
+ // SurfaceClient implementation.
+ virtual void ReturnResources(Array<ReturnedResourcePtr> resources) override {
+ DCHECK(!resources.size());
+ }
+ // NativeViewportClient implementation.
+ virtual void OnSizeChanged(mojo::SizePtr size) override {}
+ virtual void OnDestroyed() override {}
+ virtual void OnEvent(mojo::EventPtr event,
+ const mojo::Callback<void()>& callback) override {
+ callback.Run();
+ }
+
+ private:
+ void OnCreatedNativeViewport(uint64_t native_viewport_id) {}
+
+ SurfacesServicePtr surfaces_service_;
+ SurfacePtr surface_;
+ cc::SurfaceId onscreen_id_;
+ scoped_ptr<cc::SurfaceIdAllocator> allocator_;
+ scoped_ptr<Embedder> embedder_;
+ ChildPtr child_one_;
+ cc::SurfaceId child_one_id_;
+ ChildPtr child_two_;
+ cc::SurfaceId child_two_id_;
+ gfx::Size size_;
+ gfx::Size child_size_;
+
+ NativeViewportPtr viewport_;
+
+ base::WeakPtrFactory<SurfacesApp> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesApp);
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::SurfacesApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/surfaces_app/surfaces_util.cc b/mojo/examples/surfaces_app/surfaces_util.cc
new file mode 100644
index 0000000..50bfbff
--- /dev/null
+++ b/mojo/examples/surfaces_app/surfaces_util.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/examples/surfaces_app/surfaces_util.h"
+
+#include "cc/quads/render_pass.h"
+#include "cc/quads/shared_quad_state.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+namespace examples {
+
+using cc::SharedQuadState;
+
+void CreateAndAppendSimpleSharedQuadState(cc::RenderPass* render_pass,
+ const gfx::Transform& transform,
+ const gfx::Size& size) {
+ const gfx::Size content_bounds = size;
+ const gfx::Rect visible_content_rect = gfx::Rect(size);
+ const gfx::Rect clip_rect = gfx::Rect(size);
+ bool is_clipped = false;
+ float opacity = 1.f;
+ const SkXfermode::Mode blend_mode = SkXfermode::kSrcOver_Mode;
+ SharedQuadState* shared_state = render_pass->CreateAndAppendSharedQuadState();
+ shared_state->SetAll(transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity,
+ blend_mode,
+ 0);
+}
+
+} // namespace mojo
+} // namespace examples
diff --git a/mojo/examples/surfaces_app/surfaces_util.h b/mojo/examples/surfaces_app/surfaces_util.h
new file mode 100644
index 0000000..3109258
--- /dev/null
+++ b/mojo/examples/surfaces_app/surfaces_util.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_EXAMPLES_SURFACES_APP_SURFACES_UTIL_H_
+#define MOJO_EXAMPLES_SURFACES_APP_SURFACES_UTIL_H_
+
+namespace cc {
+class RenderPass;
+}
+
+namespace gfx {
+class Transform;
+class Size;
+}
+
+namespace mojo {
+namespace examples {
+
+void CreateAndAppendSimpleSharedQuadState(cc::RenderPass* render_pass,
+ const gfx::Transform& transform,
+ const gfx::Size& size);
+
+} // namespace mojo
+} // namespace examples
+
+#endif // MOJO_EXAMPLES_SURFACES_APP_SURFACES_UTIL_H_
diff --git a/mojo/examples/wget/BUILD.gn b/mojo/examples/wget/BUILD.gn
new file mode 100644
index 0000000..448e4ec
--- /dev/null
+++ b/mojo/examples/wget/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wget
+shared_library("wget") {
+ output_name = "mojo_wget"
+
+ deps = [
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/services/public/interfaces/network",
+ ]
+
+ sources = [
+ "wget.cc"
+ ]
+}
diff --git a/mojo/examples/wget/wget.cc b/mojo/examples/wget/wget.cc
new file mode 100644
index 0000000..f7c03bb
--- /dev/null
+++ b/mojo/examples/wget/wget.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 <stdio.h>
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/utility/run_loop.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 {
+namespace {
+
+class ResponsePrinter {
+ public:
+ void Run(URLResponsePtr response) const {
+ if (response->error) {
+ printf("Got error: %d (%s)\n",
+ response->error->code, response->error->description.get().c_str());
+ } else {
+ PrintResponse(response);
+ PrintResponseBody(response->body.Pass());
+ }
+
+ RunLoop::current()->Quit(); // All done!
+ }
+
+ void PrintResponse(const URLResponsePtr& response) const {
+ 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(ScopedDataPipeConsumerHandle body) const {
+ // Read response body in blocking fashion.
+ printf(">>> Body <<<\n");
+
+ for (;;) {
+ char buf[512];
+ uint32_t num_bytes = sizeof(buf);
+ MojoResult result = ReadDataRaw(body.get(), buf, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ Wait(body.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ } else if (result == MOJO_RESULT_OK) {
+ if (fwrite(buf, num_bytes, 1, stdout) != 1) {
+ printf("\nUnexpected error writing to file\n");
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ printf("\n>>> EOF <<<\n");
+ }
+};
+
+} // namespace
+
+class WGetApp : public ApplicationDelegate {
+ public:
+ virtual void Initialize(ApplicationImpl* app) override {
+ app->ConnectToService("mojo:mojo_network_service", &network_service_);
+ Start(app->args());
+ }
+
+ private:
+ void Start(const Array<String>& args) {
+ std::string url((args.size() > 1) ? args[1].get() : PromptForURL());
+ printf("Loading: %s\n", url.c_str());
+
+ network_service_->CreateURLLoader(Get(&url_loader_));
+
+ URLRequestPtr request(URLRequest::New());
+ request->url = url;
+ request->method = "GET";
+ request->auto_follow_redirects = true;
+
+ url_loader_->Start(request.Pass(), ResponsePrinter());
+ }
+
+ std::string PromptForURL() {
+ printf("Enter URL> ");
+ char buf[1024];
+ if (scanf("%1023s", buf) != 1)
+ buf[0] = '\0';
+ return buf;
+ }
+
+ NetworkServicePtr network_service_;
+ URLLoaderPtr url_loader_;
+};
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::examples::WGetApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/window_manager/BUILD.gn b/mojo/examples/window_manager/BUILD.gn
new file mode 100644
index 0000000..8c89c04
--- /dev/null
+++ b/mojo/examples/window_manager/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+assert(use_aura)
+
+# GYP version: mojo/mojo_examples.gypi:mojo_window_manager
+shared_library("window_manager") {
+ output_name = "mojo_window_manager"
+
+ sources = [
+ "debug_panel.cc",
+ "debug_panel.h",
+ "window_manager.cc",
+ ]
+
+ deps = [
+ ":bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/aura",
+ "//mojo/examples/keyboard:bindings",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/navigation",
+ "//mojo/services/window_manager:lib",
+ "//mojo/views",
+ "//ui/aura",
+ "//ui/base",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ "//ui/resources",
+ "//ui/views",
+ "//ui/wm",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_window_manager_bindings
+mojom("bindings") {
+ sources = [
+ "window_manager.mojom",
+ ]
+ deps = [
+ "//mojo/services/public/interfaces/geometry",
+ ]
+}
+
diff --git a/mojo/examples/window_manager/DEPS b/mojo/examples/window_manager/DEPS
new file mode 100644
index 0000000..372b6a8
--- /dev/null
+++ b/mojo/examples/window_manager/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+ui/aura",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/views",
+ "+ui/wm/core",
+]
diff --git a/mojo/examples/window_manager/debug_panel.cc b/mojo/examples/window_manager/debug_panel.cc
new file mode 100644
index 0000000..c65785c
--- /dev/null
+++ b/mojo/examples/window_manager/debug_panel.cc
@@ -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.
+
+#include "mojo/examples/window_manager/debug_panel.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/services/public/cpp/view_manager/view.h"
+#include "mojo/views/native_widget_view_manager.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/blue_button.h"
+#include "ui/views/controls/button/radio_button.h"
+#include "ui/views/widget/widget.h"
+
+namespace mojo {
+namespace examples {
+
+namespace {
+
+const int kControlBorderInset = 5;
+const int kNavigationTargetGroupId = 1;
+
+} // namespace
+
+DebugPanel::DebugPanel(Delegate* delegate, View* view)
+ : delegate_(delegate),
+ view_(view),
+ navigation_target_label_(new views::Label(
+ base::ASCIIToUTF16("Navigation target:"))),
+ navigation_target_new_(new views::RadioButton(
+ base::ASCIIToUTF16("New window"), kNavigationTargetGroupId)),
+ navigation_target_source_(new views::RadioButton(
+ base::ASCIIToUTF16("Source window"), kNavigationTargetGroupId)),
+ navigation_target_default_(new views::RadioButton(
+ base::ASCIIToUTF16("Default"), kNavigationTargetGroupId)),
+ colored_square_(new views::BlueButton(
+ this, base::ASCIIToUTF16("Local nav test"))),
+ close_last_(new views::BlueButton(
+ this, base::ASCIIToUTF16("Close last window"))),
+ cross_app_(new views::BlueButton(
+ this, base::ASCIIToUTF16("Cross-app nav test"))) {
+ navigation_target_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ navigation_target_default_->SetChecked(true);
+
+ views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView();
+ widget_delegate->GetContentsView()->set_background(
+ views::Background::CreateSolidBackground(0xFFDDDDDD));
+ widget_delegate->GetContentsView()->AddChildView(navigation_target_label_);
+ widget_delegate->GetContentsView()->AddChildView(navigation_target_default_);
+ widget_delegate->GetContentsView()->AddChildView(navigation_target_new_);
+ widget_delegate->GetContentsView()->AddChildView(navigation_target_source_);
+ widget_delegate->GetContentsView()->AddChildView(colored_square_);
+ widget_delegate->GetContentsView()->AddChildView(close_last_);
+ widget_delegate->GetContentsView()->AddChildView(cross_app_);
+ widget_delegate->GetContentsView()->SetLayoutManager(this);
+
+ views::Widget* widget = new views::Widget();
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.native_widget = new NativeWidgetViewManager(widget, view);
+ params.delegate = widget_delegate;
+ params.bounds = gfx::Rect(view->bounds().size());
+ widget->Init(params);
+ widget->Show();
+}
+
+DebugPanel::~DebugPanel() {
+}
+
+gfx::Size DebugPanel::GetPreferredSize(const views::View* view) const {
+ return gfx::Size();
+}
+
+Target DebugPanel::navigation_target() const {
+ if (navigation_target_new_->checked())
+ return TARGET_NEW_NODE;
+ if (navigation_target_source_->checked())
+ return TARGET_SOURCE_NODE;
+ return TARGET_DEFAULT;
+}
+
+void DebugPanel::Layout(views::View* view) {
+ int y = kControlBorderInset;
+ int w = view->width() - kControlBorderInset * 2;
+
+ navigation_target_label_->SetBounds(
+ kControlBorderInset, y, w,
+ navigation_target_label_->GetPreferredSize().height());
+ y += navigation_target_label_->height();
+
+ views::RadioButton* radios[] = {
+ navigation_target_default_,
+ navigation_target_new_,
+ navigation_target_source_,
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(radios); ++i) {
+ radios[i]->SetBounds(kControlBorderInset, y, w,
+ radios[i]->GetPreferredSize().height());
+ y += radios[i]->height();
+ }
+
+ y += kControlBorderInset;
+ views::Button* buttons[] = {
+ colored_square_,
+ close_last_,
+ cross_app_,
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(buttons); ++i) {
+ buttons[i]->SetBounds(kControlBorderInset, y, w,
+ buttons[i]->GetPreferredSize().height());
+ y += buttons[i]->height();
+ }
+}
+
+void DebugPanel::ButtonPressed(views::Button* sender, const ui::Event& event) {
+ if (sender == colored_square_) {
+ Navigate("mojo://mojo_embedded_app/");
+ } else if (sender == close_last_) {
+ delegate_->CloseTopWindow();
+ } else if (sender == cross_app_) {
+ Navigate("http://www.aaronboodman.com/z_dropbox/test.html");
+ }
+}
+
+void DebugPanel::Navigate(const std::string& url) {
+ URLRequestPtr request(URLRequest::New());
+ request->url = url;
+ delegate_->RequestNavigate(view_->id(), TARGET_NEW_NODE, request.Pass());
+}
+
+} // namespace examples
+} // namespace mojo
diff --git a/mojo/examples/window_manager/debug_panel.h b/mojo/examples/window_manager/debug_panel.h
new file mode 100644
index 0000000..160e400
--- /dev/null
+++ b/mojo/examples/window_manager/debug_panel.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_EXAMPLES_WINDOW_MANAGER_DEBUG_PANEL_H_
+#define MOJO_EXAMPLES_WINDOW_MANAGER_DEBUG_PANEL_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "mojo/services/public/interfaces/navigation/navigation.mojom.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/layout/layout_manager.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace views {
+class Label;
+class RadioButton;
+}
+
+namespace mojo {
+
+class View;
+
+namespace examples {
+
+namespace {
+}
+
+// A panel of controls intended to demonstrate the functionality of the window
+// manager.
+class DebugPanel : public views::LayoutManager, public views::ButtonListener {
+ public:
+ class Delegate {
+ public:
+ virtual void CloseTopWindow() = 0;
+ virtual void RequestNavigate(uint32 source_view_id,
+ Target target,
+ URLRequestPtr url_request) = 0;
+
+ protected:
+ virtual ~Delegate(){}
+ };
+
+ DebugPanel(Delegate* delegate, View* view);
+ virtual ~DebugPanel();
+
+ Target navigation_target() const;
+
+ private:
+ // LayoutManager overrides:
+ virtual gfx::Size GetPreferredSize(const views::View* view) const override;
+ virtual void Layout(views::View* host) override;
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) override;
+
+ void Navigate(const std::string& url);
+
+ Delegate* delegate_;
+ View* view_;
+
+ views::Label* navigation_target_label_;
+ views::RadioButton* navigation_target_new_;
+ views::RadioButton* navigation_target_source_;
+ views::RadioButton* navigation_target_default_;
+
+ views::Button* colored_square_;
+ views::Button* close_last_;
+ views::Button* cross_app_;
+
+ DISALLOW_COPY_AND_ASSIGN(DebugPanel);
+};
+
+} // examples
+} // mojo
+
+#endif // MOJO_EXAMPLES_WINDOW_MANAGER_DEBUG_PANEL_H_
diff --git a/mojo/examples/window_manager/window_manager.cc b/mojo/examples/window_manager/window_manager.cc
new file mode 100644
index 0000000..2cb5458
--- /dev/null
+++ b/mojo/examples/window_manager/window_manager.cc
@@ -0,0 +1,623 @@
+// Copyright 2014 The Chromium 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/macros.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/keyboard/keyboard.mojom.h"
+#include "mojo/examples/window_manager/debug_panel.h"
+#include "mojo/examples/window_manager/window_manager.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.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/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/cpp/view_manager/window_manager_delegate.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+#include "mojo/services/public/interfaces/navigation/navigation.mojom.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "mojo/views/views_init.h"
+#include "ui/aura/window.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/gfx/geometry/size_conversions.h"
+#include "ui/wm/core/focus_rules.h"
+
+#if defined CreateWindow
+#undef CreateWindow
+#endif
+
+namespace mojo {
+namespace examples {
+
+class WindowManager;
+
+namespace {
+
+const int kBorderInset = 25;
+const int kControlPanelWidth = 200;
+const int kTextfieldHeight = 25;
+
+class WMFocusRules : public wm::FocusRules {
+ public:
+ WMFocusRules(mojo::WindowManagerApp* window_manager_app,
+ mojo::View* window_container)
+ : window_container_(window_container),
+ window_manager_app_(window_manager_app) {}
+ virtual ~WMFocusRules() {}
+
+ private:
+ // Overridden from wm::FocusRules:
+ virtual bool IsToplevelWindow(aura::Window* window) const override {
+ return mojo::WindowManagerApp::GetViewForWindow(window)->parent() ==
+ window_container_;
+ }
+ virtual bool CanActivateWindow(aura::Window* window) const override {
+ return mojo::WindowManagerApp::GetViewForWindow(window)->parent() ==
+ window_container_;
+ }
+ virtual bool CanFocusWindow(aura::Window* window) const override {
+ return true;
+ }
+ virtual aura::Window* GetToplevelWindow(aura::Window* window) const override {
+ mojo::View* view = mojo::WindowManagerApp::GetViewForWindow(window);
+ while (view->parent() != window_container_) {
+ view = view->parent();
+ // Unparented hierarchy, there is no "top level" window.
+ if (!view)
+ return NULL;
+ }
+
+ return window_manager_app_->GetWindowForViewId(view->id());
+ }
+ virtual aura::Window* GetActivatableWindow(
+ aura::Window* window) const override {
+ return GetToplevelWindow(window);
+ }
+ virtual aura::Window* GetFocusableWindow(
+ aura::Window* window) const override {
+ return window;
+ }
+ virtual aura::Window* GetNextActivatableWindow(
+ aura::Window* ignore) const override {
+ aura::Window* activatable = GetActivatableWindow(ignore);
+ const aura::Window::Windows& children = activatable->parent()->children();
+ for (aura::Window::Windows::const_reverse_iterator it = children.rbegin();
+ it != children.rend(); ++it) {
+ if (*it != ignore)
+ return *it;
+ }
+ return NULL;
+ }
+
+ mojo::View* window_container_;
+ mojo::WindowManagerApp* window_manager_app_;
+
+ DISALLOW_COPY_AND_ASSIGN(WMFocusRules);
+};
+
+} // 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 view_id) override;
+ virtual void ShowKeyboard(Id view_id, RectPtr bounds) override;
+ virtual void HideKeyboard(Id view_id) override;
+
+ WindowManager* window_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerConnection);
+};
+
+class NavigatorHostImpl : public InterfaceImpl<NavigatorHost> {
+ public:
+ explicit NavigatorHostImpl(WindowManager* window_manager, Id view_id)
+ : window_manager_(window_manager), view_id_(view_id) {}
+ virtual ~NavigatorHostImpl() {
+ }
+
+ private:
+ virtual void DidNavigateLocally(const mojo::String& url) override;
+ virtual void RequestNavigate(Target target, URLRequestPtr request) override;
+
+ WindowManager* window_manager_;
+ Id view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigatorHostImpl);
+};
+
+class KeyboardManager : public KeyboardClient,
+ public ViewObserver {
+ public:
+ KeyboardManager() : view_manager_(NULL), view_(NULL) {
+ }
+ virtual ~KeyboardManager() {
+ if (view_)
+ view_->parent()->RemoveObserver(this);
+ }
+
+ View* view() { return view_; }
+
+ void Init(ApplicationImpl* application,
+ ViewManager* view_manager,
+ View* parent,
+ const gfx::Rect& bounds) {
+ view_manager_ = view_manager;
+ view_ = View::Create(view_manager);
+ view_->SetBounds(bounds);
+ parent->AddChild(view_);
+ view_->Embed("mojo:mojo_keyboard");
+ application->ConnectToService("mojo:mojo_keyboard", &keyboard_service_);
+ keyboard_service_.set_client(this);
+ parent->AddObserver(this);
+ }
+
+ void Show(Id view_id, const gfx::Rect& bounds) {
+ keyboard_service_->SetTarget(view_id);
+ view_->SetVisible(true);
+ }
+
+ void Hide(Id view_id) {
+ keyboard_service_->SetTarget(0);
+ view_->SetVisible(false);
+ }
+
+ private:
+ // KeyboardClient:
+ virtual void OnKeyboardEvent(Id view_id,
+ int32_t code,
+ int32_t flags) override {
+ View* view = view_manager_->GetViewById(view_id);
+ if (!view)
+ return;
+#if defined(OS_WIN)
+ const bool is_char = code != ui::VKEY_BACK && code != ui::VKEY_RETURN;
+#else
+ const bool is_char = false;
+#endif
+ if (is_char) {
+ view_manager_->DispatchEvent(
+ view,
+ Event::From(ui::KeyEvent(ui::ET_KEY_PRESSED,
+ static_cast<ui::KeyboardCode>(code),
+ flags)));
+ } else {
+ view_manager_->DispatchEvent(
+ view,
+ Event::From(ui::KeyEvent(static_cast<base::char16>(code),
+ static_cast<ui::KeyboardCode>(code),
+ flags)));
+ }
+ view_manager_->DispatchEvent(
+ view,
+ Event::From(ui::KeyEvent(ui::ET_KEY_RELEASED,
+ static_cast<ui::KeyboardCode>(code),
+ flags)));
+ }
+
+ // Overridden from ViewObserver:
+ virtual void OnViewBoundsChanged(View* parent,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override {
+ gfx::Rect keyboard_bounds(view_->bounds());
+ keyboard_bounds.set_y(new_bounds.bottom() - keyboard_bounds.height());
+ keyboard_bounds.set_width(keyboard_bounds.width() +
+ new_bounds.width() - old_bounds.width());
+ view_->SetBounds(keyboard_bounds);
+ }
+ virtual void OnViewDestroyed(View* parent) override {
+ DCHECK_EQ(parent, view_->parent());
+ parent->RemoveObserver(this);
+ view_ = NULL;
+ }
+
+ KeyboardServicePtr keyboard_service_;
+ ViewManager* view_manager_;
+
+ // View the keyboard is attached to.
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardManager);
+};
+
+class RootLayoutManager : public ViewObserver {
+ public:
+ RootLayoutManager(ViewManager* view_manager,
+ View* root,
+ Id content_view_id,
+ Id launcher_ui_view_id,
+ Id control_panel_view_id)
+ : root_(root),
+ view_manager_(view_manager),
+ content_view_id_(content_view_id),
+ launcher_ui_view_id_(launcher_ui_view_id),
+ control_panel_view_id_(control_panel_view_id) {}
+ virtual ~RootLayoutManager() {
+ if (root_)
+ root_->RemoveObserver(this);
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override {
+ DCHECK_EQ(view, root_);
+
+ View* content_view = view_manager_->GetViewById(content_view_id_);
+ content_view->SetBounds(new_bounds);
+
+ int delta_width = new_bounds.width() - old_bounds.width();
+ int delta_height = new_bounds.height() - old_bounds.height();
+
+ View* launcher_ui_view =
+ view_manager_->GetViewById(launcher_ui_view_id_);
+ gfx::Rect launcher_ui_bounds(launcher_ui_view->bounds());
+ launcher_ui_bounds.set_width(launcher_ui_bounds.width() + delta_width);
+ launcher_ui_view->SetBounds(launcher_ui_bounds);
+
+ View* control_panel_view =
+ view_manager_->GetViewById(control_panel_view_id_);
+ gfx::Rect control_panel_bounds(control_panel_view->bounds());
+ control_panel_bounds.set_x(control_panel_bounds.x() + delta_width);
+ control_panel_view->SetBounds(control_panel_bounds);
+
+ const View::Children& content_views = content_view->children();
+ View::Children::const_iterator iter = content_views.begin();
+ for(; iter != content_views.end(); ++iter) {
+ View* view = *iter;
+ if (view->id() == control_panel_view->id() ||
+ view->id() == launcher_ui_view->id())
+ continue;
+ gfx::Rect view_bounds(view->bounds());
+ view_bounds.set_width(view_bounds.width() + delta_width);
+ view_bounds.set_height(view_bounds.height() + delta_height);
+ view->SetBounds(view_bounds);
+ }
+ }
+ virtual void OnViewDestroyed(View* view) override {
+ DCHECK_EQ(view, root_);
+ root_->RemoveObserver(this);
+ root_ = NULL;
+ }
+
+ View* root_;
+ ViewManager* view_manager_;
+ const Id content_view_id_;
+ const Id launcher_ui_view_id_;
+ const Id control_panel_view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(RootLayoutManager);
+};
+
+class Window : public InterfaceFactory<NavigatorHost> {
+ public:
+ Window(WindowManager* window_manager, View* view)
+ : window_manager_(window_manager), view_(view) {}
+
+ virtual ~Window() {}
+
+ View* view() const { return view_; }
+
+ void Embed(const std::string& url) {
+ scoped_ptr<ServiceProviderImpl> service_provider_impl(
+ new ServiceProviderImpl());
+ service_provider_impl->AddService<NavigatorHost>(this);
+ view_->Embed(url, service_provider_impl.Pass());
+ }
+
+ private:
+ // InterfaceFactory<NavigatorHost>
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<NavigatorHost> request) override {
+ BindToRequest(new NavigatorHostImpl(window_manager_, view_->id()),
+ &request);
+ }
+
+ WindowManager* window_manager_;
+ View* view_;
+};
+
+class WindowManager
+ : public ApplicationDelegate,
+ public DebugPanel::Delegate,
+ public ViewManagerDelegate,
+ public WindowManagerDelegate,
+ public ui::EventHandler {
+ public:
+ WindowManager()
+ : window_manager_factory_(this),
+ launcher_ui_(NULL),
+ view_manager_(NULL),
+ window_manager_app_(new WindowManagerApp(this, this)),
+ app_(NULL) {}
+
+ virtual ~WindowManager() {
+ // host() may be destroyed by the time we get here.
+ // TODO: figure out a way to always cleanly remove handler.
+ if (window_manager_app_->host())
+ window_manager_app_->host()->window()->RemovePreTargetHandler(this);
+ }
+
+ void CloseWindow(Id view_id) {
+ WindowVector::iterator iter = GetWindowByViewId(view_id);
+ DCHECK(iter != windows_.end());
+ Window* window = *iter;
+ windows_.erase(iter);
+ window->view()->Destroy();
+ }
+
+ void ShowKeyboard(Id view_id, const gfx::Rect& bounds) {
+ // TODO: this needs to validate |view_id|. That is, it shouldn't assume
+ // |view_id| is valid and it also needs to make sure the client that sent
+ // this really owns |view_id|.
+ // TODO: honor |bounds|.
+ if (!keyboard_manager_) {
+ keyboard_manager_.reset(new KeyboardManager);
+ View* parent = view_manager_->GetRoots().back();
+ int ideal_height = 200;
+ // TODO(sky): 10 is a bit of a hack here. There is a bug that causes
+ // white strips to appear when 0 is used. Figure this out!
+ const gfx::Rect keyboard_bounds(
+ 10, parent->bounds().height() - ideal_height,
+ parent->bounds().width() - 20, ideal_height);
+ keyboard_manager_->Init(app_, view_manager_, parent, keyboard_bounds);
+ }
+ keyboard_manager_->Show(view_id, bounds);
+ }
+
+ void HideKeyboard(Id view_id) {
+ // See comment in ShowKeyboard() about validating args.
+ if (keyboard_manager_)
+ keyboard_manager_->Hide(view_id);
+ }
+
+ void DidNavigateLocally(uint32 source_view_id, const mojo::String& url) {
+ LOG(ERROR) << "DidNavigateLocally: source_view_id: " << source_view_id
+ << " url: " << url.To<std::string>();
+ }
+
+ // Overridden from DebugPanel::Delegate:
+ virtual void CloseTopWindow() override {
+ if (!windows_.empty())
+ CloseWindow(windows_.back()->view()->id());
+ }
+
+ virtual void RequestNavigate(uint32 source_view_id,
+ Target target,
+ URLRequestPtr request) override {
+ OnLaunch(source_view_id, target, request->url);
+ }
+
+ private:
+ typedef std::vector<Window*> WindowVector;
+
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ app_ = app;
+ views_init_.reset(new ViewsInit);
+ window_manager_app_->Initialize(app);
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(&window_manager_factory_);
+ window_manager_app_->ConfigureIncomingConnection(connection);
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ DCHECK(!view_manager_);
+ view_manager_ = view_manager;
+
+ View* view = View::Create(view_manager_);
+ root->AddChild(view);
+ view->SetBounds(gfx::Rect(root->bounds().size()));
+ content_view_id_ = view->id();
+
+ Id launcher_ui_id = CreateLauncherUI();
+ Id control_panel_id = CreateControlPanel(view);
+
+ root_layout_manager_.reset(
+ new RootLayoutManager(view_manager, root,
+ content_view_id_,
+ launcher_ui_id,
+ control_panel_id));
+ root->AddObserver(root_layout_manager_.get());
+
+ window_manager_app_->host()->window()->AddPreTargetHandler(this);
+
+ window_manager_app_->InitFocus(new WMFocusRules(window_manager_app_.get(),
+ view));
+ }
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {
+ DCHECK_EQ(view_manager_, view_manager);
+ view_manager_ = NULL;
+ base::MessageLoop::current()->Quit();
+ }
+
+ // Overridden from WindowManagerDelegate:
+ virtual void Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) override {
+ const Id kInvalidSourceViewId = 0;
+ OnLaunch(kInvalidSourceViewId, TARGET_DEFAULT, url);
+ }
+ virtual void DispatchEvent(EventPtr event) override {}
+
+ // Overridden from ui::EventHandler:
+ virtual void OnEvent(ui::Event* event) override {
+ View* view = WindowManagerApp::GetViewForWindow(
+ static_cast<aura::Window*>(event->target()));
+ if (event->type() == ui::ET_MOUSE_PRESSED &&
+ !IsDescendantOfKeyboard(view)) {
+ view->SetFocus();
+ }
+ }
+
+ void OnLaunch(uint32 source_view_id,
+ Target requested_target,
+ const mojo::String& url) {
+ Target target = debug_panel_->navigation_target();
+ if (target == TARGET_DEFAULT) {
+ if (requested_target != TARGET_DEFAULT) {
+ target = requested_target;
+ } else {
+ // TODO(aa): Should be TARGET_NEW_NODE if source origin and dest origin
+ // are different?
+ target = TARGET_SOURCE_NODE;
+ }
+ }
+
+ Window* dest_view = NULL;
+ if (target == TARGET_SOURCE_NODE) {
+ WindowVector::iterator source_view = GetWindowByViewId(source_view_id);
+ bool app_initiated = source_view != windows_.end();
+ if (app_initiated)
+ dest_view = *source_view;
+ else if (!windows_.empty())
+ dest_view = windows_.back();
+ }
+
+ if (!dest_view) {
+ dest_view = CreateWindow();
+ windows_.push_back(dest_view);
+ }
+
+ dest_view->Embed(url);
+ }
+
+ // TODO(beng): proper layout manager!!
+ Id CreateLauncherUI() {
+ View* view = view_manager_->GetViewById(content_view_id_);
+ gfx::Rect bounds = view->bounds();
+ bounds.Inset(kBorderInset, kBorderInset);
+ bounds.set_height(kTextfieldHeight);
+ launcher_ui_ = CreateWindow(bounds);
+ launcher_ui_->Embed("mojo:mojo_browser");
+ return launcher_ui_->view()->id();
+ }
+
+ Window* CreateWindow() {
+ View* view = view_manager_->GetViewById(content_view_id_);
+ gfx::Rect bounds(kBorderInset,
+ 2 * kBorderInset + kTextfieldHeight,
+ view->bounds().width() - 3 * kBorderInset -
+ kControlPanelWidth,
+ view->bounds().height() -
+ (3 * kBorderInset + kTextfieldHeight));
+ if (!windows_.empty()) {
+ gfx::Point position = windows_.back()->view()->bounds().origin();
+ position.Offset(35, 35);
+ bounds.set_origin(position);
+ }
+ return CreateWindow(bounds);
+ }
+
+ Window* CreateWindow(const gfx::Rect& bounds) {
+ View* content = view_manager_->GetViewById(content_view_id_);
+ View* view = View::Create(view_manager_);
+ content->AddChild(view);
+ view->SetBounds(bounds);
+ view->SetFocus();
+ return new Window(this, view);
+ }
+
+ bool IsDescendantOfKeyboard(View* target) {
+ return keyboard_manager_.get() &&
+ keyboard_manager_->view()->Contains(target);
+ }
+
+ Id CreateControlPanel(View* root) {
+ View* view = View::Create(view_manager_);
+ root->AddChild(view);
+
+ gfx::Rect bounds(root->bounds().width() - kControlPanelWidth -
+ kBorderInset,
+ kBorderInset * 2 + kTextfieldHeight,
+ kControlPanelWidth,
+ root->bounds().height() - kBorderInset * 3 -
+ kTextfieldHeight);
+ view->SetBounds(bounds);
+
+ debug_panel_ = new DebugPanel(this, view);
+ return view->id();
+ }
+
+ WindowVector::iterator GetWindowByViewId(Id view_id) {
+ for (std::vector<Window*>::iterator iter = windows_.begin();
+ iter != windows_.end();
+ ++iter) {
+ if ((*iter)->view()->id() == view_id) {
+ return iter;
+ }
+ }
+ return windows_.end();
+ }
+
+ InterfaceFactoryImplWithContext<WindowManagerConnection, WindowManager>
+ window_manager_factory_;
+
+ scoped_ptr<ViewsInit> views_init_;
+ DebugPanel* debug_panel_;
+ Window* launcher_ui_;
+ WindowVector windows_;
+ ViewManager* view_manager_;
+ scoped_ptr<RootLayoutManager> root_layout_manager_;
+
+ scoped_ptr<WindowManagerApp> window_manager_app_;
+
+ // Id of the view most content is added to. The keyboard is NOT added here.
+ Id content_view_id_;
+
+ scoped_ptr<KeyboardManager> keyboard_manager_;
+ ApplicationImpl* app_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManager);
+};
+
+void WindowManagerConnection::CloseWindow(Id view_id) {
+ window_manager_->CloseWindow(view_id);
+}
+
+void WindowManagerConnection::ShowKeyboard(Id view_id, RectPtr bounds) {
+ window_manager_->ShowKeyboard(view_id, bounds.To<gfx::Rect>());
+}
+
+void WindowManagerConnection::HideKeyboard(Id view_id) {
+ window_manager_->HideKeyboard(view_id);
+}
+
+void NavigatorHostImpl::DidNavigateLocally(const mojo::String& url) {
+ window_manager_->DidNavigateLocally(view_id_, url);
+}
+
+void NavigatorHostImpl::RequestNavigate(Target target, URLRequestPtr request) {
+ window_manager_->RequestNavigate(view_id_, target, request.Pass());
+}
+
+} // namespace examples
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::examples::WindowManager);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/window_manager/window_manager.mojom b/mojo/examples/window_manager/window_manager.mojom
new file mode 100644
index 0000000..c6b4187
--- /dev/null
+++ b/mojo/examples/window_manager/window_manager.mojom
@@ -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.
+
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+
+module mojo {
+
+interface IWindowManager {
+ CloseWindow(uint32 node_id);
+
+ // Shows the keyboard for the specified view. |bounds| is the bounds of the
+ // view that is showing focus. |bounds| is relative to the bounds of the node.
+ // Events from the keyboard are routed to the view with id |view_id|.
+ ShowKeyboard(uint32 view_id, mojo.Rect? bounds);
+
+ // Hides the keyboard. This is ignored if |view_id| is not the view that was
+ // last passed to ShowKeyboard().
+ HideKeyboard(uint32 view_id);
+};
+
+}
diff --git a/mojo/examples/wm_flow/BUILD.gn b/mojo/examples/wm_flow/BUILD.gn
new file mode 100644
index 0000000..ef665c0
--- /dev/null
+++ b/mojo/examples/wm_flow/BUILD.gn
@@ -0,0 +1,106 @@
+# Copyright 2014 The Chromium 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("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+assert(use_aura)
+
+group("wm_flow" ) {
+ deps = [
+ ":app",
+ ":embedded",
+ ":init",
+ ":wm",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wm_flow_wm
+shared_library("wm") {
+ output_name = "mojo_wm_flow_wm"
+
+ sources = [
+ "wm/wm.cc",
+ "wm/frame_controller.cc",
+ "wm/frame_controller.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/window_manager:lib",
+ "//mojo/views",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wm_flow_init
+shared_library("init") {
+ output_name = "mojo_wm_flow_init"
+
+ sources = [
+ "init/init.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/view_manager",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wm_flow_app
+shared_library("app") {
+ output_name = "mojo_wm_flow_app"
+
+ sources = [
+ "app/app.cc",
+ ]
+
+ deps = [
+ ":embedder_bindings",
+ ":embeddee_bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/window_manager:lib",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wm_flow_embedded
+shared_library("embedded") {
+ output_name = "mojo_wm_flow_embedded"
+
+ sources = [
+ "embedded/embedded.cc",
+ ]
+
+ deps = [
+ ":embedder_bindings",
+ ":embeddee_bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/window_manager:lib",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wm_flow_embedder_bindings
+mojom("embedder_bindings") {
+ sources = [
+ "app/embedder.mojom",
+ ]
+}
+
+# GYP version: mojo/mojo_examples.gypi:mojo_wm_flow_embeddee_bindings
+mojom("embeddee_bindings") {
+ sources = [
+ "embedded/embeddee.mojom",
+ ]
+}
diff --git a/mojo/examples/wm_flow/app/DEPS b/mojo/examples/wm_flow/app/DEPS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/examples/wm_flow/app/DEPS
diff --git a/mojo/examples/wm_flow/app/app.cc b/mojo/examples/wm_flow/app/app.cc
new file mode 100644
index 0000000..f2bea10
--- /dev/null
+++ b/mojo/examples/wm_flow/app/app.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 "base/bind.h"
+#include "base/macros.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/wm_flow/app/embedder.mojom.h"
+#include "mojo/examples/wm_flow/embedded/embeddee.mojom.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.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_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_context.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
+#include "mojo/services/public/cpp/view_manager/view_observer.h"
+
+namespace examples {
+namespace {
+
+const SkColor kColors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorYELLOW };
+
+class EmbedderImpl : public mojo::InterfaceImpl<Embedder> {
+ public:
+ EmbedderImpl() {}
+ virtual ~EmbedderImpl() {}
+
+ private:
+ // Overridden from Embedder:
+ virtual void HelloWorld(const mojo::Callback<void()>& callback) override {
+ callback.Run();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(EmbedderImpl);
+};
+
+} // namespace
+
+// This app starts its life via Connect() rather than by being embed, so it does
+// not start with a connection to the ViewManager service. It has to obtain a
+// connection by connecting to the ViewManagerInit service and asking to be
+// embed without a view context.
+class WMFlowApp : public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate,
+ public mojo::ViewObserver {
+ public:
+ WMFlowApp() : embed_count_(0) {}
+ virtual ~WMFlowApp() {}
+
+ private:
+ // Overridden from Application:
+ virtual void Initialize(mojo::ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new mojo::ViewManagerClientFactory(app->shell(), this));
+ view_manager_context_.reset(new mojo::ViewManagerContext(app));
+ OpenNewWindow();
+ OpenNewWindow();
+ OpenNewWindow();
+ }
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ void OnConnect(bool success) {}
+
+ // Overridden from mojo::ViewManagerDelegate:
+ virtual void OnEmbed(
+ mojo::ViewManager* view_manager,
+ mojo::View* root,
+ mojo::ServiceProviderImpl* exported_services,
+ scoped_ptr<mojo::ServiceProvider> imported_services) override {
+ root->AddObserver(this);
+ root->SetColor(kColors[embed_count_++ % arraysize(kColors)]);
+
+ mojo::View* embed = mojo::View::Create(view_manager);
+ root->AddChild(embed);
+ gfx::Rect bounds = gfx::Rect(root->bounds().size());
+ bounds.Inset(25, 25);
+ embed->SetBounds(bounds);
+
+ scoped_ptr<mojo::ServiceProviderImpl> registry(
+ new mojo::ServiceProviderImpl);
+ // Expose some services to the embeddee...
+ registry->AddService(&embedder_factory_);
+ scoped_ptr<mojo::ServiceProvider> imported =
+ embed->Embed("mojo:mojo_wm_flow_embedded", registry.Pass());
+ mojo::ConnectToService(imported.get(), &embeddee_);
+ embeddee_->HelloBack(base::Bind(&WMFlowApp::HelloBackAck,
+ base::Unretained(this)));
+ }
+ virtual void OnViewManagerDisconnected(
+ mojo::ViewManager* view_manager) override {}
+
+ // Overridden from mojo::ViewObserver:
+ virtual void OnViewInputEvent(mojo::View* view,
+ const mojo::EventPtr& event) override {
+ if (event->action == mojo::EVENT_TYPE_MOUSE_RELEASED &&
+ event->flags & mojo::EVENT_FLAGS_LEFT_MOUSE_BUTTON) {
+ OpenNewWindow();
+ }
+ }
+ virtual void OnViewDestroyed(mojo::View* view) override {
+ --embed_count_;
+ view->RemoveObserver(this);
+ }
+
+ void HelloBackAck() {
+ printf("HelloBack() ack'ed\n");
+ }
+
+ void OpenNewWindow() {
+ view_manager_context_->Embed("mojo:mojo_wm_flow_app");
+ }
+
+ int embed_count_;
+ scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_;
+ mojo::InterfaceFactoryImpl<EmbedderImpl> embedder_factory_;
+ scoped_ptr<mojo::ViewManagerContext> view_manager_context_;
+ EmbeddeePtr embeddee_;
+
+ DISALLOW_COPY_AND_ASSIGN(WMFlowApp);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new examples::WMFlowApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/wm_flow/app/embedder.mojom b/mojo/examples/wm_flow/app/embedder.mojom
new file mode 100644
index 0000000..aae7870
--- /dev/null
+++ b/mojo/examples/wm_flow/app/embedder.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 examples {
+
+interface Embedder {
+ HelloWorld() => ();
+};
+
+}
diff --git a/mojo/examples/wm_flow/embedded/embedded.cc b/mojo/examples/wm_flow/embedded/embedded.cc
new file mode 100644
index 0000000..f69aa90
--- /dev/null
+++ b/mojo/examples/wm_flow/embedded/embedded.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 "base/bind.h"
+#include "base/macros.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/wm_flow/app/embedder.mojom.h"
+#include "mojo/examples/wm_flow/embedded/embeddee.mojom.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.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_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
+
+namespace examples {
+
+namespace {
+
+class EmbeddeeImpl : public mojo::InterfaceImpl<Embeddee> {
+ public:
+ EmbeddeeImpl() {}
+ virtual ~EmbeddeeImpl() {}
+
+ private:
+ // Overridden from Embeddee:
+ virtual void HelloBack(const mojo::Callback<void()>& callback) override {
+ callback.Run();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(EmbeddeeImpl);
+};
+
+} // namespace
+
+class WMFlowEmbedded : public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate {
+ public:
+ WMFlowEmbedded() {}
+ virtual ~WMFlowEmbedded() {}
+
+ private:
+ // Overridden from Application:
+ virtual void Initialize(mojo::ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new mojo::ViewManagerClientFactory(app->shell(), this));
+ }
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // Overridden from mojo::ViewManagerDelegate:
+ virtual void OnEmbed(
+ mojo::ViewManager* view_manager,
+ mojo::View* root,
+ mojo::ServiceProviderImpl* exported_services,
+ scoped_ptr<mojo::ServiceProvider> imported_services) override {
+ root->SetColor(SK_ColorMAGENTA);
+
+ exported_services->AddService(&embeddee_factory_);
+ mojo::ConnectToService(imported_services.get(), &embedder_);
+ embedder_->HelloWorld(base::Bind(&WMFlowEmbedded::HelloWorldAck,
+ base::Unretained(this)));
+ }
+ virtual void OnViewManagerDisconnected(
+ mojo::ViewManager* view_manager) override {}
+
+ void HelloWorldAck() {
+ printf("HelloWorld() ack'ed\n");
+ }
+
+ scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_;
+ EmbedderPtr embedder_;
+ mojo::InterfaceFactoryImpl<EmbeddeeImpl> embeddee_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WMFlowEmbedded);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new examples::WMFlowEmbedded);
+ return runner.Run(shell_handle);
+}
+
diff --git a/mojo/examples/wm_flow/embedded/embeddee.mojom b/mojo/examples/wm_flow/embedded/embeddee.mojom
new file mode 100644
index 0000000..b989da2
--- /dev/null
+++ b/mojo/examples/wm_flow/embedded/embeddee.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 examples {
+
+interface Embeddee {
+ HelloBack() => ();
+};
+
+}
diff --git a/mojo/examples/wm_flow/init/init.cc b/mojo/examples/wm_flow/init/init.cc
new file mode 100644
index 0000000..ba17b44
--- /dev/null
+++ b/mojo/examples/wm_flow/init/init.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/bind.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/public/cpp/view_manager/view_manager.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_context.h"
+
+namespace examples {
+
+// This application starts the view manager, embeds the window manager and then
+// starts another app (wm_flow_app) which also connects to the view manager and
+// asks to be embedded without context.
+class WMFlowInit : public mojo::ApplicationDelegate {
+ public:
+ WMFlowInit() {}
+ virtual ~WMFlowInit() {}
+
+ private:
+ // Overridden from Application:
+ virtual void Initialize(mojo::ApplicationImpl* app) override {
+ context_.reset(new mojo::ViewManagerContext(app));
+ context_->Embed("mojo:mojo_wm_flow_wm");
+ app->ConnectToApplication("mojo:mojo_wm_flow_app");
+ }
+
+ scoped_ptr<mojo::ViewManagerContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(WMFlowInit);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new examples::WMFlowInit);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/examples/wm_flow/wm/DEPS b/mojo/examples/wm_flow/wm/DEPS
new file mode 100644
index 0000000..1486b9f
--- /dev/null
+++ b/mojo/examples/wm_flow/wm/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+ui/aura",
+ "+ui/gfx/geometry",
+ "+ui/views",
+ "+ui/wm/core",
+ "+ui/wm/public",
+]
diff --git a/mojo/examples/wm_flow/wm/frame_controller.cc b/mojo/examples/wm_flow/wm/frame_controller.cc
new file mode 100644
index 0000000..7ed1dac
--- /dev/null
+++ b/mojo/examples/wm_flow/wm/frame_controller.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 "mojo/examples/wm_flow/wm/frame_controller.h"
+
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/services/public/cpp/view_manager/view.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "mojo/views/native_widget_view_manager.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/layout/layout_manager.h"
+#include "ui/views/widget/widget.h"
+#include "ui/wm/public/activation_client.h"
+
+class FrameController::LayoutManager : public views::LayoutManager,
+ public views::ButtonListener {
+ public:
+ explicit LayoutManager(FrameController* controller)
+ : controller_(controller),
+ close_button_(
+ new views::LabelButton(this, base::ASCIIToUTF16("Begone"))),
+ maximize_button_(
+ new views::LabelButton(this, base::ASCIIToUTF16("Embiggen"))) {}
+ virtual ~LayoutManager() {}
+
+ private:
+ static const int kButtonFrameMargin = 5;
+ static const int kButtonFrameSpacing = 2;
+ static const int kFrameSize = 10;
+
+ // Overridden from views::LayoutManager:
+ virtual void Installed(views::View* host) override {
+ host->AddChildView(close_button_);
+ host->AddChildView(maximize_button_);
+ }
+ virtual void Layout(views::View* host) override {
+ gfx::Size ps = close_button_->GetPreferredSize();
+ gfx::Rect bounds = host->GetLocalBounds();
+ close_button_->SetBounds(bounds.right() - kButtonFrameMargin - ps.width(),
+ kButtonFrameMargin, ps.width(), ps.height());
+
+ ps = maximize_button_->GetPreferredSize();
+ maximize_button_->SetBounds(
+ close_button_->x() - kButtonFrameSpacing - ps.width(),
+ kButtonFrameMargin, ps.width(), ps.height());
+
+ bounds.Inset(kFrameSize,
+ close_button_->bounds().bottom() + kButtonFrameMargin,
+ kFrameSize, kFrameSize);
+ controller_->app_view_->SetBounds(bounds);
+ }
+ virtual gfx::Size GetPreferredSize(const views::View* host) const override {
+ return gfx::Size();
+ }
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) override {
+ if (sender == close_button_)
+ controller_->CloseWindow();
+ else if (sender == maximize_button_)
+ controller_->ToggleMaximize();
+ }
+
+ FrameController* controller_;
+ views::Button* close_button_;
+ views::Button* maximize_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayoutManager);
+};
+
+class FrameController::FrameEventHandler : public ui::EventHandler {
+ public:
+ explicit FrameEventHandler(FrameController* frame_controller)
+ : frame_controller_(frame_controller) {}
+ virtual ~FrameEventHandler() {}
+
+ private:
+
+ // Overriden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) override {
+ if (event->type() == ui::ET_MOUSE_PRESSED)
+ frame_controller_->ActivateWindow();
+ }
+
+ FrameController* frame_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameEventHandler);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// FrameController, public:
+
+FrameController::FrameController(
+ mojo::View* view,
+ mojo::View** app_view,
+ aura::client::ActivationClient* activation_client,
+ mojo::WindowManagerApp* window_manager_app)
+ : view_(view),
+ app_view_(mojo::View::Create(view->view_manager())),
+ frame_view_(new views::View),
+ frame_view_layout_manager_(new LayoutManager(this)),
+ widget_(new views::Widget),
+ maximized_(false),
+ activation_client_(activation_client),
+ window_manager_app_(window_manager_app) {
+ view_->AddChild(app_view_);
+ view_->AddObserver(this);
+ *app_view = app_view_;
+ frame_view_->set_background(
+ views::Background::CreateSolidBackground(SK_ColorBLUE));
+ frame_view_->SetLayoutManager(frame_view_layout_manager_);
+ frame_event_handler_.reset(new FrameEventHandler(this));
+ frame_view_->AddPreTargetHandler(frame_event_handler_.get());
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.native_widget = new mojo::NativeWidgetViewManager(widget_, view_);
+ params.bounds = gfx::Rect(view_->bounds().size());
+ widget_->Init(params);
+ widget_->SetContentsView(frame_view_);
+ widget_->Show();
+}
+
+FrameController::~FrameController() {}
+
+void FrameController::CloseWindow() {
+ app_view_->Destroy();
+ view_->Destroy();
+}
+
+void FrameController::ToggleMaximize() {
+ if (!maximized_)
+ restored_bounds_ = view_->bounds();
+ maximized_ = !maximized_;
+ if (maximized_)
+ view_->SetBounds(view_->parent()->bounds());
+ else
+ view_->SetBounds(restored_bounds_);
+}
+
+void FrameController::ActivateWindow() {
+ aura::Window* window = window_manager_app_->GetWindowForViewId(view_->id());
+ activation_client_->ActivateWindow(window);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FrameController, mojo::ViewObserver implementation:
+
+void FrameController::OnViewDestroyed(mojo::View* view) {
+ view_->RemoveObserver(this);
+ delete this;
+}
diff --git a/mojo/examples/wm_flow/wm/frame_controller.h b/mojo/examples/wm_flow/wm/frame_controller.h
new file mode 100644
index 0000000..2c7d9e9
--- /dev/null
+++ b/mojo/examples/wm_flow/wm/frame_controller.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_EXAMPLES_WM_FLOW_WM_FRAME_CONTROLLER_H_
+#define MOJO_EXAMPLES_WM_FLOW_WM_FRAME_CONTROLLER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/services/public/cpp/view_manager/view_observer.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace aura {
+namespace client {
+class ActivationClient;
+}
+}
+
+namespace mojo {
+class NativeWidgetViewManager;
+class View;
+class WindowManagerApp;
+}
+
+namespace views {
+class View;
+class Widget;
+}
+
+// FrameController encapsulates the window manager's frame additions to a window
+// created by an application. It renders the content of the frame and responds
+// to any events targeted at it.
+class FrameController : mojo::ViewObserver {
+ public:
+ FrameController(mojo::View* view,
+ mojo::View** app_view,
+ aura::client::ActivationClient* activation_client,
+ mojo::WindowManagerApp* window_manager_app);
+ virtual ~FrameController();
+
+ void CloseWindow();
+ void ToggleMaximize();
+
+ void ActivateWindow();
+
+ private:
+ class LayoutManager;
+ friend class LayoutManager;
+ class FrameEventHandler;
+
+ virtual void OnViewDestroyed(mojo::View* view) override;
+
+ mojo::View* view_;
+ mojo::View* app_view_;
+ views::View* frame_view_;
+ LayoutManager* frame_view_layout_manager_;
+ views::Widget* widget_;
+ bool maximized_;
+ gfx::Rect restored_bounds_;
+ aura::client::ActivationClient* activation_client_;
+ mojo::WindowManagerApp* window_manager_app_;
+ scoped_ptr<FrameEventHandler> frame_event_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameController);
+};
+
+#endif // MOJO_EXAMPLES_WM_FLOW_WM_FRAME_CONTROLLER_H_
diff --git a/mojo/examples/wm_flow/wm/wm.cc b/mojo/examples/wm_flow/wm/wm.cc
new file mode 100644
index 0000000..f2787fc
--- /dev/null
+++ b/mojo/examples/wm_flow/wm/wm.cc
@@ -0,0 +1,200 @@
+// Copyright 2014 The Chromium 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 <vector>
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/examples/wm_flow/wm/frame_controller.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.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/cpp/view_manager/window_manager_delegate.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "mojo/views/views_init.h"
+#include "ui/aura/window.h"
+#include "ui/wm/core/focus_rules.h"
+#include "ui/wm/public/activation_client.h"
+
+namespace examples {
+
+namespace {
+
+class WMFocusRules : public wm::FocusRules {
+ public:
+ WMFocusRules(mojo::WindowManagerApp* window_manager_app,
+ mojo::View* window_container)
+ : window_container_(window_container),
+ window_manager_app_(window_manager_app) {}
+ virtual ~WMFocusRules() {}
+
+ private:
+ // Overridden from wm::FocusRules:
+ virtual bool IsToplevelWindow(aura::Window* window) const override {
+ return mojo::WindowManagerApp::GetViewForWindow(window)->parent() ==
+ window_container_;
+ }
+ virtual bool CanActivateWindow(aura::Window* window) const override {
+ return mojo::WindowManagerApp::GetViewForWindow(window)->parent() ==
+ window_container_;
+ }
+ virtual bool CanFocusWindow(aura::Window* window) const override {
+ return true;
+ }
+ virtual aura::Window* GetToplevelWindow(aura::Window* window) const override {
+ mojo::View* view = mojo::WindowManagerApp::GetViewForWindow(window);
+ while (view->parent() != window_container_) {
+ view = view->parent();
+ // Unparented hierarchy, there is no "top level" window.
+ if (!view)
+ return NULL;
+ }
+
+ return window_manager_app_->GetWindowForViewId(view->id());
+ }
+ virtual aura::Window* GetActivatableWindow(
+ aura::Window* window) const override {
+ return GetToplevelWindow(window);
+ }
+ virtual aura::Window* GetFocusableWindow(
+ aura::Window* window) const override {
+ return window;
+ }
+ virtual aura::Window* GetNextActivatableWindow(
+ aura::Window* ignore) const override {
+ aura::Window* activatable = GetActivatableWindow(ignore);
+ const aura::Window::Windows& children = activatable->parent()->children();
+ for (aura::Window::Windows::const_reverse_iterator it = children.rbegin();
+ it != children.rend(); ++it) {
+ if (*it != ignore)
+ return *it;
+ }
+ return NULL;
+ }
+
+ mojo::View* window_container_;
+ mojo::WindowManagerApp* window_manager_app_;
+
+ DISALLOW_COPY_AND_ASSIGN(WMFocusRules);
+};
+
+} // namespace
+
+class SimpleWM : public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate,
+ public mojo::WindowManagerDelegate,
+ public mojo::ViewObserver {
+ public:
+ SimpleWM()
+ : window_manager_app_(new mojo::WindowManagerApp(this, this)),
+ view_manager_(NULL),
+ root_(NULL),
+ window_container_(NULL),
+ next_window_origin_(10, 10) {}
+ virtual ~SimpleWM() {}
+
+ private:
+ // Overridden from mojo::ApplicationDelegate:
+ virtual void Initialize(mojo::ApplicationImpl* impl) override {
+ window_manager_app_->Initialize(impl);
+ }
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ window_manager_app_->ConfigureIncomingConnection(connection);
+ return true;
+ }
+
+ // Overridden from mojo::ViewManagerDelegate:
+ virtual void OnEmbed(
+ mojo::ViewManager* view_manager,
+ mojo::View* root,
+ mojo::ServiceProviderImpl* exported_services,
+ scoped_ptr<mojo::ServiceProvider> remote_service_provider) override {
+ view_manager_ = view_manager;
+ root_ = root;
+
+ window_container_ = mojo::View::Create(view_manager_);
+ window_container_->SetBounds(root_->bounds());
+ root_->AddChild(window_container_);
+
+ window_manager_app_->InitFocus(new WMFocusRules(window_manager_app_.get(),
+ window_container_));
+ }
+ virtual void OnViewManagerDisconnected(
+ mojo::ViewManager* view_manager) override {
+ view_manager_ = NULL;
+ root_ = NULL;
+ }
+
+ // Overridden from mojo::WindowManagerDelegate:
+ virtual void Embed(
+ const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> service_provider) override {
+ mojo::View* app_view = NULL;
+ mojo::View* frame_view = CreateTopLevelWindow(&app_view);
+ window_container_->AddChild(frame_view);
+
+ // TODO(beng): We're dropping the |service_provider| passed from the client
+ // on the floor here and passing our own. Seems like we should
+ // be sending both. I'm not yet sure how this sould work for
+ // N levels of proxying.
+ app_view->Embed(url, scoped_ptr<mojo::ServiceProviderImpl>(
+ new mojo::ServiceProviderImpl).Pass());
+ }
+ virtual void DispatchEvent(mojo::EventPtr event) override {}
+
+ // Overridden from mojo::ViewObserver:
+ virtual void OnViewInputEvent(mojo::View* view,
+ const mojo::EventPtr& event) override {
+ if (event->action == mojo::EVENT_TYPE_MOUSE_RELEASED &&
+ event->flags & mojo::EVENT_FLAGS_RIGHT_MOUSE_BUTTON &&
+ view->parent() == window_container_) {
+ CloseWindow(view);
+ }
+ }
+ virtual void OnViewDestroyed(mojo::View* view) override {
+ view->RemoveObserver(this);
+ }
+
+ void CloseWindow(mojo::View* view) {
+ mojo::View* first_child = view->children().front();
+ first_child->Destroy();
+ view->Destroy();
+ next_window_origin_.Offset(-50, -50);
+ }
+
+ mojo::View* CreateTopLevelWindow(mojo::View** app_view) {
+ mojo::View* frame_view = mojo::View::Create(view_manager_);
+ frame_view->SetBounds(gfx::Rect(next_window_origin_, gfx::Size(400, 400)));
+ next_window_origin_.Offset(50, 50);
+
+ aura::client::ActivationClient* client = aura::client::GetActivationClient(
+ window_manager_app_->host()->window());
+ new FrameController(frame_view, app_view, client,
+ window_manager_app_.get());
+ return frame_view;
+ }
+
+ scoped_ptr<mojo::WindowManagerApp> window_manager_app_;
+
+ mojo::ViewManager* view_manager_;
+ mojo::View* root_;
+ mojo::View* window_container_;
+
+ gfx::Point next_window_origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleWM);
+};
+
+} // namespace examples
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ViewsInit views_init;
+ mojo::ApplicationRunnerChromium runner(new examples::SimpleWM);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/gles2/BUILD.gn b/mojo/gles2/BUILD.gn
new file mode 100644
index 0000000..14e1b8e
--- /dev/null
+++ b/mojo/gles2/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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+config("mojo_use_gles2") {
+ defines = [ "MOJO_USE_GLES2_IMPL" ]
+}
+
+config("gles2_use_mojo") {
+ defines = [ "GLES2_USE_MOJO" ]
+}
+
+# GYP version: mojo/mojo_base.gyp:mojo_gles2_impl
+component("gles2") {
+ output_name = "mojo_gles2_impl"
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//gpu/command_buffer/client",
+ "//gpu/command_buffer/common",
+ "//gpu/command_buffer/client:gles2_implementation",
+ "//gpu/command_buffer/client:gles2_interface",
+ "//mojo/public/c/system:for_component",
+ "//mojo/services/gles2:bindings",
+ "//mojo/services/gles2:interfaces",
+ "//mojo/environment:chromium",
+ ]
+
+ defines = [
+ "GL_GLEXT_PROTOTYPES",
+ "MOJO_GLES2_IMPL_IMPLEMENTATION",
+ "MOJO_GLES2_IMPLEMENTATION",
+ ]
+
+ configs += [
+ ":gles2_use_mojo",
+ ":mojo_use_gles2",
+ ]
+ public_configs = [ ":gles2_use_mojo" ]
+ all_dependent_configs = [ ":mojo_use_gles2" ]
+
+ sources = [
+ "command_buffer_client_impl.cc",
+ "command_buffer_client_impl.h",
+ "gles2_impl_export.h",
+ "gles2_impl.cc",
+ "gles2_context.cc",
+ "gles2_context.h",
+ ]
+}
diff --git a/mojo/gles2/DEPS b/mojo/gles2/DEPS
new file mode 100644
index 0000000..db90643
--- /dev/null
+++ b/mojo/gles2/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+gpu/command_buffer/client",
+ "+gpu/command_buffer/common",
+ "+gpu/GLES2",
+]
diff --git a/mojo/gles2/README.md b/mojo/gles2/README.md
new file mode 100644
index 0000000..94b3997
--- /dev/null
+++ b/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/mojo/gles2/command_buffer_client_impl.cc b/mojo/gles2/command_buffer_client_impl.cc
new file mode 100644
index 0000000..5c317a0
--- /dev/null
+++ b/mojo/gles2/command_buffer_client_impl.cc
@@ -0,0 +1,286 @@
+// Copyright 2014 The Chromium 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/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;
+}
+
+} // namespace
+
+CommandBufferDelegate::~CommandBufferDelegate() {}
+
+void CommandBufferDelegate::ContextLost() {}
+
+class CommandBufferClientImpl::SyncClientImpl
+ : public InterfaceImpl<CommandBufferSyncClient> {
+ public:
+ SyncClientImpl() : initialized_successfully_(false) {}
+
+ bool WaitForInitialization() {
+ if (!WaitForIncomingMethodCall())
+ return false;
+ return initialized_successfully_;
+ }
+
+ CommandBufferStatePtr WaitForProgress() {
+ if (!WaitForIncomingMethodCall())
+ return CommandBufferStatePtr();
+ return command_buffer_state_.Pass();
+ }
+
+ private:
+ // CommandBufferSyncClient methods:
+ virtual void DidInitialize(bool success) override {
+ initialized_successfully_ = success;
+ }
+ virtual void DidMakeProgress(CommandBufferStatePtr state) override {
+ command_buffer_state_ = state.Pass();
+ }
+
+ bool initialized_successfully_;
+ CommandBufferStatePtr command_buffer_state_;
+};
+
+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),
+ 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();
+
+ CommandBufferSyncClientPtr sync_client;
+ sync_client_impl_.reset(
+ WeakBindToProxy(new SyncClientImpl(), &sync_client, async_waiter_));
+
+ command_buffer_->Initialize(sync_client.Pass(), duped.Pass());
+
+ // Wait for DidInitialize to come on the sync client pipe.
+ if (!sync_client_impl_->WaitForInitialization()) {
+ VLOG(1) << "Channel encountered error while creating command buffer";
+ return false;
+ }
+ return true;
+}
+
+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(jamesr): Optimize this.
+ WaitForGetOffsetInRange(last_put_offset_, last_put_offset_);
+ return 0;
+}
+
+uint32 CommandBufferClientImpl::InsertFutureSyncPoint() {
+ // TODO(jamesr): Optimize this.
+ WaitForGetOffsetInRange(last_put_offset_, last_put_offset_);
+ return 0;
+}
+
+void CommandBufferClientImpl::RetireSyncPoint(uint32 sync_point) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+}
+
+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();
+}
+
+uint32 CommandBufferClientImpl::CreateStreamTexture(uint32 texture_id) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+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);
+
+ CommandBufferStatePtr state = sync_client_impl_->WaitForProgress();
+ if (!state) {
+ VLOG(1) << "Channel encountered error while waiting for command buffer";
+ // TODO(piman): is it ok for this to re-enter?
+ DidDestroy();
+ return;
+ }
+
+ if (state->generation - last_state_.generation < 0x80000000U)
+ last_state_ = state.To<State>();
+}
+
+} // namespace gles2
+} // namespace mojo
diff --git a/mojo/gles2/command_buffer_client_impl.h b/mojo/gles2/command_buffer_client_impl.h
new file mode 100644
index 0000000..1c8ca53
--- /dev/null
+++ b/mojo/gles2/command_buffer_client_impl.h
@@ -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.
+
+#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 {
+namespace gles2 {
+class CommandBufferClientImpl;
+
+class CommandBufferDelegate {
+ public:
+ virtual ~CommandBufferDelegate();
+ virtual void ContextLost();
+};
+
+class CommandBufferClientImpl : public CommandBufferClient,
+ 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 uint32 InsertFutureSyncPoint() override;
+ virtual void RetireSyncPoint(uint32 sync_point) 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 uint32 CreateStreamTexture(uint32 texture_id) override;
+
+ private:
+ class SyncClientImpl;
+
+ // CommandBufferClient implementation:
+ virtual void DidDestroy() override;
+ virtual void LostContext(int32_t lost_reason) override;
+
+ // ErrorHandler implementation:
+ virtual void OnConnectionError() override;
+
+ void TryUpdateState();
+ void MakeProgressAndUpdateState();
+
+ gpu::CommandBufferSharedState* shared_state() const { return shared_state_; }
+
+ CommandBufferDelegate* delegate_;
+ CommandBufferPtr command_buffer_;
+ scoped_ptr<SyncClientImpl> sync_client_impl_;
+
+ State last_state_;
+ mojo::ScopedSharedBufferHandle shared_state_handle_;
+ gpu::CommandBufferSharedState* shared_state_;
+ int32 last_put_offset_;
+ int32 next_transfer_buffer_id_;
+
+ const MojoAsyncWaiter* async_waiter_;
+};
+
+} // gles2
+} // mojo
+
+#endif // MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_
diff --git a/mojo/gles2/gles2_context.cc b/mojo/gles2/gles2_context.cc
new file mode 100644
index 0000000..c454aed
--- /dev/null
+++ b/mojo/gles2/gles2_context.cc
@@ -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.
+
+#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,
+ void* closure)
+ : command_buffer_(this, async_waiter, command_buffer_handle.Pass()),
+ lost_callback_(lost_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::ContextLost() { lost_callback_(closure_); }
+
+} // namespace gles2
+} // namespace mojo
diff --git a/mojo/gles2/gles2_context.h b/mojo/gles2/gles2_context.h
new file mode 100644
index 0000000..0bbb754
--- /dev/null
+++ b/mojo/gles2/gles2_context.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_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,
+ void* closure);
+ virtual ~GLES2Context();
+ bool Initialize();
+
+ gpu::gles2::GLES2Interface* interface() const {
+ return implementation_.get();
+ }
+ gpu::ContextSupport* context_support() const { return implementation_.get(); }
+
+ private:
+ virtual void ContextLost() 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_;
+ void* closure_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(GLES2Context);
+};
+
+} // namespace gles2
+} // namespace mojo
+
+#endif // MOJO_GLES2_GLES2_CONTEXT_H_
diff --git a/mojo/gles2/gles2_impl.cc b/mojo/gles2/gles2_impl.cc
new file mode 100644
index 0000000..8cfcff1
--- /dev/null
+++ b/mojo/gles2/gles2_impl.cc
@@ -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.
+
+#include "mojo/public/c/gles2/gles2.h"
+
+#include "base/lazy_instance.h"
+#include "base/threading/thread_local.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "mojo/gles2/gles2_context.h"
+#include "mojo/gles2/gles2_impl_export.h"
+
+using mojo::gles2::GLES2Context;
+
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<gpu::gles2::GLES2Interface> >::Leaky
+ g_gpu_interface;
+
+} // namespace
+
+extern "C" {
+MojoGLES2Context MojoGLES2CreateContext(MojoHandle handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure,
+ const MojoAsyncWaiter* async_waiter) {
+ mojo::MessagePipeHandle mph(handle);
+ mojo::ScopedMessagePipeHandle scoped_handle(mph);
+ scoped_ptr<GLES2Context> client(new GLES2Context(
+ async_waiter, scoped_handle.Pass(), lost_callback, closure));
+ if (!client->Initialize())
+ client.reset();
+ return client.release();
+}
+
+void MojoGLES2DestroyContext(MojoGLES2Context context) {
+ delete static_cast<GLES2Context*>(context);
+}
+
+void MojoGLES2MakeCurrent(MojoGLES2Context context) {
+ gpu::gles2::GLES2Interface* interface = NULL;
+ if (context) {
+ GLES2Context* client = static_cast<GLES2Context*>(context);
+ interface = client->interface();
+ DCHECK(interface);
+ }
+ g_gpu_interface.Get().Set(interface);
+}
+
+void MojoGLES2SwapBuffers() {
+ DCHECK(g_gpu_interface.Get().Get());
+ g_gpu_interface.Get().Get()->SwapBuffers();
+}
+
+void* MojoGLES2GetGLES2Interface(MojoGLES2Context context) {
+ return static_cast<GLES2Context*>(context)->interface();
+}
+
+void* MojoGLES2GetContextSupport(MojoGLES2Context context) {
+ return static_cast<GLES2Context*>(context)->context_support();
+}
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType gl##Function PARAMETERS { \
+ DCHECK(g_gpu_interface.Get().Get()); \
+ return g_gpu_interface.Get().Get()->Function ARGUMENTS; \
+ }
+#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h"
+#undef VISIT_GL_CALL
+
+} // extern "C"
diff --git a/mojo/gles2/gles2_impl_export.h b/mojo/gles2/gles2_impl_export.h
new file mode 100644
index 0000000..3299ced
--- /dev/null
+++ b/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/mojo/go/BUILD.gn b/mojo/go/BUILD.gn
new file mode 100644
index 0000000..e190379
--- /dev/null
+++ b/mojo/go/BUILD.gn
@@ -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.
+
+import("//build/go/rules.gni")
+
+group("go") {
+ deps = [
+ ":system_test",
+ ]
+}
+
+go_test_binary("system_test") {
+ sources = [
+ "tests/system_test.go",
+ ]
+ static_library_sources = [
+ "c_embedder/c_embedder.cc",
+ "c_embedder/c_embedder.h",
+ ]
+ deps = [
+ "//mojo/edk/system",
+ ]
+}
diff --git a/mojo/go/c_embedder/c_embedder.cc b/mojo/go/c_embedder/c_embedder.cc
new file mode 100644
index 0000000..7d6a2e0
--- /dev/null
+++ b/mojo/go/c_embedder/c_embedder.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/go/c_embedder/c_embedder.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void InitializeMojoEmbedder() {
+ mojo::embedder::Init(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/mojo/go/c_embedder/c_embedder.h b/mojo/go/c_embedder/c_embedder.h
new file mode 100644
index 0000000..f2961d6
--- /dev/null
+++ b/mojo/go/c_embedder/c_embedder.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_GO_C_EMBEDDER_C_EMBEDDER_H_
+#define MOJO_GO_C_EMBEDDER_C_EMBEDDER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+__attribute__((visibility("default"))) void InitializeMojoEmbedder();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_GO_C_EMBEDDER_C_EMBEDDER_H__
diff --git a/mojo/go/system/embedder/embedder.go b/mojo/go/system/embedder/embedder.go
new file mode 100644
index 0000000..6f605df
--- /dev/null
+++ b/mojo/go/system/embedder/embedder.go
@@ -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.
+
+package embedder;
+
+/*
+#include "mojo/go/c_embedder/c_embedder.h"
+*/
+import "C"
+
+func InitializeMojoEmbedder() {
+ C.InitializeMojoEmbedder();
+}
diff --git a/mojo/go/system/impl/core_impl.go b/mojo/go/system/impl/core_impl.go
new file mode 100644
index 0000000..9341a06
--- /dev/null
+++ b/mojo/go/system/impl/core_impl.go
@@ -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.
+
+package impl
+
+/*
+#include "mojo/public/platform/native/system_thunks.h"
+*/
+import "C"
+import "mojo/public/go/mojo/edk/system"
+
+type CoreImpl struct {
+}
+
+func (c CoreImpl) GetTimeTicksNow() int64 {
+ return (int64)(C.MojoGetTimeTicksNow())
+}
+
+var lazyInstance *CoreImpl = nil
+
+func GetCore() system.Core {
+ if lazyInstance == nil {
+ lazyInstance = new(CoreImpl)
+ }
+ return lazyInstance
+}
diff --git a/mojo/go/tests/system_test.go b/mojo/go/tests/system_test.go
new file mode 100644
index 0000000..549d01d
--- /dev/null
+++ b/mojo/go/tests/system_test.go
@@ -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.
+
+package tests
+
+import "mojo/go/system/embedder"
+import "mojo/go/system/impl"
+import "mojo/public/go/mojo/edk/system"
+import "testing"
+
+func Init() {
+ embedder.InitializeMojoEmbedder()
+}
+
+func TestGetTimeTicksNow(t *testing.T) {
+ Init()
+ var c system.Core = impl.GetCore()
+ x := c.GetTimeTicksNow()
+ if x < 10 {
+ t.Error("Invalid GetTimeTicksNow return value")
+ }
+}
diff --git a/mojo/mojo.gyp b/mojo/mojo.gyp
new file mode 100644
index 0000000..30ffcf2
--- /dev/null
+++ b/mojo/mojo.gyp
@@ -0,0 +1,704 @@
+# 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.
+
+{
+ 'target_defaults': {
+ 'conditions': [
+ ['mojo_shell_debug_url != ""', {
+ 'defines': [
+ 'MOJO_SHELL_DEBUG=1',
+ 'MOJO_SHELL_DEBUG_URL="<(mojo_shell_debug_url)"',
+ ],
+ }],
+ ],
+ },
+ 'includes': [
+ 'mojo_apps.gypi',
+ 'mojo_examples.gypi',
+ 'mojo_services.gypi',
+ 'mojo_variables.gypi',
+ ],
+ 'targets': [
+ {
+ # GN version: //mojo
+ 'target_name': 'mojo',
+ 'type': 'none',
+ 'dependencies': [
+ 'mojo_application_manager',
+ 'mojo_application_manager_unittests',
+ 'mojo_apps_js_unittests',
+ 'mojo_base.gyp:mojo_base',
+ 'mojo_clipboard',
+ 'mojo_clipboard_unittests',
+ 'mojo_compositor_app',
+ 'mojo_content_handler_demo',
+ 'mojo_echo_client',
+ 'mojo_echo_service',
+ 'mojo_example_apptests',
+ 'mojo_example_service',
+ 'mojo_geometry_lib',
+ 'mojo_html_viewer',
+ 'mojo_js_content_handler',
+ 'mojo_js_standalone',
+ 'mojo_js_unittests',
+ 'mojo_native_viewport_service',
+ 'mojo_network_service',
+ 'mojo_pepper_container_app',
+ 'mojo_png_viewer',
+ 'mojo_sample_app',
+ 'mojo_shell',
+ 'mojo_shell_lib',
+ 'mojo_shell_tests',
+ 'mojo_surfaces_app',
+ 'mojo_surfaces_app',
+ 'mojo_surfaces_child_app',
+ 'mojo_surfaces_child_gl_app',
+ 'mojo_surfaces_lib',
+ 'mojo_surfaces_lib_unittests',
+ 'mojo_surfaces_service',
+ 'mojo_test_app',
+ 'mojo_test_request_tracker_app',
+ '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_core_window_manager',
+ 'mojo_core_window_manager_unittests',
+ 'mojo_demo_launcher',
+ 'mojo_embedded_app',
+ 'mojo_keyboard',
+ 'mojo_media_viewer',
+ 'mojo_nesting_app',
+ 'mojo_window_manager',
+ 'mojo_wm_flow_app',
+ 'mojo_wm_flow_embedded',
+ 'mojo_wm_flow_init',
+ 'mojo_wm_flow_wm',
+ 'mojo_view_manager',
+ 'mojo_view_manager_unittests',
+ ],
+ }],
+ ['OS == "linux"', {
+ 'dependencies': [
+ 'mojo_external_application_tests',
+ ],
+ }],
+ ['component != "shared_library" and OS == "linux"', {
+ 'dependencies': [
+ 'mojo_python_bindings',
+ 'mojo_python_embedder',
+ 'mojo_python_system',
+ 'mojo_python',
+ ],
+ }],
+ ]
+ },
+ {
+ # GN version: //mojo/shell:external_service_bindings
+ '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_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/spy
+ 'target_name': 'mojo_spy',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../net/net.gyp:http_server',
+ '../url/url.gyp:url_lib',
+ 'mojo_application_manager',
+ ],
+ 'variables': {
+ 'mojom_base_output_dir': 'mojo',
+ },
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'sources': [
+ 'spy/public/spy.mojom',
+ 'spy/common.h',
+ 'spy/spy.cc',
+ 'spy/spy.h',
+ 'spy/spy_server_impl.h',
+ 'spy/spy_server_impl.cc',
+ 'spy/websocket_server.cc',
+ 'spy/websocket_server.h',
+ ],
+ },
+ {
+ # GN version: //mojo/shell:lib
+ 'target_name': 'mojo_shell_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_static',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_gles2_impl',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_external_service_bindings',
+ 'mojo_network_bindings',
+ 'mojo_spy',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'sources': [
+ '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/dynamic_application_loader.cc',
+ 'shell/dynamic_application_loader.h',
+ 'shell/dynamic_service_runner.h',
+ 'shell/external_application_listener_posix.cc',
+ 'shell/external_application_listener_win.cc',
+ 'shell/external_application_listener.h',
+ 'shell/external_application_registrar.mojom',
+ 'shell/incoming_connection_listener_posix.cc',
+ 'shell/incoming_connection_listener_posix.h',
+ 'shell/init.cc',
+ 'shell/init.h',
+ 'shell/in_process_dynamic_service_runner.cc',
+ 'shell/in_process_dynamic_service_runner.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/switches.cc',
+ 'shell/switches.h',
+ 'shell/task_runners.cc',
+ 'shell/task_runners.h',
+ 'shell/test_child_process.cc',
+ 'shell/test_child_process.h',
+ 'shell/ui_application_loader_android.cc',
+ 'shell/ui_application_loader_android.h',
+ ],
+ 'conditions': [
+ ['component=="shared_library"', {
+ 'dependencies': [
+ '../ui/gl/gl.gyp:gl',
+ ],
+ }],
+ ['OS=="linux"', {
+ 'sources': [
+ 'shell/external_application_registrar_connection.cc',
+ 'shell/external_application_registrar_connection.h',
+ ],
+ }],
+ ['OS=="android"', {
+ 'dependencies': [
+ 'mojo_network_service_lib',
+ 'mojo_native_viewport_service_lib',
+ ],
+ 'sources': [
+ 'shell/network_application_loader.cc',
+ 'shell/network_application_loader.h',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/shell:test_support
+ 'target_name': 'mojo_shell_test_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_shell_lib',
+ ],
+ 'sources': [
+ 'shell/shell_test_helper.cc',
+ 'shell/shell_test_helper.h',
+ ],
+ },
+ {
+ # GN version: //mojo/shell
+ 'target_name': 'mojo_shell',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_shell_lib',
+ ],
+ 'sources': [
+ 'shell/desktop/mojo_main.cc',
+ ],
+ 'conditions': [
+ ['component=="shared_library"', {
+ 'dependencies': [
+ '../ui/gfx/gfx.gyp:gfx',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/shell:mojo_shell_tests
+ 'target_name': 'mojo_shell_tests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ '../net/net.gyp:net_test_support',
+ '../url/url.gyp:url_lib',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_shell_lib',
+ 'mojo_test_app',
+ 'mojo_test_request_tracker_app',
+ 'mojo_test_service_bindings',
+ ],
+ 'sources': [
+ 'shell/child_process_host_unittest.cc',
+ 'shell/dynamic_application_loader_unittest.cc',
+ 'shell/in_process_dynamic_service_runner_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 == "android"', {
+ 'dependencies': [
+ '../testing/android/native_test.gyp:native_test_native_code',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/application_manager
+ 'target_name': 'mojo_application_manager',
+ 'type': '<(component)',
+ 'defines': [
+ 'MOJO_APPLICATION_MANAGER_IMPLEMENTATION',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../url/url.gyp:url_lib',
+ 'mojo_content_handler_bindings',
+ 'mojo_network_bindings',
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ '<(mojo_system_for_component)',
+ ],
+ 'sources': [
+ 'application_manager/application_loader.cc',
+ 'application_manager/application_loader.h',
+ 'application_manager/application_manager.cc',
+ 'application_manager/application_manager.h',
+ 'application_manager/application_manager_export.h',
+ 'application_manager/background_shell_application_loader.cc',
+ 'application_manager/background_shell_application_loader.h',
+ ],
+ 'export_dependent_settings': [
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_network_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/application_manager:mojo_application_manager_unittests
+ 'target_name': 'mojo_application_manager_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ '../url/url.gyp:url_lib',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_base.gyp:mojo_run_all_unittests',
+ 'mojo_base.gyp:mojo_application_chromium',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'sources': [
+ 'application_manager/application_manager_unittest.cc',
+ 'application_manager/background_shell_application_loader_unittest.cc',
+ 'application_manager/test.mojom',
+ ],
+ },
+ {
+ # GN version: //mojo/cc
+ 'target_name': 'mojo_cc_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../gpu/gpu.gyp:gles2_implementation',
+ 'mojo_geometry_lib',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_surfaces_bindings',
+ ],
+ 'sources': [
+ 'cc/context_provider_mojo.cc',
+ 'cc/context_provider_mojo.h',
+ 'cc/output_surface_mojo.cc',
+ 'cc/output_surface_mojo.h',
+ ],
+ },
+ {
+ # GN version: //mojo/bindings/js/tests:mojo_js_unittests
+ 'target_name': 'mojo_js_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../gin/gin.gyp:gin_test',
+ 'mojo_base.gyp:mojo_common_test_support',
+ 'mojo_base.gyp:mojo_environment_standalone',
+ 'mojo_base.gyp:mojo_js_bindings_lib',
+ 'mojo_base.gyp:mojo_public_test_interfaces',
+ 'mojo_base.gyp:mojo_run_all_unittests',
+ 'mojo_base.gyp:mojo_utility',
+ ],
+ 'sources': [
+ 'bindings/js/tests/run_js_tests.cc',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'mojo_native_viewport_java',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_java',
+ ],
+ 'variables': {
+ 'java_in_dir': '<(DEPTH)/mojo/services/native_viewport/android',
+ },
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ 'target_name': 'libmojo_shell',
+ 'type': 'shared_library',
+ 'dependencies': [
+ '../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_base.gyp:mojo_application_bindings',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_base.gyp:mojo_jni_headers',
+ 'mojo_shell_lib',
+ ],
+ 'sources': [
+ 'shell/android/library_loader.cc',
+ 'shell/android/mojo_main.cc',
+ 'shell/android/mojo_main.h',
+ ],
+ },
+ {
+ 'target_name': 'mojo_shell_apk',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_java',
+ '../net/net.gyp:net_java',
+ 'mojo_native_viewport_java',
+ 'libmojo_shell',
+ ],
+ 'variables': {
+ 'apk_name': 'MojoShell',
+ 'java_in_dir': '<(DEPTH)/mojo/shell/android/apk',
+ 'resource_dir': '<(DEPTH)/mojo/shell/android/apk/res',
+ 'native_lib_target': 'libmojo_shell',
+ },
+ 'includes': [ '../build/java_apk.gypi' ],
+ }
+ ],
+ }],
+ ['OS=="linux"', {
+ 'targets': [
+ {
+ # GN version: //mojo/shell:mojo_external_application_tests
+ 'target_name': 'mojo_external_application_tests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ '../net/net.gyp:net_test_support',
+ '../url/url.gyp:url_lib',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_shell_lib',
+ ],
+ 'sources': [
+ 'shell/incoming_connection_listener_unittest.cc',
+ 'shell/external_application_listener_unittest.cc',
+ 'shell/external_application_test_main.cc',
+ ],
+ },
+ ],
+ }],
+ ['use_aura==1', {
+ 'targets': [
+ {
+ # GN version: //mojo/aura
+ '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',
+ 'mojo_cc_support',
+ 'mojo_native_viewport_bindings',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ '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',
+ ],
+ },
+ {
+ # GN version: //mojo/views
+ 'target_name': 'mojo_views_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../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',
+ 'mojo_views_support_internal',
+ 'mojo_view_manager_bindings',
+ ],
+ 'sources': [
+ 'views/input_method_mojo_linux.cc',
+ 'views/input_method_mojo_linux.h',
+ 'views/native_widget_view_manager.cc',
+ 'views/native_widget_view_manager.h',
+ 'views/views_init.cc',
+ 'views/views_init.h',
+ ],
+ },
+ {
+ # GN version: //mojo/views:views_internal
+ 'target_name': 'mojo_views_support_internal',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../skia/skia.gyp:skia',
+ '../third_party/icu/icu.gyp:icui18n',
+ '../third_party/icu/icu.gyp:icuuc',
+ '../ui/base/ui_base.gyp:ui_base',
+ '../ui/gfx/gfx.gyp:gfx',
+ ],
+ 'sources': [
+ 'views/mojo_views_export.h',
+ 'views/views_init_internal.cc',
+ 'views/views_init_internal.h',
+ ],
+ 'defines': [
+ 'MOJO_VIEWS_IMPLEMENTATION',
+ ],
+ },
+ ],
+ }],
+ ['component!="shared_library" and OS=="linux"', {
+ 'targets': [
+ {
+ # GN version: //mojo/public/python:system
+ 'target_name': 'mojo_python_system',
+ 'variables': {
+ 'python_base_module': 'mojo',
+ 'python_cython_module': 'system',
+ },
+ 'sources': [
+ 'public/python/mojo/c_core.pxd',
+ 'public/python/mojo/c_environment.pxd',
+ 'public/python/mojo/system.pyx',
+ 'public/python/src/python_system_helper.cc',
+ 'public/python/src/python_system_helper.h',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_environment_standalone',
+ 'mojo_base.gyp:mojo_system',
+ 'mojo_base.gyp:mojo_utility',
+ ],
+ 'includes': [ '../third_party/cython/cython_compiler.gypi' ],
+ },
+ {
+ # GN version: //mojo/python:embedder
+ 'target_name': 'mojo_python_embedder',
+ 'type': 'loadable_module',
+ 'variables': {
+ 'python_base_module': 'mojo',
+ 'python_cython_module': 'embedder',
+ },
+ 'sources': [
+ 'python/system/mojo/embedder.pyx',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_system_impl',
+ ],
+ 'includes': [ '../third_party/cython/cython_compiler.gypi' ],
+ },
+ {
+ # GN version: //mojo/public/python:bindings
+ 'target_name': 'mojo_python_bindings',
+ 'type': 'none',
+ 'variables': {
+ 'python_base_module': 'mojo/bindings',
+ },
+ 'sources': [
+ 'public/python/mojo/bindings/__init__.py',
+ 'public/python/mojo/bindings/descriptor.py',
+ 'public/python/mojo/bindings/messaging.py',
+ 'public/python/mojo/bindings/promise.py',
+ 'public/python/mojo/bindings/reflection.py',
+ 'public/python/mojo/bindings/serialization.py',
+ ],
+ 'dependencies': [
+ 'mojo_python_system',
+ ],
+ 'includes': [ '../third_party/cython/python_module.gypi' ],
+ },
+ {
+ # GN version: //mojo/python
+ 'target_name': 'mojo_python',
+ 'type': 'none',
+ 'variables': {
+ 'python_base_module': 'mojo',
+ },
+ 'sources': [
+ 'public/python/mojo/__init__.py',
+ ],
+ 'dependencies': [
+ 'mojo_python_bindings',
+ 'mojo_python_embedder',
+ 'mojo_python_system',
+ ],
+ # The python module need to be copied to their destinations
+ 'actions': [
+ {
+ 'action_name': 'Copy system module.',
+ 'inputs': [
+ '<(DEPTH)/build/cp.py',
+ '<(PRODUCT_DIR)/libmojo_python_system.so',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/python/mojo/system.so',
+ ],
+ 'action': [
+ 'python',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ]
+ },
+ {
+ 'action_name': 'Copy embedder module.',
+ 'inputs': [
+ '<(DEPTH)/build/cp.py',
+ '<(PRODUCT_DIR)/libmojo_python_embedder.so',
+ ],
+ 'outputs': [
+ '<(PRODUCT_DIR)/python/mojo/embedder.so',
+ ],
+ 'action': [
+ 'python',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ]
+ },
+ ],
+ 'includes': [ '../third_party/cython/python_module.gypi' ],
+ },
+ ],
+ }],
+ ['component!="shared_library" and OS=="linux" and test_isolation_mode!="noop"', {
+ 'targets': [
+ {
+ 'target_name': 'mojo_python_unittests_run',
+ 'type': 'none',
+ 'dependencies': [
+ 'mojo_python',
+ 'mojo_base.gyp:mojo_public_test_interfaces',
+ ],
+ 'includes': [
+ '../build/isolate.gypi',
+ ],
+ 'sources': [
+ 'mojo_python_unittests.isolate',
+ ],
+ },
+ ],
+ }],
+ ['test_isolation_mode != "noop"', {
+ 'targets': [
+ {
+ 'target_name': 'mojo_js_unittests_run',
+ 'type': 'none',
+ 'dependencies': [
+ 'mojo_js_unittests',
+ ],
+ 'includes': [
+ '../build/isolate.gypi',
+ ],
+ 'sources': [
+ 'mojo_js_unittests.isolate',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/mojo/mojo_apps.gypi b/mojo/mojo_apps.gypi
new file mode 100644
index 0000000..de1919a
--- /dev/null
+++ b/mojo/mojo_apps.gypi
@@ -0,0 +1,148 @@
+# Copyright 2014 The Chromium 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': [
+ {
+ # GN version: //mojo/apps/js
+ # //mojo/apps/js/bindings
+ # //mojo/apps/js/bindings/gl
+ 'target_name': 'mojo_js_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../gin/gin.gyp:gin',
+ '../ui/gl/gl.gyp:gl',
+ '../v8/tools/gyp/v8.gyp:v8',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_base.gyp:mojo_js_bindings_lib',
+ 'mojo_native_viewport_bindings',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'export_dependent_settings': [
+ '../base/base.gyp:base',
+ '../gin/gin.gyp:gin',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_native_viewport_bindings',
+ ],
+ '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/gl/context.cc',
+ 'apps/js/bindings/gl/context.h',
+ 'apps/js/bindings/gl/module.cc',
+ 'apps/js/bindings/gl/module.h',
+ 'apps/js/bindings/monotonic_clock.cc',
+ 'apps/js/bindings/monotonic_clock.h',
+ ],
+ },
+ {
+ # GN version: //mojo/apps/js/test:js_to_cpp_bindings
+ '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_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/apps/js/test/mojo_apps_js_unittests
+ 'target_name': 'mojo_apps_js_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../gin/gin.gyp:gin_test',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_common_test_support',
+ 'mojo_base.gyp:mojo_public_test_interfaces',
+ 'mojo_base.gyp:mojo_run_all_unittests',
+ 'mojo_apps_js_bindings',
+ 'mojo_js_lib',
+ ],
+ 'sources': [
+ 'apps/js/test/handle_unittest.cc',
+ 'apps/js/test/js_to_cpp_unittest.cc',
+ 'apps/js/test/run_apps_js_tests.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/apps/js/test:mojo_js_apps_lib
+ 'target_name': 'mojo_js_apps_lib',
+ 'type': 'static_library',
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_apps_js_bindings',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_js_lib',
+ ],
+ 'sources': [
+ 'apps/js/application_delegate_impl.cc',
+ 'apps/js/js_app.cc',
+ 'apps/js/mojo_module.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/apps/js:mojo_js_content_handler
+ 'target_name': 'mojo_js_content_handler',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_content_handler_bindings',
+ 'mojo_js_apps_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'apps/js/content_handler_impl.cc',
+ 'apps/js/content_handler_main.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/apps/js:mojo_js_standalone
+ 'target_name': 'mojo_js_standalone',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_js_apps_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'apps/js/standalone_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',
+ ],
+ 'sources': [
+ 'mojo_apps_js_unittests.isolate',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/mojo/mojo_apps_js_unittests.isolate b/mojo/mojo_apps_js_unittests.isolate
new file mode 100644
index 0000000..6921a46
--- /dev/null
+++ b/mojo/mojo_apps_js_unittests.isolate
@@ -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.
+{
+ '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',
+ ],
+ 'files': [
+ '../gin/test/expect.js',
+ '../testing/test_env.py',
+ '<(PRODUCT_DIR)/gen/mojo/public/interfaces/bindings/tests/',
+ '<(PRODUCT_DIR)/mojo_apps_js_unittests<(EXECUTABLE_SUFFIX)',
+ 'apps/js/',
+ 'bindings/js/',
+ 'public/js/bindings/',
+ ],
+ },
+ }],
+ ['OS=="win"', {
+ 'variables': {
+ 'files': [
+ '<(PRODUCT_DIR)/mojo_test_support.dll',
+ ],
+ },
+ }],
+ ['OS=="linux"', {
+ 'variables': {
+ 'files': [
+ '<(PRODUCT_DIR)/lib/libmojo_test_support.so',
+ ],
+ },
+ }],
+ ['OS=="mac"', {
+ 'variables': {
+ 'files': [
+ '<(PRODUCT_DIR)/libmojo_test_support.dylib',
+ ],
+ },
+ }],
+ ],
+}
diff --git a/mojo/mojo_base.gyp b/mojo/mojo_base.gyp
new file mode 100644
index 0000000..0cf8766
--- /dev/null
+++ b/mojo/mojo_base.gyp
@@ -0,0 +1,642 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Essential components (and their tests) that are needed to build
+# Chrome should be here. Other components that are useful only in
+# Mojo land like mojo_shell should be in mojo.gyp.
+{
+ 'includes': [
+ 'mojo_public.gypi',
+ 'mojo_public_tests.gypi',
+ 'mojo_variables.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'mojo_base',
+ 'type': 'none',
+ 'dependencies': [
+ # NOTE: If adding a new dependency here, please consider whether it
+ # should also be added to the list of Mojo-related dependencies of
+ # build/all.gyp:All on iOS, as All cannot depend on the mojo_base
+ # target on iOS due to the presence of the js targets, which cause v8
+ # to be built.
+ 'mojo_common_lib',
+ 'mojo_common_unittests',
+ 'mojo_cpp_bindings',
+ 'mojo_js_bindings',
+ 'mojo_message_generator',
+ 'mojo_message_pipe_perftests',
+ 'mojo_public_application_unittests',
+ '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_system',
+ 'mojo_system_impl',
+ 'mojo_system_unittests',
+ 'mojo_utility',
+ ],
+ 'conditions': [
+ ['OS == "android"', {
+ 'dependencies': [
+ 'mojo_bindings_java',
+ 'mojo_public_java',
+ 'mojo_system_java',
+ 'libmojo_system_java',
+ 'mojo_test_apk',
+ ],
+ }],
+ ]
+ },
+ {
+ 'target_name': 'mojo_none',
+ 'type': 'none',
+ },
+ {
+ # GN version: //mojo/common/test:run_all_unittests
+ 'target_name': 'mojo_run_all_unittests',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_system_impl',
+ 'mojo_test_support',
+ 'mojo_test_support_impl',
+ ],
+ 'sources': [
+ 'common/test/run_all_unittests.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/common/test:run_all_perftests
+ 'target_name': 'mojo_run_all_perftests',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:test_support_base',
+ 'mojo_system_impl',
+ 'mojo_test_support',
+ 'mojo_test_support_impl',
+ ],
+ 'sources': [
+ 'common/test/run_all_perftests.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/edk/system
+ 'target_name': 'mojo_system_impl',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../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': [
+ 'edk/embedder/channel_init.cc',
+ 'edk/embedder/channel_init.h',
+ 'edk/embedder/embedder.cc',
+ 'edk/embedder/embedder.h',
+ 'edk/embedder/platform_channel_pair.cc',
+ 'edk/embedder/platform_channel_pair.h',
+ 'edk/embedder/platform_channel_pair_posix.cc',
+ 'edk/embedder/platform_channel_pair_win.cc',
+ 'edk/embedder/platform_channel_utils_posix.cc',
+ 'edk/embedder/platform_channel_utils_posix.h',
+ 'edk/embedder/platform_handle.cc',
+ 'edk/embedder/platform_handle.h',
+ 'edk/embedder/platform_handle_utils.h',
+ 'edk/embedder/platform_handle_utils_posix.cc',
+ 'edk/embedder/platform_handle_utils_win.cc',
+ 'edk/embedder/platform_handle_vector.h',
+ 'edk/embedder/platform_shared_buffer.h',
+ 'edk/embedder/platform_support.h',
+ 'edk/embedder/scoped_platform_handle.h',
+ 'edk/embedder/simple_platform_shared_buffer.cc',
+ 'edk/embedder/simple_platform_shared_buffer.h',
+ 'edk/embedder/simple_platform_shared_buffer_posix.cc',
+ 'edk/embedder/simple_platform_shared_buffer_win.cc',
+ 'edk/embedder/simple_platform_support.cc',
+ 'edk/embedder/simple_platform_support.h',
+ 'edk/system/channel.cc',
+ 'edk/system/channel.h',
+ 'edk/system/channel_endpoint.cc',
+ 'edk/system/channel_endpoint.h',
+ 'edk/system/constants.h',
+ 'edk/system/core.cc',
+ 'edk/system/core.h',
+ 'edk/system/data_pipe.cc',
+ 'edk/system/data_pipe.h',
+ 'edk/system/data_pipe_consumer_dispatcher.cc',
+ 'edk/system/data_pipe_consumer_dispatcher.h',
+ 'edk/system/data_pipe_producer_dispatcher.cc',
+ 'edk/system/data_pipe_producer_dispatcher.h',
+ 'edk/system/dispatcher.cc',
+ 'edk/system/dispatcher.h',
+ 'edk/system/entrypoints.cc',
+ 'edk/system/handle_signals_state.h',
+ 'edk/system/handle_table.cc',
+ 'edk/system/handle_table.h',
+ 'edk/system/local_data_pipe.cc',
+ 'edk/system/local_data_pipe.h',
+ 'edk/system/local_message_pipe_endpoint.cc',
+ 'edk/system/local_message_pipe_endpoint.h',
+ 'edk/system/mapping_table.cc',
+ 'edk/system/mapping_table.h',
+ 'edk/system/memory.cc',
+ 'edk/system/memory.h',
+ 'edk/system/message_in_transit.cc',
+ 'edk/system/message_in_transit.h',
+ 'edk/system/message_in_transit_queue.cc',
+ 'edk/system/message_in_transit_queue.h',
+ 'edk/system/message_pipe.cc',
+ 'edk/system/message_pipe.h',
+ 'edk/system/message_pipe_dispatcher.cc',
+ 'edk/system/message_pipe_dispatcher.h',
+ 'edk/system/message_pipe_endpoint.cc',
+ 'edk/system/message_pipe_endpoint.h',
+ 'edk/system/options_validation.h',
+ 'edk/system/platform_handle_dispatcher.cc',
+ 'edk/system/platform_handle_dispatcher.h',
+ 'edk/system/proxy_message_pipe_endpoint.cc',
+ 'edk/system/proxy_message_pipe_endpoint.h',
+ 'edk/system/raw_channel.cc',
+ 'edk/system/raw_channel.h',
+ 'edk/system/raw_channel_posix.cc',
+ 'edk/system/raw_channel_win.cc',
+ 'edk/system/shared_buffer_dispatcher.cc',
+ 'edk/system/shared_buffer_dispatcher.h',
+ 'edk/system/simple_dispatcher.cc',
+ 'edk/system/simple_dispatcher.h',
+ 'edk/system/transport_data.cc',
+ 'edk/system/transport_data.h',
+ 'edk/system/waiter.cc',
+ 'edk/system/waiter.h',
+ 'edk/system/waiter_list.cc',
+ 'edk/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.
+ 'edk/embedder/test_embedder.cc',
+ 'edk/embedder/test_embedder.h',
+ ],
+ 'all_dependent_settings': {
+ # Ensures that dependent projects import the core functions on Windows.
+ 'defines': ['MOJO_USE_SYSTEM_IMPL'],
+ }
+ },
+ {
+ # GN version: //mojo/edk/system:mojo_system_unittests
+ 'target_name': 'mojo_system_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_common_test_support',
+ 'mojo_system_impl',
+ ],
+ 'sources': [
+ 'edk/embedder/embedder_unittest.cc',
+ 'edk/embedder/platform_channel_pair_posix_unittest.cc',
+ 'edk/embedder/simple_platform_shared_buffer_unittest.cc',
+ 'edk/system/channel_unittest.cc',
+ 'edk/system/core_unittest.cc',
+ 'edk/system/core_test_base.cc',
+ 'edk/system/core_test_base.h',
+ 'edk/system/data_pipe_unittest.cc',
+ 'edk/system/dispatcher_unittest.cc',
+ 'edk/system/local_data_pipe_unittest.cc',
+ 'edk/system/memory_unittest.cc',
+ 'edk/system/message_pipe_dispatcher_unittest.cc',
+ 'edk/system/message_pipe_test_utils.h',
+ 'edk/system/message_pipe_test_utils.cc',
+ 'edk/system/message_pipe_unittest.cc',
+ 'edk/system/multiprocess_message_pipe_unittest.cc',
+ 'edk/system/options_validation_unittest.cc',
+ 'edk/system/platform_handle_dispatcher_unittest.cc',
+ 'edk/system/raw_channel_unittest.cc',
+ 'edk/system/remote_message_pipe_unittest.cc',
+ 'edk/system/run_all_unittests.cc',
+ 'edk/system/shared_buffer_dispatcher_unittest.cc',
+ 'edk/system/simple_dispatcher_unittest.cc',
+ 'edk/system/test_utils.cc',
+ 'edk/system/test_utils.h',
+ 'edk/system/waiter_list_unittest.cc',
+ 'edk/system/waiter_test_utils.cc',
+ 'edk/system/waiter_test_utils.h',
+ 'edk/system/waiter_unittest.cc',
+ ],
+ 'conditions': [
+ ['OS=="ios"', {
+ 'sources!': [
+ 'edk/embedder/embedder_unittest.cc',
+ 'edk/system/multiprocess_message_pipe_unittest.cc',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/edk/system:mojo_message_pipe_perftests
+ 'target_name': 'mojo_message_pipe_perftests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../base/base.gyp:test_support_perf',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_common_test_support',
+ 'mojo_environment_chromium',
+ 'mojo_system_impl',
+ ],
+ 'sources': [
+ 'edk/system/message_pipe_perftest.cc',
+ 'edk/system/message_pipe_test_utils.h',
+ 'edk/system/message_pipe_test_utils.cc',
+ 'edk/system/test_utils.cc',
+ 'edk/system/test_utils.h',
+ ],
+ },
+ {
+ # GN version: //mojo/common/test:test_support_impl
+ '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': [
+ 'MOJO_COMMON_IMPLEMENTATION',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../url/url.gyp:url_lib',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '<(mojo_system_for_component)',
+ ],
+ 'export_dependent_settings': [
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ ],
+ 'sources': [
+ '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',
+ 'common/time_helper.cc',
+ 'common/time_helper.h',
+ ],
+ },
+ {
+ # GN version: //mojo/edk/test:test_support
+ 'target_name': 'mojo_common_test_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_system_impl',
+ ],
+ 'sources': [
+ 'edk/test/multiprocess_test_helper.cc',
+ 'edk/test/multiprocess_test_helper.h',
+ 'edk/test/test_utils.h',
+ 'edk/test/test_utils_posix.cc',
+ 'edk/test/test_utils_win.cc',
+ ],
+ 'conditions': [
+ ['OS=="ios"', {
+ 'sources!': [
+ 'edk/test/multiprocess_test_helper.cc',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/common:mojo_common_unittests
+ 'target_name': 'mojo_common_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_message_loop_tests',
+ '../testing/gtest.gyp:gtest',
+ '../url/url.gyp:url_lib',
+ 'mojo_cpp_bindings',
+ 'mojo_environment_chromium',
+ 'mojo_common_lib',
+ 'mojo_common_test_support',
+ 'mojo_public_test_utils',
+ 'mojo_run_all_unittests',
+ ],
+ 'sources': [
+ 'common/common_type_converters_unittest.cc',
+ 'common/handle_watcher_unittest.cc',
+ 'common/message_pump_mojo_unittest.cc',
+ 'edk/test/multiprocess_test_helper_unittest.cc',
+ ],
+ 'conditions': [
+ ['OS=="ios"', {
+ 'sources!': [
+ 'edk/test/multiprocess_test_helper_unittest.cc',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/environment:chromium
+ 'target_name': 'mojo_environment_chromium',
+ 'type': 'static_library',
+ 'dependencies': [
+ '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.cc",
+ ],
+ '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',
+ '<(mojo_system_for_component)',
+ ],
+ '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/services/gles2:interfaces (for files generated from
+ # the mojom file)
+ # GN version: //mojo/services/gles2:bindings
+ 'target_name': 'mojo_gles2_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ '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/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_cpp_bindings',
+ '../gpu/gpu.gyp:command_buffer_common',
+ ],
+ },
+ {
+ # GN version: //mojo/gles2
+ 'target_name': 'mojo_gles2_impl',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../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_environment_chromium',
+ 'mojo_gles2_bindings',
+ '<(mojo_system_for_component)',
+ ],
+ 'defines': [
+ 'GLES2_USE_MOJO',
+ 'GL_GLEXT_PROTOTYPES',
+ 'MOJO_GLES2_IMPLEMENTATION',
+ 'MOJO_GLES2_IMPL_IMPLEMENTATION',
+ 'MOJO_USE_GLES2_IMPL'
+ ],
+ 'direct_dependent_settings': {
+ 'defines': [
+ 'GLES2_USE_MOJO',
+ ],
+ },
+ 'export_dependent_settings': [
+ 'mojo_gles2_bindings',
+ ],
+ 'sources': [
+ 'gles2/command_buffer_client_impl.cc',
+ 'gles2/command_buffer_client_impl.h',
+ 'gles2/gles2_impl_export.h',
+ 'gles2/gles2_impl.cc',
+ 'gles2/gles2_context.cc',
+ 'gles2/gles2_context.h',
+ ],
+ 'all_dependent_settings': {
+ # Ensures that dependent projects import the core functions on Windows.
+ 'defines': ['MOJO_USE_GLES2_IMPL'],
+ }
+ },
+ {
+ # GN version: //mojo/application
+ 'target_name': 'mojo_application_chromium',
+ 'type': 'static_library',
+ 'sources': [
+ 'application/application_runner_chromium.cc',
+ 'application/application_runner_chromium.h',
+ ],
+ 'dependencies': [
+ 'mojo_application_base',
+ 'mojo_common_lib',
+ 'mojo_environment_chromium',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_application_base',
+ ],
+ },
+ {
+ # GN version: //mojo/bindings/js
+ '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': [
+ # Sources list duplicated in GN build.
+ 'bindings/js/core.cc',
+ 'bindings/js/core.h',
+ 'bindings/js/drain_data.cc',
+ 'bindings/js/drain_data.h',
+ 'bindings/js/handle.cc',
+ 'bindings/js/handle.h',
+ 'bindings/js/handle_close_observer.h',
+ 'bindings/js/support.cc',
+ 'bindings/js/support.h',
+ 'bindings/js/waiting_callback.cc',
+ 'bindings/js/waiting_callback.h',
+ ],
+ },
+ {
+ # GN version: //mojo/tools:message_generator
+ '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',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'mojo_jni_headers',
+ 'type': 'none',
+ 'dependencies': [
+ 'mojo_java_set_jni_headers',
+ ],
+ 'sources': [
+ 'android/javatests/src/org/chromium/mojo/MojoTestCase.java',
+ 'android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java',
+ 'android/system/src/org/chromium/mojo/system/impl/CoreImpl.java',
+ 'services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.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_java_set_jni_headers',
+ 'type': 'none',
+ 'variables': {
+ 'jni_gen_package': 'mojo',
+ 'input_java_class': 'java/util/HashSet.class',
+ },
+ 'includes': [ '../build/jar_file_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/android/system',
+ },
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ '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_system_impl',
+ ],
+ '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',
+ 'mojo_public_bindings_test_utils',
+ ],
+ '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',
+ 'android/javatests/validation_test_util.cc',
+ 'android/javatests/validation_test_util.h',
+ ],
+ },
+ {
+ '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': {
+ '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/java_apk.gypi' ],
+ },
+ ]
+ }],
+ ]
+}
diff --git a/mojo/mojo_examples.gypi b/mojo/mojo_examples.gypi
new file mode 100644
index 0000000..9638743
--- /dev/null
+++ b/mojo/mojo_examples.gypi
@@ -0,0 +1,868 @@
+# 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': [
+ {
+ # GN version: //mojo/examples/echo:client
+ 'target_name': 'mojo_echo_client',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_echo_service_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/echo/echo_client.cc',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_echo_client',
+ 'variables': {
+ 'app_name': 'mojo_echo_client',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/echo:bindings
+ 'target_name': 'mojo_echo_service_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/echo/echo_service.mojom',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/echo:service
+ 'target_name': 'mojo_echo_service',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_echo_service_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/echo/echo_service.cc',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_echo_service',
+ 'variables': {
+ 'app_name': 'mojo_echo_service',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/sample_app
+ 'target_name': 'mojo_sample_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_geometry_bindings',
+ 'mojo_gpu_bindings',
+ 'mojo_native_viewport_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/sample_app/gles2_client_impl.cc',
+ 'examples/sample_app/gles2_client_impl.h',
+ 'examples/sample_app/sample_app.cc',
+ 'examples/sample_app/spinning_cube.cc',
+ 'examples/sample_app/spinning_cube.h',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_sample_app',
+ 'variables': {
+ 'app_name': 'mojo_sample_app',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/apptest:bindings
+ 'target_name': 'mojo_example_service_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/apptest/example_service.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/apptest:service
+ 'target_name': 'mojo_example_service',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_standalone', # For ApplicationDelegate.
+ 'mojo_base.gyp:mojo_cpp_bindings', # For *.mojom.h
+ 'mojo_example_service_bindings',
+ 'mojo_base.gyp:mojo_utility', # For RunLoop.
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/apptest/example_service_application.cc',
+ 'examples/apptest/example_service_application.h',
+ 'examples/apptest/example_service_impl.cc',
+ 'examples/apptest/example_service_impl.h',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/apptest:apptests
+ 'target_name': 'mojo_example_apptests',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../testing/gtest.gyp:gtest',
+ 'mojo_base.gyp:mojo_application_standalone', # For ApplicationDelegate.
+ 'mojo_example_service',
+ 'mojo_example_service_bindings',
+ 'mojo_base.gyp:mojo_utility', # For RunLoop.
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/apptest/example_apptest.cc',
+ 'examples/apptest/example_client_application.cc',
+ 'examples/apptest/example_client_application.h',
+ 'examples/apptest/example_client_impl.cc',
+ 'examples/apptest/example_client_impl.h',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_example_apptests',
+ 'variables': {
+ 'app_name': 'mojo_example_apptests',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/compositor_app
+ 'target_name': 'mojo_compositor_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_cc_support',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_gpu_bindings',
+ 'mojo_native_viewport_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/compositor_app/compositor_app.cc',
+ 'examples/compositor_app/compositor_host.cc',
+ 'examples/compositor_app/compositor_host.h',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_compositor_app',
+ 'variables': {
+ 'app_name': 'mojo_compositor_app',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/wget
+ 'target_name': 'mojo_wget',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_network_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/wget/wget.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/content_handler_demo
+ 'target_name': 'mojo_content_handler_demo',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_content_handler_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/content_handler_demo/content_handler_demo.cc',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_wget',
+ 'variables': {
+ 'app_name': 'mojo_wget',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/media_viewer:bindings
+ 'target_name': 'mojo_media_viewer_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/media_viewer/media_viewer.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/png_viewer
+ 'target_name': 'mojo_png_viewer',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_content_handler_bindings',
+ 'mojo_media_viewer_bindings',
+ 'mojo_network_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/png_viewer/png_viewer.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/pepper_container_app
+ 'target_name': 'mojo_pepper_container_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../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_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_gpu_bindings',
+ 'mojo_native_viewport_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ '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': [
+ # 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',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/surfaces_app:parent_app
+ 'target_name': 'mojo_surfaces_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_gpu_bindings',
+ 'mojo_native_viewport_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_app_bindings',
+ 'mojo_surfaces_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/surfaces_app/embedder.cc',
+ 'examples/surfaces_app/embedder.h',
+ 'examples/surfaces_app/surfaces_app.cc',
+ 'examples/surfaces_app/surfaces_util.cc',
+ 'examples/surfaces_app/surfaces_util.h',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/surfaces_app:bindings
+ 'target_name': 'mojo_surfaces_app_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/surfaces_app/child.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_surfaces_bindings',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_surfaces_app',
+ 'variables': {
+ 'app_name': 'mojo_surfaces_app',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/examples/surfaces_app:child_app
+ 'target_name': 'mojo_surfaces_child_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_surfaces_app_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/surfaces_app/child_app.cc',
+ 'examples/surfaces_app/child_impl.cc',
+ 'examples/surfaces_app/child_impl.h',
+ 'examples/surfaces_app/surfaces_util.cc',
+ 'examples/surfaces_app/surfaces_util.h',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/surfaces_app:child_gl_app
+ 'target_name': 'mojo_surfaces_child_gl_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_gpu_bindings',
+ 'mojo_surfaces_app_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/surfaces_app/child_gl_app.cc',
+ 'examples/surfaces_app/child_gl_impl.cc',
+ 'examples/surfaces_app/child_gl_impl.h',
+ 'examples/surfaces_app/surfaces_util.cc',
+ 'examples/surfaces_app/surfaces_util.h',
+ 'examples/sample_app/spinning_cube.cc',
+ 'examples/sample_app/spinning_cube.h',
+ ],
+ }
+ ],
+ 'conditions': [
+ ['use_aura==1', {
+ 'targets': [
+ {
+ # GN version: //mojo/examples/aura_demo:mojo_aura_demo
+ 'target_name': 'mojo_aura_demo',
+ 'type': 'loadable_module',
+ '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_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_aura_support',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/aura_demo/aura_demo.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/aura_demo:init
+ 'target_name': 'mojo_aura_demo_init',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/aura_demo/view_manager_init.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/browser
+ 'target_name': 'mojo_browser',
+ 'type': 'loadable_module',
+ '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_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_aura_support',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_input_events_lib',
+ 'mojo_navigation_bindings',
+ 'mojo_views_support',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ 'mojo_window_manager_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/browser/browser.cc',
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_aura_demo',
+ 'variables': {
+ 'app_name': 'mojo_aura_demo',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GYP version: //mojo/examples/demo_launcher
+ 'target_name': 'mojo_demo_launcher',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_geometry_bindings',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/demo_launcher/demo_launcher.cc',
+ ],
+ },
+ {
+ # GYP version: //mojo/examples/keyboard
+ 'target_name': 'mojo_keyboard',
+ 'type': 'loadable_module',
+ '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_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_aura_support',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_input_events_lib',
+ 'mojo_keyboard_bindings',
+ 'mojo_navigation_bindings',
+ 'mojo_views_support',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/keyboard/keyboard_delegate.h',
+ 'examples/keyboard/keyboard_view.cc',
+ 'examples/keyboard/keyboard_view.h',
+ 'examples/keyboard/keyboard.cc',
+ 'examples/keyboard/keys.cc',
+ 'examples/keyboard/keys.h',
+ ],
+ },
+ {
+ # GYP version: //mojo/examples/keyboard:bindings
+ 'target_name': 'mojo_keyboard_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/keyboard/keyboard.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/window_manager:bindings
+ 'target_name': 'mojo_window_manager_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/window_manager/window_manager.mojom',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_geometry_bindings',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/window_manager
+ 'target_name': 'mojo_window_manager',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../ui/aura/aura.gyp:aura',
+ '../ui/base/ui_base.gyp:ui_base',
+ '../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',
+ '../ui/wm/wm.gyp:wm',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_aura_support',
+ 'mojo_core_window_manager_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_input_events_lib',
+ 'mojo_keyboard_bindings',
+ 'mojo_navigation_bindings',
+ 'mojo_view_manager_lib',
+ 'mojo_views_support',
+ 'mojo_window_manager_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/window_manager/debug_panel.h',
+ 'examples/window_manager/debug_panel.cc',
+ 'examples/window_manager/window_manager.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/embedded_app
+ 'target_name': 'mojo_embedded_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../url/url.gyp:url_lib',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_geometry_bindings',
+ 'mojo_navigation_bindings',
+ 'mojo_view_manager_lib',
+ 'mojo_window_manager_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/embedded_app/embedded_app.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/nesting_app
+ 'target_name': 'mojo_nesting_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../url/url.gyp:url_lib',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_geometry_bindings',
+ 'mojo_navigation_bindings',
+ 'mojo_view_manager_lib',
+ 'mojo_window_manager_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'examples/nesting_app/nesting_app.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/media_viewer
+ 'target_name': 'mojo_media_viewer',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../ui/views/views.gyp:views',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_input_events_lib',
+ 'mojo_media_viewer_bindings',
+ 'mojo_navigation_bindings',
+ 'mojo_views_support',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/media_viewer/media_viewer.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/wm_flow
+ 'target_name': 'mojo_wm_flow_wm',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../skia/skia.gyp:skia',
+ '../ui/views/views.gyp:views',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_core_window_manager_lib',
+ 'mojo_view_manager_lib',
+ 'mojo_views_support',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/wm_flow/wm/frame_controller.cc',
+ 'examples/wm_flow/wm/frame_controller.h',
+ 'examples/wm_flow/wm/wm.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/wm_flow:init
+ 'target_name': 'mojo_wm_flow_init',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/wm_flow/init/init.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/wm_flow:embedder_bindings
+ 'target_name': 'mojo_wm_flow_embedder_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/wm_flow/app/embedder.mojom',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/wm_flow:embeddee_bindings
+ 'target_name': 'mojo_wm_flow_embeddee_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'examples/wm_flow/embedded/embeddee.mojom',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/wm_flow:app
+ 'target_name': 'mojo_wm_flow_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_core_window_manager_bindings',
+ 'mojo_view_manager_lib',
+ 'mojo_wm_flow_embeddee_bindings',
+ 'mojo_wm_flow_embedder_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/wm_flow/app/app.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/examples/wm_flow:embedded
+ 'target_name': 'mojo_wm_flow_embedded',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_core_window_manager_bindings',
+ 'mojo_view_manager_lib',
+ 'mojo_wm_flow_embeddee_bindings',
+ 'mojo_wm_flow_embedder_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'examples/wm_flow/embedded/embedded.cc',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/mojo/mojo_js_unittests.isolate b/mojo/mojo_js_unittests.isolate
new file mode 100644
index 0000000..2b3131b
--- /dev/null
+++ b/mojo/mojo_js_unittests.isolate
@@ -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.
+{
+ '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',
+ ],
+ 'files': [
+ '../gin/test/expect.js',
+ '../testing/test_env.py',
+ '<(PRODUCT_DIR)/gen/mojo/public/interfaces/bindings/tests/',
+ '<(PRODUCT_DIR)/mojo_js_unittests<(EXECUTABLE_SUFFIX)',
+ 'bindings/js/',
+ 'public/js/bindings/',
+ ],
+ },
+ }],
+ ['OS=="win"', {
+ 'variables': {
+ 'files': [
+ '<(PRODUCT_DIR)/mojo_test_support.dll',
+ ],
+ },
+ }],
+ ['OS=="linux"', {
+ 'variables': {
+ 'files': [
+ '<(PRODUCT_DIR)/lib/libmojo_test_support.so',
+ ],
+ },
+ }],
+ ['OS=="mac"', {
+ 'variables': {
+ 'files': [
+ '<(PRODUCT_DIR)/libmojo_test_support.dylib',
+ ],
+ },
+ }],
+ ],
+}
diff --git a/mojo/mojo_nacl.gyp b/mojo/mojo_nacl.gyp
new file mode 100644
index 0000000..a813046
--- /dev/null
+++ b/mojo/mojo_nacl.gyp
@@ -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.
+
+{
+ 'conditions': [
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'variables': {
+ 'monacl_codegen_dir': '<(SHARED_INTERMEDIATE_DIR)/<!(python <(DEPTH)/build/inverse_depth.py <(DEPTH))/monacl',
+ },
+ 'includes': [
+ 'mojo_variables.gypi',
+ '../build/common_untrusted.gypi',
+ '../components/nacl/nacl_defines.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'monacl_codegen',
+ 'type': 'none',
+ 'actions': [
+ {
+ 'action_name': 'generate_nacl_bindings',
+ 'inputs': [
+ 'nacl/generator/generate_nacl_bindings.py',
+ 'nacl/generator/interface.py',
+ 'nacl/generator/interface_dsl.py',
+ 'nacl/generator/mojo_syscall.cc.tmpl',
+ 'nacl/generator/libmojo.cc.tmpl',
+ ],
+ 'outputs': [
+ '<(monacl_codegen_dir)/mojo_syscall.cc',
+ '<(monacl_codegen_dir)/libmojo.cc',
+ ],
+ 'action': [
+ 'python',
+ 'nacl/generator/generate_nacl_bindings.py',
+ '-d', '<(monacl_codegen_dir)',
+ ],
+ },
+ ],
+ },
+ {
+ 'target_name': 'monacl_sel',
+ 'type': 'static_library',
+ 'defines': [
+ '<@(nacl_defines)',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
+ 'sources': [
+ '<(monacl_codegen_dir)/mojo_syscall.cc',
+ 'nacl/monacl_sel_main.cc',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/native_client/src/trusted/service_runtime/service_runtime.gyp:sel',
+ ],
+ },
+ {
+ 'target_name': 'monacl_shell',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'monacl_sel',
+ ],
+ 'sources': [
+ 'nacl/monacl_shell.cc',
+ ],
+ },
+ {
+ 'target_name': 'mojo_nacl',
+ 'type': 'none',
+ 'variables': {
+ 'nlib_target': 'libmojo.a',
+ 'build_glibc': 0,
+ 'build_newlib': 0,
+ 'build_pnacl_newlib': 1,
+ },
+ 'defines': [
+ '<@(nacl_defines)',
+ ],
+ 'sources': [
+ '<(monacl_codegen_dir)/libmojo.cc',
+ ],
+ 'dependencies': [
+ 'monacl_codegen',
+ ],
+ },
+ {
+ 'target_name': 'monacl_test',
+ 'type': 'none',
+ 'variables': {
+ 'nexe_target': 'monacl_test',
+ 'build_newlib': 0,
+ 'build_pnacl_newlib': 1,
+ 'translate_pexe_with_build': 1,
+ 'link_flags': [
+ '-pthread',
+ '-lmojo',
+ '-limc_syscalls',
+ ],
+ 'sources': [
+ '<@(mojo_public_system_unittest_sources)',
+ ],
+ },
+ 'dependencies': [
+ '<(DEPTH)/native_client/tools.gyp:prep_toolchain',
+ '<(DEPTH)/native_client/src/untrusted/nacl/nacl.gyp:nacl_lib',
+ '<(DEPTH)/native_client/src/untrusted/nacl/nacl.gyp:imc_syscalls_lib',
+ '<(DEPTH)/native_client/src/untrusted/pthread/pthread.gyp:pthread_lib',
+ '../testing/gtest_nacl.gyp:gtest_nacl',
+ '../testing/gtest_nacl.gyp:gtest_main_nacl',
+ 'mojo_nacl',
+ 'monacl_codegen',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/mojo/mojo_public.gypi b/mojo/mojo_public.gypi
new file mode 100644
index 0000000..cc7fc15
--- /dev/null
+++ b/mojo/mojo_public.gypi
@@ -0,0 +1,257 @@
+# Copyright 2014 The Chromium 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': [
+ {
+ # GN version: //mojo/public/c/system
+ 'target_name': 'mojo_system',
+ 'type': 'static_library',
+ 'defines': [
+ 'MOJO_SYSTEM_IMPLEMENTATION',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '..',
+ ],
+ },
+ '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/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',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/bindings
+ 'target_name': 'mojo_cpp_bindings',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '..'
+ ],
+ 'sources': [
+ '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/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/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/js/bindings/constants.cc',
+ 'public/js/bindings/constants.h',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/environment:standalone
+ 'target_name': 'mojo_environment_standalone',
+ 'type': 'static_library',
+ 'sources': [
+ 'public/c/environment/async_waiter.h',
+ 'public/c/environment/logger.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',
+ 'public/cpp/environment/logging.h',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/utility
+ 'target_name': 'mojo_utility',
+ 'type': 'static_library',
+ 'sources': [
+ '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': [
+ '..',
+ ],
+ },
+ {
+ # GN version: //mojo/public/interfaces/application:application
+ 'target_name': 'mojo_application_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'public/interfaces/application/application.mojom',
+ 'public/interfaces/application/service_provider.mojom',
+ 'public/interfaces/application/shell.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_cpp_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/application
+ 'target_name': 'mojo_application_base',
+ 'type': 'static_library',
+ 'sources': [
+ 'public/cpp/application/application_connection.h',
+ 'public/cpp/application/application_delegate.h',
+ 'public/cpp/application/application_impl.h',
+ 'public/cpp/application/connect.h',
+ 'public/cpp/application/service_provider_impl.h',
+ 'public/cpp/application/interface_factory.h',
+ 'public/cpp/application/interface_factory_impl.h',
+ 'public/cpp/application/lib/application_connection.cc',
+ 'public/cpp/application/lib/application_delegate.cc',
+ 'public/cpp/application/lib/application_impl.cc',
+ 'public/cpp/application/lib/service_provider_impl.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',
+ 'public/cpp/application/lib/weak_service_provider.cc',
+ 'public/cpp/application/lib/weak_service_provider.h',
+ ],
+ 'dependencies': [
+ 'mojo_application_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_application_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/application:standalone"
+ 'target_name': 'mojo_application_standalone',
+ 'type': 'static_library',
+ 'sources': [
+ 'public/cpp/application/lib/application_runner.cc',
+ 'public/cpp/application/application_runner.h',
+ ],
+ 'dependencies': [
+ 'mojo_application_base',
+ 'mojo_environment_standalone',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_application_base',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['OS == "android"', {
+ 'targets': [
+ {
+ # GN version: //mojo/public/java_system
+ 'target_name': 'mojo_public_java',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': 'public/java/system',
+ },
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ # GN version: //mojo/public/java_bindings
+ 'target_name': 'mojo_bindings_java',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': 'public/java/bindings',
+ },
+ 'dependencies': [
+ 'mojo_public_java',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/mojo/mojo_public_gles2_for_loadable_module.gypi b/mojo/mojo_public_gles2_for_loadable_module.gypi
new file mode 100644
index 0000000..42421dc
--- /dev/null
+++ b/mojo/mojo_public_gles2_for_loadable_module.gypi
@@ -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.
+
+# In the non component build, the thunks need to be linked directly into the
+# loadable module since they define symbols that should be exported from that
+# library. So, this variable expands out to either list the sources directly (in
+# the component build where no symbols need to be exported) a dependency.
+{
+ 'conditions': [
+ ['component=="shared_library"', {
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_gles2_impl',
+ ],
+ }, { # component!="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',
+ ],
+ },
+ 'all_dependent_settings': {
+ 'conditions': [
+ # We need to be able to call the MojoSetGLES2Thunks() function in
+ # gles2_thunks.cc
+ ['OS=="android"', {
+ 'ldflags!': [
+ '-Wl,--exclude-libs=ALL',
+ ],
+ }],
+ ],
+ },
+ 'sources': [
+ 'public/c/gles2/gles2.h',
+ 'public/c/gles2/gles2_export.h',
+ 'public/platform/native/gles2_thunks.cc',
+ 'public/platform/native/gles2_thunks.h',
+ 'public/platform/native/gles2_impl_thunks.cc',
+ 'public/platform/native/gles2_impl_thunks.h',
+ 'public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.cc',
+ 'public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.h',
+ 'public/platform/native/gles2_impl_chromium_sync_point_thunks.cc',
+ 'public/platform/native/gles2_impl_chromium_sync_point_thunks.h',
+ ],
+ }]
+ ]
+}
diff --git a/mojo/mojo_public_tests.gypi b/mojo/mojo_public_tests.gypi
new file mode 100644
index 0000000..6cf4730
--- /dev/null
+++ b/mojo/mojo_public_tests.gypi
@@ -0,0 +1,217 @@
+# Copyright 2014 The Chromium 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': [
+ {
+ # GN version: //mojo/public/c/test_support
+ 'target_name': 'mojo_test_support',
+ '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',
+ # TODO(vtl): Convert this to thunks http://crbug.com/386799
+ 'public/tests/test_support_private.cc',
+ 'public/tests/test_support_private.h',
+ ],
+ 'conditions': [
+ ['OS=="ios"', {
+ 'type': 'static_library',
+ }, {
+ 'type': 'shared_library',
+ }],
+ ['OS=="mac"', {
+ 'xcode_settings': {
+ # Make it a run-path dependent library.
+ 'DYLIB_INSTALL_NAME_BASE': '@loader_path',
+ },
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/test_support:test_utils
+ 'target_name': 'mojo_public_test_utils',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ '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',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/bindings/tests:mojo_public_bindings_test_utils
+ 'target_name': 'mojo_public_bindings_test_utils',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ ],
+ 'sources': [
+ 'public/cpp/bindings/tests/validation_test_input_parser.cc',
+ 'public/cpp/bindings/tests/validation_test_input_parser.h',
+ ],
+ },
+ # TODO(vtl): Reorganize the mojo_public_*_unittests.
+ {
+ # GN version: //mojo/public/cpp/bindings/tests:mojo_public_bindings_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_bindings_test_utils',
+ '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/serialization_warning_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_unittest.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/environment/tests:mojo_public_environment_unittests
+ 'target_name': 'mojo_public_environment_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../testing/gtest.gyp:gtest',
+ 'mojo_environment_standalone',
+ 'mojo_public_test_utils',
+ 'mojo_run_all_unittests',
+ 'mojo_utility',
+ ],
+ 'include_dirs': [ '..' ],
+ 'sources': [
+ 'public/cpp/environment/tests/async_waiter_unittest.cc',
+ 'public/cpp/environment/tests/logger_unittest.cc',
+ 'public/cpp/environment/tests/logging_unittest.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/application/tests:mojo_public_application_unittests
+ 'target_name': 'mojo_public_application_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_application_standalone',
+ 'mojo_utility',
+ 'mojo_environment_standalone',
+ 'mojo_run_all_unittests',
+ ],
+ 'sources': [
+ 'public/cpp/application/tests/service_registry_unittest.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/application/tests:mojo_public_system_unittests
+ 'target_name': 'mojo_public_system_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../testing/gtest.gyp:gtest',
+ 'mojo_public_test_utils',
+ 'mojo_run_all_unittests',
+ ],
+ 'include_dirs': [ '..' ],
+ 'sources': [
+ '<@(mojo_public_system_unittest_sources)',
+ ],
+ },
+ {
+ # GN version: //mojo/public/cpp/application/tests:mojo_public_utility_unittests
+ 'target_name': 'mojo_public_utility_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../testing/gtest.gyp:gtest',
+ 'mojo_public_test_utils',
+ 'mojo_run_all_unittests',
+ 'mojo_utility',
+ ],
+ 'include_dirs' : [ '..' ],
+ 'sources': [
+ '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',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/public/c/system/tests:perftests
+ 'target_name': 'mojo_public_system_perftests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_public_test_utils',
+ 'mojo_run_all_perftests',
+ 'mojo_utility',
+ ],
+ 'sources': [
+ 'public/c/system/tests/core_perftest.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/public/interfaces/bindings/tests:test_interfaces
+ 'target_name': 'mojo_public_test_interfaces',
+ 'type': 'static_library',
+ 'sources': [
+ 'public/interfaces/bindings/tests/math_calculator.mojom',
+ 'public/interfaces/bindings/tests/no_module.mojom',
+ 'public/interfaces/bindings/tests/rect.mojom',
+ 'public/interfaces/bindings/tests/regression_tests.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/serialization_test_structs.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_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_cpp_bindings',
+ ],
+ },
+ ],
+}
diff --git a/mojo/mojo_python_unittests.isolate b/mojo/mojo_python_unittests.isolate
new file mode 100644
index 0000000..4fea84a
--- /dev/null
+++ b/mojo/mojo_python_unittests.isolate
@@ -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.
+{
+ 'conditions': [
+ ['OS=="linux"', {
+ 'variables': {
+ 'command': [
+ 'tools/run_mojo_python_bindings_tests.py',
+ '--build-dir=<(PRODUCT_DIR)',
+ ],
+ 'files': [
+ '<(PRODUCT_DIR)/gen/mojo/public/interfaces/bindings/tests/',
+ '<(PRODUCT_DIR)/python/',
+ 'python/tests/',
+ 'tools/pylib/mojo_python_tests_runner.py',
+ 'tools/run_mojo_python_bindings_tests.py',
+ ],
+ },
+ }],
+ ],
+}
diff --git a/mojo/mojo_services.gypi b/mojo/mojo_services.gypi
new file mode 100644
index 0000000..96f69f7
--- /dev/null
+++ b/mojo/mojo_services.gypi
@@ -0,0 +1,991 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ # GN version: //mojo/services/clipboard/
+ 'target_name': 'mojo_clipboard',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_clipboard_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'services/clipboard/clipboard_standalone_impl.cc',
+ 'services/clipboard/clipboard_standalone_impl.h',
+ 'services/clipboard/main.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/clipboard
+ 'target_name': 'mojo_clipboard_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/clipboard/clipboard.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/clipboard:mojo_clipboard_unittests
+ 'target_name': 'mojo_clipboard_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_run_all_unittests',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_clipboard_bindings',
+ 'mojo_shell_test_support',
+ ],
+ 'sources': [
+ 'services/clipboard/clipboard_standalone_unittest.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/services/html_viewer
+ 'target_name': 'mojo_html_viewer',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../cc/blink/cc_blink.gyp:cc_blink',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../media/blink/media_blink.gyp:media_blink',
+ '../media/media.gyp:media',
+ '../net/net.gyp:net',
+ '../skia/skia.gyp:skia',
+ '../third_party/WebKit/public/blink.gyp:blink',
+ '../ui/native_theme/native_theme.gyp:native_theme',
+ '../url/url.gyp:url_lib',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_clipboard_bindings',
+ 'mojo_cc_support',
+ 'mojo_content_handler_bindings',
+ 'mojo_gpu_bindings',
+ 'mojo_navigation_bindings',
+ 'mojo_network_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_view_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'include_dirs': [
+ 'third_party/WebKit'
+ ],
+ 'sources': [
+ 'services/html_viewer/blink_basic_type_converters.cc',
+ 'services/html_viewer/blink_basic_type_converters.h',
+ 'services/html_viewer/blink_input_events_type_converters.cc',
+ 'services/html_viewer/blink_input_events_type_converters.h',
+ 'services/html_viewer/blink_platform_impl.cc',
+ 'services/html_viewer/blink_platform_impl.h',
+ 'services/html_viewer/blink_url_request_type_converters.cc',
+ 'services/html_viewer/blink_url_request_type_converters.h',
+ 'services/html_viewer/html_viewer.cc',
+ 'services/html_viewer/html_document_view.cc',
+ 'services/html_viewer/html_document_view.h',
+ 'services/html_viewer/webclipboard_impl.cc',
+ 'services/html_viewer/webclipboard_impl.h',
+ 'services/html_viewer/webcookiejar_impl.cc',
+ 'services/html_viewer/webcookiejar_impl.h',
+ 'services/html_viewer/webmediaplayer_factory.cc',
+ 'services/html_viewer/webmediaplayer_factory.h',
+ 'services/html_viewer/webmimeregistry_impl.cc',
+ 'services/html_viewer/webmimeregistry_impl.h',
+ 'services/html_viewer/websockethandle_impl.cc',
+ 'services/html_viewer/websockethandle_impl.h',
+ 'services/html_viewer/webstoragenamespace_impl.cc',
+ 'services/html_viewer/webstoragenamespace_impl.h',
+ 'services/html_viewer/webthemeengine_impl.cc',
+ 'services/html_viewer/webthemeengine_impl.h',
+ 'services/html_viewer/webthread_impl.cc',
+ 'services/html_viewer/webthread_impl.h',
+ 'services/html_viewer/weburlloader_impl.cc',
+ 'services/html_viewer/weburlloader_impl.h',
+ 'services/html_viewer/weblayertreeview_impl.cc',
+ 'services/html_viewer/weblayertreeview_impl.h',
+ 'services/public/cpp/network/web_socket_read_queue.cc',
+ 'services/public/cpp/network/web_socket_read_queue.h',
+ 'services/public/cpp/network/web_socket_write_queue.cc',
+ 'services/public/cpp/network/web_socket_write_queue.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/input_events
+ '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_base.gyp:mojo_environment_chromium',
+ 'mojo_input_events_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ '<(mojo_system_for_component)',
+ ],
+ 'sources': [
+ 'services/public/cpp/input_events/lib/input_events_type_converters.cc',
+ 'services/public/cpp/input_events/lib/mojo_extended_key_event_data.cc',
+ 'services/public/cpp/input_events/lib/mojo_extended_key_event_data.h',
+ 'services/public/cpp/input_events/input_events_type_converters.h',
+ 'services/public/cpp/input_events/mojo_input_events_export.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/input_events
+ 'target_name': 'mojo_input_events_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/input_events/input_event_constants.mojom',
+ 'services/public/interfaces/input_events/input_events.mojom',
+ 'services/public/interfaces/input_events/input_key_codes.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_geometry_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_geometry_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/geometry
+ '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_base.gyp:mojo_cpp_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/geometry
+ 'target_name': 'mojo_geometry_lib',
+ 'type': '<(component)',
+ 'defines': [
+ 'MOJO_GEOMETRY_IMPLEMENTATION',
+ ],
+ 'dependencies': [
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_geometry_bindings',
+ '<(mojo_system_for_component)',
+ ],
+ 'export_dependent_settings': [
+ '../ui/gfx/gfx.gyp:gfx',
+ ],
+ 'sources': [
+ 'services/public/cpp/geometry/lib/geometry_type_converters.cc',
+ 'services/public/cpp/geometry/geometry_type_converters.h',
+ 'services/public/cpp/geometry/mojo_geometry_export.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/surfaces
+ 'target_name': 'mojo_surfaces_lib',
+ 'type': '<(component)',
+ 'defines': [
+ 'MOJO_SURFACES_IMPLEMENTATION',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../gpu/gpu.gyp:gpu',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_geometry_lib',
+ 'mojo_surfaces_bindings',
+ '<(mojo_system_for_component)',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_geometry_lib',
+ 'mojo_surfaces_bindings',
+ ],
+ 'sources': [
+ 'services/public/cpp/surfaces/lib/surfaces_type_converters.cc',
+ 'services/public/cpp/surfaces/lib/surfaces_utils.cc',
+ 'services/public/cpp/surfaces/surfaces_type_converters.h',
+ 'services/public/cpp/surfaces/surfaces_utils.h',
+ 'services/public/cpp/surfaces/mojo_surfaces_export.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/surfaces/tests
+ 'target_name': 'mojo_surfaces_lib_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../gpu/gpu.gyp:gpu',
+ '../skia/skia.gyp:skia',
+ '../testing/gtest.gyp:gtest',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../ui/gfx/gfx.gyp:gfx_test_support',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_base.gyp:mojo_run_all_unittests',
+ 'mojo_geometry_lib',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ ],
+ 'sources': [
+ 'services/public/cpp/surfaces/tests/surface_unittest.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/services/gles2
+ 'target_name': 'mojo_gles2_service',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../gpu/gpu.gyp:command_buffer_service',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../ui/gl/gl.gyp:gl',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ ],
+ 'sources': [
+ 'services/gles2/command_buffer_impl.cc',
+ 'services/gles2/command_buffer_impl.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/gpu
+ 'target_name': 'mojo_gpu_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/gpu/gpu.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_geometry_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_geometry_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/native_viewport
+ 'target_name': 'mojo_native_viewport_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/native_viewport/native_viewport.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_input_events_bindings',
+ 'mojo_surface_id_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_input_events_bindings',
+ 'mojo_surface_id_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/native_viewport
+ 'target_name': 'mojo_native_viewport_service_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../ui/events/events.gyp:events',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_gles2_service',
+ 'mojo_gpu_bindings',
+ 'mojo_input_events_lib',
+ 'mojo_native_viewport_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_geometry_bindings',
+ 'mojo_gpu_bindings',
+ 'mojo_native_viewport_bindings',
+ 'mojo_surfaces_bindings',
+ ],
+ 'sources': [
+ 'services/native_viewport/gpu_impl.cc',
+ 'services/native_viewport/gpu_impl.h',
+ 'services/native_viewport/native_viewport_impl.cc',
+ 'services/native_viewport/native_viewport_impl.h',
+ 'services/native_viewport/platform_viewport.h',
+ 'services/native_viewport/platform_viewport_android.cc',
+ 'services/native_viewport/platform_viewport_headless.cc',
+ 'services/native_viewport/platform_viewport_headless.h',
+ 'services/native_viewport/platform_viewport_mac.mm',
+ 'services/native_viewport/platform_viewport_ozone.cc',
+ 'services/native_viewport/platform_viewport_stub.cc',
+ 'services/native_viewport/platform_viewport_win.cc',
+ 'services/native_viewport/platform_viewport_x11.cc',
+ 'services/native_viewport/viewport_surface.cc',
+ 'services/native_viewport/viewport_surface.h',
+ ],
+ 'conditions': [
+ ['OS=="win" or OS=="android" or OS=="linux" or OS=="mac"', {
+ 'sources!': [
+ 'services/native_viewport/platform_viewport_stub.cc',
+ ],
+ }],
+ ['OS=="android"', {
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_jni_headers',
+ ],
+ }],
+ ['OS=="win"', {
+ 'dependencies': [
+ '../ui/platform_window/win/win_window.gyp:win_window',
+ ],
+ }],
+ ['use_x11==1', {
+ 'dependencies': [
+ '../ui/platform_window/x11/x11_window.gyp:x11_window',
+ '../ui/events/platform/x11/x11_events_platform.gyp:x11_events_platform',
+ ],
+ }],
+ ['use_ozone==1', {
+ 'dependencies': [
+ '../ui/ozone/ozone.gyp:ozone',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'mojo_native_viewport_service',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_native_viewport_bindings',
+ 'mojo_native_viewport_service_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_native_viewport_bindings',
+ ],
+ 'sources': [
+ 'services/native_viewport/main.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/navigation
+ '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_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_network_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/content_handler
+ 'target_name': 'mojo_content_handler_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/content_handler/content_handler.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_network_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/network
+ 'target_name': 'mojo_network_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/network/cookie_store.mojom',
+ 'services/public/interfaces/network/net_address.mojom',
+ 'services/public/interfaces/network/network_error.mojom',
+ 'services/public/interfaces/network/network_service.mojom',
+ 'services/public/interfaces/network/tcp_bound_socket.mojom',
+ 'services/public/interfaces/network/tcp_client_socket.mojom',
+ 'services/public/interfaces/network/tcp_server_socket.mojom',
+ 'services/public/interfaces/network/udp_socket.mojom',
+ 'services/public/interfaces/network/url_loader.mojom',
+ 'services/public/interfaces/network/web_socket.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/network:lib
+ 'target_name': 'mojo_network_service_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../net/net.gyp:net',
+ '../url/url.gyp:url_lib',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_network_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_network_bindings',
+ ],
+ 'sources': [
+ 'services/network/cookie_store_impl.cc',
+ 'services/network/cookie_store_impl.h',
+ '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',
+ 'services/network/web_socket_impl.cc',
+ 'services/network/web_socket_impl.h',
+ 'services/public/cpp/network/web_socket_read_queue.cc',
+ 'services/public/cpp/network/web_socket_read_queue.h',
+ 'services/public/cpp/network/web_socket_write_queue.cc',
+ 'services/public/cpp/network/web_socket_write_queue.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/network
+ 'target_name': 'mojo_network_service',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_network_bindings',
+ 'mojo_network_service_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_network_bindings',
+ ],
+ 'sources': [
+ 'services/network/main.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/services/surfaces
+ 'target_name': 'mojo_surfaces_service',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc',
+ '../cc/cc.gyp:cc_surfaces',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_cc_support',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_gpu_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'services/surfaces/surfaces_impl.cc',
+ 'services/surfaces/surfaces_impl.h',
+ 'services/surfaces/surfaces_service_application.cc',
+ 'services/surfaces/surfaces_service_application.h',
+ 'services/surfaces/surfaces_service_impl.cc',
+ 'services/surfaces/surfaces_service_impl.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/view_manager:common
+ 'target_name': 'mojo_view_manager_common',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/cpp/view_manager/types.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/view_manager
+ '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' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_input_events_bindings',
+ 'mojo_surface_id_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_input_events_bindings',
+ 'mojo_surface_id_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/view_manager
+ 'target_name': 'mojo_view_manager_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc_surfaces',
+ '../gpu/gpu.gyp:gpu',
+ '../skia/skia.gyp:skia',
+ '../ui/events/events.gyp:events',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../third_party/khronos/khronos.gyp:khronos_headers',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_application_bindings',
+ 'mojo_core_window_manager_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_common',
+ 'mojo_gpu_bindings',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'sources': [
+ 'services/public/cpp/view_manager/lib/bitmap_uploader.cc',
+ 'services/public/cpp/view_manager/lib/bitmap_uploader.h',
+ 'services/public/cpp/view_manager/lib/view.cc',
+ 'services/public/cpp/view_manager/lib/view_manager_client_factory.cc',
+ '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/lib/view_manager_context.cc',
+ 'services/public/cpp/view_manager/lib/view_observer.cc',
+ 'services/public/cpp/view_manager/lib/view_private.cc',
+ 'services/public/cpp/view_manager/lib/view_private.h',
+ 'services/public/cpp/view_manager/view.h',
+ 'services/public/cpp/view_manager/view_manager.h',
+ 'services/public/cpp/view_manager/view_manager_client_factory.h',
+ 'services/public/cpp/view_manager/view_manager_context.h',
+ 'services/public/cpp/view_manager/view_manager_delegate.h',
+ 'services/public/cpp/view_manager/view_observer.h',
+ 'services/public/cpp/view_manager/window_manager_delegate.h',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_gpu_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_view_manager_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/view_manager/tests:mojo_view_manager_lib_unittests
+ 'target_name': 'mojo_view_manager_lib_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_test_support',
+ 'mojo_base.gyp: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/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_base.gyp:mojo_run_all_unittests',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/surfaces
+ 'target_name': 'mojo_surfaces_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/surfaces/surfaces.mojom',
+ 'services/public/interfaces/surfaces/surfaces_service.mojom',
+ 'services/public/interfaces/surfaces/quads.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_surface_id_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ 'mojo_base.gyp:mojo_gles2_bindings',
+ 'mojo_geometry_bindings',
+ 'mojo_surface_id_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/surfaces:surface_id
+ 'target_name': 'mojo_surface_id_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/surfaces/surface_id.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/test_service:bindings
+ 'target_name': 'mojo_test_service_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ # TODO(tim): Move to services/public/interfaces?
+ 'services/test_service/test_request_tracker.mojom',
+ 'services/test_service/test_service.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ {
+ # GN version: //mojo/services/test_service
+ 'target_name': 'mojo_test_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_test_service_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'services/test_service/test_request_tracker_client_impl.cc',
+ 'services/test_service/test_request_tracker_client_impl.h',
+ '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',
+ 'services/test_service/test_time_service_impl.cc',
+ 'services/test_service/test_time_service_impl.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/test_service:request_tracker
+ 'target_name': 'mojo_test_request_tracker_app',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'mojo_base.gyp:mojo_utility',
+ 'mojo_base.gyp:mojo_application_standalone',
+ 'mojo_test_service_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'services/test_service/test_request_tracker_client_impl.cc',
+ 'services/test_service/test_request_tracker_client_impl.h',
+ 'services/test_service/test_request_tracker_application.cc',
+ 'services/test_service/test_request_tracker_application.h',
+ 'services/test_service/test_time_service_impl.cc',
+ 'services/test_service/test_time_service_impl.h',
+ 'services/test_service/test_request_tracker_impl.cc',
+ 'services/test_service/test_request_tracker_impl.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/interfaces/window_manager
+ 'target_name': 'mojo_core_window_manager_bindings',
+ 'type': 'static_library',
+ 'sources': [
+ 'services/public/interfaces/window_manager/window_manager.mojom',
+ ],
+ 'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+ 'export_dependent_settings': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ 'dependencies': [
+ 'mojo_base.gyp:mojo_cpp_bindings',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['use_aura==1', {
+ 'targets': [
+ {
+ # GN version: //mojo/services/view_manager
+ 'target_name': 'mojo_view_manager',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../cc/cc.gyp:cc_surfaces',
+ '../skia/skia.gyp:skia',
+ '../ui/base/ui_base.gyp:ui_base',
+ '../ui/events/events.gyp:events',
+ '../ui/events/events.gyp:events_base',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_gpu_bindings',
+ 'mojo_input_events_bindings',
+ 'mojo_input_events_lib',
+ 'mojo_native_viewport_bindings',
+ 'mojo_surfaces_bindings',
+ 'mojo_surfaces_lib',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_common',
+ 'mojo_gpu_bindings',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'services/view_manager/access_policy.h',
+ 'services/view_manager/access_policy_delegate.h',
+ 'services/view_manager/connection_manager.cc',
+ 'services/view_manager/connection_manager.h',
+ 'services/view_manager/default_access_policy.cc',
+ 'services/view_manager/default_access_policy.h',
+ 'services/view_manager/display_manager.cc',
+ 'services/view_manager/display_manager.h',
+ 'services/view_manager/ids.h',
+ 'services/view_manager/main.cc',
+ 'services/view_manager/server_view.cc',
+ 'services/view_manager/server_view.h',
+ 'services/view_manager/server_view_delegate.h',
+ 'services/view_manager/view_manager_export.h',
+ 'services/view_manager/view_manager_init_service_context.cc',
+ 'services/view_manager/view_manager_init_service_context.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/window_manager_access_policy.cc',
+ 'services/view_manager/window_manager_access_policy.h',
+ ],
+ 'includes': [
+ 'mojo_public_gles2_for_loadable_module.gypi',
+ ],
+ 'defines': [
+ 'MOJO_VIEW_MANAGER_IMPLEMENTATION',
+ ],
+ },
+ {
+ # GN version: //mojo/services/public/cpp/view_manager/lib:run_unittests
+ 'target_name': 'mojo_view_manager_run_unittests',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ ],
+ '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',
+ ],
+ 'conditions': [
+ ['use_x11==1', {
+ 'dependencies': [
+ '../ui/gfx/x/gfx_x11.gyp:gfx_x11',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //mojo/services/view_manager:mojo_view_manager_unittests
+ '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',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_geometry_bindings',
+ 'mojo_geometry_lib',
+ 'mojo_input_events_bindings',
+ 'mojo_input_events_lib',
+ 'mojo_shell_test_support',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_common',
+ 'mojo_view_manager_run_unittests',
+ # Included only to force deps for bots.
+ 'mojo_native_viewport_service',
+ 'mojo_surfaces_service',
+ 'mojo_view_manager',
+ ],
+ 'sources': [
+ 'services/view_manager/test_change_tracker.cc',
+ 'services/view_manager/test_change_tracker.h',
+ 'services/view_manager/view_manager_unittest.cc',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'dependencies': [
+ '../ui/gfx/gfx.gyp:gfx',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'package_mojo_view_manager',
+ 'variables': {
+ 'app_name': 'mojo_view_manager',
+ },
+ 'includes': [ 'build/package_app.gypi' ],
+ },
+ {
+ # GN version: //mojo/services/window_manager:lib
+ 'target_name': 'mojo_core_window_manager_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../ui/base/ui_base.gyp:ui_base',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gfx/gfx.gyp:gfx_geometry',
+ '../ui/wm/wm.gyp:wm',
+ 'mojo_base.gyp:mojo_common_lib',
+ 'mojo_base.gyp:mojo_application_chromium',
+ 'mojo_aura_support',
+ 'mojo_core_window_manager_bindings',
+ 'mojo_input_events_lib',
+ 'mojo_view_manager_lib',
+ ],
+ 'sources': [
+ 'services/window_manager/window_manager_app.cc',
+ 'services/window_manager/window_manager_app.h',
+ 'services/window_manager/window_manager_service_impl.cc',
+ 'services/window_manager/window_manager_service_impl.h',
+ ],
+ },
+ {
+ # GN version: //mojo/services/window_manager
+ 'target_name': 'mojo_core_window_manager',
+ 'type': 'loadable_module',
+ 'dependencies': [
+ 'mojo_core_window_manager_lib',
+ '<(mojo_system_for_loadable_module)',
+ ],
+ 'sources': [
+ 'services/window_manager/main.cc',
+ ],
+ },
+ {
+ # GN version: //mojo/services/window_manager:mojo_core_window_manager_unittests
+ 'target_name': 'mojo_core_window_manager_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ 'mojo_application_manager',
+ 'mojo_base.gyp:mojo_system_impl',
+ 'mojo_base.gyp:mojo_environment_chromium',
+ 'mojo_core_window_manager_bindings',
+ 'mojo_shell_test_support',
+ 'mojo_view_manager_bindings',
+ 'mojo_view_manager_lib',
+ ],
+ 'sources': [
+ 'services/window_manager/window_manager_api_unittest.cc',
+ 'services/window_manager/window_manager_unittests.cc',
+ ],
+ 'conditions': [
+ ['OS=="linux"', {
+ 'dependencies': [
+ '../third_party/mesa/mesa.gyp:osmesa',
+ 'mojo_native_viewport_service_lib',
+ ],
+ }],
+ ['use_x11==1', {
+ 'dependencies': [
+ '../ui/gfx/x/gfx_x11.gyp:gfx_x11',
+ ],
+ }],
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/mojo/mojo_variables.gypi b/mojo/mojo_variables.gypi
new file mode 100644
index 0000000..39146d7
--- /dev/null
+++ b/mojo/mojo_variables.gypi
@@ -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.
+
+#
+# A set of GYP variables that are shared between various mojo .gyp files.
+#
+{
+ 'variables': {
+ 'chromium_code': 1,
+ 'mojo_shell_debug_url%': "",
+ 'conditions': [
+ #
+ # The following mojo_system-prefixed variables are used to express a
+ # dependency on the mojo system APIs.
+ #
+ # In a component == "shared_library" build, everything can link against
+ # mojo_system_impl because it is built as a shared library. However, in a
+ # component != "shared_library" build, mojo_system_impl is linked into an
+ # executable (e.g., mojo_shell), and must be injected into other shared
+ # libraries (i.e., Mojo Apps) that need the mojo system API.
+ #
+ # For component targets, add <(mojo_system_for_component) to your
+ # dependencies section. For loadable module targets (e.g., a Mojo App),
+ # add <(mojo_system_for_loadable_module) to your dependencies section.
+ #
+ # NOTE: component != "shared_library" implies that we are generating a
+ # static library, and in that case, it is expected that the target
+ # listing the component as a dependency will specify either mojo_system
+ # or mojo_system_impl to link against. This enables multiple targets to
+ # link against the same component library without having to agree on
+ # which Mojo system library they are using.
+ #
+ ['component=="shared_library"', {
+ 'mojo_system_for_component': "<(DEPTH)/mojo/mojo_base.gyp:mojo_system_impl",
+ 'mojo_system_for_loadable_module': "<(DEPTH)/mojo/mojo_base.gyp:mojo_system_impl",
+ 'mojo_gles2_for_component': "<(DEPTH)/mojo/mojo_base.gyp:mojo_gles2_impl",
+ }, {
+ 'mojo_system_for_component': "<(DEPTH)/mojo/mojo_base.gyp:mojo_none",
+ 'mojo_system_for_loadable_module': "<(DEPTH)/mojo/mojo_base.gyp:mojo_system",
+ 'mojo_gles2_for_component': "<(DEPTH)/mojo/mojo_base.gyp:mojo_none",
+ }],
+ ],
+ 'mojo_public_system_unittest_sources': [
+ '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',
+ ],
+ },
+}
diff --git a/mojo/nacl/DEPS b/mojo/nacl/DEPS
new file mode 100644
index 0000000..69e7ada
--- /dev/null
+++ b/mojo/nacl/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+native_client/src/public",
+ "+native_client/src/trusted/desc",
+ "+native_client/src/trusted/service_runtime",
+]
diff --git a/mojo/nacl/README b/mojo/nacl/README
new file mode 100644
index 0000000..36913ac
--- /dev/null
+++ b/mojo/nacl/README
@@ -0,0 +1,24 @@
+This is a prototype for plumbing Mojo into the NaCl sandbox. It is
+currently insecure (see below), does not provide a stable ABI (IRT
+support must be added), and does not support Mojo functions that
+return pointers (for example, MojoMapBuffer).
+
+generator/interface.py contains a programmatic description of the
+stable Mojo interface. This will need to be updated as the interface
+changes. Run generator/generate_nacl_bindings.py to generate the
+bindings that plumb this interface into the NaCl sandbox.
+
+To test: Build "monacl_shell" and "monacl_test". Run monacl_shell
+with the IRT as the first argument and the monacl_test as the second
+argument. For example, to run a Debug 32-bit Intel build:
+
+ cd out/Debug
+ ./monacl_shell irt_core_newlib_x32.nexe monacl_test_newlib_x32.nexe
+
+Security TODO list:
+ Separate trusted and untrusted Mojo handles.
+ Validate and copy option structures.
+ Protect untrusted buffers passed into Mojo:
+ NaClVmIoWillStart/HasEnded.
+ volatile accesses to untrusted memory (untrusted code could race).
+ Overflow checking in array bounds validation.
\ No newline at end of file
diff --git a/mojo/nacl/generator/generate_nacl_bindings.py b/mojo/nacl/generator/generate_nacl_bindings.py
new file mode 100755
index 0000000..2c8ae9d
--- /dev/null
+++ b/mojo/nacl/generator/generate_nacl_bindings.py
@@ -0,0 +1,404 @@
+#!/usr/bin/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.
+
+# pylint: disable=W0104,W0106,F0401,R0201
+
+import optparse
+import os.path
+import sys
+
+import interface
+
+
+def _ScriptDir():
+ return os.path.dirname(os.path.abspath(__file__))
+
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = _ScriptDir()
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+
+def _AddThirdPartyImportPath():
+ sys.path.append(os.path.join(_GetDirAbove('mojo'), 'third_party'))
+
+
+_AddThirdPartyImportPath()
+import jinja2
+
+loader = jinja2.FileSystemLoader(_ScriptDir())
+jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True)
+
+
+# Accumulate lines of code with varying levels of indentation.
+class CodeWriter(object):
+ def __init__(self):
+ self._lines = []
+ self._margin = ''
+ self._margin_stack = []
+
+ def __lshift__(self, line):
+ self._lines.append((self._margin + line).rstrip())
+
+ def PushMargin(self):
+ self._margin_stack.append(self._margin)
+ self._margin += ' '
+
+ def PopMargin(self):
+ self._margin = self._margin_stack.pop()
+
+ def GetValue(self):
+ return '\n'.join(self._lines).rstrip() + '\n'
+
+ def Indent(self):
+ return Indent(self)
+
+
+# Context handler that automatically indents and dedents a CodeWriter
+class Indent(object):
+ def __init__(self, writer):
+ self._writer = writer
+
+ def __enter__(self):
+ self._writer.PushMargin()
+
+ def __exit__(self, type_, value, traceback):
+ self._writer.PopMargin()
+
+
+def TemplateFile(name):
+ return os.path.join(os.path.dirname(__file__), name)
+
+
+# Wraps comma separated lists as needed.
+def Wrap(pre, items, post):
+ complete = pre + ', '.join(items) + post
+ if len(complete) <= 80:
+ return [complete]
+ lines = [pre]
+ indent = ' '
+ for i, item in enumerate(items):
+ if i < len(items) - 1:
+ lines.append(indent + item + ',')
+ else:
+ lines.append(indent + item + post)
+ return lines
+
+
+def GeneratorWarning():
+ return ('// WARNING this file was generated by %s\n// Do not edit by hand.' %
+ os.path.basename(__file__))
+
+
+# Untrusted library implementing the public Mojo API.
+def GenerateLibMojo(functions, out):
+ template = jinja_env.get_template('libmojo.cc.tmpl')
+
+ code = CodeWriter()
+
+ for f in functions:
+ for line in Wrap('%s %s(' % (f.return_type, f.name), f.ParamList(), ') {'):
+ code << line
+
+ # 2 extra parameters: message ID and return value.
+ num_params = len(f.params) + 2
+
+ with code.Indent():
+ code << 'uint32_t params[%d];' % num_params
+ return_type = f.result_param.base_type
+ if return_type == 'MojoResult':
+ default = 'MOJO_RESULT_INVALID_ARGUMENT'
+ elif return_type == 'MojoTimeTicks':
+ default = '0'
+ else:
+ raise Exception('Unhandled return type: ' + return_type)
+ code << '%s %s = %s;' % (return_type, f.result_param.name, default)
+
+ # Message ID
+ code << 'params[0] = %d;' % f.uid
+ # Parameter pointers
+ cast_template = 'params[%d] = reinterpret_cast<uint32_t>(%s);'
+ for p in f.params:
+ ptr = p.name
+ if p.IsPassedByValue():
+ ptr = '&' + ptr
+ code << cast_template % (p.uid + 1, ptr)
+ # Return value pointer
+ code << cast_template % (num_params - 1, '&' + f.result_param.name)
+
+ code << 'DoMojoCall(params, sizeof(params));'
+ code << 'return %s;' % f.result_param.name
+
+ code << '}'
+ code << ''
+
+ body = code.GetValue()
+ text = template.render(
+ generator_warning=GeneratorWarning(),
+ body=body)
+ out.write(text)
+
+
+# Parameters passed into trusted code are handled differently depending on
+# details of the parameter. ParamImpl instances encapsulate these differences
+# and are used to generate the code that transfers parameters across the
+# untrusted/trusted boundary.
+class ParamImpl(object):
+ def __init__(self, param):
+ self.param = param
+
+ # Declare whatever variables are needed to handle this particular parameter.
+ def DeclareVars(self, code):
+ raise NotImplementedError()
+
+ # Convert the untrusted representation of the parameter into a trusted
+ # representation, such as a scalar value or a trusted pointer into the
+ # untrusted address space.
+ def ConvertParam(self):
+ raise NotImplementedError()
+
+ # For this particular parameter, what expression should be passed when
+ # invoking the trusted Mojo API function?
+ def CallParam(self):
+ raise NotImplementedError()
+
+ # After invoking the trusted Mojo API function, transfer data back into
+ # untrusted memory. Overriden for Out and InOut parameters.
+ def CopyOut(self, code):
+ pass
+
+ # Converting array parameters needs to be defered until after the scalar
+ # parameter containing the size of the array has itself been converted.
+ def IsArray(self):
+ return False
+
+
+class ScalarInputImpl(ParamImpl):
+ def DeclareVars(self, code):
+ code << '%s %s_value;' % (self.param.base_type, self.param.name)
+
+ def ConvertParam(self):
+ p = self.param
+ return ('ConvertScalarInput(nap, params[%d], &%s_value)' %
+ (p.uid + 1, p.name))
+
+ def CallParam(self):
+ return '%s_value' % self.param.name
+
+
+class ScalarOutputImpl(ParamImpl):
+ def DeclareVars(self, code):
+ code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name)
+ code << '%s %s_value;' % (self.param.base_type, self.param.name)
+
+ def ConvertParam(self):
+ p = self.param
+ return 'ConvertScalarOutput(nap, params[%d], &%s_ptr)' % (p.uid + 1, p.name)
+
+ def CallParam(self):
+ return '&%s_value' % self.param.name
+
+ def CopyOut(self, code):
+ name = self.param.name
+ code << '*%s_ptr = %s_value;' % (name, name)
+
+
+class ScalarInOutImpl(ParamImpl):
+ def DeclareVars(self, code):
+ code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name)
+ code << '%s %s_value;' % (self.param.base_type, self.param.name)
+
+ def ConvertParam(self):
+ p = self.param
+ return ('ConvertScalarInOut(nap, params[%d], %s, &%s_value, &%s_ptr)' %
+ (p.uid + 1, CBool(p.is_optional), p.name, p.name))
+
+ def CallParam(self):
+ name = self.param.name
+ expr = '&%s_value' % name
+ if self.param.is_optional:
+ expr = '%s_ptr ? %s : NULL' % (name, expr)
+ return expr
+
+ def CopyOut(self, code):
+ name = self.param.name
+ if self.param.is_optional:
+ code << 'if (%s_ptr != NULL) {' % (name)
+ with code.Indent():
+ code << '*%s_ptr = %s_value;' % (name, name)
+ code << '}'
+ else:
+ code << '*%s_ptr = %s_value;' % (name, name)
+
+
+class ArrayImpl(ParamImpl):
+ def DeclareVars(self, code):
+ code << '%s %s;' % (self.param.param_type, self.param.name)
+
+ def ConvertParam(self):
+ p = self.param
+ if p.base_type == 'void':
+ element_size = '1'
+ else:
+ element_size = 'sizeof(*%s)' % p.name
+
+ return ('ConvertArray(nap, params[%d], %s, %s, %s, &%s)' %
+ (p.uid + 1, p.size + '_value', element_size, CBool(p.is_optional),
+ p.name))
+
+ def CallParam(self):
+ return self.param.name
+
+ def IsArray(self):
+ return True
+
+
+class StructInputImpl(ParamImpl):
+ def DeclareVars(self, code):
+ code << '%s %s;' % (self.param.param_type, self.param.name)
+
+ def ConvertParam(self):
+ p = self.param
+ return ('ConvertStruct(nap, params[%d], %s, &%s)' %
+ (p.uid + 1, CBool(p.is_optional), p.name))
+
+ def CallParam(self):
+ return self.param.name
+
+
+def ImplForParam(p):
+ if p.IsScalar():
+ if p.is_output:
+ if p.is_input:
+ return ScalarInOutImpl(p)
+ else:
+ return ScalarOutputImpl(p)
+ else:
+ return ScalarInputImpl(p)
+ elif p.is_array:
+ return ArrayImpl(p)
+ elif p.is_struct:
+ return StructInputImpl(p)
+ else:
+ assert False, p
+
+
+def CBool(value):
+ return 'true' if value else 'false'
+
+
+# A trusted wrapper that validates the arguments passed from untrusted code
+# before passing them to the underlying public Mojo API.
+def GenerateMojoSyscall(functions, out):
+ template = jinja_env.get_template('mojo_syscall.cc.tmpl')
+
+ code = CodeWriter()
+ code.PushMargin()
+
+ for f in functions:
+ impls = [ImplForParam(p) for p in f.params]
+ impls.append(ImplForParam(f.result_param))
+
+ code << 'case %d:' % f.uid
+
+ code.PushMargin()
+
+ code << '{'
+
+ with code.Indent():
+ num_params = len(f.params) + 2
+ code << 'if (num_params != %d) {' % num_params
+ with code.Indent():
+ code << 'return -1;'
+ code << '}'
+
+ # Declare temporaries.
+ for impl in impls:
+ impl.DeclareVars(code)
+
+ def ConvertParam(code, impl):
+ code << 'if (!%s) {' % impl.ConvertParam()
+ with code.Indent():
+ code << 'return -1;'
+ code << '}'
+
+ code << '{'
+ with code.Indent():
+ code << 'ScopedCopyLock copy_lock(nap);'
+ # Convert and validate pointers in two passes.
+ # Arrays cannot be validated until the size parameter has been
+ # converted.
+ for impl in impls:
+ if not impl.IsArray():
+ ConvertParam(code, impl)
+ for impl in impls:
+ if impl.IsArray():
+ ConvertParam(code, impl)
+ code << '}'
+ code << ''
+
+ # Call
+ getParams = [impl.CallParam() for impl in impls[:-1]]
+ code << 'result_value = %s(%s);' % (f.name, ', '.join(getParams))
+ code << ''
+
+ # Write outputs
+ code << '{'
+ with code.Indent():
+ code << 'ScopedCopyLock copy_lock(nap);'
+ for impl in impls:
+ impl.CopyOut(code)
+ code << '}'
+ code << ''
+
+ code << 'return 0;'
+ code << '}'
+
+ code.PopMargin()
+
+ body = code.GetValue()
+ text = template.render(
+ generator_warning=GeneratorWarning(),
+ body=body)
+ out.write(text)
+
+
+def OutFile(dir_path, name):
+ if not os.path.exists(dir_path):
+ os.makedirs(dir_path)
+ return open(os.path.join(dir_path, name), 'w')
+
+
+def main(args):
+ usage = 'usage: %prog [options]'
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option(
+ '-d',
+ dest='out_dir',
+ metavar='DIR',
+ help='output generated code into directory DIR')
+ options, args = parser.parse_args(args=args)
+ if not options.out_dir:
+ parser.error('-d is required')
+ if args:
+ parser.error('unexpected positional arguments: %s' % ' '.join(args))
+
+ mojo = interface.MakeInterface()
+
+ out = OutFile(options.out_dir, 'libmojo.cc')
+ GenerateLibMojo(mojo.functions, out)
+
+ out = OutFile(options.out_dir, 'mojo_syscall.cc')
+ GenerateMojoSyscall(mojo.functions, out)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/mojo/nacl/generator/interface.py b/mojo/nacl/generator/interface.py
new file mode 100644
index 0000000..2a23fd7
--- /dev/null
+++ b/mojo/nacl/generator/interface.py
@@ -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.
+
+# pylint: disable=F0401
+
+import interface_dsl
+
+def MakeInterface():
+ mojo = interface_dsl.Interface()
+
+ f = mojo.Func('MojoCreateSharedBuffer', 'MojoResult')
+ f.Param('options').InStruct('MojoCreateSharedBufferOptions').Optional()
+ f.Param('num_bytes').In('uint64_t')
+ f.Param('shared_buffer_handle').Out('MojoHandle')
+
+ f = mojo.Func('MojoDuplicateBufferHandle', 'MojoResult')
+ f.Param('buffer_handle').In('MojoHandle')
+ f.Param('options').InStruct('MojoDuplicateBufferHandleOptions').Optional()
+ f.Param('new_buffer_handle').Out('MojoHandle')
+
+ f = mojo.Func('MojoMapBuffer', 'MojoResult')
+ f.Param('buffer_handle').In('MojoHandle')
+ f.Param('offset').In('uint64_t')
+ f.Param('num_bytes').In('uint64_t')
+ f.Param('buffer').Out('void*')
+ f.Param('flags').In('MojoMapBufferFlags')
+
+ f = mojo.Func('MojoUnmapBuffer', 'MojoResult')
+ f.Param('buffer').In('void*')
+
+ f = mojo.Func('MojoCreateDataPipe', 'MojoResult')
+ f.Param('options').InStruct('MojoCreateDataPipeOptions').Optional()
+ f.Param('data_pipe_producer_handle').Out('MojoHandle')
+ f.Param('data_pipe_consumer_handle').Out('MojoHandle')
+
+ f = mojo.Func('MojoWriteData', 'MojoResult')
+ f.Param('data_pipe_producer_handle').In('MojoHandle')
+ f.Param('elements').InArray('void', 'num_bytes')
+ f.Param('num_bytes').InOut('uint32_t')
+ f.Param('flags').In('MojoWriteDataFlags')
+
+ f = mojo.Func('MojoBeginWriteData', 'MojoResult')
+ f.Param('data_pipe_producer_handle').In('MojoHandle')
+ f.Param('buffer').Out('void*')
+ f.Param('buffer_num_bytes').InOut('uint32_t')
+ f.Param('flags').In('MojoWriteDataFlags')
+
+ f = mojo.Func('MojoEndWriteData', 'MojoResult')
+ f.Param('data_pipe_producer_handle').In('MojoHandle')
+ f.Param('num_bytes_written').In('uint32_t')
+
+ f = mojo.Func('MojoReadData', 'MojoResult')
+ f.Param('data_pipe_consumer_handle').In('MojoHandle')
+ f.Param('elements').OutArray('void', 'num_bytes')
+ f.Param('num_bytes').InOut('uint32_t')
+ f.Param('flags').In('MojoReadDataFlags')
+
+ f = mojo.Func('MojoBeginReadData', 'MojoResult')
+ f.Param('data_pipe_consumer_handle').In('MojoHandle')
+ f.Param('buffer').Out('const void*')
+ f.Param('buffer_num_bytes').InOut('uint32_t')
+ f.Param('flags').In('MojoReadDataFlags')
+
+ f = mojo.Func('MojoEndReadData', 'MojoResult')
+ f.Param('data_pipe_consumer_handle').In('MojoHandle')
+ f.Param('num_bytes_read').In('uint32_t')
+
+ f = mojo.Func('MojoGetTimeTicksNow', 'MojoTimeTicks')
+
+ f = mojo.Func('MojoClose', 'MojoResult')
+ f.Param('handle').In('MojoHandle')
+
+ f = mojo.Func('MojoWait', 'MojoResult')
+ f.Param('handle').In('MojoHandle')
+ f.Param('signals').In('MojoHandleSignals')
+ f.Param('deadline').In('MojoDeadline')
+
+ f = mojo.Func('MojoWaitMany', 'MojoResult')
+ f.Param('handles').InArray('MojoHandle', 'num_handles')
+ f.Param('signals').InArray('MojoHandleSignals', 'num_handles')
+ f.Param('num_handles').In('uint32_t')
+ f.Param('deadline').In('MojoDeadline')
+
+ f = mojo.Func('MojoCreateMessagePipe', 'MojoResult')
+ f.Param('options').InStruct('MojoCreateMessagePipeOptions').Optional()
+ f.Param('message_pipe_handle0').Out('MojoHandle')
+ f.Param('message_pipe_handle1').Out('MojoHandle')
+
+ f = mojo.Func('MojoWriteMessage', 'MojoResult')
+ f.Param('message_pipe_handle').In('MojoHandle')
+ f.Param('bytes').InArray('void', 'num_bytes').Optional()
+ f.Param('num_bytes').In('uint32_t')
+ f.Param('handles').InArray('MojoHandle', 'num_handles').Optional()
+ f.Param('num_handles').In('uint32_t')
+ f.Param('flags').In('MojoWriteMessageFlags')
+
+ f = mojo.Func('MojoReadMessage', 'MojoResult')
+ f.Param('message_pipe_handle').In('MojoHandle')
+ f.Param('bytes').OutArray('void', 'num_bytes').Optional()
+ f.Param('num_bytes').InOut('uint32_t').Optional()
+ f.Param('handles').OutArray('MojoHandle', 'num_handles').Optional()
+ f.Param('num_handles').InOut('uint32_t').Optional()
+ f.Param('flags').In('MojoReadMessageFlags')
+
+ mojo.Finalize()
+
+ return mojo
diff --git a/mojo/nacl/generator/interface_dsl.py b/mojo/nacl/generator/interface_dsl.py
new file mode 100644
index 0000000..dfc85aa
--- /dev/null
+++ b/mojo/nacl/generator/interface_dsl.py
@@ -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.
+
+class Interface(object):
+ def __init__(self):
+ self.functions = []
+
+ def Func(self, name, return_type):
+ f = Function(self, len(self.functions), name, return_type)
+ self.functions.append(f)
+ return f
+
+ def Finalize(self):
+ for f in self.functions:
+ f.Finalize()
+
+class Function(object):
+ def __init__(self, parent, uid, name, return_type):
+ self.parent = parent
+ self.uid = uid
+ self.name = name
+ self.return_type = return_type
+ self.params = []
+ self.param_by_name = {}
+ self.result_param = None
+
+ def Param(self, name, param_type=None):
+ p = Param(self, len(self.params), name, param_type)
+ self.params.append(p)
+ self.param_by_name[name] = p
+ return p
+
+ def ParamList(self):
+ return [param.param_type + ' ' + param.name for param in self.params]
+
+ def ParamDecl(self):
+ if self.params:
+ return ', '.join(self.ParamList())
+ else:
+ return 'void'
+
+ def Finalize(self):
+ self.result_param = Param(self, len(self.params), 'result')
+ self.result_param.Out(self.return_type)
+
+class Param(object):
+ def __init__(self, parent, uid, name, param_type=None):
+ self.parent = parent
+ self.uid = uid
+ self.name = name
+ self.base_type = param_type
+ self.param_type = param_type
+ self.size = None
+ self.is_input = False
+ self.is_output = False
+ self.is_array = False
+ self.is_struct = False
+ self.is_optional = False
+
+ def GetSizeParam(self):
+ assert self.size
+ return self.parent.param_by_name[self.size]
+
+ def In(self, ty):
+ self.base_type = ty
+ self.param_type = ty
+ self.is_input = True
+ return self
+
+ def InArray(self, ty, size):
+ self.base_type = ty
+ self.param_type = 'const ' + ty + '*'
+ self.size = size
+ self.is_input = True
+ self.is_array = True
+ return self
+
+ def InStruct(self, ty):
+ self.base_type = ty
+ self.param_type = 'const struct ' + ty + '*'
+ self.is_input = True
+ self.is_struct = True
+ return self
+
+ def InOut(self, ty):
+ self.base_type = ty
+ self.param_type = ty + '*'
+ self.is_input = True
+ self.is_output = True
+ return self
+
+ def Out(self, ty):
+ self.base_type = ty
+ self.param_type = ty + '*'
+ self.is_output = True
+ return self
+
+ def OutArray(self, ty, size):
+ self.base_type = ty
+ self.param_type = ty + '*'
+ self.size = size
+ self.is_array = True
+ self.is_output = True
+ return self
+
+ def Optional(self):
+ assert not self.IsPassedByValue()
+ self.is_optional = True
+ return self
+
+ def IsScalar(self):
+ return not self.is_array and not self.is_struct
+
+ def IsPassedByValue(self):
+ return not self.is_output and self.IsScalar()
diff --git a/mojo/nacl/generator/libmojo.cc.tmpl b/mojo/nacl/generator/libmojo.cc.tmpl
new file mode 100644
index 0000000..7690d82
--- /dev/null
+++ b/mojo/nacl/generator/libmojo.cc.tmpl
@@ -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.
+
+{{generator_warning}}
+
+#include "mojo/public/c/system/core.h"
+#include "native_client/src/public/chrome_main.h"
+#include "native_client/src/public/imc_syscalls.h"
+#include "native_client/src/public/imc_types.h"
+
+#define NACL_MOJO_DESC (NACL_CHROME_DESC_BASE + 2)
+
+static void DoMojoCall(uint32_t params[], nacl_abi_size_t num_params) {
+ NaClAbiNaClImcMsgIoVec iov[1] = {
+ {params, num_params}
+ };
+ NaClAbiNaClImcMsgHdr msgh = {iov, 1, NULL, 0};
+ // Note: return value unchecked. We're relying on the result parameter being
+ // unmodified - if the syscall fails, the Mojo function will return whatever
+ // the result parameter was initialized to before this function was called.
+ imc_sendmsg(NACL_MOJO_DESC, &msgh, 0);
+}
+
+{{body}}
diff --git a/mojo/nacl/generator/mojo_syscall.cc.tmpl b/mojo/nacl/generator/mojo_syscall.cc.tmpl
new file mode 100644
index 0000000..136e672
--- /dev/null
+++ b/mojo/nacl/generator/mojo_syscall.cc.tmpl
@@ -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.
+
+{{generator_warning}}
+
+#include "mojo/nacl/mojo_syscall.h"
+
+#include <stdio.h>
+
+#include "mojo/nacl/mojo_syscall_internal.h"
+#include "mojo/public/c/system/core.h"
+#include "native_client/src/public/chrome_main.h"
+#include "native_client/src/public/nacl_app.h"
+#include "native_client/src/trusted/desc/nacl_desc_custom.h"
+
+namespace {
+
+void MojoDescDestroy(void* handle) {
+}
+
+ssize_t MojoDescSendMsg(void* handle,
+ const struct NaClImcTypedMsgHdr* msg,
+ int flags) {
+ struct NaClApp* nap = static_cast<struct NaClApp*>(handle);
+
+ if (msg->iov_length != 1 || msg->ndesc_length != 0) {
+ return -1;
+ }
+
+ uint32_t volatile* params = static_cast<uint32_t volatile*>(msg->iov[0].base);
+ size_t num_params = msg->iov[0].length / sizeof(*params);
+
+ if (num_params < 1) {
+ return -1;
+ }
+
+ uint32_t msg_type = params[0];
+ switch (msg_type) {
+{{body}}
+ }
+
+ return -1;
+}
+
+ssize_t MojoDescRecvMsg(void* handle,
+ struct NaClImcTypedMsgHdr* msg,
+ int flags) {
+ return -1;
+}
+
+struct NaClDesc* MakeMojoDesc(struct NaClApp* nap) {
+ struct NaClDescCustomFuncs funcs = NACL_DESC_CUSTOM_FUNCS_INITIALIZER;
+ funcs.Destroy = MojoDescDestroy;
+ funcs.SendMsg = MojoDescSendMsg;
+ funcs.RecvMsg = MojoDescRecvMsg;
+ return NaClDescMakeCustomDesc(nap, &funcs);
+}
+
+} // namespace
+
+#define NACL_MOJO_DESC (NACL_CHROME_DESC_BASE + 2)
+
+void InjectMojo(struct NaClApp* nap) {
+ NaClAppSetDesc(nap, NACL_MOJO_DESC, MakeMojoDesc(nap));
+}
diff --git a/mojo/nacl/mojo_syscall.h b/mojo/nacl/mojo_syscall.h
new file mode 100644
index 0000000..f195769
--- /dev/null
+++ b/mojo/nacl/mojo_syscall.h
@@ -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.
+
+#ifndef MOJO_NACL_MOJO_SYSCALL_H_
+#define MOJO_NACL_MOJO_SYSCALL_H_
+
+void InjectMojo(struct NaClApp* nap);
+
+#endif // MOJO_NACL_MOJO_SYSCALL_H_
diff --git a/mojo/nacl/mojo_syscall_internal.h b/mojo/nacl/mojo_syscall_internal.h
new file mode 100644
index 0000000..f0795e6
--- /dev/null
+++ b/mojo/nacl/mojo_syscall_internal.h
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium 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_NACL_MOJO_SYSCALL_INTERNAL_H_
+#define MOJO_NACL_MOJO_SYSCALL_INTERNAL_H_
+
+#include "native_client/src/trusted/service_runtime/nacl_copy.h"
+#include "native_client/src/trusted/service_runtime/sel_ldr.h"
+
+namespace {
+
+class ScopedCopyLock {
+ public:
+ explicit ScopedCopyLock(struct NaClApp* nap) : nap_(nap) {
+ NaClCopyTakeLock(nap_);
+ }
+ ~ScopedCopyLock() {
+ NaClCopyDropLock(nap_);
+ }
+ private:
+ struct NaClApp* nap_;
+};
+
+static inline uintptr_t NaClUserToSysAddrArray(
+ struct NaClApp* nap,
+ uint32_t uaddr,
+ size_t count,
+ size_t size) {
+ // TODO(ncbray): overflow checking
+ size_t range = count * size;
+ return NaClUserToSysAddrRange(nap, uaddr, range);
+}
+
+template <typename T> bool ConvertScalarInput(
+ struct NaClApp* nap,
+ uint32_t user_ptr,
+ T* value) {
+ if (user_ptr) {
+ uintptr_t temp = NaClUserToSysAddrRange(nap, user_ptr, sizeof(T));
+ if (temp != kNaClBadAddress) {
+ *value = *reinterpret_cast<T volatile*>(temp);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T> bool ConvertScalarOutput(
+ struct NaClApp* nap,
+ uint32_t user_ptr,
+ T volatile** sys_ptr) {
+ if (user_ptr) {
+ uintptr_t temp = NaClUserToSysAddrRange(nap, user_ptr, sizeof(T));
+ if (temp != kNaClBadAddress) {
+ *sys_ptr = reinterpret_cast<T volatile*>(temp);
+ return true;
+ }
+ }
+ *sys_ptr = 0; // Paranoia.
+ return false;
+}
+
+template <typename T> bool ConvertScalarInOut(
+ struct NaClApp* nap,
+ uint32_t user_ptr,
+ bool optional,
+ T* value,
+ T volatile** sys_ptr) {
+ if (user_ptr) {
+ uintptr_t temp = NaClUserToSysAddrRange(nap, user_ptr, sizeof(T));
+ if (temp != kNaClBadAddress) {
+ T volatile* converted = reinterpret_cast<T volatile*>(temp);
+ *sys_ptr = converted;
+ *value = *converted;
+ return true;
+ }
+ } else if (optional) {
+ *sys_ptr = 0;
+ *value = static_cast<T>(0); // Paranoia.
+ return true;
+ }
+ *sys_ptr = 0; // Paranoia.
+ *value = static_cast<T>(0); // Paranoia.
+ return false;
+}
+
+template <typename T> bool ConvertArray(
+ struct NaClApp* nap,
+ uint32_t user_ptr,
+ uint32_t length,
+ size_t element_size,
+ bool optional,
+ T** sys_ptr) {
+ if (user_ptr) {
+ uintptr_t temp = NaClUserToSysAddrArray(nap, user_ptr, length,
+ element_size);
+ if (temp != kNaClBadAddress) {
+ *sys_ptr = reinterpret_cast<T*>(temp);
+ return true;
+ }
+ } else if (optional) {
+ *sys_ptr = 0;
+ return true;
+ }
+ return false;
+}
+
+template <typename T> bool ConvertBytes(
+ struct NaClApp* nap,
+ uint32_t user_ptr,
+ uint32_t length,
+ bool optional,
+ T** sys_ptr) {
+ if (user_ptr) {
+ uintptr_t temp = NaClUserToSysAddrRange(nap, user_ptr, length);
+ if (temp != kNaClBadAddress) {
+ *sys_ptr = reinterpret_cast<T*>(temp);
+ return true;
+ }
+ } else if (optional) {
+ *sys_ptr = 0;
+ return true;
+ }
+ return false;
+}
+
+// TODO(ncbray): size validation and complete copy.
+// TODO(ncbray): ensure non-null / missized structs are covered by a test case.
+template <typename T> bool ConvertStruct(
+ struct NaClApp* nap,
+ uint32_t user_ptr,
+ bool optional,
+ T** sys_ptr) {
+ if (user_ptr) {
+ uintptr_t temp = NaClUserToSysAddrRange(nap, user_ptr, sizeof(T));
+ if (temp != kNaClBadAddress) {
+ *sys_ptr = reinterpret_cast<T*>(temp);
+ return true;
+ }
+ } else if (optional) {
+ *sys_ptr = 0;
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+#endif // MOJO_NACL_MOJO_SYSCALL_INTERNAL_H_
diff --git a/mojo/nacl/monacl_sel_main.cc b/mojo/nacl/monacl_sel_main.cc
new file mode 100644
index 0000000..4fbc575
--- /dev/null
+++ b/mojo/nacl/monacl_sel_main.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/nacl/monacl_sel_main.h"
+
+#include <stdio.h>
+
+#include "mojo/nacl/mojo_syscall.h"
+#include "native_client/src/public/chrome_main.h"
+#include "native_client/src/public/nacl_app.h"
+#include "native_client/src/trusted/desc/nacl_desc_io.h"
+#include "native_client/src/trusted/service_runtime/include/sys/fcntl.h"
+
+namespace mojo {
+
+void LaunchNaCl(const char* nexe_file, const char* irt_file,
+ int app_argc, char* app_argv[]) {
+ NaClChromeMainInit();
+
+ // Open the IRT.
+ struct NaClDesc* irt_desc = (struct NaClDesc*) NaClDescIoDescOpen(
+ irt_file, NACL_ABI_O_RDONLY, 0);
+ if (NULL == irt_desc) {
+ perror(irt_file);
+ exit(1);
+ }
+
+ // Open the main executable.
+ struct NaClDesc* nexe_desc = (struct NaClDesc*) NaClDescIoDescOpen(
+ nexe_file, NACL_ABI_O_RDONLY, 0);
+ if (NULL == nexe_desc) {
+ perror(nexe_file);
+ exit(1);
+ }
+
+ struct NaClChromeMainArgs* args = NaClChromeMainArgsCreate();
+ args->nexe_desc = nexe_desc;
+ args->irt_desc = irt_desc;
+
+ args->argc = app_argc;
+ args->argv = app_argv;
+
+ struct NaClApp* nap = NaClAppCreate();
+ InjectMojo(nap);
+
+ int exit_status = 1;
+ NaClChromeMainStart(nap, args, &exit_status);
+ NaClExit(exit_status);
+}
+
+} // namespace mojo
diff --git a/mojo/nacl/monacl_sel_main.h b/mojo/nacl/monacl_sel_main.h
new file mode 100644
index 0000000..f543d16
--- /dev/null
+++ b/mojo/nacl/monacl_sel_main.h
@@ -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.
+
+#ifndef MOJO_NACL_MONACL_SEL_MAIN_H_
+#define MOJO_NACL_MONACL_SEL_MAIN_H_
+
+namespace mojo {
+
+void LaunchNaCl(const char* nexe_file, const char* irt_file,
+ int app_argc, char* app_argv[]);
+
+} // namespace mojo
+
+#endif // MOJO_NACL_MONACL_SEL_MAIN_H_
diff --git a/mojo/nacl/monacl_shell.cc b/mojo/nacl/monacl_shell.cc
new file mode 100644
index 0000000..7922d9a
--- /dev/null
+++ b/mojo/nacl/monacl_shell.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 <iostream>
+
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/nacl/monacl_sel_main.h"
+
+
+int main(int argc, char* argv[]) {
+ if (argc < 3) {
+ std::cout << "Usage: " << argv[0] << " irt.nexe app.nexe [args for app]" <<
+ std::endl;
+ return 1;
+ }
+
+ const char* irt_file = argv[1];
+ const char* nexe_file = argv[2];
+
+ mojo::embedder::Init(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+
+ // Does not return.
+ mojo::LaunchNaCl(nexe_file, irt_file, argc - 2, argv + 2);
+ return 1;
+}
diff --git a/mojo/public/BUILD.gn b/mojo/public/BUILD.gn
new file mode 100644
index 0000000..1874a6c
--- /dev/null
+++ b/mojo/public/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.
+
+group("public") {
+ # Meta-target, don't link into production code.
+ testonly = true
+ deps = [
+ ":libmojo_sdk",
+ ":sdk",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ ]
+
+ if (is_linux) {
+ deps += [
+ "//mojo/public/python",
+ ]
+ }
+
+ if (is_android) {
+ deps += [
+ "//mojo/public/java:system",
+ "//mojo/public/java:bindings",
+ ]
+ }
+}
+
+group("sdk") {
+ deps = [
+ "//mojo/public/c/system",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/interfaces/application",
+ "//mojo/public/js/bindings",
+ ]
+}
+
+static_library("libmojo_sdk") {
+ complete_static_lib = true
+ deps = [ ":sdk" ]
+}
diff --git a/mojo/public/DEPS b/mojo/public/DEPS
new file mode 100644
index 0000000..0c679b9
--- /dev/null
+++ b/mojo/public/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "-base",
+ "-build",
+ "-mojo",
+ "+mojo/public",
+]
diff --git a/mojo/public/README.md b/mojo/public/README.md
new file mode 100644
index 0000000..a31a8a8
--- /dev/null
+++ b/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/mojo/public/c/DEPS b/mojo/public/c/DEPS
new file mode 100644
index 0000000..5272770
--- /dev/null
+++ b/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/mojo/public/c/PRESUBMIT.py b/mojo/public/c/PRESUBMIT.py
new file mode 100644
index 0000000..4cba433
--- /dev/null
+++ b/mojo/public/c/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Presubmit script for mojo/public/c.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += input_api.canned_checks.CheckChangeHasOnlyOneEol(input_api,
+ output_api)
+ results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
+ return results
diff --git a/mojo/public/c/README.md b/mojo/public/c/README.md
new file mode 100644
index 0000000..8e11545
--- /dev/null
+++ b/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/mojo/public/c/environment/BUILD.gn b/mojo/public/c/environment/BUILD.gn
new file mode 100644
index 0000000..669da7d
--- /dev/null
+++ b/mojo/public/c/environment/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.
+
+source_set("environment") {
+ sources = [
+ "async_waiter.h",
+ "logger.h",
+ ]
+
+ deps = [ "//mojo/public/c/system" ]
+}
diff --git a/mojo/public/c/environment/async_waiter.h b/mojo/public/c/environment/async_waiter.h
new file mode 100644
index 0000000..b5a1f75
--- /dev/null
+++ b/mojo/public/c/environment/async_waiter.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium 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);
+
+// Functions for asynchronously waiting (and cancelling asynchronous waits) on a
+// handle.
+//
+// Thread-safety:
+// - |CancelWait(wait_id)| may only be called on the same thread as the
+// |AsyncWait()| that provided |wait_id| was called on.
+// - A given |MojoAsyncWaiter|'s functions may only be called on the thread(s)
+// that it is defined to be valid on (typically including the thread on
+// which the |MojoAsyncWaiter| was provided). E.g., a library may require
+// initialization with a single |MojoAsyncWaiter| and stipulate that it only
+// be used on threads on which that |MojoAsyncWaiter| is valid.
+// - If a |MojoAsyncWaiter| is valid on multiple threads, then its functions
+// must be thread-safe (subject to the first restriction above).
+struct MojoAsyncWaiter {
+ // Arranges for |callback| to be called on the current thread at some future
+ // when |handle| satisfies |signals| or it is known that it will never satisfy
+ // |signals| (with the same behavior as |MojoWait()|).
+ //
+ // |callback| will not be called in the nested context of |AsyncWait()|, but
+ // only, e.g., from some run loop. |callback| is provided with the |closure|
+ // argument as well as the result of the wait. For each call to |AsyncWait()|,
+ // |callback| will be called at most once.
+ //
+ // |handle| must not be closed or transferred (via |MojoWriteMessage()|; this
+ // is equivalent to closing the handle) until either the callback has been
+ // executed or the async wait has been cancelled using the returned (nonzero)
+ // |MojoAsyncWaitID| (see |CancelWait()|). Otherwise, an invalid (or, worse,
+ // re-used) handle may be waited on by the implementation of this
+ // |MojoAsyncWaiter|.
+ //
+ // Note that once the callback has been called, the returned |MojoAsyncWaitID|
+ // becomes invalid.
+ MojoAsyncWaitID (*AsyncWait)(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ MojoAsyncWaitCallback callback,
+ void* closure);
+
+ // Cancels an outstanding async wait (specified by |wait_id|) initiated by
+ // |AsyncWait()|. This may only be called from the same thread on which the
+ // corresponding |AsyncWait()| was called, and may only be called if the
+ // callback to |AsyncWait()| has not been called.
+ //
+ // Once this has been called, the callback provided to |AsyncWait()| will not
+ // be called. Moreover, it is then immediately safe to close or transfer the
+ // handle provided to |AsyncWait()|. (I.e., the implementation of this
+ // |MojoAsyncWaiter| will no longer wait on, or do anything else with, the
+ // handle.)
+ void (*CancelWait)(MojoAsyncWaitID wait_id);
+};
+
+#endif // MOJO_PUBLIC_C_ENVIRONMENT_ASYNC_WAITER_H_
diff --git a/mojo/public/c/environment/logger.h b/mojo/public/c/environment/logger.h
new file mode 100644
index 0000000..c492a66
--- /dev/null
+++ b/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/mojo/public/c/gles2/BUILD.gn b/mojo/public/c/gles2/BUILD.gn
new file mode 100644
index 0000000..df0007b
--- /dev/null
+++ b/mojo/public/c/gles2/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config("gles2_config") {
+ defines = [ "GLES2_USE_MOJO" ]
+}
+
+source_set("gles2") {
+ sources = [
+ "gles2.h",
+ "gles2_export.h",
+ ]
+
+ public_configs = [ ":gles2_config" ]
+
+ public_deps = [
+ "//mojo/public/c/environment",
+ "//mojo/public/c/system",
+ ]
+}
diff --git a/mojo/public/c/gles2/DEPS b/mojo/public/c/gles2/DEPS
new file mode 100644
index 0000000..3887457
--- /dev/null
+++ b/mojo/public/c/gles2/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/public/c/environment",
+]
diff --git a/mojo/public/c/gles2/chromium_sync_point.h b/mojo/public/c/gles2/chromium_sync_point.h
new file mode 100644
index 0000000..6369712
--- /dev/null
+++ b/mojo/public/c/gles2/chromium_sync_point.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_GLES2_CHROMIUM_SYNC_POINT_H_
+#define MOJO_PUBLIC_C_GLES2_CHROMIUM_SYNC_POINT_H_
+
+// Note: This header should be compilable as C.
+
+#include <stdint.h>
+#include <GLES2/gl2.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
+
+#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_chromium_sync_point_autogen.h"
+#undef VISIT_GL_CALL
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_GLES2_CHROMIUM_SYNC_POINT_H_
diff --git a/mojo/public/c/gles2/chromium_texture_mailbox.h b/mojo/public/c/gles2/chromium_texture_mailbox.h
new file mode 100644
index 0000000..177ebbb
--- /dev/null
+++ b/mojo/public/c/gles2/chromium_texture_mailbox.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_GLES2_CHROMIUM_TEXTURE_MAILBOX_H_
+#define MOJO_PUBLIC_C_GLES2_CHROMIUM_TEXTURE_MAILBOX_H_
+
+// Note: This header should be compilable as C.
+
+#include <stdint.h>
+#include <GLES2/gl2.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
+
+#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_chromium_texture_mailbox_autogen.h"
+#undef VISIT_GL_CALL
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_GLES2_CHROMIUM_TEXTURE_MAILBOX_H_
diff --git a/mojo/public/c/gles2/gles2.h b/mojo/public/c/gles2/gles2.h
new file mode 100644
index 0000000..36c6f7f
--- /dev/null
+++ b/mojo/public/c/gles2/gles2.h
@@ -0,0 +1,45 @@
+// 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 MojoGLES2Context
+ MojoGLES2CreateContext(MojoHandle handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure,
+ const MojoAsyncWaiter* async_waiter);
+MOJO_GLES2_EXPORT void MojoGLES2DestroyContext(MojoGLES2Context context);
+MOJO_GLES2_EXPORT void MojoGLES2MakeCurrent(MojoGLES2Context context);
+MOJO_GLES2_EXPORT void MojoGLES2SwapBuffers(void);
+
+// 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/mojo/public/c/gles2/gles2_call_visitor_autogen.h b/mojo/public/c/gles2/gles2_call_visitor_autogen.h
new file mode 100644
index 0000000..72494c5
--- /dev/null
+++ b/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/mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h b/mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h
new file mode 100644
index 0000000..3c3c4b9
--- /dev/null
+++ b/mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h
@@ -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.
+
+// 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(InsertSyncPointCHROMIUM, GLuint, (), ())
+VISIT_GL_CALL(WaitSyncPointCHROMIUM, void, (GLuint sync_point), (sync_point))
diff --git a/mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h b/mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h
new file mode 100644
index 0000000..184c2f2
--- /dev/null
+++ b/mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.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.
+
+// 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(GenMailboxCHROMIUM, void, (GLbyte * mailbox), (mailbox))
+VISIT_GL_CALL(ProduceTextureCHROMIUM,
+ void,
+ (GLenum target, const GLbyte* mailbox),
+ (target, mailbox))
+VISIT_GL_CALL(ProduceTextureDirectCHROMIUM,
+ void,
+ (GLuint texture, GLenum target, const GLbyte* mailbox),
+ (texture, target, mailbox))
+VISIT_GL_CALL(ConsumeTextureCHROMIUM,
+ void,
+ (GLenum target, const GLbyte* mailbox),
+ (target, mailbox))
+VISIT_GL_CALL(CreateAndConsumeTextureCHROMIUM,
+ GLuint,
+ (GLenum target, const GLbyte* mailbox),
+ (target, mailbox))
diff --git a/mojo/public/c/gles2/gles2_export.h b/mojo/public/c/gles2/gles2_export.h
new file mode 100644
index 0000000..60667b1
--- /dev/null
+++ b/mojo/public/c/gles2/gles2_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_GLES2_GLES2_EXPORT_H_
+#define MOJO_PUBLIC_C_GLES2_GLES2_EXPORT_H_
+
+#if defined(COMPONENT_BUILD) && defined(MOJO_USE_GLES2_IMPL)
+#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)
+
+#else // !defined(COMPONENT_BUILD) || !defined(MOJO_USE_GLES2_IMPL)
+
+#define MOJO_GLES2_EXPORT
+
+#endif // defined(COMPONENT_BUILD) && defined(MOJO_USE_GLES2_IMPL)
+
+#endif // MOJO_PUBLIC_C_GLES2_GLES2_EXPORT_H_
diff --git a/mojo/public/c/gles2/gles2_types.h b/mojo/public/c/gles2/gles2_types.h
new file mode 100644
index 0000000..3ecf4db
--- /dev/null
+++ b/mojo/public/c/gles2/gles2_types.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_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);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_GLES2_GLES2_TYPES_H_
diff --git a/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn
new file mode 100644
index 0000000..b47b3da
--- /dev/null
+++ b/mojo/public/c/system/BUILD.gn
@@ -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.
+
+# Depend on this target to use the types etc defined in the system without
+# linking against a specific implementation of the system. To link against a
+# particular implementation, use the :for_component or
+# :for_shared_library targets, depending on the type of target you are.
+source_set("system") {
+ sources = [
+ "buffer.h",
+ "core.h",
+ "data_pipe.h",
+ "functions.h",
+ "macros.h",
+ "message_pipe.h",
+ "system_export.h",
+ "types.h",
+ ]
+}
+
+# In an is_component_build build, everything can link against //mojo/edk/system
+# because it is built as a shared library. However, in a static build,
+# //mojo/edk/system is linked into an executable (e.g., mojo_shell), and must be
+# injected into other shared libraries (i.e., Mojo Apps) that need the mojo
+# system API.
+#
+# For component targets, add //mojo/public/c/system:for_component to your deps
+# section.
+#
+# For shared_library targets (e.g., a Mojo App), add
+# //mojo/public/c/system:for_shared_library to your deps
+
+group("for_shared_library") {
+ public_deps = [ ":system" ]
+ if (is_component_build) {
+ deps = [ "//mojo/edk/system" ]
+ } else {
+ deps = [ "//mojo/public/platform/native:system_thunks" ]
+ }
+}
+
+group("for_component") {
+ public_deps = [ ":system" ]
+ if (is_component_build) {
+ deps = [ "//mojo/edk/system" ]
+ }
+}
diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h
new file mode 100644
index 0000000..19e3c52
--- /dev/null
+++ b/mojo/public/c/system/buffer.h
@@ -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.
+
+// 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.,
+// |*options| is 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.,
+// |buffer_handle| is not a valid buffer handle or |*options| is 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.,
+// |buffer_handle| is not a valid buffer handle or the range specified by
+// |offset| and |num_bytes| is not valid).
+// |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., is not the
+// result of |MojoMapBuffer()| or 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/mojo/public/c/system/core.h b/mojo/public/c/system/core.h
new file mode 100644
index 0000000..0e78786
--- /dev/null
+++ b/mojo/public/c/system/core.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// 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/main.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/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h
new file mode 100644
index 0000000..c8087ea
--- /dev/null
+++ b/mojo/public/c/system/data_pipe.h
@@ -0,0 +1,364 @@
+// Copyright 2014 The Chromium 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.,
+// |*options| is 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.,
+// |data_pipe_producer_dispatcher| is not a handle to a data pipe
+// producer 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.,
+// |data_pipe_producer_handle| is not a handle to a data pipe producer 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 some argument was invalid (e.g.,
+// |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.,
+// |data_pipe_consumer_handle| is not a handle to a data pipe consumer,
+// |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 some argument was invalid (e.g.,
+// |data_pipe_consumer_handle| is not a handle to a data pipe consumer or
+// |num_bytes_written| is 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/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h
new file mode 100644
index 0000000..6045f2f
--- /dev/null
+++ b/mojo/public/c/system/functions.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.
+
+// 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,
+// it becomes known that no signal indicated by |signals| will ever be satisfied
+// (see the description of the |MOJO_RESULT_CANCELLED| and
+// |MOJO_RESULT_FAILED_PRECONDITION| return values below), or until |deadline|
+// has passed.
+//
+// If |deadline| is |MOJO_DEADLINE_INDEFINITE|, this will wait "forever" (until
+// one of the other wait termination conditions is satisfied). If |deadline| is
+// 0, this will return |MOJO_RESULT_DEADLINE_EXCEEDED| only if one of the other
+// termination conditions (e.g., a signal is satisfied, or all signals are
+// unsatisfiable) is not already satisfied.
+//
+// Returns:
+// |MOJO_RESULT_OK| if some signal in |signals| was satisfied (or is already
+// satisfied).
+// |MOJO_RESULT_CANCELLED| if |handle| was closed (necessarily from another
+// thread) during the wait.
+// |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 becomes known that none of the
+// signals in |signals| can ever be satisfied (e.g., when waiting on one
+// end of a message pipe and the other end is closed).
+//
+// 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]| until (at least) one
+// satisfies a signal indicated in its respective |signals[0]|, ...,
+// |signals[num_handles-1]|, it becomes known that no signal in some
+// |signals[i]| will ever be satisfied, or until |deadline| has passed.
+//
+// This means that |MojoWaitMany()| behaves as if |MojoWait()| were called on
+// each handle/signals pair simultaneously, completing when the first
+// |MojoWait()| would complete.
+//
+// See |MojoWait()| for more details about |deadline|.
+//
+// Returns:
+// The index |i| (from 0 to |num_handles-1|) if |handle[i]| satisfies a signal
+// from |signals[i]|.
+// |MOJO_RESULT_CANCELLED| if some |handle[i]| was closed (necessarily from
+// another thread) during the wait.
+// |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/mojo/public/c/system/macros.h b/mojo/public/c/system/macros.h
new file mode 100644
index 0000000..564ee60
--- /dev/null
+++ b/mojo/public/c/system/macros.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_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
+
+#ifdef __cplusplus
+// Used to explicitly mark the return value of a function as unused. If you are
+// really sure you don't want to do anything with the return value of a function
+// that has been marked WARN_UNUSED_RESULT, wrap it with this. Example:
+//
+// scoped_ptr<MyType> my_var = ...;
+// if (TakeOwnership(my_var.get()) == SUCCESS)
+// mojo_ignore_result(my_var.release());
+//
+template <typename T>
+inline void mojo_ignore_result(const T&) {
+}
+#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/mojo/public/c/system/main.h b/mojo/public/c/system/main.h
new file mode 100644
index 0000000..31e2012
--- /dev/null
+++ b/mojo/public/c/system/main.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_PUBLIC_C_SYSTEM_MAIN_H_
+#define MOJO_PUBLIC_C_SYSTEM_MAIN_H_
+
+#include "mojo/public/c/system/types.h"
+
+// Implement MojoMain directly as the entry point for an application.
+//
+// MojoResult MojoMain(MojoHandle service_provider_handle) {
+// ...
+// }
+//
+// TODO(davemoore): Establish this as part of our SDK for third party mojo
+// application writers.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(WIN32)
+__declspec(dllexport) MojoResult
+ __cdecl MojoMain(MojoHandle service_provider_handle);
+#else // !defined(WIN32)
+__attribute__((visibility("default"))) MojoResult
+ MojoMain(MojoHandle service_provider_handle);
+#endif // defined(WIN32)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_MAIN_H_
diff --git a/mojo/public/c/system/message_pipe.h b/mojo/public/c/system/message_pipe.h
new file mode 100644
index 0000000..b08ba75
--- /dev/null
+++ b/mojo/public/c/system/message_pipe.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.
+
+// 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 some argument was invalid (e.g.,
+// |*options| is invalid).
+// |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_UNIMPLEMENTED| if an unsupported flag was set in |*options|.
+// |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/mojo/public/c/system/system_export.h b/mojo/public/c/system/system_export.h
new file mode 100644
index 0000000..bc3b459
--- /dev/null
+++ b/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/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn
new file mode 100644
index 0000000..e9aa06f
--- /dev/null
+++ b/mojo/public/c/system/tests/BUILD.gn
@@ -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.
+
+source_set("tests") {
+ testonly = true
+ visibility = [ "//mojo/public/cpp/system/tests:mojo_public_system_unittests" ]
+
+ deps = [
+ "//mojo/public/c/environment",
+ "//mojo/public/c/system",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "core_unittest.cc",
+ "core_unittest_pure_c.c",
+ "macros_unittest.cc",
+ ]
+}
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_system_perftests
+executable("perftests") {
+ testonly = true
+ output_name = "mojo_public_system_perftests"
+
+ sources = [
+ "core_perftest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/common/test:run_all_perftests",
+ "//mojo/public/c/environment",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/cpp/utility",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/c/system/tests/core_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc
new file mode 100644
index 0000000..b9a8114
--- /dev/null
+++ b/mojo/public/c/system/tests/core_perftest.cc
@@ -0,0 +1,339 @@
+// 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 tests the performance of the C API.
+
+#include "mojo/public/c/system/core.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// TODO(vtl): (here and below) crbug.com/342893
+#if !defined(WIN32)
+#include <time.h>
+#include "mojo/public/cpp/utility/thread.h"
+#endif // !defined(WIN32)
+
+namespace {
+
+#if !defined(WIN32)
+class MessagePipeWriterThread : public mojo::Thread {
+ public:
+ MessagePipeWriterThread(MojoHandle handle, uint32_t num_bytes)
+ : handle_(handle), num_bytes_(num_bytes), num_writes_(0) {}
+ virtual ~MessagePipeWriterThread() {}
+
+ virtual void Run() override {
+ char buffer[10000];
+ assert(num_bytes_ <= sizeof(buffer));
+
+ // TODO(vtl): Should I throttle somehow?
+ for (;;) {
+ MojoResult result = MojoWriteMessage(
+ handle_, buffer, num_bytes_, NULL, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ num_writes_++;
+ continue;
+ }
+
+ // We failed to write.
+ // Either |handle_| or its peer was closed.
+ assert(result == MOJO_RESULT_INVALID_ARGUMENT ||
+ result == MOJO_RESULT_FAILED_PRECONDITION);
+ break;
+ }
+ }
+
+ // Use only after joining the thread.
+ int64_t num_writes() const { return num_writes_; }
+
+ private:
+ const MojoHandle handle_;
+ const uint32_t num_bytes_;
+ int64_t num_writes_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MessagePipeWriterThread);
+};
+
+class MessagePipeReaderThread : public mojo::Thread {
+ public:
+ explicit MessagePipeReaderThread(MojoHandle handle)
+ : handle_(handle), num_reads_(0) {}
+ virtual ~MessagePipeReaderThread() {}
+
+ virtual void Run() override {
+ char buffer[10000];
+
+ for (;;) {
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadMessage(
+ handle_, buffer, &num_bytes, NULL, NULL, MOJO_READ_MESSAGE_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ num_reads_++;
+ continue;
+ }
+
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ result = MojoWait(
+ handle_, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE);
+ if (result == MOJO_RESULT_OK) {
+ // Go to the top of the loop to read again.
+ continue;
+ }
+ }
+
+ // We failed to read and possibly failed to wait.
+ // Either |handle_| or its peer was closed.
+ assert(result == MOJO_RESULT_INVALID_ARGUMENT ||
+ result == MOJO_RESULT_FAILED_PRECONDITION);
+ break;
+ }
+ }
+
+ // Use only after joining the thread.
+ int64_t num_reads() const { return num_reads_; }
+
+ private:
+ const MojoHandle handle_;
+ int64_t num_reads_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MessagePipeReaderThread);
+};
+#endif // !defined(WIN32)
+
+class CorePerftest : public testing::Test {
+ public:
+ CorePerftest() : buffer_(NULL), num_bytes_(0) {}
+ virtual ~CorePerftest() {}
+
+ static void NoOp(void* /*closure*/) {}
+
+ static void MessagePipe_CreateAndClose(void* closure) {
+ CorePerftest* self = static_cast<CorePerftest*>(closure);
+ MojoResult result MOJO_ALLOW_UNUSED;
+ result = MojoCreateMessagePipe(NULL, &self->h0_, &self->h1_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(self->h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(self->h1_);
+ assert(result == MOJO_RESULT_OK);
+ }
+
+ static void MessagePipe_WriteAndRead(void* closure) {
+ CorePerftest* self = static_cast<CorePerftest*>(closure);
+ MojoResult result MOJO_ALLOW_UNUSED;
+ result = MojoWriteMessage(self->h0_,
+ self->buffer_,
+ self->num_bytes_,
+ NULL,
+ 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ assert(result == MOJO_RESULT_OK);
+ uint32_t read_bytes = self->num_bytes_;
+ result = MojoReadMessage(self->h1_,
+ self->buffer_,
+ &read_bytes,
+ NULL,
+ NULL,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ assert(result == MOJO_RESULT_OK);
+ }
+
+ static void MessagePipe_EmptyRead(void* closure) {
+ CorePerftest* self = static_cast<CorePerftest*>(closure);
+ MojoResult result MOJO_ALLOW_UNUSED;
+ result = MojoReadMessage(
+ self->h0_, NULL, NULL, NULL, NULL, MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
+ assert(result == MOJO_RESULT_SHOULD_WAIT);
+ }
+
+ protected:
+#if !defined(WIN32)
+ void DoMessagePipeThreadedTest(unsigned num_writers,
+ unsigned num_readers,
+ uint32_t num_bytes) {
+ static const int64_t kPerftestTimeMicroseconds = 3 * 1000000;
+
+ assert(num_writers > 0);
+ assert(num_readers > 0);
+
+ MojoResult result MOJO_ALLOW_UNUSED;
+ result = MojoCreateMessagePipe(NULL, &h0_, &h1_);
+ assert(result == MOJO_RESULT_OK);
+
+ std::vector<MessagePipeWriterThread*> writers;
+ for (unsigned i = 0; i < num_writers; i++)
+ writers.push_back(new MessagePipeWriterThread(h0_, num_bytes));
+
+ std::vector<MessagePipeReaderThread*> readers;
+ for (unsigned i = 0; i < num_readers; i++)
+ readers.push_back(new MessagePipeReaderThread(h1_));
+
+ // Start time here, just before we fire off the threads.
+ const MojoTimeTicks start_time = MojoGetTimeTicksNow();
+
+ // Interleave the starts.
+ for (unsigned i = 0; i < num_writers || i < num_readers; i++) {
+ if (i < num_writers)
+ writers[i]->Start();
+ if (i < num_readers)
+ readers[i]->Start();
+ }
+
+ Sleep(kPerftestTimeMicroseconds);
+
+ // Close both handles to make writers and readers stop immediately.
+ result = MojoClose(h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(h1_);
+ assert(result == MOJO_RESULT_OK);
+
+ // Join everything.
+ for (unsigned i = 0; i < num_writers; i++)
+ writers[i]->Join();
+ for (unsigned i = 0; i < num_readers; i++)
+ readers[i]->Join();
+
+ // Stop time here.
+ MojoTimeTicks end_time = MojoGetTimeTicksNow();
+
+ // Add up write and read counts, and destroy the threads.
+ int64_t num_writes = 0;
+ for (unsigned i = 0; i < num_writers; i++) {
+ num_writes += writers[i]->num_writes();
+ delete writers[i];
+ }
+ writers.clear();
+ int64_t num_reads = 0;
+ for (unsigned i = 0; i < num_readers; i++) {
+ num_reads += readers[i]->num_reads();
+ delete readers[i];
+ }
+ readers.clear();
+
+ char test_name[200];
+ sprintf(test_name,
+ "MessagePipe_Threaded_Writes_%uw_%ur_%ubytes",
+ num_writers,
+ num_readers,
+ static_cast<unsigned>(num_bytes));
+ mojo::test::LogPerfResult(
+ test_name,
+ 1000000.0 * static_cast<double>(num_writes) / (end_time - start_time),
+ "writes/second");
+ sprintf(test_name,
+ "MessagePipe_Threaded_Reads_%uw_%ur_%ubytes",
+ num_writers,
+ num_readers,
+ static_cast<unsigned>(num_bytes));
+ mojo::test::LogPerfResult(
+ test_name,
+ 1000000.0 * static_cast<double>(num_reads) / (end_time - start_time),
+ "reads/second");
+ }
+#endif // !defined(WIN32)
+
+ MojoHandle h0_;
+ MojoHandle h1_;
+
+ void* buffer_;
+ uint32_t num_bytes_;
+
+ private:
+#if !defined(WIN32)
+ void Sleep(int64_t microseconds) {
+ struct timespec req = {
+ static_cast<time_t>(microseconds / 1000000), // Seconds.
+ static_cast<long>(microseconds % 1000000) * 1000L // Nanoseconds.
+ };
+ int rv MOJO_ALLOW_UNUSED;
+ rv = nanosleep(&req, NULL);
+ assert(rv == 0);
+ }
+#endif // !defined(WIN32)
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(CorePerftest);
+};
+
+// A no-op test so we can compare performance.
+TEST_F(CorePerftest, NoOp) {
+ mojo::test::IterateAndReportPerf("NoOp", &CorePerftest::NoOp, this);
+}
+
+TEST_F(CorePerftest, MessagePipe_CreateAndClose) {
+ mojo::test::IterateAndReportPerf("MessagePipe_CreateAndClose",
+ &CorePerftest::MessagePipe_CreateAndClose,
+ this);
+}
+
+TEST_F(CorePerftest, MessagePipe_WriteAndRead) {
+ MojoResult result MOJO_ALLOW_UNUSED;
+ result = MojoCreateMessagePipe(NULL, &h0_, &h1_);
+ assert(result == MOJO_RESULT_OK);
+ char buffer[10000] = {0};
+ buffer_ = buffer;
+ num_bytes_ = 10u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead_10bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ num_bytes_ = 100u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead_100bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ num_bytes_ = 1000u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead_1000bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ num_bytes_ = 10000u;
+ mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead_10000bytes",
+ &CorePerftest::MessagePipe_WriteAndRead,
+ this);
+ result = MojoClose(h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(h1_);
+ assert(result == MOJO_RESULT_OK);
+}
+
+TEST_F(CorePerftest, MessagePipe_EmptyRead) {
+ MojoResult result MOJO_ALLOW_UNUSED;
+ result = MojoCreateMessagePipe(NULL, &h0_, &h1_);
+ assert(result == MOJO_RESULT_OK);
+ mojo::test::IterateAndReportPerf(
+ "MessagePipe_EmptyRead", &CorePerftest::MessagePipe_EmptyRead, this);
+ result = MojoClose(h0_);
+ assert(result == MOJO_RESULT_OK);
+ result = MojoClose(h1_);
+ assert(result == MOJO_RESULT_OK);
+}
+
+#if !defined(WIN32)
+TEST_F(CorePerftest, MessagePipe_Threaded) {
+ DoMessagePipeThreadedTest(1u, 1u, 100u);
+ DoMessagePipeThreadedTest(2u, 2u, 100u);
+ DoMessagePipeThreadedTest(3u, 3u, 100u);
+ DoMessagePipeThreadedTest(10u, 10u, 100u);
+ DoMessagePipeThreadedTest(10u, 1u, 100u);
+ DoMessagePipeThreadedTest(1u, 10u, 100u);
+
+ // For comparison of overhead:
+ DoMessagePipeThreadedTest(1u, 1u, 10u);
+ // 100 was done above.
+ DoMessagePipeThreadedTest(1u, 1u, 1000u);
+ DoMessagePipeThreadedTest(1u, 1u, 10000u);
+
+ DoMessagePipeThreadedTest(3u, 3u, 10u);
+ // 100 was done above.
+ DoMessagePipeThreadedTest(3u, 3u, 1000u);
+ DoMessagePipeThreadedTest(3u, 3u, 10000u);
+}
+#endif // !defined(WIN32)
+
+} // namespace
diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc
new file mode 100644
index 0000000..d071f48
--- /dev/null
+++ b/mojo/public/c/system/tests/core_unittest.cc
@@ -0,0 +1,298 @@
+// 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 file tests the C API.
+
+#include "mojo/public/c/system/core.h"
+
+#include <string.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+TEST(CoreTest, GetTimeTicksNow) {
+ const MojoTimeTicks start = MojoGetTimeTicksNow();
+ EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
+ << "MojoGetTimeTicksNow should return nonzero value";
+}
+
+// The only handle that's guaranteed to be invalid is |MOJO_HANDLE_INVALID|.
+// Tests that everything that takes a handle properly recognizes it.
+TEST(CoreTest, InvalidHandle) {
+ MojoHandle h0, h1;
+ MojoHandleSignals sig;
+ char buffer[10] = {0};
+ uint32_t buffer_size;
+ void* write_pointer;
+ const void* read_pointer;
+
+ // Close:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(MOJO_HANDLE_INVALID));
+
+ // Wait:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE, 1000000));
+ h0 = MOJO_HANDLE_INVALID;
+ sig = ~MOJO_HANDLE_SIGNAL_NONE;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWaitMany(&h0, &sig, 1, MOJO_DEADLINE_INDEFINITE));
+
+ // Message pipe:
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWriteMessage(h0, buffer, 3, NULL, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoReadMessage(
+ h0, buffer, &buffer_size, NULL, NULL, MOJO_READ_MESSAGE_FLAG_NONE));
+
+ // Data pipe:
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWriteData(h0, buffer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE));
+ write_pointer = NULL;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoBeginWriteData(
+ h0, &write_pointer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndWriteData(h0, 1));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoReadData(h0, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+ read_pointer = NULL;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoBeginReadData(
+ h0, &read_pointer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndReadData(h0, 1));
+
+ // Shared buffer:
+ h1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoDuplicateBufferHandle(h0, NULL, &h1));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoMapBuffer(h0, 0, 1, &write_pointer, MOJO_MAP_BUFFER_FLAG_NONE));
+}
+
+TEST(CoreTest, BasicMessagePipe) {
+ MojoHandle h0, h1;
+ MojoHandleSignals sig;
+ char buffer[10] = {0};
+ uint32_t buffer_size;
+
+ h0 = MOJO_HANDLE_INVALID;
+ h1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &h0, &h1));
+ EXPECT_NE(h0, MOJO_HANDLE_INVALID);
+ EXPECT_NE(h1, MOJO_HANDLE_INVALID);
+
+ // Shouldn't be readable.
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, 0));
+
+ // Should be writable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(h0, MOJO_HANDLE_SIGNAL_WRITABLE, 0));
+
+ // Try to read.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(
+ MOJO_RESULT_SHOULD_WAIT,
+ MojoReadMessage(
+ h0, buffer, &buffer_size, NULL, NULL, MOJO_READ_MESSAGE_FLAG_NONE));
+
+ // Write to |h1|.
+ static const char kHello[] = "hello";
+ buffer_size = static_cast<uint32_t>(sizeof(kHello));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(
+ h1, kHello, buffer_size, NULL, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // |h0| should be readable.
+ sig = MOJO_HANDLE_SIGNAL_READABLE;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWaitMany(&h0, &sig, 1, MOJO_DEADLINE_INDEFINITE));
+
+ // Read from |h0|.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoReadMessage(
+ h0, buffer, &buffer_size, NULL, NULL, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(static_cast<uint32_t>(sizeof(kHello)), buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+
+ // |h0| should no longer be readable.
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE, 10));
+
+ // Close |h0|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0));
+
+ // |h1| should no longer be readable or writable.
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(
+ h1, MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, 1000));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1));
+}
+
+// TODO(ncbray): enable these tests once NaCl supports the corresponding APIs.
+#ifdef __native_client__
+#define MAYBE_BasicDataPipe DISABLED_BasicDataPipe
+#define MAYBE_BasicSharedBuffer DISABLED_BasicSharedBuffer
+#else
+#define MAYBE_BasicDataPipe BasicDataPipe
+#define MAYBE_BasicSharedBuffer BasicSharedBuffer
+#endif
+
+TEST(CoreTest, MAYBE_BasicDataPipe) {
+ MojoHandle hp, hc;
+ MojoHandleSignals sig;
+ char buffer[20] = {0};
+ uint32_t buffer_size;
+ void* write_pointer;
+ const void* read_pointer;
+
+ hp = MOJO_HANDLE_INVALID;
+ hc = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(NULL, &hp, &hc));
+ EXPECT_NE(hp, MOJO_HANDLE_INVALID);
+ EXPECT_NE(hc, MOJO_HANDLE_INVALID);
+
+ // The consumer |hc| shouldn't be readable.
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 0));
+
+ // The producer |hp| should be writable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(hp, MOJO_HANDLE_SIGNAL_WRITABLE, 0));
+
+ // Try to read from |hc|.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+
+ // Try to begin a two-phase read from |hc|.
+ read_pointer = NULL;
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoBeginReadData(
+ hc, &read_pointer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+
+ // Write to |hp|.
+ static const char kHello[] = "hello ";
+ // Don't include terminating null.
+ buffer_size = static_cast<uint32_t>(strlen(kHello));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteData(hp, kHello, &buffer_size, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // |hc| should be(come) readable.
+ sig = MOJO_HANDLE_SIGNAL_READABLE;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWaitMany(&hc, &sig, 1, MOJO_DEADLINE_INDEFINITE));
+
+ // Do a two-phase write to |hp|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoBeginWriteData(
+ hp, &write_pointer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE));
+ static const char kWorld[] = "world";
+ ASSERT_GE(buffer_size, sizeof(kWorld));
+ // Include the terminating null.
+ memcpy(write_pointer, kWorld, sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoEndWriteData(hp, static_cast<uint32_t>(sizeof(kWorld))));
+
+ // Read one character from |hc|.
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+
+ // Close |hp|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hp));
+
+ // |hc| should still be readable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 0));
+
+ // Do a two-phase read from |hc|.
+ read_pointer = NULL;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoBeginReadData(
+ hc, &read_pointer, &buffer_size, MOJO_READ_DATA_FLAG_NONE));
+ ASSERT_LE(buffer_size, sizeof(buffer) - 1);
+ memcpy(&buffer[1], read_pointer, buffer_size);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoEndReadData(hc, buffer_size));
+ EXPECT_STREQ("hello world", buffer);
+
+ // |hc| should no longer be readable.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(hc, MOJO_HANDLE_SIGNAL_READABLE, 1000));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hc));
+
+ // TODO(vtl): Test the other way around -- closing the consumer should make
+ // the producer never-writable?
+}
+
+TEST(CoreTest, MAYBE_BasicSharedBuffer) {
+ MojoHandle h0, h1;
+ void* pointer;
+
+ // Create a shared buffer (|h0|).
+ h0 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(NULL, 100, &h0));
+ EXPECT_NE(h0, MOJO_HANDLE_INVALID);
+
+ // Map everything.
+ pointer = NULL;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoMapBuffer(h0, 0, 100, &pointer, MOJO_MAP_BUFFER_FLAG_NONE));
+ ASSERT_TRUE(pointer);
+ static_cast<char*>(pointer)[50] = 'x';
+
+ // Duplicate |h0| to |h1|.
+ h1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(h0, NULL, &h1));
+ EXPECT_NE(h1, MOJO_HANDLE_INVALID);
+
+ // Close |h0|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0));
+
+ // The mapping should still be good.
+ static_cast<char*>(pointer)[51] = 'y';
+
+ // Unmap it.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer));
+
+ // Map half of |h1|.
+ pointer = NULL;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoMapBuffer(h1, 50, 50, &pointer, MOJO_MAP_BUFFER_FLAG_NONE));
+ ASSERT_TRUE(pointer);
+
+ // It should have what we wrote.
+ EXPECT_EQ('x', static_cast<char*>(pointer)[0]);
+ EXPECT_EQ('y', static_cast<char*>(pointer)[1]);
+
+ // Unmap it.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1));
+}
+
+// Defined in core_unittest_pure_c.c.
+extern "C" const char* MinimalCTest(void);
+
+// This checks that things actually work in C (not C++).
+TEST(CoreTest, MinimalCTest) {
+ const char* failure = MinimalCTest();
+ EXPECT_TRUE(failure == NULL) << failure;
+}
+
+// TODO(vtl): Add multi-threaded tests.
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/c/system/tests/core_unittest_pure_c.c b/mojo/public/c/system/tests/core_unittest_pure_c.c
new file mode 100644
index 0000000..33de688
--- /dev/null
+++ b/mojo/public/c/system/tests/core_unittest_pure_c.c
@@ -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.
+
+#ifdef __cplusplus
+#error "This file should be compiled as C, not C++."
+#endif
+
+#include <stddef.h>
+#include <string.h>
+
+// Include all the header files that are meant to be compilable as C. Start with
+// core.h, since it's the most important one.
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/c/system/macros.h"
+
+// The joys of the C preprocessor....
+#define STRINGIFY(x) #x
+#define STRINGIFY2(x) STRINGIFY(x)
+#define FAILURE(message) \
+ __FILE__ "(" STRINGIFY2(__LINE__) "): Failure: " message
+
+// Poor man's gtest.
+#define EXPECT_EQ(a, b) \
+ do { \
+ if ((a) != (b)) \
+ return FAILURE(STRINGIFY(a) " != " STRINGIFY(b) " (expected ==)"); \
+ } while (0)
+#define EXPECT_NE(a, b) \
+ do { \
+ if ((a) == (b)) \
+ return FAILURE(STRINGIFY(a) " == " STRINGIFY(b) " (expected !=)"); \
+ } while (0)
+
+// This function exists mainly to be compiled and linked. We do some cursory
+// checks and call it from a unit test, to make sure that link problems aren't
+// missed due to deadstripping. Returns null on success and a string on failure
+// (describing the failure).
+const char* MinimalCTest(void) {
+ // MSVS before 2013 *really* only supports C90: All variables must be declared
+ // at the top. (MSVS 2013 is more reasonable.)
+ MojoTimeTicks ticks;
+ MojoHandle handle0, handle1;
+ MojoHandleSignals signals;
+ const char kHello[] = "hello";
+ char buffer[200] = {0};
+ uint32_t num_bytes;
+
+ ticks = MojoGetTimeTicksNow();
+ EXPECT_NE(ticks, 0);
+
+ handle0 = MOJO_HANDLE_INVALID;
+ EXPECT_NE(MOJO_RESULT_OK, MojoClose(handle0));
+
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWait(handle0, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE));
+
+ handle1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1));
+
+ signals = MOJO_HANDLE_SIGNAL_READABLE;
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWaitMany(&handle0, &signals, 1, 1));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(handle0,
+ kHello,
+ (uint32_t)sizeof(kHello),
+ NULL,
+ 0u,
+ MOJO_WRITE_DATA_FLAG_NONE));
+
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWait(handle1, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE));
+
+ num_bytes = (uint32_t)sizeof(buffer);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(handle1,
+ buffer,
+ &num_bytes,
+ NULL,
+ NULL,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ((uint32_t)sizeof(kHello), num_bytes);
+ EXPECT_EQ(0, memcmp(buffer, kHello, sizeof(kHello)));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle0));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle1));
+
+ // TODO(vtl): data pipe
+
+ return NULL;
+}
diff --git a/mojo/public/c/system/tests/macros_unittest.cc b/mojo/public/c/system/tests/macros_unittest.cc
new file mode 100644
index 0000000..6a694b8
--- /dev/null
+++ b/mojo/public/c/system/tests/macros_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file tests the C Mojo system macros and consists of "positive" tests,
+// i.e., those verifying that things work (without compile errors, or even
+// warnings if warnings are treated as errors).
+// TODO(vtl): Fix no-compile tests (which are all disabled; crbug.com/105388)
+// and write some "negative" tests.
+
+#include "mojo/public/c/system/macros.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+TEST(MacrosTest, AllowUnused) {
+ // Test that no warning/error is issued even though |x| is unused.
+ int x MOJO_ALLOW_UNUSED = 123;
+}
+
+int MustUseReturnedResult() MOJO_WARN_UNUSED_RESULT;
+int MustUseReturnedResult() {
+ return 456;
+}
+
+TEST(MacrosTest, WarnUnusedResult) {
+ if (!MustUseReturnedResult())
+ abort();
+}
+
+// First test |MOJO_COMPILE_ASSERT()| in a global scope.
+MOJO_COMPILE_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t),
+ bad_compile_assert_failure_in_global_scope);
+
+TEST(MacrosTest, CompileAssert) {
+ // Then in a local scope.
+ MOJO_COMPILE_ASSERT(sizeof(int32_t) == 2 * sizeof(int16_t),
+ bad_compile_assert_failure);
+}
+
+TEST(MacrosTest, Alignof) {
+ // Strictly speaking, this isn't a portable test, but I think it'll pass on
+ // all the platforms we currently support.
+ EXPECT_EQ(1u, MOJO_ALIGNOF(char));
+ EXPECT_EQ(4u, MOJO_ALIGNOF(int32_t));
+ EXPECT_EQ(8u, MOJO_ALIGNOF(int64_t));
+ EXPECT_EQ(8u, MOJO_ALIGNOF(double));
+}
+
+// These structs are used in the Alignas test. Define them globally to avoid
+// MSVS warnings/errors.
+#if defined(_MSC_VER)
+#pragma warning(push)
+// Disable the warning "structure was padded due to __declspec(align())".
+#pragma warning(disable : 4324)
+#endif
+struct MOJO_ALIGNAS(1) StructAlignas1 {
+ char x;
+};
+struct MOJO_ALIGNAS(4) StructAlignas4 {
+ char x;
+};
+struct MOJO_ALIGNAS(8) StructAlignas8 {
+ char x;
+};
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+TEST(MacrosTest, Alignas) {
+ EXPECT_EQ(1u, MOJO_ALIGNOF(StructAlignas1));
+ EXPECT_EQ(4u, MOJO_ALIGNOF(StructAlignas4));
+ EXPECT_EQ(8u, MOJO_ALIGNOF(StructAlignas8));
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h
new file mode 100644
index 0000000..5a72d2f
--- /dev/null
+++ b/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/mojo/public/c/test_support/BUILD.gn b/mojo/public/c/test_support/BUILD.gn
new file mode 100644
index 0000000..42db434
--- /dev/null
+++ b/mojo/public/c/test_support/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_test_support
+shared_library("test_support") {
+ output_name = "mojo_test_support"
+
+ defines = [ "MOJO_TEST_SUPPORT_IMPLEMENTATION" ]
+
+ sources = [
+ "test_support.h",
+ "test_support_export.h",
+ # TODO(vtl): Convert this to thunks http://crbug.com/386799
+ "../../tests/test_support_private.cc",
+ "../../tests/test_support_private.h",
+ ]
+
+ if (is_mac) {
+# TODO(GYP)
+# # Make it a run-path dependent library.
+# 'DYLIB_INSTALL_NAME_BASE': '@loader_path',
+ }
+}
diff --git a/mojo/public/c/test_support/test_support.h b/mojo/public/c/test_support/test_support.h
new file mode 100644
index 0000000..2b686b2
--- /dev/null
+++ b/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/mojo/public/c/test_support/test_support_export.h b/mojo/public/c/test_support/test_support_export.h
new file mode 100644
index 0000000..e22a9e3
--- /dev/null
+++ b/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/mojo/public/cpp/DEPS b/mojo/public/cpp/DEPS
new file mode 100644
index 0000000..74acd7c
--- /dev/null
+++ b/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/mojo/public/cpp/README.md b/mojo/public/cpp/README.md
new file mode 100644
index 0000000..8f03d98
--- /dev/null
+++ b/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/mojo/public/cpp/application/BUILD.gn b/mojo/public/cpp/application/BUILD.gn
new file mode 100644
index 0000000..900edef
--- /dev/null
+++ b/mojo/public/cpp/application/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/mojo_public.gypi:mojo_application_base
+source_set("application") {
+ sources = [
+ "application_connection.h",
+ "application_delegate.h",
+ "application_impl.h",
+ "connect.h",
+ "service_provider_impl.h",
+ "interface_factory.h",
+ "interface_factory_impl.h",
+ "lib/application_connection.cc",
+ "lib/application_delegate.cc",
+ "lib/application_impl.cc",
+ "lib/service_provider_impl.cc",
+ "lib/service_connector.cc",
+ "lib/service_connector.h",
+ "lib/service_registry.cc",
+ "lib/service_registry.h",
+ "lib/weak_service_provider.cc",
+ "lib/weak_service_provider.h",
+ ]
+
+ deps = [
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/environment",
+ "//mojo/public/cpp/system",
+ "//mojo/public/interfaces/application",
+ ]
+}
+
+# GYP version: mojo/mojo_public.gypi:mojo_application_standalone
+source_set("standalone") {
+ sources = [
+ "lib/application_runner.cc"
+ ]
+
+ public_deps = [ ":application" ]
+
+ deps = [
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/utility",
+ ]
+}
diff --git a/mojo/public/cpp/application/DEPS b/mojo/public/cpp/application/DEPS
new file mode 100644
index 0000000..dcd8e6f
--- /dev/null
+++ b/mojo/public/cpp/application/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+ "+mojo/public/cpp/bindings",
+ "+mojo/public/cpp/environment",
+ "+mojo/public/interfaces/application",
+ "+mojo/public/interfaces/service_provider",
+]
+
+specific_include_rules = {
+ r"application_runner_chromium.*": [
+ "+base",
+ "+mojo/common",
+ "+mojo/public/cpp"
+ ],
+ r"application_runner.*": [
+ "+mojo/public/cpp"
+ ],
+}
diff --git a/mojo/public/cpp/application/application_connection.h b/mojo/public/cpp/application/application_connection.h
new file mode 100644
index 0000000..d5d6dde
--- /dev/null
+++ b/mojo/public/cpp/application/application_connection.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_APPLICATION_APPLICATION_CONNECTION_H_
+#define MOJO_PUBLIC_APPLICATION_APPLICATION_CONNECTION_H_
+
+#include <string>
+
+#include "mojo/public/cpp/application/lib/service_connector.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+
+// An instance of this class is passed to
+// ApplicationDelegate's ConfigureIncomingConnection() method each time a
+// connection is made to this app, and to ApplicationDelegate's
+// ConfigureOutgoingConnection() method when the app connects to
+// another.
+//
+// To use define a class that implements your specific service api, e.g. FooImpl
+// to implement a service named Foo.
+// That class must subclass an InterfaceImpl specialization.
+//
+// Then implement an InterfaceFactory<Foo> that binds instances of FooImpl to
+// InterfaceRequest<Foo>s and register that on the connection.
+//
+// connection->AddService(&factory);
+//
+// Or if you have multiple factories implemented by the same type, explicitly
+// specify the interface to register the factory for:
+//
+// connection->AddService<Foo>(&my_foo_and_bar_factory_);
+// connection->AddService<Bar>(&my_foo_and_bar_factory_);
+//
+// The InterfaceFactory must outlive the ApplicationConnection.
+class ApplicationConnection {
+ public:
+ virtual ~ApplicationConnection();
+
+ template <typename Interface>
+ void AddService(InterfaceFactory<Interface>* factory) {
+ AddServiceConnector(
+ new internal::InterfaceFactoryConnector<Interface>(factory));
+ }
+
+ // Connect to the service implementing |Interface|.
+ template <typename Interface>
+ void ConnectToService(InterfacePtr<Interface>* ptr) {
+ MessagePipe pipe;
+ ptr->Bind(pipe.handle0.Pass());
+ GetServiceProvider()->ConnectToService(Interface::Name_,
+ pipe.handle1.Pass());
+ }
+
+ // The url identifying the application on the other end of this connection.
+ virtual const std::string& GetRemoteApplicationURL() = 0;
+
+ // Establishes a new connection to an application.
+ // TODO(davemoore): Would it be better to expose the ApplicationImpl?
+ virtual ApplicationConnection* ConnectToApplication(
+ const std::string& url) = 0;
+
+ // Connect to application identified by |application_url| and connect to
+ // the service implementation of the interface identified by |Interface|.
+ template <typename Interface>
+ void ConnectToService(const std::string& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ConnectToApplication(application_url)->ConnectToService(ptr);
+ }
+
+ // Raw ServiceProvider interface to remote application.
+ virtual ServiceProvider* GetServiceProvider() = 0;
+
+ private:
+ virtual void AddServiceConnector(
+ internal::ServiceConnectorBase* service_connector) = 0;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_APPLICATION_APPLICATION_CONNECTION_H_
diff --git a/mojo/public/cpp/application/application_delegate.h b/mojo/public/cpp/application/application_delegate.h
new file mode 100644
index 0000000..cb67308
--- /dev/null
+++ b/mojo/public/cpp/application/application_delegate.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_APPLICATION_APPLICATION_DELEGATE_H_
+#define MOJO_PUBLIC_APPLICATION_APPLICATION_DELEGATE_H_
+
+#include <string>
+
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+class ApplicationImpl;
+
+class ApplicationDelegate {
+ public:
+ ApplicationDelegate();
+ virtual ~ApplicationDelegate();
+
+ // Implement this method to create the specific subclass of
+ // ApplicationDelegate. Ownership is taken by the caller. It will be deleted.
+ static ApplicationDelegate* Create();
+
+ virtual void Initialize(ApplicationImpl* app);
+
+ // Override this method to configure what services a connection supports when
+ // being connected to from an app.
+ // return false to reject the connection entirely.
+ virtual bool ConfigureIncomingConnection(ApplicationConnection* connection);
+
+ // Override this method to configure what services a connection supports when
+ // connecting to another app.
+ // return false to reject the connection entirely.
+ virtual bool ConfigureOutgoingConnection(ApplicationConnection* connection);
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ApplicationDelegate);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_APPLICATION_APPLICATION_DELEGATE_H_
diff --git a/mojo/public/cpp/application/application_impl.h b/mojo/public/cpp/application/application_impl.h
new file mode 100644
index 0000000..4b4cb93
--- /dev/null
+++ b/mojo/public/cpp/application/application_impl.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_APPLICATION_APPLICATION_IMPL_H_
+#define MOJO_PUBLIC_APPLICATION_APPLICATION_IMPL_H_
+#include <vector>
+
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/lib/service_connector.h"
+#include "mojo/public/cpp/application/lib/service_registry.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+
+namespace mojo {
+
+class ApplicationDelegate;
+
+// Utility class for communicating with the Shell, and providing Services
+// to clients.
+//
+// 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(ApplicationContext* app_context) {}
+// };
+//
+// or
+//
+// class BarImpl : public InterfaceImpl<Bar> {
+// public:
+// // contexts will remain valid for the lifetime of BarImpl.
+// BarImpl(ApplicationContext* app_context, BarContext* service_context)
+// : app_context_(app_context), servicecontext_(context) {}
+//
+// Create an ApplicationImpl instance that collects any service implementations.
+//
+// ApplicationImpl app(service_provider_handle);
+// app.AddService<FooImpl>();
+//
+// BarContext context;
+// app.AddService<BarImpl>(&context);
+//
+//
+class ApplicationImpl : public InterfaceImpl<Application> {
+ public:
+ ApplicationImpl(ApplicationDelegate* delegate,
+ ScopedMessagePipeHandle shell_handle);
+ ApplicationImpl(ApplicationDelegate* delegate,
+ MojoHandle shell_handle);
+ virtual ~ApplicationImpl();
+
+ Shell* shell() const { return shell_.get(); }
+
+ // Returns any initial configuration arguments, passed by the Shell.
+ const Array<String>& args() { return args_; }
+
+ // Establishes a new connection to an application. Caller does not own.
+ ApplicationConnection* ConnectToApplication(const String& application_url);
+
+ // Connect to application identified by |application_url| and connect to the
+ // service implementation of the interface identified by |Interface|.
+ template <typename Interface>
+ void ConnectToService(const std::string& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ConnectToApplication(application_url)->ConnectToService(ptr);
+ }
+
+ private:
+ class ShellPtrWatcher;
+
+ void BindShell(ScopedMessagePipeHandle shell_handle);
+ void ClearConnections();
+ void OnShellError() {
+ ClearConnections();
+ Terminate();
+ };
+
+ // Quits the main run loop for this application.
+ static void Terminate();
+
+ // Application implementation.
+ virtual void Initialize(Array<String> args) override;
+ virtual void AcceptConnection(const String& requestor_url,
+ ServiceProviderPtr provider) override;
+
+ typedef std::vector<internal::ServiceRegistry*> ServiceRegistryList;
+
+ bool initialized_;
+ ServiceRegistryList incoming_service_registries_;
+ ServiceRegistryList outgoing_service_registries_;
+ ApplicationDelegate* delegate_;
+ ShellPtr shell_;
+ ShellPtrWatcher* shell_watch_;
+ Array<String> args_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ApplicationImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_APPLICATION_APPLICATION_IMPL_H_
diff --git a/mojo/public/cpp/application/application_runner.h b/mojo/public/cpp/application/application_runner.h
new file mode 100644
index 0000000..b88ec8e
--- /dev/null
+++ b/mojo/public/cpp/application/application_runner.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_APPLICATION_APPLICATION_RUNNER_H_
+#define MOJO_PUBLIC_APPLICATION_APPLICATION_RUNNER_H_
+
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+class ApplicationDelegate;
+
+// A utility for running an Application. The typical use case is to use
+// when writing your MojoMain:
+//
+// MojoResult MojoMain(MojoHandle shell_handle) {
+// mojo::ApplicationRunner runner(new MyApplicationDelegate());
+// return runner.Run(shell_handle);
+// }
+//
+// ApplicationRunner takes care of mojo environment initialization and
+// shutdown, and starting a RunLoop from which your application can run and
+// ultimately Quit().
+class ApplicationRunner {
+ public:
+ // Takes ownership of |delegate|.
+ explicit ApplicationRunner(ApplicationDelegate* delegate);
+ ~ApplicationRunner();
+
+ // Once the various parameters have been set above, use Run to initialize an
+ // ApplicationImpl wired to the provided delegate, and run a RunLoop until
+ // the application exits.
+ MojoResult Run(MojoHandle shell_handle);
+
+ private:
+ ApplicationDelegate* delegate_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ApplicationRunner);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_APPLICATION_APPLICATION_RUNNER_H_
diff --git a/mojo/public/cpp/application/connect.h b/mojo/public/cpp/application/connect.h
new file mode 100644
index 0000000..a41c028
--- /dev/null
+++ b/mojo/public/cpp/application/connect.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_PUBLIC_CPP_APPLICATION_CONNECT_H_
+#define MOJO_PUBLIC_CPP_APPLICATION_CONNECT_H_
+
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+
+template <typename Interface>
+inline void ConnectToService(ServiceProvider* service_provider,
+ InterfacePtr<Interface>* ptr) {
+ MessagePipe pipe;
+ ptr->Bind(pipe.handle0.Pass());
+ service_provider->ConnectToService(Interface::Name_, pipe.handle1.Pass());
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_APPLICATION_CONNECT_H_
diff --git a/mojo/public/cpp/application/interface_factory.h b/mojo/public/cpp/application/interface_factory.h
new file mode 100644
index 0000000..90abd13
--- /dev/null
+++ b/mojo/public/cpp/application/interface_factory.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_CPP_APPLICATION_INTERFACE_FACTORY_H_
+#define MOJO_PUBLIC_CPP_APPLICATION_INTERFACE_FACTORY_H_
+
+#include "mojo/public/cpp/bindings/interface_impl.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+template <typename Interface> class InterfaceRequest;
+
+// Implement this class to provide implementations of a given interface and
+// bind them to incoming requests. The implementation of this class is
+// responsible for managing the lifetime of the implementations of the
+// interface.
+template <typename Interface>
+class InterfaceFactory {
+ public:
+ virtual ~InterfaceFactory() {}
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Interface> request) = 0;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_APPLICATION_INTERFACE_FACTORY_H_
diff --git a/mojo/public/cpp/application/interface_factory_impl.h b/mojo/public/cpp/application/interface_factory_impl.h
new file mode 100644
index 0000000..72d3254
--- /dev/null
+++ b/mojo/public/cpp/application/interface_factory_impl.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_PUBLIC_CPP_APPLICATION_INTERFACE_FACTORY_IMPL_H_
+#define MOJO_PUBLIC_CPP_APPLICATION_INTERFACE_FACTORY_IMPL_H_
+
+#include "mojo/public/cpp/application/interface_factory.h"
+
+namespace mojo {
+
+// Use this class to allocate and bind instances of Impl to interface requests.
+// The lifetime of the constructed Impl is bound to the pipe.
+template <typename Impl,
+ typename Interface = typename Impl::ImplementedInterface>
+class InterfaceFactoryImpl : public InterfaceFactory<Interface> {
+ public:
+ virtual ~InterfaceFactoryImpl() {}
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Interface> request) override {
+ BindToRequest(new Impl(), &request);
+ }
+};
+
+// Use this class to allocate and bind instances of Impl constructed with a
+// context parameter to interface requests. The lifetime of the constructed
+// Impl is bound to the pipe.
+template <typename Impl,
+ typename Context,
+ typename Interface = typename Impl::ImplementedInterface>
+class InterfaceFactoryImplWithContext : public InterfaceFactory<Interface> {
+ public:
+ explicit InterfaceFactoryImplWithContext(Context* context)
+ : context_(context) {}
+ virtual ~InterfaceFactoryImplWithContext() {}
+
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Interface> request) override {
+ BindToRequest(new Impl(context_), &request);
+ }
+
+ private:
+ Context* context_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_APPLICATION_INTERFACE_FACTORY_IMPL_H_
diff --git a/mojo/public/cpp/application/lazy_interface_ptr.h b/mojo/public/cpp/application/lazy_interface_ptr.h
new file mode 100644
index 0000000..1f4b973
--- /dev/null
+++ b/mojo/public/cpp/application/lazy_interface_ptr.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_CPP_APPLICATION_LAZY_INTERFACE_PTR_H_
+#define MOJO_PUBLIC_CPP_APPLICATION_LAZY_INTERFACE_PTR_H_
+
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+
+template<typename Interface>
+class LazyInterfacePtr : public InterfacePtr<Interface> {
+ public:
+ LazyInterfacePtr() : service_provider_(nullptr) {}
+
+ LazyInterfacePtr(ServiceProvider* service_provider)
+ : service_provider_(service_provider) {
+ }
+
+ void set_service_provider(ServiceProvider* service_provider) {
+ if (service_provider != service_provider_) {
+ InterfacePtr<Interface>::reset();
+ }
+ service_provider_ = service_provider;
+ }
+
+ Interface* get() const {
+ if (!InterfacePtr<Interface>::get()) {
+ mojo::ConnectToService<Interface>(
+ service_provider_,
+ const_cast<LazyInterfacePtr<Interface>*>(this));
+ }
+ return InterfacePtr<Interface>::get();
+ }
+ Interface* operator->() const { return get(); }
+ Interface& operator*() const { return *get(); }
+
+ private:
+ ServiceProvider* service_provider_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_APPLICATION_LAZY_INTERFACE_PTR_H_
diff --git a/mojo/public/cpp/application/lib/application_connection.cc b/mojo/public/cpp/application/lib/application_connection.cc
new file mode 100644
index 0000000..4978a35
--- /dev/null
+++ b/mojo/public/cpp/application/lib/application_connection.cc
@@ -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.
+
+#include "mojo/public/cpp/application/application_connection.h"
+
+namespace mojo {
+
+ApplicationConnection::~ApplicationConnection() {}
+
+} // namespace mojo
+
diff --git a/mojo/public/cpp/application/lib/application_delegate.cc b/mojo/public/cpp/application/lib/application_delegate.cc
new file mode 100644
index 0000000..715daa0
--- /dev/null
+++ b/mojo/public/cpp/application/lib/application_delegate.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/public/cpp/application/application_delegate.h"
+
+namespace mojo {
+
+ApplicationDelegate::ApplicationDelegate() {}
+ApplicationDelegate::~ApplicationDelegate() {}
+
+void ApplicationDelegate::Initialize(ApplicationImpl* app) {}
+
+bool ApplicationDelegate::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ return true;
+}
+
+bool ApplicationDelegate::ConfigureOutgoingConnection(
+ ApplicationConnection* connection) {
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/application_impl.cc b/mojo/public/cpp/application/lib/application_impl.cc
new file mode 100644
index 0000000..0c86356
--- /dev/null
+++ b/mojo/public/cpp/application/lib/application_impl.cc
@@ -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.
+
+#include "mojo/public/cpp/application/application_impl.h"
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/lib/service_registry.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+
+class ApplicationImpl::ShellPtrWatcher : public ErrorHandler {
+ public:
+ ShellPtrWatcher(ApplicationImpl* impl)
+ : impl_(impl) {}
+
+ virtual ~ShellPtrWatcher() {}
+
+ virtual void OnConnectionError() override { impl_->OnShellError(); }
+
+ private:
+ ApplicationImpl* impl_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ShellPtrWatcher);
+};
+
+ApplicationImpl::ApplicationImpl(ApplicationDelegate* delegate,
+ ScopedMessagePipeHandle shell_handle)
+ : initialized_(false), delegate_(delegate), shell_watch_(nullptr) {
+ BindShell(shell_handle.Pass());
+}
+
+ApplicationImpl::ApplicationImpl(ApplicationDelegate* delegate,
+ MojoHandle shell_handle)
+ : initialized_(false), delegate_(delegate), shell_watch_(nullptr) {
+ BindShell(MakeScopedHandle(MessagePipeHandle(shell_handle)));
+}
+
+void ApplicationImpl::ClearConnections() {
+ for (ServiceRegistryList::iterator i(incoming_service_registries_.begin());
+ i != incoming_service_registries_.end(); ++i)
+ delete *i;
+ for (ServiceRegistryList::iterator i(outgoing_service_registries_.begin());
+ i != outgoing_service_registries_.end(); ++i)
+ delete *i;
+ incoming_service_registries_.clear();
+ outgoing_service_registries_.clear();
+}
+
+ApplicationImpl::~ApplicationImpl() {
+ ClearConnections();
+ delete shell_watch_;
+}
+
+void ApplicationImpl::Initialize(Array<String> args) {
+ MOJO_CHECK(!initialized_);
+ initialized_ = true;
+ args_ = args.Pass();
+ delegate_->Initialize(this);
+}
+
+ApplicationConnection* ApplicationImpl::ConnectToApplication(
+ const String& application_url) {
+ MOJO_CHECK(initialized_);
+ ServiceProviderPtr out_service_provider;
+ shell_->ConnectToApplication(application_url, Get(&out_service_provider));
+ internal::ServiceRegistry* registry = new internal::ServiceRegistry(
+ this,
+ application_url,
+ out_service_provider.Pass());
+ if (!delegate_->ConfigureOutgoingConnection(registry)) {
+ delete registry;
+ return nullptr;
+ }
+ outgoing_service_registries_.push_back(registry);
+ return registry;
+}
+
+void ApplicationImpl::BindShell(ScopedMessagePipeHandle shell_handle) {
+ shell_watch_ = new ShellPtrWatcher(this);
+ shell_.Bind(shell_handle.Pass());
+ shell_.set_client(this);
+ shell_.set_error_handler(shell_watch_);
+}
+
+void ApplicationImpl::AcceptConnection(const String& requestor_url,
+ ServiceProviderPtr service_provider) {
+ internal::ServiceRegistry* registry = new internal::ServiceRegistry(
+ this, requestor_url, service_provider.Pass());
+ if (!delegate_->ConfigureIncomingConnection(registry)) {
+ delete registry;
+ return;
+ }
+ incoming_service_registries_.push_back(registry);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/application_runner.cc b/mojo/public/cpp/application/lib/application_runner.cc
new file mode 100644
index 0000000..b451a90
--- /dev/null
+++ b/mojo/public/cpp/application/lib/application_runner.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/application/application_runner.h"
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+
+namespace mojo {
+
+// static
+void ApplicationImpl::Terminate() {
+ RunLoop::current()->Quit();
+}
+
+ApplicationRunner::ApplicationRunner(ApplicationDelegate* delegate)
+ : delegate_(delegate) {}
+ApplicationRunner::~ApplicationRunner() { assert(!delegate_); }
+
+MojoResult ApplicationRunner::Run(MojoHandle shell_handle) {
+ Environment env;
+ {
+ RunLoop loop;
+ ApplicationImpl app(delegate_, shell_handle);
+ loop.Run();
+ }
+
+ delete delegate_;
+ delegate_ = nullptr;
+ return MOJO_RESULT_OK;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/service_connector.cc b/mojo/public/cpp/application/lib/service_connector.cc
new file mode 100644
index 0000000..51e7d84
--- /dev/null
+++ b/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),
+ application_connection_(nullptr) {
+}
+
+ServiceConnectorBase::~ServiceConnectorBase() {}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/service_connector.h b/mojo/public/cpp/application/lib/service_connector.h
new file mode 100644
index 0000000..9ddaeb7
--- /dev/null
+++ b/mojo/public/cpp/application/lib/service_connector.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_CPP_APPLICATION_LIB_SERVICE_CONNECTOR_H_
+#define MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_CONNECTOR_H_
+
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+
+namespace mojo {
+class ApplicationConnection;
+
+namespace internal {
+
+class ServiceConnectorBase {
+ public:
+ ServiceConnectorBase(const std::string& name);
+ virtual ~ServiceConnectorBase();
+ virtual void ConnectToService(const std::string& name,
+ ScopedMessagePipeHandle client_handle) = 0;
+ std::string name() const { return name_; }
+ void set_application_connection(ApplicationConnection* connection) {
+ application_connection_ = connection; }
+
+ protected:
+ std::string name_;
+ ApplicationConnection* application_connection_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceConnectorBase);
+};
+
+template <typename Interface>
+class InterfaceFactoryConnector : public ServiceConnectorBase {
+ public:
+ explicit InterfaceFactoryConnector(InterfaceFactory<Interface>* factory)
+ : ServiceConnectorBase(Interface::Name_), factory_(factory) {}
+ virtual ~InterfaceFactoryConnector() {}
+
+ virtual void ConnectToService(const std::string& name,
+ ScopedMessagePipeHandle client_handle) {
+ factory_->Create(application_connection_,
+ MakeRequest<Interface>(client_handle.Pass()));
+ }
+
+ private:
+ InterfaceFactory<Interface>* factory_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(InterfaceFactoryConnector);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_APPLICATION_LIB_SERVICE_CONNECTOR_H_
diff --git a/mojo/public/cpp/application/lib/service_provider_impl.cc b/mojo/public/cpp/application/lib/service_provider_impl.cc
new file mode 100644
index 0000000..08a0648
--- /dev/null
+++ b/mojo/public/cpp/application/lib/service_provider_impl.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/application/service_provider_impl.h"
+
+#include "mojo/public/cpp/application/lib/service_connector.h"
+#include "mojo/public/cpp/application/lib/weak_service_provider.h"
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+
+ServiceProviderImpl::ServiceProviderImpl() : remote_(nullptr) {
+}
+
+ServiceProviderImpl::~ServiceProviderImpl() {
+}
+
+ServiceProvider* ServiceProviderImpl::CreateRemoteServiceProvider() {
+ // TODO(beng): it sure would be nice if this method could return a scoped_ptr.
+ MOJO_DCHECK(!remote_);
+ remote_ = new internal::WeakServiceProvider(this, client());
+ return remote_;
+}
+
+void ServiceProviderImpl::ConnectToService(
+ const String& service_name,
+ ScopedMessagePipeHandle client_handle) {
+ if (service_connectors_.find(service_name) == service_connectors_.end()) {
+ client_handle.reset();
+ return;
+ }
+
+ internal::ServiceConnectorBase* service_connector =
+ service_connectors_[service_name];
+ return service_connector->ConnectToService(service_name,
+ client_handle.Pass());
+}
+
+void ServiceProviderImpl::OnConnectionError() {
+ ClearRemote();
+}
+
+void ServiceProviderImpl::AddServiceConnector(
+ internal::ServiceConnectorBase* service_connector) {
+ RemoveServiceConnector(service_connector);
+ service_connectors_[service_connector->name()] = service_connector;
+ // TODO(beng): perhaps take app connection thru ctor??
+ service_connector->set_application_connection(nullptr);
+}
+
+void ServiceProviderImpl::RemoveServiceConnector(
+ internal::ServiceConnectorBase* service_connector) {
+ NameToServiceConnectorMap::iterator it =
+ service_connectors_.find(service_connector->name());
+ if (it == service_connectors_.end())
+ return;
+ delete it->second;
+ service_connectors_.erase(it);
+}
+
+void ServiceProviderImpl::ClearRemote() {
+ if (remote_) {
+ remote_->Clear();
+ remote_ = nullptr;
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/service_registry.cc b/mojo/public/cpp/application/lib/service_registry.cc
new file mode 100644
index 0000000..c83e84f
--- /dev/null
+++ b/mojo/public/cpp/application/lib/service_registry.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/public/cpp/application/lib/service_registry.h"
+
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/lib/service_connector.h"
+
+namespace mojo {
+namespace internal {
+
+ServiceRegistry::ServiceRegistry(ApplicationImpl* application_impl,
+ const std::string& url,
+ ServiceProviderPtr service_provider)
+ : application_impl_(application_impl),
+ url_(url),
+ remote_service_provider_(service_provider.Pass()) {
+ remote_service_provider_.set_client(this);
+}
+
+ServiceRegistry::ServiceRegistry() : application_impl_(nullptr) {}
+
+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) {
+ RemoveServiceConnectorInternal(service_connector);
+ name_to_service_connector_[service_connector->name()] = service_connector;
+ service_connector->set_application_connection(this);
+}
+
+void ServiceRegistry::RemoveServiceConnector(
+ ServiceConnectorBase* service_connector) {
+ RemoveServiceConnectorInternal(service_connector);
+ if (name_to_service_connector_.empty())
+ remote_service_provider_.reset();
+}
+
+bool ServiceRegistry::RemoveServiceConnectorInternal(
+ ServiceConnectorBase* service_connector) {
+ NameToServiceConnectorMap::iterator it =
+ name_to_service_connector_.find(service_connector->name());
+ if (it == name_to_service_connector_.end())
+ return false;
+ delete it->second;
+ name_to_service_connector_.erase(it);
+ return true;
+}
+
+const std::string& ServiceRegistry::GetRemoteApplicationURL() {
+ return url_;
+}
+
+ServiceProvider* ServiceRegistry::GetServiceProvider() {
+ return remote_service_provider_.get();
+}
+
+ApplicationConnection* ServiceRegistry::ConnectToApplication(
+ const std::string& url) {
+ return application_impl_->ConnectToApplication(url);
+}
+
+void ServiceRegistry::ConnectToService(const mojo::String& service_name,
+ ScopedMessagePipeHandle client_handle) {
+ if (name_to_service_connector_.find(service_name) ==
+ name_to_service_connector_.end()) {
+ client_handle.reset();
+ return;
+ }
+ internal::ServiceConnectorBase* service_connector =
+ name_to_service_connector_[service_name];
+ return service_connector->ConnectToService(service_name,
+ client_handle.Pass());
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/service_registry.h b/mojo/public/cpp/application/lib/service_registry.h
new file mode 100644
index 0000000..32d4b2b
--- /dev/null
+++ b/mojo/public/cpp/application/lib/service_registry.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium 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/cpp/application/application_connection.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+
+class Application;
+class ApplicationImpl;
+
+namespace internal {
+
+class ServiceConnectorBase;
+
+// A ServiceRegistry represents each half of a connection between two
+// applications, allowing customization of which services are published to the
+// other.
+class ServiceRegistry : public ServiceProvider, public ApplicationConnection {
+ public:
+ ServiceRegistry();
+ ServiceRegistry(ApplicationImpl* application_impl,
+ const std::string& url,
+ ServiceProviderPtr service_provider);
+ virtual ~ServiceRegistry();
+
+ // ApplicationConnection overrides.
+ virtual void AddServiceConnector(
+ ServiceConnectorBase* service_connector) override;
+ virtual const std::string& GetRemoteApplicationURL() override;
+ virtual ApplicationConnection* ConnectToApplication(
+ const std::string& url) override;
+ virtual ServiceProvider* GetServiceProvider() override;
+
+ virtual void RemoveServiceConnector(ServiceConnectorBase* service_connector);
+
+ private:
+ // ServiceProvider method.
+ virtual void ConnectToService(const mojo::String& service_name,
+ ScopedMessagePipeHandle client_handle) override;
+
+ ApplicationImpl* application_impl_;
+ const std::string url_;
+
+ private:
+ bool RemoveServiceConnectorInternal(
+ ServiceConnectorBase* service_connector);
+
+ 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/mojo/public/cpp/application/lib/weak_service_provider.cc b/mojo/public/cpp/application/lib/weak_service_provider.cc
new file mode 100644
index 0000000..b0d2511
--- /dev/null
+++ b/mojo/public/cpp/application/lib/weak_service_provider.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/application/lib/weak_service_provider.h"
+
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+namespace internal {
+
+WeakServiceProvider::WeakServiceProvider(ServiceProviderImpl* creator,
+ ServiceProvider* service_provider)
+ : creator_(creator),
+ service_provider_(service_provider) {}
+
+WeakServiceProvider::~WeakServiceProvider() {
+ if (creator_)
+ creator_->ClearRemote();
+}
+
+void WeakServiceProvider::Clear() {
+ creator_ = nullptr;
+ service_provider_ = nullptr;
+}
+
+void WeakServiceProvider::ConnectToService(
+ const String& service_name,
+ ScopedMessagePipeHandle client_handle) {
+ if (service_provider_)
+ service_provider_->ConnectToService(service_name, client_handle.Pass());
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/application/lib/weak_service_provider.h b/mojo/public/cpp/application/lib/weak_service_provider.h
new file mode 100644
index 0000000..05ce939
--- /dev/null
+++ b/mojo/public/cpp/application/lib/weak_service_provider.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_APPLICATION_LIB_WEAK_SERVICE_PROVIDER_H_
+#define MOJO_PUBLIC_APPLICATION_LIB_WEAK_SERVICE_PROVIDER_H_
+
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+class ServiceProviderImpl;
+namespace internal {
+class ServiceConnectorBase;
+
+// Implements a weak pointer to a ServiceProvider. Necessary as the lifetime of
+// the ServiceProviderImpl is bound to that of its pipe, but code may continue
+// to reference a remote service provider beyond the lifetime of said pipe.
+// Calls to ConnectToService() are silently dropped when the pipe is closed.
+class WeakServiceProvider : public ServiceProvider {
+ public:
+ WeakServiceProvider(ServiceProviderImpl* creator,
+ ServiceProvider* service_provider);
+ virtual ~WeakServiceProvider();
+
+ void Clear();
+
+ private:
+ // Overridden from ServiceProvider:
+ virtual void ConnectToService(const String& service_name,
+ ScopedMessagePipeHandle client_handle) override;
+
+ ServiceProviderImpl* creator_;
+ ServiceProvider* service_provider_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(WeakServiceProvider);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_APPLICATION_LIB_WEAK_SERVICE_PROVIDER_H_
diff --git a/mojo/public/cpp/application/service_provider_impl.h b/mojo/public/cpp/application/service_provider_impl.h
new file mode 100644
index 0000000..f388511
--- /dev/null
+++ b/mojo/public/cpp/application/service_provider_impl.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_APPLICATION_SERVICE_PROVIDER_IMPL_H_
+#define MOJO_PUBLIC_APPLICATION_SERVICE_PROVIDER_IMPL_H_
+
+#include "mojo/public/cpp/application/lib/service_connector.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+namespace internal {
+class WeakServiceProvider;
+class ServiceConnectorBase;
+}
+
+// Implements a registry that can be used to expose services to another app.
+class ServiceProviderImpl : public InterfaceImpl<ServiceProvider> {
+ public:
+ ServiceProviderImpl();
+ virtual ~ServiceProviderImpl();
+
+ template <typename Interface>
+ void AddService(InterfaceFactory<Interface>* factory) {
+ AddServiceConnector(
+ new internal::InterfaceFactoryConnector<Interface>(factory));
+ }
+
+ // Returns an instance of a ServiceProvider that weakly wraps this impl's
+ // connection to some other app. Whereas the lifetime of an instance of
+ // ServiceProviderImpl is bound to the lifetime of the pipe it
+ // encapsulates, the lifetime of the ServiceProvider instance returned by this
+ // method is assumed by the caller. After the pipe is closed
+ // ConnectToService() calls on this object will be silently dropped.
+ // This method must only be called once per ServiceProviderImpl.
+ ServiceProvider* CreateRemoteServiceProvider();
+
+ private:
+ typedef std::map<std::string, internal::ServiceConnectorBase*>
+ NameToServiceConnectorMap;
+
+ friend class internal::WeakServiceProvider;
+
+ // Overridden from ServiceProvider:
+ virtual void ConnectToService(const String& service_name,
+ ScopedMessagePipeHandle client_handle) override;
+
+ // Overridden from InterfaceImpl:
+ virtual void OnConnectionError() override;
+
+ void AddServiceConnector(
+ internal::ServiceConnectorBase* service_connector);
+ void RemoveServiceConnector(
+ internal::ServiceConnectorBase* service_connector);
+
+ void ClearRemote();
+
+ NameToServiceConnectorMap service_connectors_;
+
+ internal::WeakServiceProvider* remote_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ServiceProviderImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_APPLICATION_SERVICE_PROVIDER_IMPL_H_
diff --git a/mojo/public/cpp/application/tests/BUILD.gn b/mojo/public/cpp/application/tests/BUILD.gn
new file mode 100644
index 0000000..4c22c19
--- /dev/null
+++ b/mojo/public/cpp/application/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_application_unittests
+test("mojo_public_application_unittests") {
+ deps = [
+ "//base",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/utility",
+ "//testing/gtest",
+ ]
+
+ sources = [ "service_registry_unittest.cc" ]
+}
diff --git a/mojo/public/cpp/application/tests/service_registry_unittest.cc b/mojo/public/cpp/application/tests/service_registry_unittest.cc
new file mode 100644
index 0000000..a0511ff
--- /dev/null
+++ b/mojo/public/cpp/application/tests/service_registry_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/application/lib/service_registry.h"
+
+#include "mojo/public/cpp/application/lib/service_connector.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace internal {
+namespace {
+
+class TestConnector : public ServiceConnectorBase {
+ public:
+ TestConnector(const std::string& name, int* delete_count)
+ : ServiceConnectorBase(name), delete_count_(delete_count) {}
+ virtual ~TestConnector() { (*delete_count_)++; }
+ virtual void ConnectToService(
+ const std::string& name,
+ ScopedMessagePipeHandle client_handle) override {}
+
+ private:
+ int* delete_count_;
+};
+
+TEST(ServiceRegistryTest, Ownership) {
+ int delete_count = 0;
+
+ // Destruction.
+ {
+ ServiceRegistry registry;
+ registry.AddServiceConnector(new TestConnector("TC1", &delete_count));
+ }
+ EXPECT_EQ(1, delete_count);
+
+ // Removal.
+ {
+ ServiceRegistry registry;
+ ServiceConnectorBase* c = new TestConnector("TC1", &delete_count);
+ registry.AddServiceConnector(c);
+ registry.RemoveServiceConnector(c);
+ EXPECT_EQ(2, delete_count);
+ }
+
+ // Multiple.
+ {
+ ServiceRegistry registry;
+ registry.AddServiceConnector(new TestConnector("TC1", &delete_count));
+ registry.AddServiceConnector(new TestConnector("TC2", &delete_count));
+ }
+ EXPECT_EQ(4, delete_count);
+
+ // Re-addition.
+ {
+ ServiceRegistry registry;
+ registry.AddServiceConnector(new TestConnector("TC1", &delete_count));
+ registry.AddServiceConnector(new TestConnector("TC1", &delete_count));
+ EXPECT_EQ(5, delete_count);
+ }
+ EXPECT_EQ(6, delete_count);
+}
+
+} // namespace
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
new file mode 100644
index 0000000..dad10d8
--- /dev/null
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -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.
+
+source_set("bindings") {
+ sources = [
+ "array.h",
+ "error_handler.h",
+ "interface_ptr.h",
+ "message.h",
+ "message_filter.h",
+ "no_interface.h",
+ "string.h",
+ "struct_ptr.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/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_header_validator.cc",
+ "lib/message_header_validator.h",
+ "lib/message_internal.h",
+ "lib/message_queue.cc",
+ "lib/message_queue.h",
+ "lib/no_interface.cc",
+ "lib/router.cc",
+ "lib/router.h",
+ "lib/string_serialization.cc",
+ "lib/string_serialization.h",
+ "lib/validation_errors.cc",
+ "lib/validation_errors.h",
+ ]
+
+ deps = [
+ ":callback",
+ "//mojo/public/cpp/environment",
+ "//mojo/public/cpp/system",
+ ]
+}
+
+source_set("callback") {
+ sources = [
+ "callback.h",
+ "lib/callback_internal.h",
+ "lib/template_util.h",
+ "lib/shared_data.h",
+ "lib/shared_ptr.h",
+ ]
+
+ deps = [ "//mojo/public/cpp/system" ]
+}
diff --git a/mojo/public/cpp/bindings/DEPS b/mojo/public/cpp/bindings/DEPS
new file mode 100644
index 0000000..2a0496e
--- /dev/null
+++ b/mojo/public/cpp/bindings/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/public/cpp/environment",
+]
diff --git a/mojo/public/cpp/bindings/array.h b/mojo/public/cpp/bindings/array.h
new file mode 100644
index 0000000..53c8c18
--- /dev/null
+++ b/mojo/public/cpp/bindings/array.h
@@ -0,0 +1,159 @@
+// 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>::Convert(other);
+ }
+
+ template <typename U>
+ U To() const {
+ return TypeConverter<U, Array>::Convert(*this);
+ }
+
+ void reset() {
+ if (!vec_.empty()) {
+ Traits::Finalize(&vec_);
+ vec_.clear();
+ }
+ is_null_ = true;
+ }
+
+ bool is_null() const { return is_null_; }
+
+ ConstRefType front() const { return vec_.front(); }
+ RefType front() { return vec_.front(); }
+
+ 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);
+ }
+
+ // Please note that calling this method will fail compilation if the element
+ // type cannot be cloned (which usually means that it is a Mojo handle type or
+ // a type contains Mojo handles).
+ Array Clone() const {
+ Array result;
+ result.is_null_ = is_null_;
+ Traits::Clone(vec_, &result.vec_);
+ return result.Pass();
+ }
+
+ 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>
+struct TypeConverter<Array<T>, std::vector<E> > {
+ static Array<T> Convert(const std::vector<E>& input) {
+ Array<T> result(input.size());
+ for (size_t i = 0; i < input.size(); ++i)
+ result[i] = TypeConverter<T, E>::Convert(input[i]);
+ return result.Pass();
+ }
+};
+
+template <typename E, typename T>
+struct TypeConverter<std::vector<E>, Array<T> > {
+ static std::vector<E> Convert(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<E, T>::Convert(input[i]);
+ }
+ return result;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_H_
diff --git a/mojo/public/cpp/bindings/callback.h b/mojo/public/cpp/bindings/callback.h
new file mode 100644
index 0000000..0cd4924
--- /dev/null
+++ b/mojo/public/cpp/bindings/callback.h
@@ -0,0 +1,522 @@
+// 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();
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ private:
+ template <typename Sink>
+ struct Adapter : public Runnable {
+ explicit Adapter(const Sink& sink) : sink(sink) {}
+ virtual void Run() const 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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));
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ 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 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_;
+};
+
+typedef Callback<void()> Closure;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_
diff --git a/mojo/public/cpp/bindings/callback.h.pump b/mojo/public/cpp/bindings/callback.h.pump
new file mode 100644
index 0000000..856e85b
--- /dev/null
+++ b/mojo/public/cpp/bindings/callback.h.pump
@@ -0,0 +1,95 @@
+$$ 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($if ARITY > 0 [[
+
+ ]]$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($if ARITY > 1 [[
+
+ ]]$for ARG ,
+ [[typename internal::Callback_ParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const {
+ if (sink_.get())
+ sink_->Run($if ARITY > 1 [[
+
+ ]]$for ARG ,
+ [[internal::Forward(a$(ARG))]]);
+ }
+
+ bool is_null() const {
+ return !sink_.get();
+ }
+
+ void reset() {
+ sink_.reset();
+ }
+
+ private:
+ template <typename Sink>
+ struct Adapter : public Runnable {
+ explicit Adapter(const Sink& sink) : sink(sink) {}
+ virtual void Run($if ARITY > 0 [[
+
+ ]]$for ARG ,
+ [[typename internal::Callback_ParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const override {
+ sink.Run($if ARITY > 1 [[
+
+ ]]$for ARG ,
+ [[internal::Forward(a$(ARG))]]);
+ }
+ Sink sink;
+ };
+
+ internal::SharedPtr<Runnable> sink_;
+};
+
+]] $$ for ARITY
+
+typedef Callback<void()> Closure;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_H_
diff --git a/mojo/public/cpp/bindings/error_handler.h b/mojo/public/cpp/bindings/error_handler.h
new file mode 100644
index 0000000..8ce1af2
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/interface_impl.h b/mojo/public/cpp/bindings/interface_impl.h
new file mode 100644
index 0000000..da1055d
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_impl.h
@@ -0,0 +1,152 @@
+// Copyright 2014 The Chromium 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;
+ typedef Interface ImplementedInterface;
+
+ 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(); }
+
+ // Blocks the current thread for the first incoming method call, i.e., either
+ // a call to a method or a client callback method. Returns |true| if a method
+ // has been called, |false| in case of error. It must only be called on a
+ // bound object.
+ bool WaitForIncomingMethodCall() {
+ return internal_state_.WaitForIncomingMethodCall();
+ }
+
+ // 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
+// and then the instance will be deleted.
+//
+// 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 the end
+// point of the pipe will be closed and the error handler's OnConnectionError
+// method will be called.
+//
+// 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(), true, waiter);
+ return instance;
+}
+
+// Like BindToPipe but does not delete the instance after a channel error.
+template <typename Impl>
+Impl* WeakBindToPipe(
+ Impl* instance,
+ ScopedMessagePipeHandle handle,
+ const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) {
+ instance->internal_state()->Bind(handle.Pass(), false, 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 and then the instance will be
+// deleted.
+//
+// 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, true, waiter);
+ return instance;
+}
+
+// Like BindToProxy but does not delete the instance after a channel error.
+template <typename Impl, typename Interface>
+Impl* WeakBindToProxy(
+ Impl* instance,
+ InterfacePtr<Interface>* ptr,
+ const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) {
+ instance->internal_state()->BindProxy(ptr, false, 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 and then the instance will be
+// deleted.
+//
+// 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);
+}
+
+// Like BindToRequest but does not delete the instance after a channel error.
+template <typename Impl, typename Interface>
+Impl* WeakBindToRequest(
+ Impl* instance,
+ InterfaceRequest<Interface>* request,
+ const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) {
+ return WeakBindToPipe(instance, request->PassMessagePipe(), waiter);
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_IMPL_H_
diff --git a/mojo/public/cpp/bindings/interface_ptr.h b/mojo/public/cpp/bindings/interface_ptr.h
new file mode 100644
index 0000000..8dfd465
--- /dev/null
+++ b/mojo/public/cpp/bindings/interface_ptr.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_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);
+ }
+
+ // Blocks the current thread for the first incoming method call, i.e., either
+ // a call to a client method or a callback method. Returns |true| if a method
+ // has been called, |false| in case of error. It must only be called on a
+ // bound object.
+ bool WaitForIncomingMethodCall() {
+ return internal_state_.WaitForIncomingMethodCall();
+ }
+
+ // 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 PassMessagePipe().
+ // 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_.Bind(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 {
+ return internal_state_.encountered_error();
+ }
+
+ // This method may be called to register an ErrorHandler to observe a
+ // connection error on the underlying pipe. It must only be called on a bound
+ // object.
+ // The callback runs asynchronously from the current message loop.
+ void set_error_handler(ErrorHandler* error_handler) {
+ internal_state_.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.PassMessagePipe();
+ }
+
+ // DO NOT USE. Exposed only for internal use and for testing.
+ internal::InterfacePtrState<Interface>* internal_state() {
+ return &internal_state_;
+ }
+
+ // Allow InterfacePtr<> to be used in boolean expressions, but not
+ // implicitly convertible to a real bool (which is dangerous).
+ private:
+ typedef internal::InterfacePtrState<Interface> InterfacePtr::*Testable;
+
+ public:
+ operator Testable() const {
+ return internal_state_.is_bound() ? &InterfacePtr::internal_state_
+ : nullptr;
+ }
+
+ private:
+ typedef internal::InterfacePtrState<Interface> State;
+ mutable 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/mojo/public/cpp/bindings/interface_request.h b/mojo/public/cpp/bindings/interface_request.h
new file mode 100644
index 0000000..6b7d303
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/DEPS b/mojo/public/cpp/bindings/lib/DEPS
new file mode 100644
index 0000000..b809b58
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/TODO b/mojo/public/cpp/bindings/lib/TODO
new file mode 100644
index 0000000..21bcb6f
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/array_internal.cc b/mojo/public/cpp/bindings/lib/array_internal.cc
new file mode 100644
index 0000000..9f62aa2
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/array_internal.cc
@@ -0,0 +1,75 @@
+// 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"
+
+#include <sstream>
+
+namespace mojo {
+namespace internal {
+
+std::string MakeMessageWithArrayIndex(const char* message,
+ size_t size,
+ size_t index) {
+ std::ostringstream stream;
+ stream << message << ": array size - " << size << "; index - " << index;
+ return stream.str();
+}
+
+std::string MakeMessageWithExpectedArraySize(const char* message,
+ size_t size,
+ size_t expected_size) {
+ std::ostringstream stream;
+ stream << message << ": array size - " << size << "; expected size - "
+ << expected_size;
+ return stream.str();
+}
+
+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);
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h
new file mode 100644
index 0000000..3681d8e
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/array_internal.h
@@ -0,0 +1,521 @@
+// 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/c/system/macros.h"
+#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/template_util.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+template <typename T> class Array;
+class String;
+
+namespace internal {
+
+// std::numeric_limits<uint32_t>::max() is not a compile-time constant (until
+// C++11).
+const uint32_t kMaxUint32 = 0xFFFFFFFF;
+
+std::string MakeMessageWithArrayIndex(const char* message,
+ size_t size,
+ size_t index);
+
+std::string MakeMessageWithExpectedArraySize(const char* message,
+ size_t size,
+ size_t expected_size);
+
+template <typename T>
+struct ArrayDataTraits {
+ typedef T StorageType;
+ typedef T& Ref;
+ typedef T const& ConstRef;
+
+ static const uint32_t kMaxNumElements =
+ (kMaxUint32 - sizeof(ArrayHeader)) / sizeof(StorageType);
+
+ static uint32_t GetStorageSize(uint32_t num_elements) {
+ MOJO_DCHECK(num_elements <= kMaxNumElements);
+ return sizeof(ArrayHeader) + 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 const uint32_t kMaxNumElements =
+ (kMaxUint32 - sizeof(ArrayHeader)) / sizeof(StorageType);
+
+ static uint32_t GetStorageSize(uint32_t num_elements) {
+ MOJO_DCHECK(num_elements <= kMaxNumElements);
+ return sizeof(ArrayHeader) + 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 const uint32_t kMaxNumElements =
+ (kMaxUint32 - sizeof(ArrayHeader)) / sizeof(StorageType);
+
+ static uint32_t GetStorageSize(uint32_t num_elements) {
+ MOJO_DCHECK(num_elements <= kMaxNumElements);
+ return sizeof(ArrayHeader) + 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_;
+ };
+
+ // Because each element consumes only 1/8 byte.
+ static const uint32_t kMaxNumElements = kMaxUint32;
+
+ typedef uint8_t StorageType;
+ typedef BitRef Ref;
+ typedef bool ConstRef;
+
+ static uint32_t GetStorageSize(uint32_t num_elements) {
+ return sizeof(ArrayHeader) + ((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;
+ }
+};
+
+// Array type information needed for valdiation.
+template <uint32_t in_expected_num_elements,
+ bool in_element_is_nullable,
+ typename InElementValidateParams>
+class ArrayValidateParams {
+ public:
+ // Validation information for elements. It is either another specialization of
+ // ArrayValidateParams (if elements are arrays) or NoValidateParams.
+ typedef InElementValidateParams ElementValidateParams;
+
+ // If |expected_num_elements| is not 0, the array is expected to have exactly
+ // that number of elements.
+ static const uint32_t expected_num_elements = in_expected_num_elements;
+ // Whether the elements are nullable.
+ static const bool element_is_nullable = in_element_is_nullable;
+};
+
+// NoValidateParams is used to indicate the end of an ArrayValidateParams chain.
+class NoValidateParams {
+};
+
+// 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 is_handle> 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) {
+ }
+
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ BoundsChecker* bounds_checker) {
+ MOJO_COMPILE_ASSERT(!element_is_nullable,
+ Primitive_type_should_be_non_nullable);
+ MOJO_COMPILE_ASSERT(
+ (IsSame<ElementValidateParams, NoValidateParams>::value),
+ Primitive_type_should_not_have_array_validate_params);
+ 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);
+
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ BoundsChecker* bounds_checker) {
+ MOJO_COMPILE_ASSERT(
+ (IsSame<ElementValidateParams, NoValidateParams>::value),
+ Handle_type_should_not_have_array_validate_params);
+
+ for (uint32_t i = 0; i < header->num_elements; ++i) {
+ if (!element_is_nullable &&
+ elements[i].value() == kEncodedInvalidHandleValue) {
+ ReportValidationError(
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ MakeMessageWithArrayIndex(
+ "invalid handle in array expecting valid handles",
+ header->num_elements, i).c_str());
+ return false;
+ }
+ if (!bounds_checker->ClaimHandle(elements[i])) {
+ ReportValidationError(VALIDATION_ERROR_ILLEGAL_HANDLE);
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+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);
+ }
+
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ BoundsChecker* bounds_checker) {
+ return ArraySerializationHelper<Handle, true>::
+ ValidateElements<element_is_nullable, ElementValidateParams>(
+ 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);
+ }
+
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static bool ValidateElements(const ArrayHeader* header,
+ const ElementType* elements,
+ BoundsChecker* bounds_checker) {
+ for (uint32_t i = 0; i < header->num_elements; ++i) {
+ if (!element_is_nullable && !elements[i].offset) {
+ ReportValidationError(
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex(
+ "null in array expecting valid pointers",
+ header->num_elements, i).c_str());
+ return false;
+ }
+ if (!ValidateEncodedPointer(&elements[i].offset)) {
+ ReportValidationError(VALIDATION_ERROR_ILLEGAL_POINTER);
+ return false;
+ }
+ if (!ValidateCaller<P, ElementValidateParams>::Run(
+ DecodePointerRaw(&elements[i].offset), bounds_checker)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ template <typename T, typename Params>
+ struct ValidateCaller {
+ static bool Run(const void* data, BoundsChecker* bounds_checker) {
+ MOJO_COMPILE_ASSERT(
+ (IsSame<Params, NoValidateParams>::value),
+ Struct_type_should_not_have_array_validate_params);
+
+ return T::Validate(data, bounds_checker);
+ }
+ };
+
+ template <typename T, typename Params>
+ struct ValidateCaller<Array_Data<T>, Params> {
+ static bool Run(const void* data, BoundsChecker* bounds_checker) {
+ return Array_Data<T>::template Validate<Params>(data, bounds_checker);
+ }
+ };
+};
+
+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;
+
+ // Returns null if |num_elements| or the corresponding storage size cannot be
+ // stored in uint32_t.
+ static Array_Data<T>* New(size_t num_elements, Buffer* buf) {
+ if (num_elements > Traits::kMaxNumElements)
+ return nullptr;
+
+ uint32_t num_bytes =
+ Traits::GetStorageSize(static_cast<uint32_t>(num_elements));
+ return new (buf->Allocate(num_bytes)) Array_Data<T>(
+ num_bytes, static_cast<uint32_t>(num_elements));
+ }
+
+ template <typename Params>
+ 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_elements > Traits::kMaxNumElements ||
+ header->num_bytes < Traits::GetStorageSize(header->num_elements)) {
+ ReportValidationError(VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER);
+ return false;
+ }
+ if (Params::expected_num_elements != 0 &&
+ header->num_elements != Params::expected_num_elements) {
+ ReportValidationError(
+ VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ MakeMessageWithExpectedArraySize(
+ "fixed-size array has wrong number of elements",
+ header->num_elements, Params::expected_num_elements).c_str());
+ 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::template ValidateElements<
+ Params::element_is_nullable, typename Params::ElementValidateParams>(
+ &object->header_, object->storage(), bounds_checker);
+ }
+
+ size_t size() const { return header_.num_elements; }
+
+ Ref at(size_t offset) {
+ MOJO_DCHECK(offset < static_cast<size_t>(header_.num_elements));
+ return Traits::ToRef(storage(), offset);
+ }
+
+ ConstRef at(size_t offset) const {
+ MOJO_DCHECK(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(uint32_t num_bytes, uint32_t num_elements) {
+ header_.num_bytes = num_bytes;
+ header_.num_elements = 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);
+ }
+ static inline void Clone(const std::vector<T>& src_vec,
+ std::vector<T>* dest_vec) {
+ dest_vec->assign(src_vec.begin(), src_vec.end());
+ }
+};
+
+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);
+ }
+ static inline void Clone(const std::vector<StorageType>& src_vec,
+ std::vector<StorageType>* dest_vec) {
+ Resize(dest_vec, src_vec.size());
+ for (size_t i = 0; i < src_vec.size(); ++i)
+ at(dest_vec, i) = at(&src_vec, i).Clone();
+ }
+};
+
+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/mojo/public/cpp/bindings/lib/array_serialization.h b/mojo/public/cpp/bindings/lib/array_serialization.h
new file mode 100644
index 0000000..4407de8
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/array_serialization.h
@@ -0,0 +1,262 @@
+// Copyright 2014 The Chromium 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.h> // For |memcpy()|.
+
+#include <vector>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/string_serialization.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+
+namespace mojo {
+
+template <typename E>
+inline size_t GetSerializedSize_(const Array<E>& input);
+
+// Because ValidateParams requires explicit argument specification, the
+// argument-dependent loopup technique used to omit namespace when calling
+// Serialize_() doesn't seem to work. Therefore, this function is named
+// differently from those Serialize_() overloads.
+template <typename ValidateParams, typename E, typename F>
+inline void SerializeArray_(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));
+ }
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static void SerializeElements(
+ Array<E> input, Buffer* buf, Array_Data<F>* output) {
+ MOJO_COMPILE_ASSERT(!element_is_nullable,
+ Primitive_type_should_be_non_nullable);
+ MOJO_COMPILE_ASSERT(
+ (IsSame<ElementValidateParams, NoValidateParams>::value),
+ Primitive_type_should_not_have_array_validate_params);
+
+ 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);
+ }
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static void SerializeElements(
+ Array<bool> input, Buffer* buf, Array_Data<bool>* output) {
+ MOJO_COMPILE_ASSERT(!element_is_nullable,
+ Primitive_type_should_be_non_nullable);
+ MOJO_COMPILE_ASSERT(
+ (IsSame<ElementValidateParams, NoValidateParams>::value),
+ Primitive_type_should_not_have_array_validate_params);
+
+ // 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));
+ }
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static void SerializeElements(Array<ScopedHandleBase<H> > input,
+ Buffer* buf,
+ Array_Data<H>* output) {
+ MOJO_COMPILE_ASSERT(
+ (IsSame<ElementValidateParams, NoValidateParams>::value),
+ Handle_type_should_not_have_array_validate_params);
+
+ for (size_t i = 0; i < input.size(); ++i) {
+ output->at(i) = input[i].release(); // Transfer ownership of the handle.
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !element_is_nullable && !output->at(i).is_valid(),
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ MakeMessageWithArrayIndex(
+ "invalid handle in array expecting valid handles",
+ input.size(), i));
+ }
+ }
+ 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;
+ }
+ template <bool element_is_nullable, typename ElementValidateParams>
+ 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;
+ SerializeCaller<S, ElementValidateParams>::Run(
+ input[i].Pass(), buf, &element);
+ output->at(i) = element;
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !element_is_nullable && !element,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex(
+ "null in array expecting valid pointers", input.size(), i));
+ }
+ }
+ 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);
+ }
+
+ private:
+ template <typename T, typename Params>
+ struct SerializeCaller {
+ static void Run(T input, Buffer* buf, typename T::Data_** output) {
+ MOJO_COMPILE_ASSERT((IsSame<Params, NoValidateParams>::value),
+ Struct_type_should_not_have_array_validate_params);
+
+ Serialize_(input.Pass(), buf, output);
+ }
+ };
+
+ template <typename T, typename Params>
+ struct SerializeCaller<Array<T>, Params> {
+ static void Run(Array<T> input,
+ Buffer* buf,
+ typename Array<T>::Data_** output) {
+ SerializeArray_<Params>(input.Pass(), buf, output);
+ }
+ };
+};
+
+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;
+ }
+ template <bool element_is_nullable, typename ElementValidateParams>
+ static void SerializeElements(
+ Array<String> input,
+ Buffer* buf,
+ Array_Data<String_Data*>* output) {
+ MOJO_COMPILE_ASSERT(
+ (IsSame<ElementValidateParams,
+ ArrayValidateParams<0, false, NoValidateParams> >::value),
+ String_type_has_unexpected_array_validate_params);
+
+ for (size_t i = 0; i < input.size(); ++i) {
+ String_Data* element;
+ Serialize_(input[i], buf, &element);
+ output->at(i) = element;
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !element_is_nullable && !element,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ MakeMessageWithArrayIndex(
+ "null in array expecting valid strings", input.size(), i));
+ }
+ }
+ 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 ValidateParams, typename E, typename F>
+inline void SerializeArray_(Array<E> input, internal::Buffer* buf,
+ internal::Array_Data<F>** output) {
+ if (input) {
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ ValidateParams::expected_num_elements != 0 &&
+ input.size() != ValidateParams::expected_num_elements,
+ internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ internal::MakeMessageWithExpectedArraySize(
+ "fixed-size array has wrong number of elements",
+ input.size(), ValidateParams::expected_num_elements));
+
+ internal::Array_Data<F>* result =
+ internal::Array_Data<F>::New(input.size(), buf);
+ if (result) {
+ internal::ArraySerializer<E, F>::template SerializeElements<
+ ValidateParams::element_is_nullable,
+ typename ValidateParams::ElementValidateParams>(
+ internal::Forward(input), buf, result);
+ }
+ *output = result;
+ } else {
+ *output = nullptr;
+ }
+}
+
+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/mojo/public/cpp/bindings/lib/bindings_internal.h b/mojo/public/cpp/bindings/lib/bindings_internal.h
new file mode 100644
index 0000000..f12be10
--- /dev/null
+++ b/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 {
+ enum { 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/mojo/public/cpp/bindings/lib/bindings_serialization.cc b/mojo/public/cpp/bindings/lib/bindings_serialization.cc
new file mode 100644
index 0000000..7161efe
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/bindings_serialization.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/lib/bindings_serialization.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"
+#include "mojo/public/cpp/environment/logging.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);
+ MOJO_DCHECK(p_obj > p_slot);
+
+ *offset = static_cast<uint64_t>(p_obj - p_slot);
+}
+
+const void* DecodePointerRaw(const uint64_t* offset) {
+ if (!*offset)
+ return nullptr;
+ 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;
+ }
+ MOJO_DCHECK(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) {
+ MOJO_DCHECK(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/mojo/public/cpp/bindings/lib/bindings_serialization.h b/mojo/public/cpp/bindings/lib/bindings_serialization.h
new file mode 100644
index 0000000..6bebf90
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/bounds_checker.cc b/mojo/public/cpp/bindings/lib/bounds_checker.cc
new file mode 100644
index 0000000..87f5b7c
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/bounds_checker.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/lib/bounds_checker.h"
+
+#include "mojo/public/cpp/bindings/lib/bindings_serialization.h"
+#include "mojo/public/cpp/environment/logging.h"
+#include "mojo/public/cpp/system/handle.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.
+ MOJO_DCHECK(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.
+ MOJO_DCHECK(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/mojo/public/cpp/bindings/lib/bounds_checker.h b/mojo/public/cpp/bindings/lib/bounds_checker.h
new file mode 100644
index 0000000..6c47230
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/buffer.h b/mojo/public/cpp/bindings/lib/buffer.h
new file mode 100644
index 0000000..c3b570e
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/callback_internal.h b/mojo/public/cpp/bindings/lib/callback_internal.h
new file mode 100644
index 0000000..f76ebef
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc
new file mode 100644
index 0000000..c0b70b8
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/connector.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/lib/connector.h"
+
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+namespace internal {
+
+// ----------------------------------------------------------------------------
+
+Connector::Connector(ScopedMessagePipeHandle message_pipe,
+ const MojoAsyncWaiter* waiter)
+ : error_handler_(nullptr),
+ waiter_(waiter),
+ message_pipe_(message_pipe.Pass()),
+ incoming_receiver_(nullptr),
+ async_wait_id_(0),
+ error_(false),
+ drop_writes_(false),
+ enforce_errors_from_incoming_receiver_(true),
+ destroyed_flag_(nullptr) {
+ // 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;
+
+ CancelWait();
+}
+
+void Connector::CloseMessagePipe() {
+ CancelWait();
+ Close(message_pipe_.Pass());
+}
+
+ScopedMessagePipeHandle Connector::PassMessagePipe() {
+ CancelWait();
+ return message_pipe_.Pass();
+}
+
+bool Connector::WaitForIncomingMessage() {
+ if (error_)
+ return false;
+
+ MojoResult rv = Wait(message_pipe_.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ if (rv != MOJO_RESULT_OK) {
+ NotifyError();
+ return false;
+ }
+ mojo_ignore_result(ReadSingleMessage(&rv));
+ return (rv == MOJO_RESULT_OK);
+}
+
+bool Connector::Accept(Message* message) {
+ MOJO_CHECK(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() ? nullptr :
+ 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;
+ case MOJO_RESULT_BUSY:
+ // We'd get a "busy" result if one of the message's handles is:
+ // - |message_pipe_|'s own handle;
+ // - simultaneously being used on another thread; or
+ // - in a "busy" state that prohibits it from being transferred (e.g.,
+ // a data pipe handle in the middle of a two-phase read/write,
+ // regardless of which thread that two-phase read/write is happening
+ // on).
+ // TODO(vtl): I wonder if this should be a |MOJO_DCHECK()|. (But, until
+ // crbug.com/389666, etc. are resolved, this will make tests fail quickly
+ // rather than hanging.)
+ MOJO_CHECK(false) << "Race condition or other bug detected";
+ return false;
+ 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) {
+ MOJO_CHECK(async_wait_id_ != 0);
+ async_wait_id_ = 0;
+ if (result != MOJO_RESULT_OK) {
+ NotifyError();
+ return;
+ }
+ ReadAllAvailableMessages();
+ // At this point, this object might have been deleted. Return.
+}
+
+void Connector::WaitToReadMore() {
+ MOJO_CHECK(!async_wait_id_);
+ async_wait_id_ = waiter_->AsyncWait(message_pipe_.get().value(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ &Connector::CallOnHandleReady,
+ this);
+}
+
+bool Connector::ReadSingleMessage(MojoResult* read_result) {
+ 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 (read_result)
+ *read_result = rv;
+
+ 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)
+ return true;
+
+ if (rv != MOJO_RESULT_OK ||
+ (enforce_errors_from_incoming_receiver_ && !receiver_result)) {
+ NotifyError();
+ return false;
+ }
+ return true;
+}
+
+void Connector::ReadAllAvailableMessages() {
+ while (!error_) {
+ MojoResult rv;
+
+ // Return immediately if |this| was destroyed. Do not touch any members!
+ if (!ReadSingleMessage(&rv))
+ return;
+
+ if (rv == MOJO_RESULT_SHOULD_WAIT) {
+ WaitToReadMore();
+ break;
+ }
+ }
+}
+
+void Connector::CancelWait() {
+ if (!async_wait_id_)
+ return;
+
+ waiter_->CancelWait(async_wait_id_);
+ async_wait_id_ = 0;
+}
+
+void Connector::NotifyError() {
+ error_ = true;
+ CancelWait();
+ if (error_handler_)
+ error_handler_->OnConnectionError();
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/connector.h b/mojo/public/cpp/bindings/lib/connector.h
new file mode 100644
index 0000000..dc598a6
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/connector.h
@@ -0,0 +1,113 @@
+// 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();
+
+ // Waits for the next message on the pipe, blocking until one arrives or an
+ // error happens. Returns |true| if a message has been delivered, |false|
+ // otherwise.
+ bool WaitForIncomingMessage();
+
+ // MessageReceiver implementation:
+ virtual bool Accept(Message* message) 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 ReadSingleMessage(MojoResult* read_result);
+
+ // |this| can be destroyed during message dispatch.
+ void ReadAllAvailableMessages();
+
+ void NotifyError();
+
+ // Cancels any calls made to |waiter_|.
+ void CancelWait();
+
+ 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/mojo/public/cpp/bindings/lib/filter_chain.cc b/mojo/public/cpp/bindings/lib/filter_chain.cc
new file mode 100644
index 0000000..6634562
--- /dev/null
+++ b/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 <algorithm>
+
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+namespace internal {
+
+FilterChain::FilterChain(MessageReceiver* sink) : sink_(sink) {
+}
+
+FilterChain::FilterChain(RValue other) : sink_(other.object->sink_) {
+ other.object->sink_ = nullptr;
+ 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) {
+ MOJO_DCHECK(!sink_);
+ sink_ = sink;
+ if (!filters_.empty())
+ filters_.back()->set_sink(sink);
+}
+
+MessageReceiver* FilterChain::GetHead() {
+ MOJO_DCHECK(sink_);
+ return filters_.empty() ? sink_ : filters_.front();
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/filter_chain.h b/mojo/public/cpp/bindings/lib/filter_chain.h
new file mode 100644
index 0000000..fc66642
--- /dev/null
+++ b/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 = nullptr);
+
+ // 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/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/mojo/public/cpp/bindings/lib/fixed_buffer.cc
new file mode 100644
index 0000000..9542ef8
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/fixed_buffer.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/bindings/lib/fixed_buffer.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+
+#include "mojo/public/cpp/bindings/lib/bindings_serialization.h"
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+namespace internal {
+
+FixedBuffer::FixedBuffer(size_t size)
+ : ptr_(nullptr),
+ 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_) {
+ MOJO_DCHECK(false) << "Not reached";
+ return nullptr;
+ }
+
+ char* result = ptr_ + cursor_;
+ cursor_ += delta;
+
+ return result;
+}
+
+void* FixedBuffer::Leak() {
+ char* ptr = ptr_;
+ ptr_ = nullptr;
+ cursor_ = 0;
+ size_ = 0;
+ return ptr;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.h b/mojo/public/cpp/bindings/lib/fixed_buffer.h
new file mode 100644
index 0000000..d23f664
--- /dev/null
+++ b/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) 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/mojo/public/cpp/bindings/lib/interface_impl_internal.h b/mojo/public/cpp/bindings/lib/interface_impl_internal.h
new file mode 100644
index 0000000..492b182
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/interface_impl_internal.h
@@ -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.
+
+#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/environment/logging.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_(nullptr),
+ proxy_(nullptr),
+ instance_bound_to_pipe_(false)
+#ifndef NDEBUG
+ ,
+ deleting_instance_due_to_error_(false)
+#endif
+ {
+ MOJO_DCHECK(instance);
+ stub_.set_sink(instance);
+ }
+
+ virtual ~InterfaceImplState() {
+#ifndef NDEBUG
+ MOJO_DCHECK(!instance_bound_to_pipe_ || deleting_instance_due_to_error_);
+#endif
+ delete proxy_;
+ if (router_) {
+ router_->set_error_handler(nullptr);
+ delete router_;
+ }
+ }
+
+ void BindProxy(
+ InterfacePtr<Interface>* ptr,
+ bool instance_bound_to_pipe,
+ const MojoAsyncWaiter* waiter = Environment::GetDefaultAsyncWaiter()) {
+ MessagePipe pipe;
+ ptr->Bind(pipe.handle0.Pass(), waiter);
+ Bind(pipe.handle1.Pass(), instance_bound_to_pipe, waiter);
+ }
+
+ void Bind(ScopedMessagePipeHandle handle,
+ bool instance_bound_to_pipe,
+ const MojoAsyncWaiter* waiter) {
+ MOJO_CHECK(!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_bound_to_pipe_ = instance_bound_to_pipe;
+
+ instance()->OnConnectionEstablished();
+ }
+
+ bool WaitForIncomingMethodCall() {
+ MOJO_DCHECK(router_);
+ return router_->WaitForIncomingMessage();
+ }
+
+ Router* router() { return router_; }
+ Client* client() { return proxy_; }
+
+ private:
+ InterfaceImplBase<Interface>* instance() {
+ return static_cast<InterfaceImplBase<Interface>*>(stub_.sink());
+ }
+
+ virtual void OnConnectionError() override {
+ // If the the instance is not bound to the pipe, the instance might choose
+ // to delete itself in the OnConnectionError handler, which would in turn
+ // delete this. Save the error behavior before invoking the error handler
+ // so we can correctly decide what to do.
+ bool bound = instance_bound_to_pipe_;
+ instance()->OnConnectionError();
+ if (!bound)
+ return;
+#ifndef NDEBUG
+ deleting_instance_due_to_error_ = true;
+#endif
+ delete instance();
+ }
+
+ Router* router_;
+ typename Client::Proxy_* proxy_;
+ typename Interface::Stub_ stub_;
+ bool instance_bound_to_pipe_;
+#ifndef NDEBUG
+ bool deleting_instance_due_to_error_;
+#endif
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(InterfaceImplState);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_IMPL_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_internal.h b/mojo/public/cpp/bindings/lib/interface_ptr_internal.h
new file mode 100644
index 0000000..77386fa
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_internal.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_
+
+#include <algorithm> // For |std::swap()|.
+
+#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/logging.h"
+
+struct MojoAsyncWaiter;
+
+namespace mojo {
+namespace internal {
+
+template <typename Interface>
+class InterfacePtrState {
+ public:
+ InterfacePtrState() : proxy_(nullptr), router_(nullptr), waiter_(nullptr) {}
+
+ ~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() {
+ ConfigureProxyIfNecessary();
+
+ // This will be null if the object is not bound.
+ return proxy_;
+ }
+
+ void Swap(InterfacePtrState* other) {
+ std::swap(other->proxy_, proxy_);
+ std::swap(other->router_, router_);
+ handle_.swap(other->handle_);
+ std::swap(other->waiter_, waiter_);
+ }
+
+ void Bind(ScopedMessagePipeHandle handle, const MojoAsyncWaiter* waiter) {
+ MOJO_DCHECK(!proxy_);
+ MOJO_DCHECK(!router_);
+ MOJO_DCHECK(!handle_.is_valid());
+ MOJO_DCHECK(!waiter_);
+
+ handle_ = handle.Pass();
+ waiter_ = waiter;
+ }
+
+ bool WaitForIncomingMethodCall() {
+ ConfigureProxyIfNecessary();
+
+ MOJO_DCHECK(router_);
+ return router_->WaitForIncomingMessage();
+ }
+
+ ScopedMessagePipeHandle PassMessagePipe() {
+ if (router_)
+ return router_->PassMessagePipe();
+
+ waiter_ = nullptr;
+ return handle_.Pass();
+ }
+
+ bool is_bound() const {
+ return handle_.is_valid() || router_;
+ }
+
+ void set_client(typename Interface::Client* client) {
+ ConfigureProxyIfNecessary();
+
+ MOJO_DCHECK(proxy_);
+ proxy_->stub.set_sink(client);
+ }
+
+ bool encountered_error() const {
+ return router_ ? router_->encountered_error() : false;
+ }
+
+ void set_error_handler(ErrorHandler* error_handler) {
+ ConfigureProxyIfNecessary();
+
+ MOJO_DCHECK(router_);
+ router_->set_error_handler(error_handler);
+ }
+
+ Router* router_for_testing() {
+ ConfigureProxyIfNecessary();
+ return router_;
+ }
+
+ 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);
+ };
+
+ void ConfigureProxyIfNecessary() {
+ // The proxy has been configured.
+ if (proxy_) {
+ MOJO_DCHECK(router_);
+ return;
+ }
+ // The object hasn't been bound.
+ if (!waiter_) {
+ MOJO_DCHECK(!handle_.is_valid());
+ return;
+ }
+
+ FilterChain filters;
+ filters.Append<MessageHeaderValidator>();
+ filters.Append<typename Interface::Client::RequestValidator_>();
+ filters.Append<typename Interface::ResponseValidator_>();
+
+ router_ = new Router(handle_.Pass(), filters.Pass(), waiter_);
+ waiter_ = nullptr;
+
+ ProxyWithStub* proxy = new ProxyWithStub(router_);
+ router_->set_incoming_receiver(&proxy->stub);
+
+ proxy_ = proxy;
+ }
+
+ ProxyWithStub* proxy_;
+ Router* router_;
+
+ // |proxy_| and |router_| are not initialized until read/write with the
+ // message pipe handle is needed. Before that, |handle_| and |waiter_| store
+ // the arguments of Bind().
+ ScopedMessagePipeHandle handle_;
+ const MojoAsyncWaiter* waiter_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(InterfacePtrState);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc
new file mode 100644
index 0000000..cec60c0
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message.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/public/cpp/bindings/message.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+
+Message::Message()
+ : data_num_bytes_(0),
+ data_(nullptr) {
+}
+
+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) {
+ MOJO_DCHECK(!data_);
+ data_num_bytes_ = num_bytes;
+ data_ = static_cast<internal::MessageData*>(malloc(num_bytes));
+}
+
+void Message::AdoptData(uint32_t num_bytes, internal::MessageData* data) {
+ MOJO_DCHECK(!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,
+ nullptr,
+ &num_bytes,
+ nullptr,
+ &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()
+ ? nullptr
+ : 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/mojo/public/cpp/bindings/lib/message_builder.cc b/mojo/public/cpp/bindings/lib/message_builder.cc
new file mode 100644
index 0000000..c746644
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/message_builder.h b/mojo/public/cpp/bindings/lib/message_builder.h
new file mode 100644
index 0000000..b4988ff
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/message_filter.cc b/mojo/public/cpp/bindings/lib/message_filter.cc
new file mode 100644
index 0000000..b09f40d
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/message_header_validator.cc b/mojo/public/cpp/bindings/lib/message_header_validator.cc
new file mode 100644
index 0000000..a55917a
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/message_header_validator.h b/mojo/public/cpp/bindings/lib/message_header_validator.h
new file mode 100644
index 0000000..790aa9b
--- /dev/null
+++ b/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 = nullptr);
+
+ virtual bool Accept(Message* message) override;
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_HEADER_VALIDATOR_H_
diff --git a/mojo/public/cpp/bindings/lib/message_internal.h b/mojo/public/cpp/bindings/lib/message_internal.h
new file mode 100644
index 0000000..3c67902
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/message_queue.cc b/mojo/public/cpp/bindings/lib/message_queue.cc
new file mode 100644
index 0000000..fd701e9
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_queue.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/lib/message_queue.h"
+
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+namespace internal {
+
+MessageQueue::MessageQueue() {
+}
+
+MessageQueue::~MessageQueue() {
+ while (!queue_.empty())
+ Pop();
+}
+
+bool MessageQueue::IsEmpty() const {
+ return queue_.empty();
+}
+
+Message* MessageQueue::Peek() {
+ MOJO_DCHECK(!queue_.empty());
+ return queue_.front();
+}
+
+void MessageQueue::Push(Message* message) {
+ queue_.push(new Message());
+ queue_.back()->Swap(message);
+}
+
+void MessageQueue::Pop(Message* message) {
+ MOJO_DCHECK(!queue_.empty());
+ queue_.front()->Swap(message);
+ Pop();
+}
+
+void MessageQueue::Pop() {
+ MOJO_DCHECK(!queue_.empty());
+ delete queue_.front();
+ queue_.pop();
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_queue.h b/mojo/public/cpp/bindings/lib/message_queue.h
new file mode 100644
index 0000000..4e46b54
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/no_interface.cc b/mojo/public/cpp/bindings/lib/no_interface.cc
new file mode 100644
index 0000000..9e0945c
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/router.cc b/mojo/public/cpp/bindings/lib/router.cc
new file mode 100644
index 0000000..3478840
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/router.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium 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"
+
+#include "mojo/public/cpp/environment/logging.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) override {
+ MOJO_DCHECK(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_(nullptr),
+ next_request_id_(0),
+ testing_mode_(false) {
+ filters_.SetSink(&thunk_);
+ connector_.set_incoming_receiver(filters_.GetHead());
+}
+
+Router::~Router() {
+ weak_self_.set_value(nullptr);
+
+ for (ResponderMap::const_iterator i = responders_.begin();
+ i != responders_.end(); ++i) {
+ delete i->second;
+ }
+}
+
+bool Router::Accept(Message* message) {
+ MOJO_DCHECK(!message->has_flag(kMessageExpectsResponse));
+ return connector_.Accept(message);
+}
+
+bool Router::AcceptWithResponder(Message* message,
+ MessageReceiver* responder) {
+ MOJO_DCHECK(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()) {
+ MOJO_DCHECK(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/mojo/public/cpp/bindings/lib/router.h b/mojo/public/cpp/bindings/lib/router.h
new file mode 100644
index 0000000..ebd4928
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/router.h
@@ -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.
+
+#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) override;
+ virtual bool AcceptWithResponder(Message* message,
+ MessageReceiver* responder) override;
+
+ // Blocks the current thread for the first incoming method call, i.e., either
+ // a call to a client method or a callback method.
+ bool WaitForIncomingMessage() {
+ return connector_.WaitForIncomingMessage();
+ }
+
+ // 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) 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/mojo/public/cpp/bindings/lib/shared_data.h b/mojo/public/cpp/bindings/lib/shared_data.h
new file mode 100644
index 0000000..c7bd54f
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/shared_ptr.h b/mojo/public/cpp/bindings/lib/shared_ptr.h
new file mode 100644
index 0000000..899e792
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/shared_ptr.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_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;
+ }
+
+ void reset() {
+ impl_.reset();
+ }
+
+ P* operator->() { return get(); }
+ const P* operator->() const { return get(); }
+
+ private:
+ class Impl {
+ public:
+ ~Impl() {
+ if (ptr)
+ delete ptr;
+ }
+
+ Impl() : ptr(nullptr) {
+ }
+
+ 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/mojo/public/cpp/bindings/lib/string_serialization.cc b/mojo/public/cpp/bindings/lib/string_serialization.cc
new file mode 100644
index 0000000..a0544c0
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/string_serialization.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/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);
+ if (result)
+ memcpy(result->storage(), input.data(), input.size());
+ *output = result;
+ } else {
+ *output = nullptr;
+ }
+}
+
+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/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h
new file mode 100644
index 0000000..bad2a0c
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/template_util.h b/mojo/public/cpp/bindings/lib/template_util.h
new file mode 100644
index 0000000..5991266
--- /dev/null
+++ b/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/mojo/public/cpp/bindings/lib/validation_errors.cc b/mojo/public/cpp/bindings/lib/validation_errors.cc
new file mode 100644
index 0000000..47bcf02
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_errors.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/cpp/bindings/lib/validation_errors.h"
+
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+namespace internal {
+namespace {
+
+ValidationErrorObserverForTesting* g_validation_error_observer = nullptr;
+SerializationWarningObserverForTesting* g_serialization_warning_observer =
+ nullptr;
+
+} // 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_UNEXPECTED_INVALID_HANDLE:
+ return "VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE";
+ case VALIDATION_ERROR_ILLEGAL_POINTER:
+ return "VALIDATION_ERROR_ILLEGAL_POINTER";
+ case VALIDATION_ERROR_UNEXPECTED_NULL_POINTER:
+ return "VALIDATION_ERROR_UNEXPECTED_NULL_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, const char* description) {
+ if (g_validation_error_observer) {
+ g_validation_error_observer->set_last_error(error);
+ } else if (description) {
+ MOJO_LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error)
+ << " (" << description << ")";
+ } else {
+ MOJO_LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error);
+ }
+}
+
+ValidationErrorObserverForTesting::ValidationErrorObserverForTesting()
+ : last_error_(VALIDATION_ERROR_NONE) {
+ MOJO_DCHECK(!g_validation_error_observer);
+ g_validation_error_observer = this;
+}
+
+ValidationErrorObserverForTesting::~ValidationErrorObserverForTesting() {
+ MOJO_DCHECK(g_validation_error_observer == this);
+ g_validation_error_observer = nullptr;
+}
+
+bool ReportSerializationWarning(ValidationError error) {
+ if (g_serialization_warning_observer) {
+ g_serialization_warning_observer->set_last_warning(error);
+ return true;
+ }
+
+ return false;
+}
+
+SerializationWarningObserverForTesting::SerializationWarningObserverForTesting()
+ : last_warning_(VALIDATION_ERROR_NONE) {
+ MOJO_DCHECK(!g_serialization_warning_observer);
+ g_serialization_warning_observer = this;
+}
+
+SerializationWarningObserverForTesting::
+~SerializationWarningObserverForTesting() {
+ MOJO_DCHECK(g_serialization_warning_observer == this);
+ g_serialization_warning_observer = nullptr;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/validation_errors.h b/mojo/public/cpp/bindings/lib/validation_errors.h
new file mode 100644
index 0000000..6152e60
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/validation_errors.h
@@ -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.
+
+#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.
+ // - For fixed-size arrays, |num_elements| is different than the specified
+ // size.
+ VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ // An encoded handle is illegal.
+ VALIDATION_ERROR_ILLEGAL_HANDLE,
+ // A non-nullable handle field is set to invalid handle.
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ // An encoded pointer is illegal.
+ VALIDATION_ERROR_ILLEGAL_POINTER,
+ // A non-nullable pointer field is set to null.
+ VALIDATION_ERROR_UNEXPECTED_NULL_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,
+ const char* description = nullptr);
+
+// 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);
+};
+
+// Used only by MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING. Don't use it directly.
+//
+// The function returns true if the error is recorded (by a
+// SerializationWarningObserverForTesting object), false otherwise.
+bool ReportSerializationWarning(ValidationError error);
+
+// Only used by serialization tests and when there is only one thread doing
+// message serialization.
+class SerializationWarningObserverForTesting {
+ public:
+ SerializationWarningObserverForTesting();
+ ~SerializationWarningObserverForTesting();
+
+ ValidationError last_warning() const { return last_warning_; }
+ void set_last_warning(ValidationError error) { last_warning_ = error; }
+
+ private:
+ ValidationError last_warning_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SerializationWarningObserverForTesting);
+};
+
+} // namespace internal
+} // namespace mojo
+
+// In debug build, logs a serialization warning if |condition| evaluates to
+// true:
+// - if there is a SerializationWarningObserverForTesting object alive,
+// records |error| in it;
+// - otherwise, logs a fatal-level message.
+// |error| is the validation error that will be triggered by the receiver
+// of the serialzation result.
+//
+// In non-debug build, does nothing (not even compiling |condition|).
+#define MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( \
+ condition, error, description) \
+ MOJO_DLOG_IF(FATAL, (condition) && !ReportSerializationWarning(error)) \
+ << "The outgoing message will trigger " \
+ << ValidationErrorToString(error) << " at the receiving side (" \
+ << description << ").";
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h
new file mode 100644
index 0000000..62801f1
--- /dev/null
+++ b/mojo/public/cpp/bindings/message.h
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium 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 <vector>
+
+#include "mojo/public/cpp/bindings/lib/message_internal.h"
+#include "mojo/public/cpp/environment/logging.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 {
+ MOJO_DCHECK(has_request_id());
+ return static_cast<const internal::MessageHeaderWithRequestID*>(
+ &data_->header)->request_id;
+ }
+ void set_request_id(uint64_t request_id) {
+ MOJO_DCHECK(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 {
+ MOJO_DCHECK(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/mojo/public/cpp/bindings/message_filter.h b/mojo/public/cpp/bindings/message_filter.h
new file mode 100644
index 0000000..f4faafb
--- /dev/null
+++ b/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 = nullptr);
+ 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 = nullptr);
+
+ virtual bool Accept(Message* message) override;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_FILTER_H_
diff --git a/mojo/public/cpp/bindings/no_interface.h b/mojo/public/cpp/bindings/no_interface.h
new file mode 100644
index 0000000..07587bd
--- /dev/null
+++ b/mojo/public/cpp/bindings/no_interface.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_CPP_BINDINGS_NO_INTERFACE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_NO_INTERFACE_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 nullptr; }
+ virtual bool Accept(Message* message) override;
+ virtual bool AcceptWithResponder(Message* message,
+ MessageReceiver* responder) 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/mojo/public/cpp/bindings/string.h b/mojo/public/cpp/bindings/string.h
new file mode 100644
index 0000000..cd7db26
--- /dev/null
+++ b/mojo/public/cpp/bindings/string.h
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium 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 <string>
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/type_converter.h"
+#include "mojo/public/cpp/environment/logging.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>::Convert(other);
+ }
+
+ template <typename U>
+ U To() const {
+ return TypeConverter<U, String>::Convert(*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); }
+
+inline std::ostream& operator<<(std::ostream& out, const String& s) {
+ return out << s.get();
+}
+
+// TODO(darin): Add similar variants of operator<,<=,>,>=
+
+template <>
+struct TypeConverter<String, std::string> {
+ static String Convert(const std::string& input) { return String(input); }
+};
+
+template <>
+struct TypeConverter<std::string, String> {
+ static std::string Convert(const String& input) { return input; }
+};
+
+template <size_t N>
+struct TypeConverter<String, char[N]> {
+ static String Convert(const char input[N]) {
+ MOJO_DCHECK(input);
+ return String(input, N-1);
+ }
+};
+
+// Appease MSVC.
+template <size_t N>
+struct TypeConverter<String, const char[N]> {
+ static String Convert(const char input[N]) {
+ MOJO_DCHECK(input);
+ return String(input, N-1);
+ }
+};
+
+template <>
+struct TypeConverter<String, const char*> {
+ // |input| may be null, in which case a null String will be returned.
+ static String Convert(const char* input) { return String(input); }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_H_
diff --git a/mojo/public/cpp/bindings/struct_ptr.h b/mojo/public/cpp/bindings/struct_ptr.h
new file mode 100644
index 0000000..46729dd
--- /dev/null
+++ b/mojo/public/cpp/bindings/struct_ptr.h
@@ -0,0 +1,167 @@
+// Copyright 2014 The Chromium 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 <new>
+
+#include "mojo/public/cpp/environment/logging.h"
+#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_(nullptr) {}
+ ~StructPtr() {
+ delete ptr_;
+ }
+
+ StructPtr(RValue other) : ptr_(nullptr) { Take(other.object); }
+ StructPtr& operator=(RValue other) {
+ Take(other.object);
+ return *this;
+ }
+
+ template <typename U>
+ U To() const {
+ return TypeConverter<U, StructPtr>::Convert(*this);
+ }
+
+ void reset() {
+ if (ptr_) {
+ delete ptr_;
+ ptr_ = nullptr;
+ }
+ }
+
+ bool is_null() const { return ptr_ == nullptr; }
+
+ Struct& operator*() const {
+ MOJO_DCHECK(ptr_);
+ return *ptr_;
+ }
+ Struct* operator->() const {
+ MOJO_DCHECK(ptr_);
+ return ptr_;
+ }
+ Struct* get() const { return ptr_; }
+
+ void Swap(StructPtr* other) {
+ std::swap(ptr_, other->ptr_);
+ }
+
+ // Please note that calling this method will fail compilation if the value
+ // type |Struct| doesn't have a Clone() method defined (which usually means
+ // that it contains Mojo handles).
+ StructPtr Clone() const {
+ return is_null() ? StructPtr() : ptr_->Clone();
+ }
+
+ private:
+ typedef Struct* StructPtr::*Testable;
+
+ public:
+ operator Testable() const { return ptr_ ? &StructPtr::ptr_ : 0; }
+
+ private:
+ friend class internal::StructHelper<Struct>;
+ void Initialize() {
+ MOJO_DCHECK(!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<U, InlinedStructPtr>::Convert(*this);
+ }
+
+ void reset() {
+ is_null_ = true;
+ value_.~Struct();
+ new (&value_) Struct();
+ }
+
+ bool is_null() const { return is_null_; }
+
+ Struct& operator*() const {
+ MOJO_DCHECK(!is_null_);
+ return value_;
+ }
+ Struct* operator->() const {
+ MOJO_DCHECK(!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_);
+ }
+
+ InlinedStructPtr Clone() const {
+ return is_null() ? InlinedStructPtr() : value_.Clone();
+ }
+
+ 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/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
new file mode 100644
index 0000000..6a4b2df
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_bindings_unittests
+test("mojo_public_bindings_unittests") {
+ sources = [
+ "array_unittest.cc",
+ "bounds_checker_unittest.cc",
+ "buffer_unittest.cc",
+ "connector_unittest.cc",
+ "handle_passing_unittest.cc",
+ "interface_ptr_unittest.cc",
+ "request_response_unittest.cc",
+ "router_unittest.cc",
+ "sample_service_unittest.cc",
+ "serialization_warning_unittest.cc",
+ "string_unittest.cc",
+ "struct_unittest.cc",
+ "type_conversion_unittest.cc",
+ "validation_unittest.cc",
+ ]
+
+ deps = [
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//testing/gtest",
+ ":mojo_public_bindings_test_utils",
+ ]
+}
+
+source_set("mojo_public_bindings_test_utils") {
+ sources = [
+ "validation_test_input_parser.cc",
+ "validation_test_input_parser.h",
+ ]
+
+ deps = [
+ "//mojo/public/c/system",
+ ]
+}
diff --git a/mojo/public/cpp/bindings/tests/DEPS b/mojo/public/cpp/bindings/tests/DEPS
new file mode 100644
index 0000000..b99d520
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+mojo/public/cpp/environment",
+ "+mojo/public/interfaces/bindings/tests",
+]
diff --git a/mojo/public/cpp/bindings/tests/array_unittest.cc b/mojo/public/cpp/bindings/tests/array_unittest.cc
new file mode 100644
index 0000000..82a705f
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/array_unittest.cc
@@ -0,0 +1,486 @@
+// Copyright 2014 The Chromium 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/array.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/array_serialization.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using mojo::internal::Array_Data;
+using mojo::internal::ArrayValidateParams;
+using mojo::internal::FixedBuffer;
+using mojo::internal::NoValidateParams;
+using mojo::internal::String_Data;
+
+class CopyableType {
+ public:
+ CopyableType() : copied_(false), ptr_(this) { num_instances_++; }
+ CopyableType(const CopyableType& other) : copied_(true), ptr_(other.ptr()) {
+ num_instances_++;
+ }
+ CopyableType& operator=(const CopyableType& other) {
+ copied_ = true;
+ ptr_ = other.ptr();
+ return *this;
+ }
+ ~CopyableType() { num_instances_--; }
+
+ bool copied() const { return copied_; }
+ static size_t num_instances() { return num_instances_; }
+ CopyableType* ptr() const { return ptr_; }
+ void ResetCopied() { copied_ = false; }
+
+ private:
+ bool copied_;
+ static size_t num_instances_;
+ CopyableType* ptr_;
+};
+
+size_t CopyableType::num_instances_ = 0;
+
+class MoveOnlyType {
+ MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(MoveOnlyType, RValue)
+ public:
+ typedef MoveOnlyType Data_;
+ MoveOnlyType() : moved_(false), ptr_(this) { num_instances_++; }
+ MoveOnlyType(RValue other) : moved_(true), ptr_(other.object->ptr()) {
+ num_instances_++;
+ }
+ MoveOnlyType& operator=(RValue other) {
+ moved_ = true;
+ ptr_ = other.object->ptr();
+ return *this;
+ }
+ ~MoveOnlyType() { num_instances_--; }
+
+ bool moved() const { return moved_; }
+ static size_t num_instances() { return num_instances_; }
+ MoveOnlyType* ptr() const { return ptr_; }
+ void ResetMoved() { moved_ = false; }
+
+ private:
+ bool moved_;
+ static size_t num_instances_;
+ MoveOnlyType* ptr_;
+};
+
+size_t MoveOnlyType::num_instances_ = 0;
+
+class ArrayTest : public testing::Test {
+ public:
+ virtual ~ArrayTest() {}
+
+ private:
+ Environment env_;
+};
+
+// Tests that basic Array operations work.
+TEST_F(ArrayTest, Basic) {
+ Array<char> array(8);
+ for (size_t i = 0; i < array.size(); ++i) {
+ char val = static_cast<char>(i*2);
+ array[i] = val;
+ EXPECT_EQ(val, array.at(i));
+ }
+}
+
+// Tests that basic Array<bool> operations work.
+TEST_F(ArrayTest, Bool) {
+ Array<bool> array(64);
+ for (size_t i = 0; i < array.size(); ++i) {
+ bool val = i % 3 == 0;
+ array[i] = val;
+ EXPECT_EQ(val, array.at(i));
+ }
+}
+
+// Tests that Array<ScopedMessagePipeHandle> supports transferring handles.
+TEST_F(ArrayTest, Handle) {
+ MessagePipe pipe;
+ Array<ScopedMessagePipeHandle> handles(2);
+ handles[0] = pipe.handle0.Pass();
+ handles[1].reset(pipe.handle1.release());
+
+ EXPECT_FALSE(pipe.handle0.is_valid());
+ EXPECT_FALSE(pipe.handle1.is_valid());
+
+ Array<ScopedMessagePipeHandle> handles2 = handles.Pass();
+ EXPECT_TRUE(handles2[0].is_valid());
+ EXPECT_TRUE(handles2[1].is_valid());
+
+ ScopedMessagePipeHandle pipe_handle = handles2[0].Pass();
+ EXPECT_TRUE(pipe_handle.is_valid());
+ EXPECT_FALSE(handles2[0].is_valid());
+}
+
+// Tests that Array<ScopedMessagePipeHandle> supports closing handles.
+TEST_F(ArrayTest, HandlesAreClosed) {
+ MessagePipe pipe;
+ MojoHandle pipe0_value = pipe.handle0.get().value();
+ MojoHandle pipe1_value = pipe.handle0.get().value();
+
+ {
+ Array<ScopedMessagePipeHandle> handles(2);
+ handles[0] = pipe.handle0.Pass();
+ handles[1].reset(pipe.handle0.release());
+ }
+
+ // We expect the pipes to have been closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(pipe0_value));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(pipe1_value));
+}
+
+TEST_F(ArrayTest, Clone) {
+ {
+ // Test POD.
+ Array<int32_t> array(3);
+ for (size_t i = 0; i < array.size(); ++i)
+ array[i] = static_cast<int32_t>(i);
+
+ Array<int32_t> clone_array = array.Clone();
+ EXPECT_EQ(array.size(), clone_array.size());
+ for (size_t i = 0; i < array.size(); ++i)
+ EXPECT_EQ(array[i], clone_array[i]);
+ }
+
+ {
+ // Test copyable object.
+ Array<String> array(2);
+ array[0] = "hello";
+ array[1] = "world";
+
+ Array<String> clone_array = array.Clone();
+ EXPECT_EQ(array.size(), clone_array.size());
+ for (size_t i = 0; i < array.size(); ++i)
+ EXPECT_EQ(array[i], clone_array[i]);
+ }
+
+ {
+ // Test struct.
+ Array<RectPtr> array(2);
+ array[1] = Rect::New();
+ array[1]->x = 1;
+ array[1]->y = 2;
+ array[1]->width = 3;
+ array[1]->height = 4;
+
+ Array<RectPtr> clone_array = array.Clone();
+ EXPECT_EQ(array.size(), clone_array.size());
+ EXPECT_TRUE(clone_array[0].is_null());
+ EXPECT_EQ(array[1]->x, clone_array[1]->x);
+ EXPECT_EQ(array[1]->y, clone_array[1]->y);
+ EXPECT_EQ(array[1]->width, clone_array[1]->width);
+ EXPECT_EQ(array[1]->height, clone_array[1]->height);
+ }
+
+ {
+ // Test array of array.
+ Array<Array<int8_t>> array(2);
+ array[1] = Array<int8_t>(2);
+ array[1][0] = 0;
+ array[1][1] = 1;
+
+ Array<Array<int8_t>> clone_array = array.Clone();
+ EXPECT_EQ(array.size(), clone_array.size());
+ EXPECT_TRUE(clone_array[0].is_null());
+ EXPECT_EQ(array[1].size(), clone_array[1].size());
+ EXPECT_EQ(array[1][0], clone_array[1][0]);
+ EXPECT_EQ(array[1][1], clone_array[1][1]);
+ }
+
+ {
+ // Test that array of handles still works although Clone() is not available.
+ Array<ScopedMessagePipeHandle> array(10);
+ EXPECT_FALSE(array[0].is_valid());
+ }
+}
+
+TEST_F(ArrayTest, Serialization_ArrayOfPOD) {
+ Array<int32_t> array(4);
+ for (size_t i = 0; i < array.size(); ++i)
+ array[i] = static_cast<int32_t>(i);
+
+ size_t size = GetSerializedSize_(array);
+ EXPECT_EQ(8U + 4*4U, size);
+
+ FixedBuffer buf(size);
+ Array_Data<int32_t>* data;
+ SerializeArray_<ArrayValidateParams<0, false, NoValidateParams>>(
+ array.Pass(), &buf, &data);
+
+ Array<int32_t> array2;
+ Deserialize_(data, &array2);
+
+ EXPECT_EQ(4U, array2.size());
+ for (size_t i = 0; i < array2.size(); ++i)
+ EXPECT_EQ(static_cast<int32_t>(i), array2[i]);
+}
+
+TEST_F(ArrayTest, Serialization_ArrayOfArrayOfPOD) {
+ Array<Array<int32_t>> array(2);
+ for (size_t j = 0; j < array.size(); ++j) {
+ Array<int32_t> inner(4);
+ for (size_t i = 0; i < inner.size(); ++i)
+ inner[i] = static_cast<int32_t>(i + (j * 10));
+ array[j] = inner.Pass();
+ }
+
+ size_t size = GetSerializedSize_(array);
+ EXPECT_EQ(8U + 2*8U + 2*(8U + 4*4U), size);
+
+ FixedBuffer buf(size);
+ Array_Data<Array_Data<int32_t>*>* data;
+ SerializeArray_<ArrayValidateParams<0, false,
+ ArrayValidateParams<0, false,
+ NoValidateParams>>>(
+ array.Pass(), &buf, &data);
+
+ Array<Array<int32_t>> array2;
+ Deserialize_(data, &array2);
+
+ EXPECT_EQ(2U, array2.size());
+ for (size_t j = 0; j < array2.size(); ++j) {
+ const Array<int32_t>& inner = array2[j];
+ EXPECT_EQ(4U, inner.size());
+ for (size_t i = 0; i < inner.size(); ++i)
+ EXPECT_EQ(static_cast<int32_t>(i + (j * 10)), inner[i]);
+ }
+}
+
+TEST_F(ArrayTest, Serialization_ArrayOfBool) {
+ Array<bool> array(10);
+ for (size_t i = 0; i < array.size(); ++i)
+ array[i] = i % 2 ? true : false;
+
+ size_t size = GetSerializedSize_(array);
+ EXPECT_EQ(8U + 8U, size);
+
+ FixedBuffer buf(size);
+ Array_Data<bool>* data;
+ SerializeArray_<ArrayValidateParams<0, false, NoValidateParams>>(
+ array.Pass(), &buf, &data);
+
+ Array<bool> array2;
+ Deserialize_(data, &array2);
+
+ EXPECT_EQ(10U, array2.size());
+ for (size_t i = 0; i < array2.size(); ++i)
+ EXPECT_EQ(i % 2 ? true : false, array2[i]);
+}
+
+TEST_F(ArrayTest, Serialization_ArrayOfString) {
+ Array<String> array(10);
+ for (size_t i = 0; i < array.size(); ++i) {
+ char c = 'A' + static_cast<char>(i);
+ array[i] = String(&c, 1);
+ }
+
+ size_t size = GetSerializedSize_(array);
+ EXPECT_EQ(8U + // array header
+ 10*8U + // array payload (10 pointers)
+ 10*(8U + // string header
+ 8U), // string length of 1 padded to 8
+ size);
+
+ FixedBuffer buf(size);
+ Array_Data<String_Data*>* data;
+ SerializeArray_<ArrayValidateParams<0, false,
+ ArrayValidateParams<0, false,
+ NoValidateParams>>>(
+ array.Pass(), &buf, &data);
+
+ Array<String> array2;
+ Deserialize_(data, &array2);
+
+ EXPECT_EQ(10U, array2.size());
+ for (size_t i = 0; i < array2.size(); ++i) {
+ char c = 'A' + static_cast<char>(i);
+ EXPECT_EQ(String(&c, 1), array2[i]);
+ }
+}
+
+TEST_F(ArrayTest, Resize_Copyable) {
+ ASSERT_EQ(0u, CopyableType::num_instances());
+ mojo::Array<CopyableType> array(3);
+ std::vector<CopyableType*> value_ptrs;
+ value_ptrs.push_back(array[0].ptr());
+ value_ptrs.push_back(array[1].ptr());
+
+ for (size_t i = 0; i < array.size(); i++)
+ array[i].ResetCopied();
+
+ array.resize(2);
+ ASSERT_EQ(2u, array.size());
+ EXPECT_EQ(array.size(), CopyableType::num_instances());
+ for (size_t i = 0; i < array.size(); i++) {
+ EXPECT_FALSE(array[i].copied());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ }
+
+ array.resize(3);
+ array[2].ResetCopied();
+ ASSERT_EQ(3u, array.size());
+ EXPECT_EQ(array.size(), CopyableType::num_instances());
+ for (size_t i = 0; i < array.size(); i++)
+ EXPECT_FALSE(array[i].copied());
+ value_ptrs.push_back(array[2].ptr());
+
+ size_t capacity = array.storage().capacity();
+ array.resize(capacity);
+ ASSERT_EQ(capacity, array.size());
+ EXPECT_EQ(array.size(), CopyableType::num_instances());
+ for (size_t i = 0; i < 3; i++)
+ EXPECT_FALSE(array[i].copied());
+ for (size_t i = 3; i < array.size(); i++) {
+ array[i].ResetCopied();
+ value_ptrs.push_back(array[i].ptr());
+ }
+
+ array.resize(capacity + 2);
+ ASSERT_EQ(capacity + 2, array.size());
+ EXPECT_EQ(array.size(), CopyableType::num_instances());
+ for (size_t i = 0; i < capacity; i++) {
+ EXPECT_TRUE(array[i].copied());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ }
+ array.reset();
+ EXPECT_EQ(0u, CopyableType::num_instances());
+ EXPECT_FALSE(array);
+ array.resize(0);
+ EXPECT_EQ(0u, CopyableType::num_instances());
+ EXPECT_TRUE(array);
+}
+
+TEST_F(ArrayTest, Resize_MoveOnly) {
+ ASSERT_EQ(0u, MoveOnlyType::num_instances());
+ mojo::Array<MoveOnlyType> array(3);
+ std::vector<MoveOnlyType*> value_ptrs;
+ value_ptrs.push_back(array[0].ptr());
+ value_ptrs.push_back(array[1].ptr());
+
+ for (size_t i = 0; i < array.size(); i++)
+ EXPECT_FALSE(array[i].moved());
+
+ array.resize(2);
+ ASSERT_EQ(2u, array.size());
+ EXPECT_EQ(array.size(), MoveOnlyType::num_instances());
+ for (size_t i = 0; i < array.size(); i++) {
+ EXPECT_FALSE(array[i].moved());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ }
+
+ array.resize(3);
+ ASSERT_EQ(3u, array.size());
+ EXPECT_EQ(array.size(), MoveOnlyType::num_instances());
+ for (size_t i = 0; i < array.size(); i++)
+ EXPECT_FALSE(array[i].moved());
+ value_ptrs.push_back(array[2].ptr());
+
+ size_t capacity = array.storage().capacity();
+ array.resize(capacity);
+ ASSERT_EQ(capacity, array.size());
+ EXPECT_EQ(array.size(), MoveOnlyType::num_instances());
+ for (size_t i = 0; i < array.size(); i++)
+ EXPECT_FALSE(array[i].moved());
+ for (size_t i = 3; i < array.size(); i++)
+ value_ptrs.push_back(array[i].ptr());
+
+ array.resize(capacity + 2);
+ ASSERT_EQ(capacity + 2, array.size());
+ EXPECT_EQ(array.size(), MoveOnlyType::num_instances());
+ for (size_t i = 0; i < capacity; i++) {
+ EXPECT_TRUE(array[i].moved());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ }
+ for (size_t i = capacity; i < array.size(); i++)
+ EXPECT_FALSE(array[i].moved());
+
+ array.reset();
+ EXPECT_EQ(0u, MoveOnlyType::num_instances());
+ EXPECT_FALSE(array);
+ array.resize(0);
+ EXPECT_EQ(0u, MoveOnlyType::num_instances());
+ EXPECT_TRUE(array);
+}
+
+TEST_F(ArrayTest, PushBack_Copyable) {
+ ASSERT_EQ(0u, CopyableType::num_instances());
+ mojo::Array<CopyableType> array(2);
+ array.reset();
+ std::vector<CopyableType*> value_ptrs;
+ size_t capacity = array.storage().capacity();
+ for (size_t i = 0; i < capacity; i++) {
+ CopyableType value;
+ value_ptrs.push_back(value.ptr());
+ array.push_back(value);
+ ASSERT_EQ(i + 1, array.size());
+ ASSERT_EQ(i + 1, value_ptrs.size());
+ EXPECT_EQ(array.size() + 1, CopyableType::num_instances());
+ EXPECT_TRUE(array[i].copied());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ array[i].ResetCopied();
+ EXPECT_TRUE(array);
+ }
+ {
+ CopyableType value;
+ value_ptrs.push_back(value.ptr());
+ array.push_back(value);
+ EXPECT_EQ(array.size() + 1, CopyableType::num_instances());
+ }
+ ASSERT_EQ(capacity + 1, array.size());
+ EXPECT_EQ(array.size(), CopyableType::num_instances());
+
+ for (size_t i = 0; i < array.size(); i++) {
+ EXPECT_TRUE(array[i].copied());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ }
+ array.reset();
+ EXPECT_EQ(0u, CopyableType::num_instances());
+}
+
+TEST_F(ArrayTest, PushBack_MoveOnly) {
+ ASSERT_EQ(0u, MoveOnlyType::num_instances());
+ mojo::Array<MoveOnlyType> array(2);
+ array.reset();
+ std::vector<MoveOnlyType*> value_ptrs;
+ size_t capacity = array.storage().capacity();
+ for (size_t i = 0; i < capacity; i++) {
+ MoveOnlyType value;
+ value_ptrs.push_back(value.ptr());
+ array.push_back(value.Pass());
+ ASSERT_EQ(i + 1, array.size());
+ ASSERT_EQ(i + 1, value_ptrs.size());
+ EXPECT_EQ(array.size() + 1, MoveOnlyType::num_instances());
+ EXPECT_TRUE(array[i].moved());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ array[i].ResetMoved();
+ EXPECT_TRUE(array);
+ }
+ {
+ MoveOnlyType value;
+ value_ptrs.push_back(value.ptr());
+ array.push_back(value.Pass());
+ EXPECT_EQ(array.size() + 1, MoveOnlyType::num_instances());
+ }
+ ASSERT_EQ(capacity + 1, array.size());
+ EXPECT_EQ(array.size(), MoveOnlyType::num_instances());
+
+ for (size_t i = 0; i < array.size(); i++) {
+ EXPECT_TRUE(array[i].moved());
+ EXPECT_EQ(value_ptrs[i], array[i].ptr());
+ }
+ array.reset();
+ EXPECT_EQ(0u, MoveOnlyType::num_instances());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/bounds_checker_unittest.cc b/mojo/public/cpp/bindings/tests/bounds_checker_unittest.cc
new file mode 100644
index 0000000..8378cdd
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/bounds_checker_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium 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 <limits>
+
+#include "mojo/public/cpp/bindings/lib/bindings_serialization.h"
+#include "mojo/public/cpp/bindings/lib/bounds_checker.h"
+#include "mojo/public/cpp/system/core.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+const void* ToPtr(uintptr_t ptr) {
+ return reinterpret_cast<const void*>(ptr);
+}
+
+#ifdef NDEBUG
+TEST(BoundsCheckerTest, ConstructorRangeOverflow) {
+ {
+ // Test memory range overflow.
+ internal::BoundsChecker
+ checker(ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 5000, 0);
+
+ EXPECT_FALSE(checker.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1));
+ EXPECT_FALSE(checker.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1));
+ }
+
+ if (sizeof(size_t) > sizeof(uint32_t)) {
+ // Test handle index range overflow.
+ size_t num_handles =
+ static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 5;
+ internal::BoundsChecker checker(ToPtr(0), 0, num_handles);
+
+ EXPECT_FALSE(checker.ClaimHandle(Handle(0)));
+ EXPECT_FALSE(
+ checker.ClaimHandle(Handle(std::numeric_limits<uint32_t>::max() - 1)));
+
+ EXPECT_TRUE(
+ checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue)));
+ }
+}
+#endif
+
+TEST(BoundsCheckerTest, IsValidRange) {
+ {
+ internal::BoundsChecker checker(ToPtr(1234), 100, 0);
+
+ // Basics.
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(100), 5));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1230), 50));
+ EXPECT_TRUE(checker.IsValidRange(ToPtr(1234), 5));
+ EXPECT_TRUE(checker.IsValidRange(ToPtr(1240), 50));
+ EXPECT_TRUE(checker.IsValidRange(ToPtr(1234), 100));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 101));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1240), 100));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1333), 5));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(2234), 5));
+
+ // ClaimMemory() updates the valid range.
+ EXPECT_TRUE(checker.ClaimMemory(ToPtr(1254), 10));
+
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 1));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1254), 10));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1263), 1));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1263), 10));
+ EXPECT_TRUE(checker.IsValidRange(ToPtr(1264), 10));
+ EXPECT_TRUE(checker.IsValidRange(ToPtr(1264), 70));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1264), 71));
+ }
+
+ {
+ internal::BoundsChecker checker(ToPtr(1234), 100, 0);
+ // Should return false for empty ranges.
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(0), 0));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1200), 0));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 0));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1240), 0));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(2234), 0));
+ }
+
+ {
+ // The valid memory range is empty.
+ internal::BoundsChecker checker(ToPtr(1234), 0, 0);
+
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 1));
+ EXPECT_FALSE(checker.IsValidRange(ToPtr(1234), 0));
+ }
+
+ {
+ internal::BoundsChecker
+ checker(ToPtr(std::numeric_limits<uintptr_t>::max() - 2000), 1000, 0);
+
+ // Test overflow.
+ EXPECT_FALSE(checker.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 4000));
+ EXPECT_FALSE(checker.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500),
+ std::numeric_limits<uint32_t>::max()));
+
+ // This should be fine.
+ EXPECT_TRUE(checker.IsValidRange(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 200));
+ }
+}
+
+TEST(BoundsCheckerTest, ClaimHandle) {
+ {
+ internal::BoundsChecker checker(ToPtr(0), 0, 10);
+
+ // Basics.
+ EXPECT_TRUE(checker.ClaimHandle(Handle(0)));
+ EXPECT_FALSE(checker.ClaimHandle(Handle(0)));
+
+ EXPECT_TRUE(checker.ClaimHandle(Handle(9)));
+ EXPECT_FALSE(checker.ClaimHandle(Handle(10)));
+
+ // Should fail because it is smaller than the max index that has been
+ // claimed.
+ EXPECT_FALSE(checker.ClaimHandle(Handle(8)));
+
+ // Should return true for invalid handle.
+ EXPECT_TRUE(
+ checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue)));
+ EXPECT_TRUE(
+ checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ // No handle to claim.
+ internal::BoundsChecker checker(ToPtr(0), 0, 0);
+
+ EXPECT_FALSE(checker.ClaimHandle(Handle(0)));
+
+ // Should still return true for invalid handle.
+ EXPECT_TRUE(
+ checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue)));
+ }
+
+ {
+ // Test the case that |num_handles| is the same value as
+ // |internal::kEncodedInvalidHandleValue|.
+ EXPECT_EQ(internal::kEncodedInvalidHandleValue,
+ std::numeric_limits<uint32_t>::max());
+ internal::BoundsChecker checker(ToPtr(0), 0,
+ std::numeric_limits<uint32_t>::max());
+
+ EXPECT_TRUE(
+ checker.ClaimHandle(Handle(std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(
+ checker.ClaimHandle(Handle(std::numeric_limits<uint32_t>::max() - 1)));
+ EXPECT_FALSE(checker.ClaimHandle(Handle(0)));
+
+ // Should still return true for invalid handle.
+ EXPECT_TRUE(
+ checker.ClaimHandle(Handle(internal::kEncodedInvalidHandleValue)));
+ }
+}
+
+TEST(BoundsCheckerTest, ClaimMemory) {
+ {
+ internal::BoundsChecker checker(ToPtr(1000), 2000, 0);
+
+ // Basics.
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(500), 100));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(800), 300));
+ EXPECT_TRUE(checker.ClaimMemory(ToPtr(1000), 100));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(1099), 100));
+ EXPECT_TRUE(checker.ClaimMemory(ToPtr(1100), 200));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(2000), 1001));
+ EXPECT_TRUE(checker.ClaimMemory(ToPtr(2000), 500));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(2000), 500));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(1400), 100));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(3000), 1));
+ EXPECT_TRUE(checker.ClaimMemory(ToPtr(2500), 500));
+ }
+
+ {
+ // No memory to claim.
+ internal::BoundsChecker checker(ToPtr(10000), 0, 0);
+
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(10000), 1));
+ EXPECT_FALSE(checker.ClaimMemory(ToPtr(10000), 0));
+ }
+
+ {
+ internal::BoundsChecker
+ checker(ToPtr(std::numeric_limits<uintptr_t>::max() - 1000), 500, 0);
+
+ // Test overflow.
+ EXPECT_FALSE(checker.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 4000));
+ EXPECT_FALSE(checker.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 750),
+ std::numeric_limits<uint32_t>::max()));
+
+ // This should be fine.
+ EXPECT_TRUE(checker.ClaimMemory(
+ ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 200));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/buffer_unittest.cc b/mojo/public/cpp/bindings/tests/buffer_unittest.cc
new file mode 100644
index 0000000..61424c0
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/buffer_unittest.cc
@@ -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.
+
+#include <limits>
+
+#include "mojo/public/cpp/bindings/lib/bindings_serialization.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+bool IsZero(void* p_buf, size_t size) {
+ char* buf = reinterpret_cast<char*>(p_buf);
+ for (size_t i = 0; i < size; ++i) {
+ if (buf[i] != 0)
+ return false;
+ }
+ return true;
+}
+
+// Tests that FixedBuffer allocates memory aligned to 8 byte boundaries.
+TEST(FixedBufferTest, Alignment) {
+ internal::FixedBuffer buf(internal::Align(10) * 2);
+ ASSERT_EQ(buf.size(), 16u * 2);
+
+ void* a = buf.Allocate(10);
+ ASSERT_TRUE(a);
+ EXPECT_TRUE(IsZero(a, 10));
+ EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(a) % 8);
+
+ void* b = buf.Allocate(10);
+ ASSERT_TRUE(b);
+ EXPECT_TRUE(IsZero(b, 10));
+ EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(b) % 8);
+
+ // Any more allocations would result in an assert, but we can't test that.
+}
+
+// Tests that FixedBuffer::Leak passes ownership to the caller.
+TEST(FixedBufferTest, Leak) {
+ void* ptr = nullptr;
+ void* buf_ptr = nullptr;
+ {
+ internal::FixedBuffer buf(8);
+ ASSERT_EQ(8u, buf.size());
+
+ ptr = buf.Allocate(8);
+ ASSERT_TRUE(ptr);
+ buf_ptr = buf.Leak();
+
+ // The buffer should point to the first element allocated.
+ // TODO(mpcomplete): Is this a reasonable expectation?
+ EXPECT_EQ(ptr, buf_ptr);
+
+ // The FixedBuffer should be empty now.
+ EXPECT_EQ(0u, buf.size());
+ EXPECT_FALSE(buf.Leak());
+ }
+
+ // Since we called Leak, ptr is still writable after FixedBuffer went out of
+ // scope.
+ memset(ptr, 1, 8);
+ free(buf_ptr);
+}
+
+#ifdef NDEBUG
+TEST(FixedBufferTest, TooBig) {
+ internal::FixedBuffer buf(24);
+
+ // A little bit too large.
+ EXPECT_EQ(reinterpret_cast<void*>(0), buf.Allocate(32));
+
+ // Move the cursor forward.
+ EXPECT_NE(reinterpret_cast<void*>(0), buf.Allocate(16));
+
+ // A lot too large.
+ EXPECT_EQ(reinterpret_cast<void*>(0),
+ buf.Allocate(std::numeric_limits<size_t>::max() - 1024u));
+
+ // A lot too large, leading to possible integer overflow.
+ EXPECT_EQ(reinterpret_cast<void*>(0),
+ buf.Allocate(std::numeric_limits<size_t>::max() - 8u));
+}
+#endif
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/connector_unittest.cc b/mojo/public/cpp/bindings/tests/connector_unittest.cc
new file mode 100644
index 0000000..317f4c8
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/connector_unittest.cc
@@ -0,0 +1,402 @@
+// 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 <stdlib.h>
+#include <string.h>
+
+#include "mojo/public/cpp/bindings/lib/connector.h"
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/message_queue.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class MessageAccumulator : public MessageReceiver {
+ public:
+ MessageAccumulator() {
+ }
+
+ virtual bool Accept(Message* message) override {
+ queue_.Push(message);
+ return true;
+ }
+
+ bool IsEmpty() const {
+ return queue_.IsEmpty();
+ }
+
+ void Pop(Message* message) {
+ queue_.Pop(message);
+ }
+
+ private:
+ internal::MessageQueue queue_;
+};
+
+class ConnectorDeletingMessageAccumulator : public MessageAccumulator {
+ public:
+ ConnectorDeletingMessageAccumulator(internal::Connector** connector)
+ : connector_(connector) {}
+
+ virtual bool Accept(Message* message) override {
+ delete *connector_;
+ *connector_ = 0;
+ return MessageAccumulator::Accept(message);
+ }
+
+ private:
+ internal::Connector** connector_;
+};
+
+class ReentrantMessageAccumulator : public MessageAccumulator {
+ public:
+ ReentrantMessageAccumulator(internal::Connector* connector)
+ : connector_(connector), number_of_calls_(0) {}
+
+ virtual bool Accept(Message* message) override {
+ if (!MessageAccumulator::Accept(message))
+ return false;
+ number_of_calls_++;
+ if (number_of_calls_ == 1) {
+ return connector_->WaitForIncomingMessage();
+ }
+ return true;
+ }
+
+ int number_of_calls() { return number_of_calls_; }
+
+ private:
+ internal::Connector* connector_;
+ int number_of_calls_;
+};
+
+class ConnectorTest : public testing::Test {
+ public:
+ ConnectorTest() {
+ }
+
+ virtual void SetUp() override {
+ CreateMessagePipe(nullptr, &handle0_, &handle1_);
+ }
+
+ virtual void TearDown() override {}
+
+ void AllocMessage(const char* text, Message* message) {
+ size_t payload_size = strlen(text) + 1; // Plus null terminator.
+ internal::MessageBuilder builder(1, payload_size);
+ memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
+ builder.Finish(message);
+ }
+
+ void PumpMessages() {
+ loop_.RunUntilIdle();
+ }
+
+ protected:
+ ScopedMessagePipeHandle handle0_;
+ ScopedMessagePipeHandle handle1_;
+
+ private:
+ Environment env_;
+ RunLoop loop_;
+};
+
+TEST_F(ConnectorTest, Basic) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ PumpMessages();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, Basic_Synchronous) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ connector1.WaitForIncomingMessage();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, Basic_EarlyIncomingReceiver) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ PumpMessages();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, Basic_TwoMessages) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ const char* kText[] = { "hello", "world" };
+
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) {
+ Message message;
+ AllocMessage(kText[i], &message);
+
+ connector0.Accept(&message);
+ }
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ PumpMessages();
+
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) {
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText[i]),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+ }
+}
+
+TEST_F(ConnectorTest, Basic_TwoMessages_Synchronous) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ const char* kText[] = { "hello", "world" };
+
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) {
+ Message message;
+ AllocMessage(kText[i], &message);
+
+ connector0.Accept(&message);
+ }
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ connector1.WaitForIncomingMessage();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText[0]),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+
+ ASSERT_TRUE(accumulator.IsEmpty());
+}
+
+TEST_F(ConnectorTest, WriteToClosedPipe) {
+ internal::Connector connector0(handle0_.Pass());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ // Close the other end of the pipe.
+ handle1_.reset();
+
+ // Not observed yet because we haven't spun the RunLoop yet.
+ EXPECT_FALSE(connector0.encountered_error());
+
+ // Write failures are not reported.
+ bool ok = connector0.Accept(&message);
+ EXPECT_TRUE(ok);
+
+ // Still not observed.
+ EXPECT_FALSE(connector0.encountered_error());
+
+ // Spin the RunLoop, and then we should start observing the closed pipe.
+ PumpMessages();
+
+ EXPECT_TRUE(connector0.encountered_error());
+}
+
+TEST_F(ConnectorTest, MessageWithHandles) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ const char kText[] = "hello world";
+
+ Message message1;
+ AllocMessage(kText, &message1);
+
+ MessagePipe pipe;
+ message1.mutable_handles()->push_back(pipe.handle0.release());
+
+ connector0.Accept(&message1);
+
+ // The message should have been transferred, releasing the handles.
+ EXPECT_TRUE(message1.handles()->empty());
+
+ MessageAccumulator accumulator;
+ connector1.set_incoming_receiver(&accumulator);
+
+ PumpMessages();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+ ASSERT_EQ(1U, message_received.handles()->size());
+
+ // Now send a message to the transferred handle and confirm it's sent through
+ // to the orginal pipe.
+ // TODO(vtl): Do we need a better way of "downcasting" the handle types?
+ ScopedMessagePipeHandle smph;
+ smph.reset(MessagePipeHandle(message_received.handles()->front().value()));
+ message_received.mutable_handles()->front() = Handle();
+ // |smph| now owns this handle.
+
+ internal::Connector connector_received(smph.Pass());
+ internal::Connector connector_original(pipe.handle1.Pass());
+
+ Message message2;
+ AllocMessage(kText, &message2);
+
+ connector_received.Accept(&message2);
+ connector_original.set_incoming_receiver(&accumulator);
+ PumpMessages();
+
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, WaitForIncomingMessageWithError) {
+ internal::Connector connector0(handle0_.Pass());
+ // Close the other end of the pipe.
+ handle1_.reset();
+ ASSERT_FALSE(connector0.WaitForIncomingMessage());
+}
+
+TEST_F(ConnectorTest, WaitForIncomingMessageWithDeletion) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector* connector1 = new internal::Connector(handle1_.Pass());
+
+ const char kText[] = "hello world";
+
+ Message message;
+ AllocMessage(kText, &message);
+
+ connector0.Accept(&message);
+
+ ConnectorDeletingMessageAccumulator accumulator(&connector1);
+ connector1->set_incoming_receiver(&accumulator);
+
+ connector1->WaitForIncomingMessage();
+
+ ASSERT_FALSE(connector1);
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+}
+
+TEST_F(ConnectorTest, WaitForIncomingMessageWithReentrancy) {
+ internal::Connector connector0(handle0_.Pass());
+ internal::Connector connector1(handle1_.Pass());
+
+ const char* kText[] = { "hello", "world" };
+
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) {
+ Message message;
+ AllocMessage(kText[i], &message);
+
+ connector0.Accept(&message);
+ }
+
+ ReentrantMessageAccumulator accumulator(&connector1);
+ connector1.set_incoming_receiver(&accumulator);
+
+ PumpMessages();
+
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(kText); ++i) {
+ ASSERT_FALSE(accumulator.IsEmpty());
+
+ Message message_received;
+ accumulator.Pop(&message_received);
+
+ EXPECT_EQ(
+ std::string(kText[i]),
+ std::string(reinterpret_cast<const char*>(message_received.payload())));
+ }
+
+ ASSERT_EQ(2, accumulator.number_of_calls());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
new file mode 100644
index 0000000..d4cffb4
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
@@ -0,0 +1,350 @@
+// 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/environment/environment.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+const char kText1[] = "hello";
+const char kText2[] = "world";
+
+class StringRecorder {
+ public:
+ explicit StringRecorder(std::string* buf) : buf_(buf) {
+ }
+ void Run(const String& a) const {
+ *buf_ = a.To<std::string>();
+ }
+ private:
+ std::string* buf_;
+};
+
+class ImportedInterfaceImpl
+ : public InterfaceImpl<imported::ImportedInterface> {
+ public:
+ virtual void DoSomething() override { do_something_count_++; }
+
+ static int do_something_count() { return do_something_count_; }
+
+ private:
+ static int do_something_count_;
+};
+int ImportedInterfaceImpl::do_something_count_ = 0;
+
+class SampleNamedObjectImpl : public InterfaceImpl<sample::NamedObject> {
+ public:
+ virtual void SetName(const mojo::String& name) override { name_ = name; }
+
+ virtual void GetName(
+ const mojo::Callback<void(mojo::String)>& callback) override {
+ callback.Run(name_);
+ }
+
+ private:
+ std::string name_;
+};
+
+class SampleFactoryImpl : public InterfaceImpl<sample::Factory> {
+ public:
+ virtual void DoStuff(sample::RequestPtr request,
+ ScopedMessagePipeHandle pipe) override {
+ std::string text1;
+ if (pipe.is_valid())
+ EXPECT_TRUE(ReadTextMessage(pipe.get(), &text1));
+
+ std::string text2;
+ if (request->pipe.is_valid()) {
+ EXPECT_TRUE(ReadTextMessage(request->pipe.get(), &text2));
+
+ // Ensure that simply accessing request->pipe does not close it.
+ EXPECT_TRUE(request->pipe.is_valid());
+ }
+
+ ScopedMessagePipeHandle pipe0;
+ if (!text2.empty()) {
+ CreateMessagePipe(nullptr, &pipe0, &pipe1_);
+ EXPECT_TRUE(WriteTextMessage(pipe1_.get(), text2));
+ }
+
+ sample::ResponsePtr response(sample::Response::New());
+ response->x = 2;
+ response->pipe = pipe0.Pass();
+ client()->DidStuff(response.Pass(), text1);
+
+ if (request->obj)
+ request->obj->DoSomething();
+ }
+
+ virtual void DoStuff2(ScopedDataPipeConsumerHandle pipe) override {
+ // Read the data from the pipe, writing the response (as a string) to
+ // DidStuff2().
+ ASSERT_TRUE(pipe.is_valid());
+ uint32_t data_size = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadDataRaw(pipe.get(), nullptr, &data_size,
+ MOJO_READ_DATA_FLAG_QUERY));
+ ASSERT_NE(0, static_cast<int>(data_size));
+ char data[64];
+ ASSERT_LT(static_cast<int>(data_size), 64);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadDataRaw(pipe.get(), data, &data_size,
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+
+ client()->DidStuff2(data);
+ }
+
+ virtual void CreateNamedObject(
+ InterfaceRequest<sample::NamedObject> object_request) override {
+ EXPECT_TRUE(object_request.is_pending());
+ BindToRequest(new SampleNamedObjectImpl(), &object_request);
+ }
+
+ // These aren't called or implemented, but exist here to test that the
+ // methods are generated with the correct argument types for imported
+ // interfaces.
+ virtual void RequestImportedInterface(
+ InterfaceRequest<imported::ImportedInterface> imported,
+ const mojo::Callback<void(InterfaceRequest<imported::ImportedInterface>)>&
+ callback) override {}
+ virtual void TakeImportedInterface(
+ imported::ImportedInterfacePtr imported,
+ const mojo::Callback<void(imported::ImportedInterfacePtr)>& callback)
+ override {}
+
+ private:
+ ScopedMessagePipeHandle pipe1_;
+};
+
+class SampleFactoryClientImpl : public sample::FactoryClient {
+ public:
+ SampleFactoryClientImpl() : got_response_(false) {
+ }
+
+ void set_expected_text_reply(const std::string& expected_text_reply) {
+ expected_text_reply_ = expected_text_reply;
+ }
+
+ bool got_response() const {
+ return got_response_;
+ }
+
+ virtual void DidStuff(sample::ResponsePtr response,
+ const String& text_reply) override {
+ EXPECT_EQ(expected_text_reply_, text_reply);
+
+ if (response->pipe.is_valid()) {
+ std::string text2;
+ EXPECT_TRUE(ReadTextMessage(response->pipe.get(), &text2));
+
+ // Ensure that simply accessing response.pipe does not close it.
+ EXPECT_TRUE(response->pipe.is_valid());
+
+ EXPECT_EQ(std::string(kText2), text2);
+
+ // Do some more tests of handle passing:
+ ScopedMessagePipeHandle p = response->pipe.Pass();
+ EXPECT_TRUE(p.is_valid());
+ EXPECT_FALSE(response->pipe.is_valid());
+ }
+
+ got_response_ = true;
+ }
+
+ virtual void DidStuff2(const String& text_reply) override {
+ got_response_ = true;
+ EXPECT_EQ(expected_text_reply_, text_reply);
+ }
+
+ private:
+ ScopedMessagePipeHandle pipe1_;
+ ScopedMessagePipeHandle pipe3_;
+ std::string expected_text_reply_;
+ bool got_response_;
+};
+
+class HandlePassingTest : public testing::Test {
+ public:
+ virtual void TearDown() {
+ PumpMessages();
+ }
+
+ void PumpMessages() {
+ loop_.RunUntilIdle();
+ }
+
+ private:
+ Environment env_;
+ RunLoop loop_;
+};
+
+TEST_F(HandlePassingTest, Basic) {
+ sample::FactoryPtr factory;
+ BindToProxy(new SampleFactoryImpl(), &factory);
+
+ SampleFactoryClientImpl factory_client;
+ factory_client.set_expected_text_reply(kText1);
+
+ factory.set_client(&factory_client);
+
+ MessagePipe pipe0;
+ EXPECT_TRUE(WriteTextMessage(pipe0.handle1.get(), kText1));
+
+ MessagePipe pipe1;
+ EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2));
+
+ imported::ImportedInterfacePtr imported;
+ BindToProxy(new ImportedInterfaceImpl(), &imported);
+
+ sample::RequestPtr request(sample::Request::New());
+ request->x = 1;
+ request->pipe = pipe1.handle0.Pass();
+ request->obj = imported.Pass();
+ factory->DoStuff(request.Pass(), pipe0.handle0.Pass());
+
+ EXPECT_FALSE(factory_client.got_response());
+ int count_before = ImportedInterfaceImpl::do_something_count();
+
+ PumpMessages();
+
+ EXPECT_TRUE(factory_client.got_response());
+ EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before);
+}
+
+TEST_F(HandlePassingTest, PassInvalid) {
+ sample::FactoryPtr factory;
+ BindToProxy(new SampleFactoryImpl(), &factory);
+
+ SampleFactoryClientImpl factory_client;
+ factory.set_client(&factory_client);
+
+ sample::RequestPtr request(sample::Request::New());
+ request->x = 1;
+ factory->DoStuff(request.Pass(), ScopedMessagePipeHandle().Pass());
+
+ EXPECT_FALSE(factory_client.got_response());
+
+ PumpMessages();
+
+ EXPECT_TRUE(factory_client.got_response());
+}
+
+// Verifies DataPipeConsumer can be passed and read from.
+TEST_F(HandlePassingTest, DataPipe) {
+ sample::FactoryPtr factory;
+ BindToProxy(new SampleFactoryImpl(), &factory);
+
+ SampleFactoryClientImpl factory_client;
+ factory.set_client(&factory_client);
+
+ // Writes a string to a data pipe and passes the data pipe (consumer) to the
+ // factory.
+ ScopedDataPipeProducerHandle producer_handle;
+ ScopedDataPipeConsumerHandle consumer_handle;
+ MojoCreateDataPipeOptions options = {
+ sizeof(MojoCreateDataPipeOptions),
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ 1,
+ 1024};
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateDataPipe(&options, &producer_handle, &consumer_handle));
+ std::string expected_text_reply = "got it";
+ factory_client.set_expected_text_reply(expected_text_reply);
+ // +1 for \0.
+ uint32_t data_size = static_cast<uint32_t>(expected_text_reply.size() + 1);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ WriteDataRaw(producer_handle.get(), expected_text_reply.c_str(),
+ &data_size, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+
+ factory->DoStuff2(consumer_handle.Pass());
+
+ EXPECT_FALSE(factory_client.got_response());
+
+ PumpMessages();
+
+ EXPECT_TRUE(factory_client.got_response());
+}
+
+TEST_F(HandlePassingTest, PipesAreClosed) {
+ sample::FactoryPtr factory;
+ BindToProxy(new SampleFactoryImpl(), &factory);
+
+ SampleFactoryClientImpl factory_client;
+ factory.set_client(&factory_client);
+
+ MessagePipe extra_pipe;
+
+ MojoHandle handle0_value = extra_pipe.handle0.get().value();
+ MojoHandle handle1_value = extra_pipe.handle1.get().value();
+
+ {
+ Array<ScopedMessagePipeHandle> pipes(2);
+ pipes[0] = extra_pipe.handle0.Pass();
+ pipes[1] = extra_pipe.handle1.Pass();
+
+ sample::RequestPtr request(sample::Request::New());
+ request->more_pipes = pipes.Pass();
+
+ factory->DoStuff(request.Pass(), ScopedMessagePipeHandle());
+ }
+
+ // We expect the pipes to have been closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle0_value));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle1_value));
+}
+
+TEST_F(HandlePassingTest, IsHandle) {
+ // Validate that mojo::internal::IsHandle<> works as expected since this.
+ // template is key to ensuring that we don't leak handles.
+ EXPECT_TRUE(internal::IsHandle<Handle>::value);
+ EXPECT_TRUE(internal::IsHandle<MessagePipeHandle>::value);
+ EXPECT_TRUE(internal::IsHandle<DataPipeConsumerHandle>::value);
+ EXPECT_TRUE(internal::IsHandle<DataPipeProducerHandle>::value);
+ EXPECT_TRUE(internal::IsHandle<SharedBufferHandle>::value);
+
+ // Basic sanity checks...
+ EXPECT_FALSE(internal::IsHandle<int>::value);
+ EXPECT_FALSE(internal::IsHandle<sample::FactoryPtr>::value);
+ EXPECT_FALSE(internal::IsHandle<String>::value);
+}
+
+TEST_F(HandlePassingTest, CreateNamedObject) {
+ sample::FactoryPtr factory;
+ BindToProxy(new SampleFactoryImpl(), &factory);
+
+ sample::NamedObjectPtr object1;
+ EXPECT_FALSE(object1);
+
+ InterfaceRequest<sample::NamedObject> object1_request = Get(&object1);
+ EXPECT_TRUE(object1_request.is_pending());
+ factory->CreateNamedObject(object1_request.Pass());
+ EXPECT_FALSE(object1_request.is_pending()); // We've passed the request.
+
+ ASSERT_TRUE(object1);
+ object1->SetName("object1");
+
+ sample::NamedObjectPtr object2;
+ factory->CreateNamedObject(Get(&object2));
+ object2->SetName("object2");
+
+ std::string name1;
+ object1->GetName(StringRecorder(&name1));
+
+ std::string name2;
+ object2->GetName(StringRecorder(&name2));
+
+ PumpMessages(); // Yield for results.
+
+ EXPECT_EQ(std::string("object1"), name1);
+ EXPECT_EQ(std::string("object2"), name2);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
new file mode 100644
index 0000000..ded6d47
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
@@ -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.
+
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class ErrorObserver : public ErrorHandler {
+ public:
+ ErrorObserver() : encountered_error_(false) {
+ }
+
+ bool encountered_error() const { return encountered_error_; }
+
+ virtual void OnConnectionError() override { encountered_error_ = true; }
+
+ private:
+ bool encountered_error_;
+};
+
+class MathCalculatorImpl : public InterfaceImpl<math::Calculator> {
+ public:
+ virtual ~MathCalculatorImpl() {}
+
+ MathCalculatorImpl()
+ : total_(0.0),
+ got_connection_(false) {
+ }
+
+ virtual void OnConnectionEstablished() override { got_connection_ = true; }
+
+ virtual void Clear() override { client()->Output(total_); }
+
+ virtual void Add(double value) override {
+ total_ += value;
+ client()->Output(total_);
+ }
+
+ virtual void Multiply(double value) override {
+ total_ *= value;
+ client()->Output(total_);
+ }
+
+ bool got_connection() const {
+ return got_connection_;
+ }
+
+ private:
+ double total_;
+ bool got_connection_;
+};
+
+class MathCalculatorUIImpl : public math::CalculatorUI {
+ public:
+ explicit MathCalculatorUIImpl(math::CalculatorPtr calculator)
+ : calculator_(calculator.Pass()),
+ output_(0.0) {
+ calculator_.set_client(this);
+ }
+
+ bool WaitForIncomingMethodCall() {
+ return calculator_.WaitForIncomingMethodCall();
+ }
+
+ bool encountered_error() const {
+ return calculator_.encountered_error();
+ }
+
+ void Add(double value) {
+ calculator_->Add(value);
+ }
+
+ void Subtract(double value) {
+ calculator_->Add(-value);
+ }
+
+ void Multiply(double value) {
+ calculator_->Multiply(value);
+ }
+
+ void Divide(double value) {
+ calculator_->Multiply(1.0 / value);
+ }
+
+ double GetOutput() const {
+ return output_;
+ }
+
+ private:
+ // math::CalculatorUI implementation:
+ virtual void Output(double value) override { output_ = value; }
+
+ math::CalculatorPtr calculator_;
+ double output_;
+};
+
+class SelfDestructingMathCalculatorUIImpl : public math::CalculatorUI {
+ public:
+ explicit SelfDestructingMathCalculatorUIImpl(math::CalculatorPtr calculator)
+ : calculator_(calculator.Pass()),
+ nesting_level_(0) {
+ ++num_instances_;
+ calculator_.set_client(this);
+ }
+
+ void BeginTest(bool nested) {
+ nesting_level_ = nested ? 2 : 1;
+ calculator_->Add(1.0);
+ }
+
+ static int num_instances() { return num_instances_; }
+
+ private:
+ virtual ~SelfDestructingMathCalculatorUIImpl() {
+ --num_instances_;
+ }
+
+ virtual void Output(double value) override {
+ if (--nesting_level_ > 0) {
+ // Add some more and wait for re-entrant call to Output!
+ calculator_->Add(1.0);
+ RunLoop::current()->RunUntilIdle();
+ } else {
+ delete this;
+ }
+ }
+
+ math::CalculatorPtr calculator_;
+ int nesting_level_;
+ static int num_instances_;
+};
+
+// static
+int SelfDestructingMathCalculatorUIImpl::num_instances_ = 0;
+
+class ReentrantServiceImpl : public InterfaceImpl<sample::Service> {
+ public:
+ virtual ~ReentrantServiceImpl() {}
+
+ ReentrantServiceImpl()
+ : got_connection_(false), call_depth_(0), max_call_depth_(0) {}
+
+ virtual void OnConnectionEstablished() override { got_connection_ = true; }
+
+ bool got_connection() const {
+ return got_connection_;
+ }
+
+ int max_call_depth() { return max_call_depth_; }
+
+ virtual void Frobinate(sample::FooPtr foo,
+ sample::Service::BazOptions baz,
+ sample::PortPtr port) override {
+ max_call_depth_ = std::max(++call_depth_, max_call_depth_);
+ if (call_depth_ == 1) {
+ EXPECT_TRUE(WaitForIncomingMethodCall());
+ }
+ call_depth_--;
+ }
+
+ virtual void GetPort(mojo::InterfaceRequest<sample::Port> port) override {}
+
+ private:
+ bool got_connection_;
+ int call_depth_;
+ int max_call_depth_;
+};
+
+class InterfacePtrTest : public testing::Test {
+ public:
+ virtual ~InterfacePtrTest() {
+ loop_.RunUntilIdle();
+ }
+
+ void PumpMessages() {
+ loop_.RunUntilIdle();
+ }
+
+ private:
+ Environment env_;
+ RunLoop loop_;
+};
+
+TEST_F(InterfacePtrTest, EndToEnd) {
+ math::CalculatorPtr calc;
+ MathCalculatorImpl* impl = BindToProxy(new MathCalculatorImpl(), &calc);
+ EXPECT_TRUE(impl->got_connection());
+
+ // Suppose this is instantiated in a process that has pipe1_.
+ MathCalculatorUIImpl calculator_ui(calc.Pass());
+
+ calculator_ui.Add(2.0);
+ calculator_ui.Multiply(5.0);
+
+ PumpMessages();
+
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+}
+
+TEST_F(InterfacePtrTest, EndToEnd_Synchronous) {
+ math::CalculatorPtr calc;
+ MathCalculatorImpl* impl = BindToProxy(new MathCalculatorImpl(), &calc);
+ EXPECT_TRUE(impl->got_connection());
+
+ // Suppose this is instantiated in a process that has pipe1_.
+ MathCalculatorUIImpl calculator_ui(calc.Pass());
+
+ EXPECT_EQ(0.0, calculator_ui.GetOutput());
+
+ calculator_ui.Add(2.0);
+ EXPECT_EQ(0.0, calculator_ui.GetOutput());
+ impl->WaitForIncomingMethodCall();
+ calculator_ui.WaitForIncomingMethodCall();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+
+ calculator_ui.Multiply(5.0);
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+ impl->WaitForIncomingMethodCall();
+ calculator_ui.WaitForIncomingMethodCall();
+ EXPECT_EQ(10.0, calculator_ui.GetOutput());
+}
+
+TEST_F(InterfacePtrTest, Movable) {
+ math::CalculatorPtr a;
+ math::CalculatorPtr b;
+ BindToProxy(new MathCalculatorImpl(), &b);
+
+ EXPECT_TRUE(!a);
+ EXPECT_FALSE(!b);
+
+ a = b.Pass();
+
+ EXPECT_FALSE(!a);
+ EXPECT_TRUE(!b);
+}
+
+TEST_F(InterfacePtrTest, Resettable) {
+ math::CalculatorPtr a;
+
+ EXPECT_TRUE(!a);
+
+ MessagePipe pipe;
+
+ // Save this so we can test it later.
+ Handle handle = pipe.handle0.get();
+
+ a = MakeProxy<math::Calculator>(pipe.handle0.Pass());
+
+ EXPECT_FALSE(!a);
+
+ a.reset();
+
+ EXPECT_TRUE(!a);
+ EXPECT_FALSE(a.internal_state()->router_for_testing());
+
+ // Test that handle was closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle));
+}
+
+TEST_F(InterfacePtrTest, EncounteredError) {
+ math::CalculatorPtr proxy;
+ MathCalculatorImpl* server = BindToProxy(new MathCalculatorImpl(), &proxy);
+
+ MathCalculatorUIImpl calculator_ui(proxy.Pass());
+
+ calculator_ui.Add(2.0);
+ PumpMessages();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ calculator_ui.Multiply(5.0);
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ // Close the server.
+ server->internal_state()->router()->CloseMessagePipe();
+
+ // The state change isn't picked up locally yet.
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ PumpMessages();
+
+ // OK, now we see the error.
+ EXPECT_TRUE(calculator_ui.encountered_error());
+}
+
+TEST_F(InterfacePtrTest, EncounteredErrorCallback) {
+ math::CalculatorPtr proxy;
+ MathCalculatorImpl* server = BindToProxy(new MathCalculatorImpl(), &proxy);
+
+ ErrorObserver error_observer;
+ proxy.set_error_handler(&error_observer);
+
+ MathCalculatorUIImpl calculator_ui(proxy.Pass());
+
+ calculator_ui.Add(2.0);
+ PumpMessages();
+ EXPECT_EQ(2.0, calculator_ui.GetOutput());
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ calculator_ui.Multiply(5.0);
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ // Close the server.
+ server->internal_state()->router()->CloseMessagePipe();
+
+ // The state change isn't picked up locally yet.
+ EXPECT_FALSE(calculator_ui.encountered_error());
+
+ PumpMessages();
+
+ // OK, now we see the error.
+ EXPECT_TRUE(calculator_ui.encountered_error());
+
+ // We should have also been able to observe the error through the
+ // ErrorHandler interface.
+ EXPECT_TRUE(error_observer.encountered_error());
+}
+
+TEST_F(InterfacePtrTest, NoClientAttribute) {
+ // This is a test to ensure the following compiles. The sample::Port interface
+ // does not have an explicit Client attribute.
+ sample::PortPtr port;
+ MessagePipe pipe;
+ port.Bind(pipe.handle0.Pass());
+}
+
+TEST_F(InterfacePtrTest, DestroyInterfacePtrOnClientMethod) {
+ math::CalculatorPtr proxy;
+ BindToProxy(new MathCalculatorImpl(), &proxy);
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
+
+ SelfDestructingMathCalculatorUIImpl* impl =
+ new SelfDestructingMathCalculatorUIImpl(proxy.Pass());
+ impl->BeginTest(false);
+
+ PumpMessages();
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
+}
+
+TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnClientMethod) {
+ math::CalculatorPtr proxy;
+ BindToProxy(new MathCalculatorImpl(), &proxy);
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
+
+ SelfDestructingMathCalculatorUIImpl* impl =
+ new SelfDestructingMathCalculatorUIImpl(proxy.Pass());
+ impl->BeginTest(true);
+
+ PumpMessages();
+
+ EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
+}
+
+TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) {
+ sample::ServicePtr proxy;
+ ReentrantServiceImpl* impl = BindToProxy(new ReentrantServiceImpl(), &proxy);
+ EXPECT_TRUE(impl->got_connection());
+
+ proxy->Frobinate(sample::FooPtr(),
+ sample::Service::BAZ_OPTIONS_REGULAR,
+ sample::PortPtr());
+ proxy->Frobinate(sample::FooPtr(),
+ sample::Service::BAZ_OPTIONS_REGULAR,
+ sample::PortPtr());
+
+ PumpMessages();
+
+ EXPECT_EQ(2, impl->max_call_depth());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/request_response_unittest.cc b/mojo/public/cpp/bindings/tests/request_response_unittest.cc
new file mode 100644
index 0000000..3755e64
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/request_response_unittest.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/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/public/interfaces/bindings/tests/sample_import.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class ProviderImpl : public InterfaceImpl<sample::Provider> {
+ public:
+ virtual void EchoString(const String& a,
+ const Callback<void(String)>& callback) override {
+ Callback<void(String)> callback_copy;
+ // Make sure operator= is used.
+ callback_copy = callback;
+ callback_copy.Run(a);
+ }
+
+ virtual void EchoStrings(
+ const String& a,
+ const String& b,
+ const Callback<void(String, String)>& callback) override {
+ callback.Run(a, b);
+ }
+
+ virtual void EchoMessagePipeHandle(
+ ScopedMessagePipeHandle a,
+ const Callback<void(ScopedMessagePipeHandle)>& callback) override {
+ callback.Run(a.Pass());
+ }
+
+ virtual void EchoEnum(sample::Enum a,
+ const Callback<void(sample::Enum)>& callback) override {
+ callback.Run(a);
+ }
+};
+
+class StringRecorder {
+ public:
+ explicit StringRecorder(std::string* buf) : buf_(buf) {
+ }
+ void Run(const String& a) const {
+ *buf_ = a;
+ }
+ void Run(const String& a, const String& b) const {
+ *buf_ = a.get() + b.get();
+ }
+ private:
+ std::string* buf_;
+};
+
+class EnumRecorder {
+ public:
+ explicit EnumRecorder(sample::Enum* value) : value_(value) {
+ }
+ void Run(sample::Enum a) const {
+ *value_ = a;
+ }
+ private:
+ sample::Enum* value_;
+};
+
+class MessagePipeWriter {
+ public:
+ explicit MessagePipeWriter(const char* text) : text_(text) {
+ }
+ void Run(ScopedMessagePipeHandle handle) const {
+ WriteTextMessage(handle.get(), text_);
+ }
+ private:
+ std::string text_;
+};
+
+class RequestResponseTest : public testing::Test {
+ public:
+ virtual ~RequestResponseTest() {
+ loop_.RunUntilIdle();
+ }
+
+ void PumpMessages() {
+ loop_.RunUntilIdle();
+ }
+
+ private:
+ Environment env_;
+ RunLoop loop_;
+};
+
+TEST_F(RequestResponseTest, EchoString) {
+ sample::ProviderPtr provider;
+ BindToProxy(new ProviderImpl(), &provider);
+
+ std::string buf;
+ provider->EchoString(String::From("hello"), StringRecorder(&buf));
+
+ PumpMessages();
+
+ EXPECT_EQ(std::string("hello"), buf);
+}
+
+TEST_F(RequestResponseTest, EchoStrings) {
+ sample::ProviderPtr provider;
+ BindToProxy(new ProviderImpl(), &provider);
+
+ std::string buf;
+ provider->EchoStrings(
+ String::From("hello"), String::From(" world"), StringRecorder(&buf));
+
+ PumpMessages();
+
+ EXPECT_EQ(std::string("hello world"), buf);
+}
+
+TEST_F(RequestResponseTest, EchoMessagePipeHandle) {
+ sample::ProviderPtr provider;
+ BindToProxy(new ProviderImpl(), &provider);
+
+ MessagePipe pipe2;
+ provider->EchoMessagePipeHandle(pipe2.handle1.Pass(),
+ MessagePipeWriter("hello"));
+
+ PumpMessages();
+
+ std::string value;
+ ReadTextMessage(pipe2.handle0.get(), &value);
+
+ EXPECT_EQ(std::string("hello"), value);
+}
+
+TEST_F(RequestResponseTest, EchoEnum) {
+ sample::ProviderPtr provider;
+ BindToProxy(new ProviderImpl(), &provider);
+
+ sample::Enum value;
+ provider->EchoEnum(sample::ENUM_VALUE, EnumRecorder(&value));
+
+ PumpMessages();
+
+ EXPECT_EQ(sample::ENUM_VALUE, value);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/router_unittest.cc b/mojo/public/cpp/bindings/tests/router_unittest.cc
new file mode 100644
index 0000000..d74e525
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/router_unittest.cc
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium 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 <stdlib.h>
+#include <string.h>
+
+#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/lib/message_queue.h"
+#include "mojo/public/cpp/bindings/lib/router.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+void AllocRequestMessage(uint32_t name, const char* text, Message* message) {
+ size_t payload_size = strlen(text) + 1; // Plus null terminator.
+ internal::RequestMessageBuilder builder(name, payload_size);
+ memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
+ builder.Finish(message);
+}
+
+void AllocResponseMessage(uint32_t name, const char* text,
+ uint64_t request_id, Message* message) {
+ size_t payload_size = strlen(text) + 1; // Plus null terminator.
+ internal::ResponseMessageBuilder builder(name, payload_size, request_id);
+ memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
+ builder.Finish(message);
+}
+
+class MessageAccumulator : public MessageReceiver {
+ public:
+ explicit MessageAccumulator(internal::MessageQueue* queue) : queue_(queue) {
+ }
+
+ virtual bool Accept(Message* message) override {
+ queue_->Push(message);
+ return true;
+ }
+
+ private:
+ internal::MessageQueue* queue_;
+};
+
+class ResponseGenerator : public MessageReceiverWithResponder {
+ public:
+ ResponseGenerator() {
+ }
+
+ virtual bool Accept(Message* message) override { return false; }
+
+ virtual bool AcceptWithResponder(Message* message,
+ MessageReceiver* responder) override {
+ EXPECT_TRUE(message->has_flag(internal::kMessageExpectsResponse));
+
+ return SendResponse(message->name(), message->request_id(), responder);
+ }
+
+ bool SendResponse(uint32_t name, uint64_t request_id,
+ MessageReceiver* responder) {
+ Message response;
+ AllocResponseMessage(name, "world", request_id, &response);
+
+ bool result = responder->Accept(&response);
+ delete responder;
+ return result;
+ }
+};
+
+class LazyResponseGenerator : public ResponseGenerator {
+ public:
+ LazyResponseGenerator() : responder_(nullptr), name_(0), request_id_(0) {
+ }
+
+ virtual ~LazyResponseGenerator() {
+ delete responder_;
+ }
+
+ virtual bool AcceptWithResponder(Message* message,
+ MessageReceiver* responder) override {
+ name_ = message->name();
+ request_id_ = message->request_id();
+ responder_ = responder;
+ return true;
+ }
+
+ bool has_responder() const { return !!responder_; }
+
+ void Complete() {
+ SendResponse(name_, request_id_, responder_);
+ responder_ = nullptr;
+ }
+
+ private:
+ MessageReceiver* responder_;
+ uint32_t name_;
+ uint32_t request_id_;
+};
+
+class RouterTest : public testing::Test {
+ public:
+ RouterTest() {
+ }
+
+ virtual void SetUp() override {
+ CreateMessagePipe(nullptr, &handle0_, &handle1_);
+ }
+
+ virtual void TearDown() override {}
+
+ void PumpMessages() {
+ loop_.RunUntilIdle();
+ }
+
+ protected:
+ ScopedMessagePipeHandle handle0_;
+ ScopedMessagePipeHandle handle1_;
+
+ private:
+ Environment env_;
+ RunLoop loop_;
+};
+
+TEST_F(RouterTest, BasicRequestResponse) {
+ internal::Router router0(handle0_.Pass(), internal::FilterChain());
+ internal::Router router1(handle1_.Pass(), internal::FilterChain());
+
+ ResponseGenerator generator;
+ router1.set_incoming_receiver(&generator);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ internal::MessageQueue message_queue;
+ router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue));
+
+ PumpMessages();
+
+ EXPECT_FALSE(message_queue.IsEmpty());
+
+ Message response;
+ message_queue.Pop(&response);
+
+ EXPECT_EQ(std::string("world"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+}
+
+TEST_F(RouterTest, BasicRequestResponse_Synchronous) {
+ internal::Router router0(handle0_.Pass(), internal::FilterChain());
+ internal::Router router1(handle1_.Pass(), internal::FilterChain());
+
+ ResponseGenerator generator;
+ router1.set_incoming_receiver(&generator);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ internal::MessageQueue message_queue;
+ router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue));
+
+ router1.WaitForIncomingMessage();
+ router0.WaitForIncomingMessage();
+
+ EXPECT_FALSE(message_queue.IsEmpty());
+
+ Message response;
+ message_queue.Pop(&response);
+
+ EXPECT_EQ(std::string("world"),
+ std::string(reinterpret_cast<const char*>(response.payload())));
+}
+
+TEST_F(RouterTest, RequestWithNoReceiver) {
+ internal::Router router0(handle0_.Pass(), internal::FilterChain());
+ internal::Router router1(handle1_.Pass(), internal::FilterChain());
+
+ // Without an incoming receiver set on router1, we expect router0 to observe
+ // an error as a result of sending a message.
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ internal::MessageQueue message_queue;
+ router0.AcceptWithResponder(&request, new MessageAccumulator(&message_queue));
+
+ PumpMessages();
+
+ EXPECT_TRUE(router0.encountered_error());
+ EXPECT_TRUE(router1.encountered_error());
+ EXPECT_TRUE(message_queue.IsEmpty());
+}
+
+TEST_F(RouterTest, LateResponse) {
+ // Test that things won't blow up if we try to send a message to a
+ // MessageReceiver, which was given to us via AcceptWithResponder,
+ // after the router has gone away.
+
+ LazyResponseGenerator generator;
+ {
+ internal::Router router0(handle0_.Pass(), internal::FilterChain());
+ internal::Router router1(handle1_.Pass(), internal::FilterChain());
+
+ router1.set_incoming_receiver(&generator);
+
+ Message request;
+ AllocRequestMessage(1, "hello", &request);
+
+ internal::MessageQueue message_queue;
+ router0.AcceptWithResponder(&request,
+ new MessageAccumulator(&message_queue));
+
+ PumpMessages();
+
+ EXPECT_TRUE(generator.has_responder());
+
+ }
+
+ generator.Complete(); // This should end up doing nothing.
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
new file mode 100644
index 0000000..2385772
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
@@ -0,0 +1,377 @@
+// Copyright 2014 The Chromium 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 <ostream>
+#include <string>
+
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+template <>
+struct TypeConverter<int32_t, sample::BarPtr> {
+ static int32_t Convert(const sample::BarPtr& bar) {
+ return static_cast<int32_t>(bar->alpha) << 16 |
+ static_cast<int32_t>(bar->beta) << 8 |
+ static_cast<int32_t>(bar->gamma);
+ }
+};
+
+} // namespace mojo
+
+namespace sample {
+namespace {
+
+// Set this variable to true to print the message in hex.
+bool g_dump_message_as_hex = false;
+
+// Set this variable to true to print the message in human readable form.
+bool g_dump_message_as_text = false;
+
+// Make a sample |Foo|.
+FooPtr MakeFoo() {
+ mojo::String name("foopy");
+
+ BarPtr bar(Bar::New());
+ bar->alpha = 20;
+ bar->beta = 40;
+ bar->gamma = 60;
+ bar->type = Bar::TYPE_VERTICAL;
+
+ mojo::Array<BarPtr> extra_bars(3);
+ for (size_t i = 0; i < extra_bars.size(); ++i) {
+ Bar::Type type = i % 2 == 0 ? Bar::TYPE_VERTICAL : Bar::TYPE_HORIZONTAL;
+ BarPtr bar(Bar::New());
+ uint8_t base = static_cast<uint8_t>(i * 100);
+ bar->alpha = base;
+ bar->beta = base + 20;
+ bar->gamma = base + 40;
+ bar->type = type;
+ extra_bars[i] = bar.Pass();
+ }
+
+ mojo::Array<uint8_t> data(10);
+ for (size_t i = 0; i < data.size(); ++i)
+ data[i] = static_cast<uint8_t>(data.size() - i);
+
+ mojo::Array<mojo::ScopedDataPipeConsumerHandle> input_streams(2);
+ mojo::Array<mojo::ScopedDataPipeProducerHandle> output_streams(2);
+ for (size_t i = 0; i < input_streams.size(); ++i) {
+ MojoCreateDataPipeOptions options;
+ options.struct_size = sizeof(MojoCreateDataPipeOptions);
+ options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = 1024;
+ mojo::ScopedDataPipeProducerHandle producer;
+ mojo::ScopedDataPipeConsumerHandle consumer;
+ mojo::CreateDataPipe(&options, &producer, &consumer);
+ input_streams[i] = consumer.Pass();
+ output_streams[i] = producer.Pass();
+ }
+
+ mojo::Array<mojo::Array<bool> > array_of_array_of_bools(2);
+ for (size_t i = 0; i < 2; ++i) {
+ mojo::Array<bool> array_of_bools(2);
+ for (size_t j = 0; j < 2; ++j)
+ array_of_bools[j] = j;
+ array_of_array_of_bools[i] = array_of_bools.Pass();
+ }
+
+ mojo::MessagePipe pipe;
+ FooPtr foo(Foo::New());
+ foo->name = name;
+ foo->x = 1;
+ foo->y = 2;
+ foo->a = false;
+ foo->b = true;
+ foo->c = false;
+ foo->bar = bar.Pass();
+ foo->extra_bars = extra_bars.Pass();
+ foo->data = data.Pass();
+ foo->source = pipe.handle1.Pass();
+ foo->input_streams = input_streams.Pass();
+ foo->output_streams = output_streams.Pass();
+ foo->array_of_array_of_bools = array_of_array_of_bools.Pass();
+
+ return foo.Pass();
+}
+
+// Check that the given |Foo| is identical to the one made by |MakeFoo()|.
+void CheckFoo(const Foo& foo) {
+ const std::string kName("foopy");
+ ASSERT_FALSE(foo.name.is_null());
+ EXPECT_EQ(kName.size(), foo.name.size());
+ for (size_t i = 0; i < std::min(kName.size(), foo.name.size()); i++) {
+ // Test both |operator[]| and |at|.
+ EXPECT_EQ(kName[i], foo.name.at(i)) << i;
+ EXPECT_EQ(kName[i], foo.name[i]) << i;
+ }
+ EXPECT_EQ(kName, foo.name.get());
+
+ EXPECT_EQ(1, foo.x);
+ EXPECT_EQ(2, foo.y);
+ EXPECT_FALSE(foo.a);
+ EXPECT_TRUE(foo.b);
+ EXPECT_FALSE(foo.c);
+
+ EXPECT_EQ(20, foo.bar->alpha);
+ EXPECT_EQ(40, foo.bar->beta);
+ EXPECT_EQ(60, foo.bar->gamma);
+ EXPECT_EQ(Bar::TYPE_VERTICAL, foo.bar->type);
+
+ EXPECT_EQ(3u, foo.extra_bars.size());
+ for (size_t i = 0; i < foo.extra_bars.size(); i++) {
+ uint8_t base = static_cast<uint8_t>(i * 100);
+ Bar::Type type = i % 2 == 0 ? Bar::TYPE_VERTICAL : Bar::TYPE_HORIZONTAL;
+ EXPECT_EQ(base, foo.extra_bars[i]->alpha) << i;
+ EXPECT_EQ(base + 20, foo.extra_bars[i]->beta) << i;
+ EXPECT_EQ(base + 40, foo.extra_bars[i]->gamma) << i;
+ EXPECT_EQ(type, foo.extra_bars[i]->type) << i;
+ }
+
+ EXPECT_EQ(10u, foo.data.size());
+ for (size_t i = 0; i < foo.data.size(); ++i) {
+ EXPECT_EQ(static_cast<uint8_t>(foo.data.size() - i), foo.data[i]) << i;
+ }
+
+ EXPECT_FALSE(foo.input_streams.is_null());
+ EXPECT_EQ(2u, foo.input_streams.size());
+
+ EXPECT_FALSE(foo.output_streams.is_null());
+ EXPECT_EQ(2u, foo.output_streams.size());
+
+ EXPECT_EQ(2u, foo.array_of_array_of_bools.size());
+ for (size_t i = 0; i < foo.array_of_array_of_bools.size(); ++i) {
+ EXPECT_EQ(2u, foo.array_of_array_of_bools[i].size());
+ for (size_t j = 0; j < foo.array_of_array_of_bools[i].size(); ++j) {
+ EXPECT_EQ(bool(j), foo.array_of_array_of_bools[i][j]);
+ }
+ }
+}
+
+void PrintSpacer(int depth) {
+ for (int i = 0; i < depth; ++i)
+ std::cout << " ";
+}
+
+void Print(int depth, const char* name, bool value) {
+ PrintSpacer(depth);
+ std::cout << name << ": " << (value ? "true" : "false") << std::endl;
+}
+
+void Print(int depth, const char* name, int32_t value) {
+ PrintSpacer(depth);
+ std::cout << name << ": " << value << std::endl;
+}
+
+void Print(int depth, const char* name, uint8_t value) {
+ PrintSpacer(depth);
+ std::cout << name << ": " << uint32_t(value) << std::endl;
+}
+
+template <typename H>
+void Print(int depth, const char* name,
+ const mojo::ScopedHandleBase<H>& value) {
+ PrintSpacer(depth);
+ std::cout << name << ": 0x" << std::hex << value.get().value() << std::endl;
+}
+
+void Print(int depth, const char* name, const mojo::String& str) {
+ PrintSpacer(depth);
+ std::cout << name << ": \"" << str.get() << "\"" << std::endl;
+}
+
+void Print(int depth, const char* name, const BarPtr& bar) {
+ PrintSpacer(depth);
+ std::cout << name << ":" << std::endl;
+ if (!bar.is_null()) {
+ ++depth;
+ Print(depth, "alpha", bar->alpha);
+ Print(depth, "beta", bar->beta);
+ Print(depth, "gamma", bar->gamma);
+ Print(depth, "packed", bar.To<int32_t>());
+ --depth;
+ }
+}
+
+template <typename T>
+void Print(int depth, const char* name, const mojo::Array<T>& array) {
+ PrintSpacer(depth);
+ std::cout << name << ":" << std::endl;
+ if (!array.is_null()) {
+ ++depth;
+ for (size_t i = 0; i < array.size(); ++i) {
+ std::stringstream buf;
+ buf << i;
+ Print(depth, buf.str().data(), array.at(i));
+ }
+ --depth;
+ }
+}
+
+void Print(int depth, const char* name, const FooPtr& foo) {
+ PrintSpacer(depth);
+ std::cout << name << ":" << std::endl;
+ if (!foo.is_null()) {
+ ++depth;
+ Print(depth, "name", foo->name);
+ Print(depth, "x", foo->x);
+ Print(depth, "y", foo->y);
+ Print(depth, "a", foo->a);
+ Print(depth, "b", foo->b);
+ Print(depth, "c", foo->c);
+ Print(depth, "bar", foo->bar);
+ Print(depth, "extra_bars", foo->extra_bars);
+ Print(depth, "data", foo->data);
+ Print(depth, "source", foo->source);
+ Print(depth, "input_streams", foo->input_streams);
+ Print(depth, "output_streams", foo->output_streams);
+ Print(depth, "array_of_array_of_bools", foo->array_of_array_of_bools);
+ --depth;
+ }
+}
+
+void DumpHex(const uint8_t* bytes, uint32_t num_bytes) {
+ for (uint32_t i = 0; i < num_bytes; ++i) {
+ std::cout << std::setw(2) << std::setfill('0') << std::hex <<
+ uint32_t(bytes[i]);
+
+ if (i % 16 == 15) {
+ std::cout << std::endl;
+ continue;
+ }
+
+ if (i % 2 == 1)
+ std::cout << " ";
+ if (i % 8 == 7)
+ std::cout << " ";
+ }
+}
+
+class ServiceImpl : public Service {
+ public:
+ virtual void Frobinate(FooPtr foo, BazOptions baz, PortPtr port) override {
+ // Users code goes here to handle the incoming Frobinate message.
+
+ // We mainly check that we're given the expected arguments.
+ EXPECT_FALSE(foo.is_null());
+ if (!foo.is_null())
+ CheckFoo(*foo);
+ EXPECT_EQ(BAZ_OPTIONS_EXTRA, baz);
+
+ if (g_dump_message_as_text) {
+ // Also dump the Foo structure and all of its members.
+ std::cout << "Frobinate:" << std::endl;
+ int depth = 1;
+ Print(depth, "foo", foo);
+ Print(depth, "baz", baz);
+ Print(depth, "port", port.get());
+ }
+ }
+
+ virtual void GetPort(mojo::InterfaceRequest<Port> port_request) override {}
+};
+
+class ServiceProxyImpl : public ServiceProxy {
+ public:
+ explicit ServiceProxyImpl(mojo::MessageReceiverWithResponder* receiver)
+ : ServiceProxy(receiver) {
+ }
+};
+
+class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder {
+ public:
+ virtual bool Accept(mojo::Message* message) override {
+ // Imagine some IPC happened here.
+
+ if (g_dump_message_as_hex) {
+ DumpHex(reinterpret_cast<const uint8_t*>(message->data()),
+ message->data_num_bytes());
+ }
+
+ // In the receiving process, an implementation of ServiceStub is known to
+ // the system. It receives the incoming message.
+ ServiceImpl impl;
+
+ ServiceStub stub;
+ stub.set_sink(&impl);
+ return stub.Accept(message);
+ }
+
+ virtual bool AcceptWithResponder(mojo::Message* message,
+ mojo::MessageReceiver* responder) override {
+ return false;
+ }
+};
+
+class BindingsSampleTest : public testing::Test {
+ public:
+ BindingsSampleTest() {}
+ virtual ~BindingsSampleTest() {}
+
+ private:
+ mojo::Environment env_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(BindingsSampleTest);
+};
+
+TEST_F(BindingsSampleTest, Basic) {
+ SimpleMessageReceiver receiver;
+
+ // User has a proxy to a Service somehow.
+ Service* service = new ServiceProxyImpl(&receiver);
+
+ // User constructs a message to send.
+
+ // Notice that it doesn't matter in what order the structs / arrays are
+ // allocated. Here, the various members of Foo are allocated before Foo is
+ // allocated.
+
+ FooPtr foo = MakeFoo();
+ CheckFoo(*foo);
+
+ PortPtr port;
+ service->Frobinate(foo.Pass(), Service::BAZ_OPTIONS_EXTRA, port.Pass());
+
+ delete service;
+}
+
+TEST_F(BindingsSampleTest, DefaultValues) {
+ DefaultsTestPtr defaults(DefaultsTest::New());
+ EXPECT_EQ(-12, defaults->a0);
+ EXPECT_EQ(kTwelve, defaults->a1);
+ EXPECT_EQ(1234, defaults->a2);
+ EXPECT_EQ(34567U, defaults->a3);
+ EXPECT_EQ(123456, defaults->a4);
+ EXPECT_EQ(3456789012U, defaults->a5);
+ EXPECT_EQ(-111111111111LL, defaults->a6);
+ EXPECT_EQ(9999999999999999999ULL, defaults->a7);
+ EXPECT_EQ(0x12345, defaults->a8);
+ EXPECT_EQ(-0x12345, defaults->a9);
+ EXPECT_EQ(1234, defaults->a10);
+ EXPECT_TRUE(defaults->a11);
+ EXPECT_FALSE(defaults->a12);
+ EXPECT_FLOAT_EQ(123.25f, defaults->a13);
+ EXPECT_DOUBLE_EQ(1234567890.123, defaults->a14);
+ EXPECT_DOUBLE_EQ(1E10, defaults->a15);
+ EXPECT_DOUBLE_EQ(-1.2E+20, defaults->a16);
+ EXPECT_DOUBLE_EQ(1.23E-20, defaults->a17);
+ EXPECT_TRUE(defaults->a18.is_null());
+ EXPECT_TRUE(defaults->a19.is_null());
+ EXPECT_EQ(Bar::TYPE_BOTH, defaults->a20);
+ EXPECT_TRUE(defaults->a21.is_null());
+ ASSERT_FALSE(defaults->a22.is_null());
+ EXPECT_EQ(imported::SHAPE_RECTANGLE, defaults->a22->shape);
+ EXPECT_EQ(imported::COLOR_BLACK, defaults->a22->color);
+ EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, defaults->a23);
+ EXPECT_EQ(0x123456789, defaults->a24);
+ EXPECT_EQ(-0x123456789, defaults->a25);
+}
+
+} // namespace
+} // namespace sample
diff --git a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc
new file mode 100644
index 0000000..8709460
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Serialization warnings are only recorded in debug build.
+#ifndef NDEBUG
+
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/lib/array_serialization.h"
+#include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+using mojo::internal::ArrayValidateParams;
+using mojo::internal::NoValidateParams;
+
+// Creates an array of arrays of handles (2 X 3) for testing.
+Array<Array<ScopedHandle> > CreateTestNestedHandleArray() {
+ Array<Array<ScopedHandle> > array(2);
+ for (size_t i = 0; i < array.size(); ++i) {
+ Array<ScopedHandle> nested_array(3);
+ for (size_t j = 0; j < nested_array.size(); ++j) {
+ MessagePipe pipe;
+ nested_array[j] = ScopedHandle::From(pipe.handle1.Pass());
+ }
+ array[i] = nested_array.Pass();
+ }
+
+ return array.Pass();
+}
+
+class SerializationWarningTest : public testing::Test {
+ public:
+ virtual ~SerializationWarningTest() {}
+
+ protected:
+ template <typename T>
+ void TestWarning(T obj, mojo::internal::ValidationError expected_warning) {
+ warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
+
+ mojo::internal::FixedBuffer buf(GetSerializedSize_(obj));
+ typename T::Data_* data;
+ Serialize_(obj.Pass(), &buf, &data);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ template <typename ValidateParams, typename T>
+ void TestArrayWarning(T obj,
+ mojo::internal::ValidationError expected_warning) {
+ warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
+
+ mojo::internal::FixedBuffer buf(GetSerializedSize_(obj));
+ typename T::Data_* data;
+ SerializeArray_<ValidateParams>(obj.Pass(), &buf, &data);
+
+ EXPECT_EQ(expected_warning, warning_observer_.last_warning());
+ }
+
+ mojo::internal::SerializationWarningObserverForTesting warning_observer_;
+ Environment env_;
+};
+
+TEST_F(SerializationWarningTest, HandleInStruct) {
+ Struct2Ptr test_struct(Struct2::New());
+ EXPECT_FALSE(test_struct->hdl.is_valid());
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE);
+
+ test_struct = Struct2::New();
+ MessagePipe pipe;
+ test_struct->hdl = ScopedHandle::From(pipe.handle1.Pass());
+
+ TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, StructInStruct) {
+ Struct3Ptr test_struct(Struct3::New());
+ EXPECT_TRUE(!test_struct->struct_1);
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct3::New();
+ test_struct->struct_1 = Struct1::New();
+
+ TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfStructsInStruct) {
+ Struct4Ptr test_struct(Struct4::New());
+ EXPECT_TRUE(!test_struct->data);
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct4::New();
+ test_struct->data.resize(1);
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct4::New();
+ test_struct->data.resize(0);
+
+ TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+
+ test_struct = Struct4::New();
+ test_struct->data.resize(1);
+ test_struct->data[0] = Struct1::New();
+
+ TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, FixedArrayOfStructsInStruct) {
+ Struct5Ptr test_struct(Struct5::New());
+ EXPECT_TRUE(!test_struct->pair);
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct5::New();
+ test_struct->pair.resize(1);
+ test_struct->pair[0] = Struct1::New();
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER);
+
+ test_struct = Struct5::New();
+ test_struct->pair.resize(2);
+ test_struct->pair[0] = Struct1::New();
+ test_struct->pair[1] = Struct1::New();
+
+ TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, StringInStruct) {
+ Struct6Ptr test_struct(Struct6::New());
+ EXPECT_TRUE(!test_struct->str);
+
+ TestWarning(test_struct.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_struct = Struct6::New();
+ test_struct->str = "hello world";
+
+ TestWarning(test_struct.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfArraysOfHandles) {
+ Array<Array<ScopedHandle> > test_array = CreateTestNestedHandleArray();
+ test_array[0] = Array<ScopedHandle>();
+ test_array[1][0] = ScopedHandle();
+
+ TestArrayWarning<ArrayValidateParams<0, true,
+ ArrayValidateParams<0, true, NoValidateParams> > >(
+ test_array.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+
+ test_array = CreateTestNestedHandleArray();
+ test_array[0] = Array<ScopedHandle>();
+ TestArrayWarning<ArrayValidateParams<0, false,
+ ArrayValidateParams<0, true, NoValidateParams> > >(
+ test_array.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_array = CreateTestNestedHandleArray();
+ test_array[1][0] = ScopedHandle();
+ TestArrayWarning<ArrayValidateParams<0, true,
+ ArrayValidateParams<0, false, NoValidateParams> > >(
+ test_array.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE);
+}
+
+TEST_F(SerializationWarningTest, ArrayOfStrings) {
+ Array<String> test_array(3);
+ for (size_t i = 0; i < test_array.size(); ++i)
+ test_array[i] = "hello";
+
+ TestArrayWarning<ArrayValidateParams<0, true,
+ ArrayValidateParams<0, false, NoValidateParams> > >(
+ test_array.Pass(), mojo::internal::VALIDATION_ERROR_NONE);
+
+ test_array = Array<String>(3);
+ TestArrayWarning<ArrayValidateParams<0, false,
+ ArrayValidateParams<0, false, NoValidateParams> > >(
+ test_array.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER);
+
+ test_array = Array<String>(2);
+ TestArrayWarning<ArrayValidateParams<3, true,
+ ArrayValidateParams<0, false, NoValidateParams> > >(
+ test_array.Pass(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER);
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
+
+#endif
diff --git a/mojo/public/cpp/bindings/tests/string_unittest.cc b/mojo/public/cpp/bindings/tests/string_unittest.cc
new file mode 100644
index 0000000..6f85443
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/string_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium 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/string.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+
+TEST(StringTest, DefaultIsNull) {
+ String s;
+ EXPECT_TRUE(s.is_null());
+}
+
+TEST(StringTest, ConstructedWithNULL) {
+ String s(nullptr);
+ EXPECT_TRUE(s.is_null());
+}
+
+TEST(StringTest, ConstructedWithNullCharPointer) {
+ const char* null = nullptr;
+ String s(null);
+ EXPECT_TRUE(s.is_null());
+}
+
+TEST(StringTest, AssignedNULL) {
+ String s("");
+ EXPECT_FALSE(s.is_null());
+ s = nullptr;
+ EXPECT_TRUE(s.is_null());
+}
+
+TEST(StringTest, Empty) {
+ String s("");
+ EXPECT_FALSE(s.is_null());
+ EXPECT_TRUE(s.get().empty());
+}
+
+TEST(StringTest, Basic) {
+ String s("hello world");
+ EXPECT_EQ(std::string("hello world"), s.get());
+}
+
+TEST(StringTest, Assignment) {
+ String s("hello world");
+ String t = s; // Makes a copy.
+ EXPECT_FALSE(t.is_null());
+ EXPECT_EQ(std::string("hello world"), t.get());
+ EXPECT_FALSE(s.is_null());
+}
+
+TEST(StringTest, Equality) {
+ String s("hello world");
+ String t("hello world");
+ EXPECT_EQ(s, t);
+ EXPECT_TRUE(s == t);
+ EXPECT_TRUE("hello world" == s);
+ EXPECT_TRUE(s == "hello world");
+ EXPECT_TRUE("not" != s);
+ EXPECT_TRUE(s != "not");
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc
new file mode 100644
index 0000000..7d74498
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright 2014 The Chromium 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 "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+RectPtr MakeRect(int32_t factor = 1) {
+ RectPtr rect(Rect::New());
+ rect->x = 1 * factor;
+ rect->y = 2 * factor;
+ rect->width = 10 * factor;
+ rect->height = 20 * factor;
+ return rect.Pass();
+}
+
+void CheckRect(const Rect& rect, int32_t factor = 1) {
+ EXPECT_EQ(1 * factor, rect.x);
+ EXPECT_EQ(2 * factor, rect.y);
+ EXPECT_EQ(10 * factor, rect.width);
+ EXPECT_EQ(20 * factor, rect.height);
+}
+
+class StructTest : public testing::Test {
+ public:
+ virtual ~StructTest() {}
+
+ private:
+ Environment env_;
+};
+
+} // namespace
+
+TEST_F(StructTest, Rect) {
+ RectPtr rect;
+ EXPECT_TRUE(rect.is_null());
+ EXPECT_TRUE(!rect);
+ EXPECT_FALSE(rect);
+
+ rect = MakeRect();
+ EXPECT_FALSE(rect.is_null());
+ EXPECT_FALSE(!rect);
+ EXPECT_TRUE(rect);
+
+ CheckRect(*rect);
+}
+
+TEST_F(StructTest, Clone) {
+ NamedRegionPtr region;
+
+ NamedRegionPtr clone_region = region.Clone();
+ EXPECT_TRUE(clone_region.is_null());
+
+ region = NamedRegion::New();
+ clone_region = region.Clone();
+ EXPECT_TRUE(clone_region->name.is_null());
+ EXPECT_TRUE(clone_region->rects.is_null());
+
+ region->name = "hello world";
+ clone_region = region.Clone();
+ EXPECT_EQ(region->name, clone_region->name);
+
+ region->rects = Array<RectPtr>(2);
+ region->rects[1] = MakeRect();
+ clone_region = region.Clone();
+ EXPECT_EQ(2u, clone_region->rects.size());
+ EXPECT_TRUE(clone_region->rects[0].is_null());
+ CheckRect(*clone_region->rects[1]);
+
+ // NoDefaultFieldValues contains handles, so Clone() is not available, but
+ // NoDefaultFieldValuesPtr should still compile.
+ NoDefaultFieldValuesPtr no_default_field_values(NoDefaultFieldValues::New());
+ EXPECT_FALSE(no_default_field_values->f13.is_valid());
+}
+
+// Serialization test of a struct with no pointer or handle members.
+TEST_F(StructTest, Serialization_Basic) {
+ RectPtr rect(MakeRect());
+
+ size_t size = GetSerializedSize_(rect);
+ EXPECT_EQ(8U + 16U, size);
+
+ mojo::internal::FixedBuffer buf(size);
+ internal::Rect_Data* data;
+ Serialize_(rect.Pass(), &buf, &data);
+
+ RectPtr rect2;
+ Deserialize_(data, &rect2);
+
+ CheckRect(*rect2);
+}
+
+// Serialization test of a struct with struct pointers.
+TEST_F(StructTest, Serialization_StructPointers) {
+ RectPairPtr pair(RectPair::New());
+ pair->first = MakeRect();
+ pair->second = MakeRect();
+
+ size_t size = GetSerializedSize_(pair);
+ EXPECT_EQ(8U + 16U + 2*(8U + 16U), size);
+
+ mojo::internal::FixedBuffer buf(size);
+ internal::RectPair_Data* data;
+ Serialize_(pair.Pass(), &buf, &data);
+
+ RectPairPtr pair2;
+ Deserialize_(data, &pair2);
+
+ CheckRect(*pair2->first);
+ CheckRect(*pair2->second);
+}
+
+// Serialization test of a struct with an array member.
+TEST_F(StructTest, Serialization_ArrayPointers) {
+ NamedRegionPtr region(NamedRegion::New());
+ region->name = "region";
+ region->rects = Array<RectPtr>::New(4);
+ for (size_t i = 0; i < region->rects.size(); ++i)
+ region->rects[i] = MakeRect(static_cast<int32_t>(i) + 1);
+
+ size_t size = GetSerializedSize_(region);
+ EXPECT_EQ(8U + // header
+ 8U + // name pointer
+ 8U + // rects pointer
+ 8U + // name header
+ 8U + // name payload (rounded up)
+ 8U + // rects header
+ 4*8U + // rects payload (four pointers)
+ 4*(8U + // rect header
+ 16U), // rect payload (four ints)
+ size);
+
+ mojo::internal::FixedBuffer buf(size);
+ internal::NamedRegion_Data* data;
+ Serialize_(region.Pass(), &buf, &data);
+
+ NamedRegionPtr region2;
+ Deserialize_(data, ®ion2);
+
+ EXPECT_EQ(String("region"), region2->name);
+
+ EXPECT_EQ(4U, region2->rects.size());
+ for (size_t i = 0; i < region2->rects.size(); ++i)
+ CheckRect(*region2->rects[i], static_cast<int32_t>(i) + 1);
+}
+
+// Serialization test of a struct with null array pointers.
+TEST_F(StructTest, Serialization_NullArrayPointers) {
+ NamedRegionPtr region(NamedRegion::New());
+ EXPECT_TRUE(region->name.is_null());
+ EXPECT_TRUE(region->rects.is_null());
+
+ size_t size = GetSerializedSize_(region);
+ EXPECT_EQ(8U + // header
+ 8U + // name pointer
+ 8U, // rects pointer
+ size);
+
+ mojo::internal::FixedBuffer buf(size);
+ internal::NamedRegion_Data* data;
+ Serialize_(region.Pass(), &buf, &data);
+
+ NamedRegionPtr region2;
+ Deserialize_(data, ®ion2);
+
+ EXPECT_TRUE(region2->name.is_null());
+ EXPECT_TRUE(region2->rects.is_null());
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc
new file mode 100644
index 0000000..9500839
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc
@@ -0,0 +1,208 @@
+// 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/environment/environment.h"
+#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+struct RedmondRect {
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
+};
+
+struct RedmondNamedRegion {
+ std::string name;
+ std::vector<RedmondRect> rects;
+};
+
+bool AreEqualRectArrays(const Array<test::RectPtr>& rects1,
+ const Array<test::RectPtr>& rects2) {
+ if (rects1.size() != rects2.size())
+ return false;
+
+ for (size_t i = 0; i < rects1.size(); ++i) {
+ if (rects1[i]->x != rects2[i]->x ||
+ rects1[i]->y != rects2[i]->y ||
+ rects1[i]->width != rects2[i]->width ||
+ rects1[i]->height != rects2[i]->height) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+template <>
+struct TypeConverter<test::RectPtr, RedmondRect> {
+ static test::RectPtr Convert(const RedmondRect& input) {
+ test::RectPtr rect(test::Rect::New());
+ rect->x = input.left;
+ rect->y = input.top;
+ rect->width = input.right - input.left;
+ rect->height = input.bottom - input.top;
+ return rect.Pass();
+ }
+};
+
+template <>
+struct TypeConverter<RedmondRect, test::RectPtr> {
+ static RedmondRect Convert(const test::RectPtr& input) {
+ RedmondRect rect;
+ rect.left = input->x;
+ rect.top = input->y;
+ rect.right = input->x + input->width;
+ rect.bottom = input->y + input->height;
+ return rect;
+ }
+};
+
+template <>
+struct TypeConverter<test::NamedRegionPtr, RedmondNamedRegion> {
+ static test::NamedRegionPtr Convert(const RedmondNamedRegion& input) {
+ test::NamedRegionPtr region(test::NamedRegion::New());
+ region->name = input.name;
+ region->rects = Array<test::RectPtr>::From(input.rects);
+ return region.Pass();
+ }
+};
+
+template <>
+struct TypeConverter<RedmondNamedRegion, test::NamedRegionPtr> {
+ static RedmondNamedRegion Convert(const test::NamedRegionPtr& input) {
+ RedmondNamedRegion region;
+ region.name = input->name;
+ region.rects = input->rects.To<std::vector<RedmondRect> >();
+ return region;
+ }
+};
+
+namespace test {
+namespace {
+
+TEST(TypeConversionTest, String) {
+ const char kText[6] = "hello";
+
+ String a = std::string(kText);
+ String b(kText);
+ String c(static_cast<const char*>(kText));
+
+ EXPECT_EQ(std::string(kText), a.To<std::string>());
+ EXPECT_EQ(std::string(kText), b.To<std::string>());
+ EXPECT_EQ(std::string(kText), c.To<std::string>());
+}
+
+TEST(TypeConversionTest, String_Null) {
+ String a;
+ EXPECT_TRUE(a.is_null());
+ EXPECT_EQ(std::string(), a.To<std::string>());
+
+ String b = String::From(static_cast<const char*>(nullptr));
+ EXPECT_TRUE(b.is_null());
+}
+
+TEST(TypeConversionTest, String_Empty) {
+ String a = "";
+ EXPECT_EQ(std::string(), a.To<std::string>());
+
+ String b = std::string();
+ EXPECT_FALSE(b.is_null());
+ EXPECT_EQ(std::string(), b.To<std::string>());
+}
+
+TEST(TypeConversionTest, StringWithEmbeddedNull) {
+ const std::string kText("hel\0lo", 6);
+
+ String a(kText);
+ EXPECT_EQ(kText, a.To<std::string>());
+
+ // Expect truncation:
+ String b(kText.c_str());
+ EXPECT_EQ(std::string("hel"), b.To<std::string>());
+}
+
+TEST(TypeConversionTest, CustomTypeConverter) {
+ RectPtr rect(Rect::New());
+ rect->x = 10;
+ rect->y = 20;
+ rect->width = 50;
+ rect->height = 45;
+
+ RedmondRect rr = rect.To<RedmondRect>();
+ EXPECT_EQ(10, rr.left);
+ EXPECT_EQ(20, rr.top);
+ EXPECT_EQ(60, rr.right);
+ EXPECT_EQ(65, rr.bottom);
+
+ RectPtr rect2(Rect::From(rr));
+ EXPECT_EQ(rect->x, rect2->x);
+ EXPECT_EQ(rect->y, rect2->y);
+ EXPECT_EQ(rect->width, rect2->width);
+ EXPECT_EQ(rect->height, rect2->height);
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Array_Null) {
+ Array<RectPtr> rects;
+
+ std::vector<RedmondRect> redmond_rects =
+ rects.To<std::vector<RedmondRect> >();
+
+ EXPECT_TRUE(redmond_rects.empty());
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Array) {
+ const RedmondRect kBase = { 10, 20, 30, 40 };
+
+ Array<RectPtr> rects(10);
+ for (size_t i = 0; i < rects.size(); ++i) {
+ RedmondRect rr = kBase;
+ rr.left += static_cast<int32_t>(i);
+ rr.top += static_cast<int32_t>(i);
+ rects[i] = Rect::From(rr);
+ }
+
+ std::vector<RedmondRect> redmond_rects =
+ rects.To<std::vector<RedmondRect> >();
+
+ Array<RectPtr> rects2 = Array<RectPtr>::From(redmond_rects);
+ EXPECT_TRUE(AreEqualRectArrays(rects, rects2));
+}
+
+TEST(TypeConversionTest, CustomTypeConverter_Nested) {
+ RedmondNamedRegion redmond_region;
+ redmond_region.name = "foopy";
+
+ const RedmondRect kBase = { 10, 20, 30, 40 };
+
+ for (size_t i = 0; i < 10; ++i) {
+ RedmondRect rect = kBase;
+ rect.left += static_cast<int32_t>(i);
+ rect.top += static_cast<int32_t>(i);
+ redmond_region.rects.push_back(rect);
+ }
+
+ // Round-trip through generated struct and TypeConverter.
+
+ NamedRegionPtr copy = NamedRegion::From(redmond_region);
+ RedmondNamedRegion redmond_region2 = copy.To<RedmondNamedRegion>();
+
+ EXPECT_EQ(redmond_region.name, redmond_region2.name);
+ EXPECT_EQ(redmond_region.rects.size(), redmond_region2.rects.size());
+ for (size_t i = 0; i < redmond_region.rects.size(); ++i) {
+ EXPECT_EQ(redmond_region.rects[i].left, redmond_region2.rects[i].left);
+ EXPECT_EQ(redmond_region.rects[i].top, redmond_region2.rects[i].top);
+ EXPECT_EQ(redmond_region.rects[i].right, redmond_region2.rects[i].right);
+ EXPECT_EQ(redmond_region.rects[i].bottom, redmond_region2.rects[i].bottom);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc
new file mode 100644
index 0000000..f5b5a01
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.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 "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <limits>
+#include <map>
+#include <set>
+#include <utility>
+
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+class ValidationTestInputParser {
+ public:
+ ValidationTestInputParser(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message);
+ ~ValidationTestInputParser();
+
+ bool Run();
+
+ private:
+ struct DataType;
+
+ typedef std::pair<const char*, const char*> Range;
+
+ typedef bool (ValidationTestInputParser::*ParseDataFunc)(
+ const DataType& type, const std::string& value_string);
+
+ struct DataType {
+ const char* name;
+ size_t name_size;
+ size_t data_size;
+ ParseDataFunc parse_data_func;
+ };
+
+ // A dist4/8 item that hasn't been matched with an anchr item.
+ struct PendingDistanceItem {
+ // Where this data item is located in |data_|.
+ size_t pos;
+ // Either 4 or 8 (bytes).
+ size_t data_size;
+ };
+
+ bool GetNextItem(Range* range);
+
+ bool ParseItem(const Range& range);
+
+ bool ParseUnsignedInteger(const DataType& type,
+ const std::string& value_string);
+ bool ParseSignedInteger(const DataType& type,
+ const std::string& value_string);
+ bool ParseFloat(const DataType& type, const std::string& value_string);
+ bool ParseDouble(const DataType& type, const std::string& value_string);
+ bool ParseBinarySequence(const DataType& type,
+ const std::string& value_string);
+ bool ParseDistance(const DataType& type, const std::string& value_string);
+ bool ParseAnchor(const DataType& type, const std::string& value_string);
+ bool ParseHandles(const DataType& type, const std::string& value_string);
+
+ bool StartsWith(const Range& range, const char* prefix, size_t prefix_length);
+
+ bool ConvertToUnsignedInteger(const std::string& value_string,
+ unsigned long long int* value);
+
+ template <typename T>
+ void AppendData(T data) {
+ size_t pos = data_->size();
+ data_->resize(pos + sizeof(T));
+ memcpy(&(*data_)[pos], &data, sizeof(T));
+ }
+
+ template <typename TargetType, typename InputType>
+ bool ConvertAndAppendData(InputType value) {
+ if (value > std::numeric_limits<TargetType>::max() ||
+ value < std::numeric_limits<TargetType>::min()) {
+ return false;
+ }
+ AppendData(static_cast<TargetType>(value));
+ return true;
+ }
+
+ template <typename TargetType, typename InputType>
+ bool ConvertAndFillData(size_t pos, InputType value) {
+ if (value > std::numeric_limits<TargetType>::max() ||
+ value < std::numeric_limits<TargetType>::min()) {
+ return false;
+ }
+ TargetType target_value = static_cast<TargetType>(value);
+ assert(pos + sizeof(TargetType) <= data_->size());
+ memcpy(&(*data_)[pos], &target_value, sizeof(TargetType));
+ return true;
+ }
+
+ static const DataType kDataTypes[];
+ static const size_t kDataTypeCount;
+
+ const std::string& input_;
+ size_t input_cursor_;
+
+ std::vector<uint8_t>* data_;
+ size_t* num_handles_;
+ std::string* error_message_;
+
+ std::map<std::string, PendingDistanceItem> pending_distance_items_;
+ std::set<std::string> anchors_;
+};
+
+#define DATA_TYPE(name, data_size, parse_data_func) \
+ {name, sizeof(name) - 1, data_size, parse_data_func}
+
+const ValidationTestInputParser::DataType
+ ValidationTestInputParser::kDataTypes[] = {
+ DATA_TYPE("[u1]", 1, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[u2]", 2, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[u4]", 4, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[u8]", 8, &ValidationTestInputParser::ParseUnsignedInteger),
+ DATA_TYPE("[s1]", 1, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[s2]", 2, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[s4]", 4, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[s8]", 8, &ValidationTestInputParser::ParseSignedInteger),
+ DATA_TYPE("[b]", 1, &ValidationTestInputParser::ParseBinarySequence),
+ DATA_TYPE("[f]", 4, &ValidationTestInputParser::ParseFloat),
+ DATA_TYPE("[d]", 8, &ValidationTestInputParser::ParseDouble),
+ DATA_TYPE("[dist4]", 4, &ValidationTestInputParser::ParseDistance),
+ DATA_TYPE("[dist8]", 8, &ValidationTestInputParser::ParseDistance),
+ DATA_TYPE("[anchr]", 0, &ValidationTestInputParser::ParseAnchor),
+ DATA_TYPE("[handles]", 0, &ValidationTestInputParser::ParseHandles)
+};
+
+const size_t ValidationTestInputParser::kDataTypeCount =
+ sizeof(ValidationTestInputParser::kDataTypes) /
+ sizeof(ValidationTestInputParser::kDataTypes[0]);
+
+ValidationTestInputParser::ValidationTestInputParser(
+ const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message)
+ : input_(input),
+ input_cursor_(0),
+ data_(data),
+ num_handles_(num_handles),
+ error_message_(error_message) {
+ assert(data_);
+ assert(num_handles_);
+ assert(error_message_);
+ data_->clear();
+ *num_handles_ = 0;
+ error_message_->clear();
+}
+
+ValidationTestInputParser::~ValidationTestInputParser() {
+}
+
+bool ValidationTestInputParser::Run() {
+ Range range;
+ bool result = true;
+ while (result && GetNextItem(&range))
+ result = ParseItem(range);
+
+ if (!result) {
+ *error_message_ = "Error occurred when parsing " +
+ std::string(range.first, range.second);
+ } else if (!pending_distance_items_.empty()) {
+ // We have parsed all the contents in |input_| successfully, but there are
+ // unmatched dist4/8 items.
+ *error_message_ = "Error occurred when matching [dist4/8] and [anchr].";
+ result = false;
+ }
+ if (!result) {
+ data_->clear();
+ *num_handles_ = 0;
+ } else {
+ assert(error_message_->empty());
+ }
+
+ return result;
+}
+
+bool ValidationTestInputParser::GetNextItem(Range* range) {
+ const char kWhitespaceChars[] = " \t\n\r";
+ const char kItemDelimiters[] = " \t\n\r/";
+ const char kEndOfLineChars[] = "\n\r";
+ while (true) {
+ // Skip leading whitespaces.
+ // If there are no non-whitespace characters left, |input_cursor_| will be
+ // set to std::npos.
+ input_cursor_ = input_.find_first_not_of(kWhitespaceChars, input_cursor_);
+
+ if (input_cursor_ >= input_.size())
+ return false;
+
+ if (StartsWith(Range(&input_[0] + input_cursor_,
+ &input_[0] + input_.size()),
+ "//", 2)) {
+ // Skip contents until the end of the line.
+ input_cursor_ = input_.find_first_of(kEndOfLineChars, input_cursor_);
+ } else {
+ range->first = &input_[0] + input_cursor_;
+ input_cursor_ = input_.find_first_of(kItemDelimiters, input_cursor_);
+ range->second = input_cursor_ >= input_.size() ?
+ &input_[0] + input_.size() : &input_[0] + input_cursor_;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ValidationTestInputParser::ParseItem(const Range& range) {
+ for (size_t i = 0; i < kDataTypeCount; ++i) {
+ if (StartsWith(range, kDataTypes[i].name, kDataTypes[i].name_size)) {
+ return (this->*kDataTypes[i].parse_data_func)(
+ kDataTypes[i],
+ std::string(range.first + kDataTypes[i].name_size, range.second));
+ }
+ }
+
+ // "[u1]" is optional.
+ return ParseUnsignedInteger(kDataTypes[0],
+ std::string(range.first, range.second));
+}
+
+bool ValidationTestInputParser::ParseUnsignedInteger(
+ const DataType& type, const std::string& value_string) {
+ unsigned long long int value;
+ if (!ConvertToUnsignedInteger(value_string, &value))
+ return false;
+
+ switch (type.data_size) {
+ case 1:
+ return ConvertAndAppendData<uint8_t>(value);
+ case 2:
+ return ConvertAndAppendData<uint16_t>(value);
+ case 4:
+ return ConvertAndAppendData<uint32_t>(value);
+ case 8:
+ return ConvertAndAppendData<uint64_t>(value);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool ValidationTestInputParser::ParseSignedInteger(
+ const DataType& type, const std::string& value_string) {
+ long long int value;
+ if (sscanf(value_string.c_str(), "%lli", &value) != 1)
+ return false;
+
+ switch (type.data_size) {
+ case 1:
+ return ConvertAndAppendData<int8_t>(value);
+ case 2:
+ return ConvertAndAppendData<int16_t>(value);
+ case 4:
+ return ConvertAndAppendData<int32_t>(value);
+ case 8:
+ return ConvertAndAppendData<int64_t>(value);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool ValidationTestInputParser::ParseFloat(
+ const DataType& type, const std::string& value_string) {
+ MOJO_COMPILE_ASSERT(sizeof(float) == 4, float_size_is_not_4);
+
+ float value;
+ if (sscanf(value_string.c_str(), "%f", &value) != 1)
+ return false;
+
+ AppendData(value);
+ return true;
+}
+
+bool ValidationTestInputParser::ParseDouble(const DataType& type,
+ const std::string& value_string) {
+ MOJO_COMPILE_ASSERT(sizeof(double) == 8, double_size_is_not_8);
+
+ double value;
+ if (sscanf(value_string.c_str(), "%lf", &value) != 1)
+ return false;
+
+ AppendData(value);
+ return true;
+}
+
+bool ValidationTestInputParser::ParseBinarySequence(
+ const DataType& type, const std::string& value_string) {
+ if (value_string.size() != 8)
+ return false;
+
+ uint8_t value = 0;
+ for (std::string::const_iterator iter = value_string.begin();
+ iter != value_string.end();
+ ++iter) {
+ value <<= 1;
+ if (*iter == '1')
+ value++;
+ else if (*iter != '0')
+ return false;
+ }
+ AppendData(value);
+ return true;
+}
+
+bool ValidationTestInputParser::ParseDistance(const DataType& type,
+ const std::string& value_string) {
+ if (pending_distance_items_.find(value_string) !=
+ pending_distance_items_.end())
+ return false;
+
+ PendingDistanceItem item = {data_->size(), type.data_size};
+ data_->resize(data_->size() + type.data_size);
+ pending_distance_items_[value_string] = item;
+
+ return true;
+}
+
+bool ValidationTestInputParser::ParseAnchor(const DataType& type,
+ const std::string& value_string) {
+ if (anchors_.find(value_string) != anchors_.end())
+ return false;
+ anchors_.insert(value_string);
+
+ std::map<std::string, PendingDistanceItem>::iterator iter =
+ pending_distance_items_.find(value_string);
+ if (iter == pending_distance_items_.end())
+ return false;
+
+ PendingDistanceItem dist_item = iter->second;
+ pending_distance_items_.erase(iter);
+
+ size_t distance = data_->size() - dist_item.pos;
+ switch (dist_item.data_size) {
+ case 4:
+ return ConvertAndFillData<uint32_t>(dist_item.pos, distance);
+ case 8:
+ return ConvertAndFillData<uint64_t>(dist_item.pos, distance);
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool ValidationTestInputParser::ParseHandles(const DataType& type,
+ const std::string& value_string) {
+ // It should be the first item.
+ if (!data_->empty())
+ return false;
+
+ unsigned long long int value;
+ if (!ConvertToUnsignedInteger(value_string, &value))
+ return false;
+
+ if (value > std::numeric_limits<size_t>::max())
+ return false;
+
+ *num_handles_ = static_cast<size_t>(value);
+ return true;
+}
+
+bool ValidationTestInputParser::StartsWith(const Range& range,
+ const char* prefix,
+ size_t prefix_length) {
+ if (static_cast<size_t>(range.second - range.first) < prefix_length)
+ return false;
+
+ return memcmp(range.first, prefix, prefix_length) == 0;
+}
+
+bool ValidationTestInputParser::ConvertToUnsignedInteger(
+ const std::string& value_string,
+ unsigned long long int* value) {
+ const char* format = nullptr;
+ if (value_string.find_first_of("xX") != std::string::npos)
+ format = "%llx";
+ else
+ format = "%llu";
+ return sscanf(value_string.c_str(), format, value) == 1;
+}
+
+} // namespace
+
+bool ParseValidationTestInput(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message) {
+ ValidationTestInputParser parser(input, data, num_handles, error_message);
+ return parser.Run();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.h b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h
new file mode 100644
index 0000000..c8821cd
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium 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_TESTS_VALIDATION_TEST_INPUT_PARSER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+namespace mojo {
+namespace test {
+
+// Input Format of Mojo Message Validation Tests.
+//
+// Data items are separated by whitespaces:
+// - ' ' (0x20) space;
+// - '\t' (0x09) horizontal tab;
+// - '\n' (0x0a) newline;
+// - '\r' (0x0d) carriage return.
+// A comment starts with //, extending to the end of the line.
+// Each data item is of the format [<type>]<value>. The types defined and the
+// corresponding value formats are described below.
+//
+// Type: u1 / u2 / u4 / u8
+// Description: Little-endian 1/2/4/8-byte unsigned integer.
+// Value Format:
+// - Decimal integer: 0|[1-9][0-9]*
+// - Hexadecimal integer: 0[xX][0-9a-fA-F]+
+// - The type prefix (including the square brackets) of 1-byte unsigned
+// integer is optional.
+//
+// Type: s1 / s2 / s4 / s8
+// Description: Little-endian 1/2/4/8-byte signed integer.
+// Value Format:
+// - Decimal integer: [-+]?(0|[1-9][0-9]*)
+// - Hexadecimal integer: [-+]?0[xX][0-9a-fA-F]+
+//
+// Type: b
+// Description: Binary sequence of 1 byte.
+// Value Format: [01]{8}
+//
+// Type: f / d
+// Description: Little-endian IEEE-754 format of float (4 bytes) and double (8
+// bytes).
+// Value Format: [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?
+//
+// Type: dist4 / dist8
+// Description: Little-endian 4/8-byte unsigned integer. The actual value is set
+// to the byte distance from the location of this integer to the location of the
+// anchr item with the same ID. A dist8 and anchr pair can be used to easily
+// represent an encoded pointer. A dist4 and anchr pair can be used to easily
+// calculate struct/array size.
+// Value Format: The value is an ID: [0-9a-zA-Z_]+
+//
+// Type: anchr
+// Description: Mark an anchor location. It doesn’t translate into any actual
+// data.
+// Value Format: The value is an ID of the same format as that of dist4/8.
+//
+// Type: handles
+// Description: The number of handles that are associated with the message. This
+// special item is not part of the message data. If specified, it should be the
+// first item.
+// Value Format: The same format as u1/2/4/8.
+//
+// EXAMPLE:
+//
+// Suppose you have the following Mojo types defined:
+// struct Bar {
+// int32 a;
+// bool b;
+// bool c;
+// };
+// struct Foo {
+// Bar x;
+// uint32 y;
+// };
+//
+// The following describes a valid message whose payload is a Foo struct:
+// // message header
+// [dist4]message_header // num_bytes
+// [u4]3 // num_fields
+// [u4]0 // type
+// [u4]1 // flags
+// [u8]1234 // request_id
+// [anchr]message_header
+//
+// // payload
+// [dist4]foo // num_bytes
+// [u4]2 // num_fields
+// [dist8]bar_ptr // x
+// [u4]0xABCD // y
+// [u4]0 // padding
+// [anchr]foo
+//
+// [anchr]bar_ptr
+// [dist4]bar // num_bytes
+// [u4]3 // num_fields
+// [s4]-1 // a
+// [b]00000010 // b and c
+// 0 0 0 // padding
+// [anchr]bar
+
+// Parses validation test input.
+// On success, |data| and |num_handles| store the parsing result,
+// |error_message| is cleared; on failure, |error_message| is set to a message
+// describing the error, |data| is cleared and |num_handles| set to 0.
+// Note: For now, this method only works on little-endian platforms.
+bool ParseValidationTestInput(const std::string& input,
+ std::vector<uint8_t>* data,
+ size_t* num_handles,
+ std::string* error_message);
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_
diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc
new file mode 100644
index 0000000..bf0894c
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc
@@ -0,0 +1,434 @@
+// Copyright 2014 The Chromium 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 <algorithm>
+#include <string>
+#include <vector>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/cpp/bindings/interface_impl.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/lib/connector.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/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+template <typename T>
+void Append(std::vector<uint8_t>* data_vector, T data) {
+ size_t pos = data_vector->size();
+ data_vector->resize(pos + sizeof(T));
+ memcpy(&(*data_vector)[pos], &data, sizeof(T));
+}
+
+bool TestInputParser(const std::string& input,
+ bool expected_result,
+ const std::vector<uint8_t>& expected_data,
+ size_t expected_num_handles) {
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ std::string error_message;
+
+ bool result = ParseValidationTestInput(input, &data, &num_handles,
+ &error_message);
+ if (expected_result) {
+ if (result && error_message.empty() &&
+ expected_data == data && expected_num_handles == num_handles) {
+ return true;
+ }
+
+ // Compare with an empty string instead of checking |error_message.empty()|,
+ // so that the message will be printed out if the two are not equal.
+ EXPECT_EQ(std::string(), error_message);
+ EXPECT_EQ(expected_data, data);
+ EXPECT_EQ(expected_num_handles, num_handles);
+ return false;
+ }
+
+ EXPECT_FALSE(error_message.empty());
+ return !result && !error_message.empty();
+}
+
+std::vector<std::string> GetMatchingTests(const std::vector<std::string>& names,
+ const std::string& prefix) {
+ const std::string suffix = ".data";
+ std::vector<std::string> tests;
+ for (size_t i = 0; i < names.size(); ++i) {
+ if (names[i].size() >= suffix.size() &&
+ names[i].substr(0, prefix.size()) == prefix &&
+ names[i].substr(names[i].size() - suffix.size()) == suffix)
+ tests.push_back(names[i].substr(0, names[i].size() - suffix.size()));
+ }
+ return tests;
+}
+
+bool ReadFile(const std::string& path, std::string* result) {
+ FILE* fp = OpenSourceRootRelativeFile(path.c_str());
+ if (!fp) {
+ ADD_FAILURE() << "File not found: " << path;
+ return false;
+ }
+ fseek(fp, 0, SEEK_END);
+ size_t size = static_cast<size_t>(ftell(fp));
+ if (size == 0) {
+ result->clear();
+ fclose(fp);
+ return true;
+ }
+ fseek(fp, 0, SEEK_SET);
+ result->resize(size);
+ size_t size_read = fread(&result->at(0), 1, size, fp);
+ fclose(fp);
+ return size == size_read;
+}
+
+bool ReadAndParseDataFile(const std::string& path,
+ std::vector<uint8_t>* data,
+ size_t* num_handles) {
+ std::string input;
+ if (!ReadFile(path, &input))
+ return false;
+
+ std::string error_message;
+ if (!ParseValidationTestInput(input, data, num_handles, &error_message)) {
+ ADD_FAILURE() << error_message;
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadResultFile(const std::string& path, std::string* result) {
+ if (!ReadFile(path, result))
+ return false;
+
+ // Result files are new-line delimited text files. Remove any CRs.
+ result->erase(std::remove(result->begin(), result->end(), '\r'),
+ result->end());
+
+ // Remove trailing LFs.
+ size_t pos = result->find_last_not_of('\n');
+ if (pos == std::string::npos)
+ result->clear();
+ else
+ result->resize(pos + 1);
+
+ return true;
+}
+
+std::string GetPath(const std::string& root, const std::string& suffix) {
+ return "mojo/public/interfaces/bindings/tests/data/validation/" +
+ root + suffix;
+}
+
+// |message| should be a newly created object.
+bool ReadTestCase(const std::string& test,
+ Message* message,
+ std::string* expected) {
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) ||
+ !ReadResultFile(GetPath(test, ".expected"), expected)) {
+ return false;
+ }
+
+ message->AllocUninitializedData(static_cast<uint32_t>(data.size()));
+ if (!data.empty())
+ memcpy(message->mutable_data(), &data[0], data.size());
+ message->mutable_handles()->resize(num_handles);
+
+ return true;
+}
+
+void RunValidationTests(const std::string& prefix,
+ MessageReceiver* test_message_receiver) {
+ std::vector<std::string> names =
+ EnumerateSourceRootRelativeDirectory(GetPath("", ""));
+ std::vector<std::string> tests = GetMatchingTests(names, prefix);
+
+ for (size_t i = 0; i < tests.size(); ++i) {
+ Message message;
+ std::string expected;
+ ASSERT_TRUE(ReadTestCase(tests[i], &message, &expected));
+
+ std::string result;
+ mojo::internal::ValidationErrorObserverForTesting observer;
+ bool unused MOJO_ALLOW_UNUSED = test_message_receiver->Accept(&message);
+ if (observer.last_error() == mojo::internal::VALIDATION_ERROR_NONE)
+ result = "PASS";
+ else
+ result = mojo::internal::ValidationErrorToString(observer.last_error());
+
+ EXPECT_EQ(expected, result) << "failed test: " << tests[i];
+ }
+}
+
+class DummyMessageReceiver : public MessageReceiver {
+ public:
+ virtual bool Accept(Message* message) override {
+ return true; // Any message is OK.
+ }
+};
+
+class ValidationTest : public testing::Test {
+ public:
+ virtual ~ValidationTest() {
+ }
+
+ private:
+ Environment env_;
+};
+
+class ValidationIntegrationTest : public ValidationTest {
+ public:
+ ValidationIntegrationTest() : test_message_receiver_(nullptr) {
+ }
+
+ virtual ~ValidationIntegrationTest() {
+ }
+
+ virtual void SetUp() override {
+ ScopedMessagePipeHandle tester_endpoint;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateMessagePipe(nullptr, &tester_endpoint, &testee_endpoint_));
+ test_message_receiver_ =
+ new TestMessageReceiver(this, tester_endpoint.Pass());
+ }
+
+ virtual void TearDown() override {
+ delete test_message_receiver_;
+ test_message_receiver_ = nullptr;
+
+ // Make sure that the other end receives the OnConnectionError()
+ // notification.
+ PumpMessages();
+ }
+
+ MessageReceiver* test_message_receiver() {
+ return test_message_receiver_;
+ }
+
+ ScopedMessagePipeHandle testee_endpoint() {
+ return testee_endpoint_.Pass();
+ }
+
+ private:
+ class TestMessageReceiver : public MessageReceiver {
+ public:
+ TestMessageReceiver(ValidationIntegrationTest* owner,
+ ScopedMessagePipeHandle handle)
+ : owner_(owner),
+ connector_(handle.Pass()) {
+ }
+ virtual ~TestMessageReceiver() {
+ }
+
+ virtual bool Accept(Message* message) override {
+ bool rv = connector_.Accept(message);
+ owner_->PumpMessages();
+ return rv;
+ }
+
+ public:
+ ValidationIntegrationTest* owner_;
+ mojo::internal::Connector connector_;
+ };
+
+ void PumpMessages() {
+ loop_.RunUntilIdle();
+ }
+
+ RunLoop loop_;
+ TestMessageReceiver* test_message_receiver_;
+ ScopedMessagePipeHandle testee_endpoint_;
+};
+
+class IntegrationTestInterface1Client : public IntegrationTestInterface1 {
+ public:
+ virtual ~IntegrationTestInterface1Client() {
+ }
+
+ virtual void Method0(BasicStructPtr param0) override {}
+};
+
+class IntegrationTestInterface1Impl
+ : public InterfaceImpl<IntegrationTestInterface1> {
+ public:
+ virtual ~IntegrationTestInterface1Impl() {
+ }
+
+ virtual void Method0(BasicStructPtr param0) override {}
+};
+
+TEST_F(ValidationTest, InputParser) {
+ {
+ // The parser, as well as Append() defined above, assumes that this code is
+ // running on a little-endian platform. Test whether that is true.
+ uint16_t x = 1;
+ ASSERT_EQ(1, *(reinterpret_cast<char*>(&x)));
+ }
+ {
+ // Test empty input.
+ std::string input;
+ std::vector<uint8_t> expected;
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ // Test input that only consists of comments and whitespaces.
+ std::string input = " \t // hello world \n\r \t// the answer is 42 ";
+ std::vector<uint8_t> expected;
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n"
+ "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(0x10));
+ Append(&expected, static_cast<uint16_t>(65535));
+ Append(&expected, static_cast<uint32_t>(65536));
+ Append(&expected, static_cast<uint64_t>(0xffffffffffffffff));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint8_t>(0xff));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40";
+ std::vector<uint8_t> expected;
+ Append(&expected, -static_cast<int64_t>(0x800));
+ Append(&expected, static_cast<int8_t>(-128));
+ Append(&expected, static_cast<int16_t>(0));
+ Append(&expected, static_cast<int32_t>(-40));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(11));
+ Append(&expected, static_cast<uint8_t>(128));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[f]+.3e9 [d]-10.03";
+ std::vector<uint8_t> expected;
+ Append(&expected, +.3e9f);
+ Append(&expected, -10.03);
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint32_t>(14));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint64_t>(9));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "// This message has handles! \n[handles]50 [u8]2";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint64_t>(2));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 50));
+ }
+
+ // Test some failure cases.
+ {
+ const char* error_inputs[] = {
+ "/ hello world",
+ "[u1]x",
+ "[u2]-1000",
+ "[u1]0x100",
+ "[s2]-0x8001",
+ "[b]1",
+ "[b]1111111k",
+ "[dist4]unmatched",
+ "[anchr]hello [dist8]hello",
+ "[dist4]a [dist4]a [anchr]a",
+ "[dist4]a [anchr]a [dist4]a [anchr]a",
+ "0 [handles]50",
+ nullptr
+ };
+
+ for (size_t i = 0; error_inputs[i]; ++i) {
+ std::vector<uint8_t> expected;
+ if (!TestInputParser(error_inputs[i], false, expected, 0))
+ ADD_FAILURE() << "Unexpected test result for: " << error_inputs[i];
+ }
+ }
+}
+
+TEST_F(ValidationTest, Conformance) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::internal::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::internal::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("conformance_", validators.GetHead());
+}
+
+TEST_F(ValidationTest, NotImplemented) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::internal::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::internal::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("not_implemented_", validators.GetHead());
+}
+
+TEST_F(ValidationIntegrationTest, InterfacePtr) {
+ // Test that InterfacePtr<X> applies the correct validators and they don't
+ // conflict with each other:
+ // - MessageHeaderValidator
+ // - X::Client::RequestValidator_
+ // - X::ResponseValidator_
+
+ IntegrationTestInterface1Client interface1_client;
+ IntegrationTestInterface2Ptr interface2_ptr =
+ MakeProxy<IntegrationTestInterface2>(testee_endpoint().Pass());
+ interface2_ptr.set_client(&interface1_client);
+ interface2_ptr.internal_state()->router_for_testing()->EnableTestingMode();
+
+ RunValidationTests("integration_", test_message_receiver());
+}
+
+TEST_F(ValidationIntegrationTest, InterfaceImpl) {
+ // Test that InterfaceImpl<X> applies the correct validators and they don't
+ // conflict with each other:
+ // - MessageHeaderValidator
+ // - X::RequestValidator_
+ // - X::Client::ResponseValidator_
+
+ // |interface1_impl| will delete itself when the pipe is closed.
+ IntegrationTestInterface1Impl* interface1_impl =
+ BindToPipe(new IntegrationTestInterface1Impl(), testee_endpoint().Pass());
+ interface1_impl->internal_state()->router()->EnableTestingMode();
+
+ RunValidationTests("integration_", test_message_receiver());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/type_converter.h b/mojo/public/cpp/bindings/type_converter.h
new file mode 100644
index 0000000..ff94cda
--- /dev/null
+++ b/mojo/public/cpp/bindings/type_converter.h
@@ -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.
+
+#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> struct TypeConverter;
+// to perform type conversion for Mojom-defined structs and arrays. Here, T is
+// the target type; U is the input type.
+//
+// Specializations should implement the following interfaces:
+// namespace mojo {
+// template <>
+// struct TypeConverter<X, Y> {
+// static X Convert(const Y& input);
+// };
+// template <>
+// struct TypeConverter<Y, X> {
+// static Y Convert(const X& 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 <>
+// struct TypeConverter<geometry::PointPtr, gfx::Point> {
+// static geometry::PointPtr Convert(const gfx::Point& input) {
+// geometry::PointPtr result;
+// result->x = input.x();
+// result->y = input.y();
+// return result.Pass();
+// }
+// };
+// template <>
+// struct TypeConverter<gfx::Point, geometry::PointPtr> {
+// static gfx::Point Convert(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);
+//
+// // Inferring the input type using the ConvertTo helper function.
+// gfx::Point pt2 = ConvertTo<gfx::Point>(input);
+// }
+//
+template <typename T, typename U>
+struct TypeConverter;
+
+// The following specialization is useful when you are converting between
+// Array<POD> and std::vector<POD>.
+template <typename T>
+struct TypeConverter<T, T> {
+ static T Convert(const T& obj) { return obj; }
+};
+
+// The following helper function is useful for shorthand. The compiler can infer
+// the input type, so you can write:
+// OutputType out = ConvertTo<OutputType>(input);
+template <typename T, typename U>
+inline T ConvertTo(const U& obj) {
+ return TypeConverter<T, U>::Convert(obj);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_
diff --git a/mojo/public/cpp/environment/BUILD.gn b/mojo/public/cpp/environment/BUILD.gn
new file mode 100644
index 0000000..a0d6ca7
--- /dev/null
+++ b/mojo/public/cpp/environment/BUILD.gn
@@ -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.
+
+source_set("environment") {
+ sources = [
+ "logging.h",
+ "environment.h",
+ ]
+
+ public_deps = [ "//mojo/public/c/environment" ]
+
+ deps = [ "//mojo/public/cpp/system" ]
+}
+
+source_set("standalone") {
+ sources = [
+ "lib/default_async_waiter.cc",
+ "lib/default_async_waiter.h",
+ "lib/default_logger.cc",
+ "lib/default_logger.h",
+ "lib/environment.cc",
+ "lib/logging.cc",
+ ]
+
+ public_deps = [ ":environment" ]
+
+ deps = [
+ "//mojo/public/c/environment",
+ "//mojo/public/cpp/utility",
+ ]
+}
diff --git a/mojo/public/cpp/environment/environment.h b/mojo/public/cpp/environment/environment.h
new file mode 100644
index 0000000..48f4c26
--- /dev/null
+++ b/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/mojo/public/cpp/environment/lib/DEPS b/mojo/public/cpp/environment/lib/DEPS
new file mode 100644
index 0000000..1889e1f
--- /dev/null
+++ b/mojo/public/cpp/environment/lib/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+mojo/public/cpp/environment",
+ "+mojo/public/cpp/utility",
+]
diff --git a/mojo/public/cpp/environment/lib/default_async_waiter.cc b/mojo/public/cpp/environment/lib/default_async_waiter.cc
new file mode 100644
index 0000000..b03623e
--- /dev/null
+++ b/mojo/public/cpp/environment/lib/default_async_waiter.cc
@@ -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.
+
+#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) override {
+ NotifyCallback(MOJO_RESULT_OK);
+ }
+
+ virtual void OnHandleError(const Handle& handle, MojoResult result) 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/mojo/public/cpp/environment/lib/default_async_waiter.h b/mojo/public/cpp/environment/lib/default_async_waiter.h
new file mode 100644
index 0000000..49ce233
--- /dev/null
+++ b/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/mojo/public/cpp/environment/lib/default_logger.cc b/mojo/public/cpp/environment/lib/default_logger.cc
new file mode 100644
index 0000000..af4a628
--- /dev/null
+++ b/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/mojo/public/cpp/environment/lib/default_logger.h b/mojo/public/cpp/environment/lib/default_logger.h
new file mode 100644
index 0000000..4db3233
--- /dev/null
+++ b/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/mojo/public/cpp/environment/lib/environment.cc b/mojo/public/cpp/environment/lib/environment.cc
new file mode 100644
index 0000000..5fde8f7
--- /dev/null
+++ b/mojo/public/cpp/environment/lib/environment.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/environment/environment.h"
+
+#include <assert.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 = nullptr;
+const MojoLogger* g_default_logger = nullptr;
+
+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(nullptr, nullptr);
+}
+
+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?
+ g_default_async_waiter = nullptr;
+ g_default_logger = nullptr;
+}
+
+// static
+const MojoAsyncWaiter* Environment::GetDefaultAsyncWaiter() {
+ assert(g_default_async_waiter); // Fails if not "inside" |Environment|.
+ return g_default_async_waiter;
+}
+
+// static
+const MojoLogger* Environment::GetDefaultLogger() {
+ assert(g_default_logger); // Fails if not "inside" |Environment|.
+ return g_default_logger;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/environment/lib/logging.cc b/mojo/public/cpp/environment/lib/logging.cc
new file mode 100644
index 0000000..990626d
--- /dev/null
+++ b/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/mojo/public/cpp/environment/logging.h b/mojo/public/cpp/environment/logging.h
new file mode 100644
index 0000000..a3e2cef
--- /dev/null
+++ b/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/mojo/public/cpp/environment/tests/BUILD.gn b/mojo/public/cpp/environment/tests/BUILD.gn
new file mode 100644
index 0000000..55ded10
--- /dev/null
+++ b/mojo/public/cpp/environment/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_environment_unittests
+test("mojo_public_environment_unittests") {
+ sources = [
+ "async_waiter_unittest.cc",
+ "logger_unittest.cc",
+ "logging_unittest.cc",
+ ]
+
+ deps = [
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/public/c/environment",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/cpp/utility",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/cpp/environment/tests/async_waiter_unittest.cc b/mojo/public/cpp/environment/tests/async_waiter_unittest.cc
new file mode 100644
index 0000000..0992f89
--- /dev/null
+++ b/mojo/public/cpp/environment/tests/async_waiter_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2014 The Chromium 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 "mojo/public/c/environment/async_waiter.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/cpp/test_support/test_utils.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class TestAsyncWaitCallback {
+ public:
+ TestAsyncWaitCallback() : result_count_(0), last_result_(MOJO_RESULT_OK) {
+ }
+ virtual ~TestAsyncWaitCallback() {}
+
+ int result_count() const { return result_count_; }
+
+ MojoResult last_result() const { return last_result_; }
+
+ // MojoAsyncWaitCallback:
+ static void OnHandleReady(void* closure, MojoResult result) {
+ TestAsyncWaitCallback* self = static_cast<TestAsyncWaitCallback*>(closure);
+ self->result_count_++;
+ self->last_result_ = result;
+ }
+
+ private:
+ int result_count_;
+ MojoResult last_result_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestAsyncWaitCallback);
+};
+
+MojoAsyncWaitID CallAsyncWait(const Handle& handle,
+ MojoHandleSignals signals,
+ TestAsyncWaitCallback* callback) {
+ return Environment::GetDefaultAsyncWaiter()->AsyncWait(
+ handle.value(),
+ signals,
+ MOJO_DEADLINE_INDEFINITE,
+ &TestAsyncWaitCallback::OnHandleReady,
+ callback);
+}
+
+void CallCancelWait(MojoAsyncWaitID wait_id) {
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id);
+}
+
+class AsyncWaiterTest : public testing::Test {
+ public:
+ AsyncWaiterTest() {}
+
+ private:
+ Environment environment_;
+ RunLoop run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AsyncWaiterTest);
+};
+
+// Verifies AsyncWaitCallback is notified when pipe is ready.
+TEST_F(AsyncWaiterTest, CallbackNotified) {
+ TestAsyncWaitCallback callback;
+ MessagePipe test_pipe;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string()));
+
+ CallAsyncWait(test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ &callback);
+ RunLoop::current()->Run();
+ EXPECT_EQ(1, callback.result_count());
+ EXPECT_EQ(MOJO_RESULT_OK, callback.last_result());
+}
+
+// Verifies 2 AsyncWaitCallbacks are notified when there pipes are ready.
+TEST_F(AsyncWaiterTest, TwoCallbacksNotified) {
+ TestAsyncWaitCallback callback1;
+ TestAsyncWaitCallback callback2;
+ MessagePipe test_pipe1;
+ MessagePipe test_pipe2;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe1.handle1.get(), std::string()));
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe2.handle1.get(), std::string()));
+
+ CallAsyncWait(test_pipe1.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ &callback1);
+ CallAsyncWait(test_pipe2.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ &callback2);
+
+ RunLoop::current()->Run();
+ EXPECT_EQ(1, callback1.result_count());
+ EXPECT_EQ(MOJO_RESULT_OK, callback1.last_result());
+ EXPECT_EQ(1, callback2.result_count());
+ EXPECT_EQ(MOJO_RESULT_OK, callback2.last_result());
+}
+
+// Verifies cancel works.
+TEST_F(AsyncWaiterTest, CancelCallback) {
+ TestAsyncWaitCallback callback;
+ MessagePipe test_pipe;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string()));
+
+ CallCancelWait(
+ CallAsyncWait(test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ &callback));
+ RunLoop::current()->Run();
+ EXPECT_EQ(0, callback.result_count());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/environment/tests/logger_unittest.cc b/mojo/public/cpp/environment/tests/logger_unittest.cc
new file mode 100644
index 0000000..e30a84b
--- /dev/null
+++ b/mojo/public/cpp/environment/tests/logger_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.
+
+#include "mojo/public/c/environment/logger.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+TEST(LoggerTest, Basic) {
+ Environment environment;
+ const MojoLogger* const logger = Environment::GetDefaultLogger();
+
+ logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE-1, "Logged at VERBOSE-1 level");
+ logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE, "Logged at VERBOSE level");
+ logger->LogMessage(MOJO_LOG_LEVEL_INFO, "Logged at INFO level");
+ logger->LogMessage(MOJO_LOG_LEVEL_WARNING, "Logged at WARNING level");
+ logger->LogMessage(MOJO_LOG_LEVEL_ERROR, "Logged at ERROR level");
+
+ // This should kill us:
+ EXPECT_DEATH_IF_SUPPORTED({
+ logger->LogMessage(MOJO_LOG_LEVEL_FATAL, "Logged at FATAL level");
+ }, "");
+}
+
+TEST(LoggerTest, LogLevels) {
+ Environment environment;
+ const MojoLogger* const logger = Environment::GetDefaultLogger();
+
+ for (MojoLogLevel log_level = MOJO_LOG_LEVEL_VERBOSE-1;
+ log_level <= MOJO_LOG_LEVEL_FATAL+1;
+ log_level++) {
+ logger->SetMinimumLogLevel(log_level);
+
+ if (log_level <= MOJO_LOG_LEVEL_FATAL)
+ EXPECT_EQ(log_level, logger->GetMinimumLogLevel());
+ else
+ EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, logger->GetMinimumLogLevel());
+
+ logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE-1, "Logged at VERBOSE-1 level");
+ logger->LogMessage(MOJO_LOG_LEVEL_VERBOSE, "Logged at VERBOSE level");
+ logger->LogMessage(MOJO_LOG_LEVEL_INFO, "Logged at INFO level");
+ logger->LogMessage(MOJO_LOG_LEVEL_WARNING, "Logged at WARNING level");
+ logger->LogMessage(MOJO_LOG_LEVEL_ERROR, "Logged at ERROR level");
+
+ // This should kill us:
+ EXPECT_DEATH_IF_SUPPORTED({
+ logger->LogMessage(MOJO_LOG_LEVEL_FATAL, "Logged at FATAL level");
+ }, "");
+ }
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/environment/tests/logging_unittest.cc b/mojo/public/cpp/environment/tests/logging_unittest.cc
new file mode 100644
index 0000000..45f527e
--- /dev/null
+++ b/mojo/public/cpp/environment/tests/logging_unittest.cc
@@ -0,0 +1,474 @@
+// Copyright 2014 The Chromium 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 <stdlib.h>
+
+#include <sstream>
+#include <string>
+
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/environment/logging.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// A macro, so it can be automatically joined with other string literals. (Not
+// simply __FILE__, since that may contain a path.)
+#define OUR_FILENAME "logging_unittest.cc"
+
+namespace mojo {
+namespace {
+
+class PtrToMemberHelper {
+ public:
+ int member;
+};
+
+bool DcheckTestHelper(bool* was_called) {
+ *was_called = true;
+ return false;
+}
+
+class LoggingTest : public testing::Test {
+ public:
+ LoggingTest() : environment_(nullptr, &kMockLogger) {
+ minimum_log_level_ = MOJO_LOG_LEVEL_INFO;
+ ResetMockLogger();
+ }
+ virtual ~LoggingTest() {}
+
+ protected:
+ // Note: Does not reset |minimum_log_level_|.
+ static void ResetMockLogger() {
+ log_message_was_called_ = false;
+ last_log_level_ = MOJO_LOG_LEVEL_INFO;
+ last_message_.clear();
+ }
+
+ // A function returning |bool| that shouldn't be called.
+ static bool NotCalledCondition() {
+ not_called_condition_was_called_ = true;
+ return false;
+ }
+
+ static bool log_message_was_called() { return log_message_was_called_; }
+ static MojoLogLevel last_log_level() { return last_log_level_; }
+ static const std::string& last_message() { return last_message_; }
+ static bool not_called_condition_was_called() {
+ return not_called_condition_was_called_;
+ }
+
+ private:
+ // Note: We record calls even if |log_level| is below |minimum_log_level_|
+ // (since the macros should mostly avoid this, and we want to be able to check
+ // that they do).
+ static void MockLogMessage(MojoLogLevel log_level, const char* message) {
+ log_message_was_called_ = true;
+ last_log_level_ = log_level;
+ last_message_ = message;
+ }
+
+ static MojoLogLevel MockGetMinimumLogLevel() {
+ return minimum_log_level_;
+ }
+
+ static void MockSetMinimumLogLevel(MojoLogLevel minimum_log_level) {
+ minimum_log_level_ = minimum_log_level;
+ }
+
+ Environment environment_;
+
+ static const MojoLogger kMockLogger;
+ static MojoLogLevel minimum_log_level_;
+ static bool log_message_was_called_;
+ static MojoLogLevel last_log_level_;
+ static std::string last_message_;
+ static bool not_called_condition_was_called_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(LoggingTest);
+};
+
+// static
+const MojoLogger LoggingTest::kMockLogger = {
+ &LoggingTest::MockLogMessage,
+ &LoggingTest::MockGetMinimumLogLevel,
+ &LoggingTest::MockSetMinimumLogLevel
+};
+
+// static
+MojoLogLevel LoggingTest::minimum_log_level_ = MOJO_LOG_LEVEL_INFO;
+
+// static
+bool LoggingTest::log_message_was_called_ = MOJO_LOG_LEVEL_INFO;
+
+// static
+MojoLogLevel LoggingTest::last_log_level_ = MOJO_LOG_LEVEL_INFO;
+
+// static
+std::string LoggingTest::last_message_;
+
+// static
+bool LoggingTest::not_called_condition_was_called_ = false;
+
+std::string ExpectedLogMessage(int line, const char* message) {
+ std::ostringstream s;
+ s << OUR_FILENAME "(" << line << "): " << message;
+ return s.str();
+}
+
+TEST_F(LoggingTest, InternalLogMessage) {
+ internal::LogMessage("foo.cc", 123, MOJO_LOG_LEVEL_INFO).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage("./path/to/foo.cc", 123, MOJO_LOG_LEVEL_WARNING).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_WARNING, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage("/path/to/foo.cc", 123, MOJO_LOG_LEVEL_ERROR).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage("path/to/foo.cc", 123, MOJO_LOG_LEVEL_FATAL).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage(".\\xy\\foo.cc", 123, MOJO_LOG_LEVEL_VERBOSE).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_VERBOSE, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage("xy\\foo.cc", 123, MOJO_LOG_LEVEL_VERBOSE-1).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_VERBOSE-1, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage("C:\\xy\\foo.cc", 123, MOJO_LOG_LEVEL_VERBOSE-9).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_VERBOSE-9, last_log_level());
+ EXPECT_EQ("foo.cc(123): hello world", last_message());
+
+ ResetMockLogger();
+
+ internal::LogMessage(__FILE__, 123, MOJO_LOG_LEVEL_INFO).stream()
+ << "hello " << "world";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ(OUR_FILENAME "(123): hello world", last_message());
+}
+
+TEST_F(LoggingTest, LogStream) {
+ MOJO_LOG_STREAM(INFO) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hello"), last_message());
+
+ ResetMockLogger();
+
+ MOJO_LOG_STREAM(ERROR) << "hi " << 123;
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hi 123"), last_message());
+}
+
+TEST_F(LoggingTest, LazyLogStream) {
+ MOJO_LAZY_LOG_STREAM(INFO, true) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hello"), last_message());
+
+ ResetMockLogger();
+
+ MOJO_LAZY_LOG_STREAM(ERROR, true) << "hi " << 123;
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hi 123"), last_message());
+
+ ResetMockLogger();
+
+ MOJO_LAZY_LOG_STREAM(INFO, false) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LAZY_LOG_STREAM(FATAL, false) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ PtrToMemberHelper helper;
+ helper.member = 1;
+ int PtrToMemberHelper::*member_ptr = &PtrToMemberHelper::member;
+
+ // This probably fails to compile if we forget to parenthesize the condition
+ // in the macro (.* has lower precedence than !, which can't apply to
+ // |helper|).
+ MOJO_LAZY_LOG_STREAM(ERROR, helper.*member_ptr == 1) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LAZY_LOG_STREAM(WARNING, helper.*member_ptr == 0) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+}
+
+TEST_F(LoggingTest, ShouldLog) {
+ // We start at |MOJO_LOG_LEVEL_INFO|.
+ EXPECT_FALSE(MOJO_SHOULD_LOG(VERBOSE));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(INFO));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(WARNING));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(ERROR));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(FATAL));
+
+ Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_ERROR);
+ EXPECT_FALSE(MOJO_SHOULD_LOG(VERBOSE));
+ EXPECT_FALSE(MOJO_SHOULD_LOG(INFO));
+ EXPECT_FALSE(MOJO_SHOULD_LOG(WARNING));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(ERROR));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(FATAL));
+
+ Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_VERBOSE-1);
+ EXPECT_TRUE(MOJO_SHOULD_LOG(VERBOSE));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(INFO));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(WARNING));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(ERROR));
+ EXPECT_TRUE(MOJO_SHOULD_LOG(FATAL));
+}
+
+TEST_F(LoggingTest, Log) {
+ // We start at |MOJO_LOG_LEVEL_INFO|.
+ MOJO_LOG(VERBOSE) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG(INFO) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hello"), last_message());
+
+ ResetMockLogger();
+
+ MOJO_LOG(ERROR) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hello"), last_message());
+
+ ResetMockLogger();
+
+ Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_ERROR);
+
+ MOJO_LOG(VERBOSE) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG(INFO) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG(ERROR) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hello"), last_message());
+}
+
+TEST_F(LoggingTest, LogIf) {
+ // We start at |MOJO_LOG_LEVEL_INFO|.
+ MOJO_LOG_IF(VERBOSE, true) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG_IF(VERBOSE, false) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+ Environment::GetDefaultLogger()->SetMinimumLogLevel(MOJO_LOG_LEVEL_ERROR);
+
+ bool x = true;
+ // Also try to make sure that we parenthesize the condition properly.
+ MOJO_LOG_IF(INFO, false || x) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG_IF(INFO, 0 != 1) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG_IF(WARNING, 1 + 1 == 2) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_LOG_IF(ERROR, 1 * 2 == 2) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_ERROR, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 3, "hello"), last_message());
+
+ ResetMockLogger();
+
+ MOJO_LOG_IF(FATAL, 1 * 2 == 3) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ // |MOJO_LOG_IF()| shouldn't evaluate its condition if the level is below the
+ // minimum.
+ MOJO_LOG_IF(INFO, NotCalledCondition()) << "hello";
+ EXPECT_FALSE(not_called_condition_was_called());
+ EXPECT_FALSE(log_message_was_called());
+}
+
+TEST_F(LoggingTest, Check) {
+ MOJO_CHECK(true) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ PtrToMemberHelper helper;
+ helper.member = 0;
+ int PtrToMemberHelper::*member_ptr = &PtrToMemberHelper::member;
+
+ // Also try to make sure that we parenthesize the condition properly.
+ MOJO_CHECK(helper.*member_ptr == 1) << "hello";
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, last_log_level());
+ // Different compilers have different ideas about the line number of a split
+ // line.
+ int line = __LINE__;
+ EXPECT_EQ(ExpectedLogMessage(line - 5,
+ "Check failed: helper.*member_ptr == 1. hello"),
+ last_message());
+
+ ResetMockLogger();
+
+ // Also test a "naked" |MOJO_CHECK()|s.
+ MOJO_CHECK(1 + 2 == 3);
+ EXPECT_FALSE(log_message_was_called());
+}
+
+TEST_F(LoggingTest, Dlog) {
+ // We start at |MOJO_LOG_LEVEL_INFO|.
+ MOJO_DLOG(VERBOSE) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_DLOG(INFO) << "hello";
+#ifdef NDEBUG
+ EXPECT_FALSE(log_message_was_called());
+#else
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 6, "hello"), last_message());
+#endif
+}
+
+TEST_F(LoggingTest, DlogIf) {
+ // We start at |MOJO_LOG_LEVEL_INFO|. It shouldn't evaluate the condition in
+ // this case.
+ MOJO_DLOG_IF(VERBOSE, NotCalledCondition()) << "hello";
+ EXPECT_FALSE(not_called_condition_was_called());
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_DLOG_IF(INFO, 1 == 0) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_DLOG_IF(INFO, 1 == 1) << "hello";
+#ifdef NDEBUG
+ EXPECT_FALSE(log_message_was_called());
+#else
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_INFO, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 6, "hello"), last_message());
+#endif
+
+ ResetMockLogger();
+
+ // |MOJO_DLOG_IF()| shouldn't compile its condition for non-debug builds.
+#ifndef NDEBUG
+ bool debug_only = true;
+#endif
+ MOJO_DLOG_IF(WARNING, debug_only) << "hello";
+#ifdef NDEBUG
+ EXPECT_FALSE(log_message_was_called());
+#else
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_WARNING, last_log_level());
+ EXPECT_EQ(ExpectedLogMessage(__LINE__ - 6, "hello"), last_message());
+#endif
+}
+
+TEST_F(LoggingTest, Dcheck) {
+ MOJO_DCHECK(true);
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ MOJO_DCHECK(true) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+
+ ResetMockLogger();
+
+ // |MOJO_DCHECK()| should compile (but not evaluate) its condition even for
+ // non-debug builds. (Hopefully, we'll get an unused variable error if it
+ // fails to compile the condition.)
+ bool was_called = false;
+ MOJO_DCHECK(DcheckTestHelper(&was_called)) << "hello";
+#ifdef NDEBUG
+ EXPECT_FALSE(was_called);
+ EXPECT_FALSE(log_message_was_called());
+#else
+ EXPECT_TRUE(was_called);
+ EXPECT_TRUE(log_message_was_called());
+ EXPECT_EQ(MOJO_LOG_LEVEL_FATAL, last_log_level());
+ // Different compilers have different ideas about the line number of a split
+ // line.
+ int line = __LINE__;
+ EXPECT_EQ(
+ ExpectedLogMessage(line - 10,
+ "Check failed: DcheckTestHelper(&was_called). hello"),
+ last_message());
+#endif
+
+ ResetMockLogger();
+
+ // Also try to make sure that we parenthesize the condition properly.
+ bool x = true;
+ MOJO_DCHECK(false || x) << "hello";
+ EXPECT_FALSE(log_message_was_called());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn
new file mode 100644
index 0000000..046ede0
--- /dev/null
+++ b/mojo/public/cpp/system/BUILD.gn
@@ -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.
+
+source_set("system") {
+ sources = [
+ "buffer.h",
+ "core.h",
+ "data_pipe.h",
+ "functions.h",
+ "handle.h",
+ "macros.h",
+ "message_pipe.h"
+ ]
+
+ public_deps = [ "//mojo/public/c/system" ]
+}
diff --git a/mojo/public/cpp/system/buffer.h b/mojo/public/cpp/system/buffer.h
new file mode 100644
index 0000000..ad9966a
--- /dev/null
+++ b/mojo/public/cpp/system/buffer.h
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium 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_BUFFER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_
+
+#include <assert.h>
+
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+// 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(nullptr, 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_BUFFER_H_
diff --git a/mojo/public/cpp/system/core.h b/mojo/public/cpp/system/core.h
new file mode 100644
index 0000000..b08a5a6
--- /dev/null
+++ b/mojo/public/cpp/system/core.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_CORE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_CORE_H_
+
+#include "mojo/public/cpp/system/buffer.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/functions.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_CORE_H_
diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h
new file mode 100644
index 0000000..b2f6e68
--- /dev/null
+++ b/mojo/public/cpp/system/data_pipe.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_
+
+#include <assert.h>
+
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+// 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(nullptr, &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() {
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_
diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h
new file mode 100644
index 0000000..d73d27a
--- /dev/null
+++ b/mojo/public/cpp/system/functions.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_SYSTEM_FUNCTIONS_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_
+
+#include "mojo/public/c/system/functions.h"
+
+namespace mojo {
+
+// Standalone functions --------------------------------------------------------
+
+inline MojoTimeTicks GetTimeTicksNow() {
+ return MojoGetTimeTicksNow();
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_
diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h
new file mode 100644
index 0000000..72b14c8
--- /dev/null
+++ b/mojo/public/cpp/system/handle.h
@@ -0,0 +1,246 @@
+// Copyright 2014 The Chromium 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_HANDLE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_
+
+#include <assert.h>
+#include <limits>
+
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/types.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.
+
+// 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;
+ }
+
+ const 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(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(nullptr, nullptr, 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,
+inline bool operator<(const Handle a, const Handle b) {
+ return a.value() < b.value();
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_
diff --git a/mojo/public/cpp/system/macros.h b/mojo/public/cpp/system/macros.h
new file mode 100644
index 0000000..605906e
--- /dev/null
+++ b/mojo/public/cpp/system/macros.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_CPP_SYSTEM_MACROS_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_MACROS_H_
+
+#include "mojo/public/c/system/macros.h"
+
+// Define a set of C++ specific macros.
+// Mojo C++ API users can assume that mojo/public/cpp/system/macros.h
+// includes mojo/public/c/system/macros.h.
+
+// 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/mojo/public/cpp/system/message_pipe.h b/mojo/public/cpp/system/message_pipe.h
new file mode 100644
index 0000000..7ef6314
--- /dev/null
+++ b/mojo/public/cpp/system/message_pipe.h
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium 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_MESSAGE_PIPE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_
+
+#include <assert.h>
+
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+// 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(nullptr, &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() {
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_
diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn
new file mode 100644
index 0000000..3226662
--- /dev/null
+++ b/mojo/public/cpp/system/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_system_unittests
+test("mojo_public_system_unittests") {
+ deps = [
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/public/c/system/tests",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "core_unittest.cc",
+ "macros_unittest.cc",
+ ]
+}
diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc
new file mode 100644
index 0000000..a44bc07
--- /dev/null
+++ b/mojo/public/cpp/system/tests/core_unittest.cc
@@ -0,0 +1,419 @@
+// Copyright 2014 The Chromium 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 tests the C++ Mojo system core wrappers.
+// TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets
+// compiled into a different binary from the C API tests.
+
+#include "mojo/public/cpp/system/core.h"
+
+#include <map>
+
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+TEST(CoreCppTest, GetTimeTicksNow) {
+ const MojoTimeTicks start = GetTimeTicksNow();
+ EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
+ << "GetTimeTicksNow should return nonzero value";
+}
+
+TEST(CoreCppTest, Basic) {
+ // Basic |Handle| implementation:
+ {
+ EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue);
+
+ Handle h0;
+ EXPECT_EQ(kInvalidHandleValue, h0.value());
+ EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value());
+ EXPECT_FALSE(h0.is_valid());
+
+ Handle h1(static_cast<MojoHandle>(123));
+ EXPECT_EQ(static_cast<MojoHandle>(123), h1.value());
+ EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value());
+ EXPECT_TRUE(h1.is_valid());
+ *h1.mutable_value() = static_cast<MojoHandle>(456);
+ EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
+ EXPECT_TRUE(h1.is_valid());
+
+ h1.swap(h0);
+ EXPECT_EQ(static_cast<MojoHandle>(456), h0.value());
+ EXPECT_TRUE(h0.is_valid());
+ EXPECT_FALSE(h1.is_valid());
+
+ h1.set_value(static_cast<MojoHandle>(789));
+ h0.swap(h1);
+ EXPECT_EQ(static_cast<MojoHandle>(789), h0.value());
+ EXPECT_TRUE(h0.is_valid());
+ EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
+ EXPECT_TRUE(h1.is_valid());
+
+ // Make sure copy constructor works.
+ Handle h2(h0);
+ EXPECT_EQ(static_cast<MojoHandle>(789), h2.value());
+ // And assignment.
+ h2 = h1;
+ EXPECT_EQ(static_cast<MojoHandle>(456), h2.value());
+
+ // Make sure that we can put |Handle|s into |std::map|s.
+ h0 = Handle(static_cast<MojoHandle>(987));
+ h1 = Handle(static_cast<MojoHandle>(654));
+ h2 = Handle(static_cast<MojoHandle>(321));
+ Handle h3;
+ std::map<Handle, int> handle_to_int;
+ handle_to_int[h0] = 0;
+ handle_to_int[h1] = 1;
+ handle_to_int[h2] = 2;
+ handle_to_int[h3] = 3;
+
+ EXPECT_EQ(4u, handle_to_int.size());
+ EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end());
+ EXPECT_EQ(0, handle_to_int[h0]);
+ EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end());
+ EXPECT_EQ(1, handle_to_int[h1]);
+ EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end());
+ EXPECT_EQ(2, handle_to_int[h2]);
+ EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end());
+ EXPECT_EQ(3, handle_to_int[h3]);
+ EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) ==
+ handle_to_int.end());
+
+ // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out
+ // how to support the variations of |hash_map|.)
+ }
+
+ // |Handle|/|ScopedHandle| functions:
+ {
+ ScopedHandle h;
+
+ EXPECT_EQ(kInvalidHandleValue, h.get().value());
+
+ // This should be a no-op.
+ Close(h.Pass());
+
+ // It should still be invalid.
+ EXPECT_EQ(kInvalidHandleValue, h.get().value());
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE, 1000000));
+
+ std::vector<Handle> wh;
+ wh.push_back(h.get());
+ std::vector<MojoHandleSignals> sigs;
+ sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE);
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE));
+ }
+
+ // |MakeScopedHandle| (just compilation tests):
+ {
+ EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid());
+ EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid());
+ }
+
+ // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions:
+ {
+ MessagePipeHandle h_invalid;
+ EXPECT_FALSE(h_invalid.is_valid());
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WriteMessageRaw(h_invalid,
+ nullptr, 0,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ char buffer[10] = { 0 };
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ WriteMessageRaw(h_invalid,
+ buffer, sizeof(buffer),
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ ReadMessageRaw(h_invalid,
+ nullptr, nullptr,
+ nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ ReadMessageRaw(h_invalid,
+ buffer, &buffer_size,
+ nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ // Basic tests of waiting and closing.
+ MojoHandle hv0 = kInvalidHandleValue;
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ EXPECT_FALSE(h0.get().is_valid());
+ EXPECT_FALSE(h1.get().is_valid());
+
+ CreateMessagePipe(nullptr, &h0, &h1);
+ EXPECT_TRUE(h0.get().is_valid());
+ EXPECT_TRUE(h1.get().is_valid());
+ EXPECT_NE(h0.get().value(), h1.get().value());
+ // Save the handle values, so we can check that things got closed
+ // correctly.
+ hv0 = h0.get().value();
+ MojoHandle hv1 = h1.get().value();
+
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, 0));
+ std::vector<Handle> wh;
+ wh.push_back(h0.get());
+ wh.push_back(h1.get());
+ std::vector<MojoHandleSignals> sigs;
+ sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE);
+ sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE);
+ EXPECT_EQ(1, WaitMany(wh, sigs, 1000));
+
+ // Test closing |h1| explicitly.
+ Close(h1.Pass());
+ EXPECT_FALSE(h1.get().is_valid());
+
+ // Make sure |h1| is closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWait(hv1, ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE));
+
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE));
+ }
+ // |hv0| should have been closed when |h0| went out of scope, so this close
+ // should fail.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
+
+ // Actually test writing/reading messages.
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ CreateMessagePipe(nullptr, &h0, &h1);
+
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h0.get(),
+ kHello, kHelloSize,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE));
+ char buffer[10] = { 0 };
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(h1.get(),
+ buffer, &buffer_size,
+ nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+
+ // Send a handle over the previously-establish message pipe. Use the
+ // |MessagePipe| wrapper (to test it), which automatically creates a
+ // message pipe.
+ MessagePipe mp;
+
+ // Write a message to |mp.handle0|, before we send |mp.handle1|.
+ const char kWorld[] = "world!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(mp.handle0.get(),
+ kWorld, kWorldSize,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Send |mp.handle1| over |h1| to |h0|.
+ MojoHandle handles[5];
+ handles[0] = mp.handle1.release().value();
+ EXPECT_NE(kInvalidHandleValue, handles[0]);
+ EXPECT_FALSE(mp.handle1.get().is_valid());
+ uint32_t handles_count = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h1.get(),
+ kHello, kHelloSize,
+ handles, handles_count,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // |handles[0]| should actually be invalid now.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0]));
+
+ // Read "hello" and the sent handle.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE));
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(handles); i++)
+ handles[i] = kInvalidHandleValue;
+ handles_count = static_cast<uint32_t>(MOJO_ARRAYSIZE(handles));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(h0.get(),
+ buffer, &buffer_size,
+ handles, &handles_count,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, buffer_size);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(1u, handles_count);
+ EXPECT_NE(kInvalidHandleValue, handles[0]);
+
+ // Read from the sent/received handle.
+ mp.handle1.reset(MessagePipeHandle(handles[0]));
+ // Save |handles[0]| to check that it gets properly closed.
+ hv0 = handles[0];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE));
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(handles); i++)
+ handles[i] = kInvalidHandleValue;
+ handles_count = static_cast<uint32_t>(MOJO_ARRAYSIZE(handles));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(mp.handle1.get(),
+ buffer, &buffer_size,
+ handles, &handles_count,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kWorldSize, buffer_size);
+ EXPECT_STREQ(kWorld, buffer);
+ EXPECT_EQ(0u, handles_count);
+ }
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
+ }
+
+ // TODO(vtl): Test |CloseRaw()|.
+ // TODO(vtl): Test |reset()| more thoroughly?
+}
+
+TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
+ // Tear down a message pipe which still has a message enqueued, with the
+ // message also having a valid message pipe handle.
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ CreateMessagePipe(nullptr, &h0, &h1);
+
+ // Send a handle over the previously-establish message pipe.
+ ScopedMessagePipeHandle h2;
+ ScopedMessagePipeHandle h3;
+ CreateMessagePipe(nullptr, &h2, &h3);
+
+ // Write a message to |h2|, before we send |h3|.
+ const char kWorld[] = "world!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h2.get(),
+ kWorld, kWorldSize,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // And also a message to |h3|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h3.get(),
+ kWorld, kWorldSize,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Send |h3| over |h1| to |h0|.
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ MojoHandle h3_value;
+ h3_value = h3.release().value();
+ EXPECT_NE(kInvalidHandleValue, h3_value);
+ EXPECT_FALSE(h3.get().is_valid());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h1.get(),
+ kHello, kHelloSize,
+ &h3_value, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // |h3_value| should actually be invalid now.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
+ }
+
+ // Do this in a different order: make the enqueued message pipe handle only
+ // half-alive.
+ {
+ ScopedMessagePipeHandle h0;
+ ScopedMessagePipeHandle h1;
+ CreateMessagePipe(nullptr, &h0, &h1);
+
+ // Send a handle over the previously-establish message pipe.
+ ScopedMessagePipeHandle h2;
+ ScopedMessagePipeHandle h3;
+ CreateMessagePipe(nullptr, &h2, &h3);
+
+ // Write a message to |h2|, before we send |h3|.
+ const char kWorld[] = "world!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h2.get(),
+ kWorld, kWorldSize,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // And also a message to |h3|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h3.get(),
+ kWorld, kWorldSize,
+ nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Send |h3| over |h1| to |h0|.
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ MojoHandle h3_value;
+ h3_value = h3.release().value();
+ EXPECT_NE(kInvalidHandleValue, h3_value);
+ EXPECT_FALSE(h3.get().is_valid());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(h1.get(),
+ kHello, kHelloSize,
+ &h3_value, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ // |h3_value| should actually be invalid now.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
+ }
+}
+
+TEST(CoreCppTest, ScopedHandleMoveCtor) {
+ ScopedSharedBufferHandle buffer1;
+ EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer1));
+ EXPECT_TRUE(buffer1.is_valid());
+
+ ScopedSharedBufferHandle buffer2;
+ EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer2));
+ EXPECT_TRUE(buffer2.is_valid());
+
+ // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will
+ // assert.
+ buffer1 = buffer2.Pass();
+
+ EXPECT_TRUE(buffer1.is_valid());
+ EXPECT_FALSE(buffer2.is_valid());
+}
+
+TEST(CoreCppTest, ScopedHandleMoveCtorSelf) {
+ ScopedSharedBufferHandle buffer1;
+ EXPECT_EQ(MOJO_RESULT_OK, CreateSharedBuffer(nullptr, 1024, &buffer1));
+ EXPECT_TRUE(buffer1.is_valid());
+
+ buffer1 = buffer1.Pass();
+
+ EXPECT_TRUE(buffer1.is_valid());
+}
+
+// TODO(vtl): Write data pipe tests.
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/macros_unittest.cc b/mojo/public/cpp/system/tests/macros_unittest.cc
new file mode 100644
index 0000000..49a8005
--- /dev/null
+++ b/mojo/public/cpp/system/tests/macros_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium 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 tests the C++ Mojo system macros and consists of "positive" tests,
+// i.e., those verifying that things work (without compile errors, or even
+// warnings if warnings are treated as errors).
+// TODO(vtl): Maybe rename "MacrosCppTest" -> "MacrosTest" if/when this gets
+// compiled into a different binary from the C API tests.
+// TODO(vtl): Fix no-compile tests (which are all disabled; crbug.com/105388)
+// and write some "negative" tests.
+
+#include "mojo/public/cpp/system/macros.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+// Note: MSVS is very strict (and arguably buggy) about warnings for classes
+// defined in a local scope, so define these globally.
+struct TestOverrideBaseClass {
+ virtual ~TestOverrideBaseClass() {}
+ virtual void ToBeOverridden() {}
+ virtual void AlsoToBeOverridden() = 0;
+};
+
+struct TestOverrideSubclass : public TestOverrideBaseClass {
+ virtual ~TestOverrideSubclass() {}
+ virtual void ToBeOverridden() override {}
+ virtual void AlsoToBeOverridden() override {}
+};
+
+TEST(MacrosCppTest, Override) {
+ TestOverrideSubclass x;
+ x.ToBeOverridden();
+ x.AlsoToBeOverridden();
+}
+
+// Note: MSVS is very strict (and arguably buggy) about warnings for classes
+// defined in a local scope, so define these globally.
+class TestDisallowCopyAndAssignClass {
+ public:
+ TestDisallowCopyAndAssignClass() {}
+ explicit TestDisallowCopyAndAssignClass(int) {}
+ void NoOp() {}
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestDisallowCopyAndAssignClass);
+};
+
+TEST(MacrosCppTest, DisallowCopyAndAssign) {
+ TestDisallowCopyAndAssignClass x;
+ x.NoOp();
+ TestDisallowCopyAndAssignClass y(789);
+ y.NoOp();
+}
+
+// Test that |MOJO_ARRAYSIZE()| works in a |MOJO_COMPILE_ASSERT()|.
+const int kGlobalArray[5] = { 1, 2, 3, 4, 5 };
+MOJO_COMPILE_ASSERT(MOJO_ARRAYSIZE(kGlobalArray) == 5u,
+ mojo_array_size_failed_in_compile_assert);
+
+TEST(MacrosCppTest, ArraySize) {
+ double local_array[4] = { 6.7, 7.8, 8.9, 9.0 };
+ EXPECT_EQ(4u, MOJO_ARRAYSIZE(local_array));
+}
+
+// Note: MSVS is very strict (and arguably buggy) about warnings for classes
+// defined in a local scope, so define these globally.
+class MoveOnlyInt {
+ MOJO_MOVE_ONLY_TYPE_FOR_CPP_03(MoveOnlyInt, RValue)
+
+ public:
+ MoveOnlyInt() : is_set_(false), value_() {}
+ explicit MoveOnlyInt(int value) : is_set_(true), value_(value) {}
+ ~MoveOnlyInt() {}
+
+ // Move-only constructor and operator=.
+ MoveOnlyInt(RValue other) { *this = other; }
+ MoveOnlyInt& operator=(RValue other) {
+ if (other.object != this) {
+ is_set_ = other.object->is_set_;
+ value_ = other.object->value_;
+ other.object->is_set_ = false;
+ }
+ return *this;
+ }
+
+ int value() const {
+ assert(is_set());
+ return value_;
+ }
+ bool is_set() const { return is_set_; }
+
+ private:
+ bool is_set_;
+ int value_;
+};
+
+TEST(MacrosCppTest, MoveOnlyTypeForCpp03) {
+ MoveOnlyInt x(123);
+ EXPECT_TRUE(x.is_set());
+ EXPECT_EQ(123, x.value());
+ MoveOnlyInt y;
+ EXPECT_FALSE(y.is_set());
+ y = x.Pass();
+ EXPECT_FALSE(x.is_set());
+ EXPECT_TRUE(y.is_set());
+ EXPECT_EQ(123, y.value());
+ MoveOnlyInt z(y.Pass());
+ EXPECT_FALSE(y.is_set());
+ EXPECT_TRUE(z.is_set());
+ EXPECT_EQ(123, z.value());
+ z = z.Pass();
+ EXPECT_TRUE(z.is_set());
+ EXPECT_EQ(123, z.value());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/test_support/BUILD.gn b/mojo/public/cpp/test_support/BUILD.gn
new file mode 100644
index 0000000..6c73c29
--- /dev/null
+++ b/mojo/public/cpp/test_support/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_test_utils
+source_set("test_utils") {
+ testonly = true
+ deps = [
+ "//base",
+ "//mojo/public/c/test_support",
+ "//mojo/public/cpp/system",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "lib/test_support.cc",
+ "lib/test_utils.cc",
+ "test_utils.h",
+ ]
+}
diff --git a/mojo/public/cpp/test_support/DEPS b/mojo/public/cpp/test_support/DEPS
new file mode 100644
index 0000000..6dc5394
--- /dev/null
+++ b/mojo/public/cpp/test_support/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/public/c/test_support",
+]
diff --git a/mojo/public/cpp/test_support/lib/test_support.cc b/mojo/public/cpp/test_support/lib/test_support.cc
new file mode 100644
index 0000000..0b6035b
--- /dev/null
+++ b/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 != nullptr; ++ptr) {
+ results.push_back(*ptr);
+ free(*ptr);
+ }
+ free(names);
+ return results;
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc
new file mode 100644
index 0000000..8491c4b
--- /dev/null
+++ b/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()),
+ nullptr,
+ 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,
+ nullptr,
+ &num_bytes,
+ nullptr,
+ &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,
+ nullptr,
+ &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ return rv == MOJO_RESULT_OK;
+}
+
+bool DiscardMessage(const MessagePipeHandle& handle) {
+ MojoResult rv = ReadMessageRaw(handle, nullptr, nullptr, nullptr, nullptr,
+ 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/mojo/public/cpp/test_support/test_support.h b/mojo/public/cpp/test_support/test_support.h
new file mode 100644
index 0000000..eb4d4be
--- /dev/null
+++ b/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/mojo/public/cpp/test_support/test_utils.h b/mojo/public/cpp/test_support/test_utils.h
new file mode 100644
index 0000000..43a3ea9
--- /dev/null
+++ b/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/mojo/public/cpp/utility/BUILD.gn b/mojo/public/cpp/utility/BUILD.gn
new file mode 100644
index 0000000..a0898b9
--- /dev/null
+++ b/mojo/public/cpp/utility/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.
+
+source_set("utility") {
+ sources = [
+ "mutex.h",
+ "run_loop.h",
+ "run_loop_handler.h",
+ "thread.h",
+ "lib/mutex.cc",
+ "lib/run_loop.cc",
+ "lib/thread.cc",
+ "lib/thread_local.h",
+ "lib/thread_local_posix.cc",
+ "lib/thread_local_win.cc",
+ ]
+
+ deps = [
+ "//mojo/public/cpp/bindings:callback",
+ "//mojo/public/cpp/system",
+ ]
+
+ if (is_win) {
+ # See crbug.com/342893:
+ sources -= [
+ "mutex.h",
+ "thread.h",
+ "lib/mutex.cc",
+ "lib/thread.cc",
+ ]
+ }
+}
diff --git a/mojo/public/cpp/utility/DEPS b/mojo/public/cpp/utility/DEPS
new file mode 100644
index 0000000..a9dfbd1
--- /dev/null
+++ b/mojo/public/cpp/utility/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/public/cpp/bindings/callback.h"
+]
diff --git a/mojo/public/cpp/utility/lib/mutex.cc b/mojo/public/cpp/utility/lib/mutex.cc
new file mode 100644
index 0000000..23370e1
--- /dev/null
+++ b/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/mojo/public/cpp/utility/lib/run_loop.cc b/mojo/public/cpp/utility/lib/run_loop.cc
new file mode 100644
index 0000000..bcbc623
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/run_loop.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/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), next_sequence_number_(0) {
+ assert(!current());
+ current_run_loop.Set(this);
+}
+
+RunLoop::~RunLoop() {
+ assert(current() == this);
+ NotifyHandlers(MOJO_RESULT_ABORTED, IGNORE_DEADLINE);
+ 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() {
+ RunInternal(UNTIL_EMPTY);
+}
+
+void RunLoop::RunUntilIdle() {
+ RunInternal(UNTIL_IDLE);
+}
+
+void RunLoop::RunInternal(RunMode run_mode) {
+ assert(current() == this);
+ RunState* old_state = run_state_;
+ RunState run_state;
+ run_state_ = &run_state;
+ for (;;) {
+ bool did_work = DoDelayedWork();
+ if (run_state.should_quit)
+ break;
+ did_work |= Wait(run_mode == UNTIL_IDLE);
+ if (run_state.should_quit)
+ break;
+ if (!did_work && run_mode == UNTIL_IDLE)
+ break;
+ }
+ run_state_ = old_state;
+}
+
+bool RunLoop::DoDelayedWork() {
+ MojoTimeTicks now = GetTimeTicksNow();
+ if (!delayed_tasks_.empty() && delayed_tasks_.top().run_time <= now) {
+ PendingTask task = delayed_tasks_.top();
+ delayed_tasks_.pop();
+ task.task.Run();
+ return true;
+ }
+ return false;
+}
+
+void RunLoop::Quit() {
+ assert(current() == this);
+ if (run_state_)
+ run_state_->should_quit = true;
+}
+
+void RunLoop::PostDelayedTask(const Closure& task, MojoTimeTicks delay) {
+ assert(current() == this);
+ MojoTimeTicks run_time = delay + GetTimeTicksNow();
+ delayed_tasks_.push(PendingTask(task, run_time, next_sequence_number_++));
+}
+
+bool RunLoop::Wait(bool non_blocking) {
+ const WaitState wait_state = GetWaitState(non_blocking);
+ if (wait_state.handles.empty() && delayed_tasks_.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 NotifyHandlers(MOJO_RESULT_DEADLINE_EXCEEDED, CHECK_DEADLINE);
+ }
+
+ assert(false);
+ return false;
+}
+
+bool RunLoop::NotifyHandlers(MojoResult error, CheckDeadline check) {
+ 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) {
+ // Only check deadline exceeded if that's what we're notifying.
+ if (check == CHECK_DEADLINE && (i->second.deadline == kInvalidTimeTicks ||
+ i->second.deadline > now)) {
+ continue;
+ }
+
+ // Since we're iterating over a clone of the handlers, verify the handler
+ // is still valid before notifying.
+ if (handler_data_.find(i->first) == handler_data_.end() ||
+ handler_data_[i->first].id != i->second.id) {
+ continue;
+ }
+
+ RunLoopHandler* handler = i->second.handler;
+ handler_data_.erase(i->first);
+ handler->OnHandleError(i->first, error);
+ 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 (!delayed_tasks_.empty()) {
+ MojoTimeTicks delayed_min_time = delayed_tasks_.top().run_time;
+ if (min_time == kInvalidTimeTicks)
+ min_time = delayed_min_time;
+ else
+ min_time = std::min(min_time, delayed_min_time);
+ }
+ 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;
+}
+
+RunLoop::PendingTask::PendingTask(const Closure& task,
+ MojoTimeTicks run_time,
+ uint64_t sequence_number)
+ : task(task), run_time(run_time), sequence_number(sequence_number) {
+}
+
+RunLoop::PendingTask::~PendingTask() {
+}
+
+bool RunLoop::PendingTask::operator<(const RunLoop::PendingTask& other) const {
+ if (run_time != other.run_time) {
+ // std::priority_queue<> puts the least element at the end of the queue. We
+ // want the soonest eligible task to be at the head of the queue, so
+ // run_times further in the future are considered lesser.
+ return run_time > other.run_time;
+ }
+
+ return sequence_number > other.sequence_number;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/lib/thread.cc b/mojo/public/cpp/utility/lib/thread.cc
new file mode 100644
index 0000000..da33497
--- /dev/null
+++ b/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/mojo/public/cpp/utility/lib/thread_local.h b/mojo/public/cpp/utility/lib/thread_local.h
new file mode 100644
index 0000000..4c3625d
--- /dev/null
+++ b/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/mojo/public/cpp/utility/lib/thread_local_posix.cc b/mojo/public/cpp/utility/lib/thread_local_posix.cc
new file mode 100644
index 0000000..b33dfc6
--- /dev/null
+++ b/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/mojo/public/cpp/utility/lib/thread_local_win.cc b/mojo/public/cpp/utility/lib/thread_local_win.cc
new file mode 100644
index 0000000..98841f7
--- /dev/null
+++ b/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/mojo/public/cpp/utility/mutex.h b/mojo/public/cpp/utility/mutex.h
new file mode 100644
index 0000000..35611c2
--- /dev/null
+++ b/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/mojo/public/cpp/utility/run_loop.h b/mojo/public/cpp/utility/run_loop.h
new file mode 100644
index 0000000..359208d
--- /dev/null
+++ b/mojo/public/cpp/utility/run_loop.h
@@ -0,0 +1,155 @@
+// 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 <queue>
+
+#include "mojo/public/cpp/bindings/callback.h"
+#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 and tasks as they are ready. This returns
+ // when Quit() is invoked, or there are no more handles nor tasks.
+ void Run();
+
+ // Runs the loop servicing any handles and tasks that are ready. Does not wait
+ // for handles or tasks to become ready before returning. Returns early if
+ // Quit() is invoked.
+ void RunUntilIdle();
+
+ void Quit();
+
+ // Adds a task to be performed after delay has elapsed. Must be posted to the
+ // current thread's RunLoop.
+ void PostDelayedTask(const Closure& task, MojoTimeTicks delay);
+
+ 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;
+
+ // Used for NotifyHandlers to specify whether HandlerData's |deadline|
+ // should be checked prior to notifying.
+ enum CheckDeadline {
+ CHECK_DEADLINE,
+ IGNORE_DEADLINE
+ };
+
+ // Mode of operation of the run loop.
+ enum RunMode {
+ UNTIL_EMPTY,
+ UNTIL_IDLE
+ };
+
+ // Runs the loop servicing any handles and tasks that are ready. If
+ // |run_mode| is |UNTIL_IDLE|, does not wait for handles or tasks to become
+ // ready before returning. Returns early if Quit() is invoked.
+ void RunInternal(RunMode run_mode);
+
+ // Do one unit of delayed work, if eligible. Returns true is a task was run.
+ bool DoDelayedWork();
+
+ // Waits for a handle to be ready or until the next task must be run. Returns
+ // after servicing at least one handle (or there are no more handles) unless
+ // a task must be run or |non_blocking| is true, in which case it will also
+ // return if no task is registered and servicing at least one handle would
+ // require blocking. Returns true if a RunLoopHandler was notified.
+ bool Wait(bool non_blocking);
+
+ // Notifies handlers of |error|. If |check| == CHECK_DEADLINE, this will
+ // only notify handlers whose deadline has expired and skips the rest.
+ // Returns true if a RunLoopHandler was notified.
+ bool NotifyHandlers(MojoResult error, CheckDeadline check);
+
+ // 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_;
+
+ struct PendingTask {
+ PendingTask(const Closure& task,
+ MojoTimeTicks runtime,
+ uint64_t sequence_number);
+ ~PendingTask();
+
+ bool operator<(const PendingTask& other) const;
+
+ Closure task;
+ MojoTimeTicks run_time;
+ uint64_t sequence_number;
+ };
+ // An ever increasing sequence number attached to each pending task in order
+ // to preserve relative order of tasks posted at the 'same' time.
+ uint64_t next_sequence_number_;
+ typedef std::priority_queue<PendingTask> DelayedTaskQueue;
+ DelayedTaskQueue delayed_tasks_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoop);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_
diff --git a/mojo/public/cpp/utility/run_loop_handler.h b/mojo/public/cpp/utility/run_loop_handler.h
new file mode 100644
index 0000000..69838d5
--- /dev/null
+++ b/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/mojo/public/cpp/utility/tests/BUILD.gn b/mojo/public/cpp/utility/tests/BUILD.gn
new file mode 100644
index 0000000..225bf07
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_public_tests.gypi:mojo_public_utility_unittests
+test("mojo_public_utility_unittests") {
+ deps = [
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/cpp/utility",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "mutex_unittest.cc",
+ "run_loop_unittest.cc",
+ "thread_unittest.cc",
+ ]
+}
diff --git a/mojo/public/cpp/utility/tests/mutex_unittest.cc b/mojo/public/cpp/utility/tests/mutex_unittest.cc
new file mode 100644
index 0000000..eb5a330
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/mutex_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright 2014 The Chromium 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 <stdlib.h> // For |rand()|.
+#include <time.h> // For |nanosleep()| (defined by POSIX).
+
+#include <vector>
+
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+TEST(MutexTest, TrivialSingleThreaded) {
+ Mutex mutex;
+
+ mutex.Lock();
+ mutex.AssertHeld();
+ mutex.Unlock();
+
+ EXPECT_TRUE(mutex.TryLock());
+ mutex.AssertHeld();
+ mutex.Unlock();
+
+ {
+ MutexLock lock(&mutex);
+ mutex.AssertHeld();
+ }
+
+ EXPECT_TRUE(mutex.TryLock());
+ mutex.Unlock();
+}
+
+class Fiddler {
+ public:
+ enum Type { kTypeLock, kTypeTry };
+ Fiddler(size_t times_to_lock,
+ Type type,
+ bool should_sleep,
+ Mutex* mutex,
+ int* shared_value)
+ : times_to_lock_(times_to_lock),
+ type_(type),
+ should_sleep_(should_sleep),
+ mutex_(mutex),
+ shared_value_(shared_value) {
+ }
+
+ ~Fiddler() {
+ }
+
+ void Fiddle() {
+ for (size_t i = 0; i < times_to_lock_;) {
+ switch (type_) {
+ case kTypeLock: {
+ mutex_->Lock();
+ int old_shared_value = *shared_value_;
+ if (should_sleep_)
+ SleepALittle();
+ *shared_value_ = old_shared_value + 1;
+ mutex_->Unlock();
+ i++;
+ break;
+ }
+ case kTypeTry:
+ if (mutex_->TryLock()) {
+ int old_shared_value = *shared_value_;
+ if (should_sleep_)
+ SleepALittle();
+ *shared_value_ = old_shared_value + 1;
+ mutex_->Unlock();
+ i++;
+ } else {
+ SleepALittle(); // Don't spin.
+ }
+ break;
+ }
+ }
+ }
+
+ private:
+ static void SleepALittle() {
+ static const long kNanosPerMilli = 1000000;
+ struct timespec req = {
+ 0, // Seconds.
+ (rand() % 10) * kNanosPerMilli // Nanoseconds.
+ };
+ int rv MOJO_ALLOW_UNUSED = nanosleep(&req, NULL);
+ assert(rv == 0);
+ }
+
+ const size_t times_to_lock_;
+ const Type type_;
+ const bool should_sleep_;
+ Mutex* const mutex_;
+ int* const shared_value_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Fiddler);
+};
+
+class FiddlerThread : public Thread {
+ public:
+ // Takes ownership of |fiddler|.
+ FiddlerThread(Fiddler* fiddler)
+ : fiddler_(fiddler) {
+ }
+
+ virtual ~FiddlerThread() {
+ delete fiddler_;
+ }
+
+ virtual void Run() override { fiddler_->Fiddle(); }
+
+ private:
+ Fiddler* const fiddler_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(FiddlerThread);
+};
+
+// This does a stress test (that also checks exclusion).
+TEST(MutexTest, ThreadedStress) {
+ static const size_t kNumThreads = 20;
+ static const int kTimesToLockEach = 20;
+ assert(kNumThreads % 4 == 0);
+
+ Mutex mutex;
+ int shared_value = 0;
+
+ std::vector<FiddlerThread*> fiddler_threads;
+
+ for (size_t i = 0; i < kNumThreads; i += 4) {
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeLock, false, &mutex, &shared_value)));
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeTry, false, &mutex, &shared_value)));
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value)));
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeTry, true, &mutex, &shared_value)));
+ }
+
+ for (size_t i = 0; i < kNumThreads; i++)
+ fiddler_threads[i]->Start();
+
+ // Do some fiddling ourselves.
+ Fiddler(kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value)
+ .Fiddle();
+
+ // Join.
+ for (size_t i = 0; i < kNumThreads; i++)
+ fiddler_threads[i]->Join();
+
+ EXPECT_EQ(static_cast<int>(kNumThreads + 1) * kTimesToLockEach, shared_value);
+
+ // Delete.
+ for (size_t i = 0; i < kNumThreads; i++)
+ delete fiddler_threads[i];
+ fiddler_threads.clear();
+}
+
+class TryThread : public Thread {
+ public:
+ explicit TryThread(Mutex* mutex) : mutex_(mutex), try_lock_succeeded_() {}
+ virtual ~TryThread() {}
+
+ virtual void Run() override {
+ try_lock_succeeded_ = mutex_->TryLock();
+ if (try_lock_succeeded_)
+ mutex_->Unlock();
+ }
+
+ bool try_lock_succeeded() const { return try_lock_succeeded_; }
+
+ private:
+ Mutex* const mutex_;
+ bool try_lock_succeeded_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TryThread);
+};
+
+TEST(MutexTest, TryLock) {
+ Mutex mutex;
+
+ // |TryLock()| should succeed -- we don't have the lock.
+ {
+ TryThread thread(&mutex);
+ thread.Start();
+ thread.Join();
+ EXPECT_TRUE(thread.try_lock_succeeded());
+ }
+
+ // Take the lock.
+ ASSERT_TRUE(mutex.TryLock());
+
+ // Now it should fail.
+ {
+ TryThread thread(&mutex);
+ thread.Start();
+ thread.Join();
+ EXPECT_FALSE(thread.try_lock_succeeded());
+ }
+
+ // Release the lock.
+ mutex.Unlock();
+
+ // It should succeed again.
+ {
+ TryThread thread(&mutex);
+ thread.Start();
+ thread.Join();
+ EXPECT_TRUE(thread.try_lock_succeeded());
+ }
+}
+
+
+// Tests of assertions for Debug builds.
+#if !defined(NDEBUG)
+// Test |AssertHeld()| (which is an actual user API).
+TEST(MutexTest, DebugAssertHeldFailure) {
+ Mutex mutex;
+ EXPECT_DEATH_IF_SUPPORTED(mutex.AssertHeld(), "");
+}
+
+// Test other consistency checks.
+TEST(MutexTest, DebugAssertionFailures) {
+ // Unlock without lock held.
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Unlock();
+ }, "");
+
+ // Lock with lock held (on same thread).
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Lock();
+ mutex.Lock();
+ }, "");
+
+ // Try lock with lock held.
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Lock();
+ mutex.TryLock();
+ }, "");
+
+ // Destroy lock with lock held.
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Lock();
+ }, "");
+}
+#endif // !defined(NDEBUG)
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/tests/run_loop_unittest.cc b/mojo/public/cpp/utility/tests/run_loop_unittest.cc
new file mode 100644
index 0000000..c6199d7
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/run_loop_unittest.cc
@@ -0,0 +1,431 @@
+// Copyright 2014 The Chromium 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 <string>
+
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/cpp/utility/run_loop_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class TestRunLoopHandler : public RunLoopHandler {
+ public:
+ TestRunLoopHandler()
+ : ready_count_(0),
+ error_count_(0),
+ last_error_result_(MOJO_RESULT_OK) {
+ }
+ virtual ~TestRunLoopHandler() {}
+
+ void clear_ready_count() { ready_count_ = 0; }
+ int ready_count() const { return ready_count_; }
+
+ void clear_error_count() { error_count_ = 0; }
+ int error_count() const { return error_count_; }
+
+ MojoResult last_error_result() const { return last_error_result_; }
+
+ // RunLoopHandler:
+ virtual void OnHandleReady(const Handle& handle) override { ready_count_++; }
+ virtual void OnHandleError(const Handle& handle, MojoResult result) override {
+ error_count_++;
+ last_error_result_ = result;
+ }
+
+ private:
+ int ready_count_;
+ int error_count_;
+ MojoResult last_error_result_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestRunLoopHandler);
+};
+
+class RunLoopTest : public testing::Test {
+ public:
+ RunLoopTest() {}
+
+ virtual void SetUp() override {
+ Test::SetUp();
+ RunLoop::SetUp();
+ }
+ virtual void TearDown() override {
+ RunLoop::TearDown();
+ Test::TearDown();
+ }
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoopTest);
+};
+
+// Trivial test to verify Run() with no added handles returns.
+TEST_F(RunLoopTest, ExitsWithNoHandles) {
+ RunLoop run_loop;
+ run_loop.Run();
+}
+
+class RemoveOnReadyRunLoopHandler : public TestRunLoopHandler {
+ public:
+ RemoveOnReadyRunLoopHandler() : run_loop_(NULL) {
+ }
+ virtual ~RemoveOnReadyRunLoopHandler() {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ virtual void OnHandleReady(const Handle& handle) override {
+ run_loop_->RemoveHandler(handle);
+ TestRunLoopHandler::OnHandleReady(handle);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveOnReadyRunLoopHandler);
+};
+
+// Verifies RunLoop quits when no more handles (handle is removed when ready).
+TEST_F(RunLoopTest, HandleReady) {
+ RemoveOnReadyRunLoopHandler handler;
+ MessagePipe test_pipe;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string()));
+
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE);
+ run_loop.Run();
+ EXPECT_EQ(1, handler.ready_count());
+ EXPECT_EQ(0, handler.error_count());
+ EXPECT_FALSE(run_loop.HasHandler(test_pipe.handle0.get()));
+}
+
+class QuitOnReadyRunLoopHandler : public TestRunLoopHandler {
+ public:
+ QuitOnReadyRunLoopHandler() : run_loop_(NULL) {
+ }
+ virtual ~QuitOnReadyRunLoopHandler() {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ virtual void OnHandleReady(const Handle& handle) override {
+ run_loop_->Quit();
+ TestRunLoopHandler::OnHandleReady(handle);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnReadyRunLoopHandler);
+};
+
+// Verifies Quit() from OnHandleReady() quits the loop.
+TEST_F(RunLoopTest, QuitFromReady) {
+ QuitOnReadyRunLoopHandler handler;
+ MessagePipe test_pipe;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string()));
+
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE);
+ run_loop.Run();
+ EXPECT_EQ(1, handler.ready_count());
+ EXPECT_EQ(0, handler.error_count());
+ EXPECT_TRUE(run_loop.HasHandler(test_pipe.handle0.get()));
+}
+
+class QuitOnErrorRunLoopHandler : public TestRunLoopHandler {
+ public:
+ QuitOnErrorRunLoopHandler() : run_loop_(NULL) {
+ }
+ virtual ~QuitOnErrorRunLoopHandler() {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ virtual void OnHandleError(const Handle& handle, MojoResult result) override {
+ run_loop_->Quit();
+ TestRunLoopHandler::OnHandleError(handle, result);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnErrorRunLoopHandler);
+};
+
+// Verifies Quit() when the deadline is reached works.
+TEST_F(RunLoopTest, QuitWhenDeadlineExpired) {
+ QuitOnErrorRunLoopHandler handler;
+ MessagePipe test_pipe;
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ static_cast<MojoDeadline>(10000));
+ run_loop.Run();
+ EXPECT_EQ(0, handler.ready_count());
+ EXPECT_EQ(1, handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, handler.last_error_result());
+ EXPECT_FALSE(run_loop.HasHandler(test_pipe.handle0.get()));
+}
+
+// Test that handlers are notified of loop destruction.
+TEST_F(RunLoopTest, Destruction) {
+ TestRunLoopHandler handler;
+ MessagePipe test_pipe;
+ {
+ RunLoop run_loop;
+ run_loop.AddHandler(&handler,
+ test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ }
+ EXPECT_EQ(1, handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, handler.last_error_result());
+}
+
+class RemoveManyRunLoopHandler : public TestRunLoopHandler {
+ public:
+ RemoveManyRunLoopHandler() : run_loop_(NULL) {
+ }
+ virtual ~RemoveManyRunLoopHandler() {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+ void add_handle(const Handle& handle) { handles_.push_back(handle); }
+
+ // RunLoopHandler:
+ virtual void OnHandleError(const Handle& handle, MojoResult result) override {
+ for (size_t i = 0; i < handles_.size(); i++)
+ run_loop_->RemoveHandler(handles_[i]);
+ TestRunLoopHandler::OnHandleError(handle, result);
+ }
+
+ private:
+ std::vector<Handle> handles_;
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveManyRunLoopHandler);
+};
+
+// Test that handlers are notified of loop destruction.
+TEST_F(RunLoopTest, MultipleHandleDestruction) {
+ RemoveManyRunLoopHandler odd_handler;
+ TestRunLoopHandler even_handler;
+ MessagePipe test_pipe1, test_pipe2, test_pipe3;
+ {
+ RunLoop run_loop;
+ odd_handler.set_run_loop(&run_loop);
+ odd_handler.add_handle(test_pipe1.handle0.get());
+ odd_handler.add_handle(test_pipe3.handle0.get());
+ run_loop.AddHandler(&odd_handler,
+ test_pipe1.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ run_loop.AddHandler(&even_handler,
+ test_pipe2.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ run_loop.AddHandler(&odd_handler,
+ test_pipe3.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ }
+ EXPECT_EQ(1, odd_handler.error_count());
+ EXPECT_EQ(1, even_handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, odd_handler.last_error_result());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, even_handler.last_error_result());
+}
+
+class AddHandlerOnErrorHandler : public TestRunLoopHandler {
+ public:
+ AddHandlerOnErrorHandler() : run_loop_(NULL) {
+ }
+ virtual ~AddHandlerOnErrorHandler() {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ virtual void OnHandleError(const Handle& handle, MojoResult result) override {
+ run_loop_->AddHandler(this, handle,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ TestRunLoopHandler::OnHandleError(handle, result);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AddHandlerOnErrorHandler);
+};
+
+TEST_F(RunLoopTest, AddHandlerOnError) {
+ AddHandlerOnErrorHandler handler;
+ MessagePipe test_pipe;
+ {
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler,
+ test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ }
+ EXPECT_EQ(1, handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, handler.last_error_result());
+}
+
+TEST_F(RunLoopTest, Current) {
+ EXPECT_TRUE(RunLoop::current() == NULL);
+ {
+ RunLoop run_loop;
+ EXPECT_EQ(&run_loop, RunLoop::current());
+ }
+ EXPECT_TRUE(RunLoop::current() == NULL);
+}
+
+class NestingRunLoopHandler : public TestRunLoopHandler {
+ public:
+ static const size_t kDepthLimit;
+ static const char kSignalMagic;
+
+ NestingRunLoopHandler()
+ : run_loop_(NULL),
+ pipe_(NULL),
+ depth_(0),
+ reached_depth_limit_(false) {}
+
+ virtual ~NestingRunLoopHandler() {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+ void set_pipe(MessagePipe* pipe) { pipe_ = pipe; }
+ bool reached_depth_limit() const { return reached_depth_limit_; }
+
+ // RunLoopHandler:
+ virtual void OnHandleReady(const Handle& handle) override {
+ TestRunLoopHandler::OnHandleReady(handle);
+ EXPECT_EQ(handle.value(), pipe_->handle0.get().value());
+
+ ReadSignal();
+ size_t current_depth = ++depth_;
+ if (current_depth < kDepthLimit) {
+ WriteSignal();
+ run_loop_->Run();
+ if (current_depth == kDepthLimit - 1) {
+ // The topmost loop Quit()-ed, so its parent takes back the
+ // control without exeeding deadline.
+ EXPECT_EQ(error_count(), 0);
+ } else {
+ EXPECT_EQ(error_count(), 1);
+ }
+
+ } else {
+ EXPECT_EQ(current_depth, kDepthLimit);
+ reached_depth_limit_ = true;
+ run_loop_->Quit();
+ }
+ --depth_;
+ }
+
+ void WriteSignal() {
+ char write_byte = kSignalMagic;
+ MojoResult write_result = WriteMessageRaw(
+ pipe_->handle1.get(),
+ &write_byte, 1, NULL, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ EXPECT_EQ(write_result, MOJO_RESULT_OK);
+ }
+
+ void ReadSignal() {
+ char read_byte = 0;
+ uint32_t bytes_read = 1;
+ uint32_t handles_read = 0;
+ MojoResult read_result = ReadMessageRaw(
+ pipe_->handle0.get(),
+ &read_byte, &bytes_read, NULL, &handles_read,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ EXPECT_EQ(read_result, MOJO_RESULT_OK);
+ EXPECT_EQ(read_byte, kSignalMagic);
+ }
+
+ private:
+ RunLoop* run_loop_;
+ MessagePipe* pipe_;
+ size_t depth_;
+ bool reached_depth_limit_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(NestingRunLoopHandler);
+};
+
+const size_t NestingRunLoopHandler::kDepthLimit = 10;
+const char NestingRunLoopHandler::kSignalMagic = 'X';
+
+TEST_F(RunLoopTest, NestedRun) {
+ NestingRunLoopHandler handler;
+ MessagePipe test_pipe;
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ handler.set_pipe(&test_pipe);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ static_cast<MojoDeadline>(10000));
+ handler.WriteSignal();
+ run_loop.Run();
+
+ EXPECT_TRUE(handler.reached_depth_limit());
+ // Got MOJO_RESULT_DEADLINE_EXCEEDED once then removed from the
+ // RunLoop's handler list.
+ EXPECT_EQ(handler.error_count(), 1);
+ EXPECT_EQ(handler.last_error_result(), MOJO_RESULT_DEADLINE_EXCEEDED);
+}
+
+struct Task {
+ Task(int num, std::vector<int>* sequence) : num(num), sequence(sequence) {}
+
+ void Run() const { sequence->push_back(num); }
+
+ int num;
+ std::vector<int>* sequence;
+};
+
+TEST_F(RunLoopTest, DelayedTaskOrder) {
+ std::vector<int> sequence;
+ RunLoop run_loop;
+ run_loop.PostDelayedTask(Closure(Task(1, &sequence)), 0);
+ run_loop.PostDelayedTask(Closure(Task(2, &sequence)), 0);
+ run_loop.PostDelayedTask(Closure(Task(3, &sequence)), 0);
+ run_loop.RunUntilIdle();
+
+ ASSERT_EQ(3u, sequence.size());
+ EXPECT_EQ(1, sequence[0]);
+ EXPECT_EQ(2, sequence[1]);
+ EXPECT_EQ(3, sequence[2]);
+}
+
+struct QuittingTask {
+ explicit QuittingTask(RunLoop* run_loop) : run_loop(run_loop) {}
+
+ void Run() const { run_loop->Quit(); }
+
+ RunLoop* run_loop;
+};
+
+TEST_F(RunLoopTest, QuitFromDelayedTask) {
+ TestRunLoopHandler handler;
+ MessagePipe test_pipe;
+ RunLoop run_loop;
+ run_loop.AddHandler(&handler,
+ test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ run_loop.PostDelayedTask(Closure(QuittingTask(&run_loop)), 0);
+ run_loop.Run();
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/tests/thread_unittest.cc b/mojo/public/cpp/utility/tests/thread_unittest.cc
new file mode 100644
index 0000000..0d81ef8
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/thread_unittest.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium 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 "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class SetIntThread : public Thread {
+ public:
+ SetIntThread(int* int_to_set, int value)
+ : int_to_set_(int_to_set),
+ value_(value) {
+ }
+ SetIntThread(const Options& options, int* int_to_set, int value)
+ : Thread(options),
+ int_to_set_(int_to_set),
+ value_(value) {
+ }
+
+ virtual ~SetIntThread() {
+ }
+
+ virtual void Run() override { *int_to_set_ = value_; }
+
+ private:
+ int* const int_to_set_;
+ const int value_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SetIntThread);
+};
+
+TEST(ThreadTest, CreateAndJoin) {
+ int value = 0;
+
+ // Not starting the thread should result in a no-op.
+ {
+ SetIntThread thread(&value, 1234567);
+ }
+ EXPECT_EQ(0, value);
+
+ // Start and join.
+ {
+ SetIntThread thread(&value, 12345678);
+ thread.Start();
+ thread.Join();
+ EXPECT_EQ(12345678, value);
+ }
+
+ // Ditto, with non-default (but reasonable) stack size.
+ {
+ Thread::Options options;
+ options.set_stack_size(1024 * 1024); // 1 MB.
+ SetIntThread thread(options, &value, 12345678);
+ thread.Start();
+ thread.Join();
+ EXPECT_EQ(12345678, value);
+ }
+}
+
+// Tests of assertions for Debug builds.
+// Note: It's okay to create threads, despite gtest having to fork. (The threads
+// are in the child process.)
+#if !defined(NDEBUG)
+TEST(ThreadTest, DebugAssertionFailures) {
+ // Can only start once.
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ SetIntThread thread(&value, 1);
+ thread.Start();
+ thread.Start();
+ }, "");
+
+ // Must join (if you start).
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ SetIntThread thread(&value, 2);
+ thread.Start();
+ }, "");
+
+ // Can only join once.
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ SetIntThread thread(&value, 3);
+ thread.Start();
+ thread.Join();
+ thread.Join();
+ }, "");
+
+ // Stack too big (we're making certain assumptions here).
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ Thread::Options options;
+ options.set_stack_size(static_cast<size_t>(-1));
+ SetIntThread thread(options, &value, 4);
+ thread.Start();
+ thread.Join();
+ }, "");
+}
+#endif // !defined(NDEBUG)
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/thread.h b/mojo/public/cpp/utility/thread.h
new file mode 100644
index 0000000..b7d10ee
--- /dev/null
+++ b/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/mojo/public/gles2/BUILD.gn b/mojo/public/gles2/BUILD.gn
new file mode 100644
index 0000000..5c21e78
--- /dev/null
+++ b/mojo/public/gles2/BUILD.gn
@@ -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.
+
+# In an is_component_build build, everything can link against //mojo/gles2
+# because it is built as a shared library. However, in a static build,
+# //mojo/gles2 is linked into an executable (e.g., mojo_shell), and must be
+# injected into other shared libraries (i.e., Mojo Apps) that need the mojo
+# gles2 API.
+#
+# For component targets, add //mojo/public/gles2:for_component to your deps
+# section.
+#
+# For shared_library targets (e.g., a Mojo App), add
+# //mojo/public/gles2:for_shared_library to your deps
+
+group("for_shared_library") {
+ public_configs = [ "//third_party/khronos:khronos_headers" ]
+ public_deps = [ "//mojo/public/c/gles2" ]
+
+ if (is_component_build) {
+ deps = [ "//mojo/gles2" ]
+ } else {
+ deps = [ "//mojo/public/platform/native:gles2_thunks" ]
+ }
+}
+
+group("for_component") {
+ public_configs = [ "//third_party/khronos:khronos_headers" ]
+ public_deps = [ "//mojo/public/c/gles2" ]
+
+ if (is_component_build) {
+ deps = [ "//mojo/gles2" ]
+ }
+}
diff --git a/mojo/public/go/mojo/system/core.go b/mojo/public/go/mojo/system/core.go
new file mode 100644
index 0000000..8049d37
--- /dev/null
+++ b/mojo/public/go/mojo/system/core.go
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium 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 system;
+
+type Core interface {
+ GetTimeTicksNow() int64
+}
diff --git a/mojo/public/interfaces/application/BUILD.gn b/mojo/public/interfaces/application/BUILD.gn
new file mode 100644
index 0000000..91e2447
--- /dev/null
+++ b/mojo/public/interfaces/application/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_public.gypi:mojo_application_bindings
+mojom("application") {
+ sources = [
+ "application.mojom",
+ "service_provider.mojom",
+ "shell.mojom",
+ ]
+}
diff --git a/mojo/public/interfaces/application/application.mojom b/mojo/public/interfaces/application/application.mojom
new file mode 100644
index 0000000..758a053
--- /dev/null
+++ b/mojo/public/interfaces/application/application.mojom
@@ -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.
+
+import "mojo/public/interfaces/application/service_provider.mojom"
+
+module mojo {
+
+// Applications vend Services through the ServiceProvider interface. Services
+// implement Interfaces.
+[Client=Shell]
+interface Application {
+ // Initialize is guaranteed to be called before any AcceptConnection calls.
+ Initialize(array<string>? args);
+
+ AcceptConnection(string? requestor_url, ServiceProvider? provider);
+};
+
+}
diff --git a/mojo/public/interfaces/application/service_provider.mojom b/mojo/public/interfaces/application/service_provider.mojom
new file mode 100644
index 0000000..00eb057
--- /dev/null
+++ b/mojo/public/interfaces/application/service_provider.mojom
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium 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 {
+
+// ServiceProvider is used to establish connections to services in a bi-
+// directional fashion between two applications.
+[Client=ServiceProvider]
+interface ServiceProvider {
+ // Connect the given message pipe handle to the named service. If the named
+ // service does not exist, then the handle will be closed.
+ ConnectToService(string? interface_name, handle<message_pipe>? pipe);
+};
+
+}
diff --git a/mojo/public/interfaces/application/shell.mojom b/mojo/public/interfaces/application/shell.mojom
new file mode 100644
index 0000000..a9ce05f
--- /dev/null
+++ b/mojo/public/interfaces/application/shell.mojom
@@ -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 "mojo/public/interfaces/application/service_provider.mojom"
+
+module mojo {
+
+// The Shell is the finder and launcher of Applications. An Application uses
+// it's Shell interface to connect to other Applications.
+[Client=Application]
+interface Shell {
+ // Loads url. mojo:{service} will result in the user of the value of the
+ // --origin flag to the shell being used.
+ ConnectToApplication(string? application_url, ServiceProvider&? provider);
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
new file mode 100644
index 0000000..e56f9b5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -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("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("test_interfaces") {
+ testonly = true
+ sources = [
+ "math_calculator.mojom",
+ "no_module.mojom",
+ "rect.mojom",
+ "regression_tests.mojom",
+ "sample_factory.mojom",
+ "sample_import.mojom",
+ "sample_import2.mojom",
+ "sample_interfaces.mojom",
+ "sample_service.mojom",
+ "serialization_test_structs.mojom",
+ "test_structs.mojom",
+ "validation_test_interfaces.mojom",
+ ]
+}
diff --git a/mojo/public/interfaces/bindings/tests/data/message_data b/mojo/public/interfaces/bindings/tests/data/message_data
new file mode 100644
index 0000000..b288878
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/message_data
@@ -0,0 +1,25 @@
+// File generated by mojo_message_generator.
+0X10
+0X00
+0X00
+0X00
+0X02
+0X00
+0X00
+0X00
+0X15
+0X00
+0X00
+0X00
+0X00
+0X00
+0X00
+0X00
+0X09
+0X08
+0X07
+0X06
+0X00
+0X00
+0X00
+0X00
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data
new file mode 100644
index 0000000..ee5ecdb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data
@@ -0,0 +1,2 @@
+[u4]16 // num_bytes: Bigger than the total size of the message.
+[u4]2 // num_fields
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data
new file mode 100644
index 0000000..21e7fbc
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data
@@ -0,0 +1 @@
+0x00
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flags.data
new file mode 100644
index 0000000..9d66188
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flags.data
@@ -0,0 +1,6 @@
+[dist4]message_header // num_bytes
+[u4]3 // num_fields
+[u4]0x80000000 // name
+[u4]3 // flags: This combination is illegal.
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flags.expected
new file mode 100644
index 0000000..696c78d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flags.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data
new file mode 100644
index 0000000..2414431
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data
@@ -0,0 +1,6 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0x80000000 // name
+[u4]1 // flags: This is a response message which expects to
+ // have a request ID.
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected
new file mode 100644
index 0000000..083db1a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data
new file mode 100644
index 0000000..ad3b005
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data
@@ -0,0 +1,4 @@
+[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow.
+[u4]2 // num_fields
+[u4]0x80000000 // name
+[u4]0 // flags
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data
new file mode 100644
index 0000000..353b4e8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data
@@ -0,0 +1,4 @@
+[dist4]message_header // num_bytes: Less than the minimal size of message
+ // header.
+[u4]2 // num_fields
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data
new file mode 100644
index 0000000..3a94448
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data
@@ -0,0 +1,4 @@
+[u4]0 // num_bytes
+[u4]0 // num_fields
+[u4]0x80000000 // name
+[u4]0 // flags
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_1.data
new file mode 100644
index 0000000..7e8a714
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_1.data
@@ -0,0 +1,7 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0x80000000 // name
+[u4]0 // flags
+[u8]0 // Extra bytes that result in mismatched |num_bytes| and
+ // |num_fields|.
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_1.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_1.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_2.data
new file mode 100644
index 0000000..fd8b4b4
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_2.data
@@ -0,0 +1,8 @@
+[dist4]message_header // num_bytes
+[u4]3 // num_fields
+[u4]0x80000000 // name
+[u4]0 // flags
+[u8]0 // request_id
+[u8]0 // Extra bytes that result in mismatched |num_bytes| and
+ // |num_fields|.
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_2.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_3.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_3.data
new file mode 100644
index 0000000..c4b46ab
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_3.data
@@ -0,0 +1,5 @@
+[dist4]message_header // num_bytes
+[u4]8 // num_fields: |num_bytes| is too small for |num_fields|.
+[u4]0x80000000 // name
+[u4]0 // flags
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_3.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_3.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_num_fields_mismatch_3.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_fields_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_fields_less_than_min_requirement.data
new file mode 100644
index 0000000..a94e6ce
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_fields_less_than_min_requirement.data
@@ -0,0 +1,6 @@
+[dist4]message_header // num_bytes
+[u4]1 // num_fields: Less than the minimal number of fields
+ // that we expect.
+[u4]0x80000000 // name
+[u4]0 // flags
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_fields_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_fields_less_than_min_requirement.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_fields_less_than_min_requirement.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data
new file mode 100644
index 0000000..1a5603e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data
@@ -0,0 +1,11 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]1 // num_fields
+[f]-1 // param0
+[u4]0 // padding
+[anchr]method0_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data
new file mode 100644
index 0000000..5556808
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data
@@ -0,0 +1,9 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[u4]16 // num_bytes: Incomplete struct.
+[u4]1 // num_fields
+[f]-1 // param0
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data
new file mode 100644
index 0000000..9a362f6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data
@@ -0,0 +1,7 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[u4]16 // num_bytes: Incomplete struct header.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data
new file mode 100644
index 0000000..52e4540
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data
@@ -0,0 +1,10 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow.
+[u4]1 // num_fields
+[f]-1 // param0
+[u4]0 // padding
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data
new file mode 100644
index 0000000..7c966f8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data
@@ -0,0 +1,9 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method0_params // num_bytes: Less than the minimal size that we expect.
+[u4]1 // num_fields
+[anchr]method0_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data
new file mode 100644
index 0000000..af20941
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data
@@ -0,0 +1,10 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[u4]4 // num_bytes: Less than the size of struct header.
+[u4]1 // num_fields
+[f]-1 // param0
+[u4]0 // padding
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data
new file mode 100644
index 0000000..d67a72f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data
@@ -0,0 +1,16 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]1 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method1_params
+
+[anchr]param0_ptr
+[dist4]struct_a // num_bytes
+[u4]1 // num_fields
+[u8]1234 // i
+[anchr]struct_a
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data
new file mode 100644
index 0000000..051dccc
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data
@@ -0,0 +1,18 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]1 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method1_params
+
+[u1]0 // Causes the following struct to be misaligned.
+
+[anchr]param0_ptr
+[dist4]struct_a // num_bytes
+[u4]1 // num_fields
+[u8]1234 // i
+[anchr]struct_a
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected
new file mode 100644
index 0000000..acca999
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MISALIGNED_OBJECT
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data
new file mode 100644
index 0000000..98a21c4
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data
@@ -0,0 +1,11 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]1 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]1 // num_fields
+[u8]0xFFFFFFFFFFFFFFFF // param0: Test whether decoding the pointer causes
+ // overflow.
+[anchr]method1_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected
new file mode 100644
index 0000000..23abb8c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data
new file mode 100644
index 0000000..c53a78b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data
@@ -0,0 +1,10 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]1 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method1_params // num_bytes
+[u4]1 // num_fields
+[u8]0 // param0: An unexpected null pointer.
+[anchr]method1_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected
new file mode 100644
index 0000000..95d8db0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data
new file mode 100644
index 0000000..38e20ab
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]2 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]1 // num_fields
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+[u8]0 // Having extra bytes in the middle is okay if the following objects are
+ // still properly alignmented.
+
+[anchr]struct_a_ptr
+[dist4]struct_a_member // num_bytes
+[u4]1 // num_fields
+[u8]12345 // i
+[anchr]struct_a_member
+
+[anchr]param1_ptr
+[dist4]struct_a_param // num_bytes
+[u4]1 // num_fields
+[u8]67890 // i
+[anchr]struct_a_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data
new file mode 100644
index 0000000..a12121b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data
@@ -0,0 +1,25 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]2 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]1 // num_fields
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+// There are two pointers pointing to the same struct.
+[anchr]struct_a_ptr
+[anchr]param1_ptr
+[dist4]struct_a // num_bytes
+[u4]1 // num_fields
+[u8]12345 // i
+[anchr]struct_a
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data
new file mode 100644
index 0000000..a916f03
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data
@@ -0,0 +1,30 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]2 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]1 // num_fields
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+[anchr]struct_a_ptr
+[dist4]struct_a_member // num_bytes
+[u4]1 // num_fields
+
+[anchr]param1_ptr
+// The following |num_bytes| and |num_fields| fields are also the |i| field
+// of the previous struct.
+[dist4]struct_a_param // num_bytes
+[u4]1 // num_fields
+[anchr]struct_a_member
+[u8]67890 // i
+[anchr]struct_a_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data
new file mode 100644
index 0000000..7145ccd
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]2 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method2_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method2_params
+
+[anchr]param0_ptr
+[dist4]struct_b // num_bytes
+[u4]1 // num_fields
+[dist8]struct_a_ptr // struct_a
+[anchr]struct_b
+
+// The following two structs are arranged in wrong order.
+
+[anchr]param1_ptr
+[dist4]struct_a_param // num_bytes
+[u4]1 // num_fields
+[u8]67890 // i
+[anchr]struct_a_param
+
+[anchr]struct_a_ptr
+[dist4]struct_a_member // num_bytes
+[u4]1 // num_fields
+[u8]12345 // i
+[anchr]struct_a_member
+
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data
new file mode 100644
index 0000000..ca7651a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data
@@ -0,0 +1,16 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow.
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data
new file mode 100644
index 0000000..9bc1fc3
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data
@@ -0,0 +1,16 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]7 // num_bytes: Less than the size of array header.
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected
new file mode 100644
index 0000000..5a1ec4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data
new file mode 100644
index 0000000..bed891c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data
@@ -0,0 +1,18 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]array // num_bytes: Less than the size needed (array header + 12 boolean
+ // values).
+[u4]12 // num_elements
+[b]01010101
+[anchr]array
+[b]00001111
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected
new file mode 100644
index 0000000..5a1ec4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data
new file mode 100644
index 0000000..f8d7644
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data
@@ -0,0 +1,11 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[u8]0xFFFFFFFFFFFFFFFF // param0: Test whether decoding the pointer causes
+ // overflow.
+[anchr]method3_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected
new file mode 100644
index 0000000..23abb8c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data
new file mode 100644
index 0000000..cc7dd38
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data
@@ -0,0 +1,17 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[dist4]array // num_bytes
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
+[anchr]array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data
new file mode 100644
index 0000000..7850893
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data
@@ -0,0 +1,14 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]16 // num_bytes
+[u1]0 // num_elements: Incomplete array.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data
new file mode 100644
index 0000000..5de9fe6
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data
@@ -0,0 +1,13 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[anchr]param0_ptr
+[u4]16 // num_bytes: Incomplete array header.
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data
new file mode 100644
index 0000000..40a13e0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method3_params
+
+[u2]0 // Causes the following array to be misaligned.
+
+[anchr]param0_ptr
+[dist4]array // num_bytes
+[u4]12 // num_elements
+[b]01010101
+[b]00001111
+[anchr]array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected
new file mode 100644
index 0000000..acca999
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MISALIGNED_OBJECT
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data
new file mode 100644
index 0000000..0edab4b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data
@@ -0,0 +1,10 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]3 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method3_params // num_bytes
+[u4]1 // num_fields
+[u8]0 // param0: An unexpected null pointer.
+[anchr]method3_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected
new file mode 100644
index 0000000..95d8db0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data
new file mode 100644
index 0000000..46f7502
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]4 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method4_params // num_bytes: Larger than what we know is okay.
+[u4]3 // num_fields: Larger than what we know is okay.
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[u8]0 // unknown
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]1 // num_fields
+[dist8]array_ptr // array
+[anchr]struct_c
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
+
+[u4]0 [u1]0 // Padding to make the next array aligned properly.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data
new file mode 100644
index 0000000..f3a0e9f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data
@@ -0,0 +1,24 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]4 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method4_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]1 // num_fields
+[dist8]array_ptr // array
+[anchr]struct_c
+
+[anchr]param1_ptr
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data
new file mode 100644
index 0000000..d819857
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data
@@ -0,0 +1,30 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]4 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method4_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]1 // num_fields
+[dist8]array_ptr // array
+[anchr]struct_c
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+
+[anchr]param1_ptr
+// The first three bytes of |num_bytes| are also the elements of the previous
+// array.
+[dist4]array_param // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_param
+[anchr]array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data
new file mode 100644
index 0000000..fdc925f
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data
@@ -0,0 +1,33 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]4 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method4_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method4_params
+
+[anchr]param0_ptr
+[dist4]struct_c // num_bytes
+[u4]1 // num_fields
+[dist8]array_ptr // array
+[anchr]struct_c
+
+// The following two arrays are arranged in wrong order.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_param
+
+[u4]0 [u2]0 // Padding to make the next array aligned properly.
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected
new file mode 100644
index 0000000..779df88
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data
new file mode 100644
index 0000000..0518c14
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data
@@ -0,0 +1,35 @@
+[handles]10 // Larger than the number of handles that we know about is okay.
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]5 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]2 // num_fields
+[dist8]struct_d_ptr // struct_d
+[u4]3 // data_pipe_consumer
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]1 // num_fields
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data
new file mode 100644
index 0000000..43cad7c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data
@@ -0,0 +1,36 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]5 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[u4]10 // param1: It is outside of the valid encoded handle
+ // range [0, 10).
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]2 // num_fields
+[dist8]struct_d_ptr // struct_d
+[u4]3 // data_pipe_consumer
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]1 // num_fields
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected
new file mode 100644
index 0000000..eef8e38
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data
new file mode 100644
index 0000000..8b39c00
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data
@@ -0,0 +1,35 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]5 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]2 // num_fields
+[dist8]struct_d_ptr // struct_d
+[u4]4 // data_pipe_consumer: The same value as |param1| above.
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]1 // num_fields
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]0
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected
new file mode 100644
index 0000000..eef8e38
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data
new file mode 100644
index 0000000..95a356b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data
@@ -0,0 +1,35 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]5 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]2 // num_fields
+[dist8]struct_d_ptr // struct_d
+[u4]3 // data_pipe_consumer
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]1 // num_fields
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]1 // The two message pipe handles have the same value.
+[u4]1
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected
new file mode 100644
index 0000000..eef8e38
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data
new file mode 100644
index 0000000..ef49285
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data
@@ -0,0 +1,34 @@
+[handles]5
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]5 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[u4]4 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]2 // num_fields
+[dist8]struct_d_ptr // struct_d
+[s4]-1 // data_pipe_consumer: An unexpected invalid handle.
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]1 // num_fields
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]1 // num_elements
+[u4]2
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected
new file mode 100644
index 0000000..6768236
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data
new file mode 100644
index 0000000..c6fa8ea
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data
@@ -0,0 +1,36 @@
+[handles]10
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]5 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method5_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[u4]9 // param1
+[u4]0 // padding
+[anchr]method5_params
+
+[anchr]param0_ptr
+[dist4]struct_e // num_bytes
+[u4]2 // num_fields
+[dist8]struct_d_ptr // struct_d
+[u4]1 // data_pipe_consumer: It is smaller than those handles
+ // in |message_pipe_array|, which is wrong.
+[u4]0 // padding
+[anchr]struct_e
+
+[anchr]struct_d_ptr
+[dist4]struct_d // num_bytes
+[u4]1 // num_fields
+[dist8]message_pipes_ptr // message_pipes
+[anchr]struct_d
+
+[anchr]message_pipes_ptr
+[dist4]message_pipe_array // num_bytes
+[u4]2 // num_elements
+[u4]3
+[u4]4
+[anchr]message_pipe_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected
new file mode 100644
index 0000000..eef8e38
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_ILLEGAL_HANDLE
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data
new file mode 100644
index 0000000..4f34c0b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data
@@ -0,0 +1,22 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]6 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method6_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method6_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]1 // num_elements
+[dist8]element_ptr
+[anchr]array_param
+
+[anchr]element_ptr
+[dist4]array_element // num_bytes
+[u4]10 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_element
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data
new file mode 100644
index 0000000..a58396c
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data
@@ -0,0 +1,22 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]6 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method6_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method6_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]1 // num_elements
+[dist8]element_ptr
+[anchr]array_param
+
+[anchr]element_ptr
+[dist4]array_element // num_bytes: It is insufficient to store 12 elements.
+[u4]12 // num_elements
+0 1 2 3 4 5 6 7 8 9
+[anchr]array_element
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected
new file mode 100644
index 0000000..5a1ec4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data
new file mode 100644
index 0000000..d6562af
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data
@@ -0,0 +1,32 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]7 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[u8]0 // unknown
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]1 // num_fields
+[dist8]array_ptr // fixed_size_array
+[anchr]struct_f
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]3 // num_elements
+0 1 2
+[anchr]array_member
+
+[u4]0 [u1]0 // Padding to make the next array aligned properly.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_too_few_array_elements.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_too_few_array_elements.data
new file mode 100644
index 0000000..bf1ddd5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_too_few_array_elements.data
@@ -0,0 +1,31 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]7 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]1 // num_fields
+[dist8]array_ptr // fixed_size_array
+[anchr]struct_f
+
+[anchr]array_ptr
+[dist4]array_member // num_bytes
+[u4]2 // num_elements: Too few elements.
+0 1
+[anchr]array_member
+
+[u4]0 [u1]0 [u1]0 // Padding for alignment of next array.
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_too_few_array_elements.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_too_few_array_elements.expected
new file mode 100644
index 0000000..5a1ec4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_too_few_array_elements.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data
new file mode 100644
index 0000000..ab69175
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data
@@ -0,0 +1,23 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]7 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method7_params // num_bytes
+[u4]2 // num_fields
+[dist8]param0_ptr // param0
+[dist8]param1_ptr // param1
+[anchr]method7_params
+
+[anchr]param0_ptr
+[dist4]struct_f // num_bytes
+[u4]1 // num_fields
+[u8]0 // fixed_size_array: An unexpected null pointer.
+[anchr]struct_f
+
+[anchr]param1_ptr
+[dist4]array_param // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected
new file mode 100644
index 0000000..95d8db0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data
new file mode 100644
index 0000000..059e538
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data
@@ -0,0 +1,19 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]8 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method8_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]0x20000001 // num_elements: The corresponding array size should be
+ // 0x20000001 * 8 + 8 = 0x100000010 which is
+ // 2^32 + 16 (base-10), while |num_bytes| is a 32-bit
+ // unsigned integer and its value is 16.
+[u8]0
+[anchr]array_param
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected
new file mode 100644
index 0000000..5a1ec4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data
new file mode 100644
index 0000000..fd5e174
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data
@@ -0,0 +1,30 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]8 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method8_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]3 // num_elements
+[u8]0 // A null pointer, which is okay.
+[dist8]nested_array_ptr
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
+
+[anchr]nested_array_ptr
+[dist4]nested_array // num_bytes
+[u4]1 // num_elements
+[dist8]string_ptr
+[anchr]nested_array
+
+[anchr]string_ptr
+[dist4]string // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data
new file mode 100644
index 0000000..f1d8718
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data
@@ -0,0 +1,10 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]8 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]1 // num_fields
+[u8]0 // param0: An unexpected null pointer.
+[anchr]method8_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected
new file mode 100644
index 0000000..95d8db0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data
new file mode 100644
index 0000000..104dddb
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data
@@ -0,0 +1,31 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]8 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method8_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method8_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]3 // num_elements
+[u8]0 // A null pointer, which is okay.
+[dist8]nested_array_ptr
+[u8]0 // A null pointer, which is okay.
+[anchr]array_param
+
+[anchr]nested_array_ptr
+[dist4]nested_array // num_bytes
+[u4]2 // num_elements
+[dist8]string_ptr
+[u8]0 // An unexpected null pointer.
+[anchr]nested_array
+
+[anchr]string_ptr
+[dist4]string // num_bytes
+[u4]5 // num_elements
+0 1 2 3 4
+[anchr]string
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected
new file mode 100644
index 0000000..95d8db0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data
new file mode 100644
index 0000000..c548906
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data
@@ -0,0 +1,34 @@
+[handles]4
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]9 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method9_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method9_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[dist8]nested_array_ptr_0
+[dist8]nested_array_ptr_1
+[anchr]array_param
+
+[anchr]nested_array_ptr_0
+[dist4]nested_array_0 // num_bytes
+[u4]2 // num_elements
+[u4]0
+[s4]-1 // An invalid handle, which is okay.
+[anchr]nested_array_0
+
+[anchr]nested_array_ptr_1
+[dist4]nested_array_1 // num_bytes
+[u4]3 // num_elements
+[u4]2
+[s4]-1 // An invalid handle, which is okay.
+[u4]3
+[anchr]nested_array_1
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data
new file mode 100644
index 0000000..f0967d5
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data
@@ -0,0 +1,12 @@
+[handles]4
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]9 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method9_params // num_bytes
+[u4]1 // num_fields
+[u8]0 // param0: A null pointer, which is okay.
+[anchr]method9_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data
new file mode 100644
index 0000000..9c8f478
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data
@@ -0,0 +1,25 @@
+[handles]4
+
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]9 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method9_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method9_params
+
+[anchr]param0_ptr
+[dist4]array_param // num_bytes
+[u4]2 // num_elements
+[dist8]nested_array_ptr_0
+[u8]0 // An unexpected null pointer.
+[anchr]array_param
+
+[anchr]nested_array_ptr_0
+[dist4]nested_array_0 // num_bytes
+[u4]1 // num_elements
+[u4]0
+[anchr]nested_array_0
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected
new file mode 100644
index 0000000..95d8db0
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_NULL_POINTER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_good.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_good.data
new file mode 100644
index 0000000..df7b7e8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_good.data
@@ -0,0 +1,17 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]basic_struct // num_bytes
+[u4]1 // num_fields
+[s4]-1 // a
+[u4]0 // padding
+[anchr]basic_struct
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_unexpected_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_unexpected_struct_header.data
new file mode 100644
index 0000000..3dcca9d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_unexpected_struct_header.data
@@ -0,0 +1,14 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[u4]0 // num_bytes: The struct size is too small.
+[u4]0 // num_fields
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_unexpected_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_unexpected_struct_header.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf1_mthd0_rqst_unexpected_struct_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_good.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_good.data
new file mode 100644
index 0000000..f4b6c40
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_good.data
@@ -0,0 +1,17 @@
+[dist4]message_header // num_bytes
+[u4]3 // num_fields
+[u4]0 // name
+[u4]2 // flags: Is response.
+[u8]1 // request_id
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]uint8_array // num_bytes
+[u4]1 // num_elements
+[u1]0
+[anchr]uint8_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_good.expected
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_good.expected
@@ -0,0 +1 @@
+PASS
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_unexpected_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_unexpected_array_header.data
new file mode 100644
index 0000000..a357013
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_unexpected_array_header.data
@@ -0,0 +1,17 @@
+[dist4]message_header // num_bytes
+[u4]3 // num_fields
+[u4]0 // name
+[u4]2 // flags: Is response.
+[u8]1 // request_id
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]1 // num_fields
+[dist8]param0_ptr // param0
+[anchr]method0_params
+
+[anchr]param0_ptr
+[dist4]uint8_array // num_bytes
+[u4]2 // num_elements: The size is too small to hold 2 elements.
+[u1]0
+[anchr]uint8_array
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_unexpected_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_unexpected_array_header.expected
new file mode 100644
index 0000000..5a1ec4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf2_mthd0_resp_unexpected_array_header.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data
new file mode 100644
index 0000000..6d2cb4e
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data
@@ -0,0 +1,6 @@
+[dist4]message_header // num_bytes
+[u4]3 // num_fields
+[u4]0xffffffff // name
+[u4]3 // flags: This combination is illegal.
+[u8]1 // request_id
+[anchr]message_header
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected
new file mode 100644
index 0000000..696c78d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/not_implemented_mthd0_struct_num_fields_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/not_implemented_mthd0_struct_num_fields_less_than_min_requirement.data
new file mode 100644
index 0000000..ffcafd3
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/not_implemented_mthd0_struct_num_fields_less_than_min_requirement.data
@@ -0,0 +1,12 @@
+[dist4]message_header // num_bytes
+[u4]2 // num_fields
+[u4]0 // name
+[u4]0 // flags
+[anchr]message_header
+
+[dist4]method0_params // num_bytes
+[u4]0 // num_fields: Less than the minimal number of fields
+ // that we expect.
+[f]-1 // param0
+[u4]0 // padding
+[anchr]method0_params
diff --git a/mojo/public/interfaces/bindings/tests/data/validation/not_implemented_mthd0_struct_num_fields_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/not_implemented_mthd0_struct_num_fields_less_than_min_requirement.expected
new file mode 100644
index 0000000..25aceee
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/data/validation/not_implemented_mthd0_struct_num_fields_less_than_min_requirement.expected
@@ -0,0 +1 @@
+VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER
diff --git a/mojo/public/interfaces/bindings/tests/math_calculator.mojom b/mojo/public/interfaces/bindings/tests/math_calculator.mojom
new file mode 100644
index 0000000..a18add1
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/math_calculator.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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.math"]
+module math {
+
+[Client=CalculatorUI]
+interface Calculator {
+ Clear@0();
+ Add@1(double value@0);
+ Multiply@2(double value@0);
+};
+
+interface CalculatorUI {
+ Output@0(double value@0);
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/no_module.mojom b/mojo/public/interfaces/bindings/tests/no_module.mojom
new file mode 100644
index 0000000..f380011
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/no_module.mojom
@@ -0,0 +1,9 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Entities without module
+
+enum EnumWithoutModule {
+ A
+};
diff --git a/mojo/public/interfaces/bindings/tests/rect.mojom b/mojo/public/interfaces/bindings/tests/rect.mojom
new file mode 100644
index 0000000..324ce48
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/rect.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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_structs"]
+module mojo.test {
+
+struct Rect {
+ int32 x;
+ int32 y;
+ int32 width;
+ int32 height;
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/regression_tests.mojom b/mojo/public/interfaces/bindings/tests/regression_tests.mojom
new file mode 100644
index 0000000..49ab69a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/regression_tests.mojom
@@ -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.
+
+// Module containing entities for regression tests of the generator. Entities
+// must never be modified, instead new entity must be added to add new tests.
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.regression_tests"]
+module regression_tests {
+
+interface CheckMethodWithEmptyResponse {
+WithouParameterAndEmptyResponse() => ();
+WithParameterAndEmptyResponse(bool b) => ();
+};
+
+interface CheckNameCollision {
+WithNameCollision(bool message, bool response) => (bool message, bool response);
+};
+
+enum EnumWithReference {
+ k_STEREO_AND_KEYBOARD_MIC = 30,
+ k_MAX = k_STEREO_AND_KEYBOARD_MIC
+};
+
+enum EnumWithLowercase {
+ PlanarF16,
+ PlanarF32
+};
+
+enum EnumWithNumbers {
+ k_2_1 = 4
+};
+
+enum EnumWithK {
+ K = 0
+};
+
+} // module imported
diff --git a/mojo/public/interfaces/bindings/tests/sample_factory.mojom b/mojo/public/interfaces/bindings/tests/sample_factory.mojom
new file mode 100644
index 0000000..567cf23
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_factory.mojom
@@ -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.
+
+import "sample_import.mojom"
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"]
+module sample {
+
+// This sample shows how handles to MessagePipes can be sent as both parameters
+// to methods as well as fields on structs.
+
+struct Request {
+ int32 x;
+ handle<message_pipe>? pipe;
+ array<handle<message_pipe>>? more_pipes;
+
+ // Interfaces can be used as members.
+ imported.ImportedInterface? obj;
+};
+
+struct Response {
+ int32 x;
+ handle<message_pipe>? pipe;
+};
+
+interface NamedObject {
+ SetName(string name);
+ GetName() => (string name);
+};
+
+[Client=FactoryClient]
+interface Factory {
+ DoStuff(Request request, handle<message_pipe>? pipe);
+ DoStuff2(handle<data_pipe_consumer> pipe);
+ CreateNamedObject(NamedObject& obj);
+ RequestImportedInterface(
+ imported.ImportedInterface& obj) => (imported.ImportedInterface& obj);
+ TakeImportedInterface(
+ imported.ImportedInterface obj) => (imported.ImportedInterface obj);
+};
+
+interface FactoryClient {
+ DidStuff(Response response, string text);
+ DidStuff2(string text);
+};
+
+} // module sample
diff --git a/mojo/public/interfaces/bindings/tests/sample_import.mojom b/mojo/public/interfaces/bindings/tests/sample_import.mojom
new file mode 100644
index 0000000..659b74d
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_import.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"]
+module imported {
+
+// This sample just defines some types that are imported into
+// sample_service.mojom, to show how import works.
+
+enum Shape {
+ RECTANGLE = 1,
+ CIRCLE,
+ TRIANGLE,
+ LAST = TRIANGLE,
+};
+
+// These enum values should not interfere with those of Shape above.
+enum AnotherShape {
+ RECTANGLE = 10,
+ CIRCLE,
+ TRIANGLE,
+};
+
+enum YetAnotherShape {
+ RECTANGLE = 20,
+ CIRCLE,
+ TRIANGLE,
+};
+
+struct Point {
+ int32 x;
+ int32 y;
+};
+
+interface ImportedInterface {
+ DoSomething();
+};
+
+} // module imported
diff --git a/mojo/public/interfaces/bindings/tests/sample_import2.mojom b/mojo/public/interfaces/bindings/tests/sample_import2.mojom
new file mode 100644
index 0000000..63ddb5a
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_import2.mojom
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import "sample_import.mojom"
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"]
+module imported {
+
+// This sample adds more types and constants to the "imported" namespace,
+// to test a bug with importing multiple modules with the same namespace.
+
+enum Color {
+ RED,
+ BLACK,
+};
+
+struct Size {
+ int32 width;
+ int32 height;
+};
+
+struct Thing {
+ imported.Shape shape = RECTANGLE;
+ int32 color = Color.BLACK;
+ Point location;
+ Size size;
+};
+
+} // module imported
diff --git a/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom b/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom
new file mode 100644
index 0000000..01e78ed
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_interfaces.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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample",
+ JavaConstantsClassName="InterfaceConstants",
+ Foo = "hello world"]
+module sample {
+
+const uint64 kLong = 4405;
+
+enum Enum {
+ VALUE
+};
+
+[Client=ProviderClient]
+interface Provider {
+ EchoString(string a) => (string a);
+ EchoStrings(string a, string b) => (string a, string b);
+ EchoMessagePipeHandle(handle<message_pipe> a) => (handle<message_pipe> a);
+ EchoEnum(Enum a) => (Enum a);
+};
+
+// TODO(darin): We shouldn't need this, but JS bindings don't work without it.
+interface ProviderClient {
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/sample_service.mojom b/mojo/public/interfaces/bindings/tests/sample_service.mojom
new file mode 100644
index 0000000..baf03de
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/sample_service.mojom
@@ -0,0 +1,128 @@
+// 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 "sample_import.mojom"
+import "sample_import2.mojom"
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"]
+module sample {
+
+const uint8 kTwelve = 12;
+const uint64 kTooBigForSignedInt64 = 9999999999999999999;
+
+const double kDoubleInfinity = double.INFINITY;
+const double kDoubleNegativeInfinity = double.NEGATIVE_INFINITY;
+const double kDoubleNaN = double.NAN;
+
+const float kFloatInfinity = float.INFINITY;
+const float kFloatNegativeInfinity = float.NEGATIVE_INFINITY;
+const float kFloatNaN = float.NAN;
+
+struct Bar {
+ enum Type {
+ VERTICAL = 1,
+ HORIZONTAL,
+ BOTH,
+ INVALID
+ };
+ uint8 alpha@0 = 0xff;
+ uint8 beta@1;
+ uint8 gamma@2;
+ Type type@3 = sample.Bar.Type.VERTICAL;
+};
+
+[RequiredFields=7]
+struct Foo {
+ const string kFooby = "Fooby";
+ string name@8 = kFooby;
+ int32 x@0;
+ int32 y@1;
+ bool a@2 = true;
+ bool b@3;
+ bool c@4;
+ Bar? bar@5;
+ array<Bar>? extra_bars@7;
+ array<uint8>? data@6;
+ handle<message_pipe>? source@9;
+ array<handle<data_pipe_consumer>>? input_streams@10;
+ array<handle<data_pipe_producer>>? output_streams@11;
+ array<array<bool>>? array_of_array_of_bools@12;
+ array<array<array<string>>>? multi_array_of_strings@13;
+ array<bool>? array_of_bools@14;
+};
+
+struct DefaultsTest {
+ int8 a0@0 = -12;
+ uint8 a1@1 = sample.kTwelve;
+ int16 a2@2 = 1234;
+ uint16 a3@3 = 34567;
+ int32 a4@4 = 123456;
+ uint32 a5@5 = 3456789012;
+ int64 a6@6 = -111111111111;
+ uint64 a7@7 = 9999999999999999999;
+ int32 a8@8 = 0x12345;
+ int32 a9@9 = -0x12345;
+ int32 a10@10 = +1234;
+ bool a11@11 = true;
+ bool a12@12 = false;
+ float a13@13 = 123.25;
+ double a14@14 = 1234567890.123;
+ double a15@15 = 1E10;
+ double a16@16 = -1.2E+20;
+ double a17@17 = +1.23E-20;
+
+ // TODO(vtl): Add tests for default vs null when those are implemented (for
+ // structs, arrays, and strings).
+ array<uint8> a18@18;
+ string a19@19;
+
+ Bar.Type a20@20 = BOTH;
+ imported.Point a21@21;
+ imported.Thing a22@22 = default;
+
+ uint64 a23@23 = 0xFFFFFFFFFFFFFFFF;
+ int64 a24@24 = 0x123456789;
+ int64 a25@25 = -0x123456789;
+
+ double a26@26 = double.INFINITY;
+ double a27@27 = double.NEGATIVE_INFINITY;
+ double a28@28 = double.NAN;
+ float a29@29 = float.INFINITY;
+ float a30@30 = float.NEGATIVE_INFINITY;
+ float a31@31 = float.NAN;
+};
+
+struct StructWithHoleV1 {
+ int32 v1 = 1;
+ int64 v2 = 2;
+};
+
+struct StructWithHoleV2 {
+ int32 v1 = 1;
+ int64 v2 = 2;
+ int32 v3 = 3;
+};
+
+[Client=ServiceClient]
+interface Service {
+ enum BazOptions {
+ REGULAR = 0,
+ EXTRA
+ };
+ const uint8 kFavoriteBaz = 1;
+ Frobinate@0(Foo? foo@0, BazOptions baz@1, Port? port@2);
+ GetPort@1(Port& port @0);
+};
+
+interface ServiceClient {
+ DidFrobinate@0(int32 result@0);
+};
+
+// This interface is referenced above where it is defined. It also refers to
+// itself from a method.
+interface Port {
+ PostMessage@0(string message_text@0, Port port@1);
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom b/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom
new file mode 100644
index 0000000..f51c197
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"]
+module mojo.test {
+
+struct Struct1 {
+ uint8 i;
+};
+
+struct Struct2 {
+ handle hdl;
+};
+
+struct Struct3 {
+ Struct1 struct_1;
+};
+
+struct Struct4 {
+ array<Struct1> data;
+};
+
+struct Struct5 {
+ array<Struct1, 2> pair;
+};
+
+struct Struct6 {
+ string str;
+};
+
+struct StructOfNullables {
+ handle? hdl;
+ Struct1? struct_1;
+ string? str;
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/test_structs.mojom b/mojo/public/interfaces/bindings/tests/test_structs.mojom
new file mode 100644
index 0000000..d361da7
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_structs.mojom
@@ -0,0 +1,85 @@
+// 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 "mojo/public/interfaces/bindings/tests/rect.mojom"
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_structs"]
+module mojo.test {
+
+struct NamedRegion {
+ string? name;
+ array<Rect>? rects;
+};
+
+struct RectPair {
+ Rect? first;
+ Rect? second;
+};
+
+struct EmptyStruct {
+};
+
+// Used to verify that struct fields which don't specify a deafult are
+// initialized to: false for bool, 0 for numbers, and null for strings,
+// handles, and structs. The "?" nullable suffix shouldn't have any
+// impact on initial field values.
+
+struct NoDefaultFieldValues {
+ bool f0;
+ int8 f1;
+ uint8 f2;
+ int16 f3;
+ uint16 f4;
+ int32 f5;
+ uint32 f6;
+ int64 f7;
+ uint64 f8;
+ float f9;
+ double f10;
+ string f11;
+ string? f12;
+ handle<message_pipe> f13;
+ handle<data_pipe_consumer> f14;
+ handle<data_pipe_producer> f15;
+ handle<message_pipe>? f16;
+ handle<data_pipe_consumer>? f17;
+ handle<data_pipe_producer>? f18;
+ handle f19;
+ handle? f20;
+ handle<shared_buffer> f21;
+ handle<shared_buffer>? f22;
+ array<string> f23;
+ array<string?> f24;
+ array<string>? f25;
+ array<string?>? f26;
+ EmptyStruct f27;
+ EmptyStruct? f28;
+};
+
+// Used to verify that struct fields with an explicit default value
+// are initialized correctly. The "?" nullable suffix shouldn't have any
+// impact on initial field values.
+
+struct DefaultFieldValues {
+ const string kFoo = "foo";
+ bool f0 = true;
+ int8 f1 = 100;
+ uint8 f2 = 100;
+ int16 f3 = 100;
+ uint16 f4 = 100;
+ int32 f5 = 100;
+ uint32 f6 = 100;
+ int64 f7 = 100;
+ uint64 f8 = 100;
+ float f9 = 100;
+ float f10 = 100.0;
+ double f11 = 100;
+ double f12 = 100.0;
+ string f13 = kFoo;
+ string? f14 = kFoo;
+ Rect f15 = default;
+ Rect? f16 = default;
+};
+
+}
diff --git a/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom b/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom
new file mode 100644
index 0000000..5cd9d0b
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom
@@ -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.
+
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"]
+module mojo.test {
+
+struct StructA {
+ uint64 i;
+};
+
+struct StructB {
+ StructA struct_a;
+};
+
+struct StructC {
+ array<uint8> data;
+};
+
+struct StructD {
+ array<handle<message_pipe>> message_pipes;
+};
+
+struct StructE {
+ StructD struct_d;
+ handle<data_pipe_consumer> data_pipe_consumer;
+};
+
+struct StructF {
+ array<uint8, 3> fixed_size_array;
+};
+
+interface ConformanceTestInterface {
+ Method0(float param0);
+ Method1(StructA param0);
+ Method2(StructB param0, StructA param1);
+ Method3(array<bool> param0);
+ Method4(StructC param0, array<uint8> param1);
+ Method5(StructE param0, handle<data_pipe_producer> param1);
+ Method6(array<array<uint8>> param0);
+ Method7(StructF param0, array<uint8, 5> param1);
+ Method8(array<array<string>?> param0);
+ Method9(array<array<handle?>>? param0);
+};
+
+struct BasicStruct {
+ int32 a;
+};
+
+[Client=IntegrationTestInterface2]
+interface IntegrationTestInterface1 {
+ Method0(BasicStruct param0);
+};
+
+[Client=IntegrationTestInterface1]
+interface IntegrationTestInterface2 {
+ Method0() => (array<uint8> param0);
+};
+
+}
diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn
new file mode 100644
index 0000000..6f5d8e9
--- /dev/null
+++ b/mojo/public/java/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.
+
+import("//build/config/android/rules.gni")
+
+android_library("system") {
+ java_files = [
+ "system/src/org/chromium/mojo/system/AsyncWaiter.java",
+ "system/src/org/chromium/mojo/system/Core.java",
+ "system/src/org/chromium/mojo/system/DataPipe.java",
+ "system/src/org/chromium/mojo/system/Flags.java",
+ "system/src/org/chromium/mojo/system/Handle.java",
+ "system/src/org/chromium/mojo/system/InvalidHandle.java",
+ "system/src/org/chromium/mojo/system/MessagePipeHandle.java",
+ "system/src/org/chromium/mojo/system/MojoException.java",
+ "system/src/org/chromium/mojo/system/MojoResult.java",
+ "system/src/org/chromium/mojo/system/Pair.java",
+ "system/src/org/chromium/mojo/system/SharedBufferHandle.java",
+ "system/src/org/chromium/mojo/system/UntypedHandle.java",
+ ]
+}
+
+android_library("bindings") {
+ java_files = [
+ "bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java",
+ "bindings/src/org/chromium/mojo/bindings/BindingsHelper.java",
+ "bindings/src/org/chromium/mojo/bindings/Callbacks.java",
+ "bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java",
+ "bindings/src/org/chromium/mojo/bindings/Connector.java",
+ "bindings/src/org/chromium/mojo/bindings/Decoder.java",
+ "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java",
+ "bindings/src/org/chromium/mojo/bindings/DeserializationException.java",
+ "bindings/src/org/chromium/mojo/bindings/Encoder.java",
+ "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java",
+ "bindings/src/org/chromium/mojo/bindings/HandleOwner.java",
+ "bindings/src/org/chromium/mojo/bindings/Interface.java",
+ "bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java",
+ "bindings/src/org/chromium/mojo/bindings/InterfaceWithClient.java",
+ "bindings/src/org/chromium/mojo/bindings/MessageHeader.java",
+ "bindings/src/org/chromium/mojo/bindings/Message.java",
+ "bindings/src/org/chromium/mojo/bindings/MessageReceiver.java",
+ "bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java",
+ "bindings/src/org/chromium/mojo/bindings/RouterImpl.java",
+ "bindings/src/org/chromium/mojo/bindings/Router.java",
+ "bindings/src/org/chromium/mojo/bindings/SerializationException.java",
+ "bindings/src/org/chromium/mojo/bindings/ServiceMessage.java",
+ "bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java",
+ "bindings/src/org/chromium/mojo/bindings/Struct.java",
+ ]
+
+ deps = [ ":system" ]
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
new file mode 100644
index 0000000..8a83be9
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper around {@link Router} that will close the connection when not referenced anymore.
+ */
+class AutoCloseableRouter implements Router {
+
+ /**
+ * The underlying router.
+ */
+ private final Router mRouter;
+
+ /**
+ * The executor to close the underlying router.
+ */
+ private final Executor mExecutor;
+
+ /**
+ * Flags to keep track if this router has been correctly closed.
+ */
+ private boolean mClosed;
+
+ /**
+ * Constructor.
+ */
+ public AutoCloseableRouter(Core core, Router router) {
+ mRouter = router;
+ mExecutor = ExecutorFactory.getExecutorForCurrentThread(core);
+ }
+
+ /**
+ * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder)
+ */
+ @Override
+ public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) {
+ mRouter.setIncomingMessageReceiver(incomingMessageReceiver);
+ }
+
+ /**
+ * @see HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ return mRouter.passHandle();
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ return mRouter.accept(message);
+ }
+
+ /**
+ * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver)
+ */
+ @Override
+ public boolean acceptWithResponder(Message message, MessageReceiver responder) {
+ return mRouter.acceptWithResponder(message, responder);
+
+ }
+
+ /**
+ * @see Router#start()
+ */
+ @Override
+ public void start() {
+ mRouter.start();
+ }
+
+ /**
+ * @see Router#setErrorHandler(ConnectionErrorHandler)
+ */
+ @Override
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ mRouter.setErrorHandler(errorHandler);
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ mRouter.close();
+ mClosed = true;
+ }
+
+ /**
+ * @see Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ if (!mClosed) {
+ mExecutor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ close();
+ }
+ });
+ throw new IllegalStateException("Warning: Router objects should be explicitly closed " +
+ "when no longer required otherwise you may leak handles.");
+ }
+ super.finalize();
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
new file mode 100644
index 0000000..b18e5c5
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.AsyncWaiter;
+import org.chromium.mojo.system.Handle;
+
+/**
+ * Helper functions.
+ */
+public class BindingsHelper {
+ /**
+ * Alignment in bytes for mojo serialization.
+ */
+ public static final int ALIGNMENT = 8;
+
+ /**
+ * The size, in bytes, of a serialized handle. A handle is serialized as an int representing the
+ * offset of the handle in the list of handles.
+ */
+ public static final int SERIALIZED_HANDLE_SIZE = 4;
+
+ /**
+ * The size, in bytes, of a serialized pointer. A pointer is serializaed as an unsigned long
+ * representing the offset from its position to the pointed elemnt.
+ */
+ public static final int POINTER_SIZE = 8;
+
+ /**
+ * The value used for the expected length of a non-fixed size array.
+ */
+ public static final int UNSPECIFIED_ARRAY_LENGTH = -1;
+
+ /**
+ * Align |size| on {@link BindingsHelper#ALIGNMENT}.
+ */
+ public static int align(int size) {
+ return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
+ }
+
+ /**
+ * Passed as |arrayNullability| when neither the array nor its elements are nullable.
+ */
+ public static final int NOTHING_NULLABLE = 0;
+
+ /**
+ * "Array bit" of |arrayNullability| is set iff the array itself is nullable.
+ */
+ public static final int ARRAY_NULLABLE = (1 << 0);
+
+ /**
+ * "Element bit" of |arrayNullability| is set iff the array elements are nullable.
+ */
+ public static final int ELEMENT_NULLABLE = (1 << 1);
+
+ public static boolean isArrayNullable(int arrayNullability) {
+ return (arrayNullability & ARRAY_NULLABLE) > 0;
+ }
+
+ public static boolean isElementNullable(int arrayNullability) {
+ return (arrayNullability & ELEMENT_NULLABLE) > 0;
+ }
+
+ /**
+ * Align |size| on {@link BindingsHelper#ALIGNMENT}.
+ */
+ public static long align(long size) {
+ return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
+ }
+
+ /**
+ * Compute the size in bytes of the given string encoded as utf8.
+ */
+ public static int utf8StringSizeInBytes(String s) {
+ int res = 0;
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ int codepoint = c;
+ if (isSurrogate(c)) {
+ i++;
+ char c2 = s.charAt(i);
+ codepoint = Character.toCodePoint(c, c2);
+ }
+ res += 1;
+ if (codepoint > 0x7f) {
+ res += 1;
+ if (codepoint > 0x7ff) {
+ res += 1;
+ if (codepoint > 0xffff) {
+ res += 1;
+ if (codepoint > 0x1fffff) {
+ res += 1;
+ if (codepoint > 0x3ffffff) {
+ res += 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Determines if the given {@code char} value is a Unicode <i>surrogate code unit</i>. See
+ * {@link Character#isSurrogate}. Extracting here because the method only exists at API level
+ * 19.
+ */
+ private static boolean isSurrogate(char c) {
+ return c >= Character.MIN_SURROGATE && c < (Character.MAX_SURROGATE + 1);
+ }
+
+ /**
+ * Returns an {@link AsyncWaiter} to use with the given handle, or <code>null</code> if none if
+ * available.
+ */
+ static AsyncWaiter getDefaultAsyncWaiterForHandle(Handle handle) {
+ if (handle.getCore() != null) {
+ return handle.getCore().getDefaultAsyncWaiter();
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java
new file mode 100644
index 0000000..c6b14c1
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium 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 {
+
+ /**
+ * A generic callback.
+ */
+ interface Callback0 {
+ /**
+ * Call the callback.
+ */
+ public void call();
+ }
+
+ /**
+ * A generic 1-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ */
+ interface Callback1<T1> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1);
+ }
+
+ /**
+ * A generic 2-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ */
+ interface Callback2<T1, T2> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2);
+ }
+
+ /**
+ * A generic 3-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ */
+ interface Callback3<T1, T2, T3> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3);
+ }
+
+ /**
+ * A generic 4-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ */
+ interface Callback4<T1, T2, T3, T4> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
+ }
+
+ /**
+ * A generic 5-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ * @param <T5> the type of argument 5.
+ */
+ interface Callback5<T1, T2, T3, T4, T5> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
+ }
+
+ /**
+ * A generic 6-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ * @param <T5> the type of argument 5.
+ * @param <T6> the type of argument 6.
+ */
+ interface Callback6<T1, T2, T3, T4, T5, T6> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
+ }
+
+ /**
+ * A generic 7-argument callback.
+ *
+ * @param <T1> the type of argument 1.
+ * @param <T2> the type of argument 2.
+ * @param <T3> the type of argument 3.
+ * @param <T4> the type of argument 4.
+ * @param <T5> the type of argument 5.
+ * @param <T6> the type of argument 6.
+ * @param <T7> the type of argument 7.
+ */
+ interface Callback7<T1, T2, T3, T4, T5, T6, T7> {
+ /**
+ * Call the callback.
+ */
+ public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java
new file mode 100644
index 0000000..abb0bdf
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MojoException;
+
+/**
+ * A {@link ConnectionErrorHandler} is notified of an error happening while using the bindings over
+ * message pipes.
+ */
+interface ConnectionErrorHandler {
+ public void onConnectionError(MojoException e);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java
new file mode 100644
index 0000000..cbb1924
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java
@@ -0,0 +1,241 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.AsyncWaiter;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link Connector} owns a {@link MessagePipeHandle} and will send any received messages to the
+ * registered {@link MessageReceiver}. It also acts as a {@link MessageReceiver} and will send any
+ * message through the handle.
+ * <p>
+ * The method |start| must be called before the {@link Connector} will start listening to incoming
+ * messages.
+ */
+public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle> {
+
+ /**
+ * The callback that is notified when the state of the owned handle changes.
+ */
+ private final AsyncWaiterCallback mAsyncWaiterCallback = new AsyncWaiterCallback();
+
+ /**
+ * The owned message pipe.
+ */
+ private final MessagePipeHandle mMessagePipeHandle;
+
+ /**
+ * A waiter which is notified when a new message is available on the owned message pipe.
+ */
+ private final AsyncWaiter mAsyncWaiter;
+
+ /**
+ * The {@link MessageReceiver} to which received messages are sent.
+ */
+ private MessageReceiver mIncomingMessageReceiver;
+
+ /**
+ * The Cancellable for the current wait. Is |null| when not currently waiting for new messages.
+ */
+ private AsyncWaiter.Cancellable mCancellable;
+
+ /**
+ * The error handler to notify of errors.
+ */
+ private ConnectionErrorHandler mErrorHandler;
+
+ /**
+ * Create a new connector over a |messagePipeHandle|. The created connector will use the default
+ * {@link AsyncWaiter} from the {@link Core} implementation of |messagePipeHandle|.
+ */
+ public Connector(MessagePipeHandle messagePipeHandle) {
+ this(messagePipeHandle, BindingsHelper.getDefaultAsyncWaiterForHandle(messagePipeHandle));
+ }
+
+ /**
+ * Create a new connector over a |messagePipeHandle| using the given {@link AsyncWaiter} to get
+ * notified of changes on the handle.
+ */
+ public Connector(MessagePipeHandle messagePipeHandle, AsyncWaiter asyncWaiter) {
+ mCancellable = null;
+ mMessagePipeHandle = messagePipeHandle;
+ mAsyncWaiter = asyncWaiter;
+ }
+
+ /**
+ * Set the {@link MessageReceiver} that will receive message from the owned message pipe.
+ */
+ public void setIncomingMessageReceiver(MessageReceiver incomingMessageReceiver) {
+ mIncomingMessageReceiver = incomingMessageReceiver;
+ }
+
+ /**
+ * Set the {@link ConnectionErrorHandler} that will be notified of errors on the owned message
+ * pipe.
+ */
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ mErrorHandler = errorHandler;
+ }
+
+ /**
+ * Start listening for incoming messages.
+ */
+ public void start() {
+ assert mCancellable == null;
+ registerAsyncWaiterForRead();
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ try {
+ mMessagePipeHandle.writeMessage(message.getData(),
+ message.getHandles(), MessagePipeHandle.WriteFlags.NONE);
+ return true;
+ } catch (MojoException e) {
+ onError(e);
+ return false;
+ }
+ }
+
+ /**
+ * Pass the owned handle of the connector. After this, the connector is disconnected. It cannot
+ * accept new message and it isn't listening to the handle anymore.
+ *
+ * @see org.chromium.mojo.bindings.HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ cancelIfActive();
+ MessagePipeHandle handle = mMessagePipeHandle.pass();
+ if (mIncomingMessageReceiver != null) {
+ mIncomingMessageReceiver.close();
+ }
+ return handle;
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ cancelIfActive();
+ mMessagePipeHandle.close();
+ if (mIncomingMessageReceiver != null) {
+ mIncomingMessageReceiver.close();
+ }
+ }
+
+ private class AsyncWaiterCallback implements AsyncWaiter.Callback {
+
+ /**
+ * @see org.chromium.mojo.system.AsyncWaiter.Callback#onResult(int)
+ */
+ @Override
+ public void onResult(int result) {
+ Connector.this.onAsyncWaiterResult(result);
+ }
+
+ /**
+ * @see org.chromium.mojo.system.AsyncWaiter.Callback#onError(MojoException)
+ */
+ @Override
+ public void onError(MojoException exception) {
+ Connector.this.onError(exception);
+ }
+
+ }
+
+ /**
+ * @see org.chromium.mojo.system.AsyncWaiter.Callback#onResult(int)
+ */
+ private void onAsyncWaiterResult(int result) {
+ mCancellable = null;
+ if (result == MojoResult.OK) {
+ readOutstandingMessages();
+ } else {
+ onError(new MojoException(result));
+ }
+ }
+
+ private void onError(MojoException exception) {
+ mCancellable = null;
+ close();
+ if (mErrorHandler != null) {
+ mErrorHandler.onConnectionError(exception);
+ }
+ }
+
+ /**
+ * Register to be called back when a new message is available on the owned message pipe.
+ */
+ private void registerAsyncWaiterForRead() {
+ assert mCancellable == null;
+ if (mAsyncWaiter != null) {
+ mCancellable = mAsyncWaiter.asyncWait(mMessagePipeHandle, Core.HandleSignals.READABLE,
+ Core.DEADLINE_INFINITE, mAsyncWaiterCallback);
+ } else {
+ onError(new MojoException(MojoResult.INVALID_ARGUMENT));
+ }
+ }
+
+ /**
+ * Read all available messages on the owned message pipe.
+ */
+ private void readOutstandingMessages() {
+ int result;
+ do {
+ try {
+ result = readAndDispatchMessage(mMessagePipeHandle, mIncomingMessageReceiver);
+ } catch (MojoException e) {
+ onError(e);
+ return;
+ }
+ } while (result == MojoResult.OK);
+ if (result == MojoResult.SHOULD_WAIT) {
+ registerAsyncWaiterForRead();
+ } else {
+ onError(new MojoException(result));
+ }
+ }
+
+ private void cancelIfActive() {
+ if (mCancellable != null) {
+ mCancellable.cancel();
+ mCancellable = null;
+ }
+ }
+
+ /**
+ * Read a message, and pass it to the given |MessageReceiver| if not null. If the
+ * |MessageReceiver| is null, the message is lost.
+ *
+ * @param receiver The {@link MessageReceiver} that will receive the read {@link Message}. Can
+ * be <code>null</code>, in which case the message is discarded.
+ */
+ static int readAndDispatchMessage(MessagePipeHandle handle, MessageReceiver receiver) {
+ // TODO(qsr) Allow usage of a pool of pre-allocated buffer for performance.
+ ReadMessageResult result = handle.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE);
+ if (result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) {
+ return result.getMojoResult();
+ }
+ ByteBuffer buffer = ByteBuffer.allocateDirect(result.getMessageSize());
+ result = handle.readMessage(buffer, result.getHandlesCount(),
+ MessagePipeHandle.ReadFlags.NONE);
+ if (receiver != null && result.getMojoResult() == MojoResult.OK) {
+ receiver.accept(new Message(buffer, result.getHandles()));
+ }
+ return result.getMojoResult();
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
new file mode 100644
index 0000000..efb9842
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java
@@ -0,0 +1,630 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Interface.Proxy;
+import org.chromium.mojo.bindings.Struct.DataHeader;
+import org.chromium.mojo.system.DataPipe;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.InvalidHandle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.SharedBufferHandle;
+import org.chromium.mojo.system.UntypedHandle;
+
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+/**
+ * A Decoder is a helper class for deserializing a mojo struct. It enables deserialization of basic
+ * types from a {@link Message} object at a given offset into it's byte buffer.
+ */
+public class Decoder {
+
+ /**
+ * Helper class to validate the decoded message.
+ */
+ static final class Validator {
+
+ /**
+ * Minimal value for the next handle to deserialize.
+ */
+ private int mMinNextClaimedHandle = 0;
+ /**
+ * Minimal value of the start of the next memory to claim.
+ */
+ private long mMinNextMemory = 0;
+
+ /**
+ * The maximal memory accessible.
+ */
+ private final long mMaxMemory;
+
+ /**
+ * The number of handles in the message.
+ */
+ private final long mNumberOfHandles;
+
+ /**
+ * Constructor.
+ */
+ Validator(long maxMemory, int numberOfHandles) {
+ mMaxMemory = maxMemory;
+ mNumberOfHandles = numberOfHandles;
+ }
+
+ public void claimHandle(int handle) {
+ if (handle < mMinNextClaimedHandle) {
+ throw new DeserializationException(
+ "Trying to access handle out of order.");
+ }
+ if (handle >= mNumberOfHandles) {
+ throw new DeserializationException("Trying to access non present handle.");
+ }
+ mMinNextClaimedHandle = handle + 1;
+ }
+
+ public void claimMemory(long start, long end) {
+ if (start % BindingsHelper.ALIGNMENT != 0) {
+ throw new DeserializationException("Incorrect starting alignment: " + start + ".");
+ }
+ if (start < mMinNextMemory) {
+ throw new DeserializationException("Trying to access memory out of order.");
+ }
+ if (end < start) {
+ throw new DeserializationException("Incorrect memory range.");
+ }
+ if (end > mMaxMemory) {
+ throw new DeserializationException("Trying to access out of range memory.");
+ }
+ mMinNextMemory = BindingsHelper.align(end);
+ }
+ }
+
+ /**
+ * The message to deserialize from.
+ */
+ private final Message mMessage;
+
+ /**
+ * The base offset in the byte buffer.
+ */
+ private final int mBaseOffset;
+
+ /**
+ * Validator for the decoded message.
+ */
+ private final Validator mValidator;
+
+ /**
+ * Constructor.
+ *
+ * @param message The message to decode.
+ */
+ public Decoder(Message message) {
+ this(message, new Validator(message.getData().limit(), message.getHandles().size()), 0);
+ }
+
+ private Decoder(Message message, Validator validator, int baseOffset) {
+ mMessage = message;
+ mMessage.getData().order(ByteOrder.nativeOrder());
+ mBaseOffset = baseOffset;
+ mValidator = validator;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset.
+ */
+ public DataHeader readDataHeader() {
+ // Claim the memory for the header.
+ mValidator.claimMemory(mBaseOffset, mBaseOffset + DataHeader.HEADER_SIZE);
+ int size = readInt(DataHeader.SIZE_OFFSET);
+ int numFields = readInt(DataHeader.NUM_FIELDS_OFFSET);
+ if (size < 0) {
+ throw new DeserializationException(
+ "Negative size. Unsigned integers are not valid for java.");
+ }
+ if (numFields < 0) {
+ throw new DeserializationException(
+ "Negative number of fields. Unsigned integers are not valid for java.");
+ }
+
+ // Claim the remaining memory.
+ mValidator.claimMemory(mBaseOffset + DataHeader.HEADER_SIZE, mBaseOffset + size);
+ DataHeader res = new DataHeader(size, numFields);
+ return res;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an
+ * array where element have the given size.
+ */
+ public DataHeader readDataHeaderForPointerArray(int expectedLength) {
+ return readDataHeaderForArray(8, expectedLength);
+ }
+
+ /**
+ * Deserializes a byte at the given offset.
+ */
+ public byte readByte(int offset) {
+ validateBufferSize(offset, 1);
+ return mMessage.getData().get(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a boolean at the given offset, re-using any partially read byte.
+ */
+ public boolean readBoolean(int offset, int bit) {
+ validateBufferSize(offset, 1);
+ return (readByte(offset) & (1 << bit)) != 0;
+ }
+
+ /**
+ * Deserializes a short at the given offset.
+ */
+ public short readShort(int offset) {
+ validateBufferSize(offset, 2);
+ return mMessage.getData().getShort(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes an int at the given offset.
+ */
+ public int readInt(int offset) {
+ validateBufferSize(offset, 4);
+ return mMessage.getData().getInt(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a float at the given offset.
+ */
+ public float readFloat(int offset) {
+ validateBufferSize(offset, 4);
+ return mMessage.getData().getFloat(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a long at the given offset.
+ */
+ public long readLong(int offset) {
+ validateBufferSize(offset, 8);
+ return mMessage.getData().getLong(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a double at the given offset.
+ */
+ public double readDouble(int offset) {
+ validateBufferSize(offset, 8);
+ return mMessage.getData().getDouble(mBaseOffset + offset);
+ }
+
+ /**
+ * Deserializes a pointer at the given offset. Returns a Decoder suitable to decode the content
+ * of the pointer.
+ */
+ public Decoder readPointer(int offset, boolean nullable) {
+ int basePosition = mBaseOffset + offset;
+ long pointerOffset = readLong(offset);
+ if (pointerOffset == 0) {
+ if (!nullable) {
+ throw new DeserializationException(
+ "Trying to decode null pointer for a non-nullable type.");
+ }
+ return null;
+ }
+ int newPosition = (int) (basePosition + pointerOffset);
+ // The method |getDecoderAtPosition| will validate that the pointer address is valid.
+ return getDecoderAtPosition(newPosition);
+
+ }
+
+ /**
+ * Deserializes an array of boolean at the given offset.
+ */
+ public boolean[] readBooleans(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForBooleanArray(expectedLength);
+ byte[] bytes = new byte[(si.numFields + 7) / BindingsHelper.ALIGNMENT];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().get(bytes);
+ boolean[] result = new boolean[si.numFields];
+ for (int i = 0; i < bytes.length; ++i) {
+ for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) {
+ int booleanIndex = i * BindingsHelper.ALIGNMENT + j;
+ if (booleanIndex < result.length) {
+ result[booleanIndex] = (bytes[i] & (1 << j)) != 0;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of bytes at the given offset.
+ */
+ public byte[] readBytes(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(1, expectedLength);
+ byte[] result = new byte[si.numFields];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of shorts at the given offset.
+ */
+ public short[] readShorts(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(2, expectedLength);
+ short[] result = new short[si.numFields];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asShortBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of ints at the given offset.
+ */
+ public int[] readInts(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ int[] result = new int[si.numFields];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asIntBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of floats at the given offset.
+ */
+ public float[] readFloats(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ float[] result = new float[si.numFields];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asFloatBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of longs at the given offset.
+ */
+ public long[] readLongs(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(8, expectedLength);
+ long[] result = new long[si.numFields];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asLongBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an array of doubles at the given offset.
+ */
+ public double[] readDoubles(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(8, expectedLength);
+ double[] result = new double[si.numFields];
+ d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE);
+ d.mMessage.getData().asDoubleBuffer().get(result);
+ return result;
+ }
+
+ /**
+ * Deserializes an |Handle| at the given offset.
+ */
+ public Handle readHandle(int offset, boolean nullable) {
+ int index = readInt(offset);
+ if (index == -1) {
+ if (!nullable) {
+ throw new DeserializationException(
+ "Trying to decode an invalid handle for a non-nullable type.");
+ }
+ return InvalidHandle.INSTANCE;
+ }
+ mValidator.claimHandle(index);
+ return mMessage.getHandles().get(index);
+ }
+
+ /**
+ * Deserializes an |UntypedHandle| at the given offset.
+ */
+ public UntypedHandle readUntypedHandle(int offset, boolean nullable) {
+ return readHandle(offset, nullable).toUntypedHandle();
+ }
+
+ /**
+ * Deserializes a |ConsumerHandle| at the given offset.
+ */
+ public DataPipe.ConsumerHandle readConsumerHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toDataPipeConsumerHandle();
+ }
+
+ /**
+ * Deserializes a |ProducerHandle| at the given offset.
+ */
+ public DataPipe.ProducerHandle readProducerHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toDataPipeProducerHandle();
+ }
+
+ /**
+ * Deserializes a |MessagePipeHandle| at the given offset.
+ */
+ public MessagePipeHandle readMessagePipeHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toMessagePipeHandle();
+ }
+
+ /**
+ * Deserializes a |SharedBufferHandle| at the given offset.
+ */
+ public SharedBufferHandle readSharedBufferHandle(int offset, boolean nullable) {
+ return readUntypedHandle(offset, nullable).toSharedBufferHandle();
+ }
+
+ /**
+ * Deserializes an interface at the given offset.
+ *
+ * @return a proxy to the service.
+ */
+ public <P extends Proxy> P readServiceInterface(int offset, boolean nullable,
+ Interface.Manager<?, P> manager) {
+ MessagePipeHandle handle = readMessagePipeHandle(offset, nullable);
+ if (!handle.isValid()) {
+ return null;
+ }
+ return manager.attachProxy(handle);
+ }
+
+ /**
+ * Deserializes a |InterfaceRequest| at the given offset.
+ */
+ public <I extends Interface> InterfaceRequest<I> readInterfaceRequest(int offset,
+ boolean nullable) {
+ MessagePipeHandle handle = readMessagePipeHandle(offset, nullable);
+ if (handle == null) {
+ return null;
+ }
+ return new InterfaceRequest<I>(handle);
+ }
+
+ /**
+ * Deserializes a string at the given offset.
+ */
+ public String readString(int offset, boolean nullable) {
+ final int arrayNullability = nullable ? BindingsHelper.ARRAY_NULLABLE : 0;
+ byte[] bytes = readBytes(offset, arrayNullability, BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+ if (bytes == null) {
+ return null;
+ }
+ return new String(bytes, Charset.forName("utf8"));
+ }
+
+ /**
+ * Deserializes an array of |Handle| at the given offset.
+ */
+ public Handle[] readHandles(int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ Handle[] result = new Handle[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |UntypedHandle| at the given offset.
+ */
+ public UntypedHandle[] readUntypedHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ UntypedHandle[] result = new UntypedHandle[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readUntypedHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |ConsumerHandle| at the given offset.
+ */
+ public DataPipe.ConsumerHandle[] readConsumerHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ DataPipe.ConsumerHandle[] result = new DataPipe.ConsumerHandle[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readConsumerHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |ProducerHandle| at the given offset.
+ */
+ public DataPipe.ProducerHandle[] readProducerHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ DataPipe.ProducerHandle[] result = new DataPipe.ProducerHandle[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readProducerHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+
+ }
+
+ /**
+ * Deserializes an array of |MessagePipeHandle| at the given offset.
+ */
+ public MessagePipeHandle[] readMessagePipeHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ MessagePipeHandle[] result = new MessagePipeHandle[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readMessagePipeHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+
+ }
+
+ /**
+ * Deserializes an array of |SharedBufferHandle| at the given offset.
+ */
+ public SharedBufferHandle[] readSharedBufferHandles(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ SharedBufferHandle[] result = new SharedBufferHandle[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readSharedBufferHandle(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+
+ }
+
+ /**
+ * Deserializes an array of |ServiceHandle| at the given offset.
+ */
+ public <S extends Interface, P extends Proxy> S[] readServiceInterfaces(
+ int offset, int arrayNullability, int expectedLength, Interface.Manager<S, P> manager) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ S[] result = manager.buildArray(si.numFields);
+ for (int i = 0; i < result.length; ++i) {
+ // This cast is necessary because java 6 doesn't handle wildcard correctly when using
+ // Manager<S, ? extends S>
+ @SuppressWarnings("unchecked")
+ S value = (S) d.readServiceInterface(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability), manager);
+ result[i] = value;
+ }
+ return result;
+ }
+
+ /**
+ * Deserializes an array of |InterfaceRequest| at the given offset.
+ */
+ public <I extends Interface> InterfaceRequest<I>[] readInterfaceRequests(
+ int offset, int arrayNullability, int expectedLength) {
+ Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ if (d == null) {
+ return null;
+ }
+ DataHeader si = d.readDataHeaderForArray(4, expectedLength);
+ @SuppressWarnings("unchecked")
+ InterfaceRequest<I>[] result = new InterfaceRequest[si.numFields];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = d.readInterfaceRequest(
+ DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ return result;
+ }
+
+ /**
+ * Returns a view of this decoder at the offset |offset|.
+ */
+ private Decoder getDecoderAtPosition(int offset) {
+ return new Decoder(mMessage, mValidator, offset);
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an
+ * array of booleans.
+ */
+ private DataHeader readDataHeaderForBooleanArray(int expectedLength) {
+ DataHeader dataHeader = readDataHeader();
+ if (dataHeader.size < DataHeader.HEADER_SIZE + (dataHeader.numFields + 7) / 8) {
+ throw new DeserializationException("Array header is incorrect.");
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && dataHeader.numFields != expectedLength) {
+ throw new DeserializationException("Incorrect array length. Expected: " +
+ expectedLength + ", but got: " + dataHeader.numFields + ".");
+ }
+ return dataHeader;
+ }
+
+ /**
+ * Deserializes a {@link DataHeader} of an array at the given offset.
+ */
+ private DataHeader readDataHeaderForArray(long elementSize, int expectedLength) {
+ DataHeader dataHeader = readDataHeader();
+ if (dataHeader.size < (DataHeader.HEADER_SIZE + elementSize * dataHeader.numFields)) {
+ throw new DeserializationException("Array header is incorrect.");
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+ && dataHeader.numFields != expectedLength) {
+ throw new DeserializationException("Incorrect array length. Expected: " +
+ expectedLength + ", but got: " + dataHeader.numFields + ".");
+ }
+ return dataHeader;
+ }
+
+ private void validateBufferSize(int offset, int size) {
+ if (mMessage.getData().limit() < offset + size) {
+ throw new DeserializationException("Buffer is smaller than expected.");
+ }
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java
new file mode 100644
index 0000000..2ee2ae7
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MojoException;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A {@link ConnectionErrorHandler} that delegate the errors to a list of registered handlers. This
+ * class will use weak pointers to prevent keeping references to any handlers it delegates to.
+ */
+public class DelegatingConnectionErrorHandler implements ConnectionErrorHandler {
+
+ /**
+ * The registered handlers. This uses a {@link WeakHashMap} so that it doesn't prevent the
+ * handler from being garbage collected.
+ */
+ private final Set<ConnectionErrorHandler> mHandlers =
+ Collections.newSetFromMap(new WeakHashMap<ConnectionErrorHandler, Boolean>());
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ for (ConnectionErrorHandler handler : mHandlers) {
+ handler.onConnectionError(e);
+ }
+ }
+
+ /**
+ * Add a handler that will be notified of any error this object receives.
+ */
+ public void addConnectionErrorHandler(ConnectionErrorHandler handler) {
+ mHandlers.add(handler);
+ }
+
+ /**
+ * Remove a previously registered handler.
+ */
+ public void removeConnectionErrorHandler(ConnectionErrorHandler handler) {
+ mHandlers.remove(handler);
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java
new file mode 100644
index 0000000..eeb511c
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Error when deserializing a mojo message.
+ */
+public class DeserializationException extends RuntimeException {
+
+ /**
+ * Constructs a new deserialization exception with the specified detail message.
+ */
+ public DeserializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new deserialization exception with the specified cause.
+ */
+ public DeserializationException(Exception cause) {
+ super(cause);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
new file mode 100644
index 0000000..e3b2bfe
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Struct.DataHeader;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.Pair;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to encode a mojo struct. It keeps track of the output buffer, resizing it as needed.
+ * It also keeps track of the associated handles, and the offset of the current data section.
+ */
+public class Encoder {
+
+ /**
+ * Container class for all state that must be shared between the main encoder and any used sub
+ * encoder.
+ */
+ private static class EncoderState {
+
+ /**
+ * The core used to encode interfaces.
+ */
+ public final Core core;
+
+ /**
+ * The ByteBuffer to which the message will be encoded.
+ */
+ public ByteBuffer byteBuffer;
+
+ /**
+ * The list of encountered handles.
+ */
+ public final List<Handle> handles = new ArrayList<Handle>();
+
+ /**
+ * The current absolute position for the next data section.
+ */
+ public int dataEnd;
+
+ /**
+ * @param core the |Core| implementation used to generate handles. Only used if the |Struct|
+ * being encoded contains interfaces, can be |null| otherwise.
+ * @param bufferSize A hint on the size of the message. Used to build the initial byte
+ * buffer.
+ */
+ private EncoderState(Core core, int bufferSize) {
+ assert bufferSize % BindingsHelper.ALIGNMENT == 0;
+ this.core = core;
+ byteBuffer = ByteBuffer.allocateDirect(
+ bufferSize > 0 ? bufferSize : INITIAL_BUFFER_SIZE);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ dataEnd = 0;
+ }
+
+ /**
+ * Claim the given amount of memory at the end of the buffer, resizing it if needed.
+ */
+ public void claimMemory(int size) {
+ dataEnd += size;
+ growIfNeeded();
+ }
+
+ /**
+ * Grow the associated ByteBuffer if needed.
+ */
+ private void growIfNeeded() {
+ if (byteBuffer.capacity() >= dataEnd) {
+ return;
+ }
+ int targetSize = byteBuffer.capacity() * 2;
+ while (targetSize < dataEnd) {
+ targetSize *= 2;
+ }
+ ByteBuffer newBuffer = ByteBuffer.allocateDirect(targetSize);
+ newBuffer.order(ByteOrder.nativeOrder());
+ byteBuffer.position(0);
+ byteBuffer.limit(byteBuffer.capacity());
+ newBuffer.put(byteBuffer);
+ byteBuffer = newBuffer;
+ }
+ }
+
+ /**
+ * Default initial size of the data buffer. This must be a multiple of 8 bytes.
+ */
+ private static final int INITIAL_BUFFER_SIZE = 1024;
+
+ /**
+ * Base offset in the byte buffer for writing.
+ */
+ private int mBaseOffset;
+
+ /**
+ * The encoder state shared by the main encoder and all its sub-encoder.
+ */
+ private final EncoderState mEncoderState;
+
+ /**
+ * Returns the result message.
+ */
+ public Message getMessage() {
+ mEncoderState.byteBuffer.position(0);
+ mEncoderState.byteBuffer.limit(mEncoderState.dataEnd);
+ return new Message(mEncoderState.byteBuffer, mEncoderState.handles);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param core the |Core| implementation used to generate handles. Only used if the |Struct|
+ * being encoded contains interfaces, can be |null| otherwise.
+ * @param sizeHint A hint on the size of the message. Used to build the initial byte buffer.
+ */
+ public Encoder(Core core, int sizeHint) {
+ this(new EncoderState(core, sizeHint));
+ }
+
+ /**
+ * Private constructor for sub-encoders.
+ */
+ private Encoder(EncoderState bufferInformation) {
+ mEncoderState = bufferInformation;
+ mBaseOffset = bufferInformation.dataEnd;
+ }
+
+ /**
+ * Returns a new encoder that will append to the current buffer.
+ */
+ public Encoder getEncoderAtDataOffset(DataHeader dataHeader) {
+ Encoder result = new Encoder(mEncoderState);
+ result.encode(dataHeader);
+ return result;
+ }
+
+ /**
+ * Encode a {@link DataHeader} and claim the amount of memory required for the data section
+ * (resizing the buffer if required).
+ */
+ public void encode(DataHeader s) {
+ mEncoderState.claimMemory(BindingsHelper.align(s.size));
+ encode(s.size, DataHeader.SIZE_OFFSET);
+ encode(s.numFields, DataHeader.NUM_FIELDS_OFFSET);
+ }
+
+ /**
+ * Encode a byte at the given offset.
+ */
+ public void encode(byte v, int offset) {
+ mEncoderState.byteBuffer.put(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a boolean at the given offset.
+ */
+ public void encode(boolean v, int offset, int bit) {
+ if (v) {
+ byte encodedValue = mEncoderState.byteBuffer.get(mBaseOffset + offset);
+ encodedValue |= 1 << bit;
+ mEncoderState.byteBuffer.put(mBaseOffset + offset, encodedValue);
+ }
+ }
+
+ /**
+ * Encode a short at the given offset.
+ */
+ public void encode(short v, int offset) {
+ mEncoderState.byteBuffer.putShort(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode an int at the given offset.
+ */
+ public void encode(int v, int offset) {
+ mEncoderState.byteBuffer.putInt(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a float at the given offset.
+ */
+ public void encode(float v, int offset) {
+ mEncoderState.byteBuffer.putFloat(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a long at the given offset.
+ */
+ public void encode(long v, int offset) {
+ mEncoderState.byteBuffer.putLong(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a double at the given offset.
+ */
+ public void encode(double v, int offset) {
+ mEncoderState.byteBuffer.putDouble(mBaseOffset + offset, v);
+ }
+
+ /**
+ * Encode a {@link Struct} at the given offset.
+ */
+ public void encode(Struct v, int offset, boolean nullable) {
+ if (v == null) {
+ encodeNullPointer(offset, nullable);
+ return;
+ }
+ encodePointerToNextUnclaimedData(offset);
+ v.encode(this);
+ }
+
+ /**
+ * Encodes a String.
+ */
+ public void encode(String v, int offset, boolean nullable) {
+ if (v == null) {
+ encodeNullPointer(offset, nullable);
+ return;
+ }
+ final int arrayNullability = nullable ?
+ BindingsHelper.ARRAY_NULLABLE : BindingsHelper.NOTHING_NULLABLE;
+ encode(v.getBytes(Charset.forName("utf8")), offset, arrayNullability,
+ BindingsHelper.UNSPECIFIED_ARRAY_LENGTH);
+ }
+
+ /**
+ * Encodes a {@link Handle}.
+ */
+ public void encode(Handle v, int offset, boolean nullable) {
+ if (v == null || !v.isValid()) {
+ encodeInvalidHandle(offset, nullable);
+ } else {
+ encode(mEncoderState.handles.size(), offset);
+ mEncoderState.handles.add(v);
+ }
+ }
+
+ /**
+ * Encode an {@link Interface}.
+ */
+ public <T extends Interface> void encode(T v, int offset, boolean nullable,
+ Interface.Manager<T, ?> manager) {
+ if (v == null) {
+ encodeInvalidHandle(offset, nullable);
+ return;
+ }
+ if (mEncoderState.core == null) {
+ throw new UnsupportedOperationException(
+ "The encoder has been created without a Core. It can't encode an interface.");
+ }
+ // If the instance is a proxy, pass the proxy's handle instead of creating a new stub.
+ if (v instanceof Interface.AbstractProxy) {
+ Interface.AbstractProxy proxy = (Interface.AbstractProxy) v;
+ if (proxy.getMessageReceiver() instanceof HandleOwner) {
+ encode(((HandleOwner<?>) proxy.getMessageReceiver()).passHandle(), offset,
+ nullable);
+ return;
+ }
+ // If the proxy is not over a message pipe, the default case applies.
+ }
+ Pair<MessagePipeHandle, MessagePipeHandle> handles =
+ mEncoderState.core.createMessagePipe(null);
+ manager.bind(v, handles.first);
+ encode(handles.second, offset, nullable);
+ }
+
+ /**
+ * Encode an {@link InterfaceRequest}.
+ */
+ public <I extends Interface> void encode(InterfaceRequest<I> v, int offset, boolean nullable) {
+ if (v == null) {
+ encodeInvalidHandle(offset, nullable);
+ return;
+ }
+ if (mEncoderState.core == null) {
+ throw new UnsupportedOperationException(
+ "The encoder has been created without a Core. It can't encode an interface.");
+ }
+ encode(v.passHandle(), offset, nullable);
+ }
+
+ /**
+ * Returns an {@link Encoder} suitable for encoding an array of pointer of the given length.
+ */
+ public Encoder encodePointerArray(int length, int offset, int expectedLength) {
+ return encoderForArray(BindingsHelper.POINTER_SIZE, length, offset, expectedLength);
+ }
+
+ /**
+ * Encodes an array of booleans.
+ */
+ public void encode(boolean[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH &&
+ expectedLength != v.length) {
+ throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+ }
+ byte[] bytes = new byte[(v.length + 7) / BindingsHelper.ALIGNMENT];
+ for (int i = 0; i < bytes.length; ++i) {
+ for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) {
+ int booleanIndex = BindingsHelper.ALIGNMENT * i + j;
+ if (booleanIndex < v.length && v[booleanIndex]) {
+ bytes[i] |= (1 << j);
+ }
+ }
+ }
+ encodeByteArray(bytes, v.length, offset);
+ }
+
+ /**
+ * Encodes an array of bytes.
+ */
+ public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH &&
+ expectedLength != v.length) {
+ throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+ }
+ encodeByteArray(v, v.length, offset);
+ }
+
+ /**
+ * Encodes an array of shorts.
+ */
+ public void encode(short[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(2, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of ints.
+ */
+ public void encode(int[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(4, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of floats.
+ */
+ public void encode(float[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(4, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of longs.
+ */
+ public void encode(long[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(8, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of doubles.
+ */
+ public void encode(double[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ encoderForArray(8, v.length, offset, expectedLength).append(v);
+ }
+
+ /**
+ * Encodes an array of {@link Handle}.
+ */
+ public void encode(Handle[] v, int offset, int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ Encoder e = encoderForArray(
+ BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength);
+ for (int i = 0; i < v.length; ++i) {
+ e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ }
+
+ /**
+ * Encodes an array of {@link Interface}.
+ */
+ public <T extends Interface> void encode(T[] v, int offset, int arrayNullability,
+ int expectedLength, Interface.Manager<T, ?> manager) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ Encoder e = encoderForArray(
+ BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength);
+ for (int i = 0; i < v.length; ++i) {
+ e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability), manager);
+ }
+ }
+
+ /**
+ * Encodes an array of {@link InterfaceRequest}.
+ */
+ public <I extends Interface> void encode(InterfaceRequest<I>[] v, int offset,
+ int arrayNullability, int expectedLength) {
+ if (v == null) {
+ encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability));
+ return;
+ }
+ Encoder e = encoderForArray(
+ BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength);
+ for (int i = 0; i < v.length; ++i) {
+ e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
+ BindingsHelper.isElementNullable(arrayNullability));
+ }
+ }
+
+ /**
+ * Encodes a <code>null</code> pointer iff the object is nullable, raises an exception
+ * otherwise.
+ */
+ public void encodeNullPointer(int offset, boolean nullable) {
+ if (!nullable) {
+ throw new SerializationException(
+ "Trying to encode a null pointer for a non-nullable type.");
+ }
+ mEncoderState.byteBuffer.putLong(mBaseOffset + offset, 0);
+ }
+
+ /**
+ * Encodes an invalid handle iff the object is nullable, raises an exception otherwise.
+ */
+ public void encodeInvalidHandle(int offset, boolean nullable) {
+ if (!nullable) {
+ throw new SerializationException(
+ "Trying to encode an invalid handle for a non-nullable type.");
+ }
+ mEncoderState.byteBuffer.putInt(mBaseOffset + offset, -1);
+ }
+
+ private void encodePointerToNextUnclaimedData(int offset) {
+ encode((long) mEncoderState.dataEnd - (mBaseOffset + offset), offset);
+ }
+
+ private Encoder encoderForArray(
+ int elementSizeInByte, int length, int offset, int expectedLength) {
+ if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH &&
+ expectedLength != length) {
+ throw new SerializationException("Trying to encode a fixed array of incorrect length.");
+ }
+ return encoderForArrayByTotalSize(length * elementSizeInByte, length, offset);
+ }
+
+ private Encoder encoderForArrayByTotalSize(int byteSize, int length, int offset) {
+ encodePointerToNextUnclaimedData(offset);
+ return getEncoderAtDataOffset(
+ new DataHeader(DataHeader.HEADER_SIZE + byteSize, length));
+ }
+
+ private void encodeByteArray(byte[] bytes, int length, int offset) {
+ encoderForArrayByTotalSize(bytes.length, length, offset).append(bytes);
+ }
+
+ private void append(byte[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.put(v);
+ }
+
+ private void append(short[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asShortBuffer().put(v);
+ }
+
+ private void append(int[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asIntBuffer().put(v);
+ }
+
+ private void append(float[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asFloatBuffer().put(v);
+ }
+
+ private void append(double[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asDoubleBuffer().put(v);
+ }
+
+ private void append(long[] v) {
+ mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE);
+ mEncoderState.byteBuffer.asLongBuffer().put(v);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
new file mode 100644
index 0000000..d0d5c3b
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.AsyncWaiter;
+import org.chromium.mojo.system.AsyncWaiter.Callback;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.MojoResult;
+import org.chromium.mojo.system.Pair;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A factory which provides per-thread executors, which enable execution on the thread from which
+ * they were obtained.
+ */
+class ExecutorFactory {
+
+ /**
+ * A null buffer which is used to send messages without any data on the PipedExecutor's
+ * signaling handles.
+ */
+ private static final ByteBuffer NOTIFY_BUFFER = null;
+
+ /**
+ * Implementation of the executor which uses a pair of {@link MessagePipeHandle} for signaling.
+ * The executor will wait asynchronously on one end of a {@link MessagePipeHandle} on the thread
+ * on which it was created. Other threads can call execute with a {@link Runnable}, and the
+ * executor will queue the {@link Runnable} and write a message on the other end of the handle.
+ * This will wake up the executor which is waiting on the handle, which will then dequeue the
+ * {@link Runnable} and execute it on the original thread.
+ */
+ private static class PipedExecutor implements Executor, Callback {
+
+ /**
+ * The handle which is written to. Access to this object must be protected with |mLock|.
+ */
+ private final MessagePipeHandle mWriteHandle;
+ /**
+ * The handle which is read from.
+ */
+ private final MessagePipeHandle mReadHandle;
+ /**
+ * The list of actions left to be run. Access to this object must be protected with |mLock|.
+ */
+ private final List<Runnable> mPendingActions;
+ /**
+ * Lock protecting access to |mWriteHandle| and |mPendingActions|.
+ */
+ private final Object mLock;
+ /**
+ * The {@link AsyncWaiter} to get notified of new message availability on |mReadHandle|.
+ */
+ private final AsyncWaiter mWaiter;
+
+ /**
+ * Constructor.
+ */
+ public PipedExecutor(Core core) {
+ mWaiter = core.getDefaultAsyncWaiter();
+ assert mWaiter != null;
+ mLock = new Object();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(
+ new MessagePipeHandle.CreateOptions());
+ mReadHandle = handles.first;
+ mWriteHandle = handles.second;
+ mPendingActions = new ArrayList<Runnable>();
+ asyncWait();
+ }
+
+ /**
+ * Asynchronously wait for the next command to arrive. This should only be called on the
+ * executor thread.
+ */
+ private void asyncWait() {
+ mWaiter.asyncWait(mReadHandle, Core.HandleSignals.READABLE, Core.DEADLINE_INFINITE,
+ this);
+ }
+
+ /**
+ * @see Callback#onResult(int)
+ */
+ @Override
+ public void onResult(int result) {
+ if (result == MojoResult.OK && readNotifyBufferMessage()) {
+ runNextAction();
+ } else {
+ close();
+ }
+ }
+
+ /**
+ * @see Callback#onError(MojoException)
+ */
+ @Override
+ public void onError(MojoException exception) {
+ close();
+ }
+
+ /**
+ * Close the handles. Should only be called on the executor thread.
+ */
+ private void close() {
+ synchronized (mLock) {
+ mWriteHandle.close();
+ mPendingActions.clear();
+ }
+ mReadHandle.close();
+ }
+
+ /**
+ * Read the next message on |mReadHandle|, and return |true| if successful, |false|
+ * otherwise.
+ */
+ private boolean readNotifyBufferMessage() {
+ try {
+ ReadMessageResult readMessageResult = mReadHandle.readMessage(NOTIFY_BUFFER, 0,
+ MessagePipeHandle.ReadFlags.NONE);
+ if (readMessageResult.getMojoResult() == MojoResult.OK) {
+ asyncWait();
+ return true;
+ }
+ } catch (MojoException e) {
+ // Will be closed by the fall back at the end of this method.
+ }
+ return false;
+ }
+
+ /**
+ * Run the next action in the |mPendingActions| queue.
+ */
+ private void runNextAction() {
+ Runnable toRun = null;
+ synchronized (mWriteHandle) {
+ toRun = mPendingActions.remove(0);
+ }
+ toRun.run();
+ }
+
+ /**
+ * Execute the given |command| in the executor thread. This can be called on any thread.
+ *
+ * @see Executor#execute(Runnable)
+ */
+ @Override
+ public void execute(Runnable command) {
+ // Accessing the write handle must be protected by the lock, because it can be closed
+ // from the executor's thread.
+ synchronized (mLock) {
+ if (!mWriteHandle.isValid()) {
+ throw new IllegalStateException(
+ "Trying to execute an action on a closed executor.");
+ }
+ mPendingActions.add(command);
+ mWriteHandle.writeMessage(NOTIFY_BUFFER, null, MessagePipeHandle.WriteFlags.NONE);
+ }
+ }
+ }
+
+ /**
+ * Keep one executor per executor thread.
+ */
+ private static final ThreadLocal<Executor> EXECUTORS = new ThreadLocal<Executor>();
+
+ /**
+ * Returns an {@link Executor} that will run all of its actions in the current thread.
+ */
+ public static Executor getExecutorForCurrentThread(Core core) {
+ Executor executor = EXECUTORS.get();
+ if (executor == null) {
+ executor = new PipedExecutor(core);
+ EXECUTORS.set(executor);
+ }
+ return executor;
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java
new file mode 100644
index 0000000..60fc33b
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Handle;
+
+import java.io.Closeable;
+
+/**
+ * Describes a class that owns a handle.
+ *
+ * @param <H> The type of the owned handle.
+ */
+public interface HandleOwner<H extends Handle> extends Closeable {
+
+ /**
+ * Pass the handle owned by this class.
+ */
+ public H passHandle();
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close();
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java
new file mode 100644
index 0000000..c3b26a2
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java
@@ -0,0 +1,257 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojo.system.Pair;
+
+import java.io.Closeable;
+
+/**
+ * Base class for mojo generated interfaces.
+ */
+public interface Interface extends ConnectionErrorHandler, Closeable {
+
+ /**
+ * The close method is called when the connection to the interface is closed.
+ *
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close();
+
+ /**
+ * A proxy to a mojo interface. This is base class for all generated proxies. It implements the
+ * Interface and each time a method is called, the parameters are serialized and sent to the
+ * {@link MessageReceiverWithResponder}, along with the response callback if needed.
+ */
+ public interface Proxy extends Interface {
+
+ /**
+ * Set the {@link ConnectionErrorHandler} that will be notified of errors.
+ */
+ public void setErrorHandler(ConnectionErrorHandler errorHandler);
+
+ }
+
+ /**
+ * Base implementation of {@link Proxy}.
+ */
+ abstract class AbstractProxy implements Proxy {
+
+ /**
+ * The {@link Core} implementation to use.
+ */
+ private final Core mCore;
+
+ /**
+ * The {@link MessageReceiverWithResponder} that will receive a serialized message for each
+ * method call.
+ */
+ private final MessageReceiverWithResponder mMessageReceiver;
+
+ /**
+ * The {@link ConnectionErrorHandler} that will be notified of errors.
+ */
+ private ConnectionErrorHandler mErrorHandler = null;
+
+ /**
+ * Constructor.
+ *
+ * @param core the Core implementation used to create pipes and access the async waiter.
+ * @param messageReceiver the message receiver to send message to.
+ */
+ protected AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) {
+ this.mCore = core;
+ this.mMessageReceiver = messageReceiver;
+ }
+
+ /**
+ * Returns the message receiver to send message to.
+ */
+ protected MessageReceiverWithResponder getMessageReceiver() {
+ return mMessageReceiver;
+ }
+
+ /**
+ * Returns the Core implementation.
+ */
+ protected Core getCore() {
+ return mCore;
+ }
+
+ /**
+ * @see Proxy#setErrorHandler(ConnectionErrorHandler)
+ */
+ @Override
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ this.mErrorHandler = errorHandler;
+ }
+
+ /**
+ * @see ConnectionErrorHandler#onConnectionError(MojoException)
+ */
+ @Override
+ public void onConnectionError(MojoException e) {
+ if (mErrorHandler != null) {
+ mErrorHandler.onConnectionError(e);
+ }
+ }
+
+ /**
+ * @see Closeable#close()
+ */
+ @Override
+ public void close() {
+ mMessageReceiver.close();
+ }
+ }
+
+ /**
+ * Base implementation of Stub. Stubs are message receivers that deserialize the payload and
+ * call the appropriate method in the implementation. If the method returns result, the stub
+ * serializes the response and sends it back.
+ *
+ * @param <I> the type of the interface to delegate calls to.
+ */
+ abstract class Stub<I extends Interface> implements MessageReceiverWithResponder {
+
+ /**
+ * The {@link Core} implementation to use.
+ */
+ private final Core mCore;
+
+ /**
+ * The implementation to delegate calls to.
+ */
+ private final I mImpl;
+
+ /**
+ * Constructor.
+ *
+ * @param core the {@link Core} implementation to use.
+ * @param impl the implementation to delegate calls to.
+ */
+ public Stub(Core core, I impl) {
+ mCore = core;
+ mImpl = impl;
+ }
+
+ /**
+ * Returns the Core implementation.
+ */
+ protected Core getCore() {
+ return mCore;
+ }
+
+ /**
+ * Returns the implementation to delegate calls to.
+ */
+ protected I getImpl() {
+ return mImpl;
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.MessageReceiver#close()
+ */
+ @Override
+ public void close() {
+ mImpl.close();
+ }
+
+ }
+
+ /**
+ * The |Manager| object enables building of proxies and stubs for a given interface.
+ *
+ * @param <I> the type of the interface the manager can handle.
+ * @param <P> the type of the proxy the manager can handle. To be noted, P always extends I.
+ */
+ abstract class Manager<I extends Interface, P extends Proxy> {
+
+ /**
+ * Returns the name of the interface. This is an opaque (but human readable) identifier used
+ * by the service provider to identify services.
+ */
+ public abstract String getName();
+
+ /**
+ * Binds the given implementation to the handle.
+ */
+ public void bind(I impl, MessagePipeHandle handle) {
+ // The router (and by consequence the handle) is intentionally leaked. It will close
+ // itself when the connected handle is closed and the proxy receives the connection
+ // error.
+ Router router = new RouterImpl(handle);
+ bind(handle.getCore(), impl, router);
+ router.start();
+ }
+
+ /**
+ * Binds the given implementation to the InterfaceRequest.
+ */
+ public final void bind(I impl, InterfaceRequest<I> request) {
+ bind(impl, request.passHandle());
+ }
+
+ /**
+ * Returns a Proxy that will send messages to the given |handle|. This implies that the
+ * other end of the handle must be binded to an implementation of the interface.
+ */
+ public final P attachProxy(MessagePipeHandle handle) {
+ RouterImpl router = new RouterImpl(handle);
+ P proxy = attachProxy(handle.getCore(), router);
+ DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
+ handlers.addConnectionErrorHandler(proxy);
+ router.setErrorHandler(handlers);
+ router.start();
+ return proxy;
+ }
+
+ /**
+ * Constructs a new |InterfaceRequest| for the interface. This method returns a Pair where
+ * the first element is a proxy, and the second element is the request. The proxy can be
+ * used immediately.
+ */
+ public final Pair<P, InterfaceRequest<I>> getInterfaceRequest(Core core) {
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ P proxy = attachProxy(handles.first);
+ return Pair.create(proxy, new InterfaceRequest<I>(handles.second));
+ }
+
+ /**
+ * Binds the implementation to the given |router|.
+ */
+ final void bind(Core core, I impl, Router router) {
+ router.setErrorHandler(impl);
+ router.setIncomingMessageReceiver(buildStub(core, impl));
+ }
+
+ /**
+ * Returns a Proxy that will send messages to the given |router|.
+ */
+ final P attachProxy(Core core, Router router) {
+ return buildProxy(core, new AutoCloseableRouter(core, router));
+ }
+
+ /**
+ * Creates a new array of the given |size|.
+ */
+ protected abstract I[] buildArray(int size);
+
+ /**
+ * Constructs a Stub delegating to the given implementation.
+ */
+ protected abstract Stub<I> buildStub(Core core, I impl);
+
+ /**
+ * Constructs a Proxy forwarding the calls to the given message receiver.
+ */
+ protected abstract P buildProxy(Core core, MessageReceiverWithResponder messageReceiver);
+
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java
new file mode 100644
index 0000000..87835e0
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MessagePipeHandle;
+
+/**
+ * One end of the message pipe representing a request to create an implementation to be bound to it.
+ * The other end of the pipe is bound to a proxy, which can be used immediately, while the
+ * InterfaceRequest is being sent.
+ * <p>
+ * InterfaceRequest are built using |Interface.Manager|.
+ *
+ * @param <P> the type of the remote interface proxy.
+ */
+public class InterfaceRequest<P extends Interface> implements HandleOwner<MessagePipeHandle> {
+
+ /**
+ * The handle which will be sent and will be connected to the implementation.
+ */
+ private final MessagePipeHandle mHandle;
+
+ /**
+ * Constructor.
+ *
+ * @param handle the handle which will be sent and will be connected to the implementation.
+ */
+ InterfaceRequest(MessagePipeHandle handle) {
+ mHandle = handle;
+ }
+
+ /**
+ * @see HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ return mHandle.pass();
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ mHandle.close();
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceWithClient.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceWithClient.java
new file mode 100644
index 0000000..f7d8afe
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceWithClient.java
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojo.system.Pair;
+
+/**
+ * Base class for mojo generated interfaces that have a client.
+ *
+ * @param <CI> the type of the client interface.
+ */
+public interface InterfaceWithClient<CI extends Interface> extends Interface {
+
+ /**
+ * Proxy class for interfaces with a client.
+ */
+ interface Proxy<CI extends Interface> extends Interface.Proxy, InterfaceWithClient<CI> {
+ }
+
+ /**
+ * Base implementation of Proxy.
+ *
+ * @param <CI> the type of the client interface.
+ */
+ abstract class AbstractProxy<CI extends Interface> extends Interface.AbstractProxy
+ implements Proxy<CI> {
+
+ /**
+ * Constructor.
+ *
+ * @param core the Core implementation used to create pipes and access the async waiter.
+ * @param messageReceiver the message receiver to send message to.
+ */
+ public AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) {
+ super(core, messageReceiver);
+ }
+
+ /**
+ * @see InterfaceWithClient#setClient(Interface)
+ */
+ @Override
+ public void setClient(CI client) {
+ throw new UnsupportedOperationException(
+ "Setting the client on a proxy is not supported");
+ }
+ }
+
+ /**
+ * Base manager implementation for interfaces that have a client.
+ *
+ * @param <I> the type of the interface the manager can handle.
+ * @param <P> the type of the proxy the manager can handle. To be noted, P always extends I.
+ * @param <CI> the type of the client interface.
+ */
+ abstract class Manager<I extends InterfaceWithClient<CI>, P extends Proxy<CI>,
+ CI extends Interface> extends Interface.Manager<I, P> {
+
+ /**
+ * @see Interface.Manager#bind(Interface, MessagePipeHandle)
+ */
+ @Override
+ public final void bind(I impl, MessagePipeHandle handle) {
+ Router router = new RouterImpl(handle);
+ super.bind(handle.getCore(), impl, router);
+ @SuppressWarnings("unchecked")
+ CI client = (CI) getClientManager().attachProxy(handle.getCore(), router);
+ impl.setClient(client);
+ router.start();
+ }
+
+ /**
+ * Returns a Proxy that will send messages to the given |handle|. This implies that the
+ * other end of the handle must be connected to an implementation of the interface. |client|
+ * is the implementation of the client interface.
+ */
+ public P attachProxy(MessagePipeHandle handle, CI client) {
+ Router router = new RouterImpl(handle);
+ DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
+ handlers.addConnectionErrorHandler(client);
+ router.setErrorHandler(handlers);
+ getClientManager().bind(handle.getCore(), client, router);
+ P proxy = super.attachProxy(handle.getCore(), router);
+ handlers.addConnectionErrorHandler(proxy);
+ router.start();
+ return proxy;
+ }
+
+ /**
+ * Constructs a new |InterfaceRequest| for the interface. This method returns a Pair where
+ * the first element is a proxy, and the second element is the request. The proxy can be
+ * used immediately.
+ *
+ * @param client the implementation of the client interface.
+ */
+ public final Pair<P, InterfaceRequest<I>> getInterfaceRequest(Core core, CI client) {
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ P proxy = attachProxy(handles.first, client);
+ return Pair.create(proxy, new InterfaceRequest<I>(handles.second));
+ }
+
+ /**
+ * Returns a manager for the client inetrafce.
+ */
+ protected abstract Interface.Manager<CI, ?> getClientManager();
+ }
+
+ /**
+ * Set the client implementation for this interface.
+ */
+ public void setClient(CI client);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
new file mode 100644
index 0000000..0d270cc
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Handle;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A raw message to be sent/received from a {@link MessagePipeHandle}. Note that this can contain
+ * any data, not necessarily a Mojo message with a proper header. See also {@link ServiceMessage}.
+ */
+public class Message {
+
+ /**
+ * The data of the message.
+ */
+ private final ByteBuffer mBuffer;
+
+ /**
+ * The handles of the message.
+ */
+ private final List<? extends Handle> mHandle;
+
+ /**
+ * This message interpreted as a message for a mojo service with an appropriate header.
+ */
+ private ServiceMessage mWithHeader = null;
+
+ /**
+ * Constructor.
+ *
+ * @param buffer The buffer containing the bytes to send. This must be a direct buffer.
+ * @param handles The list of handles to send.
+ */
+ public Message(ByteBuffer buffer, List<? extends Handle> handles) {
+ assert buffer.isDirect();
+ mBuffer = buffer;
+ mHandle = handles;
+ }
+
+ /**
+ * The data of the message.
+ */
+ public ByteBuffer getData() {
+ return mBuffer;
+ }
+
+ /**
+ * The handles of the message.
+ */
+ public List<? extends Handle> getHandles() {
+ return mHandle;
+ }
+
+ /**
+ * Returns the message interpreted as a message for a mojo service.
+ */
+ public ServiceMessage asServiceMessage() {
+ if (mWithHeader == null) {
+ mWithHeader = new ServiceMessage(this);
+ }
+ return mWithHeader;
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java
new file mode 100644
index 0000000..ad4e108
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java
@@ -0,0 +1,254 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.bindings.Struct.DataHeader;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Header information for a message.
+ */
+public class MessageHeader {
+
+ private static final int SIMPLE_MESSAGE_SIZE = 16;
+ private static final int SIMPLE_MESSAGE_NUM_FIELDS = 2;
+ private static final DataHeader SIMPLE_MESSAGE_STRUCT_INFO =
+ new DataHeader(SIMPLE_MESSAGE_SIZE, SIMPLE_MESSAGE_NUM_FIELDS);
+
+ private static final int MESSAGE_WITH_REQUEST_ID_SIZE = 24;
+ private static final int MESSAGE_WITH_REQUEST_ID_NUM_FIELDS = 3;
+ private static final DataHeader MESSAGE_WITH_REQUEST_ID_STRUCT_INFO =
+ new DataHeader(MESSAGE_WITH_REQUEST_ID_SIZE, MESSAGE_WITH_REQUEST_ID_NUM_FIELDS);
+
+ private static final int TYPE_OFFSET = 8;
+ private static final int FLAGS_OFFSET = 12;
+ private static final int REQUEST_ID_OFFSET = 16;
+
+ /**
+ * Flag for a header of a simple message.
+ */
+ public static final int NO_FLAG = 0;
+
+ /**
+ * Flag for a header of a message that expected a response.
+ */
+ public static final int MESSAGE_EXPECTS_RESPONSE_FLAG = 1 << 0;
+
+ /**
+ * Flag for a header of a message that is a response.
+ */
+ public static final int MESSAGE_IS_RESPONSE_FLAG = 1 << 1;
+
+ private final DataHeader mDataHeader;
+ private final int mType;
+ private final int mFlags;
+ private long mRequestId;
+
+ /**
+ * Constructor for the header of a message which does not have a response.
+ */
+ public MessageHeader(int type) {
+ mDataHeader = SIMPLE_MESSAGE_STRUCT_INFO;
+ mType = type;
+ mFlags = 0;
+ mRequestId = 0;
+ }
+
+ /**
+ * Constructor for the header of a message which have a response or being itself a response.
+ */
+ public MessageHeader(int type, int flags, long requestId) {
+ assert mustHaveRequestId(flags);
+ mDataHeader = MESSAGE_WITH_REQUEST_ID_STRUCT_INFO;
+ mType = type;
+ mFlags = flags;
+ mRequestId = requestId;
+ }
+
+ /**
+ * Constructor, parsing the header from a message. Should only be used by {@link Message}
+ * itself.
+ */
+ MessageHeader(Message message) {
+ Decoder decoder = new Decoder(message);
+ mDataHeader = decoder.readDataHeader();
+ validateDataHeader(mDataHeader);
+ mType = decoder.readInt(TYPE_OFFSET);
+ mFlags = decoder.readInt(FLAGS_OFFSET);
+ if (mustHaveRequestId(mFlags)) {
+ if (mDataHeader.size < MESSAGE_WITH_REQUEST_ID_SIZE) {
+ throw new DeserializationException("Incorrect message size, expecting at least "
+ + MESSAGE_WITH_REQUEST_ID_SIZE
+ + " for a message with a request identifier, but got: " + mDataHeader.size);
+
+ }
+ mRequestId = decoder.readLong(REQUEST_ID_OFFSET);
+ } else {
+ mRequestId = 0;
+ }
+ }
+
+ /**
+ * Returns the size in bytes of the serialization of the header.
+ */
+ public int getSize() {
+ return mDataHeader.size;
+ }
+
+ /**
+ * Returns the type of the message.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the flags associated to the message.
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Returns if the message has the given flag.
+ */
+ public boolean hasFlag(int flag) {
+ return (mFlags & flag) == flag;
+ }
+
+ /**
+ * Returns if the message has a request id.
+ */
+ public boolean hasRequestId() {
+ return mustHaveRequestId(mFlags);
+ }
+
+ /**
+ * Return the request id for the message. Must only be called if the message has a request id.
+ */
+ public long getRequestId() {
+ assert hasRequestId();
+ return mRequestId;
+ }
+
+ /**
+ * Encode the header.
+ */
+ public void encode(Encoder encoder) {
+ encoder.encode(mDataHeader);
+ encoder.encode(getType(), TYPE_OFFSET);
+ encoder.encode(getFlags(), FLAGS_OFFSET);
+ if (hasRequestId()) {
+ encoder.encode(getRequestId(), REQUEST_ID_OFFSET);
+ }
+ }
+
+ /**
+ * Returns true if the header has the expected flags. Only considers flags this class knows
+ * about in order to allow this class to work with future version of the header format.
+ */
+ public boolean validateHeader(int expectedFlags) {
+ int knownFlags = getFlags() & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG);
+ return knownFlags == expectedFlags;
+ }
+
+ /**
+ * Returns true if the header has the expected type and flags. Only consider flags this class
+ * knows about in order to allow this class to work with future version of the header format.
+ */
+ public boolean validateHeader(int expectedType, int expectedFlags) {
+ return getType() == expectedType && validateHeader(expectedFlags);
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mDataHeader == null) ? 0 : mDataHeader.hashCode());
+ result = prime * result + mFlags;
+ result = prime * result + (int) (mRequestId ^ (mRequestId >>> 32));
+ result = prime * result + mType;
+ return result;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == this)
+ return true;
+ if (object == null)
+ return false;
+ if (getClass() != object.getClass())
+ return false;
+
+ MessageHeader other = (MessageHeader) object;
+ if (mDataHeader == null) {
+ if (other.mDataHeader != null) {
+ return false;
+ }
+ } else {
+ if (!mDataHeader.equals(other.mDataHeader)) {
+ return false;
+ }
+ }
+
+ return (mFlags == other.mFlags &&
+ mRequestId == other.mRequestId &&
+ mType == other.mType);
+ }
+
+ /**
+ * Set the request id on the message contained in the given buffer.
+ */
+ void setRequestId(ByteBuffer buffer, long requestId) {
+ assert mustHaveRequestId(buffer.getInt(FLAGS_OFFSET));
+ buffer.putLong(REQUEST_ID_OFFSET, requestId);
+ mRequestId = requestId;
+ }
+
+ /**
+ * Returns whether a message with the given flags must have a request Id.
+ */
+ private static boolean mustHaveRequestId(int flags) {
+ return (flags & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG)) != 0;
+ }
+
+ /**
+ * Validate that the given {@link DataHeader} can be the data header of a message header.
+ */
+ private static void validateDataHeader(DataHeader dataHeader) {
+ if (dataHeader.numFields < SIMPLE_MESSAGE_NUM_FIELDS) {
+ throw new DeserializationException(
+ "Incorrect number of fields, expecting at least " + SIMPLE_MESSAGE_NUM_FIELDS
+ + ", but got: " + dataHeader.numFields);
+ }
+ if (dataHeader.size < SIMPLE_MESSAGE_SIZE) {
+ throw new DeserializationException(
+ "Incorrect message size, expecting at least " + SIMPLE_MESSAGE_SIZE
+ + ", but got: " + dataHeader.size);
+ }
+ if (dataHeader.numFields == SIMPLE_MESSAGE_NUM_FIELDS
+ && dataHeader.size != SIMPLE_MESSAGE_SIZE) {
+ throw new DeserializationException(
+ "Incorrect message size for a message with " + SIMPLE_MESSAGE_NUM_FIELDS
+ + " fields, expecting " + SIMPLE_MESSAGE_SIZE + ", but got: "
+ + dataHeader.size);
+ }
+ if (dataHeader.numFields == MESSAGE_WITH_REQUEST_ID_NUM_FIELDS
+ && dataHeader.size != MESSAGE_WITH_REQUEST_ID_SIZE) {
+ throw new DeserializationException(
+ "Incorrect message size for a message with "
+ + MESSAGE_WITH_REQUEST_ID_NUM_FIELDS + " fields, expecting "
+ + MESSAGE_WITH_REQUEST_ID_SIZE + ", but got: " + dataHeader.size);
+ }
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java
new file mode 100644
index 0000000..4223297
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import java.io.Closeable;
+
+/**
+ * A class which implements this interface can receive {@link Message} objects.
+ */
+public interface MessageReceiver extends Closeable {
+
+ /**
+ * Receive a {@link Message}. The {@link MessageReceiver} is allowed to mutate the message.
+ * Returns |true| if the message has been handled, |false| otherwise.
+ */
+ boolean accept(Message message);
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close();
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java
new file mode 100644
index 0000000..e24a558
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * A {@link MessageReceiver} that can also handle the handle the response message generated from the
+ * given message.
+ */
+public interface MessageReceiverWithResponder extends MessageReceiver {
+
+ /**
+ * A variant on {@link #accept(Message)} that registers a {@link MessageReceiver}
+ * (known as the responder) to handle the response message generated from the given message. The
+ * responder's {@link #accept(Message)} method may be called as part of the call to
+ * {@link #acceptWithResponder(Message, MessageReceiver)}, or some time after its
+ * return.
+ */
+ boolean acceptWithResponder(Message message, MessageReceiver responder);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java
new file mode 100644
index 0000000..ba19ae5
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.MessagePipeHandle;
+
+/**
+ * A {@link Router} will handle mojo message and forward those to a {@link Connector}. It deals with
+ * parsing of headers and adding of request ids in order to be able to match a response to a
+ * request.
+ */
+public interface Router extends MessageReceiverWithResponder, HandleOwner<MessagePipeHandle> {
+
+ /**
+ * Start listening for incoming messages.
+ */
+ public void start();
+
+ /**
+ * Set the {@link MessageReceiverWithResponder} that will deserialize and use the message
+ * received from the pipe.
+ */
+ public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver);
+
+ /**
+ * Set the handle that will be notified of errors on the message pipe.
+ */
+ public void setErrorHandler(ConnectionErrorHandler errorHandler);
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java
new file mode 100644
index 0000000..5b27d7e
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java
@@ -0,0 +1,195 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.AsyncWaiter;
+import org.chromium.mojo.system.MessagePipeHandle;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of {@link Router}.
+ */
+public class RouterImpl implements Router {
+
+ /**
+ * {@link MessageReceiver} used as the {@link Connector} callback.
+ */
+ private class ResponderThunk implements MessageReceiver {
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ return handleIncomingMessage(message);
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.MessageReceiver#close()
+ */
+ @Override
+ public void close() {
+ handleConnectorClose();
+ }
+
+ }
+
+ /**
+ * The {@link Connector} which is connected to the handle.
+ */
+ private final Connector mConnector;
+
+ /**
+ * The {@link MessageReceiverWithResponder} that will consume the messages received from the
+ * pipe.
+ */
+ private MessageReceiverWithResponder mIncomingMessageReceiver;
+
+ /**
+ * The next id to use for a request id which needs a response. It is auto-incremented.
+ */
+ private long mNextRequestId = 1;
+
+ /**
+ * The map from request ids to {@link MessageReceiver} of request currently in flight.
+ */
+ private Map<Long, MessageReceiver> mResponders = new HashMap<Long, MessageReceiver>();
+
+ /**
+ * Constructor that will use the default {@link AsyncWaiter}.
+ *
+ * @param messagePipeHandle The {@link MessagePipeHandle} to route message for.
+ */
+ public RouterImpl(MessagePipeHandle messagePipeHandle) {
+ this(messagePipeHandle, BindingsHelper.getDefaultAsyncWaiterForHandle(messagePipeHandle));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param messagePipeHandle The {@link MessagePipeHandle} to route message for.
+ * @param asyncWaiter the {@link AsyncWaiter} to use to get notification of new messages on the
+ * handle.
+ */
+ public RouterImpl(MessagePipeHandle messagePipeHandle, AsyncWaiter asyncWaiter) {
+ mConnector = new Connector(messagePipeHandle, asyncWaiter);
+ mConnector.setIncomingMessageReceiver(new ResponderThunk());
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.Router#start()
+ */
+ @Override
+ public void start() {
+ mConnector.start();
+ }
+
+ /**
+ * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder)
+ */
+ @Override
+ public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) {
+ this.mIncomingMessageReceiver = incomingMessageReceiver;
+ }
+
+ /**
+ * @see MessageReceiver#accept(Message)
+ */
+ @Override
+ public boolean accept(Message message) {
+ // A message without responder is directly forwarded to the connector.
+ return mConnector.accept(message);
+ }
+
+ /**
+ * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver)
+ */
+ @Override
+ public boolean acceptWithResponder(Message message, MessageReceiver responder) {
+ // The message must have a header.
+ ServiceMessage messageWithHeader = message.asServiceMessage();
+ // Checking the message expects a response.
+ assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG);
+
+ // Compute a request id for being able to route the response.
+ long requestId = mNextRequestId++;
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ if (requestId == 0) {
+ requestId = mNextRequestId++;
+ }
+ if (mResponders.containsKey(requestId)) {
+ throw new IllegalStateException("Unable to find a new request identifier.");
+ }
+ messageWithHeader.setRequestId(requestId);
+ if (!mConnector.accept(messageWithHeader)) {
+ return false;
+ }
+ // Only keep the responder is the message has been accepted.
+ mResponders.put(requestId, responder);
+ return true;
+ }
+
+ /**
+ * @see org.chromium.mojo.bindings.HandleOwner#passHandle()
+ */
+ @Override
+ public MessagePipeHandle passHandle() {
+ return mConnector.passHandle();
+ }
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ mConnector.close();
+ }
+
+ /**
+ * @see Router#setErrorHandler(ConnectionErrorHandler)
+ */
+ @Override
+ public void setErrorHandler(ConnectionErrorHandler errorHandler) {
+ mConnector.setErrorHandler(errorHandler);
+ }
+
+ /**
+ * Receive a message from the connector. Returns |true| if the message has been handled.
+ */
+ private boolean handleIncomingMessage(Message message) {
+ MessageHeader header = message.asServiceMessage().getHeader();
+ if (header.hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) {
+ if (mIncomingMessageReceiver != null) {
+ return mIncomingMessageReceiver.acceptWithResponder(message, this);
+ }
+ // 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.
+ close();
+ return false;
+ } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
+ long requestId = header.getRequestId();
+ MessageReceiver responder = mResponders.get(requestId);
+ if (responder == null) {
+ return false;
+ }
+ mResponders.remove(requestId);
+ return responder.accept(message);
+ } else {
+ if (mIncomingMessageReceiver != null) {
+ return mIncomingMessageReceiver.accept(message);
+ }
+ // OK to drop the message.
+ }
+ return false;
+ }
+
+ private void handleConnectorClose() {
+ if (mIncomingMessageReceiver != null) {
+ mIncomingMessageReceiver.close();
+ }
+ }
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java
new file mode 100644
index 0000000..d4f5502
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+/**
+ * Error that can be thrown when serializing a mojo message.
+ */
+public class SerializationException extends RuntimeException {
+
+ /**
+ * Constructs a new serialization exception with the specified detail message.
+ */
+ public SerializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new serialization exception with the specified cause.
+ */
+ public SerializationException(Exception cause) {
+ super(cause);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java
new file mode 100644
index 0000000..ac62bf6
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Represents a {@link Message} which contains a {@link MessageHeader}. Deals with parsing the
+ * {@link MessageHeader} for a message.
+ */
+public class ServiceMessage extends Message {
+
+ private final MessageHeader mHeader;
+ private Message mPayload;
+
+ /**
+ * Reinterpret the given |message| as a message with the given |header|. The |message| must
+ * contain the |header| as the start of its raw data.
+ */
+ public ServiceMessage(Message baseMessage, MessageHeader header) {
+ super(baseMessage.getData(), baseMessage.getHandles());
+ assert header.equals(new org.chromium.mojo.bindings.MessageHeader(baseMessage));
+ this.mHeader = header;
+ }
+
+ /**
+ * Reinterpret the given |message| as a message with a header. The |message| must contain a
+ * header as the start of it's raw data, which will be parsed by this constructor.
+ */
+ ServiceMessage(Message baseMessage) {
+ this(baseMessage, new org.chromium.mojo.bindings.MessageHeader(baseMessage));
+ }
+
+ /**
+ * @see Message#asServiceMessage()
+ */
+ @Override
+ public ServiceMessage asServiceMessage() {
+ return this;
+ }
+
+ /**
+ * Returns the header of the given message. This will throw a {@link DeserializationException}
+ * if the start of the message is not a valid header.
+ */
+ public MessageHeader getHeader() {
+ return mHeader;
+ }
+
+ /**
+ * Returns the payload of the message.
+ */
+ public Message getPayload() {
+ if (mPayload == null) {
+ ByteBuffer truncatedBuffer =
+ ((ByteBuffer) getData().position(getHeader().getSize())).slice();
+ truncatedBuffer.order(ByteOrder.nativeOrder());
+ mPayload = new Message(truncatedBuffer, getHandles());
+ }
+ return mPayload;
+ }
+
+ /**
+ * Set the request identifier on the message.
+ */
+ void setRequestId(long requestId) {
+ mHeader.setRequestId(getData(), requestId);
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java
new file mode 100644
index 0000000..118c991
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.bindings;
+
+import java.io.Closeable;
+
+/**
+ * An implementation of closeable that doesn't do anything.
+ */
+public class SideEffectFreeCloseable implements Closeable {
+
+ /**
+ * @see java.io.Closeable#close()
+ */
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java
new file mode 100644
index 0000000..017d0ef
--- /dev/null
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.bindings;
+
+import org.chromium.mojo.system.Core;
+
+/**
+ * Base class for all mojo structs.
+ */
+public abstract class Struct {
+
+ /**
+ * The header for a mojo complex element.
+ */
+ public static final class DataHeader {
+
+ /**
+ * The size of a serialized header, in bytes.
+ */
+ public static final int HEADER_SIZE = 8;
+
+ /**
+ * The offset of the size field.
+ */
+ public static final int SIZE_OFFSET = 0;
+
+ /**
+ * The offset of the number of fields field.
+ */
+ public static final int NUM_FIELDS_OFFSET = 4;
+
+ public final int size;
+ public final int numFields;
+
+ /**
+ * Constructor.
+ */
+ public DataHeader(int size, int numFields) {
+ super();
+ this.size = size;
+ this.numFields = numFields;
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + numFields;
+ result = prime * result + size;
+ return result;
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == this)
+ return true;
+ if (object == null)
+ return false;
+ if (getClass() != object.getClass())
+ return false;
+
+ DataHeader other = (DataHeader) object;
+ return (numFields == other.numFields &&
+ size == other.size);
+ }
+ }
+
+ /**
+ * The base size of the struct.
+ */
+ protected final int mEncodedBaseSize;
+
+ /**
+ * Constructor.
+ */
+ protected Struct(int encodedBaseSize) {
+ this.mEncodedBaseSize = encodedBaseSize;
+ }
+
+ /**
+ * Use the given encoder to serialized this struct.
+ */
+ protected abstract void encode(Encoder encoder);
+
+ /**
+ * Returns the serialization of the struct. This method can close Handles.
+ *
+ * @param core the |Core| implementation used to generate handles. Only used if the |Struct|
+ * being encoded contains interfaces, can be |null| otherwise.
+ */
+ public Message serialize(Core core) {
+ Encoder encoder = new Encoder(core, mEncodedBaseSize);
+ encode(encoder);
+ return encoder.getMessage();
+ }
+
+ /**
+ * Returns the serialization of the struct prepended with the given header.
+ *
+ * @param header the header to prepend to the returned message.
+ * @param core the |Core| implementation used to generate handles. Only used if the |Struct|
+ * being encoded contains interfaces, can be |null| otherwise.
+ */
+ public ServiceMessage serializeWithHeader(Core core, MessageHeader header) {
+ Encoder encoder = new Encoder(core, mEncodedBaseSize + header.getSize());
+ header.encode(encoder);
+ encode(encoder);
+ return new ServiceMessage(encoder.getMessage(), header);
+ }
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/AsyncWaiter.java b/mojo/public/java/system/src/org/chromium/mojo/system/AsyncWaiter.java
new file mode 100644
index 0000000..474de4b
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/AsyncWaiter.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import org.chromium.mojo.system.Core.HandleSignals;
+
+/**
+ * A class which implements the {@link AsyncWaiter} allows asynchronously waiting on a background
+ * thread.
+ */
+public interface AsyncWaiter {
+
+ /**
+ * Allows cancellation of an asyncWait operation.
+ */
+ interface Cancellable {
+ /**
+ * Cancels an asyncWait operation. Has no effect if the operation has already been canceled
+ * or the callback has already been called.
+ * <p>
+ * Must be called from the same thread as {@link AsyncWaiter#asyncWait} was called from.
+ */
+ void cancel();
+ }
+
+ /**
+ * Callback passed to {@link AsyncWaiter#asyncWait}.
+ */
+ public interface Callback {
+ /**
+ * Called when the handle is ready.
+ */
+ public void onResult(int result);
+
+ /**
+ * Called when an error occurred while waiting.
+ */
+ public void onError(MojoException exception);
+ }
+
+ /**
+ * Asynchronously call wait on a background thread. The given {@link Callback} will be notified
+ * of the result of the wait on the same thread as asyncWait was called.
+ *
+ * @return a {@link Cancellable} object that can be used to cancel waiting. The cancellable
+ * should only be used on the current thread, and becomes invalid once the callback has
+ * been notified.
+ */
+ Cancellable asyncWait(Handle handle, HandleSignals signals, long deadline, Callback callback);
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
new file mode 100644
index 0000000..d6c8e7d
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java
@@ -0,0 +1,195 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.system;
+
+import java.util.List;
+
+/**
+ * Core mojo interface giving access to the base operations. See |src/mojo/public/c/system/core.h|
+ * for the underlying api.
+ */
+public interface Core {
+
+ /**
+ * Used to indicate an infinite deadline (timeout).
+ */
+ public static final long DEADLINE_INFINITE = -1;
+
+ /**
+ * Signals for the wait operations on handles.
+ */
+ public static class HandleSignals extends Flags<HandleSignals> {
+ /**
+ * Constructor.
+ *
+ * @param signals the serialized signals.
+ */
+ private HandleSignals(int signals) {
+ super(signals);
+ }
+
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_READABLE = 1 << 0;
+ private static final int FLAG_WRITABLE = 1 << 1;
+
+ /**
+ * Immutable signals.
+ */
+ public static final HandleSignals NONE = HandleSignals.none().immutable();
+ public static final HandleSignals READABLE =
+ HandleSignals.none().setReadable(true).immutable();
+ public static final HandleSignals WRITABLE =
+ HandleSignals.none().setWritable(true).immutable();
+
+ /**
+ * Change the readable bit of this signal.
+ *
+ * @param readable the new value of the readable bit.
+ * @return this.
+ */
+ public HandleSignals setReadable(boolean readable) {
+ return setFlag(FLAG_READABLE, readable);
+ }
+
+ /**
+ * Change the writable bit of this signal.
+ *
+ * @param writable the new value of the writable bit.
+ * @return this.
+ */
+ public HandleSignals setWritable(boolean writable) {
+ return setFlag(FLAG_WRITABLE, writable);
+ }
+
+ /**
+ * @return a signal with no bit set.
+ */
+ public static HandleSignals none() {
+ return new HandleSignals(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * @return a platform-dependent monotonically increasing tick count representing "right now."
+ */
+ public long getTimeTicksNow();
+
+ /**
+ * Waits on the given |handle| until the state indicated by |signals| is satisfied or until
+ * |deadline| has passed.
+ *
+ * @return |MojoResult.OK| if some signal in |signals| was satisfied (or is already satisfied).
+ * <p>
+ * |MojoResult.DEADLINE_EXCEEDED| if the deadline has passed without any of the signals
+ * being satisfied.
+ * <p>
+ * |MojoResult.CANCELLED| if |handle| is closed concurrently by another thread.
+ * <p>
+ * |MojoResult.FAILED_PRECONDITION| if it is or becomes impossible that any flag in
+ * |signals| will ever be satisfied (for example, if the other endpoint is close).
+ */
+ public int wait(Handle handle, HandleSignals signals, long deadline);
+
+ /**
+ * Result for the |waitMany| method.
+ */
+ public static class WaitManyResult {
+
+ /**
+ * See |wait| for the different possible values.
+ */
+ private int mMojoResult;
+ /**
+ * If |mojoResult| is |MojoResult.OK|, |handleIndex| is the index of the handle for which
+ * some flag was satisfied (or is already satisfied). If |mojoResult| is
+ * |MojoResult.CANCELLED| or |MojoResult.FAILED_PRECONDITION|, |handleIndex| is the index of
+ * the handle for which the issue occurred.
+ */
+ private int mHandleIndex;
+
+ /**
+ * @return the mojoResult
+ */
+ public int getMojoResult() {
+ return mMojoResult;
+ }
+
+ /**
+ * @param mojoResult the mojoResult to set
+ */
+ public void setMojoResult(int mojoResult) {
+ mMojoResult = mojoResult;
+ }
+
+ /**
+ * @return the handleIndex
+ */
+ public int getHandleIndex() {
+ return mHandleIndex;
+ }
+
+ /**
+ * @param handleIndex the handleIndex to set
+ */
+ public void setHandleIndex(int handleIndex) {
+ mHandleIndex = handleIndex;
+ }
+ }
+
+ /**
+ * Waits on handle in |handles| for at least one of them to satisfy the associated
+ * |HandleSignals|, or until |deadline| has passed.
+ *
+ * @returns a |WaitManyResult|.
+ */
+ public WaitManyResult waitMany(List<Pair<Handle, HandleSignals>> handles, long deadline);
+
+ /**
+ * Creates a message pipe, which is a bidirectional communication channel for framed data (i.e.,
+ * messages), with the given options. Messages can contain plain data and/or Mojo handles.
+ *
+ * @return the set of handles for the two endpoints (ports) of the message pipe.
+ */
+ public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe(
+ MessagePipeHandle.CreateOptions options);
+
+ /**
+ * 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 |DataPipe.CreateOptions| 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).
+ *
+ * @return the set of handles for the two endpoints of the data pipe.
+ */
+ public Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> createDataPipe(
+ DataPipe.CreateOptions options);
+
+ /**
+ * Creates a buffer that can be shared between applications (by duplicating the handle -- see
+ * |SharedBufferHandle.duplicate()| -- and passing it over a message pipe). To access the
+ * buffer, one must call |SharedBufferHandle.map|.
+ *
+ * @return the new |SharedBufferHandle|.
+ */
+ public SharedBufferHandle createSharedBuffer(SharedBufferHandle.CreateOptions options,
+ long numBytes);
+
+ /**
+ * Acquires a handle from the native side. The handle will be owned by the returned object and
+ * must not be closed outside of it.
+ *
+ * @return a new {@link UntypedHandle} representing the native handle.
+ */
+ public UntypedHandle acquireNativeHandle(int handle);
+
+ /**
+ * Returns a default implementation of {@link AsyncWaiter}.
+ */
+ public AsyncWaiter getDefaultAsyncWaiter();
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java
new file mode 100644
index 0000000..4cd74e1
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java
@@ -0,0 +1,335 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.system;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Interface for data pipes. A data pipe is a unidirectional communication channel for unframed
+ * data. Data is unframed, but must come as (multiples of) discrete elements, of the size given at
+ * creation time.
+ */
+public interface DataPipe {
+
+ /**
+ * Flags for the data pipe creation operation.
+ */
+ public static class CreateFlags extends Flags<CreateFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_MAY_DISCARD = 1 << 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final CreateFlags NONE = CreateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected CreateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the may-discard bit of this flag. This indicates that the data pipe 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.
+ *
+ * @param mayDiscard the new value of the may-discard bit.
+ * @return this.
+ */
+ public CreateFlags setMayDiscard(boolean mayDiscard) {
+ return setFlag(FLAG_MAY_DISCARD, mayDiscard);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static CreateFlags none() {
+ return new CreateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify creation parameters for a data pipe to |Core.createDataPipe()|.
+ */
+ public static class CreateOptions {
+
+ /**
+ * Used to specify different modes of operation, see |DataPipe.CreateFlags|.
+ */
+ private CreateFlags mFlags = CreateFlags.none();
+ /**
+ * The size of an element, in bytes. All transactions and buffers will consist of an
+ * integral number of elements. Must be nonzero.
+ */
+ private int mElementNumBytes;
+ /**
+ * 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).
+ */
+ private int mCapacityNumBytes;
+
+ /**
+ * @return the flags
+ */
+ public CreateFlags getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * @return the elementNumBytes
+ */
+ public int getElementNumBytes() {
+ return mElementNumBytes;
+ }
+
+ /**
+ * @param elementNumBytes the elementNumBytes to set
+ */
+ public void setElementNumBytes(int elementNumBytes) {
+ mElementNumBytes = elementNumBytes;
+ }
+
+ /**
+ * @return the capacityNumBytes
+ */
+ public int getCapacityNumBytes() {
+ return mCapacityNumBytes;
+ }
+
+ /**
+ * @param capacityNumBytes the capacityNumBytes to set
+ */
+ public void setCapacityNumBytes(int capacityNumBytes) {
+ mCapacityNumBytes = capacityNumBytes;
+ }
+
+ }
+
+ /**
+ * Flags for the write operations on MessagePipeHandle .
+ */
+ public static class WriteFlags extends Flags<WriteFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_ALL_OR_NONE = 1 << 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final WriteFlags NONE = WriteFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ private WriteFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the all-or-none bit of those flags. If set, write either all the elements
+ * requested or none of them.
+ *
+ * @param allOrNone the new value of all-or-none bit.
+ * @return this.
+ */
+ public WriteFlags setAllOrNone(boolean allOrNone) {
+ return setFlag(FLAG_ALL_OR_NONE, allOrNone);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static WriteFlags none() {
+ return new WriteFlags(FLAG_NONE);
+ }
+ }
+
+ /**
+ * Flags for the read operations on MessagePipeHandle.
+ */
+ public static class ReadFlags extends Flags<ReadFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_ALL_OR_NONE = 1 << 0;
+ private static final int FLAG_QUERY = 1 << 2;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final ReadFlags NONE = ReadFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ private ReadFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the all-or-none bit of this flag. If set, read (or discard) either the requested
+ * number of elements or none.
+ *
+ * @param allOrNone the new value of the all-or-none bit.
+ * @return this.
+ */
+ public ReadFlags setAllOrNone(boolean allOrNone) {
+ return setFlag(FLAG_ALL_OR_NONE, allOrNone);
+ }
+
+ /**
+ * Change the query bit of this flag. If set query the number of elements available to read.
+ * Mutually exclusive with |dicard| and |allOrNone| is ignored if this flag is set.
+ *
+ * @param query the new value of the query bit.
+ * @return this.
+ */
+ public ReadFlags query(boolean query) {
+ return setFlag(FLAG_QUERY, query);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static ReadFlags none() {
+ return new ReadFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Handle for the producer part of a data pipe.
+ */
+ public static interface ProducerHandle extends Handle {
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public ProducerHandle pass();
+
+ /**
+ * Writes the given data to the data pipe producer. |elements| points to data; the buffer
+ * must be a direct ByteBuffer and the limit should be a multiple of the data pipe's element
+ * size. If |allOrNone| is set in |flags|, either all the data will be written or none is.
+ * <p>
+ * On success, returns the amount of data that was actually written.
+ * <p>
+ * 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 the buffer
+ * limit is greater than the data pipe's capacity (and |allOrNone| is not set), this will
+ * write the maximum amount possible (namely, the data pipe's capacity) and return that
+ * amount. It will *not* discard data from |elements|.
+ *
+ * @return number of written bytes.
+ */
+ public int writeData(ByteBuffer elements, WriteFlags flags);
+
+ /**
+ * Begins a two-phase write to the data pipe producer . On success, returns a |ByteBuffer|
+ * to which the caller can write. If flags has |allOrNone| set, then the buffer capacity
+ * will be at least as large as |numBytes|, which must also be a multiple of the element
+ * size (if |allOrNone| is not set, |numBytes| is ignored and the caller must check the
+ * capacity of the buffer).
+ * <p>
+ * During a two-phase write, this handle is *not* writable. E.g., if another thread tries to
+ * write to it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread can
+ * then wait for this handle to become writable again.
+ * <p>
+ * Once the caller has finished writing data to the buffer, it should call |endWriteData()|
+ * to specify the amount written and to complete the two-phase write.
+ * <p>
+ * Note: If the data pipe has the "may discard" option flag (specified on creation) and
+ * |flags| has |allOrNone| set, this may discard some data.
+ *
+ * @return The buffer to write to.
+ */
+ public ByteBuffer beginWriteData(int numBytes, WriteFlags flags);
+
+ /**
+ * Ends a two-phase write to the data pipe producer that was begun by a call to
+ * |beginWriteData()| on the same handle. |numBytesWritten| should indicate the amount of
+ * data actually written; it must be less than or equal to the capacity of the buffer
+ * returned by |beginWriteData()| and must be a multiple of the element size. The buffer
+ * returned from |beginWriteData()| must have been filled with exactly |numBytesWritten|
+ * bytes of data.
+ * <p>
+ * 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 the buffer is "put into" the
+ * data pipe.
+ */
+ public void endWriteData(int numBytesWritten);
+ }
+
+ /**
+ * Handle for the consumer part of a data pipe.
+ */
+ public static interface ConsumerHandle extends Handle {
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public ConsumerHandle pass();
+
+ /**
+ * Discards data on the data pie consumer. This method discards up to |numBytes| (which
+ * again be a multiple of the element size) bytes of data, returning the amount actually
+ * discarded. if |flags| has |allOrNone|, it will either discard exactly |numBytes| bytes of
+ * data or none. In this case, |query| must not be set.
+ */
+ public int discardData(int numBytes, ReadFlags flags);
+
+ /**
+ * Reads data from the data pipe consumer. May also be used to query the amount of data
+ * available. If |flags| has not |query| set, this tries to read up to |elements| capacity
+ * (which must be a multiple of the data pipe's element size) bytes of data to |elements|
+ * and returns the amount actually read. |elements| must be a direct ByteBuffer. If flags
+ * has |allOrNone| set, it will either read exactly |elements| capacity bytes of data or
+ * none.
+ * <p>
+ * If flags has |query| set, it queries the amount of data available, returning the number
+ * of bytes available. In this case |allOrNone| is ignored, as are |elements|.
+ */
+ public int readData(ByteBuffer elements, ReadFlags flags);
+
+ /**
+ * Begins a two-phase read from the data pipe consumer. On success, returns a |ByteBuffer|
+ * from which the caller can read up to its limit bytes of data. If flags has |allOrNone|
+ * set, then the limit will be at least as large as |numBytes|, which must also be a
+ * multiple of the element size (if |allOrNone| is not set, |numBytes| is ignored). |flags|
+ * must not have |query| set.
+ * <p>
+ * During a two-phase read, this handle is *not* readable. E.g., if another thread tries to
+ * read from it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread
+ * can then wait for this handle to become readable again.
+ * <p>
+ * Once the caller has finished reading data from the buffer, it should call |endReadData()|
+ * to specify the amount read and to complete the two-phase read.
+ */
+ public ByteBuffer beginReadData(int numBytes, ReadFlags flags);
+
+ /**
+ * Ends a two-phase read from the data pipe consumer that was begun by a call to
+ * |beginReadData()| on the same handle. |numBytesRead| should indicate the amount of data
+ * actually read; it must be less than or equal to the limit of the buffer returned by
+ * |beginReadData()| and must be a multiple of the element size.
+ * <p>
+ * 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.
+ */
+ public void endReadData(int numBytesRead);
+ }
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java
new file mode 100644
index 0000000..dd2b00a
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * Base class for bit field used as flags.
+ *
+ * @param <F> the type of the flags.
+ */
+public abstract class Flags<F extends Flags<F>> {
+ private int mFlags;
+ private boolean mImmutable;
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ protected Flags(int flags) {
+ mImmutable = false;
+ mFlags = flags;
+ }
+
+ /**
+ * @return the computed flag.
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Change the given bit of this flag.
+ *
+ * @param value the new value of given bit.
+ * @return this.
+ */
+ protected F setFlag(int flag, boolean value) {
+ if (mImmutable) {
+ throw new UnsupportedOperationException("Flags is immutable.");
+ }
+ if (value) {
+ mFlags |= flag;
+ } else {
+ mFlags &= ~flag;
+ }
+ @SuppressWarnings("unchecked")
+ F f = (F) this;
+ return f;
+ }
+
+ /**
+ * Makes this flag immutable. This is a non-reversable operation.
+ */
+ protected F immutable() {
+ mImmutable = true;
+ @SuppressWarnings("unchecked")
+ F f = (F) this;
+ return f;
+ }
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
new file mode 100644
index 0000000..907e2b9
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import java.io.Closeable;
+
+/**
+ * A generic mojo handle.
+ */
+public interface Handle extends Closeable {
+
+ /**
+ * Closes the given |handle|.
+ * <p>
+ * Concurrent operations on |handle| may succeed (or fail as usual) if they happen before the
+ * close, be cancelled with result |MojoResult.CANCELLED| if they properly overlap (this is
+ * likely the case with |wait()|, etc.), or fail with |MojoResult.INVALID_ARGUMENT| if they
+ * happen after.
+ */
+ @Override
+ public void close();
+
+ /**
+ * @see Core#wait(Handle, Core.HandleSignals, long)
+ */
+ public int wait(Core.HandleSignals signals, long deadline);
+
+ /**
+ * @return whether the handle is valid. A handle is valid until it has been explicitly closed or
+ * send through a message pipe via |MessagePipeHandle.writeMessage|.
+ */
+ public boolean isValid();
+
+ /**
+ * Converts this handle into an {@link UntypedHandle}, invalidating this handle.
+ */
+ public UntypedHandle toUntypedHandle();
+
+ /**
+ * Returns the {@link Core} implementation for this handle. Can be null if this handle is
+ * invalid.
+ */
+ public Core getCore();
+
+ /**
+ * Passes ownership of the handle from this handle to the newly created Handle object,
+ * invalidating this handle object in the process.
+ */
+ public Handle pass();
+
+ /**
+ * Releases the native handle backed by this {@link Handle}. The caller owns the handle and must
+ * close it.
+ */
+ public int releaseNativeHandle();
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
new file mode 100644
index 0000000..12f0457
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
@@ -0,0 +1,219 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.system;
+
+import org.chromium.mojo.system.Core.HandleSignals;
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A handle that will always be invalid.
+ */
+public class InvalidHandle implements UntypedHandle, MessagePipeHandle, ConsumerHandle,
+ ProducerHandle, SharedBufferHandle {
+
+ /**
+ * Instance singleton.
+ */
+ public static final InvalidHandle INSTANCE = new InvalidHandle();
+
+ /**
+ * Private constructor.
+ */
+ private InvalidHandle() {
+ }
+
+ /**
+ * @see Handle#close()
+ */
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+
+ /**
+ * @see Handle#wait(Core.HandleSignals, long)
+ */
+ @Override
+ public int wait(HandleSignals signals, long deadline) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see Handle#isValid()
+ */
+ @Override
+ public boolean isValid() {
+ return false;
+ }
+
+ /**
+ * @see Handle#getCore()
+ */
+ @Override
+ public Core getCore() {
+ return null;
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public InvalidHandle pass() {
+ return this;
+ }
+
+ /**
+ * @see Handle#toUntypedHandle()
+ */
+ @Override
+ public UntypedHandle toUntypedHandle() {
+ return this;
+ }
+
+ /**
+ * @see Handle#releaseNativeHandle()
+ */
+ @Override
+ public int releaseNativeHandle() {
+ return -1;
+ }
+
+ /**
+ * @see UntypedHandle#toMessagePipeHandle()
+ */
+ @Override
+ public MessagePipeHandle toMessagePipeHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toDataPipeConsumerHandle()
+ */
+ @Override
+ public ConsumerHandle toDataPipeConsumerHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toDataPipeProducerHandle()
+ */
+ @Override
+ public ProducerHandle toDataPipeProducerHandle() {
+ return this;
+ }
+
+ /**
+ * @see UntypedHandle#toSharedBufferHandle()
+ */
+ @Override
+ public SharedBufferHandle toSharedBufferHandle() {
+ return this;
+ }
+
+ /**
+ * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions)
+ */
+ @Override
+ public SharedBufferHandle duplicate(DuplicateOptions options) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags)
+ */
+ @Override
+ public ByteBuffer map(long offset, long numBytes, MapFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see SharedBufferHandle#unmap(java.nio.ByteBuffer)
+ */
+ @Override
+ public void unmap(ByteBuffer buffer) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags)
+ */
+ @Override
+ public int writeData(ByteBuffer elements, DataPipe.WriteFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ProducerHandle#beginWriteData(int, DataPipe.WriteFlags)
+ */
+ @Override
+ public ByteBuffer beginWriteData(int numBytes,
+ DataPipe.WriteFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ProducerHandle#endWriteData(int)
+ */
+ @Override
+ public void endWriteData(int numBytesWritten) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#discardData(int, DataPipe.ReadFlags)
+ */
+ @Override
+ public int discardData(int numBytes, DataPipe.ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags)
+ */
+ @Override
+ public int readData(ByteBuffer elements, DataPipe.ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#beginReadData(int, DataPipe.ReadFlags)
+ */
+ @Override
+ public ByteBuffer beginReadData(int numBytes,
+ DataPipe.ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see DataPipe.ConsumerHandle#endReadData(int)
+ */
+ @Override
+ public void endReadData(int numBytesRead) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List,
+ * MessagePipeHandle.WriteFlags)
+ */
+ @Override
+ public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags)
+ */
+ @Override
+ public ReadMessageResult readMessage(ByteBuffer bytes, int maxNumberOfHandles,
+ ReadFlags flags) {
+ throw new MojoException(MojoResult.INVALID_ARGUMENT);
+ }
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
new file mode 100644
index 0000000..60ac682
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
@@ -0,0 +1,242 @@
+// Copyright 2014 The Chromium 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 org.chromium.mojo.system;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Message pipes are bidirectional communication channel for framed data (i.e., messages). Messages
+ * can contain plain data and/or Mojo handles.
+ */
+public interface MessagePipeHandle extends Handle {
+
+ /**
+ * Flags for the message pipe creation operation.
+ */
+ public static class CreateFlags extends Flags<CreateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final CreateFlags NONE = CreateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected CreateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static CreateFlags none() {
+ return new CreateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify creation parameters for a message pipe to |Core#createMessagePipe()|.
+ */
+ public static class CreateOptions {
+ private CreateFlags mFlags = CreateFlags.NONE;
+
+ /**
+ * @return the flags
+ */
+ public CreateFlags getFlags() {
+ return mFlags;
+ }
+
+ }
+
+ /**
+ * Flags for the write operations on MessagePipeHandle .
+ */
+ public static class WriteFlags extends Flags<WriteFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with no bit set.
+ */
+ public static final WriteFlags NONE = WriteFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ private WriteFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static WriteFlags none() {
+ return new WriteFlags(FLAG_NONE);
+ }
+ }
+
+ /**
+ * Flags for the read operations on MessagePipeHandle.
+ */
+ public static class ReadFlags extends Flags<ReadFlags> {
+ private static final int FLAG_NONE = 0;
+ private static final int FLAG_MAY_DISCARD = 1 << 0;
+
+ /**
+ * Immutable flag with no bit set.
+ */
+ public static final ReadFlags NONE = ReadFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flag.
+ */
+ private ReadFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * Change the may-discard bit of this flag. If set, 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).
+ *
+ * @param mayDiscard the new value of the may-discard bit.
+ * @return this.
+ */
+ public ReadFlags setMayDiscard(boolean mayDiscard) {
+ return setFlag(FLAG_MAY_DISCARD, mayDiscard);
+ }
+
+ /**
+ * @return a flag with no bit set.
+ */
+ public static ReadFlags none() {
+ return new ReadFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Result of the |readMessage| method.
+ */
+ public static class ReadMessageResult {
+ /**
+ * The MojoResult value of the read call.
+ */
+ private int mMojoResult;
+ /**
+ * If a message was read, the size in bytes of the message, otherwise the size in bytes of
+ * the next message.
+ */
+ private int mMessageSize;
+ /**
+ * If a message was read, the number of handles contained in the message, otherwise the
+ * number of handles contained in the next message.
+ */
+ private int mHandlesCount;
+ /**
+ * If a message was read, the handles contained in the message, undefined otherwise.
+ */
+ private List<UntypedHandle> mHandles;
+
+ /**
+ * @return the mojoResult
+ */
+ public int getMojoResult() {
+ return mMojoResult;
+ }
+
+ /**
+ * @param mojoResult the mojoResult to set
+ */
+ public void setMojoResult(int mojoResult) {
+ mMojoResult = mojoResult;
+ }
+
+ /**
+ * @return the messageSize
+ */
+ public int getMessageSize() {
+ return mMessageSize;
+ }
+
+ /**
+ * @param messageSize the messageSize to set
+ */
+ public void setMessageSize(int messageSize) {
+ mMessageSize = messageSize;
+ }
+
+ /**
+ * @return the handlesCount
+ */
+ public int getHandlesCount() {
+ return mHandlesCount;
+ }
+
+ /**
+ * @param handlesCount the handlesCount to set
+ */
+ public void setHandlesCount(int handlesCount) {
+ mHandlesCount = handlesCount;
+ }
+
+ /**
+ * @return the handles
+ */
+ public List<UntypedHandle> getHandles() {
+ return mHandles;
+ }
+
+ /**
+ * @param handles the handles to set
+ */
+ public void setHandles(List<UntypedHandle> handles) {
+ mHandles = handles;
+ }
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public MessagePipeHandle pass();
+
+ /**
+ * Writes a message to the message pipe endpoint, with message data specified by |bytes| and
+ * attached handles specified by |handles|, and options specified by |flags|. If there is no
+ * message data, |bytes| may be null, otherwise it must be a direct ByteBuffer. If there are no
+ * attached handles, |handles| may be null.
+ * <p>
+ * 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).
+ */
+ void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags);
+
+ /**
+ * Reads a message from the message pipe endpoint; also usable to query the size of the next
+ * message or discard the next message. |bytes| indicate the buffer/buffer size to receive the
+ * message data (if any) and |maxNumberOfHandles| indicate the maximum handle count to receive
+ * the attached handles (if any). |bytes| is optional. If null, |maxNumberOfHandles| must be
+ * zero, and the return value will indicate the size of the next message. If non-null, it must
+ * be a direct ByteBuffer and the return value will indicate if the message was read or not. If
+ * the message was read its content will be in |bytes|, and the attached handles will be in the
+ * return value. Partial reads are NEVER done. Either a full read is done and |wasMessageRead|
+ * will be true, or the read is NOT done and |wasMessageRead| will be false (if |mayDiscard| was
+ * set, the message is also discarded in this case).
+ */
+ ReadMessageResult readMessage(ByteBuffer bytes, int maxNumberOfHandles,
+ ReadFlags flags);
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java
new file mode 100644
index 0000000..e06f647
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * Exception for the core mojo API.
+ */
+public class MojoException extends RuntimeException {
+
+ private final int mCode;
+
+ /**
+ * Constructor.
+ */
+ public MojoException(int code) {
+ mCode = code;
+ }
+
+ /**
+ * The mojo result code associated with this exception. See {@link MojoResult} for possible
+ * values.
+ */
+ public int getMojoResult() {
+ return mCode;
+ }
+
+ /**
+ * @see Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "MojoResult(" + mCode + "): " + MojoResult.describe(mCode);
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java
new file mode 100644
index 0000000..a829c83
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+/**
+ * The different mojo result codes.
+ */
+public final class MojoResult {
+ public static final int OK = 0;
+ public static final int CANCELLED = -1;
+ public static final int UNKNOWN = -2;
+ public static final int INVALID_ARGUMENT = -3;
+ public static final int DEADLINE_EXCEEDED = -4;
+ public static final int NOT_FOUND = -5;
+ public static final int ALREADY_EXISTS = -6;
+ public static final int PERMISSION_DENIED = -7;
+ public static final int RESOURCE_EXHAUSTED = -8;
+ public static final int FAILED_PRECONDITION = -9;
+ public static final int ABORTED = -10;
+ public static final int OUT_OF_RANGE = -11;
+ public static final int UNIMPLEMENTED = -12;
+ public static final int INTERNAL = -13;
+ public static final int UNAVAILABLE = -14;
+ public static final int DATA_LOSS = -15;
+ public static final int BUSY = -16;
+ public static final int SHOULD_WAIT = -17;
+
+ /**
+ * never instantiate.
+ */
+ private MojoResult() {
+ }
+
+ /**
+ * Describes the given result code.
+ */
+ public static String describe(int mCode) {
+ switch (mCode) {
+ case OK:
+ return "OK";
+ case CANCELLED:
+ return "CANCELLED";
+ case UNKNOWN:
+ return "UNKNOWN";
+ case INVALID_ARGUMENT:
+ return "INVALID_ARGUMENT";
+ case DEADLINE_EXCEEDED:
+ return "DEADLINE_EXCEEDED";
+ case NOT_FOUND:
+ return "NOT_FOUND";
+ case ALREADY_EXISTS:
+ return "ALREADY_EXISTS";
+ case PERMISSION_DENIED:
+ return "PERMISSION_DENIED";
+ case RESOURCE_EXHAUSTED:
+ return "RESOURCE_EXHAUSTED";
+ case FAILED_PRECONDITION:
+ return "FAILED_PRECONDITION";
+ case ABORTED:
+ return "ABORTED";
+ case OUT_OF_RANGE:
+ return "OUT_OF_RANGE";
+ case UNIMPLEMENTED:
+ return "UNIMPLEMENTED";
+ case INTERNAL:
+ return "INTERNAL";
+ case UNAVAILABLE:
+ return "UNAVAILABLE";
+ case DATA_LOSS:
+ return "DATA_LOSS";
+ case BUSY:
+ return "BUSY";
+ case SHOULD_WAIT:
+ return "SHOULD_WAIT";
+ default:
+ return "UNKNOWN";
+ }
+
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java
new file mode 100644
index 0000000..2ead020
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+
+/**
+ * A pair of object.
+ *
+ * @param <F> Type of the first element.
+ * @param <S> Type of the second element.
+ */
+public class Pair<F, S> {
+
+ public final F first;
+ public final S second;
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param first the first element of the pair.
+ * @param second the second element of the pair.
+ */
+ public Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ /**
+ * equals() that handles null values.
+ */
+ private boolean equals(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ /**
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Pair)) {
+ return false;
+ }
+ Pair<?, ?> p = (Pair<?, ?>) o;
+ return equals(first, p.first) && equals(second, p.second);
+ }
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
+ }
+
+ /**
+ * Helper method for creating a pair.
+ *
+ * @param a the first element of the pair.
+ * @param b the second element of the pair.
+ * @return the pair containing a and b.
+ */
+ public static <A, B> Pair<A, B> create(A a, B b) {
+ return new Pair<A, B>(a, b);
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java
new file mode 100644
index 0000000..df317d1
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A buffer that can be shared between applications.
+ */
+public interface SharedBufferHandle extends Handle {
+
+ /**
+ * Flags for the shared buffer creation operation.
+ */
+ public static class CreateFlags extends Flags<CreateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final CreateFlags NONE = CreateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected CreateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static CreateFlags none() {
+ return new CreateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify creation parameters for a shared buffer to |Core#createSharedBuffer()|.
+ */
+ public static class CreateOptions {
+ private CreateFlags mFlags = CreateFlags.NONE;
+
+ /**
+ * @return the flags
+ */
+ public CreateFlags getFlags() {
+ return mFlags;
+ }
+
+ }
+
+ /**
+ * Flags for the shared buffer duplication operation.
+ */
+ public static class DuplicateFlags extends Flags<DuplicateFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final DuplicateFlags NONE = DuplicateFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected DuplicateFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static DuplicateFlags none() {
+ return new DuplicateFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * Used to specify parameters in duplicating access to a shared buffer to
+ * |SharedBufferHandle#duplicate|
+ */
+ public static class DuplicateOptions {
+ private DuplicateFlags mFlags = DuplicateFlags.NONE;
+
+ /**
+ * @return the flags
+ */
+ public DuplicateFlags getFlags() {
+ return mFlags;
+ }
+
+ }
+
+ /**
+ * Flags for the shared buffer map operation.
+ */
+ public static class MapFlags extends Flags<MapFlags> {
+ private static final int FLAG_NONE = 0;
+
+ /**
+ * Immutable flag with not bit set.
+ */
+ public static final MapFlags NONE = MapFlags.none().immutable();
+
+ /**
+ * Dedicated constructor.
+ *
+ * @param flags initial value of the flags.
+ */
+ protected MapFlags(int flags) {
+ super(flags);
+ }
+
+ /**
+ * @return flags with no bit set.
+ */
+ public static MapFlags none() {
+ return new MapFlags(FLAG_NONE);
+ }
+
+ }
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public SharedBufferHandle pass();
+
+ /**
+ * Duplicates the handle. This creates another handle (returned on success), which can then be
+ * sent to another application over a message pipe, while retaining access to this handle (and
+ * any mappings that it may have).
+ */
+ public SharedBufferHandle duplicate(DuplicateOptions options);
+
+ /**
+ * Map the part (at offset |offset| of length |numBytes|) of the buffer given by this handle
+ * into memory. |offset + numBytes| must be less than or equal to the size of the buffer. On
+ * success, the returned buffer points to memory with the requested part of the buffer. 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|.
+ */
+ public ByteBuffer map(long offset, long numBytes, MapFlags flags);
+
+ /**
+ * Unmap a buffer pointer that was mapped by |map()|.
+ */
+ public void unmap(ByteBuffer buffer);
+
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java
new file mode 100644
index 0000000..199b0a1
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java
@@ -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.
+
+package org.chromium.mojo.system;
+
+import org.chromium.mojo.system.DataPipe.ConsumerHandle;
+import org.chromium.mojo.system.DataPipe.ProducerHandle;
+
+/**
+ * A mojo handle of unknown type. This handle can be typed by using one of its methods, which will
+ * return a handle of the requested type and invalidate this object. No validation is made when the
+ * conversion operation is called.
+ */
+public interface UntypedHandle extends Handle {
+
+ /**
+ * @see org.chromium.mojo.system.Handle#pass()
+ */
+ @Override
+ public UntypedHandle pass();
+
+ /**
+ * Returns the underlying handle, as a {@link MessagePipeHandle}, invalidating this
+ * representation.
+ */
+ public MessagePipeHandle toMessagePipeHandle();
+
+ /**
+ * Returns the underlying handle, as a {@link ConsumerHandle}, invalidating this representation.
+ */
+ public ConsumerHandle toDataPipeConsumerHandle();
+
+ /**
+ * Returns the underlying handle, as a {@link ProducerHandle}, invalidating this representation.
+ */
+ public ProducerHandle toDataPipeProducerHandle();
+
+ /**
+ * Returns the underlying handle, as a {@link SharedBufferHandle}, invalidating this
+ * representation.
+ */
+ public SharedBufferHandle toSharedBufferHandle();
+
+}
diff --git a/mojo/public/js/bindings/BUILD.gn b/mojo/public/js/bindings/BUILD.gn
new file mode 100644
index 0000000..0f305e9
--- /dev/null
+++ b/mojo/public/js/bindings/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("bindings") {
+ sources = [
+ "constants.cc",
+ "constants.h",
+ ]
+}
diff --git a/mojo/public/js/bindings/buffer.js b/mojo/public/js/bindings/buffer.js
new file mode 100644
index 0000000..d74fef6
--- /dev/null
+++ b/mojo/public/js/bindings/buffer.js
@@ -0,0 +1,156 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/buffer", function() {
+
+ var kHostIsLittleEndian = (function () {
+ var endianArrayBuffer = new ArrayBuffer(2);
+ var endianUint8Array = new Uint8Array(endianArrayBuffer);
+ var endianUint16Array = new Uint16Array(endianArrayBuffer);
+ endianUint16Array[0] = 1;
+ return endianUint8Array[0] == 1;
+ })();
+
+ var kHighWordMultiplier = 0x100000000;
+
+ function Buffer(sizeOrArrayBuffer) {
+ if (sizeOrArrayBuffer instanceof ArrayBuffer)
+ this.arrayBuffer = sizeOrArrayBuffer;
+ else
+ this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer);
+
+ this.dataView = new DataView(this.arrayBuffer);
+ this.next = 0;
+ }
+
+ Object.defineProperty(Buffer.prototype, "byteLength", {
+ get: function() { return this.arrayBuffer.byteLength; }
+ });
+
+ Buffer.prototype.alloc = function(size) {
+ var pointer = this.next;
+ this.next += size;
+ if (this.next > this.byteLength) {
+ var newSize = (1.5 * (this.byteLength + size)) | 0;
+ this.grow(newSize);
+ }
+ return pointer;
+ };
+
+ function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) {
+ (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer));
+ }
+
+ Buffer.prototype.grow = function(size) {
+ var newArrayBuffer = new ArrayBuffer(size);
+ copyArrayBuffer(newArrayBuffer, this.arrayBuffer);
+ this.arrayBuffer = newArrayBuffer;
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.trim = function() {
+ this.arrayBuffer = this.arrayBuffer.slice(0, this.next);
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.getUint8 = function(offset) {
+ return this.dataView.getUint8(offset);
+ }
+ Buffer.prototype.getUint16 = function(offset) {
+ return this.dataView.getUint16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint32 = function(offset) {
+ return this.dataView.getUint32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getInt8 = function(offset) {
+ return this.dataView.getInt8(offset);
+ }
+ Buffer.prototype.getInt16 = function(offset) {
+ return this.dataView.getInt16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt32 = function(offset) {
+ return this.dataView.getInt32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getInt32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getFloat32 = function(offset) {
+ return this.dataView.getFloat32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getFloat64 = function(offset) {
+ return this.dataView.getFloat64(offset, kHostIsLittleEndian);
+ }
+
+ Buffer.prototype.setUint8 = function(offset, value) {
+ this.dataView.setUint8(offset, value);
+ }
+ Buffer.prototype.setUint16 = function(offset, value) {
+ this.dataView.setUint16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint32 = function(offset, value) {
+ this.dataView.setUint32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint64 = function(offset, value) {
+ var hi = (value / kHighWordMultiplier) | 0;
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setInt8 = function(offset, value) {
+ this.dataView.setInt8(offset, value);
+ }
+ Buffer.prototype.setInt16 = function(offset, value) {
+ this.dataView.setInt16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt32 = function(offset, value) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt64 = function(offset, value) {
+ var hi = Math.floor(value / kHighWordMultiplier);
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setFloat32 = function(offset, value) {
+ this.dataView.setFloat32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setFloat64 = function(offset, value) {
+ this.dataView.setFloat64(offset, value, kHostIsLittleEndian);
+ }
+
+ var exports = {};
+ exports.Buffer = Buffer;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/codec.js b/mojo/public/js/bindings/codec.js
new file mode 100644
index 0000000..623cca7
--- /dev/null
+++ b/mojo/public/js/bindings/codec.js
@@ -0,0 +1,739 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/codec", [
+ "mojo/public/js/bindings/unicode",
+ "mojo/public/js/bindings/buffer"
+ ], function(unicode, buffer) {
+
+ var kErrorUnsigned = "Passing negative value to unsigned";
+
+ // Memory -------------------------------------------------------------------
+
+ var kAlignment = 8;
+
+ function align(size) {
+ return size + (kAlignment - (size % kAlignment)) % kAlignment;
+ }
+
+ function isAligned(offset) {
+ return offset >= 0 && (offset % kAlignment) === 0;
+ }
+
+ // Constants ----------------------------------------------------------------
+
+ var kArrayHeaderSize = 8;
+ var kStructHeaderSize = 8;
+ var kMessageHeaderSize = 16;
+ var kMessageWithRequestIDHeaderSize = 24;
+
+ var kStructHeaderNumBytesOffset = 0;
+ var kStructHeaderNumFieldsOffset = 4;
+
+ var kEncodedInvalidHandleValue = 0xFFFFFFFF;
+
+ // Decoder ------------------------------------------------------------------
+
+ function Decoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Decoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Decoder.prototype.readInt8 = function() {
+ var result = this.buffer.getInt8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readUint8 = function() {
+ var result = this.buffer.getUint8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readInt16 = function() {
+ var result = this.buffer.getInt16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readUint16 = function() {
+ var result = this.buffer.getUint16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readInt32 = function() {
+ var result = this.buffer.getInt32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readUint32 = function() {
+ var result = this.buffer.getUint32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readInt64 = function() {
+ var result = this.buffer.getInt64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readUint64 = function() {
+ var result = this.buffer.getUint64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readFloat = function() {
+ var result = this.buffer.getFloat32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readDouble = function() {
+ var result = this.buffer.getFloat64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.decodePointer = function() {
+ // TODO(abarth): To correctly decode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offsetPointer = this.next;
+ var offset = this.readUint64();
+ if (!offset)
+ return 0;
+ return offsetPointer + offset;
+ };
+
+ Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
+ return new Decoder(this.buffer, this.handles, pointer);
+ };
+
+ Decoder.prototype.decodeHandle = function() {
+ return this.handles[this.readUint32()];
+ };
+
+ Decoder.prototype.decodeString = function() {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var base = this.next;
+ this.next += numberOfElements;
+ return unicode.decodeUtf8String(
+ new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
+ };
+
+ Decoder.prototype.decodeArray = function(cls) {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var val = new Array(numberOfElements);
+ if (cls === PackedBool) {
+ var byte;
+ for (var i = 0; i < numberOfElements; ++i) {
+ if (i % 8 === 0)
+ byte = this.readUint8();
+ val[i] = (byte & (1 << i % 8)) ? true : false;
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i) {
+ val[i] = cls.decode(this);
+ }
+ }
+ return val;
+ };
+
+ Decoder.prototype.decodeStruct = function(cls) {
+ return cls.decode(this);
+ };
+
+ Decoder.prototype.decodeStructPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return cls.decode(this.decodeAndCreateDecoder(pointer));
+ };
+
+ Decoder.prototype.decodeArrayPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
+ };
+
+ Decoder.prototype.decodeStringPointer = function() {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeString();
+ };
+
+ // Encoder ------------------------------------------------------------------
+
+ function Encoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Encoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Encoder.prototype.writeInt8 = function(val) {
+ this.buffer.setInt8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeUint8 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeInt16 = function(val) {
+ this.buffer.setInt16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeUint16 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeInt32 = function(val) {
+ this.buffer.setInt32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeUint32 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeInt64 = function(val) {
+ this.buffer.setInt64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeUint64 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeFloat = function(val) {
+ this.buffer.setFloat32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeDouble = function(val) {
+ this.buffer.setFloat64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.encodePointer = function(pointer) {
+ if (!pointer)
+ return this.writeUint64(0);
+ // TODO(abarth): To correctly encode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offset = pointer - this.next;
+ this.writeUint64(offset);
+ };
+
+ Encoder.prototype.createAndEncodeEncoder = function(size) {
+ var pointer = this.buffer.alloc(align(size));
+ this.encodePointer(pointer);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ Encoder.prototype.encodeHandle = function(handle) {
+ this.handles.push(handle);
+ this.writeUint32(this.handles.length - 1);
+ };
+
+ Encoder.prototype.encodeString = function(val) {
+ var base = this.next + kArrayHeaderSize;
+ var numberOfElements = unicode.encodeUtf8String(
+ val, new Uint8Array(this.buffer.arrayBuffer, base));
+ var numberOfBytes = kArrayHeaderSize + numberOfElements;
+ this.writeUint32(numberOfBytes);
+ this.writeUint32(numberOfElements);
+ this.next += numberOfElements;
+ };
+
+ Encoder.prototype.encodeArray =
+ function(cls, val, numberOfElements, encodedSize) {
+ if (numberOfElements === undefined)
+ numberOfElements = val.length;
+ if (encodedSize === undefined)
+ encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
+
+ this.writeUint32(encodedSize);
+ this.writeUint32(numberOfElements);
+
+ if (cls === PackedBool) {
+ var byte = 0;
+ for (i = 0; i < numberOfElements; ++i) {
+ if (val[i])
+ byte |= (1 << i % 8);
+ if (i % 8 === 7 || i == numberOfElements - 1) {
+ Uint8.encode(this, byte);
+ byte = 0;
+ }
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i)
+ cls.encode(this, val[i]);
+ }
+ };
+
+ Encoder.prototype.encodeStruct = function(cls, val) {
+ return cls.encode(this, val);
+ };
+
+ Encoder.prototype.encodeStructPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encoder = this.createAndEncodeEncoder(cls.encodedSize);
+ cls.encode(encoder, val);
+ };
+
+ Encoder.prototype.encodeArrayPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var numberOfElements = val.length;
+ var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
+ Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeArray(cls, val, numberOfElements, encodedSize);
+ };
+
+ Encoder.prototype.encodeStringPointer = function(val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encodedSize = kArrayHeaderSize + unicode.utf8Length(val);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeString(val);
+ };
+
+ // Message ------------------------------------------------------------------
+
+ var kMessageNameOffset = kStructHeaderSize;
+ var kMessageFlagsOffset = kMessageNameOffset + 4;
+ var kMessageRequestIDOffset = kMessageFlagsOffset + 4;
+
+ var kMessageExpectsResponse = 1 << 0;
+ var kMessageIsResponse = 1 << 1;
+
+ function Message(buffer, handles) {
+ this.buffer = buffer;
+ this.handles = handles;
+ }
+
+ Message.prototype.getHeaderNumBytes = function() {
+ return this.buffer.getUint32(kStructHeaderNumBytesOffset);
+ };
+
+ Message.prototype.getHeaderNumFields = function() {
+ return this.buffer.getUint32(kStructHeaderNumFieldsOffset);
+ };
+
+ Message.prototype.getName = function() {
+ return this.buffer.getUint32(kMessageNameOffset);
+ };
+
+ Message.prototype.getFlags = function() {
+ return this.buffer.getUint32(kMessageFlagsOffset);
+ };
+
+ Message.prototype.isResponse = function() {
+ return (this.getFlags() & kMessageIsResponse) != 0;
+ };
+
+ Message.prototype.expectsResponse = function() {
+ return (this.getFlags() & kMessageExpectsResponse) != 0;
+ };
+
+ Message.prototype.setRequestID = function(requestID) {
+ // TODO(darin): Verify that space was reserved for this field!
+ this.buffer.setUint64(kMessageRequestIDOffset, requestID);
+ };
+
+
+ // MessageBuilder -----------------------------------------------------------
+
+ function MessageBuilder(messageName, payloadSize) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageHeaderSize + payloadSize;
+ this.buffer = new buffer.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageHeaderSize);
+ encoder.writeUint32(kMessageHeaderSize);
+ encoder.writeUint32(2); // num_fields.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(0); // flags.
+ }
+
+ MessageBuilder.prototype.createEncoder = function(size) {
+ var pointer = this.buffer.alloc(size);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ MessageBuilder.prototype.encodeStruct = function(cls, val) {
+ cls.encode(this.createEncoder(cls.encodedSize), val);
+ };
+
+ MessageBuilder.prototype.finish = function() {
+ // TODO(abarth): Rather than resizing the buffer at the end, we could
+ // compute the size we need ahead of time, like we do in C++.
+ this.buffer.trim();
+ var message = new Message(this.buffer, this.handles);
+ this.buffer = null;
+ this.handles = null;
+ this.encoder = null;
+ return message;
+ };
+
+ // MessageWithRequestIDBuilder -----------------------------------------------
+
+ function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+ requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
+ this.buffer = new buffer.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(3); // num_fields.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(flags);
+ encoder.writeUint64(requestID);
+ }
+
+ MessageWithRequestIDBuilder.prototype =
+ Object.create(MessageBuilder.prototype);
+
+ MessageWithRequestIDBuilder.prototype.constructor =
+ MessageWithRequestIDBuilder;
+
+ // MessageReader ------------------------------------------------------------
+
+ function MessageReader(message) {
+ this.decoder = new Decoder(message.buffer, message.handles, 0);
+ var messageHeaderSize = this.decoder.readUint32();
+ this.payloadSize = message.buffer.byteLength - messageHeaderSize;
+ var numFields = this.decoder.readUint32();
+ this.messageName = this.decoder.readUint32();
+ this.flags = this.decoder.readUint32();
+ if (numFields >= 3)
+ this.requestID = this.decoder.readUint64();
+ this.decoder.skip(messageHeaderSize - this.decoder.next);
+ }
+
+ MessageReader.prototype.decodeStruct = function(cls) {
+ return cls.decode(this.decoder);
+ };
+
+ // Built-in types -----------------------------------------------------------
+
+ // This type is only used with ArrayOf(PackedBool).
+ function PackedBool() {
+ }
+
+ function Int8() {
+ }
+
+ Int8.encodedSize = 1;
+
+ Int8.decode = function(decoder) {
+ return decoder.readInt8();
+ };
+
+ Int8.encode = function(encoder, val) {
+ encoder.writeInt8(val);
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Uint8() {
+ }
+
+ Uint8.encodedSize = 1;
+
+ Uint8.decode = function(decoder) {
+ return decoder.readUint8();
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Int16() {
+ }
+
+ Int16.encodedSize = 2;
+
+ Int16.decode = function(decoder) {
+ return decoder.readInt16();
+ };
+
+ Int16.encode = function(encoder, val) {
+ encoder.writeInt16(val);
+ };
+
+ function Uint16() {
+ }
+
+ Uint16.encodedSize = 2;
+
+ Uint16.decode = function(decoder) {
+ return decoder.readUint16();
+ };
+
+ Uint16.encode = function(encoder, val) {
+ encoder.writeUint16(val);
+ };
+
+ function Int32() {
+ }
+
+ Int32.encodedSize = 4;
+
+ Int32.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Int32.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function Uint32() {
+ }
+
+ Uint32.encodedSize = 4;
+
+ Uint32.decode = function(decoder) {
+ return decoder.readUint32();
+ };
+
+ Uint32.encode = function(encoder, val) {
+ encoder.writeUint32(val);
+ };
+
+ function Int64() {
+ }
+
+ Int64.encodedSize = 8;
+
+ Int64.decode = function(decoder) {
+ return decoder.readInt64();
+ };
+
+ Int64.encode = function(encoder, val) {
+ encoder.writeInt64(val);
+ };
+
+ function Uint64() {
+ }
+
+ Uint64.encodedSize = 8;
+
+ Uint64.decode = function(decoder) {
+ return decoder.readUint64();
+ };
+
+ Uint64.encode = function(encoder, val) {
+ encoder.writeUint64(val);
+ };
+
+ function String() {
+ };
+
+ String.encodedSize = 8;
+
+ String.decode = function(decoder) {
+ return decoder.decodeStringPointer();
+ };
+
+ String.encode = function(encoder, val) {
+ encoder.encodeStringPointer(val);
+ };
+
+ function NullableString() {
+ }
+
+ NullableString.encodedSize = String.encodedSize;
+
+ NullableString.decode = String.decode;
+
+ NullableString.encode = String.encode;
+
+ function Float() {
+ }
+
+ Float.encodedSize = 4;
+
+ Float.decode = function(decoder) {
+ return decoder.readFloat();
+ };
+
+ Float.encode = function(encoder, val) {
+ encoder.writeFloat(val);
+ };
+
+ function Double() {
+ }
+
+ Double.encodedSize = 8;
+
+ Double.decode = function(decoder) {
+ return decoder.readDouble();
+ };
+
+ Double.encode = function(encoder, val) {
+ encoder.writeDouble(val);
+ };
+
+ function PointerTo(cls) {
+ this.cls = cls;
+ }
+
+ PointerTo.prototype.encodedSize = 8;
+
+ PointerTo.prototype.decode = function(decoder) {
+ var pointer = decoder.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
+ };
+
+ PointerTo.prototype.encode = function(encoder, val) {
+ if (!val) {
+ encoder.encodePointer(val);
+ return;
+ }
+ var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
+ this.cls.encode(objectEncoder, val);
+ };
+
+ function NullablePointerTo(cls) {
+ PointerTo.call(this, cls);
+ }
+
+ NullablePointerTo.prototype = Object.create(PointerTo.prototype);
+
+ function ArrayOf(cls) {
+ this.cls = cls;
+ }
+
+ ArrayOf.prototype.encodedSize = 8;
+
+ ArrayOf.prototype.decode = function(decoder) {
+ return decoder.decodeArrayPointer(this.cls);
+ };
+
+ ArrayOf.prototype.encode = function(encoder, val) {
+ encoder.encodeArrayPointer(this.cls, val);
+ };
+
+ function NullableArrayOf(cls) {
+ ArrayOf.call(this, cls);
+ }
+
+ NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
+
+ function Handle() {
+ }
+
+ Handle.encodedSize = 4;
+
+ Handle.decode = function(decoder) {
+ return decoder.decodeHandle();
+ };
+
+ Handle.encode = function(encoder, val) {
+ encoder.encodeHandle(val);
+ };
+
+ function NullableHandle() {
+ }
+
+ NullableHandle.encodedSize = Handle.encodedSize;
+
+ NullableHandle.decode = Handle.decode;
+
+ NullableHandle.encode = Handle.encode;
+
+ var exports = {};
+ exports.align = align;
+ exports.isAligned = isAligned;
+ exports.Message = Message;
+ exports.MessageBuilder = MessageBuilder;
+ exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+ exports.MessageReader = MessageReader;
+ exports.kArrayHeaderSize = kArrayHeaderSize;
+ exports.kStructHeaderSize = kStructHeaderSize;
+ exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
+ exports.kMessageHeaderSize = kMessageHeaderSize;
+ exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
+ exports.kMessageExpectsResponse = kMessageExpectsResponse;
+ exports.kMessageIsResponse = kMessageIsResponse;
+ exports.Int8 = Int8;
+ exports.Uint8 = Uint8;
+ exports.Int16 = Int16;
+ exports.Uint16 = Uint16;
+ exports.Int32 = Int32;
+ exports.Uint32 = Uint32;
+ exports.Int64 = Int64;
+ exports.Uint64 = Uint64;
+ exports.Float = Float;
+ exports.Double = Double;
+ exports.String = String;
+ exports.NullableString = NullableString;
+ exports.PointerTo = PointerTo;
+ exports.NullablePointerTo = NullablePointerTo;
+ exports.ArrayOf = ArrayOf;
+ exports.NullableArrayOf = NullableArrayOf;
+ exports.PackedBool = PackedBool;
+ exports.Handle = Handle;
+ exports.NullableHandle = NullableHandle;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/codec_unittests.js b/mojo/public/js/bindings/codec_unittests.js
new file mode 100644
index 0000000..0276db2
--- /dev/null
+++ b/mojo/public/js/bindings/codec_unittests.js
@@ -0,0 +1,258 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "gin/test/expect",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/interfaces/bindings/tests/rect.mojom",
+ "mojo/public/interfaces/bindings/tests/sample_service.mojom",
+ "mojo/public/interfaces/bindings/tests/test_structs.mojom",
+ ], function(expect, codec, rect, sample, structs) {
+ testBar();
+ testFoo();
+ testNamedRegion();
+ testTypes();
+ testAlign();
+ testUtf8();
+ this.result = "PASS";
+
+ function testBar() {
+ var bar = new sample.Bar();
+ bar.alpha = 1;
+ bar.beta = 2;
+ bar.gamma = 3;
+ bar.type = 0x08070605;
+ bar.extraProperty = "banana";
+
+ var messageName = 42;
+ var payloadSize = sample.Bar.encodedSize;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(sample.Bar, bar);
+
+ var message = builder.finish();
+
+ var expectedMemory = new Uint8Array([
+ 16, 0, 0, 0,
+ 2, 0, 0, 0,
+ 42, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 16, 0, 0, 0,
+ 4, 0, 0, 0,
+
+ 1, 2, 3, 0,
+ 5, 6, 7, 8,
+ ]);
+
+ var actualMemory = new Uint8Array(message.buffer.arrayBuffer);
+ expect(actualMemory).toEqual(expectedMemory);
+
+ var reader = new codec.MessageReader(message);
+
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+
+ var bar2 = reader.decodeStruct(sample.Bar);
+
+ expect(bar2.alpha).toBe(bar.alpha);
+ expect(bar2.beta).toBe(bar.beta);
+ expect(bar2.gamma).toBe(bar.gamma);
+ expect("extraProperty" in bar2).toBeFalsy();
+ }
+
+ function testFoo() {
+ var foo = new sample.Foo();
+ foo.x = 0x212B4D5;
+ foo.y = 0x16E93;
+ foo.a = 1;
+ foo.b = 0;
+ foo.c = 3; // This will get truncated to one bit.
+ foo.bar = new sample.Bar();
+ foo.bar.alpha = 91;
+ foo.bar.beta = 82;
+ foo.bar.gamma = 73;
+ foo.data = [
+ 4, 5, 6, 7, 8,
+ ];
+ foo.extra_bars = [
+ new sample.Bar(), new sample.Bar(), new sample.Bar(),
+ ];
+ for (var i = 0; i < foo.extra_bars.length; ++i) {
+ foo.extra_bars[i].alpha = 1 * i;
+ foo.extra_bars[i].beta = 2 * i;
+ foo.extra_bars[i].gamma = 3 * i;
+ }
+ foo.name = "I am a banana";
+ // This is supposed to be a handle, but we fake it with an integer.
+ foo.source = 23423782;
+ foo.array_of_array_of_bools = [
+ [true], [false, true]
+ ];
+ foo.array_of_bools = [
+ true, false, true, false, true, false, true, true
+ ];
+
+
+ var messageName = 31;
+ var payloadSize = 304;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(sample.Foo, foo);
+
+ var message = builder.finish();
+
+ var expectedMemory = new Uint8Array([
+ /* 0: */ 16, 0, 0, 0, 2, 0, 0, 0,
+ /* 8: */ 31, 0, 0, 0, 0, 0, 0, 0,
+ /* 16: */ 96, 0, 0, 0, 15, 0, 0, 0,
+ /* 24: */ 0xD5, 0xB4, 0x12, 0x02, 0x93, 0x6E, 0x01, 0,
+ /* 32: */ 5, 0, 0, 0, 0, 0, 0, 0,
+ /* 40: */ 72, 0, 0, 0, 0, 0, 0, 0,
+ ]);
+ // TODO(abarth): Test more of the message's raw memory.
+ var actualMemory = new Uint8Array(message.buffer.arrayBuffer,
+ 0, expectedMemory.length);
+ expect(actualMemory).toEqual(expectedMemory);
+
+ var expectedHandles = [
+ 23423782,
+ ];
+
+ expect(message.handles).toEqual(expectedHandles);
+
+ var reader = new codec.MessageReader(message);
+
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+
+ var foo2 = reader.decodeStruct(sample.Foo);
+
+ expect(foo2.x).toBe(foo.x);
+ expect(foo2.y).toBe(foo.y);
+
+ expect(foo2.a).toBe(foo.a & 1 ? true : false);
+ expect(foo2.b).toBe(foo.b & 1 ? true : false);
+ expect(foo2.c).toBe(foo.c & 1 ? true : false);
+
+ expect(foo2.bar).toEqual(foo.bar);
+ expect(foo2.data).toEqual(foo.data);
+
+ expect(foo2.extra_bars).toEqual(foo.extra_bars);
+ expect(foo2.name).toBe(foo.name);
+ expect(foo2.source).toEqual(foo.source);
+
+ expect(foo2.array_of_bools).toEqual(foo.array_of_bools);
+ }
+
+ function createRect(x, y, width, height) {
+ var r = new rect.Rect();
+ r.x = x;
+ r.y = y;
+ r.width = width;
+ r.height = height;
+ return r;
+ }
+
+ // Verify that the references to the imported Rect type in test_structs.mojom
+ // are generated correctly.
+ function testNamedRegion() {
+ var r = new structs.NamedRegion();
+ r.name = "rectangle";
+ r.rects = new Array(createRect(1, 2, 3, 4), createRect(10, 20, 30, 40));
+
+ var builder = new codec.MessageBuilder(1, structs.NamedRegion.encodedSize);
+ builder.encodeStruct(structs.NamedRegion, r);
+ var reader = new codec.MessageReader(builder.finish());
+ var result = reader.decodeStruct(structs.NamedRegion);
+
+ expect(result.name).toEqual("rectangle");
+ expect(result.rects[0]).toEqual(createRect(1, 2, 3, 4));
+ expect(result.rects[1]).toEqual(createRect(10, 20, 30, 40));
+ }
+
+ function testTypes() {
+ function encodeDecode(cls, input, expectedResult, encodedSize) {
+ var messageName = 42;
+ var payloadSize = encodedSize || cls.encodedSize;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(cls, input)
+ var message = builder.finish();
+
+ var reader = new codec.MessageReader(message);
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+ var result = reader.decodeStruct(cls);
+ expect(result).toEqual(expectedResult);
+ }
+ encodeDecode(codec.String, "banana", "banana", 24);
+ encodeDecode(codec.NullableString, null, null, 8);
+ encodeDecode(codec.Int8, -1, -1);
+ encodeDecode(codec.Int8, 0xff, -1);
+ encodeDecode(codec.Int16, -1, -1);
+ encodeDecode(codec.Int16, 0xff, 0xff);
+ encodeDecode(codec.Int16, 0xffff, -1);
+ encodeDecode(codec.Int32, -1, -1);
+ encodeDecode(codec.Int32, 0xffff, 0xffff);
+ encodeDecode(codec.Int32, 0xffffffff, -1);
+ encodeDecode(codec.Float, 1.0, 1.0);
+ encodeDecode(codec.Double, 1.0, 1.0);
+ }
+
+ function testAlign() {
+ var aligned = [
+ 0, // 0
+ 8, // 1
+ 8, // 2
+ 8, // 3
+ 8, // 4
+ 8, // 5
+ 8, // 6
+ 8, // 7
+ 8, // 8
+ 16, // 9
+ 16, // 10
+ 16, // 11
+ 16, // 12
+ 16, // 13
+ 16, // 14
+ 16, // 15
+ 16, // 16
+ 24, // 17
+ 24, // 18
+ 24, // 19
+ 24, // 20
+ ];
+ for (var i = 0; i < aligned.length; ++i)
+ expect(codec.align(i)).toBe(aligned[i]);
+ }
+
+ function testUtf8() {
+ var str = "B\u03ba\u1f79"; // some UCS-2 codepoints
+ var messageName = 42;
+ var payloadSize = 24;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ var encoder = builder.createEncoder(8);
+ encoder.encodeStringPointer(str);
+ var message = builder.finish();
+ var expectedMemory = new Uint8Array([
+ /* 0: */ 16, 0, 0, 0, 2, 0, 0, 0,
+ /* 8: */ 42, 0, 0, 0, 0, 0, 0, 0,
+ /* 16: */ 8, 0, 0, 0, 0, 0, 0, 0,
+ /* 24: */ 14, 0, 0, 0, 6, 0, 0, 0,
+ /* 32: */ 0x42, 0xCE, 0xBA, 0xE1, 0xBD, 0xB9, 0, 0,
+ ]);
+ var actualMemory = new Uint8Array(message.buffer.arrayBuffer);
+ expect(actualMemory.length).toEqual(expectedMemory.length);
+ expect(actualMemory).toEqual(expectedMemory);
+
+ var reader = new codec.MessageReader(message);
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+ var str2 = reader.decoder.decodeStringPointer();
+ expect(str2).toEqual(str);
+ }
+});
diff --git a/mojo/public/js/bindings/connection.js b/mojo/public/js/bindings/connection.js
new file mode 100644
index 0000000..31cf2aa
--- /dev/null
+++ b/mojo/public/js/bindings/connection.js
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/connection", [
+ "mojo/public/js/bindings/connector",
+ "mojo/public/js/bindings/router",
+], function(connector, router) {
+
+ function Connection(
+ handle, localFactory, remoteFactory, routerFactory, connectorFactory) {
+ if (routerFactory === undefined)
+ routerFactory = router.Router;
+ this.router_ = new routerFactory(handle, connectorFactory);
+ this.remote = new remoteFactory(this.router_);
+ this.local = new localFactory(this.remote);
+ this.router_.setIncomingReceiver(this.local);
+
+ var validateRequest = localFactory.prototype.validator;
+ var validateResponse = remoteFactory.prototype.validator;
+ var payloadValidators = [];
+ if (validateRequest)
+ payloadValidators.push(validateRequest);
+ if (validateResponse)
+ payloadValidators.push(validateResponse);
+ this.router_.setPayloadValidators(payloadValidators);
+ }
+
+ Connection.prototype.close = function() {
+ this.router_.close();
+ this.router_ = null;
+ this.local = null;
+ this.remote = null;
+ };
+
+ Connection.prototype.encounteredError = function() {
+ return this.router_.encounteredError();
+ };
+
+ // The TestConnection subclass is only intended to be used in unit tests.
+
+ function TestConnection(handle, localFactory, remoteFactory) {
+ Connection.call(this,
+ handle,
+ localFactory,
+ remoteFactory,
+ router.TestRouter,
+ connector.TestConnector);
+ }
+
+ TestConnection.prototype = Object.create(Connection.prototype);
+
+ var exports = {};
+ exports.Connection = Connection;
+ exports.TestConnection = TestConnection;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/connector.js b/mojo/public/js/bindings/connector.js
new file mode 100644
index 0000000..495896d
--- /dev/null
+++ b/mojo/public/js/bindings/connector.js
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/connector", [
+ "mojo/public/js/bindings/buffer",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/core",
+ "mojo/public/js/bindings/support",
+], function(buffer, codec, core, support) {
+
+ function Connector(handle) {
+ this.handle_ = handle;
+ this.dropWrites_ = false;
+ this.error_ = false;
+ this.incomingReceiver_ = null;
+ this.readWaitCookie_ = null;
+ this.errorHandler_ = null;
+
+ this.waitToReadMore_();
+ }
+
+ Connector.prototype.close = function() {
+ if (this.readWaitCookie_) {
+ support.cancelWait(this.readWaitCookie_);
+ this.readWaitCookie_ = null;
+ }
+ if (this.handle_ != null) {
+ core.close(this.handle_);
+ this.handle_ = null;
+ }
+ };
+
+ Connector.prototype.accept = function(message) {
+ if (this.error_)
+ return false;
+
+ if (this.dropWrites_)
+ return true;
+
+ var result = core.writeMessage(this.handle_,
+ new Uint8Array(message.buffer.arrayBuffer),
+ message.handles,
+ core.WRITE_MESSAGE_FLAG_NONE);
+ switch (result) {
+ case core.RESULT_OK:
+ // The handles were successfully transferred, so we don't own them
+ // anymore.
+ message.handles = [];
+ break;
+ case core.RESULT_FAILED_PRECONDITION:
+ // There's no point in continuing to write to this pipe since the other
+ // end is gone. Avoid writing any future messages. Hide write failures
+ // from the caller since we'd like them to continue consuming any
+ // backlog of incoming messages before regarding the message pipe as
+ // closed.
+ this.dropWrites_ = true;
+ break;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
+ }
+ return true;
+ };
+
+ Connector.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Connector.prototype.setErrorHandler = function(handler) {
+ this.errorHandler_ = handler;
+ };
+
+ Connector.prototype.encounteredError = function() {
+ return this.error_;
+ };
+
+ Connector.prototype.waitToReadMore_ = function() {
+ this.readWaitCookie_ = support.asyncWait(this.handle_,
+ core.HANDLE_SIGNAL_READABLE,
+ this.readMore_.bind(this));
+ };
+
+ Connector.prototype.readMore_ = function(result) {
+ for (;;) {
+ var read = core.readMessage(this.handle_,
+ core.READ_MESSAGE_FLAG_NONE);
+ if (read.result == core.RESULT_SHOULD_WAIT) {
+ this.waitToReadMore_();
+ return;
+ }
+ if (read.result != core.RESULT_OK) {
+ this.error_ = true;
+ if (this.errorHandler_)
+ this.errorHandler_.onError(read.result);
+ return;
+ }
+ var messageBuffer = new buffer.Buffer(read.buffer);
+ var message = new codec.Message(messageBuffer, read.handles);
+ if (this.incomingReceiver_) {
+ this.incomingReceiver_.accept(message);
+ }
+ }
+ };
+
+ // The TestConnector subclass is only intended to be used in unit tests. It
+ // enables delivering a message to the pipe's handle without an async wait.
+
+ function TestConnector(handle) {
+ Connector.call(this, handle);
+ }
+
+ TestConnector.prototype = Object.create(Connector.prototype);
+
+ TestConnector.prototype.waitToReadMore_ = function() {
+ };
+
+ TestConnector.prototype.deliverMessage = function() {
+ this.readMore_(core.RESULT_OK);
+ }
+
+ var exports = {};
+ exports.Connector = Connector;
+ exports.TestConnector = TestConnector;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/constants.cc b/mojo/public/js/bindings/constants.cc
new file mode 100644
index 0000000..547279d
--- /dev/null
+++ b/mojo/public/js/bindings/constants.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/js/bindings/constants.h"
+
+namespace mojo {
+
+const char kBufferModuleName[] = "mojo/public/js/bindings/buffer";
+const char kCodecModuleName[] = "mojo/public/js/bindings/codec";
+const char kConnectionModuleName[] = "mojo/public/js/bindings/connection";
+const char kConnectorModuleName[] = "mojo/public/js/bindings/connector";
+const char kUnicodeModuleName[] = "mojo/public/js/bindings/unicode";
+const char kRouterModuleName[] = "mojo/public/js/bindings/router";
+const char kValidatorModuleName[] = "mojo/public/js/bindings/validator";
+
+} // namespace mojo
diff --git a/mojo/public/js/bindings/constants.h b/mojo/public/js/bindings/constants.h
new file mode 100644
index 0000000..9927c8e
--- /dev/null
+++ b/mojo/public/js/bindings/constants.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
+#define MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
+
+namespace mojo {
+
+// JavaScript module names:
+extern const char kBufferModuleName[];
+extern const char kCodecModuleName[];
+extern const char kConnectionModuleName[];
+extern const char kConnectorModuleName[];
+extern const char kUnicodeModuleName[];
+extern const char kRouterModuleName[];
+extern const char kValidatorModuleName[];
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
diff --git a/mojo/public/js/bindings/core.js b/mojo/public/js/bindings/core.js
new file mode 100644
index 0000000..95d49a5
--- /dev/null
+++ b/mojo/public/js/bindings/core.js
@@ -0,0 +1,229 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Module "mojo/public/js/bindings/core"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+//
+// This module provides the JavaScript bindings for mojo/public/c/system/core.h.
+// Refer to that file for more detailed documentation for equivalent methods.
+
+while (1);
+
+/**
+ * MojoHandle: An opaque handles to a Mojo object (e.g. a message pipe).
+ */
+var kInvalidHandle;
+
+/**
+ * MojoResult {number}: Result codes for Mojo operations.
+ * See core.h for more information.
+ */
+var RESULT_OK;
+var RESULT_CANCELLED;
+var RESULT_UNKNOWN;
+var RESULT_INVALID_ARGUMENT;
+var RESULT_DEADLINE_EXCEEDED;
+var RESULT_NOT_FOUND;
+var RESULT_ALREADY_EXISTS;
+var RESULT_PERMISSION_DENIED;
+var RESULT_RESOURCE_EXHAUSTED;
+var RESULT_FAILED_PRECONDITION;
+var RESULT_ABORTED;
+var RESULT_OUT_OF_RANGE;
+var RESULT_UNIMPLEMENTED;
+var RESULT_INTERNAL;
+var RESULT_UNAVAILABLE;
+var RESULT_DATA_LOSS;
+var RESULT_BUSY;
+var RESULT_SHOULD_WAIT;
+
+/**
+ * MojoDeadline {number}: Used to specify deadlines (timeouts), in microseconds.
+ * See core.h for more information.
+ */
+var DEADLINE_INDEFINITE;
+
+/**
+ * MojoHandleSignals: Used to specify signals that can be waited on for a handle
+ *(and which can be triggered), e.g., the ability to read or write to
+ * the handle.
+ * See core.h for more information.
+ */
+var HANDLE_SIGNAL_NONE;
+var HANDLE_SIGNAL_READABLE;
+var HANDLE_SIGNAL_WRITABLE;
+
+/**
+ * MojoCreateDataMessageOptions: Used to specify creation parameters for a data
+ * pipe to |createDataMessage()|.
+ * See core.h for more information.
+ */
+dictionary MojoCreateDataMessageOptions {
+ MojoCreateDataMessageOptionsFlags flags; // See below.
+};
+
+// MojoCreateDataMessageOptionsFlags
+var CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE;
+
+/*
+ * MojoWriteMessageFlags: Used to specify different modes to |writeMessage()|.
+ * See core.h for more information.
+ */
+var WRITE_MESSAGE_FLAG_NONE;
+
+/**
+ * MojoReadMessageFlags: Used to specify different modes to |readMessage()|.
+ * See core.h for more information.
+ */
+var READ_MESSAGE_FLAG_NONE;
+var READ_MESSAGE_FLAG_MAY_DISCARD;
+
+/**
+ * MojoCreateDataPipeOptions: Used to specify creation parameters for a data
+ * pipe to |createDataPipe()|.
+ * See core.h for more information.
+ */
+dictionary MojoCreateDataPipeOptions {
+ MojoCreateDataPipeOptionsFlags flags; // See below.
+ int32 elementNumBytes; // The size of an element, in bytes.
+ int32 capacityNumBytes; // The capacity of the data pipe, in bytes.
+};
+
+// MojoCreateDataPipeOptionsFlags
+var CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+var CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD;
+
+/*
+ * MojoWriteDataFlags: Used to specify different modes to |writeData()|.
+ * See core.h for more information.
+ */
+var WRITE_DATA_FLAG_NONE;
+var WRITE_DATA_FLAG_ALL_OR_NONE;
+
+/**
+ * MojoReadDataFlags: Used to specify different modes to |readData()|.
+ * See core.h for more information.
+ */
+var READ_DATA_FLAG_NONE;
+var READ_DATA_FLAG_ALL_OR_NONE;
+var READ_DATA_FLAG_DISCARD;
+var READ_DATA_FLAG_QUERY;
+
+/**
+ * Closes the given |handle|. See MojoClose for more info.
+ * @param {MojoHandle} Handle to close.
+ * @return {MojoResult} Result code.
+ */
+function close(handle) { [native code] }
+
+/**
+ * Waits on the given handle until a signal indicated by |signals| is
+ * satisfied or until |deadline| is passed. See MojoWait for more information.
+ *
+ * @param {MojoHandle} handle Handle to wait on.
+ * @param {MojoHandleSignals} signals Specifies the condition to wait for.
+ * @param {MojoDeadline} deadline Stops waiting if this is reached.
+ * @return {MojoResult} Result code.
+ */
+function wait(handle, signals, deadline) { [native code] }
+
+/**
+ * Waits on |handles[0]|, ..., |handles[handles.length-1]| for at least one of
+ * them to satisfy the state indicated by |flags[0]|, ...,
+ * |flags[handles.length-1]|, respectively, or until |deadline| has passed.
+ * See MojoWaitMany for more information.
+ *
+ * @param {Array.MojoHandle} handles Handles to wait on.
+ * @param {Array.MojoHandleSignals} signals Specifies the condition to wait for,
+ * for each corresponding handle. Must be the same length as |handles|.
+ * @param {MojoDeadline} deadline Stops waiting if this is reached.
+ * @return {MojoResult} Result code.
+ */
+function waitMany(handles, signals, deadline) { [native code] }
+
+/**
+ * Creates a message pipe. This function always succeeds.
+ * See MojoCreateMessagePipe for more information on message pipes.
+ *
+ * @param {MojoCreateMessagePipeOptions} optionsDict Options to control the
+ * message pipe parameters. May be null.
+ * @return {MessagePipe} An object of the form {
+ * handle0,
+ * handle1,
+ * }
+ * where |handle0| and |handle1| are MojoHandles to each end of the channel.
+ */
+function createMessagePipe(optionsDict) { [native code] }
+
+/**
+ * Writes a message to the message pipe endpoint given by |handle|. See
+ * MojoWriteMessage for more information, including return codes.
+ *
+ * @param {MojoHandle} handle The endpoint to write to.
+ * @param {ArrayBufferView} buffer The message data. May be empty.
+ * @param {Array.MojoHandle} handlesArray Any handles to attach. Handles are
+ * transferred on success and will no longer be valid. May be empty.
+ * @param {MojoWriteMessageFlags} flags Flags.
+ * @return {MojoResult} Result code.
+ */
+function writeMessage(handle, buffer, handlesArray, flags) { [native code] }
+
+/**
+ * Reads a message from the message pipe endpoint given by |handle|. See
+ * MojoReadMessage for more information, including return codes.
+ *
+ * @param {MojoHandle} handle The endpoint to read from.
+ * @param {MojoReadMessageFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBufferView of the message data (only on success).
+ * handles // An array of MojoHandles transferred, if any.
+ * }
+ */
+function readMessage(handle, flags) { [native code] }
+
+/**
+ * Creates a data pipe, which is a unidirectional communication channel for
+ * unframed data, with the given options. See MojoCreateDataPipe for more
+ * more information, including return codes.
+ *
+ * @param {MojoCreateDataPipeOptions} optionsDict Options to control the data
+ * pipe parameters. May be null.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * producerHandle, // MojoHandle to use with writeData (only on success).
+ * consumerHandle, // MojoHandle to use with readData (only on success).
+ * }
+ */
+function createDataPipe(optionsDict) { [native code] }
+
+/**
+ * Writes the given data to the data pipe producer given by |handle|. See
+ * MojoWriteData for more information, including return codes.
+ *
+ * @param {MojoHandle} handle A producerHandle returned by createDataPipe.
+ * @param {ArrayBufferView} buffer The data to write.
+ * @param {MojoWriteDataFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * numBytes, // The number of bytes written.
+ * }
+ */
+function writeData(handle, buffer, flags) { [native code] }
+
+/**
+ * Reads data from the data pipe consumer given by |handle|. May also
+ * be used to discard data. See MojoReadData for more information, including
+ * return codes.
+ *
+ * @param {MojoHandle} handle A consumerHandle returned by createDataPipe.
+ * @param {MojoReadDataFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBufferView of the data read (only on success).
+ * }
+ */
+function readData(handle, flags) { [native code] }
diff --git a/mojo/public/js/bindings/core_unittests.js b/mojo/public/js/bindings/core_unittests.js
new file mode 100644
index 0000000..b115cc0
--- /dev/null
+++ b/mojo/public/js/bindings/core_unittests.js
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "gin/test/expect",
+ "mojo/public/js/bindings/core",
+ "gc",
+ ], function(expect, core, gc) {
+ runWithMessagePipe(testNop);
+ runWithMessagePipe(testReadAndWriteMessage);
+ runWithMessagePipeWithOptions(testNop);
+ runWithMessagePipeWithOptions(testReadAndWriteMessage);
+ runWithDataPipe(testNop);
+ runWithDataPipe(testReadAndWriteDataPipe);
+ runWithDataPipeWithOptions(testNop);
+ runWithDataPipeWithOptions(testReadAndWriteDataPipe);
+ gc.collectGarbage(); // should not crash
+ this.result = "PASS";
+
+ function runWithMessagePipe(test) {
+ var pipe = core.createMessagePipe();
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+ }
+
+ function runWithMessagePipeWithOptions(test) {
+ var pipe = core.createMessagePipe({
+ flags: core.CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE
+ });
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+ }
+
+ function runWithDataPipe(test) {
+ var pipe = core.createDataPipe();
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
+ }
+
+ function runWithDataPipeWithOptions(test) {
+ var pipe = core.createDataPipe({
+ flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ elementNumBytes: 1,
+ capacityNumBytes: 64
+ });
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
+ }
+
+ function testNop(pipe) {
+ }
+
+ function testReadAndWriteMessage(pipe) {
+ var senderData = new Uint8Array(42);
+ for (var i = 0; i < senderData.length; ++i) {
+ senderData[i] = i * i;
+ }
+
+ var result = core.writeMessage(
+ pipe.handle0, senderData, [],
+ core.WRITE_MESSAGE_FLAG_NONE);
+
+ expect(result).toBe(core.RESULT_OK);
+
+ var read = core.readMessage(
+ pipe.handle1, core.READ_MESSAGE_FLAG_NONE);
+
+ expect(read.result).toBe(core.RESULT_OK);
+ expect(read.buffer.byteLength).toBe(42);
+ expect(read.handles.length).toBe(0);
+
+ var memory = new Uint8Array(read.buffer);
+ for (var i = 0; i < memory.length; ++i)
+ expect(memory[i]).toBe((i * i) & 0xFF);
+ }
+
+ function testReadAndWriteDataPipe(pipe) {
+ var senderData = new Uint8Array(42);
+ for (var i = 0; i < senderData.length; ++i) {
+ senderData[i] = i * i;
+ }
+
+ var write = core.writeData(
+ pipe.producerHandle, senderData,
+ core.WRITE_DATA_FLAG_ALL_OR_NONE);
+
+ expect(write.result).toBe(core.RESULT_OK);
+ expect(write.numBytes).toBe(42);
+
+ var read = core.readData(
+ pipe.consumerHandle, core.READ_DATA_FLAG_ALL_OR_NONE);
+
+ expect(read.result).toBe(core.RESULT_OK);
+ expect(read.buffer.byteLength).toBe(42);
+
+ var memory = new Uint8Array(read.buffer);
+ for (var i = 0; i < memory.length; ++i)
+ expect(memory[i]).toBe((i * i) & 0xFF);
+ }
+
+});
diff --git a/mojo/public/js/bindings/router.js b/mojo/public/js/bindings/router.js
new file mode 100644
index 0000000..3682392
--- /dev/null
+++ b/mojo/public/js/bindings/router.js
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/router", [
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/connector",
+ "mojo/public/js/bindings/validator",
+], function(codec, connector, validator) {
+
+ function Router(handle, connectorFactory) {
+ if (connectorFactory === undefined)
+ connectorFactory = connector.Connector;
+ this.connector_ = new connectorFactory(handle);
+ this.incomingReceiver_ = null;
+ this.nextRequestID_ = 0;
+ this.responders_ = {};
+ this.payloadValidators_ = [];
+
+ this.connector_.setIncomingReceiver({
+ accept: this.handleIncomingMessage_.bind(this),
+ });
+ this.connector_.setErrorHandler({
+ onError: this.handleConnectionError_.bind(this),
+ });
+ }
+
+ Router.prototype.close = function() {
+ this.responders_ = {}; // Drop any responders.
+ this.connector_.close();
+ };
+
+ Router.prototype.accept = function(message) {
+ this.connector_.accept(message);
+ };
+
+ Router.prototype.reject = function(message) {
+ // TODO(mpcomplete): no way to trasmit errors over a Connection.
+ };
+
+ Router.prototype.acceptWithResponder = function(message, responder) {
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ var requestID = this.nextRequestID_++;
+ if (requestID == 0)
+ requestID = this.nextRequestID_++;
+
+ message.setRequestID(requestID);
+ var result = this.connector_.accept(message);
+
+ this.responders_[requestID] = responder;
+
+ // TODO(mpcomplete): accept should return a Promise too, maybe?
+ if (result)
+ return Promise.resolve();
+ return Promise.reject(Error("Connection error"));
+ };
+
+ Router.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Router.prototype.setPayloadValidators = function(payloadValidators) {
+ this.payloadValidators_ = payloadValidators;
+ };
+
+ Router.prototype.encounteredError = function() {
+ return this.connector_.encounteredError();
+ };
+
+ Router.prototype.handleIncomingMessage_ = function(message) {
+ var noError = validator.validationError.NONE;
+ var messageValidator = new validator.Validator(message);
+ var err = messageValidator.validateMessageHeader();
+ for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
+ err = this.payloadValidators_[i](messageValidator);
+
+ if (err == noError)
+ this.handleValidIncomingMessage_(message);
+ else
+ this.handleInvalidIncomingMessage_(message, err);
+ };
+
+ Router.prototype.handleValidIncomingMessage_ = function(message) {
+ if (message.expectsResponse()) {
+ if (this.incomingReceiver_) {
+ this.incomingReceiver_.acceptWithResponder(message, this);
+ } else {
+ // If we receive a request expecting a response when the client is not
+ // listening, then we have no choice but to tear down the pipe.
+ this.close();
+ }
+ } else if (message.isResponse()) {
+ var reader = new codec.MessageReader(message);
+ var requestID = reader.requestID;
+ var responder = this.responders_[requestID];
+ delete this.responders_[requestID];
+ responder.accept(message);
+ } else {
+ if (this.incomingReceiver_)
+ this.incomingReceiver_.accept(message);
+ }
+ }
+
+ Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
+ this.close();
+ }
+
+ Router.prototype.handleConnectionError_ = function(result) {
+ for (var each in this.responders_)
+ this.responders_[each].reject(result);
+ this.close();
+ };
+
+ // The TestRouter subclass is only intended to be used in unit tests.
+ // It defeats valid message handling and delgates invalid message handling.
+
+ function TestRouter(handle, connectorFactory) {
+ Router.call(this, handle, connectorFactory);
+ }
+
+ TestRouter.prototype = Object.create(Router.prototype);
+
+ TestRouter.prototype.handleValidIncomingMessage_ = function() {
+ };
+
+ TestRouter.prototype.handleInvalidIncomingMessage_ =
+ function(message, error) {
+ this.validationErrorHandler(error);
+ };
+
+ var exports = {};
+ exports.Router = Router;
+ exports.TestRouter = TestRouter;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/struct_unittests.js b/mojo/public/js/bindings/struct_unittests.js
new file mode 100644
index 0000000..b9948a9
--- /dev/null
+++ b/mojo/public/js/bindings/struct_unittests.js
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "gin/test/expect",
+ "mojo/public/interfaces/bindings/tests/rect.mojom",
+ "mojo/public/interfaces/bindings/tests/test_structs.mojom"
+], function(expect,
+ rect,
+ testStructs) {
+
+ function testConstructors() {
+ var r = new rect.Rect();
+ expect(r).toEqual(new rect.Rect({x:0, y:0, width:0, height:0}));
+ expect(r).toEqual(new rect.Rect({foo:100, bar:200}));
+
+ r.x = 10;
+ r.y = 20;
+ r.width = 30;
+ r.height = 40;
+ var rp = new testStructs.RectPair({first: r, second: r});
+ expect(rp.first).toEqual(r);
+ expect(rp.second).toEqual(r);
+
+ expect(new testStructs.RectPair({second: r}).first).toBeNull();
+
+ var nr = new testStructs.NamedRegion();
+ expect(nr.name).toBeNull();
+ expect(nr.rects).toBeNull();
+ expect(nr).toEqual(new testStructs.NamedRegion({}));
+
+ nr.name = "foo";
+ nr.rects = [r, r, r];
+ expect(nr).toEqual(new testStructs.NamedRegion({
+ name: "foo",
+ rects: [r, r, r],
+ }));
+
+ var e = new testStructs.EmptyStruct();
+ expect(e).toEqual(new testStructs.EmptyStruct({foo:123}));
+ }
+
+ function testNoDefaultFieldValues() {
+ var s = new testStructs.NoDefaultFieldValues();
+ expect(s.f0).toEqual(false);
+
+ // f1 - f10, number type fields
+ for (var i = 1; i <= 10; i++)
+ expect(s["f" + i]).toEqual(0);
+
+ // f11,12 strings, f13-22 handles, f23-f26 arrays, f27,28 structs
+ for (var i = 11; i <= 28; i++)
+ expect(s["f" + i]).toBeNull();
+ }
+
+ function testDefaultFieldValues() {
+ var s = new testStructs.DefaultFieldValues();
+ expect(s.f0).toEqual(true);
+
+ // f1 - f12, number type fields
+ for (var i = 1; i <= 12; i++)
+ expect(s["f" + i]).toEqual(100);
+
+ // f13,14 "foo"
+ for (var i = 13; i <= 14; i++)
+ expect(s["f" + i]).toEqual("foo");
+
+ // f15,16 a default instance of Rect
+ var r = new rect.Rect();
+ expect(s.f15).toEqual(r);
+ expect(s.f16).toEqual(r);
+ }
+
+ testConstructors();
+ testNoDefaultFieldValues();
+ testDefaultFieldValues();
+ this.result = "PASS";
+});
\ No newline at end of file
diff --git a/mojo/public/js/bindings/support.js b/mojo/public/js/bindings/support.js
new file mode 100644
index 0000000..58df6bd
--- /dev/null
+++ b/mojo/public/js/bindings/support.js
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Module "mojo/public/js/bindings/support"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+
+while (1);
+
+/*
+ * Waits on the given handle until the state indicated by |signals| is
+ * satisfied.
+ *
+ * @param {MojoHandle} handle The handle to wait on.
+ * @param {MojoHandleSignals} signals Specifies the condition to wait for.
+ * @param {function (mojoResult)} callback Called with the result the wait is
+ * complete. See MojoWait for possible result codes.
+ *
+ * @return {MojoWaitId} A waitId that can be passed to cancelWait to cancel the
+ * wait.
+ */
+function asyncWait(handle, signals, callback) { [native code] }
+
+/*
+ * Cancels the asyncWait operation specified by the given |waitId|.
+ * @param {MojoWaitId} waitId The waitId returned by asyncWait.
+ */
+function cancelWait(waitId) { [native code] }
diff --git a/mojo/public/js/bindings/tests/validation_test_input_parser.js b/mojo/public/js/bindings/tests/validation_test_input_parser.js
new file mode 100644
index 0000000..98b1c19
--- /dev/null
+++ b/mojo/public/js/bindings/tests/validation_test_input_parser.js
@@ -0,0 +1,299 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Support for parsing binary sequences encoded as readable strings
+// or ".data" files. The input format is described here:
+// mojo/public/cpp/bindings/tests/validation_test_input_parser.h
+
+define([
+ "mojo/public/js/bindings/buffer"
+ ], function(buffer) {
+
+ // Files and Lines represent the raw text from an input string
+ // or ".data" file.
+
+ function InputError(message, line) {
+ this.message = message;
+ this.line = line;
+ }
+
+ InputError.prototype.toString = function() {
+ var s = 'Error: ' + this.message;
+ if (this.line)
+ s += ', at line ' +
+ (this.line.number + 1) + ': "' + this.line.contents + '"';
+ return s;
+ }
+
+ function File(contents) {
+ this.contents = contents;
+ this.index = 0;
+ this.lineNumber = 0;
+ }
+
+ File.prototype.endReached = function() {
+ return this.index >= this.contents.length;
+ }
+
+ File.prototype.nextLine = function() {
+ if (this.endReached())
+ return null;
+ var start = this.index;
+ var end = this.contents.indexOf('\n', start);
+ if (end == -1)
+ end = this.contents.length;
+ this.index = end + 1;
+ return new Line(this.contents.substring(start, end), this.lineNumber++);
+ }
+
+ function Line(contents, number) {
+ var i = contents.indexOf('//');
+ var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim();
+ this.contents = contents;
+ this.items = (s.length > 0) ? s.split(/\s+/) : [];
+ this.index = 0;
+ this.number = number;
+ }
+
+ Line.prototype.endReached = function() {
+ return this.index >= this.items.length;
+ }
+
+ var ITEM_TYPE_SIZES = {
+ u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8,
+ dist4: 4, dist8: 8, anchr: 0, handles: 0
+ };
+
+ function isValidItemType(type) {
+ return ITEM_TYPE_SIZES[type] !== undefined;
+ }
+
+ Line.prototype.nextItem = function() {
+ if (this.endReached())
+ return null;
+
+ var itemString = this.items[this.index++];
+ var type = 'u1';
+ var value = itemString;
+
+ if (itemString.charAt(0) == '[') {
+ var i = itemString.indexOf(']');
+ if (i != -1 && i + 1 < itemString.length) {
+ type = itemString.substring(1, i);
+ value = itemString.substring(i + 1);
+ } else {
+ throw new InputError('invalid item', this);
+ }
+ }
+ if (!isValidItemType(type))
+ throw new InputError('invalid item type', this);
+
+ return new Item(this, type, value);
+ }
+
+ // The text for each whitespace delimited binary data "item" is represented
+ // by an Item.
+
+ function Item(line, type, value) {
+ this.line = line;
+ this.type = type;
+ this.value = value;
+ this.size = ITEM_TYPE_SIZES[type];
+ }
+
+ Item.prototype.isFloat = function() {
+ return this.type == 'f' || this.type == 'd';
+ }
+
+ Item.prototype.isInteger = function() {
+ return ['u1', 'u2', 'u4', 'u8',
+ 's1', 's2', 's4', 's8'].indexOf(this.type) != -1;
+ }
+
+ Item.prototype.isNumber = function() {
+ return this.isFloat() || this.isInteger();
+ }
+
+ Item.prototype.isByte = function() {
+ return this.type == 'b';
+ }
+
+ Item.prototype.isDistance = function() {
+ return this.type == 'dist4' || this.type == 'dist8';
+ }
+
+ Item.prototype.isAnchor = function() {
+ return this.type == 'anchr';
+ }
+
+ Item.prototype.isHandles = function() {
+ return this.type == 'handles';
+ }
+
+ // A TestMessage represents the complete binary message loaded from an input
+ // string or ".data" file. The parseTestMessage() function below constructs
+ // a TestMessage from a File.
+
+ function TestMessage(byteLength) {
+ this.index = 0;
+ this.buffer = new buffer.Buffer(byteLength);
+ this.distances = {};
+ this.handleCount = 0;
+ }
+
+ function checkItemNumberValue(item, n, min, max) {
+ if (n < min || n > max)
+ throw new InputError('invalid item value', item.line);
+ }
+
+ TestMessage.prototype.addNumber = function(item) {
+ var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value);
+ if (Number.isNaN(n))
+ throw new InputError("can't parse item value", item.line);
+
+ switch(item.type) {
+ case 'u1':
+ checkItemNumberValue(item, n, 0, 0xFF);
+ this.buffer.setUint8(this.index, n);
+ break;
+ case 'u2':
+ checkItemNumberValue(item, n, 0, 0xFFFF);
+ this.buffer.setUint16(this.index, n);
+ break;
+ case 'u4':
+ checkItemNumberValue(item, n, 0, 0xFFFFFFFF);
+ this.buffer.setUint32(this.index, n);
+ break;
+ case 'u8':
+ checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER);
+ this.buffer.setUint64(this.index, n);
+ break;
+ case 's1':
+ checkItemNumberValue(item, n, -128, 127);
+ this.buffer.setInt8(this.index, n);
+ break;
+ case 's2':
+ checkItemNumberValue(item, n, -32768, 32767);
+ this.buffer.setInt16(this.index, n);
+ break;
+ case 's4':
+ checkItemNumberValue(item, n, -2147483648, 2147483647);
+ this.buffer.setInt32(this.index, n);
+ break;
+ case 's8':
+ checkItemNumberValue(item, n,
+ Number.MIN_SAFE_INTEGER,
+ Number.MAX_SAFE_INTEGER);
+ this.buffer.setInt64(this.index, n);
+ break;
+ case 'f':
+ this.buffer.setFloat32(this.index, n);
+ break;
+ case 'd':
+ this.buffer.setFloat64(this.index, n);
+ break;
+
+ default:
+ throw new InputError('unrecognized item type', item.line);
+ }
+ }
+
+ TestMessage.prototype.addByte = function(item) {
+ if (!/^[01]{8}$/.test(item.value))
+ throw new InputError('invalid byte item value', item.line);
+ function b(i) {
+ return (item.value.charAt(7 - i) == '1') ? 1 << i : 0;
+ }
+ var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7);
+ this.buffer.setUint8(this.index, n);
+ }
+
+ TestMessage.prototype.addDistance = function(item) {
+ if (this.distances[item.value])
+ throw new InputError('duplicate distance item', item.line);
+ this.distances[item.value] = {index: this.index, item: item};
+ }
+
+ TestMessage.prototype.addAnchor = function(item) {
+ var dist = this.distances[item.value];
+ if (!dist)
+ throw new InputError('unmatched anchor item', item.line);
+ delete this.distances[item.value];
+
+ var n = this.index - dist.index;
+ // TODO(hansmuller): validate n
+
+ if (dist.item.type == 'dist4')
+ this.buffer.setUint32(dist.index, n);
+ else if (dist.item.type == 'dist8')
+ this.buffer.setUint64(dist.index, n);
+ else
+ throw new InputError('unrecognzed distance item type', dist.item.line);
+ }
+
+ TestMessage.prototype.addHandles = function(item) {
+ this.handleCount = parseInt(item.value);
+ if (Number.isNaN(this.handleCount))
+ throw new InputError("can't parse handleCount", item.line);
+ }
+
+ TestMessage.prototype.addItem = function(item) {
+ if (item.isNumber())
+ this.addNumber(item);
+ else if (item.isByte())
+ this.addByte(item);
+ else if (item.isDistance())
+ this.addDistance(item);
+ else if (item.isAnchor())
+ this.addAnchor(item);
+ else if (item.isHandles())
+ this.addHandles(item);
+ else
+ throw new InputError('unrecognized item type', item.line);
+
+ this.index += item.size;
+ }
+
+ TestMessage.prototype.unanchoredDistances = function() {
+ var names = null;
+ for (var name in this.distances) {
+ if (this.distances.hasOwnProperty(name))
+ names = (names === null) ? name : names + ' ' + name;
+ }
+ return names;
+ }
+
+ function parseTestMessage(text) {
+ var file = new File(text);
+ var items = [];
+ var messageLength = 0;
+ while(!file.endReached()) {
+ var line = file.nextLine();
+ while (!line.endReached()) {
+ var item = line.nextItem();
+ if (item.isHandles() && items.length > 0)
+ throw new InputError('handles item is not first');
+ messageLength += item.size;
+ items.push(item);
+ }
+ }
+
+ var msg = new TestMessage(messageLength);
+ for (var i = 0; i < items.length; i++)
+ msg.addItem(items[i]);
+
+ if (messageLength != msg.index)
+ throw new InputError('failed to compute message length');
+ var names = msg.unanchoredDistances();
+ if (names)
+ throw new InputError('no anchors for ' + names, 0);
+
+ return msg;
+ }
+
+ var exports = {};
+ exports.parseTestMessage = parseTestMessage;
+ exports.InputError = InputError;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/unicode.js b/mojo/public/js/bindings/unicode.js
new file mode 100644
index 0000000..ba0f00f
--- /dev/null
+++ b/mojo/public/js/bindings/unicode.js
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Defines functions for translating between JavaScript strings and UTF8 strings
+ * stored in ArrayBuffers. There is much room for optimization in this code if
+ * it proves necessary.
+ */
+define("mojo/public/js/bindings/unicode", function() {
+ /**
+ * Decodes the UTF8 string from the given buffer.
+ * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
+ * @return {string} The corresponding JavaScript string.
+ */
+ function decodeUtf8String(buffer) {
+ return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer)));
+ }
+
+ /**
+ * Encodes the given JavaScript string into UTF8.
+ * @param {string} str The string to encode.
+ * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
+ * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
+ * how much space is required.
+ * @return {number} The number of bytes written to |outputBuffer|.
+ */
+ function encodeUtf8String(str, outputBuffer) {
+ var utf8String = unescape(encodeURIComponent(str));
+ if (outputBuffer.length < utf8String.length)
+ throw new Error("Buffer too small for encodeUtf8String");
+ for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++)
+ outputBuffer[i] = utf8String.charCodeAt(i);
+ return i;
+ }
+
+ /**
+ * Returns the number of bytes that a UTF8 encoding of the JavaScript string
+ * |str| would occupy.
+ */
+ function utf8Length(str) {
+ var utf8String = unescape(encodeURIComponent(str));
+ return utf8String.length;
+ }
+
+ var exports = {};
+ exports.decodeUtf8String = decodeUtf8String;
+ exports.encodeUtf8String = encodeUtf8String;
+ exports.utf8Length = utf8Length;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/validation_unittests.js b/mojo/public/js/bindings/validation_unittests.js
new file mode 100644
index 0000000..73fd9c7
--- /dev/null
+++ b/mojo/public/js/bindings/validation_unittests.js
@@ -0,0 +1,302 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "console",
+ "file",
+ "gin/test/expect",
+ "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom",
+ "mojo/public/js/bindings/buffer",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/connection",
+ "mojo/public/js/bindings/connector",
+ "mojo/public/js/bindings/core",
+ "mojo/public/js/bindings/tests/validation_test_input_parser",
+ "mojo/public/js/bindings/router",
+ "mojo/public/js/bindings/validator",
+], function(console,
+ file,
+ expect,
+ testInterface,
+ buffer,
+ codec,
+ connection,
+ connector,
+ core,
+ parser,
+ router,
+ validator) {
+
+ var noError = validator.validationError.NONE;
+
+ function checkTestMessageParser() {
+ function TestMessageParserFailure(message, input) {
+ this.message = message;
+ this.input = input;
+ }
+
+ TestMessageParserFailure.prototype.toString = function() {
+ return 'Error: ' + this.message + ' for "' + this.input + '"';
+ }
+
+ function checkData(data, expectedData, input) {
+ if (data.byteLength != expectedData.byteLength) {
+ var s = "message length (" + data.byteLength + ") doesn't match " +
+ "expected length: " + expectedData.byteLength;
+ throw new TestMessageParserFailure(s, input);
+ }
+
+ for (var i = 0; i < data.byteLength; i++) {
+ if (data.getUint8(i) != expectedData.getUint8(i)) {
+ var s = 'message data mismatch at byte offset ' + i;
+ throw new TestMessageParserFailure(s, input);
+ }
+ }
+ }
+
+ function testFloatItems() {
+ var input = '[f]+.3e9 [d]-10.03';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(12);
+ expectedData.setFloat32(0, +.3e9);
+ expectedData.setFloat64(4, -10.03);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testUnsignedIntegerItems() {
+ var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' +
+ '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(17);
+ expectedData.setUint8(0, 0x10);
+ expectedData.setUint16(1, 65535);
+ expectedData.setUint32(3, 65536);
+ expectedData.setUint64(7, 0xFFFFFFFFFFFFF);
+ expectedData.setUint8(15, 0);
+ expectedData.setUint8(16, 0xff);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testSignedIntegerItems() {
+ var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(15);
+ expectedData.setInt64(0, -0x800);
+ expectedData.setInt8(8, -128);
+ expectedData.setInt16(9, 0);
+ expectedData.setInt32(11, -40);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testByteItems() {
+ var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(3);
+ expectedData.setUint8(0, 11);
+ expectedData.setUint8(1, 128);
+ expectedData.setUint8(2, 0);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testAnchors() {
+ var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(14);
+ expectedData.setUint32(0, 14);
+ expectedData.setUint8(4, 0);
+ expectedData.setUint64(5, 9);
+ expectedData.setUint8(13, 0);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testHandles() {
+ var input = '// This message has handles! \n[handles]50 [u8]2';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(8);
+ expectedData.setUint64(0, 2);
+
+ if (msg.handleCount != 50) {
+ var s = 'wrong handle count (' + msg.handleCount + ')';
+ throw new TestMessageParserFailure(s, input);
+ }
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testEmptyInput() {
+ var msg = parser.parseTestMessage('');
+ if (msg.buffer.byteLength != 0)
+ throw new TestMessageParserFailure('expected empty message', '');
+ }
+
+ function testBlankInput() {
+ var input = ' \t // hello world \n\r \t// the answer is 42 ';
+ var msg = parser.parseTestMessage(input);
+ if (msg.buffer.byteLength != 0)
+ throw new TestMessageParserFailure('expected empty message', input);
+ }
+
+ function testInvalidInput() {
+ function parserShouldFail(input) {
+ try {
+ parser.parseTestMessage(input);
+ } catch (e) {
+ if (e instanceof parser.InputError)
+ return;
+ throw new TestMessageParserFailure(
+ 'unexpected exception ' + e.toString(), input);
+ }
+ throw new TestMessageParserFailure("didn't detect invalid input", file);
+ }
+
+ ['/ hello world',
+ '[u1]x',
+ '[u2]-1000',
+ '[u1]0x100',
+ '[s2]-0x8001',
+ '[b]1',
+ '[b]1111111k',
+ '[dist4]unmatched',
+ '[anchr]hello [dist8]hello',
+ '[dist4]a [dist4]a [anchr]a',
+ // '[dist4]a [anchr]a [dist4]a [anchr]a',
+ '0 [handles]50'
+ ].forEach(parserShouldFail);
+ }
+
+ try {
+ testFloatItems();
+ testUnsignedIntegerItems();
+ testSignedIntegerItems();
+ testByteItems();
+ testInvalidInput();
+ testEmptyInput();
+ testBlankInput();
+ testHandles();
+ testAnchors();
+ } catch (e) {
+ return e.toString();
+ }
+ return null;
+ }
+
+ function getMessageTestFiles(key) {
+ var sourceRoot = file.getSourceRootDirectory();
+ expect(sourceRoot).not.toBeNull();
+
+ var testDir = sourceRoot +
+ "/mojo/public/interfaces/bindings/tests/data/validation/";
+ var testFiles = file.getFilesInDirectory(testDir);
+ expect(testFiles).not.toBeNull();
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ // The matching ".data" pathnames with the extension removed.
+ return testFiles.filter(function(s) {
+ return s.substr(-5) == ".data";
+ }).map(function(s) {
+ return testDir + s.slice(0, -5);
+ }).filter(function(s) {
+ return s.indexOf(key) != -1;
+ });
+ }
+
+ function readTestMessage(filename) {
+ var contents = file.readFileToString(filename + ".data");
+ expect(contents).not.toBeNull();
+ return parser.parseTestMessage(contents);
+ }
+
+ function readTestExpected(filename) {
+ var contents = file.readFileToString(filename + ".expected");
+ expect(contents).not.toBeNull();
+ return contents.trim();
+ }
+
+ function checkValidationResult(testFile, err) {
+ var actualResult = (err === noError) ? "PASS" : err;
+ var expectedResult = readTestExpected(testFile);
+ if (actualResult != expectedResult)
+ console.log("[Test message validation failed: " + testFile + " ]");
+ expect(actualResult).toEqual(expectedResult);
+ }
+
+ function testMessageValidation(key, filters) {
+ var testFiles = getMessageTestFiles(key);
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ for (var i = 0; i < testFiles.length; i++) {
+ // TODO(hansmuller): Temporarily skipping array pointer overflow tests.
+ if (testFiles[i].indexOf("overflow") != -1) {
+ console.log("[Skipping " + testFiles[i] + "]");
+ continue;
+ }
+
+ var testMessage = readTestMessage(testFiles[i]);
+ var handles = new Array(testMessage.handleCount);
+ var message = new codec.Message(testMessage.buffer, handles);
+ var messageValidator = new validator.Validator(message);
+
+ var err = messageValidator.validateMessageHeader();
+ for (var j = 0; err === noError && j < filters.length; ++j)
+ err = filters[j](messageValidator);
+
+ checkValidationResult(testFiles[i], err);
+ }
+ }
+
+ function testConformanceMessageValidation() {
+ testMessageValidation("conformance_", [
+ testInterface.ConformanceTestInterfaceStub.prototype.validator]);
+ }
+
+ function testNotImplementedMessageValidation() {
+ testMessageValidation("not_implemented_", [
+ testInterface.ConformanceTestInterfaceStub.prototype.validator]);
+ }
+
+ function testIntegratedMessageValidation() {
+ var testFiles = getMessageTestFiles("integration_");
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ for (var i = 0; i < testFiles.length; i++) {
+ // TODO(hansmuller): Temporarily skipping array pointer overflow tests.
+ if (testFiles[i].indexOf("overflow") != -1) {
+ console.log("[Skipping " + testFiles[i] + "]");
+ continue;
+ }
+
+ var testMessage = readTestMessage(testFiles[i]);
+ var handles = new Array(testMessage.handleCount);
+ var testMessagePipe = new core.createMessagePipe();
+ expect(testMessagePipe.result).toBe(core.RESULT_OK);
+
+ var writeMessageValue = core.writeMessage(
+ testMessagePipe.handle0,
+ new Uint8Array(testMessage.buffer.arrayBuffer),
+ new Array(testMessage.handleCount),
+ core.WRITE_MESSAGE_FLAG_NONE);
+ expect(writeMessageValue).toBe(core.RESULT_OK);
+
+ var testConnection = new connection.TestConnection(
+ testMessagePipe.handle1,
+ testInterface.IntegrationTestInterface1Stub,
+ testInterface.IntegrationTestInterface2Proxy);
+
+ var validationError = noError;
+ testConnection.router_.validationErrorHandler = function(err) {
+ validationError = err;
+ }
+
+ testConnection.router_.connector_.deliverMessage();
+ checkValidationResult(testFiles[i], validationError);
+
+ testConnection.close();
+ expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
+ }
+ }
+
+ expect(checkTestMessageParser()).toBeNull();
+ testConformanceMessageValidation();
+ testIntegratedMessageValidation();
+ this.result = "PASS";
+});
diff --git a/mojo/public/js/bindings/validator.js b/mojo/public/js/bindings/validator.js
new file mode 100644
index 0000000..de75656
--- /dev/null
+++ b/mojo/public/js/bindings/validator.js
@@ -0,0 +1,291 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/validator", [
+ "mojo/public/js/bindings/codec",
+], function(codec) {
+
+ var validationError = {
+ NONE: 'VALIDATION_ERROR_NONE',
+ MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
+ ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
+ UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
+ UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
+ ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
+ UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
+ ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
+ UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
+ MESSAGE_HEADER_INVALID_FLAG_COMBINATION:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION',
+ MESSAGE_HEADER_MISSING_REQUEST_ID:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID'
+ };
+
+ var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
+
+ function isStringClass(cls) {
+ return cls === codec.String || cls === codec.NullableString;
+ }
+
+ function isHandleClass(cls) {
+ return cls === codec.Handle || cls === codec.NullableHandle;
+ }
+
+ function isNullable(type) {
+ return type === codec.NullableString || type === codec.NullableHandle ||
+ type instanceof codec.NullableArrayOf ||
+ type instanceof codec.NullablePointerTo;
+ }
+
+ function Validator(message) {
+ this.message = message;
+ this.offset = 0;
+ this.handleIndex = 0;
+ }
+
+ Object.defineProperty(Validator.prototype, "offsetLimit", {
+ get: function() { return this.message.buffer.byteLength; }
+ });
+
+ Object.defineProperty(Validator.prototype, "handleIndexLimit", {
+ get: function() { return this.message.handles.length; }
+ });
+
+ // True if we can safely allocate a block of bytes from start to
+ // to start + numBytes.
+ Validator.prototype.isValidRange = function(start, numBytes) {
+ // Only positive JavaScript integers that are less than 2^53
+ // (Number.MAX_SAFE_INTEGER) can be represented exactly.
+ if (start < this.offset || numBytes <= 0 ||
+ !Number.isSafeInteger(start) ||
+ !Number.isSafeInteger(numBytes))
+ return false;
+
+ var newOffset = start + numBytes;
+ if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
+ return false;
+
+ return true;
+ }
+
+ Validator.prototype.claimRange = function(start, numBytes) {
+ if (this.isValidRange(start, numBytes)) {
+ this.offset = start + numBytes;
+ return true;
+ }
+ return false;
+ }
+
+ Validator.prototype.claimHandle = function(index) {
+ if (index === codec.kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < this.handleIndex || index >= this.handleIndexLimit)
+ return false;
+
+ // This is safe because handle indices are uint32.
+ this.handleIndex = index + 1;
+ return true;
+ }
+
+ Validator.prototype.validateHandle = function(offset, nullable) {
+ var index = this.message.buffer.getUint32(offset);
+
+ if (index === codec.kEncodedInvalidHandleValue)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
+
+ if (!this.claimHandle(index))
+ return validationError.ILLEGAL_HANDLE;
+ return validationError.NONE;
+ }
+
+ Validator.prototype.validateStructHeader =
+ function(offset, minNumBytes, minNumFields) {
+ if (!codec.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, codec.kStructHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numFields = this.message.buffer.getUint32(offset + 4);
+
+ if (numBytes < minNumBytes || numFields < minNumFields)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ return validationError.NONE;
+ }
+
+ Validator.prototype.validateMessageHeader = function() {
+ var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 2);
+ if (err != validationError.NONE)
+ return err;
+
+ var numBytes = this.message.getHeaderNumBytes();
+ var numFields = this.message.getHeaderNumFields();
+
+ var validNumFieldsAndNumBytes =
+ (numFields == 2 && numBytes == codec.kMessageHeaderSize) ||
+ (numFields == 3 &&
+ numBytes == codec.kMessageWithRequestIDHeaderSize) ||
+ (numFields > 3 &&
+ numBytes >= codec.kMessageWithRequestIDHeaderSize);
+ if (!validNumFieldsAndNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ var expectsResponse = this.message.expectsResponse();
+ var isResponse = this.message.isResponse();
+
+ if (numFields == 2 && (expectsResponse || isResponse))
+ return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
+
+ if (isResponse && expectsResponse)
+ return validationError.MESSAGE_HEADER_INVALID_FLAG_COMBINATION;
+
+ return validationError.NONE;
+ }
+
+ // Returns the message.buffer relative offset this pointer "points to",
+ // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
+ // pointer's value is not valid.
+ Validator.prototype.decodePointer = function(offset) {
+ var pointerValue = this.message.buffer.getUint64(offset);
+ if (pointerValue === 0)
+ return NULL_MOJO_POINTER;
+ var bufferOffset = offset + pointerValue;
+ return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
+ }
+
+ Validator.prototype.validateArrayPointer = function(
+ offset, elementSize, expectedElementCount, elementType, nullable) {
+ var arrayOffset = this.decodePointer(offset);
+ if (arrayOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (arrayOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return this.validateArray(
+ arrayOffset, elementSize, expectedElementCount, elementType);
+ }
+
+ Validator.prototype.validateStructPointer = function(
+ offset, structClass, nullable) {
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return structClass.validate(this, structOffset);
+ }
+
+ Validator.prototype.validateStringPointer = function(offset, nullable) {
+ return this.validateArrayPointer(
+ offset, codec.Uint8.encodedSize, 0, codec.Uint8, nullable);
+ }
+
+ // Similar to Array_Data<T>::Validate()
+ // mojo/public/cpp/bindings/lib/array_internal.h
+
+ Validator.prototype.validateArray =
+ function (offset, elementSize, expectedElementCount, elementType) {
+ if (!codec.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, codec.kArrayHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numElements = this.message.buffer.getUint32(offset + 4);
+
+ // Note: this computation is "safe" because elementSize <= 8 and
+ // numElements is a uint32.
+ var elementsTotalSize = (elementType === codec.PackedBool) ?
+ Math.ceil(numElements / 8) : (elementSize * numElements);
+
+ if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (expectedElementCount != 0 && numElements != expectedElementCount)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ // Validate the array's elements if they are pointers or handles.
+
+ var elementsOffset = offset + codec.kArrayHeaderSize;
+ var nullable = isNullable(elementType);
+
+ if (isHandleClass(elementType))
+ return this.validateHandleElements(elementsOffset, numElements, nullable);
+ if (isStringClass(elementType))
+ return this.validateArrayElements(
+ elementsOffset, numElements, codec.Uint8, nullable)
+ if (elementType instanceof codec.PointerTo)
+ return this.validateStructElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+ if (elementType instanceof codec.ArrayOf)
+ return this.validateArrayElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+
+ return validationError.NONE;
+ }
+
+ // Note: the |offset + i * elementSize| computation in the validateFooElements
+ // methods below is "safe" because elementSize <= 8, offset and
+ // numElements are uint32, and 0 <= i < numElements.
+
+ Validator.prototype.validateHandleElements =
+ function(offset, numElements, nullable) {
+ var elementSize = codec.Handle.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateHandle(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ }
+
+ // The elementClass parameter is the element type of the element arrays.
+ Validator.prototype.validateArrayElements =
+ function(offset, numElements, elementClass, nullable) {
+ var elementSize = codec.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateArrayPointer(
+ elementOffset, elementClass.encodedSize, 0, elementClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ }
+
+ Validator.prototype.validateStructElements =
+ function(offset, numElements, structClass, nullable) {
+ var elementSize = codec.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err =
+ this.validateStructPointer(elementOffset, structClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ }
+
+ var exports = {};
+ exports.validationError = validationError;
+ exports.Validator = Validator;
+ return exports;
+});
diff --git a/mojo/public/platform/native/BUILD.gn b/mojo/public/platform/native/BUILD.gn
new file mode 100644
index 0000000..a3e720b
--- /dev/null
+++ b/mojo/public/platform/native/BUILD.gn
@@ -0,0 +1,59 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("system_thunks") {
+ visibility = [
+ "//mojo/public/c/system:for_component",
+ "//mojo/public/c/system:for_shared_library",
+ ]
+
+ sources = [
+ "system_thunks.h",
+ "system_thunks.cc",
+ ]
+ defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ]
+ deps = [ "//mojo/public/c/system" ]
+
+ # The GYP target analogous to this one builds this code into a
+ # static library. When building for Android, both the GYP and GN
+ # builds add --exclude-libs=ALL globally, which means that all
+ # symbols in static libraries are excluded from export. That's a
+ # problem, as code outside this target needs to be able to call
+ # MojoSetSystemThunks(). Therefore, the GYP target needs to specifiy
+ # that all dependent targets remove that link flag. Since GN uses a
+ # source_set here, this flag change is not needed.
+}
+
+# GYP version: mojo/mojo_public.gypi:mojo_gles2
+source_set("gles2_thunks") {
+ visibility = [ "//mojo/public/gles2:for_shared_library" ]
+
+ sources = [
+ "gles2_thunks.cc",
+ "gles2_thunks.h",
+ "gles2_impl_thunks.cc",
+ "gles2_impl_thunks.h",
+ "gles2_impl_chromium_texture_mailbox_thunks.cc",
+ "gles2_impl_chromium_texture_mailbox_thunks.h",
+ "gles2_impl_chromium_sync_point_thunks.cc",
+ "gles2_impl_chromium_sync_point_thunks.h",
+ ]
+
+ defines = [
+ "MOJO_GLES2_IMPLEMENTATION",
+ ]
+
+ configs += [ "//third_party/khronos:khronos_headers" ]
+
+ deps = [
+ "//mojo/public/c/gles2",
+ "//mojo/public/c/environment",
+ "//mojo/public/c/system",
+ ]
+
+ if (is_mac) {
+ # TODO(GYP): Make it a run-path dependent library.
+ # 'DYLIB_INSTALL_NAME_BASE': '@loader_path',
+ }
+}
diff --git a/mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.cc b/mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.cc
new file mode 100644
index 0000000..0a47f33
--- /dev/null
+++ b/mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.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/public/platform/native/gles2_impl_chromium_sync_point_thunks.h"
+
+#include <assert.h>
+
+#include "mojo/public/platform/native/thunk_export.h"
+
+extern "C" {
+static MojoGLES2ImplChromiumSyncPointThunks g_impl_chromium_sync_point_thunks =
+ {0};
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType gl##Function PARAMETERS { \
+ assert(g_impl_chromium_sync_point_thunks.Function); \
+ return g_impl_chromium_sync_point_thunks.Function ARGUMENTS; \
+ }
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h"
+#undef VISIT_GL_CALL
+
+extern "C" THUNK_EXPORT size_t MojoSetGLES2ImplChromiumSyncPointThunks(
+ const MojoGLES2ImplChromiumSyncPointThunks*
+ gles2_impl_chromium_sync_point_thunks) {
+ if (gles2_impl_chromium_sync_point_thunks->size >=
+ sizeof(g_impl_chromium_sync_point_thunks))
+ g_impl_chromium_sync_point_thunks = *gles2_impl_chromium_sync_point_thunks;
+ return sizeof(g_impl_chromium_sync_point_thunks);
+}
+
+} // extern "C"
diff --git a/mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.h b/mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.h
new file mode 100644
index 0000000..af6817e
--- /dev/null
+++ b/mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.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_PLATFORM_NATIVE_GLES2_IMPL_CHROMIUM_SYNC_POINT_THUNKS_H_
+#define MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_CHROMIUM_SYNC_POINT_THUNKS_H_
+
+#include <stddef.h>
+
+#include "mojo/public/c/gles2/chromium_sync_point.h"
+
+// Specifies the frozen API for the GLES2 CHROMIUM_sync_point extension.
+#pragma pack(push, 8)
+struct MojoGLES2ImplChromiumSyncPointThunks {
+ size_t size; // Should be set to sizeof(*this).
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType(*Function) PARAMETERS;
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h"
+#undef VISIT_GL_CALL
+};
+#pragma pack(pop)
+
+// Intended to be called from the embedder to get the embedder's implementation
+// of GLES2.
+inline MojoGLES2ImplChromiumSyncPointThunks
+MojoMakeGLES2ImplChromiumSyncPointThunks() {
+ MojoGLES2ImplChromiumSyncPointThunks gles2_impl_chromium_sync_point_thunks = {
+ sizeof(MojoGLES2ImplChromiumSyncPointThunks),
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) gl##Function,
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h"
+#undef VISIT_GL_CALL
+ };
+
+ return gles2_impl_chromium_sync_point_thunks;
+}
+
+// Use this type for the function found by dynamically discovering it in
+// a DSO linked with mojo_system.
+// The contents of |gles2_impl_chromium_sync_point_thunks| are copied.
+typedef size_t (*MojoSetGLES2ImplChromiumSyncPointThunksFn)(
+ const MojoGLES2ImplChromiumSyncPointThunks* thunks);
+
+#endif // MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_CHROMIUM_SYNC_POINT_THUNKS_H_
diff --git a/mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.cc b/mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.cc
new file mode 100644
index 0000000..db2bc81
--- /dev/null
+++ b/mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.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/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.h"
+
+#include <assert.h>
+
+#include "mojo/public/platform/native/thunk_export.h"
+
+extern "C" {
+static MojoGLES2ImplChromiumTextureMailboxThunks
+ g_impl_chromium_texture_mailbox_thunks = {0};
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType gl##Function PARAMETERS { \
+ assert(g_impl_chromium_texture_mailbox_thunks.Function); \
+ return g_impl_chromium_texture_mailbox_thunks.Function ARGUMENTS; \
+ }
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h"
+#undef VISIT_GL_CALL
+
+extern "C" THUNK_EXPORT size_t MojoSetGLES2ImplChromiumTextureMailboxThunks(
+ const MojoGLES2ImplChromiumTextureMailboxThunks*
+ gles2_impl_chromium_texture_mailbox_thunks) {
+ if (gles2_impl_chromium_texture_mailbox_thunks->size >=
+ sizeof(g_impl_chromium_texture_mailbox_thunks))
+ g_impl_chromium_texture_mailbox_thunks =
+ *gles2_impl_chromium_texture_mailbox_thunks;
+ return sizeof(g_impl_chromium_texture_mailbox_thunks);
+}
+
+} // extern "C"
diff --git a/mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.h b/mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.h
new file mode 100644
index 0000000..839e441
--- /dev/null
+++ b/mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.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_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_CHROMIUM_TEXTURE_MAILBOX_THUNKS_H_
+#define MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_CHROMIUM_TEXTURE_MAILBOX_THUNKS_H_
+
+#include <stddef.h>
+
+#include "mojo/public/c/gles2/chromium_texture_mailbox.h"
+
+// Specifies the frozen API for the GLES2 CHROMIUM_texture_mailbox extension.
+#pragma pack(push, 8)
+struct MojoGLES2ImplChromiumTextureMailboxThunks {
+ size_t size; // Should be set to sizeof(*this).
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType(*Function) PARAMETERS;
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h"
+#undef VISIT_GL_CALL
+};
+#pragma pack(pop)
+
+// Intended to be called from the embedder to get the embedder's implementation
+// of GLES2.
+inline MojoGLES2ImplChromiumTextureMailboxThunks
+MojoMakeGLES2ImplChromiumTextureMailboxThunks() {
+ MojoGLES2ImplChromiumTextureMailboxThunks
+ gles2_impl_chromium_texture_mailbox_thunks = {
+ sizeof(MojoGLES2ImplChromiumTextureMailboxThunks),
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) gl##Function,
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h"
+#undef VISIT_GL_CALL
+ };
+
+ return gles2_impl_chromium_texture_mailbox_thunks;
+}
+
+// Use this type for the function found by dynamically discovering it in
+// a DSO linked with mojo_system.
+// The contents of |gles2_impl_chromium_texture_mailbox_thunks| are copied.
+typedef size_t (*MojoSetGLES2ImplChromiumTextureMailboxThunksFn)(
+ const MojoGLES2ImplChromiumTextureMailboxThunks* thunks);
+
+#endif // MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_CHROMIUM_TEXTURE_MAILBOX_THUNKS_H_
diff --git a/mojo/public/platform/native/gles2_impl_thunks.cc b/mojo/public/platform/native/gles2_impl_thunks.cc
new file mode 100644
index 0000000..66dbc39
--- /dev/null
+++ b/mojo/public/platform/native/gles2_impl_thunks.cc
@@ -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.
+
+#include "mojo/public/platform/native/gles2_impl_thunks.h"
+
+#include <assert.h>
+
+#include "mojo/public/platform/native/thunk_export.h"
+
+extern "C" {
+static MojoGLES2ImplThunks g_impl_thunks = {0};
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType gl##Function PARAMETERS { \
+ assert(g_impl_thunks.Function); \
+ return g_impl_thunks.Function ARGUMENTS; \
+ }
+#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h"
+#undef VISIT_GL_CALL
+
+extern "C" THUNK_EXPORT size_t
+MojoSetGLES2ImplThunks(const MojoGLES2ImplThunks* gles2_impl_thunks) {
+ if (gles2_impl_thunks->size >= sizeof(g_impl_thunks))
+ g_impl_thunks = *gles2_impl_thunks;
+ return sizeof(g_impl_thunks);
+}
+
+} // extern "C"
diff --git a/mojo/public/platform/native/gles2_impl_thunks.h b/mojo/public/platform/native/gles2_impl_thunks.h
new file mode 100644
index 0000000..04174fa
--- /dev/null
+++ b/mojo/public/platform/native/gles2_impl_thunks.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_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_THUNKS_H_
+#define MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_THUNKS_H_
+
+#include <stddef.h>
+
+#include "mojo/public/c/gles2/gles2.h"
+
+// Like MojoGLES2ControlThunks, but specifies the frozen GLES2 API. Separated
+// out as MojoGLES2ControlThunks may be modified and added to, but this
+// interface is frozen.
+#pragma pack(push, 8)
+struct MojoGLES2ImplThunks {
+ size_t size; // Should be set to sizeof(MojoGLES2ImplThunks).
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType(*Function) PARAMETERS;
+#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h"
+#undef VISIT_GL_CALL
+};
+#pragma pack(pop)
+
+// Intended to be called from the embedder to get the embedder's implementation
+// of GLES2.
+inline MojoGLES2ImplThunks MojoMakeGLES2ImplThunks() {
+ MojoGLES2ImplThunks gles2_impl_thunks = {
+ sizeof(MojoGLES2ImplThunks),
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) gl##Function,
+#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h"
+#undef VISIT_GL_CALL
+ };
+
+ return gles2_impl_thunks;
+}
+
+// Use this type for the function found by dynamically discovering it in
+// a DSO linked with mojo_system. For example:
+// MojoSetGLES2ImplThunksFn mojo_set_gles2_impl_thunks_fn =
+// reinterpret_cast<MojoSetGLES2ImplThunksFn>(
+// app_library.GetFunctionPointer("MojoSetGLES2ImplThunks"));
+// The expected size of |gles2_impl_thunks| is returned.
+// The contents of |gles2_impl_thunks| are copied.
+typedef size_t (*MojoSetGLES2ImplThunksFn)(
+ const MojoGLES2ImplThunks* gles2_impl_thunks);
+
+#endif // MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_IMPL_THUNKS_H_
diff --git a/mojo/public/platform/native/gles2_thunks.cc b/mojo/public/platform/native/gles2_thunks.cc
new file mode 100644
index 0000000..365fac9
--- /dev/null
+++ b/mojo/public/platform/native/gles2_thunks.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.
+
+#include "mojo/public/platform/native/gles2_thunks.h"
+
+#include <assert.h>
+
+#include "mojo/public/platform/native/thunk_export.h"
+
+extern "C" {
+
+static MojoGLES2ControlThunks g_control_thunks = {0};
+
+MojoGLES2Context MojoGLES2CreateContext(MojoHandle handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure,
+ const MojoAsyncWaiter* async_waiter) {
+ assert(g_control_thunks.GLES2CreateContext);
+ return g_control_thunks.GLES2CreateContext(
+ handle, lost_callback, closure, async_waiter);
+}
+
+void MojoGLES2DestroyContext(MojoGLES2Context context) {
+ assert(g_control_thunks.GLES2DestroyContext);
+ g_control_thunks.GLES2DestroyContext(context);
+}
+
+void MojoGLES2MakeCurrent(MojoGLES2Context context) {
+ assert(g_control_thunks.GLES2MakeCurrent);
+ g_control_thunks.GLES2MakeCurrent(context);
+}
+
+void MojoGLES2SwapBuffers() {
+ assert(g_control_thunks.GLES2SwapBuffers);
+ g_control_thunks.GLES2SwapBuffers();
+}
+
+void* MojoGLES2GetGLES2Interface(MojoGLES2Context context) {
+ assert(g_control_thunks.GLES2GetGLES2Interface);
+ return g_control_thunks.GLES2GetGLES2Interface(context);
+}
+
+void* MojoGLES2GetContextSupport(MojoGLES2Context context) {
+ assert(g_control_thunks.GLES2GetContextSupport);
+ return g_control_thunks.GLES2GetContextSupport(context);
+}
+
+extern "C" THUNK_EXPORT size_t MojoSetGLES2ControlThunks(
+ const MojoGLES2ControlThunks* gles2_control_thunks) {
+ if (gles2_control_thunks->size >= sizeof(g_control_thunks))
+ g_control_thunks = *gles2_control_thunks;
+ return sizeof(g_control_thunks);
+}
+
+} // extern "C"
diff --git a/mojo/public/platform/native/gles2_thunks.h b/mojo/public/platform/native/gles2_thunks.h
new file mode 100644
index 0000000..4718ab3
--- /dev/null
+++ b/mojo/public/platform/native/gles2_thunks.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_PLATFORM_NATIVE_GLES2_THUNKS_H_
+#define MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_THUNKS_H_
+
+#include <stddef.h>
+
+#include "mojo/public/c/gles2/gles2.h"
+
+// Structure used to bind the interface which manipulates GLES2 surfaces to a
+// DSO to theose 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 MojoGLES2ControlThunks {
+ size_t size; // Should be set to sizeof(MojoGLES2ControlThunks).
+
+ MojoGLES2Context (*GLES2CreateContext)(MojoHandle handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure,
+ const MojoAsyncWaiter* async_waiter);
+ void (*GLES2DestroyContext)(MojoGLES2Context context);
+ void (*GLES2MakeCurrent)(MojoGLES2Context context);
+ void (*GLES2SwapBuffers)();
+
+ // TODO(piman): We shouldn't have to leak these 2 interfaces, especially in a
+ // type-unsafe way.
+ void* (*GLES2GetGLES2Interface)(MojoGLES2Context context);
+ void* (*GLES2GetContextSupport)(MojoGLES2Context context);
+};
+#pragma pack(pop)
+
+// Intended to be called from the embedder. Returns an object initialized to
+// contain pointers to each of the embedder's MojoGLES2ControlThunks functions.
+inline MojoGLES2ControlThunks MojoMakeGLES2ControlThunks() {
+ MojoGLES2ControlThunks gles2_control_thunks = {
+ sizeof(MojoGLES2ControlThunks),
+ MojoGLES2CreateContext,
+ MojoGLES2DestroyContext,
+ MojoGLES2MakeCurrent,
+ MojoGLES2SwapBuffers,
+ MojoGLES2GetGLES2Interface,
+ MojoGLES2GetContextSupport
+ };
+
+ return gles2_control_thunks;
+}
+
+// Use this type for the function found by dynamically discovering it in
+// a DSO linked with mojo_system. For example:
+// MojoSetGLES2ControlThunksFn mojo_set_gles2_control_thunks_fn =
+// reinterpret_cast<MojoSetGLES2ControlThunksFn>(
+// app_library.GetFunctionPointer("MojoSetGLES2ControlThunks"));
+// The expected size of |gles2_control_thunks| is returned.
+// The contents of |gles2_control_thunks| are copied.
+typedef size_t (*MojoSetGLES2ControlThunksFn)(
+ const MojoGLES2ControlThunks* gles2_control_thunks);
+
+#endif // MOJO_PUBLIC_PLATFORM_NATIVE_GLES2_THUNKS_H_
diff --git a/mojo/public/platform/native/system_thunks.cc b/mojo/public/platform/native/system_thunks.cc
new file mode 100644
index 0000000..fed6db9
--- /dev/null
+++ b/mojo/public/platform/native/system_thunks.cc
@@ -0,0 +1,164 @@
+// 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>
+
+#include "mojo/public/platform/native/thunk_export.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);
+}
+
+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/mojo/public/platform/native/system_thunks.h b/mojo/public/platform/native/system_thunks.h
new file mode 100644
index 0000000..d53485c
--- /dev/null
+++ b/mojo/public/platform/native/system_thunks.h
@@ -0,0 +1,145 @@
+// Copyright 2014 The Chromium 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 header should be compilable as C.
+
+#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 struct 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 struct 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 struct MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle);
+ MojoResult (*DuplicateBufferHandle)(
+ MojoHandle buffer_handle,
+ const struct 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)
+
+
+#ifdef __cplusplus
+// 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;
+}
+#endif
+
+
+// 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 struct MojoSystemThunks* system_thunks);
+
+#endif // MOJO_PUBLIC_PLATFORM_NATIVE_SYSTEM_THUNKS_H_
diff --git a/mojo/public/platform/native/thunk_export.h b/mojo/public/platform/native/thunk_export.h
new file mode 100644
index 0000000..d118fc5
--- /dev/null
+++ b/mojo/public/platform/native/thunk_export.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_PLATFORM_THUNK_EXPORT_H_
+#define MOJO_PUBLIC_PLATFORM_THUNK_EXPORT_H_
+
+// Call this function by looking inside the resulting shared object and
+// grabbing the symbol manually.
+//
+// Always export this api.
+#if defined(WIN32)
+#define THUNK_EXPORT __declspec(dllexport)
+#else
+#define THUNK_EXPORT __attribute__((visibility("default")))
+#endif
+
+#endif // MOJO_PUBLIC_PLATFORM_THUNK_EXPORT_H_
diff --git a/mojo/public/python/BUILD.gn b/mojo/public/python/BUILD.gn
new file mode 100644
index 0000000..66f132f
--- /dev/null
+++ b/mojo/public/python/BUILD.gn
@@ -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.
+
+import("//third_party/cython/rules.gni")
+
+group("python") {
+ deps = [
+ ":base",
+ ":bindings",
+ ":system",
+ ]
+}
+
+# GYP version: mojo.gyp:mojo_python_system
+python_binary_module("system") {
+ python_base_module = "mojo"
+ sources = [
+ "mojo/c_core.pxd",
+ "mojo/c_environment.pxd",
+ "mojo/system.pyx",
+ ]
+ additional_sources = [
+ "src/python_system_helper.cc",
+ "src/python_system_helper.h",
+ ]
+ deps = [
+ "//mojo/public/c/environment",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/environment:standalone",
+ "//mojo/public/cpp/system",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/cpp/bindings:callback",
+ ":base",
+ ]
+}
+
+copy("base") {
+ sources = [
+ "mojo/__init__.py",
+ ]
+ outputs = [
+ "$root_out_dir/python/mojo/{{source_file_part}}",
+ ]
+}
+
+# GYP version: mojo.gyp:mojo_python_bindings
+copy("bindings") {
+ sources = [
+ "mojo/bindings/__init__.py",
+ "mojo/bindings/descriptor.py",
+ "mojo/bindings/messaging.py",
+ "mojo/bindings/promise.py",
+ "mojo/bindings/reflection.py",
+ "mojo/bindings/serialization.py",
+ ]
+ outputs = [
+ "$root_out_dir/python/mojo/bindings/{{source_file_part}}",
+ ]
+ deps = [
+ ":base",
+ ":system",
+ ]
+}
diff --git a/mojo/public/python/mojo/__init__.py b/mojo/public/python/mojo/__init__.py
new file mode 100644
index 0000000..4d6aabb
--- /dev/null
+++ b/mojo/public/python/mojo/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
diff --git a/mojo/public/python/mojo/bindings/__init__.py b/mojo/public/python/mojo/bindings/__init__.py
new file mode 100644
index 0000000..4d6aabb
--- /dev/null
+++ b/mojo/public/python/mojo/bindings/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
diff --git a/mojo/public/python/mojo/bindings/descriptor.py b/mojo/public/python/mojo/bindings/descriptor.py
new file mode 100644
index 0000000..d56789a
--- /dev/null
+++ b/mojo/public/python/mojo/bindings/descriptor.py
@@ -0,0 +1,555 @@
+# Copyright 2014 The Chromium 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 descriptors used to define generated elements of the mojo python bindings.
+"""
+
+import array
+import itertools
+import struct
+
+# pylint: disable=F0401
+import mojo.bindings.serialization as serialization
+import mojo.system
+
+
+class Type(object):
+ """Describes the type of a struct field or a method parameter,"""
+
+ def Convert(self, value): # pylint: disable=R0201
+ """
+ Convert the given value into its canonical representation, raising an
+ exception if the value cannot be converted.
+ """
+ return value
+
+ def GetDefaultValue(self, value):
+ """
+ Returns the default value for this type associated with the given value.
+ This method must be able to correcly handle value being None.
+ """
+ return self.Convert(value)
+
+
+class SerializableType(Type):
+ """Describe a type that can be serialized by itself."""
+
+ def __init__(self, typecode):
+ Type.__init__(self)
+ self.typecode = typecode
+ self.byte_size = struct.calcsize('=%s' % self.GetTypeCode())
+
+ def GetTypeCode(self):
+ """
+ Returns the type code (as defined by the struct module) used to encode
+ this type.
+ """
+ return self.typecode
+
+ def GetByteSize(self):
+ """
+ Returns the size of the encoding of this type.
+ """
+ return self.byte_size
+
+ def Serialize(self, value, data_offset, data, handle_offset):
+ """
+ Serialize a value of this type.
+
+ Args:
+ value: the value to serialize.
+ data_offset: the offset to the end of the data bytearray. Used to encode
+ pointers.
+ data: the bytearray to append additional data to.
+ handle_offset: the offset to use to encode handles.
+
+ Returns a a tuple where the first element is the value to encode, and the
+ second is the array of handles to add to the message.
+ """
+ raise NotImplementedError()
+
+ def Deserialize(self, value, data, handles):
+ """
+ Deserialize a value of this type.
+
+ Args:
+ value: the base value for this type. This is always a numeric type, and
+ corresponds to the first element in the tuple returned by
+ Serialize.
+ data: the bytearray to retrieve additional data from.
+ handles: the array of handles contained in the message to deserialize.
+
+ Returns the deserialized value.
+ """
+ raise NotImplementedError()
+
+
+class BooleanType(Type):
+ """Type object for booleans"""
+
+ def Convert(self, value):
+ return bool(value)
+
+
+class NumericType(SerializableType):
+ """Base Type object for all numeric types"""
+
+ def GetDefaultValue(self, value):
+ if value is None:
+ return self.Convert(0)
+ return self.Convert(value)
+
+ def Serialize(self, value, data_offset, data, handle_offset):
+ return (value, [])
+
+ def Deserialize(self, value, data, handles):
+ return value
+
+
+class IntegerType(NumericType):
+ """Type object for integer types."""
+
+ def __init__(self, typecode):
+ NumericType.__init__(self, typecode)
+ size = 8 * self.byte_size
+ signed = typecode.islower()
+ if signed:
+ self._min_value = -(1 << (size - 1))
+ self._max_value = (1 << (size - 1)) - 1
+ else:
+ self._min_value = 0
+ self._max_value = (1 << size) - 1
+
+ def Convert(self, value):
+ if value is None:
+ raise TypeError('None is not an integer.')
+ if not isinstance(value, (int, long)):
+ raise TypeError('%r is not an integer type' % value)
+ if value < self._min_value or value > self._max_value:
+ raise OverflowError('%r is not in the range [%d, %d]' %
+ (value, self._min_value, self._max_value))
+ return value
+
+
+class FloatType(NumericType):
+ """Type object for floating point number types."""
+
+ def Convert(self, value):
+ if value is None:
+ raise TypeError('None is not an floating point number.')
+ if not isinstance(value, (int, long, float)):
+ raise TypeError('%r is not a numeric type' % value)
+ return float(value)
+
+
+class PointerType(SerializableType):
+ """Base Type object for pointers."""
+
+ def __init__(self, nullable=False):
+ SerializableType.__init__(self, 'Q')
+ self.nullable = nullable
+
+ def Serialize(self, value, data_offset, data, handle_offset):
+ if value is None and not self.nullable:
+ raise serialization.SerializationException(
+ 'Trying to serialize null for non nullable type.')
+ if value is None:
+ return (0, [])
+ return self.SerializePointer(value, data_offset, data, handle_offset)
+
+ def Deserialize(self, value, data, handles):
+ if value == 0:
+ if not self.nullable:
+ raise serialization.DeserializationException(
+ 'Trying to deserialize null for non nullable type.')
+ return None
+ pointed_data = buffer(data, value)
+ (size, nb_elements) = serialization.HEADER_STRUCT.unpack_from(pointed_data)
+ return self.DeserializePointer(size, nb_elements, pointed_data, handles)
+
+ def SerializePointer(self, value, data_offset, data, handle_offset):
+ """Serialize the not null value."""
+ raise NotImplementedError()
+
+ def DeserializePointer(self, size, nb_elements, data, handles):
+ raise NotImplementedError()
+
+
+class StringType(PointerType):
+ """
+ Type object for strings.
+
+ Strings are represented as unicode, and the conversion is done using the
+ default encoding if a string instance is used.
+ """
+
+ def __init__(self, nullable=False):
+ PointerType.__init__(self, nullable)
+ self._array_type = NativeArrayType('B', nullable)
+
+ def Convert(self, value):
+ if value is None or isinstance(value, unicode):
+ return value
+ if isinstance(value, str):
+ return unicode(value)
+ raise TypeError('%r is not a string' % value)
+
+ def SerializePointer(self, value, data_offset, data, handle_offset):
+ string_array = array.array('b')
+ string_array.fromstring(value.encode('utf8'))
+ return self._array_type.SerializeArray(
+ string_array, data_offset, data, handle_offset)
+
+ def DeserializePointer(self, size, nb_elements, data, handles):
+ string_array = self._array_type.DeserializeArray(
+ size, nb_elements, data, handles)
+ return unicode(string_array.tostring(), 'utf8')
+
+
+class HandleType(SerializableType):
+ """Type object for handles."""
+
+ def __init__(self, nullable=False):
+ SerializableType.__init__(self, 'i')
+ self.nullable = nullable
+
+ def Convert(self, value):
+ if value is None:
+ return mojo.system.Handle()
+ if not isinstance(value, mojo.system.Handle):
+ raise TypeError('%r is not a handle' % value)
+ return value
+
+ def Serialize(self, value, data_offset, data, handle_offset):
+ if not value.IsValid() and not self.nullable:
+ raise serialization.SerializationException(
+ 'Trying to serialize null for non nullable type.')
+ if not value.IsValid():
+ return (-1, [])
+ return (handle_offset, [value])
+
+ def Deserialize(self, value, data, handles):
+ if value == -1:
+ if not self.nullable:
+ raise serialization.DeserializationException(
+ 'Trying to deserialize null for non nullable type.')
+ return mojo.system.Handle()
+ # TODO(qsr) validate handle order
+ return handles[value]
+
+
+class BaseArrayType(PointerType):
+ """Abstract Type object for arrays."""
+
+ def __init__(self, nullable=False, length=0):
+ PointerType.__init__(self, nullable)
+ self.length = length
+
+ def SerializePointer(self, value, data_offset, data, handle_offset):
+ if self.length != 0 and len(value) != self.length:
+ raise serialization.SerializationException('Incorrect array size')
+ return self.SerializeArray(value, data_offset, data, handle_offset)
+
+ def SerializeArray(self, value, data_offset, data, handle_offset):
+ """Serialize the not null array."""
+ raise NotImplementedError()
+
+ def DeserializePointer(self, size, nb_elements, data, handles):
+ if self.length != 0 and size != self.length:
+ raise serialization.DeserializationException('Incorrect array size')
+ return self.DeserializeArray(size, nb_elements, data, handles)
+
+ def DeserializeArray(self, size, nb_elements, data, handles):
+ raise NotImplementedError()
+
+
+class BooleanArrayType(BaseArrayType):
+
+ def __init__(self, nullable=False, length=0):
+ BaseArrayType.__init__(self, nullable, length)
+ self._array_type = NativeArrayType('B', nullable)
+
+ def Convert(self, value):
+ if value is None:
+ return value
+ return [TYPE_BOOL.Convert(x) for x in value]
+
+ def SerializeArray(self, value, data_offset, data, handle_offset):
+ groups = [value[i:i+8] for i in range(0, len(value), 8)]
+ converted = array.array('B', [_ConvertBooleansToByte(x) for x in groups])
+ return _SerializeNativeArray(converted, data_offset, data, len(value))
+
+ def DeserializeArray(self, size, nb_elements, data, handles):
+ converted = self._array_type.DeserializeArray(
+ size, nb_elements, data, handles)
+ elements = list(itertools.islice(
+ itertools.chain.from_iterable(
+ [_ConvertByteToBooleans(x, 8) for x in converted]),
+ 0,
+ nb_elements))
+ return elements
+
+
+class GenericArrayType(BaseArrayType):
+ """Type object for arrays of pointers."""
+
+ def __init__(self, sub_type, nullable=False, length=0):
+ BaseArrayType.__init__(self, nullable, length)
+ assert isinstance(sub_type, SerializableType)
+ self.sub_type = sub_type
+
+ def Convert(self, value):
+ if value is None:
+ return value
+ return [self.sub_type.Convert(x) for x in value]
+
+ def SerializeArray(self, value, data_offset, data, handle_offset):
+ size = (serialization.HEADER_STRUCT.size +
+ self.sub_type.GetByteSize() * len(value))
+ data_end = len(data)
+ position = len(data) + serialization.HEADER_STRUCT.size
+ data.extend(bytearray(size +
+ serialization.NeededPaddingForAlignment(size)))
+ returned_handles = []
+ to_pack = []
+ for item in value:
+ (new_data, new_handles) = self.sub_type.Serialize(
+ item,
+ len(data) - position,
+ data,
+ handle_offset + len(returned_handles))
+ to_pack.append(new_data)
+ returned_handles.extend(new_handles)
+ position = position + self.sub_type.GetByteSize()
+ serialization.HEADER_STRUCT.pack_into(data, data_end, size, len(value))
+ struct.pack_into('%d%s' % (len(value), self.sub_type.GetTypeCode()),
+ data,
+ data_end + serialization.HEADER_STRUCT.size,
+ *to_pack)
+ return (data_offset, returned_handles)
+
+ def DeserializeArray(self, size, nb_elements, data, handles):
+ values = struct.unpack_from(
+ '%d%s' % (nb_elements, self.sub_type.GetTypeCode()),
+ buffer(data, serialization.HEADER_STRUCT.size))
+ result = []
+ position = serialization.HEADER_STRUCT.size
+ for value in values:
+ result.append(
+ self.sub_type.Deserialize(value, buffer(data, position), handles))
+ position += self.sub_type.GetByteSize()
+ return result
+
+
+class NativeArrayType(BaseArrayType):
+ """Type object for arrays of native types."""
+
+ def __init__(self, typecode, nullable=False, length=0):
+ BaseArrayType.__init__(self, nullable, length)
+ self.array_typecode = typecode
+
+ def Convert(self, value):
+ if value is None:
+ return value
+ if (isinstance(value, array.array) and
+ value.array_typecode == self.array_typecode):
+ return value
+ return array.array(self.array_typecode, value)
+
+ def SerializeArray(self, value, data_offset, data, handle_offset):
+ return _SerializeNativeArray(value, data_offset, data, len(value))
+
+ def DeserializeArray(self, size, nb_elements, data, handles):
+ result = array.array(self.array_typecode)
+ result.fromstring(buffer(data,
+ serialization.HEADER_STRUCT.size,
+ size - serialization.HEADER_STRUCT.size))
+ return result
+
+
+class StructType(PointerType):
+ """Type object for structs."""
+
+ def __init__(self, struct_type, nullable=False):
+ PointerType.__init__(self)
+ self.struct_type = struct_type
+ self.nullable = nullable
+
+ def Convert(self, value):
+ if value is None or isinstance(value, self.struct_type):
+ return value
+ raise TypeError('%r is not an instance of %r' % (value, self.struct_type))
+
+ def GetDefaultValue(self, value):
+ if value:
+ return self.struct_type()
+ return None
+
+ def SerializePointer(self, value, data_offset, data, handle_offset):
+ (new_data, new_handles) = value.Serialize(handle_offset)
+ data.extend(new_data)
+ return (data_offset, new_handles)
+
+ def DeserializePointer(self, size, nb_elements, data, handles):
+ return self.struct_type.Deserialize(data, handles)
+
+
+class NoneType(SerializableType):
+ """Placeholder type, used temporarily until all mojo types are handled."""
+
+ def __init__(self):
+ SerializableType.__init__(self, 'B')
+
+ def Convert(self, value):
+ return None
+
+ def Serialize(self, value, data_offset, data, handle_offset):
+ return (0, [])
+
+ def Deserialize(self, value, data, handles):
+ return None
+
+
+TYPE_NONE = NoneType()
+
+TYPE_BOOL = BooleanType()
+
+TYPE_INT8 = IntegerType('b')
+TYPE_INT16 = IntegerType('h')
+TYPE_INT32 = IntegerType('i')
+TYPE_INT64 = IntegerType('q')
+
+TYPE_UINT8 = IntegerType('B')
+TYPE_UINT16 = IntegerType('H')
+TYPE_UINT32 = IntegerType('I')
+TYPE_UINT64 = IntegerType('Q')
+
+TYPE_FLOAT = FloatType('f')
+TYPE_DOUBLE = FloatType('d')
+
+TYPE_STRING = StringType()
+TYPE_NULLABLE_STRING = StringType(True)
+
+TYPE_HANDLE = HandleType()
+TYPE_NULLABLE_HANDLE = HandleType(True)
+
+
+class FieldDescriptor(object):
+ """Describes a field in a generated struct."""
+
+ def __init__(self, name, field_type, index, version, default_value=None):
+ self.name = name
+ self.field_type = field_type
+ self.version = version
+ self.index = index
+ self._default_value = default_value
+
+ def GetDefaultValue(self):
+ return self.field_type.GetDefaultValue(self._default_value)
+
+
+class FieldGroup(object):
+ """
+ Describe a list of field in the generated struct that must be
+ serialized/deserialized together.
+ """
+ def __init__(self, descriptors):
+ self.descriptors = descriptors
+
+ def GetDescriptors(self):
+ return self.descriptors
+
+ def GetTypeCode(self):
+ raise NotImplementedError()
+
+ def GetByteSize(self):
+ raise NotImplementedError()
+
+ def GetVersion(self):
+ raise NotImplementedError()
+
+ def Serialize(self, obj, data_offset, data, handle_offset):
+ raise NotImplementedError()
+
+ def Deserialize(self, value, data, handles):
+ raise NotImplementedError()
+
+
+class SingleFieldGroup(FieldGroup, FieldDescriptor):
+ """A FieldGroup that contains a single FieldDescriptor."""
+
+ def __init__(self, name, field_type, index, version, default_value=None):
+ FieldDescriptor.__init__(
+ self, name, field_type, index, version, default_value)
+ FieldGroup.__init__(self, [self])
+
+ def GetTypeCode(self):
+ return self.field_type.GetTypeCode()
+
+ def GetByteSize(self):
+ return self.field_type.GetByteSize()
+
+ def GetVersion(self):
+ return self.version
+
+ def Serialize(self, obj, data_offset, data, handle_offset):
+ value = getattr(obj, self.name)
+ return self.field_type.Serialize(value, data_offset, data, handle_offset)
+
+ def Deserialize(self, value, data, handles):
+ entity = self.field_type.Deserialize(value, data, handles)
+ return { self.name: entity }
+
+
+class BooleanGroup(FieldGroup):
+ """A FieldGroup to pack booleans."""
+ def __init__(self, descriptors):
+ FieldGroup.__init__(self, descriptors)
+ self.version = min([descriptor.version for descriptor in descriptors])
+
+ def GetTypeCode(self):
+ return 'B'
+
+ def GetByteSize(self):
+ return 1
+
+ def GetVersion(self):
+ return self.version
+
+ def Serialize(self, obj, data_offset, data, handle_offset):
+ value = _ConvertBooleansToByte(
+ [getattr(obj, field.name) for field in self.GetDescriptors()])
+ return (value, [])
+
+ def Deserialize(self, value, data, handles):
+ values = itertools.izip_longest([x.name for x in self.descriptors],
+ _ConvertByteToBooleans(value),
+ fillvalue=False)
+ return dict(values)
+
+
+def _SerializeNativeArray(value, data_offset, data, length):
+ data_size = len(data)
+ data.extend(bytearray(serialization.HEADER_STRUCT.size))
+ data.extend(buffer(value))
+ data_length = len(data) - data_size
+ data.extend(bytearray(serialization.NeededPaddingForAlignment(data_length)))
+ serialization.HEADER_STRUCT.pack_into(data, data_size, data_length, length)
+ return (data_offset, [])
+
+
+def _ConvertBooleansToByte(booleans):
+ """Pack a list of booleans into an integer."""
+ return reduce(lambda x, y: x * 2 + y, reversed(booleans), 0)
+
+
+def _ConvertByteToBooleans(value, min_size=0):
+ "Unpack an integer into a list of booleans."""
+ res = []
+ while value:
+ res.append(bool(value&1))
+ value = value / 2
+ res.extend([False] * (min_size - len(res)))
+ return res
diff --git a/mojo/public/python/mojo/bindings/messaging.py b/mojo/public/python/mojo/bindings/messaging.py
new file mode 100644
index 0000000..956f5b3
--- /dev/null
+++ b/mojo/public/python/mojo/bindings/messaging.py
@@ -0,0 +1,387 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Utility classes to handle sending and receiving messages."""
+
+
+import struct
+import weakref
+
+# pylint: disable=F0401
+import mojo.bindings.serialization as serialization
+import mojo.system as system
+
+
+# The flag values for a message header.
+NO_FLAG = 0
+MESSAGE_EXPECTS_RESPONSE_FLAG = 1 << 0
+MESSAGE_IS_RESPONSE_FLAG = 1 << 1
+
+
+class MessageHeader(object):
+ """The header of a mojo message."""
+
+ _SIMPLE_MESSAGE_NUM_FIELDS = 2
+ _SIMPLE_MESSAGE_STRUCT = struct.Struct("=IIII")
+
+ _REQUEST_ID_STRUCT = struct.Struct("=Q")
+ _REQUEST_ID_OFFSET = _SIMPLE_MESSAGE_STRUCT.size
+
+ _MESSAGE_WITH_REQUEST_ID_NUM_FIELDS = 3
+ _MESSAGE_WITH_REQUEST_ID_SIZE = (
+ _SIMPLE_MESSAGE_STRUCT.size + _REQUEST_ID_STRUCT.size)
+
+ def __init__(self, message_type, flags, request_id=0, data=None):
+ self._message_type = message_type
+ self._flags = flags
+ self._request_id = request_id
+ self._data = data
+
+ @classmethod
+ def Deserialize(cls, data):
+ buf = buffer(data)
+ if len(data) < cls._SIMPLE_MESSAGE_STRUCT.size:
+ raise serialization.DeserializationException('Header is too short.')
+ (size, version, message_type, flags) = (
+ cls._SIMPLE_MESSAGE_STRUCT.unpack_from(buf))
+ if (version < cls._SIMPLE_MESSAGE_NUM_FIELDS):
+ raise serialization.DeserializationException('Incorrect version.')
+ request_id = 0
+ if _HasRequestId(flags):
+ if version < cls._MESSAGE_WITH_REQUEST_ID_NUM_FIELDS:
+ raise serialization.DeserializationException('Incorrect version.')
+ if (size < cls._MESSAGE_WITH_REQUEST_ID_SIZE or
+ len(data) < cls._MESSAGE_WITH_REQUEST_ID_SIZE):
+ raise serialization.DeserializationException('Header is too short.')
+ (request_id, ) = cls._REQUEST_ID_STRUCT.unpack_from(
+ buf, cls._REQUEST_ID_OFFSET)
+ return MessageHeader(message_type, flags, request_id, data)
+
+ @property
+ def message_type(self):
+ return self._message_type
+
+ # pylint: disable=E0202
+ @property
+ def request_id(self):
+ assert self.has_request_id
+ return self._request_id
+
+ # pylint: disable=E0202
+ @request_id.setter
+ def request_id(self, request_id):
+ assert self.has_request_id
+ self._request_id = request_id
+ self._REQUEST_ID_STRUCT.pack_into(self._data, self._REQUEST_ID_OFFSET,
+ request_id)
+
+ @property
+ def has_request_id(self):
+ return _HasRequestId(self._flags)
+
+ @property
+ def expects_response(self):
+ return self._HasFlag(MESSAGE_EXPECTS_RESPONSE_FLAG)
+
+ @property
+ def is_response(self):
+ return self._HasFlag(MESSAGE_IS_RESPONSE_FLAG)
+
+ @property
+ def size(self):
+ if self.has_request_id:
+ return self._MESSAGE_WITH_REQUEST_ID_SIZE
+ return self._SIMPLE_MESSAGE_STRUCT.size
+
+ def Serialize(self):
+ if not self._data:
+ self._data = bytearray(self.size)
+ version = self._SIMPLE_MESSAGE_NUM_FIELDS
+ size = self._SIMPLE_MESSAGE_STRUCT.size
+ if self.has_request_id:
+ version = self._MESSAGE_WITH_REQUEST_ID_NUM_FIELDS
+ size = self._MESSAGE_WITH_REQUEST_ID_SIZE
+ self._SIMPLE_MESSAGE_STRUCT.pack_into(self._data, 0, size, version,
+ self._message_type, self._flags)
+ if self.has_request_id:
+ self._REQUEST_ID_STRUCT.pack_into(self._data, self._REQUEST_ID_OFFSET,
+ self._request_id)
+ return self._data
+
+ def _HasFlag(self, flag):
+ return self._flags & flag != 0
+
+
+class Message(object):
+ """A message for a message pipe. This contains data and handles."""
+
+ def __init__(self, data=None, handles=None):
+ self.data = data
+ self.handles = handles
+ self._header = None
+ self._payload = None
+
+ @property
+ def header(self):
+ if self._header is None:
+ self._header = MessageHeader.Deserialize(self.data)
+ return self._header
+
+ @property
+ def payload(self):
+ if self._payload is None:
+ self._payload = Message(self.data[self.header.size:], self.handles)
+ return self._payload
+
+ def SetRequestId(self, request_id):
+ header = self.header
+ header.request_id = request_id
+ (data, _) = header.Serialize()
+ self.data[:header.Size] = data[:header.Size]
+
+
+class MessageReceiver(object):
+ """A class which implements this interface can receive Message objects."""
+
+ def Accept(self, message):
+ """
+ Receive a Message. The MessageReceiver is allowed to mutate the message.
+
+ Args:
+ message: the received message.
+
+ Returns:
+ True if the message has been handled, False otherwise.
+ """
+ raise NotImplementedError()
+
+
+class MessageReceiverWithResponder(MessageReceiver):
+ """
+ A MessageReceiver that can also handle the response message generated from the
+ given message.
+ """
+
+ def AcceptWithResponder(self, message, responder):
+ """
+ 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 as part of the call to
+ AcceptWithResponder, or some time after its return.
+
+ Args:
+ message: the received message.
+ responder: the responder that will receive the response.
+
+ Returns:
+ True if the message has been handled, False otherwise.
+ """
+ raise NotImplementedError()
+
+
+class ConnectionErrorHandler(object):
+ """
+ A ConnectionErrorHandler is notified of an error happening while using the
+ bindings over message pipes.
+ """
+
+ def OnError(self, result):
+ raise NotImplementedError()
+
+
+class Connector(MessageReceiver):
+ """
+ A Connector owns a message pipe and will send any received messages to the
+ registered MessageReceiver. It also acts as a MessageReceiver and will send
+ any message through the handle.
+
+ The method Start must be called before the Connector will start listening to
+ incoming messages.
+ """
+
+ def __init__(self, handle):
+ MessageReceiver.__init__(self)
+ self._handle = handle
+ self._cancellable = None
+ self._incoming_message_receiver = None
+ self._error_handler = None
+
+ def __del__(self):
+ if self._cancellable:
+ self._cancellable()
+
+ def SetIncomingMessageReceiver(self, message_receiver):
+ """
+ Set the MessageReceiver that will receive message from the owned message
+ pipe.
+ """
+ self._incoming_message_receiver = message_receiver
+
+ def SetErrorHandler(self, error_handler):
+ """
+ Set the ConnectionErrorHandler that will be notified of errors on the owned
+ message pipe.
+ """
+ self._error_handler = error_handler
+
+ def Start(self):
+ assert not self._cancellable
+ self._RegisterAsyncWaiterForRead()
+
+ def Accept(self, message):
+ result = self._handle.WriteMessage(message.data, message.handles)
+ return result == system.RESULT_OK
+
+ def Close(self):
+ if self._cancellable:
+ self._cancellable()
+ self._cancellable = None
+ self._handle.Close()
+
+ def _OnAsyncWaiterResult(self, result):
+ self._cancellable = None
+ if result == system.RESULT_OK:
+ self._ReadOutstandingMessages()
+ else:
+ self._OnError(result)
+
+ def _OnError(self, result):
+ assert not self._cancellable
+ if self._error_handler:
+ self._error_handler.OnError(result)
+
+ def _RegisterAsyncWaiterForRead(self) :
+ assert not self._cancellable
+ self._cancellable = self._handle.AsyncWait(
+ system.HANDLE_SIGNAL_READABLE,
+ system.DEADLINE_INDEFINITE,
+ _WeakCallback(self._OnAsyncWaiterResult))
+
+ def _ReadOutstandingMessages(self):
+ result = system.RESULT_OK
+ while result == system.RESULT_OK:
+ result = _ReadAndDispatchMessage(self._handle,
+ self._incoming_message_receiver)
+ if result == system.RESULT_SHOULD_WAIT:
+ self._RegisterAsyncWaiterForRead()
+ return
+ self._OnError(result)
+
+
+class Router(MessageReceiverWithResponder):
+ """
+ A Router will handle mojo message and forward those to a Connector. It deals
+ with parsing of headers and adding of request ids in order to be able to match
+ a response to a request.
+ """
+
+ def __init__(self, handle):
+ MessageReceiverWithResponder.__init__(self)
+ self._incoming_message_receiver = None
+ self._next_request_id = 1
+ self._responders = {}
+ self._connector = Connector(handle)
+ self._connector.SetIncomingMessageReceiver(
+ ForwardingMessageReceiver(self._HandleIncomingMessage))
+
+ def Start(self):
+ self._connector.Start()
+
+ def SetIncomingMessageReceiver(self, message_receiver):
+ """
+ Set the MessageReceiver that will receive message from the owned message
+ pipe.
+ """
+ self._incoming_message_receiver = message_receiver
+
+ def SetErrorHandler(self, error_handler):
+ """
+ Set the ConnectionErrorHandler that will be notified of errors on the owned
+ message pipe.
+ """
+ self._connector.SetErrorHandler(error_handler)
+
+ def Accept(self, message):
+ # A message without responder is directly forwarded to the connector.
+ return self._connector.Accept(message)
+
+ def AcceptWithResponder(self, message, responder):
+ # The message must have a header.
+ header = message.header
+ assert header.expects_response
+ request_id = self.NextRequestId()
+ header.request_id = request_id
+ if not self._connector.Accept(message):
+ return False
+ self._responders[request_id] = responder
+ return True
+
+ def Close(self):
+ self._connector.Close()
+
+ def _HandleIncomingMessage(self, message):
+ header = message.header
+ if header.expects_response:
+ if self._incoming_message_receiver:
+ return self._incoming_message_receiver.AcceptWithResponder(
+ message, self)
+ # 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.
+ self.Close()
+ return False
+ if header.is_response:
+ request_id = header.request_id
+ responder = self._responders.pop(request_id, None)
+ if responder is None:
+ return False
+ return responder.Accept(message)
+ if self._incoming_message_receiver:
+ return self._incoming_message_receiver.Accept(message)
+ # Ok to drop the message
+ return False
+
+ def NextRequestId(self):
+ request_id = self._next_request_id
+ while request_id == 0 or request_id in self._responders:
+ request_id = (request_id + 1) % (1 << 64)
+ self._next_request_id = (request_id + 1) % (1 << 64)
+ return request_id
+
+class ForwardingMessageReceiver(MessageReceiver):
+ """A MessageReceiver that forward calls to |Accept| to a callable."""
+
+ def __init__(self, callback):
+ MessageReceiver.__init__(self)
+ self._callback = callback
+
+ def Accept(self, message):
+ return self._callback(message)
+
+
+def _WeakCallback(callback):
+ func = callback.im_func
+ self = callback.im_self
+ if not self:
+ return callback
+ weak_self = weakref.ref(self)
+ def Callback(*args, **kwargs):
+ self = weak_self()
+ if self:
+ return func(self, *args, **kwargs)
+ return Callback
+
+
+def _ReadAndDispatchMessage(handle, message_receiver):
+ (result, _, sizes) = handle.ReadMessage()
+ if result == system.RESULT_OK and message_receiver:
+ message_receiver.Accept(Message(bytearray(), []))
+ if result != system.RESULT_RESOURCE_EXHAUSTED:
+ return result
+ (result, data, _) = handle.ReadMessage(bytearray(sizes[0]))
+ if result == system.RESULT_OK and message_receiver:
+ message_receiver.Accept(Message(data[0], data[1]))
+ return result
+
+def _HasRequestId(flags):
+ return flags & (MESSAGE_EXPECTS_RESPONSE_FLAG|MESSAGE_IS_RESPONSE_FLAG) != 0
diff --git a/mojo/public/python/mojo/bindings/promise.py b/mojo/public/python/mojo/bindings/promise.py
new file mode 100644
index 0000000..c5d7d7c
--- /dev/null
+++ b/mojo/public/python/mojo/bindings/promise.py
@@ -0,0 +1,188 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Promise used by the python bindings.
+
+The API is following the ECMAScript 6 API for promises.
+"""
+
+
+class Promise(object):
+ """The promise object."""
+
+ STATE_PENDING = 0
+ STATE_FULLFILLED = 1
+ STATE_REJECTED = 2
+ STATE_BOUND = 3
+
+ def __init__(self, generator_function):
+ """
+ Constructor.
+
+ Args:
+ generator_function: A function taking 2 arguments: resolve and reject.
+ When |resolve| is called, the promise is fullfilled with the given value.
+ When |reject| is called, the promise is rejected with the given value.
+ A promise can only be resolved or rejected once, all following calls will
+ have no effect.
+ """
+ self._onCatched = []
+ self._onFulfilled = []
+ self._onRejected = []
+ self._state = Promise.STATE_PENDING
+ self._result = None
+ if generator_function:
+ generator_function(self._Resolve, self._Reject)
+
+ @staticmethod
+ def Resolve(value):
+ """
+ If value is a promise, make a promise that have the same behavior as value,
+ otherwise make a promise that fulfills to value.
+ """
+ if isinstance(value, Promise):
+ return value
+ return Promise(lambda x, y: x(value))
+
+ @staticmethod
+ def Reject(reason):
+ "Make a promise that rejects to reason."""
+ return Promise(lambda x, y: y(reason))
+
+ @staticmethod
+ def All(*iterable):
+ """
+ Make a promise that fulfills when every item in the array fulfills, and
+ rejects if (and when) any item rejects. Each array item is passed to
+ Promise.resolve, so the array can be a mixture of promise-like objects and
+ other objects. The fulfillment value is an array (in order) of fulfillment
+ values. The rejection value is the first rejection value.
+ """
+ def GeneratorFunction(resolve, reject):
+ state = {
+ 'rejected': False,
+ 'nb_resolved': 0,
+ }
+ promises = [Promise.Resolve(x) for x in iterable]
+ results = [None for x in promises]
+ def OnFullfilled(i):
+ def OnFullfilled(res):
+ if state['rejected']:
+ return
+ results[i] = res
+ state['nb_resolved'] = state['nb_resolved'] + 1
+ if state['nb_resolved'] == len(results):
+ resolve(results)
+ return OnFullfilled
+ def OnRejected(reason):
+ if state['rejected']:
+ return
+ state['rejected'] = True
+ reject(reason)
+
+ for (i, promise) in enumerate(promises):
+ promise.Then(OnFullfilled(i), OnRejected)
+ return Promise(GeneratorFunction)
+
+ @staticmethod
+ def Race(*iterable):
+ """
+ Make a Promise that fulfills as soon as any item fulfills, or rejects as
+ soon as any item rejects, whichever happens first.
+ """
+ def GeneratorFunction(resolve, reject):
+ state = {
+ 'ended': False
+ }
+ def OnEvent(callback):
+ def OnEvent(res):
+ if state['ended']:
+ return
+ state['ended'] = True
+ callback(res)
+ return OnEvent
+ for promise in [Promise.Resolve(x) for x in iterable]:
+ promise.Then(OnEvent(resolve), OnEvent(reject))
+ return Promise(GeneratorFunction)
+
+ @property
+ def state(self):
+ if isinstance(self._result, Promise):
+ return self._result.state
+ return self._state
+
+ def Then(self, onFullfilled=None, onRejected=None):
+ """
+ onFulfilled is called when/if this promise resolves. onRejected is called
+ when/if this promise rejects. Both are optional, if either/both are omitted
+ the next onFulfilled/onRejected in the chain is called. Both callbacks have
+ a single parameter, the fulfillment value or rejection reason. |Then|
+ returns a new promise equivalent to the value you return from
+ onFulfilled/onRejected after being passed through Resolve. If an
+ error is thrown in the callback, the returned promise rejects with that
+ error.
+ """
+ if isinstance(self._result, Promise):
+ return self._result.Then(onFullfilled, onRejected)
+ def GeneratorFunction(resolve, reject):
+ if self._state == Promise.STATE_PENDING:
+ self._onFulfilled.append(_Delegate(resolve, reject, onFullfilled))
+ self._onRejected.append(_Delegate(reject, reject, onRejected))
+ if self._state == self.STATE_FULLFILLED:
+ _Delegate(resolve, reject, onFullfilled)(self._result)
+ if self._state == self.STATE_REJECTED:
+ recover = reject
+ if onRejected:
+ recover = resolve
+ _Delegate(recover, reject, onRejected)(self._result)
+ return Promise(GeneratorFunction)
+
+ def Catch(self, onCatched):
+ """Equivalent to |Then(None, onCatched)|"""
+ return self.Then(None, onCatched)
+
+ def _Resolve(self, value):
+ if self.state != Promise.STATE_PENDING:
+ return
+ self._result = value
+ if isinstance(value, Promise):
+ self._state = Promise.STATE_BOUND
+ self._result.Then(_IterateAction(self._onFulfilled),
+ _IterateAction(self._onRejected))
+ return
+ self._state = Promise.STATE_FULLFILLED
+ for f in self._onFulfilled:
+ f(value)
+ self._onFulfilled = None
+ self._onRejected = None
+
+ def _Reject(self, reason):
+ if self.state != Promise.STATE_PENDING:
+ return
+ self._result = reason
+ self._state = Promise.STATE_REJECTED
+ for f in self._onRejected:
+ f(reason)
+ self._onFulfilled = None
+ self._onRejected = None
+
+
+def _IterateAction(iterable):
+ def _Run(x):
+ for f in iterable:
+ f(x)
+ return _Run
+
+
+def _Delegate(resolve, reject, action):
+ def _Run(x):
+ try:
+ if action:
+ resolve(action(x))
+ else:
+ resolve(x)
+ except Exception as e:
+ reject(e)
+ return _Run
diff --git a/mojo/public/python/mojo/bindings/reflection.py b/mojo/public/python/mojo/bindings/reflection.py
new file mode 100644
index 0000000..c682e5e
--- /dev/null
+++ b/mojo/public/python/mojo/bindings/reflection.py
@@ -0,0 +1,163 @@
+# Copyright 2014 The Chromium 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 metaclasses used by the mojo python bindings."""
+
+import itertools
+
+# pylint: disable=F0401
+import mojo.bindings.serialization as serialization
+
+
+class MojoEnumType(type):
+ """Meta class for enumerations.
+
+ Usage:
+ class MyEnum(object):
+ __metaclass__ = MojoEnumType
+ VALUES = [
+ ('A', 0),
+ 'B',
+ ('C', 5),
+ ]
+
+ This will define a enum with 3 values, 'A' = 0, 'B' = 1 and 'C' = 5.
+ """
+
+ def __new__(mcs, name, bases, dictionary):
+ dictionary['__slots__'] = ()
+ dictionary['__new__'] = None
+ for value in dictionary.pop('VALUES', []):
+ if not isinstance(value, tuple):
+ raise ValueError('incorrect value: %r' % value)
+ key, enum_value = value
+ if isinstance(key, str) and isinstance(enum_value, int):
+ dictionary[key] = enum_value
+ else:
+ raise ValueError('incorrect value: %r' % value)
+ return type.__new__(mcs, name, bases, dictionary)
+
+ def __setattr__(mcs, key, value):
+ raise AttributeError, 'can\'t set attribute'
+
+ def __delattr__(mcs, key):
+ raise AttributeError, 'can\'t delete attribute'
+
+
+class MojoStructType(type):
+ """Meta class for structs.
+
+ Usage:
+ class MyStruct(object):
+ __metaclass__ = MojoStructType
+ DESCRIPTOR = {
+ 'constants': {
+ 'C1': 1,
+ 'C2': 2,
+ },
+ 'enums': {
+ 'ENUM1': [
+ ('V1', 1),
+ 'V2',
+ ],
+ 'ENUM2': [
+ ('V1', 1),
+ 'V2',
+ ],
+ },
+ 'fields': [
+ FieldDescriptor('x', _descriptor.TYPE_INT32, 0),
+ ],
+ }
+
+ This will define an struct, with:
+ - 2 constants 'C1' and 'C2';
+ - 2 enums 'ENUM1' and 'ENUM2', each of those having 2 values, 'V1' and
+ 'V2';
+ - 1 int32 field named 'x'.
+ """
+
+ def __new__(mcs, name, bases, dictionary):
+ dictionary['__slots__'] = ('_fields')
+ descriptor = dictionary.pop('DESCRIPTOR', {})
+
+ # Add constants
+ dictionary.update(descriptor.get('constants', {}))
+
+ # Add enums
+ enums = descriptor.get('enums', {})
+ for key in enums:
+ dictionary[key] = MojoEnumType(key, (object,), { 'VALUES': enums[key] })
+
+ # Add fields
+ groups = descriptor.get('fields', [])
+
+ fields = list(
+ itertools.chain.from_iterable([group.descriptors for group in groups]))
+ fields.sort(key=lambda f: f.index)
+ for field in fields:
+ dictionary[field.name] = _BuildProperty(field)
+
+ # Add init
+ dictionary['__init__'] = _StructInit(fields)
+
+ # Add serialization method
+ serialization_object = serialization.Serialization(groups)
+ def Serialize(self, handle_offset=0):
+ return serialization_object.Serialize(self, handle_offset)
+ dictionary['Serialize'] = Serialize
+
+ def Deserialize(cls, data, handles):
+ result = cls.__new__(cls)
+ fields = {}
+ serialization_object.Deserialize(fields, data, handles)
+ result._fields = fields
+ return result
+ dictionary['Deserialize'] = classmethod(Deserialize)
+
+ return type.__new__(mcs, name, bases, dictionary)
+
+ # Prevent adding new attributes, or mutating constants.
+ def __setattr__(mcs, key, value):
+ raise AttributeError, 'can\'t set attribute'
+
+ # Prevent deleting constants.
+ def __delattr__(mcs, key):
+ raise AttributeError, 'can\'t delete attribute'
+
+
+def _StructInit(fields):
+ def _Init(self, *args, **kwargs):
+ if len(args) + len(kwargs) > len(fields):
+ raise TypeError('__init__() takes %d argument (%d given)' %
+ (len(fields), len(args) + len(kwargs)))
+ self._fields = {}
+ for f, a in zip(fields, args):
+ self.__setattr__(f.name, a)
+ remaining_fields = set(x.name for x in fields[len(args):])
+ for name in kwargs:
+ if not name in remaining_fields:
+ if name in (x.name for x in fields[:len(args)]):
+ raise TypeError(
+ '__init__() got multiple values for keyword argument %r' % name)
+ raise TypeError('__init__() got an unexpected keyword argument %r' %
+ name)
+ self.__setattr__(name, kwargs[name])
+ return _Init
+
+
+def _BuildProperty(field):
+ """Build the property for the given field."""
+
+ # pylint: disable=W0212
+ def Get(self):
+ if field.name not in self._fields:
+ self._fields[field.name] = field.GetDefaultValue()
+ return self._fields[field.name]
+
+ # pylint: disable=W0212
+ def Set(self, value):
+ self._fields[field.name] = field.field_type.Convert(value)
+
+ return property(Get, Set)
diff --git a/mojo/public/python/mojo/bindings/serialization.py b/mojo/public/python/mojo/bindings/serialization.py
new file mode 100644
index 0000000..9a4b6a9
--- /dev/null
+++ b/mojo/public/python/mojo/bindings/serialization.py
@@ -0,0 +1,126 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Utility classes for serialization"""
+
+import struct
+
+
+# Format of a header for a struct or an array.
+HEADER_STRUCT = struct.Struct("=II")
+
+
+class SerializationException(Exception):
+ """Error when strying to serialize a struct."""
+ pass
+
+
+class DeserializationException(Exception):
+ """Error when strying to deserialize a struct."""
+ pass
+
+
+class Serialization(object):
+ """
+ Helper class to serialize/deserialize a struct.
+ """
+ def __init__(self, groups):
+ self.version = _GetVersion(groups)
+ self._groups = groups
+ main_struct = _GetStruct(groups)
+ self.size = HEADER_STRUCT.size + main_struct.size
+ self._struct_per_version = {
+ self.version: main_struct,
+ }
+ self._groups_per_version = {
+ self.version: groups,
+ }
+
+ def _GetMainStruct(self):
+ return self._GetStruct(self.version)
+
+ def _GetGroups(self, version):
+ # If asking for a version greater than the last known.
+ version = min(version, self.version)
+ if version not in self._groups_per_version:
+ self._groups_per_version[version] = _FilterGroups(self._groups, version)
+ return self._groups_per_version[version]
+
+ def _GetStruct(self, version):
+ # If asking for a version greater than the last known.
+ version = min(version, self.version)
+ if version not in self._struct_per_version:
+ self._struct_per_version[version] = _GetStruct(self._GetGroups(version))
+ return self._struct_per_version[version]
+
+ def Serialize(self, obj, handle_offset):
+ """
+ Serialize the given obj. handle_offset is the the first value to use when
+ encoding handles.
+ """
+ handles = []
+ data = bytearray(self.size)
+ HEADER_STRUCT.pack_into(data, 0, self.size, self.version)
+ position = HEADER_STRUCT.size
+ to_pack = []
+ for group in self._groups:
+ position = position + NeededPaddingForAlignment(position,
+ group.GetByteSize())
+ (entry, new_handles) = group.Serialize(
+ obj,
+ len(data) - position,
+ data,
+ handle_offset + len(handles))
+ to_pack.append(entry)
+ handles.extend(new_handles)
+ position = position + group.GetByteSize()
+ self._GetMainStruct().pack_into(data, HEADER_STRUCT.size, *to_pack)
+ return (data, handles)
+
+ def Deserialize(self, fields, data, handles):
+ if not isinstance(data, buffer):
+ data = buffer(data)
+ (_, version) = HEADER_STRUCT.unpack_from(data)
+ version_struct = self._GetStruct(version)
+ entitities = version_struct.unpack_from(data, HEADER_STRUCT.size)
+ filtered_groups = self._GetGroups(version)
+ position = HEADER_STRUCT.size
+ for (group, value) in zip(filtered_groups, entitities):
+ position = position + NeededPaddingForAlignment(position,
+ group.GetByteSize())
+ fields.update(group.Deserialize(value, buffer(data, position), handles))
+ position += group.GetByteSize()
+
+
+def NeededPaddingForAlignment(value, alignment=8):
+ """Returns the padding necessary to align value with the given alignment."""
+ if value % alignment:
+ return alignment - (value % alignment)
+ return 0
+
+
+def _GetVersion(groups):
+ return sum([len(x.descriptors) for x in groups])
+
+
+def _FilterGroups(groups, version):
+ return [group for group in groups if group.GetVersion() < version]
+
+
+def _GetStruct(groups):
+ index = 0
+ codes = [ '=' ]
+ for group in groups:
+ code = group.GetTypeCode()
+ size = group.GetByteSize()
+ needed_padding = NeededPaddingForAlignment(index, size)
+ if needed_padding:
+ codes.append('x' * needed_padding)
+ index = index + needed_padding
+ codes.append(code)
+ index = index + size
+ alignment_needed = NeededPaddingForAlignment(index)
+ if alignment_needed:
+ codes.append('x' * alignment_needed)
+ return struct.Struct(''.join(codes))
diff --git a/mojo/public/python/mojo/c_core.pxd b/mojo/public/python/mojo/c_core.pxd
new file mode 100644
index 0000000..80b8487
--- /dev/null
+++ b/mojo/public/python/mojo/c_core.pxd
@@ -0,0 +1,201 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# distutils: language = c++
+
+from cpython.buffer cimport PyBUF_CONTIG
+from cpython.buffer cimport PyBUF_CONTIG_RO
+from cpython.buffer cimport Py_buffer
+from cpython.buffer cimport PyBuffer_FillInfo
+from cpython.buffer cimport PyBuffer_Release
+from cpython.buffer cimport PyObject_GetBuffer
+from cpython.mem cimport PyMem_Malloc, PyMem_Free
+from libc.stdint cimport int32_t, int64_t, uint32_t, uint64_t, uintptr_t
+
+cdef extern from "third_party/cython/python_export.h":
+ pass
+
+cdef extern from "mojo/public/platform/native/system_thunks.h" nogil:
+ cdef struct MojoSystemThunks:
+ pass
+
+cdef extern size_t MojoSetSystemThunks(const MojoSystemThunks* system_thunks)
+
+cdef extern from "mojo/public/c/system/core.h" nogil:
+ # types.h
+ ctypedef int64_t MojoTimeTicks
+
+ ctypedef uint32_t MojoHandle
+ const MojoHandle MOJO_HANDLE_INVALID
+
+ ctypedef int32_t MojoResult
+ const MojoResult MOJO_RESULT_OK
+ const MojoResult MOJO_RESULT_CANCELLED
+ const MojoResult MOJO_RESULT_UNKNOWN
+ const MojoResult MOJO_RESULT_INVALID_ARGUMENT
+ const MojoResult MOJO_RESULT_DEADLINE_EXCEEDED
+ const MojoResult MOJO_RESULT_NOT_FOUND
+ const MojoResult MOJO_RESULT_ALREADY_EXISTS
+ const MojoResult MOJO_RESULT_PERMISSION_DENIED
+ const MojoResult MOJO_RESULT_RESOURCE_EXHAUSTED
+ const MojoResult MOJO_RESULT_FAILED_PRECONDITION
+ const MojoResult MOJO_RESULT_ABORTED
+ const MojoResult MOJO_RESULT_OUT_OF_RANGE
+ const MojoResult MOJO_RESULT_UNIMPLEMENTED
+ const MojoResult MOJO_RESULT_INTERNAL
+ const MojoResult MOJO_RESULT_UNAVAILABLE
+ const MojoResult MOJO_RESULT_DATA_LOSS
+ const MojoResult MOJO_RESULT_BUSY
+ const MojoResult MOJO_RESULT_SHOULD_WAIT
+
+ ctypedef uint64_t MojoDeadline
+ const MojoDeadline MOJO_DEADLINE_INDEFINITE
+
+ ctypedef uint32_t MojoHandleSignals
+ const MojoHandleSignals MOJO_HANDLE_SIGNAL_NONE
+ const MojoHandleSignals MOJO_HANDLE_SIGNAL_READABLE
+ const MojoHandleSignals MOJO_HANDLE_SIGNAL_WRITABLE
+
+ # functions.h
+ MojoTimeTicks MojoGetTimeTicksNow()
+ MojoResult MojoClose(MojoHandle handle)
+ MojoResult MojoWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline)
+ MojoResult MojoWaitMany(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline)
+
+ # message_pipe.h
+ ctypedef uint32_t MojoCreateMessagePipeOptionsFlags
+ const MojoCreateMessagePipeOptionsFlags MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE
+
+ ctypedef uint32_t MojoWriteMessageFlags
+ const MojoWriteMessageFlags MOJO_WRITE_MESSAGE_FLAG_NONE
+
+ ctypedef uint32_t MojoReadMessageFlags
+ const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_NONE
+ const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_MAY_DISCARD
+
+ cdef struct MojoCreateMessagePipeOptions:
+ uint32_t struct_size
+ MojoCreateMessagePipeOptionsFlags flags
+
+ MojoResult MojoCreateMessagePipe(
+ const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* 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)
+
+ MojoResult MojoReadMessage(
+ MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags)
+
+ # data_pipe.h
+ ctypedef uint32_t MojoCreateDataPipeOptionsFlags
+ const MojoCreateDataPipeOptionsFlags MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE
+ const MojoCreateDataPipeOptionsFlags MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD
+
+ cdef struct MojoCreateDataPipeOptions:
+ uint32_t struct_size
+ MojoCreateDataPipeOptionsFlags flags
+ uint32_t element_num_bytes
+ uint32_t capacity_num_bytes
+
+ ctypedef uint32_t MojoWriteDataFlags
+
+ const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_NONE
+ const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_ALL_OR_NONE
+
+ ctypedef uint32_t MojoReadDataFlags
+
+ const MojoReadDataFlags MOJO_READ_DATA_FLAG_NONE
+ const MojoReadDataFlags MOJO_READ_DATA_FLAG_ALL_OR_NONE
+ const MojoReadDataFlags MOJO_READ_DATA_FLAG_DISCARD
+ const MojoReadDataFlags MOJO_READ_DATA_FLAG_QUERY
+
+ MojoResult MojoCreateDataPipe(
+ const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle)
+
+ MojoResult MojoWriteData(
+ MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags)
+
+ MojoResult MojoBeginWriteData(
+ MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags)
+
+ MojoResult MojoEndWriteData(
+ MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written)
+
+ MojoResult MojoReadData(
+ MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags)
+
+ MojoResult MojoBeginReadData(
+ MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags)
+
+ MojoResult MojoEndReadData(
+ MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read)
+
+ # buffer.h
+ ctypedef uint32_t MojoCreateSharedBufferOptionsFlags
+ const MojoCreateSharedBufferOptionsFlags MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE
+
+ cdef struct MojoCreateSharedBufferOptions:
+ uint32_t struct_size
+ MojoCreateSharedBufferOptionsFlags flags
+
+ ctypedef uint32_t MojoDuplicateBufferHandleOptionsFlags
+ const MojoDuplicateBufferHandleOptionsFlags MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE
+
+ cdef struct MojoDuplicateBufferHandleOptions:
+ uint32_t struct_size
+ MojoDuplicateBufferHandleOptionsFlags flags
+
+ ctypedef uint32_t MojoMapBufferFlags
+ const MojoMapBufferFlags MOJO_MAP_BUFFER_FLAG_NONE
+
+ MojoResult MojoCreateSharedBuffer(
+ const MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle)
+
+ MojoResult MojoDuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle)
+
+ MojoResult MojoMapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags)
+
+ MojoResult MojoUnmapBuffer(void* buffer)
diff --git a/mojo/public/python/mojo/c_environment.pxd b/mojo/public/python/mojo/c_environment.pxd
new file mode 100644
index 0000000..13a0934
--- /dev/null
+++ b/mojo/public/python/mojo/c_environment.pxd
@@ -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.
+
+# distutils: language = c++
+
+from libc.stdint cimport int64_t, intptr_t, uint32_t, uint64_t
+
+
+cdef extern from "mojo/public/c/system/core.h" nogil:
+ ctypedef uint32_t MojoHandle
+ ctypedef uint64_t MojoDeadline
+ ctypedef uint32_t MojoHandleSignals
+
+
+cdef extern from "mojo/public/cpp/bindings/callback.h" nogil:
+ cdef cppclass CClosure "mojo::Callback<void()>":
+ CClosure()
+
+
+cdef extern from "mojo/public/c/environment/async_waiter.h" nogil:
+ ctypedef intptr_t MojoAsyncWaitID
+
+
+cdef extern from "mojo/public/python/src/python_system_helper.h" \
+ namespace "mojo::python" nogil:
+ cdef CClosure BuildClosure(object)
+ cdef cppclass PythonAsyncWaiter "mojo::python::PythonAsyncWaiter":
+ PythonAsyncWaiter()
+ MojoAsyncWaitID AsyncWait(MojoHandle,
+ MojoHandleSignals,
+ MojoDeadline,
+ object)
+ void CancelWait(MojoAsyncWaitID)
+
+
+cdef extern from "mojo/public/cpp/utility/run_loop.h" nogil:
+ cdef cppclass CRunLoop "mojo::RunLoop":
+ CRunLoop()
+ void Run() except *
+ void RunUntilIdle() except *
+ void Quit()
+ void PostDelayedTask(CClosure&, int64_t)
+
+
+cdef extern from "mojo/public/cpp/environment/environment.h" nogil:
+ cdef cppclass CEnvironment "mojo::Environment":
+ CEnvironment()
diff --git a/mojo/public/python/mojo/system.pyx b/mojo/public/python/mojo/system.pyx
new file mode 100644
index 0000000..4768247
--- /dev/null
+++ b/mojo/public/python/mojo/system.pyx
@@ -0,0 +1,741 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# distutils language = c++
+
+cimport c_core
+cimport c_environment
+
+
+from cpython.buffer cimport PyBUF_CONTIG
+from cpython.buffer cimport PyBUF_CONTIG_RO
+from cpython.buffer cimport Py_buffer
+from cpython.buffer cimport PyBuffer_FillInfo
+from cpython.buffer cimport PyBuffer_Release
+from cpython.buffer cimport PyObject_GetBuffer
+from cpython.mem cimport PyMem_Malloc, PyMem_Free
+from libc.stdint cimport int32_t, int64_t, uint32_t, uint64_t, uintptr_t
+
+def SetSystemThunks(system_thunks_as_object):
+ """Bind the basic Mojo Core functions.
+
+ This should only be used by the embedder.
+ """
+ cdef const c_core.MojoSystemThunks* system_thunks = (
+ <const c_core.MojoSystemThunks*><uintptr_t>system_thunks_as_object)
+ c_core.MojoSetSystemThunks(system_thunks)
+
+HANDLE_INVALID = c_core.MOJO_HANDLE_INVALID
+RESULT_OK = c_core.MOJO_RESULT_OK
+RESULT_CANCELLED = c_core.MOJO_RESULT_CANCELLED
+RESULT_UNKNOWN = c_core.MOJO_RESULT_UNKNOWN
+RESULT_INVALID_ARGUMENT = c_core.MOJO_RESULT_INVALID_ARGUMENT
+RESULT_DEADLINE_EXCEEDED = c_core.MOJO_RESULT_DEADLINE_EXCEEDED
+RESULT_NOT_FOUND = c_core.MOJO_RESULT_NOT_FOUND
+RESULT_ALREADY_EXISTS = c_core.MOJO_RESULT_ALREADY_EXISTS
+RESULT_PERMISSION_DENIED = c_core.MOJO_RESULT_PERMISSION_DENIED
+RESULT_RESOURCE_EXHAUSTED = c_core.MOJO_RESULT_RESOURCE_EXHAUSTED
+RESULT_FAILED_PRECONDITION = c_core.MOJO_RESULT_FAILED_PRECONDITION
+RESULT_ABORTED = c_core.MOJO_RESULT_ABORTED
+RESULT_OUT_OF_RANGE = c_core.MOJO_RESULT_OUT_OF_RANGE
+RESULT_UNIMPLEMENTED = c_core.MOJO_RESULT_UNIMPLEMENTED
+RESULT_INTERNAL = c_core.MOJO_RESULT_INTERNAL
+RESULT_UNAVAILABLE = c_core.MOJO_RESULT_UNAVAILABLE
+RESULT_DATA_LOSS = c_core.MOJO_RESULT_DATA_LOSS
+RESULT_BUSY = c_core.MOJO_RESULT_BUSY
+RESULT_SHOULD_WAIT = c_core.MOJO_RESULT_SHOULD_WAIT
+DEADLINE_INDEFINITE = c_core.MOJO_DEADLINE_INDEFINITE
+HANDLE_SIGNAL_NONE = c_core.MOJO_HANDLE_SIGNAL_NONE
+HANDLE_SIGNAL_READABLE = c_core.MOJO_HANDLE_SIGNAL_READABLE
+HANDLE_SIGNAL_WRITABLE = c_core.MOJO_HANDLE_SIGNAL_WRITABLE
+WRITE_MESSAGE_FLAG_NONE = c_core.MOJO_WRITE_MESSAGE_FLAG_NONE
+READ_MESSAGE_FLAG_NONE = c_core.MOJO_READ_MESSAGE_FLAG_NONE
+READ_MESSAGE_FLAG_MAY_DISCARD = c_core.MOJO_READ_MESSAGE_FLAG_MAY_DISCARD
+WRITE_DATA_FLAG_NONE = c_core.MOJO_WRITE_DATA_FLAG_NONE
+WRITE_DATA_FLAG_ALL_OR_NONE = c_core.MOJO_WRITE_DATA_FLAG_ALL_OR_NONE
+READ_DATA_FLAG_NONE = c_core.MOJO_READ_DATA_FLAG_NONE
+READ_DATA_FLAG_ALL_OR_NONE = c_core.MOJO_READ_DATA_FLAG_ALL_OR_NONE
+READ_DATA_FLAG_DISCARD = c_core.MOJO_READ_DATA_FLAG_DISCARD
+READ_DATA_FLAG_QUERY = c_core.MOJO_READ_DATA_FLAG_QUERY
+MAP_BUFFER_FLAG_NONE = c_core.MOJO_MAP_BUFFER_FLAG_NONE
+
+def GetTimeTicksNow():
+ """Monotonically increasing tick count representing "right now."
+
+ See mojo/public/c/system/functions.h
+ """
+ return c_core.MojoGetTimeTicksNow()
+
+cdef class _ScopedMemory:
+ """Allocate memory at creation, and deallocate it at destruction."""
+ cdef void* memory
+ def __init__(self, size):
+ self.memory = PyMem_Malloc(size)
+
+ def __dealloc__(self):
+ PyMem_Free(self.memory)
+
+cdef class _ScopedBuffer:
+ """Retrieve pointer to a buffer a creation, and release it at destruction.
+ """
+ cdef Py_buffer _buf
+ cdef void* buf
+ cdef Py_ssize_t len
+
+ def __init__(self, obj, flags=PyBUF_CONTIG_RO):
+ if obj:
+ if PyObject_GetBuffer(obj, &self._buf, flags) < 0:
+ raise TypeError('Unable to read buffer.')
+ self.buf = self._buf.buf
+ self.len = self._buf.len
+ else:
+ self.buf = NULL
+ self.len = 0
+
+ def __dealloc__(self):
+ if self.buf:
+ PyBuffer_Release(&self._buf)
+
+def _SliceBuffer(buffer, size):
+ """Slice the given buffer, reducing it to the given size.
+
+ Return None if None is passed in.
+ """
+ if not buffer:
+ return buffer
+ return buffer[:size]
+
+cdef class _NativeMemoryView(object):
+ """Create a python buffer wrapping the given memory.
+
+ Will also retain the given handle until this object is deallocated.
+ """
+ cdef void* _memory
+ cdef uint32_t _size
+ cdef char _read_only
+ cdef char _wrapped
+ cdef object _handle
+
+ def __init__(self, handle):
+ self._handle = handle
+
+ def __cinit__(self):
+ self._memory = NULL
+ self._size = 0
+ self._read_only = True
+ self._wrapped = False
+
+ cdef Wrap(self,
+ const void* memory,
+ uint32_t size,
+ read_only=True):
+ """Makes this buffer wraps the given memory.
+
+ Must be called before using this buffer, and must only be called once.
+ """
+ assert not self._wrapped
+ self._wrapped = True
+ self._memory = <void*>memory
+ self._size = size
+ self._read_only = read_only
+
+ # buffer interface (PEP 3118)
+ def __getbuffer__(self, Py_buffer *view, int flags):
+ assert self._wrapped
+ if view == NULL:
+ return
+ PyBuffer_FillInfo(view,
+ self,
+ self._memory,
+ self._size,
+ self._read_only,
+ flags)
+
+ def __releasebuffer__(self, Py_buffer *view):
+ assert self._wrapped
+ pass
+
+ # legacy buffer interface
+ def __getsegcount__(self, Py_ssize_t *sizes):
+ assert self._wrapped
+ if sizes != NULL:
+ sizes[0] = self._size
+ return 1
+
+ def __getreadbuffer__(self, Py_ssize_t index, void **data):
+ assert self._wrapped
+ if index != 0:
+ raise SystemError('Index out of bounds: %d' % index)
+ data[0] = self._memory
+ return self._size
+
+ def __getwritebuffer__(self, Py_ssize_t index, void **data):
+ assert self._wrapped
+ if index != 0:
+ raise SystemError('Index out of bounds: %d' % index)
+ if self._read_only:
+ raise TypeError('Buffer is read-only.')
+ data[0] = self._memory
+ return self._size
+
+class MojoException(Exception):
+ """Exception wrapping a mojo result error code."""
+
+ def __init__(self, mojo_result):
+ self.mojo_result = mojo_result
+
+def WaitMany(handles_and_signals, deadline):
+ """Waits on a list of handles.
+
+ Args:
+ handles_and_signals: list of tuples of handle and signal.
+
+ See mojo/public/c/system/functions.h
+ """
+ cdef uint32_t length = len(handles_and_signals)
+ cdef _ScopedMemory handles_alloc = _ScopedMemory(
+ sizeof(c_core.MojoHandle) * length)
+ cdef _ScopedMemory signals_alloc = _ScopedMemory(
+ sizeof(c_core.MojoHandleSignals) * length)
+ cdef c_core.MojoHandle* handles = <c_core.MojoHandle*>handles_alloc.memory
+ cdef c_core.MojoHandleSignals* signals = (
+ <c_core.MojoHandleSignals*>signals_alloc.memory)
+ cdef int index = 0
+ for (h, s) in handles_and_signals:
+ handles[index] = (<Handle?>h)._mojo_handle
+ signals[index] = s
+ index += 1
+ cdef c_core.MojoResult result = c_core.MOJO_RESULT_OK
+ cdef c_core.MojoDeadline cdeadline = deadline
+ with nogil:
+ result = c_core.MojoWaitMany(handles, signals, length, cdeadline)
+ return result
+
+cdef class DataPipeTwoPhaseBuffer(object):
+ """Return value for two phases read and write.
+
+ The buffer field contains the python buffer where data can be read or written.
+ When done with the buffer, the |end| method must be called with the number of
+ bytes read or written.
+ """
+
+ cdef object _buffer
+ cdef Handle _handle
+ cdef char _read
+
+ def __init__(self, handle, buffer, read=True):
+ self._buffer = buffer
+ self._handle = handle
+ self._read = read
+
+ def End(self, num_bytes):
+ self._buffer = None
+ cdef c_core.MojoResult result
+ if self._read:
+ result = c_core.MojoEndReadData(self._handle._mojo_handle, num_bytes)
+ else:
+ result = c_core.MojoEndWriteData(self._handle._mojo_handle, num_bytes)
+ self._handle = None
+ return result
+
+ @property
+ def buffer(self):
+ return self._buffer
+
+ def __dealloc__(self):
+ assert not self._buffer
+
+cdef class MappedBuffer(object):
+ """Return value for the |map| operation on shared buffer handles.
+
+ The buffer field contains the python buffer where data can be read or written.
+ When done with the buffer, the |unmap| method must be called.
+ """
+
+ cdef object _buffer
+ cdef object _handle
+ cdef object _cleanup
+
+ def __init__(self, handle, buffer, cleanup):
+ self._buffer = buffer
+ self._handle = handle
+ self._cleanup = cleanup
+
+ def UnMap(self):
+ self._buffer = None
+ cdef c_core.MojoResult result = self._cleanup()
+ self._cleanup = None
+ self._handle = None
+ return result
+
+ @property
+ def buffer(self):
+ return self._buffer
+
+ def __dealloc__(self):
+ if self._buffer:
+ self.UnMap()
+
+cdef class Handle(object):
+ """A mojo object."""
+
+ cdef c_core.MojoHandle _mojo_handle
+
+ def __init__(self, mojo_handle=c_core.MOJO_HANDLE_INVALID):
+ self._mojo_handle = mojo_handle
+
+ def _Invalidate(self):
+ """Invalidate the current handle.
+
+ The close operation is not called. It is the responsability of the caller to
+ ensure that the handle is not leaked.
+ """
+ self._mojo_handle = c_core.MOJO_HANDLE_INVALID
+
+ def IsValid(self):
+ """Returns whether this handle is valid."""
+ return self._mojo_handle != c_core.MOJO_HANDLE_INVALID
+
+ def Close(self):
+ """Closes this handle.
+
+ See mojo/public/c/system/functions.h
+ """
+ cdef c_core.MojoResult result = c_core.MOJO_RESULT_OK
+ if self.IsValid():
+ result = c_core.MojoClose(self._mojo_handle)
+ self._Invalidate()
+ return result
+
+ def __dealloc__(self):
+ self.Close()
+
+ def Wait(self, signals, deadline):
+ """Waits on the given handle.
+
+ See mojo/public/c/system/functions.h
+ """
+ cdef c_core.MojoHandle handle = self._mojo_handle
+ cdef c_core.MojoHandleSignals csignals = signals
+ cdef c_core.MojoDeadline cdeadline = deadline
+ cdef c_core.MojoResult result
+ with nogil:
+ result = c_core.MojoWait(handle, csignals, cdeadline)
+ return result
+
+ def AsyncWait(self, signals, deadline, callback):
+ cdef c_core.MojoHandle handle = self._mojo_handle
+ cdef c_core.MojoHandleSignals csignals = signals
+ cdef c_core.MojoDeadline cdeadline = deadline
+ cdef c_environment.MojoAsyncWaitID wait_id = _ASYNC_WAITER.AsyncWait(
+ handle,
+ csignals,
+ cdeadline,
+ callback)
+ def cancel():
+ _ASYNC_WAITER.CancelWait(wait_id)
+ return cancel
+
+ def WriteMessage(self,
+ buffer=None,
+ handles=None,
+ flags=WRITE_MESSAGE_FLAG_NONE):
+ """Writes a message to the message pipe.
+
+ This method can only be used on a handle obtained from |MessagePipe()|.
+
+ See mojo/public/c/system/message_pipe.h
+ """
+ cdef _ScopedBuffer buffer_as_buffer = _ScopedBuffer(buffer)
+ cdef uint32_t input_buffer_length = buffer_as_buffer.len
+ cdef c_core.MojoHandle* input_handles = NULL
+ cdef uint32_t input_handles_length = 0
+ cdef _ScopedMemory handles_alloc = None
+ if handles:
+ input_handles_length = len(handles)
+ handles_alloc = _ScopedMemory(sizeof(c_core.MojoHandle) *
+ input_handles_length)
+ input_handles = <c_core.MojoHandle*>handles_alloc.memory
+ for i in xrange(input_handles_length):
+ input_handles[i] = (<Handle?>handles[i])._mojo_handle
+ cdef c_core.MojoResult res = c_core.MojoWriteMessage(self._mojo_handle,
+ buffer_as_buffer.buf,
+ input_buffer_length,
+ input_handles,
+ input_handles_length,
+ flags)
+ if res == c_core.MOJO_RESULT_OK and handles:
+ # Handles have been transferred. Let's invalidate those.
+ for handle in handles:
+ handle._Invalidate()
+ return res
+
+ def ReadMessage(self,
+ buffer=None,
+ max_number_of_handles=0,
+ flags=READ_MESSAGE_FLAG_NONE):
+ """Reads a message from the message pipe.
+
+ This method can only be used on a handle obtained from |MessagePipe()|.
+
+ This method returns a triplet of value (code, data, sizes):
+ - if code is RESULT_OK, sizes will be None, and data will be a pair of
+ (buffer, handles) where buffer is a view of the input buffer with the read
+ data, and handles is a list of received handles.
+ - if code is RESULT_RESOURCE_EXHAUSTED, data will be None and sizes will be
+ a pair of (buffer_size, handles_size) where buffer_size is the size of the
+ next message data and handles_size is the number of handles in the next
+ message.
+ - if code is any other value, data and sizes will be None.
+
+ See mojo/public/c/system/message_pipe.h
+ """
+ cdef _ScopedBuffer buffer_as_buffer = _ScopedBuffer(buffer, PyBUF_CONTIG)
+ cdef uint32_t input_buffer_length = buffer_as_buffer.len
+ cdef c_core.MojoHandle* input_handles = NULL
+ cdef uint32_t input_handles_length = 0
+ cdef _ScopedMemory handles_alloc = None
+ if max_number_of_handles > 0:
+ input_handles_length = max_number_of_handles
+ handles_alloc = _ScopedMemory(sizeof(c_core.MojoHandle) *
+ input_handles_length)
+ input_handles = <c_core.MojoHandle*>handles_alloc.memory
+ cdef res = c_core.MojoReadMessage(self._mojo_handle,
+ buffer_as_buffer.buf,
+ &input_buffer_length,
+ input_handles,
+ &input_handles_length,
+ flags)
+ if res == c_core.MOJO_RESULT_RESOURCE_EXHAUSTED:
+ return (res, None, (input_buffer_length, input_handles_length))
+ if res == c_core.MOJO_RESULT_OK:
+ returned_handles = [Handle(input_handles[i])
+ for i in xrange(input_handles_length)]
+ return (res,
+ (_SliceBuffer(buffer, input_buffer_length), returned_handles),
+ None)
+ return (res, None, None)
+
+ def WriteData(self, buffer=None, flags=WRITE_DATA_FLAG_NONE):
+ """
+ Writes the given data to the data pipe producer.
+
+ This method can only be used on a producer handle obtained from
+ |DataPipe()|.
+
+ This method returns a tuple (code, num_bytes).
+ - If code is RESULT_OK, num_bytes is the number of written bytes.
+ - Otherwise, num_bytes is None.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ cdef _ScopedBuffer buffer_as_buffer = _ScopedBuffer(buffer)
+ cdef uint32_t input_buffer_length = buffer_as_buffer.len
+ cdef c_core.MojoResult res = c_core.MojoWriteData(self._mojo_handle,
+ buffer_as_buffer.buf,
+ &input_buffer_length,
+ flags)
+ if res == c_core.MOJO_RESULT_OK:
+ return (res, input_buffer_length)
+ return (res, None)
+
+ def BeginWriteData(self,
+ min_size=None,
+ flags=WRITE_DATA_FLAG_NONE):
+ """
+ Begins a two-phase write to the data pipe producer.
+
+ This method can only be used on a producer handle obtained from
+ |DataPipe()|.
+
+ This method returns a tuple (code, two_phase_buffer).
+ - If code is RESULT_OK, two_phase_buffer is a writable
+ DataPipeTwoPhaseBuffer
+ - Otherwise, two_phase_buffer is None.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ cdef void* out_buffer
+ cdef uint32_t out_size = 0
+ if min_size:
+ flags |= c_core.MOJO_WRITE_DATA_FLAG_ALL_OR_NONE
+ out_size = min_size
+ cdef c_core.MojoResult res = c_core.MojoBeginWriteData(self._mojo_handle,
+ &out_buffer,
+ &out_size,
+ flags)
+ if res != c_core.MOJO_RESULT_OK:
+ return (res, None)
+ cdef _NativeMemoryView view_buffer = _NativeMemoryView(self)
+ view_buffer.Wrap(out_buffer, out_size, read_only=False)
+ return (res, DataPipeTwoPhaseBuffer(self, memoryview(view_buffer), False))
+
+ def ReadData(self, buffer=None, flags=READ_DATA_FLAG_NONE):
+ """Reads data from the data pipe consumer.
+
+ This method can only be used on a consumer handle obtained from
+ |DataPipe()|.
+
+ This method returns a tuple (code, buffer)
+ - if code is RESULT_OK, buffer will be a view of the input buffer with the
+ read data.
+ - otherwise, buffer will be None.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ cdef _ScopedBuffer buffer_as_buffer = _ScopedBuffer(buffer)
+ cdef uint32_t input_buffer_length = buffer_as_buffer.len
+ cdef c_core.MojoResult res = c_core.MojoReadData(self._mojo_handle,
+ buffer_as_buffer.buf,
+ &input_buffer_length,
+ flags)
+ if res == c_core.MOJO_RESULT_OK:
+ return (res, _SliceBuffer(buffer, input_buffer_length))
+ return (res, None)
+
+ def QueryData(self, flags=READ_DATA_FLAG_NONE):
+ """Queries the amount of data available on the data pipe consumer.
+
+ This method can only be used on a consumer handle obtained from
+ |DataPipe()|.
+
+ This method returns a tuple (code, num_bytes)
+ - if code is RESULT_OK, num_bytes will be the number of bytes available on
+ the data pipe consumer.
+ - otherwise, num_bytes will be None.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ cdef uint32_t num_bytes = 0
+ cdef c_core.MojoResult res = c_core.MojoReadData(
+ self._mojo_handle,
+ NULL,
+ &num_bytes,
+ flags|c_core.MOJO_READ_DATA_FLAG_QUERY)
+ return (res, num_bytes)
+
+ def BeginReadData(self, min_size=None, flags=READ_DATA_FLAG_NONE):
+ """
+ Begins a two-phase read to the data pipe consumer.
+
+ This method can only be used on a consumer handle obtained from
+ |DataPipe()|.
+
+ This method returns a tuple (code, two_phase_buffer).
+ - If code is RESULT_OK, two_phase_buffer is a readable
+ DataPipeTwoPhaseBuffer
+ - Otherwise, two_phase_buffer is None.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ cdef const void* out_buffer
+ cdef uint32_t out_size = 0
+ if min_size:
+ flags |= c_core.MOJO_READ_DATA_FLAG_ALL_OR_NONE
+ out_size = min_size
+ cdef c_core.MojoResult res = c_core.MojoBeginReadData(self._mojo_handle,
+ &out_buffer,
+ &out_size,
+ flags)
+ if res != c_core.MOJO_RESULT_OK:
+ return (res, None)
+ cdef _NativeMemoryView view_buffer = _NativeMemoryView(self)
+ view_buffer.Wrap(out_buffer, out_size, read_only=True)
+ return (res, DataPipeTwoPhaseBuffer(self, memoryview(view_buffer), True))
+
+ def Duplicate(self, options=None):
+ """Duplicate the shared buffer handle.
+
+ This method can only be used on a handle obtained from
+ |CreateSharedBuffer()| or |Duplicate()|.
+
+ See mojo/public/c/system/buffer.h
+ """
+ cdef c_core.MojoDuplicateBufferHandleOptions coptions
+ cdef c_core.MojoDuplicateBufferHandleOptions* coptions_ptr = NULL
+ cdef c_core.MojoHandle cnew_handle = c_core.MOJO_HANDLE_INVALID
+ if options:
+ coptions.struct_size = sizeof(c_core.MojoDuplicateBufferHandleOptions)
+ coptions.flags = options.flags
+ coptions_ptr = &coptions
+ cdef c_core.MojoResult result = c_core.MojoDuplicateBufferHandle(
+ self._mojo_handle, coptions_ptr, &cnew_handle)
+ new_handle = Handle(cnew_handle)
+ if result != c_core.MOJO_RESULT_OK:
+ raise MojoException(result)
+ return new_handle
+
+ def Map(self, offset, num_bytes, flags=MAP_BUFFER_FLAG_NONE):
+ """Maps the part (at offset |offset| of length |num_bytes|) of the buffer.
+
+ This method can only be used on a handle obtained from
+ |CreateSharedBuffer()| or |Duplicate()|.
+
+ This method returns a tuple (code, mapped_buffer).
+ - If code is RESULT_OK, mapped_buffer is a readable/writable
+ MappedBuffer
+ - Otherwise, mapped_buffer is None.
+
+ See mojo/public/c/system/buffer.h
+ """
+ cdef void* buffer
+ res = c_core.MojoMapBuffer(self._mojo_handle,
+ offset,
+ num_bytes,
+ &buffer,
+ flags)
+ if res != c_core.MOJO_RESULT_OK:
+ return (res, None)
+ cdef _NativeMemoryView view_buffer = _NativeMemoryView(self)
+ view_buffer.Wrap(buffer, num_bytes, read_only=False)
+ return (res, MappedBuffer(self,
+ memoryview(view_buffer),
+ lambda: c_core.MojoUnmapBuffer(buffer)))
+
+class CreateMessagePipeOptions(object):
+ """Options for creating a message pipe.
+
+ See mojo/public/c/system/message_pipe.h
+ """
+ FLAG_NONE = c_core.MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE
+
+ def __init__(self):
+ self.flags = CreateMessagePipeOptions.FLAG_NONE
+
+class MessagePipe(object):
+ """Creates a message pipe.
+
+ The two ends of the message pipe are accessible with the members handle0 and
+ handle1.
+
+ See mojo/public/c/system/message_pipe.h
+ """
+ def __init__(self, options=None):
+ cdef c_core.MojoCreateMessagePipeOptions coptions
+ cdef c_core.MojoCreateMessagePipeOptions* coptions_ptr = NULL
+ cdef c_core.MojoHandle chandle0 = c_core.MOJO_HANDLE_INVALID
+ cdef c_core.MojoHandle chandle1 = c_core.MOJO_HANDLE_INVALID
+ if options:
+ coptions.struct_size = sizeof(c_core.MojoCreateMessagePipeOptions)
+ coptions.flags = options.flags
+ coptions_ptr = &coptions
+ cdef c_core.MojoResult result = c_core.MojoCreateMessagePipe(coptions_ptr,
+ &chandle0,
+ &chandle1)
+ self.handle0 = Handle(chandle0)
+ self.handle1 = Handle(chandle1)
+ if result != c_core.MOJO_RESULT_OK:
+ raise c_core.MojoException(result)
+
+
+class CreateDataPipeOptions(object):
+ """Options for creating a data pipe.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ FLAG_NONE = c_core.MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE
+ FLAG_MAY_DISCARD = c_core.MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD
+
+ def __init__(self):
+ self.flags = CreateDataPipeOptions.FLAG_NONE
+ self.element_num_bytes = 1
+ self.capacity_num_bytes = 0
+
+class DataPipe(object):
+ """Creates a data pipe.
+
+ The producer end of the data pipe is accessible with the member
+ producer_handle and the consumer end of the data pipe is accessible with the
+ member cconsumer_handle.
+
+ See mojo/public/c/system/data_pipe.h
+ """
+ def __init__(self, options=None):
+ cdef c_core.MojoCreateDataPipeOptions coptions
+ cdef c_core.MojoCreateDataPipeOptions* coptions_ptr = NULL
+ cdef c_core.MojoHandle cproducer_handle = c_core.MOJO_HANDLE_INVALID
+ cdef c_core.MojoHandle cconsumer_handle = c_core.MOJO_HANDLE_INVALID
+ if options:
+ coptions.struct_size = sizeof(c_core.MojoCreateDataPipeOptions)
+ coptions.flags = options.flags
+ coptions.element_num_bytes = options.element_num_bytes
+ coptions.capacity_num_bytes = options.capacity_num_bytes
+ coptions_ptr = &coptions
+ cdef c_core.MojoResult result = c_core.MojoCreateDataPipe(coptions_ptr,
+ &cproducer_handle,
+ &cconsumer_handle)
+ self.producer_handle = Handle(cproducer_handle)
+ self.consumer_handle = Handle(cconsumer_handle)
+ if result != c_core.MOJO_RESULT_OK:
+ raise MojoException(result)
+
+class CreateSharedBufferOptions(object):
+ """Options for creating a shared buffer.
+
+ See mojo/public/c/system/buffer.h
+ """
+ FLAG_NONE = c_core.MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE
+
+ def __init__(self):
+ self.flags = CreateSharedBufferOptions.FLAG_NONE
+
+def CreateSharedBuffer(num_bytes, options=None):
+ """Creates a buffer of size |num_bytes| bytes that can be shared.
+
+ See mojo/public/c/system/buffer.h
+ """
+ cdef c_core.MojoCreateSharedBufferOptions coptions
+ cdef c_core.MojoCreateSharedBufferOptions* coptions_ptr = NULL
+ cdef c_core.MojoHandle chandle = c_core.MOJO_HANDLE_INVALID
+ if options:
+ coptions.struct_size = sizeof(c_core.MojoCreateSharedBufferOptions)
+ coptions.flags = options.flags
+ coptions_ptr = &coptions
+ cdef c_core.MojoResult result = c_core.MojoCreateSharedBuffer(coptions_ptr,
+ num_bytes,
+ &chandle)
+ handle = Handle(chandle)
+ if result != c_core.MOJO_RESULT_OK:
+ raise MojoException(result)
+ return handle
+
+class DuplicateSharedBufferOptions(object):
+ """Options for duplicating a shared buffer.
+
+ See mojo/public/c/system/buffer.h
+ """
+ FLAG_NONE = c_core.MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE
+
+ def __init__(self):
+ self.flags = DuplicateSharedBufferOptions.FLAG_NONE
+
+
+cdef class RunLoop(object):
+ """RunLoop to use when using asynchronous operations on handles."""
+
+ cdef c_environment.CRunLoop c_run_loop
+
+ def Run(self):
+ """Run the runloop until Quit is called."""
+ self.c_run_loop.Run()
+
+ def RunUntilIdle(self):
+ """Run the runloop until Quit is called or no operation is waiting."""
+ self.c_run_loop.RunUntilIdle()
+
+ def Quit(self):
+ """Quit the runloop."""
+ self.c_run_loop.Quit()
+
+ def PostDelayedTask(self, runnable, delay=0):
+ """
+ Post a task on the runloop. This must be called from the thread owning the
+ runloop.
+ """
+ cdef c_environment.CClosure closure = c_environment.BuildClosure(runnable)
+ self.c_run_loop.PostDelayedTask(closure, delay)
+
+
+cdef c_environment.CEnvironment* _ENVIRONMENT = new c_environment.CEnvironment()
+cdef c_environment.PythonAsyncWaiter* _ASYNC_WAITER = new c_environment.PythonAsyncWaiter()
diff --git a/mojo/public/python/src/python_system_helper.cc b/mojo/public/python/src/python_system_helper.cc
new file mode 100644
index 0000000..f42e671
--- /dev/null
+++ b/mojo/public/python/src/python_system_helper.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium 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/python/src/python_system_helper.h"
+
+#include "Python.h"
+
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/environment/logging.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+
+namespace {
+
+class ScopedGIL {
+ public:
+ ScopedGIL() { state_ = PyGILState_Ensure(); }
+
+ ~ScopedGIL() { PyGILState_Release(state_); }
+
+ private:
+ PyGILState_STATE state_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ScopedGIL);
+};
+
+class PythonClosure : public mojo::Closure::Runnable {
+ public:
+ PythonClosure(PyObject* callable) : callable_(callable) {
+ MOJO_DCHECK(callable);
+ Py_XINCREF(callable);
+ }
+
+ virtual ~PythonClosure() {
+ ScopedGIL acquire_gil;
+ Py_DECREF(callable_);
+ }
+
+ virtual void Run() const override {
+ ScopedGIL acquire_gil;
+ PyObject* empty_tuple = PyTuple_New(0);
+ if (!empty_tuple) {
+ mojo::RunLoop::current()->Quit();
+ return;
+ }
+
+ PyObject* result = PyObject_CallObject(callable_, empty_tuple);
+ Py_DECREF(empty_tuple);
+ if (result) {
+ Py_DECREF(result);
+ } else {
+ mojo::RunLoop::current()->Quit();
+ return;
+ }
+ }
+
+ private:
+ PyObject* callable_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PythonClosure);
+};
+
+void AsyncCallbackForwarder(void* closure, MojoResult result) {
+ mojo::Callback<void(MojoResult)>* callback =
+ static_cast<mojo::Callback<void(MojoResult)>*>(closure);
+ // callback will be deleted when it is run.
+ callback->Run(result);
+}
+
+} // namespace
+
+namespace mojo {
+namespace python {
+
+class PythonAsyncWaiter::AsyncWaiterRunnable
+ : public mojo::Callback<void(MojoResult)>::Runnable {
+ public:
+ AsyncWaiterRunnable(PyObject* callable, CallbackMap* callbacks)
+ : wait_id_(0), callable_(callable), callbacks_(callbacks) {
+ MOJO_DCHECK(callable);
+ MOJO_DCHECK(callbacks_);
+ Py_XINCREF(callable);
+ }
+
+ virtual ~AsyncWaiterRunnable() {
+ ScopedGIL acquire_gil;
+ Py_DECREF(callable_);
+ }
+
+ void set_wait_id(int wait_id) { wait_id_ = wait_id; }
+
+ virtual void Run(MojoResult mojo_result) const override {
+ MOJO_DCHECK(wait_id_);
+
+ // Remove to reference to this object from PythonAsyncWaiter and ensure this
+ // object will be destroyed when this method exits.
+ MOJO_DCHECK(callbacks_->find(wait_id_) != callbacks_->end());
+ internal::SharedPtr<mojo::Callback<void(MojoResult)> > self =
+ (*callbacks_)[wait_id_];
+ callbacks_->erase(wait_id_);
+
+ ScopedGIL acquire_gil;
+ PyObject* args_tuple = Py_BuildValue("(i)", mojo_result);
+ if (!args_tuple) {
+ mojo::RunLoop::current()->Quit();
+ return;
+ }
+
+ PyObject* result = PyObject_CallObject(callable_, args_tuple);
+ Py_DECREF(args_tuple);
+ if (result) {
+ Py_DECREF(result);
+ } else {
+ mojo::RunLoop::current()->Quit();
+ return;
+ }
+ }
+
+ private:
+ MojoAsyncWaitID wait_id_;
+ PyObject* callable_;
+ CallbackMap* callbacks_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AsyncWaiterRunnable);
+};
+
+Closure BuildClosure(PyObject* callable) {
+ if (!PyCallable_Check(callable))
+ return Closure();
+
+ return Closure(
+ static_cast<mojo::Closure::Runnable*>(new PythonClosure(callable)));
+}
+
+PythonAsyncWaiter::PythonAsyncWaiter() {
+ async_waiter_ = Environment::GetDefaultAsyncWaiter();
+}
+
+PythonAsyncWaiter::~PythonAsyncWaiter() {
+ for (CallbackMap::const_iterator it = callbacks_.begin();
+ it != callbacks_.end();
+ ++it) {
+ async_waiter_->CancelWait(it->first);
+ }
+}
+
+MojoAsyncWaitID PythonAsyncWaiter::AsyncWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ PyObject* callable) {
+ AsyncWaiterRunnable* runner = new AsyncWaiterRunnable(callable, &callbacks_);
+ internal::SharedPtr<mojo::Callback<void(MojoResult)> > callback(
+ new mojo::Callback<void(MojoResult)>(
+ static_cast<mojo::Callback<void(MojoResult)>::Runnable*>(runner)));
+ MojoAsyncWaitID wait_id = async_waiter_->AsyncWait(
+ handle, signals, deadline, &AsyncCallbackForwarder, callback.get());
+ callbacks_[wait_id] = callback;
+ runner->set_wait_id(wait_id);
+ return wait_id;
+}
+
+void PythonAsyncWaiter::CancelWait(MojoAsyncWaitID wait_id) {
+ if (callbacks_.find(wait_id) != callbacks_.end()) {
+ async_waiter_->CancelWait(wait_id);
+ callbacks_.erase(wait_id);
+ }
+}
+
+} // namespace python
+} // namespace mojo
diff --git a/mojo/public/python/src/python_system_helper.h b/mojo/public/python/src/python_system_helper.h
new file mode 100644
index 0000000..e0d83ca
--- /dev/null
+++ b/mojo/public/python/src/python_system_helper.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_PUBLIC_PYTHON_SRC_PYTHON_SYSTEM_HELPER_H_
+#define MOJO_PUBLIC_PYTHON_SRC_PYTHON_SYSTEM_HELPER_H_
+
+// Python must be the first include, as it defines preprocessor variable without
+// checking if they already exist.
+#include <Python.h>
+
+#include <map>
+
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/public/cpp/bindings/lib/shared_ptr.h"
+
+namespace mojo {
+namespace python {
+
+// Create a mojo::Closure from a callable python object.
+mojo::Closure BuildClosure(PyObject* callable);
+
+class PythonAsyncWaiter {
+ public:
+ PythonAsyncWaiter();
+ ~PythonAsyncWaiter();
+ MojoAsyncWaitID AsyncWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ PyObject* callable);
+
+ void CancelWait(MojoAsyncWaitID wait_id);
+
+ private:
+ class AsyncWaiterRunnable;
+
+ typedef std::map<MojoAsyncWaitID,
+ internal::SharedPtr<mojo::Callback<void(MojoResult)> > >
+ CallbackMap;
+
+ CallbackMap callbacks_;
+ const MojoAsyncWaiter* async_waiter_;
+};
+
+} // namespace python
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_PYTHON_SRC_PYTHON_SYSTEM_HELPER_H_
diff --git a/mojo/public/tests/DEPS b/mojo/public/tests/DEPS
new file mode 100644
index 0000000..0669117
--- /dev/null
+++ b/mojo/public/tests/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo",
+]
diff --git a/mojo/public/tests/test_support_private.cc b/mojo/public/tests/test_support_private.cc
new file mode 100644
index 0000000..fa5ead4
--- /dev/null
+++ b/mojo/public/tests/test_support_private.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/tests/test_support_private.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static mojo::test::TestSupport* g_test_support = NULL;
+
+extern "C" {
+
+void MojoTestSupportLogPerfResult(const char* test_name,
+ double value,
+ const char* units) {
+ if (g_test_support)
+ g_test_support->LogPerfResult(test_name, value, units);
+ else
+ printf("[no test runner]\t%s\t%g\t%s\n", test_name, value, units);
+}
+
+FILE* MojoTestSupportOpenSourceRootRelativeFile(const char* relative_path) {
+ if (g_test_support)
+ return g_test_support->OpenSourceRootRelativeFile(relative_path);
+ printf("[no test runner]\n");
+ return NULL;
+}
+
+char** MojoTestSupportEnumerateSourceRootRelativeDirectory(
+ const char* relative_path) {
+ if (g_test_support)
+ return g_test_support->EnumerateSourceRootRelativeDirectory(relative_path);
+
+ printf("[no test runner]\n");
+
+ // Return empty list:
+ char** rv = static_cast<char**>(calloc(1, sizeof(char*)));
+ rv[0] = NULL;
+ return rv;
+}
+
+} // extern "C"
+
+namespace mojo {
+namespace test {
+
+TestSupport::~TestSupport() {
+}
+
+// static
+void TestSupport::Init(TestSupport* test_support) {
+ assert(!g_test_support);
+ g_test_support = test_support;
+}
+
+// static
+TestSupport* TestSupport::Get() {
+ return g_test_support;
+}
+
+// static
+void TestSupport::Reset() {
+ g_test_support = NULL;
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/tests/test_support_private.h b/mojo/public/tests/test_support_private.h
new file mode 100644
index 0000000..29983ca
--- /dev/null
+++ b/mojo/public/tests/test_support_private.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_
+#define MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_
+
+#include <stdio.h>
+
+#include "mojo/public/c/test_support/test_support.h"
+
+namespace mojo {
+namespace test {
+
+// Implementors of the test support APIs can use this interface to install their
+// implementation into the mojo_test_support dynamic library.
+class MOJO_TEST_SUPPORT_EXPORT TestSupport {
+ public:
+ virtual ~TestSupport();
+
+ static void Init(TestSupport* test_support);
+ static TestSupport* Get();
+ static void Reset();
+
+ virtual void LogPerfResult(const char* test_name,
+ double value,
+ const char* units) = 0;
+ virtual FILE* OpenSourceRootRelativeFile(const char* relative_path) = 0;
+ virtual char** EnumerateSourceRootRelativeDirectory(
+ const char* relative_path) = 0;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_declaration.tmpl
new file mode 100644
index 0000000..86c85d7
--- /dev/null
+++ b/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 %}
+ {{enum.name|to_all_caps}}_{{field.name}} = {{field.value|expression_to_text}},
+{%- else %}
+ {{enum.name|to_all_caps}}_{{field.name}},
+{%- endif %}
+{%- endfor %}
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
new file mode 100644
index 0000000..30d20d8
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
new file mode 100644
index 0000000..bb5b777
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
@@ -0,0 +1,334 @@
+{%- 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_any_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_description) -%}
+ {# TODO(yzshen): Consider refactoring to share code with
+ struct_serialization_definition.tmpl #}
+ {{params_name}}* params =
+ {{params_name}}::New(builder.buffer());
+{#--- Sets #}
+{% for param in parameters %}
+{%- if param.kind|is_object_kind %}
+{%- if param.kind|is_any_array_kind %}
+ mojo::SerializeArray_<{{param.kind|get_array_validate_params|indent(24)}}>(
+ mojo::internal::Forward(in_{{param.name}}), builder.buffer(), ¶ms->{{param.name}}.ptr);
+{%- else %}
+ Serialize_(mojo::internal::Forward(in_{{param.name}}), builder.buffer(), ¶ms->{{param.name}}.ptr);
+{%- endif %}
+{%- if not param.kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !params->{{param.name}}.ptr,
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ "null {{param.name}} argument in {{params_description}}");
+{%- endif %}
+{%- elif param.kind|is_any_handle_kind %}
+{%- if param.kind|is_interface_kind or
+ param.kind|is_interface_request_kind %}
+ // Delegate handle.
+ params->{{param.name}} = in_{{param.name}}.PassMessagePipe().release();
+{%- else %}
+ params->{{param.name}} = in_{{param.name}}.release();
+{%- endif %}
+{%- if not param.kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !params->{{param.name}}.is_valid(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ "invalid {{param.name}} argument in {{params_description}}");
+{%- endif %}
+{%- 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|constant_value}};
+{%- 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) 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) %}
+{%- set params_description =
+ "%s.%s request"|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, params_description)}}
+
+{%- 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) %}
+{%- set params_description =
+ "%s.%s response"|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 override;
+
+ 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, params_description)}}
+ 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_ = nullptr;
+}
+{%- endif -%}
+{%- endfor %}
+
+{{class_name}}Stub::{{class_name}}Stub()
+ : sink_(nullptr) {
+}
+
+{#--- 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/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
new file mode 100644
index 0000000..fbefce2
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
new file mode 100644
index 0000000..6b8e7c5
--- /dev/null
+++ b/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)}}
+ ) override;
+{%- endfor %}
+
+ private:
+ mojo::MessageReceiverWithResponder* receiver_;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl
new file mode 100644
index 0000000..2239b69
--- /dev/null
+++ b/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 = nullptr);
+
+ virtual bool Accept(mojo::Message* message) override;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl
new file mode 100644
index 0000000..801603d
--- /dev/null
+++ b/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 = nullptr);
+
+ virtual bool Accept(mojo::Message* message) override;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
new file mode 100644
index 0000000..afc6504
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
@@ -0,0 +1,13 @@
+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) override;
+ virtual bool AcceptWithResponder(mojo::Message* message,
+ mojo::MessageReceiver* responder) override;
+
+ private:
+ {{interface.name}}* sink_;
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-internal.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-internal.h.tmpl
new file mode 100644
index 0000000..f0cf33b
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
new file mode 100644
index 0000000..8f5e812
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
@@ -0,0 +1,94 @@
+// 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"
+#elif defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:4056)
+#pragma warning(disable:4756)
+#endif
+
+#include "{{module.path}}.h"
+
+#include <math.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|constant_value}};
+{%- 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|constant_value}};
+{%- 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
+#elif defined(_MSC_VER)
+#pragma warning(pop)
+#endif
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
new file mode 100644
index 0000000..4e21d47
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/params_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/params_definition.tmpl
new file mode 100644
index 0000000..0b11047
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
new file mode 100644
index 0000000..60a6a9e
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
new file mode 100644
index 0000000..461f158
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
new file mode 100644
index 0000000..0e51193
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
@@ -0,0 +1,115 @@
+{%- 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 %}
+{%- set kind = packed_field.field.kind %}
+{%- if kind|is_object_kind %}
+{%- set wrapper_type = kind|cpp_wrapper_type %}
+{%- if not kind|is_nullable_kind %}
+ if (!object->{{name}}.offset) {
+ ReportValidationError(
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ "null {{name}} field in {{struct.name}} struct");
+ return false;
+ }
+{%- endif %}
+ if (!mojo::internal::ValidateEncodedPointer(&object->{{name}}.offset)) {
+ ReportValidationError(mojo::internal::VALIDATION_ERROR_ILLEGAL_POINTER);
+ return false;
+ }
+{%- if kind|is_any_array_kind or kind|is_string_kind %}
+ if (!{{wrapper_type}}::Data_::Validate<
+ {{kind|get_array_validate_params|indent(10)}}>(
+ mojo::internal::DecodePointerRaw(&object->{{name}}.offset),
+ bounds_checker)) {
+{%- else %}
+ if (!{{wrapper_type}}::Data_::Validate(
+ mojo::internal::DecodePointerRaw(&object->{{name}}.offset),
+ bounds_checker)) {
+{%- endif %}
+ return false;
+ }
+{%- elif kind|is_any_handle_kind %}
+{%- if not kind|is_nullable_kind %}
+ if (object->{{name}}.value() == mojo::internal::kEncodedInvalidHandleValue) {
+ ReportValidationError(
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ "invalid {{name}} field in {{struct.name}} struct");
+ return false;
+ }
+{%- endif %}
+ 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_any_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_any_handle_kind %}
+mojo::internal::DecodeHandle(&{{pf.field.name}}, handles);
+{%- endif %}
+{%- endfor %}
+{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
new file mode 100644
index 0000000..604be86
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl
new file mode 100644
index 0000000..09bf392
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_definition.tmpl
@@ -0,0 +1,75 @@
+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 %}
+{%- if pf.field.kind|is_any_array_kind %}
+ mojo::SerializeArray_<{{pf.field.kind|get_array_validate_params|indent(26)}}>(
+ mojo::internal::Forward(input->{{pf.field.name}}), buf, &result->{{pf.field.name}}.ptr);
+{%- else %}
+ Serialize_(mojo::internal::Forward(input->{{pf.field.name}}), buf, &result->{{pf.field.name}}.ptr);
+{%- endif %}
+{%- if not pf.field.kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !result->{{pf.field.name}}.ptr,
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ "null {{pf.field.name}} field in {{struct.name}} struct");
+{%- endif %}
+{%- elif pf.field.kind|is_any_handle_kind %}
+{%- if pf.field.kind|is_interface_kind %}
+ result->{{pf.field.name}} = input->{{pf.field.name}}.PassMessagePipe().release();
+{%- else %}
+ result->{{pf.field.name}} = input->{{pf.field.name}}.release();
+{%- endif %}
+{%- if not pf.field.kind|is_nullable_kind %}
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ !result->{{pf.field.name}}.is_valid(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ "invalid {{pf.field.name}} field in {{struct.name}} struct");
+{%- endif %}
+{%- else %}
+ result->{{pf.field.name}} = input->{{pf.field.name}};
+{%- endif %}
+{%- endfor %}
+ *output = result;
+ } else {
+ *output = nullptr;
+ }
+}
+
+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_interface_kind %}
+ if (input->{{pf.field.name}}.is_valid())
+ result->{{pf.field.name}}.Bind(mojo::MakeScopedHandle(mojo::internal::FetchAndReset(&input->{{pf.field.name}})));
+{%- elif pf.field.kind|is_any_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/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
new file mode 100644
index 0000000..21f2968
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
@@ -0,0 +1,34 @@
+
+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>::Convert(u);
+ }
+
+ {{struct.name}}();
+ ~{{struct.name}}();
+{% if struct|is_cloneable_kind %}
+ {{struct.name}}Ptr Clone() const;
+{%- endif %}
+
+{#--- Getters #}
+{% for field in struct.fields %}
+{%- set type = field.kind|cpp_wrapper_type %}
+{%- set name = field.name %}
+ {{type}} {{name}};
+{%- endfor %}
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
new file mode 100644
index 0000000..42f7575
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
@@ -0,0 +1,28 @@
+// 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}}() {
+}
+{% if struct|is_cloneable_kind %}
+{{struct.name}}Ptr {{struct.name}}::Clone() const {
+ {{struct.name}}Ptr rv(New());
+{%- for field in struct.fields %}
+{%- if field.kind|is_struct_kind or field.kind|is_any_array_kind %}
+ rv->{{field.name}} = {{field.name}}.Clone();
+{%- else %}
+ rv->{{field.name}} = {{field.name}};
+{%- endif %}
+{%- endfor %}
+ return rv.Pass();
+}
+{% endif %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl
new file mode 100644
index 0000000..db193e2
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl
@@ -0,0 +1,3 @@
+{% macro constant_def(constant) %}
+public static final {{constant.kind|java_type}} {{constant|name}} = {{constant|constant_value}};
+{% endmacro %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl
new file mode 100644
index 0000000..0a4e299
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl
new file mode 100644
index 0000000..7096a18
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl
new file mode 100644
index 0000000..a16c178
--- /dev/null
+++ b/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('i32')}})
+{%- 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/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl
new file mode 100644
index 0000000..ec6a88b
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl
new file mode 100644
index 0000000..10e5d7a
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl
@@ -0,0 +1,4 @@
+{% from "interface_definition.tmpl" import interface_def %}
+{% include "header.java.tmpl" %}
+
+{{ interface_def(interface, client) }}
diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
new file mode 100644
index 0000000..941fec7
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
@@ -0,0 +1,294 @@
+{% from "constant_definition.tmpl" import constant_def %}
+{% from "enum_definition.tmpl" import enum_def %}
+{% from "struct_definition.tmpl" import struct_def %}
+
+{%- macro declare_params(parameters, boxed=false) %}
+{%- for param in parameters -%}
+{{param.kind|java_type(boxed)}} {{param|name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor %}
+{%- endmacro %}
+
+{% macro declare_request_params(method) %}
+{{declare_params(method.parameters)}}
+{%- if method.response_parameters != None -%}
+{%- if method.parameters %}, {% endif %}
+{{method|interface_response_name}} callback
+{%- endif -%}
+{% endmacro %}
+
+{%- macro declare_callback(method) -%}
+
+interface {{method|interface_response_name}} extends org.chromium.mojo.bindings.Callbacks.Callback{{method.response_parameters|length}}{% if method.response_parameters %}<
+{%- for param in method.response_parameters -%}
+{{param.kind|java_type(True)}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor -%}
+>{% endif %} { }
+{%- endmacro -%}
+
+{%- macro run_callback(variable, parameters) -%}
+{%- if parameters -%}
+{%- for param in parameters -%}
+{{variable}}.{{param|name}}
+{%- if not loop.last %}, {% endif %}
+{%- endfor -%}
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro super_class(client, with_generic=True) -%}
+{%- if client -%}
+org.chromium.mojo.bindings.InterfaceWithClient{% if with_generic %}<{{client|java_type}}>{% endif %}
+{%- else -%}
+org.chromium.mojo.bindings.Interface
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro flags_for_method(method, is_parameter) -%}
+{{flags(method.response_parameters, is_parameter)}}
+{%- endmacro -%}
+
+{%- macro flags(has_response_parameters, is_parameter) -%}
+{%- if not has_response_parameters -%}
+org.chromium.mojo.bindings.MessageHeader.NO_FLAG
+{%- elif is_parameter: -%}
+org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG
+{%- else -%}
+org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro manager_class(interface, client, fully_qualified=False) -%}
+{% if fully_qualified %}{{super_class(client, False)}}.{% endif %}Manager<{{interface|name}}, {{interface|name}}.Proxy
+{%- if client -%}, {{client|java_type}}{%- endif -%}
+>
+{%- endmacro -%}
+
+{%- macro manager_def(interface, client) -%}
+public static final {{manager_class(interface, client, True)}} MANAGER =
+ new {{manager_class(interface, client, True)}}() {
+
+ public String getName() {
+ return "{{namespace|replace(".","::")}}::{{interface.name}}";
+ }
+
+ public Proxy buildProxy(org.chromium.mojo.system.Core core,
+ org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+ return new Proxy(core, messageReceiver);
+ }
+
+ public Stub buildStub(org.chromium.mojo.system.Core core, {{interface|name}} impl) {
+ return new Stub(core, impl);
+ }
+
+ public {{interface|name}}[] buildArray(int size) {
+ return new {{interface|name}}[size];
+ }
+{% if client %}
+
+ protected org.chromium.mojo.bindings.Interface.Manager<{{client|java_type}}, ?> getClientManager() {
+ return {{client|java_type}}.MANAGER;
+ }
+{% endif %}
+};
+{%- endmacro -%}
+
+{%- macro accept_body(interface, with_response) -%}
+{% if (interface|has_method_with_response and with_response) or
+ (interface|has_method_without_response and not with_response) %}
+try {
+ org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+ message.asServiceMessage();
+ org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+ if (!header.validateHeader({{flags(with_response, True)}})) {
+ return false;
+ }
+ switch(header.getType()) {
+{% for method in interface.methods %}
+{% if (with_response and method.response_parameters != None) or
+ (not with_response and method.response_parameters == None) %}
+{% set request_struct = method|struct_from_method %}
+{% if with_response %}
+{% set response_struct = method|response_struct_from_method %}
+{% endif %}
+ case {{method|method_ordinal_name}}: {
+{% if method.parameters %}
+ {{request_struct|name}} data =
+ {{request_struct|name}}.deserialize(messageWithHeader.getPayload());
+{% else %}
+ {{request_struct|name}}.deserialize(messageWithHeader.getPayload());
+{% endif %}
+ getImpl().{{method|name}}({{run_callback('data', method.parameters)}}{% if with_response %}{% if method.parameters %}, {% endif %}new {{response_struct|name}}ProxyToResponder(getCore(), receiver, header.getRequestId()){% endif %});
+ return true;
+ }
+{% endif %}
+{% endfor %}
+ default:
+ return false;
+ }
+} catch (org.chromium.mojo.bindings.DeserializationException e) {
+ return false;
+}
+{% else %}
+return false;
+{% endif %}
+{%- endmacro -%}
+
+{% macro interface_def(interface, client) %}
+public interface {{interface|name}} extends {{super_class(client)}} {
+{% for constant in interface.constants %}
+
+ {{constant_def(constant)|indent(4)}}
+{% endfor %}
+{% for enum in interface.enums %}
+
+ {{enum_def(enum, false)|indent(4)}}
+{% endfor %}
+
+ public interface Proxy extends {{interface|name}}, {{super_class(client, False)}}.Proxy{% if client %}<{{client|java_type}}>{% endif %} {
+ }
+
+ {{manager_class(interface, client)}} MANAGER = {{interface|name}}_Internal.MANAGER;
+{% for method in interface.methods %}
+
+ void {{method|name}}({{declare_request_params(method)}});
+{% if method.response_parameters != None %}
+ {{declare_callback(method)|indent(4)}}
+{% endif %}
+{% endfor %}
+}
+{% endmacro %}
+
+{% macro interface_internal_def(interface, client) %}
+class {{interface|name}}_Internal {
+
+ {{manager_def(interface, client)|indent(4)}}
+
+{% for method in interface.methods %}
+ private static final int {{method|method_ordinal_name}} = {{method.ordinal}};
+{% endfor %}
+
+ static final class Proxy extends {% if client %}org.chromium.mojo.bindings.InterfaceWithClient.AbstractProxy<{{client|java_type}}>{% else %}org.chromium.mojo.bindings.Interface.AbstractProxy{% endif %} implements {{interface|name}}.Proxy {
+
+ Proxy(org.chromium.mojo.system.Core core,
+ org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
+ super(core, messageReceiver);
+ }
+{% for method in interface.methods %}
+
+ @Override
+ public void {{method|name}}({{declare_request_params(method)}}) {
+{% set request_struct = method|struct_from_method %}
+ {{request_struct|name}} _message = new {{request_struct|name}}();
+{% for param in method.parameters %}
+ _message.{{param|name}} = {{param|name}};
+{% endfor %}
+{% if method.response_parameters != None %}
+ getMessageReceiver().acceptWithResponder(
+ _message.serializeWithHeader(
+ getCore(),
+ new org.chromium.mojo.bindings.MessageHeader(
+ {{method|method_ordinal_name}},
+ {{flags_for_method(method, True)}},
+ 0)),
+ new {{method|response_struct_from_method|name}}ForwardToCallback(callback));
+{% else %}
+ getMessageReceiver().accept(
+ _message.serializeWithHeader(
+ getCore(),
+ new org.chromium.mojo.bindings.MessageHeader({{method|method_ordinal_name}})));
+{% endif %}
+ }
+{% endfor %}
+
+ }
+
+ static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<{{interface|name}}> {
+
+ Stub(org.chromium.mojo.system.Core core, {{interface|name}} impl) {
+ super(core, impl);
+ }
+
+ @Override
+ public boolean accept(org.chromium.mojo.bindings.Message message) {
+ {{accept_body(interface, False)|indent(12)}}
+ }
+
+ @Override
+ public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) {
+ {{accept_body(interface, True)|indent(12)}}
+ }
+ }
+{% for method in interface.methods %}
+
+ {{ struct_def(method|struct_from_method, True)|indent(4) }}
+{% if method.response_parameters != None %}
+{% set response_struct = method|response_struct_from_method %}
+
+ {{ struct_def(response_struct, True)|indent(4) }}
+
+ static class {{response_struct|name}}ForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable
+ implements org.chromium.mojo.bindings.MessageReceiver {
+ private final {{interface|name}}.{{method|interface_response_name}} mCallback;
+
+ {{response_struct|name}}ForwardToCallback({{interface|name}}.{{method|interface_response_name}} callback) {
+ this.mCallback = callback;
+ }
+
+ @Override
+ public boolean accept(org.chromium.mojo.bindings.Message message) {
+ try {
+ org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
+ message.asServiceMessage();
+ org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
+ if (!header.validateHeader({{method|method_ordinal_name}},
+ {{flags_for_method(method, False)}})) {
+ return false;
+ }
+{% if method.response_parameters|length %}
+ {{response_struct|name}} response = {{response_struct|name}}.deserialize(messageWithHeader.getPayload());
+{% endif %}
+ mCallback.call({{run_callback('response', method.response_parameters)}});
+ return true;
+ } catch (org.chromium.mojo.bindings.DeserializationException e) {
+ return false;
+ }
+ }
+ }
+
+ static class {{response_struct|name}}ProxyToResponder implements {{interface|name}}.{{method|interface_response_name}} {
+
+ private final org.chromium.mojo.system.Core mCore;
+ private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
+ private final long mRequestId;
+
+ {{response_struct|name}}ProxyToResponder(
+ org.chromium.mojo.system.Core core,
+ org.chromium.mojo.bindings.MessageReceiver messageReceiver,
+ long requestId) {
+ mCore = core;
+ mMessageReceiver = messageReceiver;
+ mRequestId = requestId;
+ }
+
+ @Override
+ public void call({{declare_params(method.response_parameters, true)}}) {
+ {{response_struct|name}} _response = new {{response_struct|name}}();
+{% for param in method.response_parameters %}
+ _response.{{param|name}} = {{param|name}};
+{% endfor %}
+ org.chromium.mojo.bindings.ServiceMessage _message =
+ _response.serializeWithHeader(
+ mCore,
+ new org.chromium.mojo.bindings.MessageHeader(
+ {{method|method_ordinal_name}},
+ {{flags_for_method(method, False)}},
+ mRequestId));
+ mMessageReceiver.accept(_message);
+ }
+ }
+{% endif %}
+{% endfor %}
+
+}
+{% endmacro %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl
new file mode 100644
index 0000000..efb17f3
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl
@@ -0,0 +1,4 @@
+{% from "interface_definition.tmpl" import interface_internal_def %}
+{% include "header.java.tmpl" %}
+
+{{ interface_internal_def(interface, client) }}
diff --git a/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl
new file mode 100644
index 0000000..232ec26
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl
@@ -0,0 +1,4 @@
+{% from "struct_definition.tmpl" import struct_def %}
+{% include "header.java.tmpl" %}
+
+{{ struct_def(struct) }}
diff --git a/mojo/public/tools/bindings/generators/java_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/struct_definition.tmpl
new file mode 100644
index 0000000..2fc4439
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/java_templates/struct_definition.tmpl
@@ -0,0 +1,118 @@
+{% from "constant_definition.tmpl" import constant_def %}
+{% from "enum_definition.tmpl" import enum_def %}
+
+{%- macro array_expected_length(kind) -%}
+{%- if kind|is_fixed_array_kind -%}
+{{kind.length}}
+{%- else -%}
+org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH
+{%- endif -%}
+{%- endmacro -%}
+
+{% macro encode(variable, kind, offset, bit, level=0) %}
+{% if kind|is_pointer_array_kind %}
+{% set sub_kind = kind.kind %}
+if ({{variable}} == null) {
+ encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}});
+} else {
+ org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.encodePointerArray({{variable}}.length, {{offset}}, {{array_expected_length(kind)}});
+ for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) {
+ {{encode(variable~'[i'~level~']', sub_kind, 'DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i'~level, 0, level+1)|indent(8)}}
+ }
+}
+{% else %}
+encoder{{level}}.{{kind|encode_method(variable, offset, bit)}};
+{% endif %}
+{% endmacro %}
+
+{% macro decode(variable, kind, offset, bit, level=0) %}
+{% if kind|is_struct_kind or kind|is_pointer_array_kind %}
+org.chromium.mojo.bindings.Decoder decoder{{level+1}} = decoder{{level}}.readPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}});
+{% if kind|is_struct_kind %}
+{{variable}} = {{kind|java_type}}.decode(decoder{{level+1}});
+{% else %}{# kind|is_pointer_array_kind #}
+if (decoder{{level+1}} == null) {
+ {{variable}} = null;
+} else {
+ DataHeader si{{level+1}} = decoder{{level+1}}.readDataHeaderForPointerArray({{array_expected_length(kind)}});
+ {{variable}} = {{kind|new_array('si'~(level+1)~'.numFields')}};
+ for (int i{{level+1}} = 0; i{{level+1}} < si{{level+1}}.numFields; ++i{{level+1}}) {
+ {{decode(variable~'[i'~(level+1)~']', kind.kind, 'DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE * i'~(level+1), 0, level+1)|indent(8)}}
+ }
+}
+{% endif %}
+{% else %}
+{{variable}} = decoder{{level}}.{{kind|decode_method(offset, bit)}};
+{% endif %}
+{% endmacro %}
+
+{% macro struct_def(struct, inner_class=False) %}
+{{'static' if inner_class else 'public'}} final class {{struct|name}} extends org.chromium.mojo.bindings.Struct {
+
+ private static final int STRUCT_SIZE = {{struct.packed|struct_size}};
+ private static final DataHeader DEFAULT_STRUCT_INFO = new DataHeader(STRUCT_SIZE, {{struct.packed.packed_fields|length}});
+{% for constant in struct.constants %}
+
+ {{constant_def(constant)|indent(4)}}
+{% endfor %}
+{% for enum in struct.enums %}
+
+ {{enum_def(enum, false)|indent(4)}}
+{% endfor %}
+{% if struct.fields %}
+
+{% for field in struct.fields %}
+ public {{field.kind|java_type}} {{field|name}};
+{% endfor %}
+{% endif %}
+
+ public {{struct|name}}() {
+ super(STRUCT_SIZE);
+{% for field in struct.fields %}
+{% if field.default %}
+ {{field|name}} = {{field|default_value}};
+{% elif field.kind|is_handle %}
+ {{field|name}} = org.chromium.mojo.system.InvalidHandle.INSTANCE;
+{% endif %}
+{% endfor %}
+ }
+
+ public static {{struct|name}} deserialize(org.chromium.mojo.bindings.Message message) {
+ return decode(new org.chromium.mojo.bindings.Decoder(message));
+ }
+
+ public static {{struct|name}} decode(org.chromium.mojo.bindings.Decoder decoder0) {
+ if (decoder0 == null) {
+ return null;
+ }
+ {{struct|name}} result = new {{struct|name}}();
+{% if not struct.bytes %}
+ decoder0.readDataHeader();
+{% else %}
+ DataHeader mainDataHeader = decoder0.readDataHeader();
+{% endif %}
+{% for byte in struct.bytes %}
+{% for packed_field in byte.packed_fields %}
+ if (mainDataHeader.numFields > {{packed_field.ordinal}}) {
+ {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(12)}}
+ }
+{% endfor %}
+{% endfor %}
+ return result;
+ }
+
+ @Override
+ protected final void encode(org.chromium.mojo.bindings.Encoder encoder) {
+{% if not struct.bytes %}
+ encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+{% else %}
+ org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO);
+{% endif %}
+{% for byte in struct.bytes %}
+{% for packed_field in byte.packed_fields %}
+ {{encode(packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(8)}}
+{% endfor %}
+{% endfor %}
+ }
+}
+{% endmacro %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl
new file mode 100644
index 0000000..795116d
--- /dev/null
+++ b/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/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
new file mode 100644
index 0000000..b41929c
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
@@ -0,0 +1,178 @@
+{%- 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));
+ }
+ };
+
+{#--- Validation #}
+
+ function validate{{interface.name}}Request(messageValidator) {
+{%- if not(interface.methods) %}
+ return validator.validationError.NONE;
+{%- else %}
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+{%- for method in interface.methods %}
+ case k{{interface.name}}_{{method.name}}_Name:
+{%- if method.response_parameters == None %}
+ if (!message.expectsResponse() && !message.isResponse())
+ paramsClass = {{interface.name}}_{{method.name}}_Params;
+{%- else %}
+ if (message.expectsResponse())
+ paramsClass = {{interface.name}}_{{method.name}}_Params;
+{%- endif %}
+ break;
+{%- endfor %}
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+{%- endif %}
+ }
+
+ function validate{{interface.name}}Response(messageValidator) {
+{%- if not(interface|has_callbacks) %}
+ return validator.validationError.NONE;
+{%- else %}
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+{%- for method in interface.methods %}
+{%- if method.response_parameters != None %}
+ case k{{interface.name}}_{{method.name}}_Name:
+ if (message.isResponse())
+ paramsClass = {{interface.name}}_{{method.name}}_ResponseParams;
+ break;
+{%- endif %}
+{%- endfor %}
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+{%- endif %}
+ }
+
+ {{interface.name}}Stub.prototype.validator = validate{{interface.name}}Request;
+{%- if interface|has_callbacks %}
+ {{interface.name}}Proxy.prototype.validator = validate{{interface.name}}Response;
+{%- else %}
+ {{interface.name}}Proxy.prototype.validator = null;
+{%- endif -%}
+
+{#--- 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/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl
new file mode 100644
index 0000000..93fa537
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/module.js.tmpl
@@ -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.
+
+define("{{module.path}}", [
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/validator",
+{%- for import in imports %}
+ "{{import.module.path}}",
+{%- endfor %}
+ ], function(codec, validator
+{%- 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/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
new file mode 100644
index 0000000..d77b28b
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
@@ -0,0 +1,116 @@
+{#--- Begin #}
+ function {{struct.name}}(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+{#--- 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 %}
+
+{#--- initDefaults() #}
+ {{struct.name}}.prototype.initDefaults_ = function() {
+{%- for packed_field in struct.packed.packed_fields %}
+ this.{{packed_field.field.name}} = {{packed_field.field|default_value}};
+{%- endfor %}
+ };
+
+{#--- initFields() #}
+ {{struct.name}}.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+{#--- Validation #}
+
+ {{struct.name}}.validate = function(messageValidator, offset) {
+ var err;
+{% macro check_err() -%}
+ if (err !== validator.validationError.NONE)
+ return err;
+{%- endmacro %}
+ err = messageValidator.validateStructHeader(offset, {{struct.name}}.encodedSize, {{struct.packed.packed_fields|length}});
+ {{check_err()}}
+
+{%- for packed_field in struct.packed.packed_fields %}
+{%- set field_name = packed_field.field.name %}
+{%- if packed_field.field|is_string_pointer_field %}
+ // validate {{struct.name}}.{{field_name}}
+ err = messageValidator.validateStringPointer({{packed_field|validate_string_params}})
+ {{check_err()}}
+{%- elif packed_field.field|is_array_pointer_field %}
+ // validate {{struct.name}}.{{field_name}}
+ err = messageValidator.validateArrayPointer({{packed_field|validate_array_params}});
+ {{check_err()}}
+{%- elif packed_field.field|is_struct_pointer_field %}
+ // validate {{struct.name}}.{{field_name}}
+ err = messageValidator.validateStructPointer({{packed_field|validate_struct_params}});
+ {{check_err()}}
+{%- elif packed_field.field|is_handle_field %}
+ // validate {{struct.name}}.{{field_name}}
+ err = messageValidator.validateHandle({{packed_field|validate_handle_params}})
+ {{check_err()}}
+{%- endif %}
+{%- endfor %}
+
+ return validator.validationError.NONE;
+ };
+
+{#--- 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/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
new file mode 100644
index 0000000..68e0379
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -0,0 +1,338 @@
+# 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.NULLABLE_HANDLE: "mojo::Handle",
+ mojom.NULLABLE_DCPIPE: "mojo::DataPipeConsumerHandle",
+ mojom.NULLABLE_DPPIPE: "mojo::DataPipeProducerHandle",
+ mojom.NULLABLE_MSGPIPE: "mojo::MessagePipeHandle",
+ mojom.NULLABLE_SHAREDBUFFER: "mojo::SharedBufferHandle",
+ mojom.INT64: "int64_t",
+ mojom.UINT64: "uint64_t",
+ mojom.DOUBLE: "double",
+}
+
+_kind_to_cpp_literal_suffix = {
+ mojom.UINT8: "U",
+ mojom.UINT16: "U",
+ mojom.UINT32: "U",
+ mojom.FLOAT: "f",
+ mojom.UINT64: "ULL",
+}
+
+def ConstantValue(constant):
+ return ExpressionToText(constant.value, kind=constant.kind)
+
+def DefaultValue(field):
+ if field.default:
+ if mojom.IsStructKind(field.kind):
+ assert field.default == "default"
+ return "%s::New()" % GetNameForKind(field.kind)
+ return ExpressionToText(field.default, kind=field.kind)
+ 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 mojom.IsStructKind(kind):
+ return "%s_Data*" % GetNameForKind(kind, internal=True)
+ if mojom.IsAnyArrayKind(kind):
+ return "mojo::internal::Array_Data<%s>*" % GetCppType(kind.kind)
+ if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
+ return "mojo::MessagePipeHandle"
+ if mojom.IsEnumKind(kind):
+ return "int32_t"
+ if mojom.IsStringKind(kind):
+ return "mojo::internal::String_Data*"
+ return _kind_to_cpp_type[kind]
+
+def GetCppPodType(kind):
+ if mojom.IsStringKind(kind):
+ return "char*"
+ return _kind_to_cpp_type[kind]
+
+def GetCppArrayArgWrapperType(kind):
+ if mojom.IsEnumKind(kind):
+ return GetNameForKind(kind)
+ if mojom.IsStructKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsAnyArrayKind(kind):
+ return "mojo::Array<%s> " % GetCppArrayArgWrapperType(kind.kind)
+ if mojom.IsInterfaceKind(kind):
+ raise Exception("Arrays of interfaces not yet supported!")
+ if mojom.IsInterfaceRequestKind(kind):
+ raise Exception("Arrays of interface requests not yet supported!")
+ if mojom.IsStringKind(kind):
+ return "mojo::String"
+ if mojom.IsHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ return _kind_to_cpp_type[kind]
+
+def GetCppResultWrapperType(kind):
+ if mojom.IsEnumKind(kind):
+ return GetNameForKind(kind)
+ if mojom.IsStructKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsAnyArrayKind(kind):
+ return "mojo::Array<%s>" % GetCppArrayArgWrapperType(kind.kind)
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ return "mojo::InterfaceRequest<%s>" % GetNameForKind(kind.kind)
+ if mojom.IsStringKind(kind):
+ return "mojo::String"
+ if mojom.IsHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ return _kind_to_cpp_type[kind]
+
+def GetCppWrapperType(kind):
+ if mojom.IsEnumKind(kind):
+ return GetNameForKind(kind)
+ if mojom.IsStructKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsAnyArrayKind(kind):
+ return "mojo::Array<%s>" % GetCppArrayArgWrapperType(kind.kind)
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ raise Exception("InterfaceRequest fields not supported!")
+ if mojom.IsStringKind(kind):
+ return "mojo::String"
+ if mojom.IsHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ return _kind_to_cpp_type[kind]
+
+def GetCppConstWrapperType(kind):
+ if mojom.IsStructKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsAnyArrayKind(kind):
+ return "mojo::Array<%s>" % GetCppArrayArgWrapperType(kind.kind)
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtr" % GetNameForKind(kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ return "mojo::InterfaceRequest<%s>" % GetNameForKind(kind.kind)
+ if mojom.IsEnumKind(kind):
+ return GetNameForKind(kind)
+ if mojom.IsStringKind(kind):
+ return "const mojo::String&"
+ if mojom.IsHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ 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 mojom.IsStructKind(kind):
+ return ("mojo::internal::StructPointer<%s_Data>" %
+ GetNameForKind(kind, internal=True))
+ if mojom.IsAnyArrayKind(kind):
+ return "mojo::internal::ArrayPointer<%s>" % GetCppType(kind.kind)
+ if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
+ return "mojo::MessagePipeHandle"
+ if mojom.IsEnumKind(kind):
+ return GetNameForKind(kind)
+ if mojom.IsStringKind(kind):
+ return "mojo::internal::StringPointer"
+ return _kind_to_cpp_type[kind]
+
+def IsStructWithHandles(struct):
+ for pf in struct.packed.packed_fields:
+ if mojom.IsAnyHandleKind(pf.field.kind):
+ return True
+ return False
+
+def TranslateConstants(token, kind):
+ if isinstance(token, mojom.NamedValue):
+ # Both variable and enum constants are constructed like:
+ # Namespace::Struct::CONSTANT_NAME
+ # For enums, CONSTANT_NAME is ENUM_NAME_ENUM_VALUE.
+ name = []
+ if token.imported_from:
+ name.extend(NamespaceToArray(token.namespace))
+ if token.parent_kind:
+ name.append(token.parent_kind.name)
+ if isinstance(token, mojom.EnumValue):
+ name.append(
+ "%s_%s" % (generator.CamelCaseToAllCaps(token.enum.name), token.name))
+ else:
+ name.append(token.name)
+ return "::".join(name)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == "double.INFINITY" or token.value == "float.INFINITY":
+ return "INFINITY";
+ if token.value == "double.NEGATIVE_INFINITY" or \
+ token.value == "float.NEGATIVE_INFINITY":
+ return "-INFINITY";
+ if token.value == "double.NAN" or token.value == "float.NAN":
+ return "NAN";
+
+ if (kind is not None and mojom.IsFloatKind(kind)):
+ return token if token.isdigit() else token + "f";
+
+ return '%s%s' % (token, _kind_to_cpp_literal_suffix.get(kind, ''))
+
+def ExpressionToText(value, kind=None):
+ return TranslateConstants(value, kind)
+
+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 mojom.IsMoveOnlyKind(field.kind):
+ return False
+ return True
+
+def GetArrayValidateParams(kind):
+ if not mojom.IsAnyArrayKind(kind) and not mojom.IsStringKind(kind):
+ return "mojo::internal::NoValidateParams"
+
+ if mojom.IsStringKind(kind):
+ expected_num_elements = 0
+ element_is_nullable = False
+ element_validate_params = "mojo::internal::NoValidateParams"
+ else:
+ expected_num_elements = generator.ExpectedArraySize(kind)
+ element_is_nullable = mojom.IsNullableKind(kind.kind)
+ element_validate_params = GetArrayValidateParams(kind.kind)
+
+ return "mojo::internal::ArrayValidateParams<%d, %s,\n%s> " % (
+ expected_num_elements,
+ 'true' if element_is_nullable else 'false',
+ element_validate_params)
+
+_HEADER_SIZE = 8
+
+class Generator(generator.Generator):
+
+ cpp_filters = {
+ "constant_value": ConstantValue,
+ "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,
+ "expected_array_size": generator.ExpectedArraySize,
+ "expression_to_text": ExpressionToText,
+ "get_array_validate_params": GetArrayValidateParams,
+ "get_name_for_kind": GetNameForKind,
+ "get_pad": pack.GetPad,
+ "has_callbacks": mojom.HasCallbacks,
+ "should_inline": ShouldInlineStruct,
+ "is_any_array_kind": mojom.IsAnyArrayKind,
+ "is_cloneable_kind": mojom.IsCloneableKind,
+ "is_enum_kind": mojom.IsEnumKind,
+ "is_move_only_kind": mojom.IsMoveOnlyKind,
+ "is_any_handle_kind": mojom.IsAnyHandleKind,
+ "is_interface_kind": mojom.IsInterfaceKind,
+ "is_interface_request_kind": mojom.IsInterfaceRequestKind,
+ "is_nullable_kind": mojom.IsNullableKind,
+ "is_object_kind": mojom.IsObjectKind,
+ "is_string_kind": mojom.IsStringKind,
+ "is_struct_kind": mojom.IsStructKind,
+ "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,
+ "to_all_caps": generator.CamelCaseToAllCaps,
+ }
+
+ 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/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py
new file mode 100644
index 0000000..043d9e3
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py
@@ -0,0 +1,504 @@
+# Copyright 2014 The Chromium 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 ast
+import contextlib
+import os
+import re
+import shutil
+import tempfile
+import zipfile
+
+from jinja2 import contextfilter
+
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+from mojom.generate.template_expander import UseJinja
+
+
+GENERATOR_PREFIX = 'java'
+
+_HEADER_SIZE = 8
+
+_spec_to_java_type = {
+ mojom.BOOL.spec: 'boolean',
+ mojom.DCPIPE.spec: 'org.chromium.mojo.system.DataPipe.ConsumerHandle',
+ mojom.DOUBLE.spec: 'double',
+ mojom.DPPIPE.spec: 'org.chromium.mojo.system.DataPipe.ProducerHandle',
+ mojom.FLOAT.spec: 'float',
+ mojom.HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
+ mojom.INT16.spec: 'short',
+ mojom.INT32.spec: 'int',
+ mojom.INT64.spec: 'long',
+ mojom.INT8.spec: 'byte',
+ mojom.MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
+ mojom.NULLABLE_DCPIPE.spec:
+ 'org.chromium.mojo.system.DataPipe.ConsumerHandle',
+ mojom.NULLABLE_DPPIPE.spec:
+ 'org.chromium.mojo.system.DataPipe.ProducerHandle',
+ mojom.NULLABLE_HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
+ mojom.NULLABLE_MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
+ mojom.NULLABLE_SHAREDBUFFER.spec:
+ 'org.chromium.mojo.system.SharedBufferHandle',
+ mojom.NULLABLE_STRING.spec: 'String',
+ mojom.SHAREDBUFFER.spec: 'org.chromium.mojo.system.SharedBufferHandle',
+ mojom.STRING.spec: 'String',
+ mojom.UINT16.spec: 'short',
+ mojom.UINT32.spec: 'int',
+ mojom.UINT64.spec: 'long',
+ mojom.UINT8.spec: 'byte',
+}
+
+_spec_to_decode_method = {
+ mojom.BOOL.spec: 'readBoolean',
+ mojom.DCPIPE.spec: 'readConsumerHandle',
+ mojom.DOUBLE.spec: 'readDouble',
+ mojom.DPPIPE.spec: 'readProducerHandle',
+ mojom.FLOAT.spec: 'readFloat',
+ mojom.HANDLE.spec: 'readUntypedHandle',
+ mojom.INT16.spec: 'readShort',
+ mojom.INT32.spec: 'readInt',
+ mojom.INT64.spec: 'readLong',
+ mojom.INT8.spec: 'readByte',
+ mojom.MSGPIPE.spec: 'readMessagePipeHandle',
+ mojom.NULLABLE_DCPIPE.spec: 'readConsumerHandle',
+ mojom.NULLABLE_DPPIPE.spec: 'readProducerHandle',
+ mojom.NULLABLE_HANDLE.spec: 'readUntypedHandle',
+ mojom.NULLABLE_MSGPIPE.spec: 'readMessagePipeHandle',
+ mojom.NULLABLE_SHAREDBUFFER.spec: 'readSharedBufferHandle',
+ mojom.NULLABLE_STRING.spec: 'readString',
+ mojom.SHAREDBUFFER.spec: 'readSharedBufferHandle',
+ mojom.STRING.spec: 'readString',
+ mojom.UINT16.spec: 'readShort',
+ mojom.UINT32.spec: 'readInt',
+ mojom.UINT64.spec: 'readLong',
+ mojom.UINT8.spec: 'readByte',
+}
+
+_java_primitive_to_boxed_type = {
+ 'boolean': 'Boolean',
+ 'byte': 'Byte',
+ 'double': 'Double',
+ 'float': 'Float',
+ 'int': 'Integer',
+ 'long': 'Long',
+ 'short': 'Short',
+}
+
+
+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 UpperCamelCase(name):
+ return ''.join([x.capitalize() 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' and len(components) > 1:
+ components = components[1:]
+ # variable cannot starts with a digit.
+ if components[0][0].isdigit():
+ components[0] = '_' + components[0]
+ return '_'.join([x.upper() for x in components])
+
+def GetNameForElement(element):
+ if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
+ mojom.IsStructKind(element)):
+ return UpperCamelCase(element.name)
+ if mojom.IsInterfaceRequestKind(element):
+ return GetNameForElement(element.kind)
+ if isinstance(element, (mojom.Method,
+ mojom.Parameter,
+ mojom.Field)):
+ return CamelCase(element.name)
+ if isinstance(element, mojom.EnumValue):
+ return (GetNameForElement(element.enum) + '.' +
+ ConstantStyle(element.name))
+ if isinstance(element, (mojom.NamedValue,
+ mojom.Constant,
+ mojom.EnumField)):
+ return ConstantStyle(element.name)
+ raise Exception('Unexpected element: %s' % element)
+
+def GetInterfaceResponseName(method):
+ return UpperCamelCase(method.name + 'Response')
+
+def ParseStringAttribute(attribute):
+ assert isinstance(attribute, basestring)
+ return attribute
+
+def GetJavaTrueFalse(value):
+ return 'true' if value else 'false'
+
+def GetArrayNullabilityFlags(kind):
+ """Returns nullability flags for an array type, see Decoder.java.
+
+ As we have dedicated decoding functions for arrays, we have to pass
+ nullability information about both the array itself, as well as the array
+ element type there.
+ """
+ assert mojom.IsAnyArrayKind(kind)
+ ARRAY_NULLABLE = \
+ 'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE'
+ ELEMENT_NULLABLE = \
+ 'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE'
+ NOTHING_NULLABLE = \
+ 'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE'
+
+ flags_to_set = []
+ if mojom.IsNullableKind(kind):
+ flags_to_set.append(ARRAY_NULLABLE)
+ if mojom.IsNullableKind(kind.kind):
+ flags_to_set.append(ELEMENT_NULLABLE)
+
+ if not flags_to_set:
+ flags_to_set = [NOTHING_NULLABLE]
+ return ' | '.join(flags_to_set)
+
+
+def AppendEncodeDecodeParams(initial_params, context, kind, bit):
+ """ Appends standard parameters shared between encode and decode calls. """
+ params = list(initial_params)
+ if (kind == mojom.BOOL):
+ params.append(str(bit))
+ if mojom.IsReferenceKind(kind):
+ if mojom.IsAnyArrayKind(kind):
+ params.append(GetArrayNullabilityFlags(kind))
+ else:
+ params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind)))
+ if mojom.IsAnyArrayKind(kind):
+ if mojom.IsFixedArrayKind(kind):
+ params.append(str(kind.length))
+ else:
+ params.append(
+ 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH');
+ if mojom.IsInterfaceKind(kind):
+ params.append('%s.MANAGER' % GetJavaType(context, kind))
+ if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
+ params.append('%s.MANAGER' % GetJavaType(context, kind.kind))
+ return params
+
+
+@contextfilter
+def DecodeMethod(context, kind, offset, bit):
+ def _DecodeMethodName(kind):
+ if mojom.IsAnyArrayKind(kind):
+ return _DecodeMethodName(kind.kind) + 's'
+ if mojom.IsEnumKind(kind):
+ return _DecodeMethodName(mojom.INT32)
+ if mojom.IsInterfaceRequestKind(kind):
+ return 'readInterfaceRequest'
+ if mojom.IsInterfaceKind(kind):
+ return 'readServiceInterface'
+ return _spec_to_decode_method[kind.spec]
+ methodName = _DecodeMethodName(kind)
+ params = AppendEncodeDecodeParams([ str(offset) ], context, kind, bit)
+ return '%s(%s)' % (methodName, ', '.join(params))
+
+@contextfilter
+def EncodeMethod(context, kind, variable, offset, bit):
+ params = AppendEncodeDecodeParams(
+ [ variable, str(offset) ], context, kind, bit)
+ return 'encode(%s)' % ', '.join(params)
+
+def GetPackage(module):
+ if 'JavaPackage' in module.attributes:
+ return ParseStringAttribute(module.attributes['JavaPackage'])
+ # Default package.
+ if module.namespace:
+ return 'org.chromium.mojom.' + module.namespace
+ return 'org.chromium.mojom'
+
+def GetNameForKind(context, kind):
+ def _GetNameHierachy(kind):
+ hierachy = []
+ if kind.parent_kind:
+ hierachy = _GetNameHierachy(kind.parent_kind)
+ hierachy.append(GetNameForElement(kind))
+ return hierachy
+
+ module = context.resolve('module')
+ elements = []
+ if GetPackage(module) != GetPackage(kind.module):
+ elements += [GetPackage(kind.module)]
+ elements += _GetNameHierachy(kind)
+ return '.'.join(elements)
+
+def GetBoxedJavaType(context, kind):
+ unboxed_type = GetJavaType(context, kind, False)
+ if unboxed_type in _java_primitive_to_boxed_type:
+ return _java_primitive_to_boxed_type[unboxed_type]
+ return unboxed_type
+
+@contextfilter
+def GetJavaType(context, kind, boxed=False):
+ if boxed:
+ return GetBoxedJavaType(context, kind)
+ if mojom.IsStructKind(kind) or mojom.IsInterfaceKind(kind):
+ return GetNameForKind(context, kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' %
+ GetNameForKind(context, kind.kind))
+ if mojom.IsAnyArrayKind(kind):
+ return '%s[]' % GetJavaType(context, kind.kind)
+ if mojom.IsEnumKind(kind):
+ return 'int'
+ return _spec_to_java_type[kind.spec]
+
+@contextfilter
+def DefaultValue(context, field):
+ assert field.default
+ if isinstance(field.kind, mojom.Struct):
+ assert field.default == 'default'
+ return 'new %s()' % GetJavaType(context, field.kind)
+ return '(%s) %s' % (
+ GetJavaType(context, field.kind),
+ ExpressionToText(context, field.default, kind_spec=field.kind.spec))
+
+@contextfilter
+def ConstantValue(context, constant):
+ return '(%s) %s' % (
+ GetJavaType(context, constant.kind),
+ ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
+
+@contextfilter
+def NewArray(context, kind, size):
+ if mojom.IsAnyArrayKind(kind.kind):
+ return NewArray(context, kind.kind, size) + '[]'
+ return 'new %s[%s]' % (GetJavaType(context, kind.kind), size)
+
+@contextfilter
+def ExpressionToText(context, token, kind_spec=''):
+ def _TranslateNamedValue(named_value):
+ entity_name = GetNameForElement(named_value)
+ if named_value.parent_kind:
+ return GetJavaType(context, 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)
+ if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
+ return entity_name
+ return GetPackage(named_value.module) + '.' + entity_name
+
+ if isinstance(token, mojom.NamedValue):
+ return _TranslateNamedValue(token)
+ if kind_spec.startswith('i') or kind_spec.startswith('u'):
+ # Add Long suffix to all integer literals.
+ number = ast.literal_eval(token.lstrip('+ '))
+ if not isinstance(number, (int, long)):
+ raise ValueError('got unexpected type %r for int literal %r' % (
+ type(number), token))
+ # If the literal is too large to fit a signed long, convert it to the
+ # equivalent signed long.
+ if number >= 2 ** 63:
+ number -= 2 ** 64
+ return '%dL' % number
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == 'double.INFINITY':
+ return 'java.lang.Double.POSITIVE_INFINITY'
+ if token.value == 'double.NEGATIVE_INFINITY':
+ return 'java.lang.Double.NEGATIVE_INFINITY'
+ if token.value == 'double.NAN':
+ return 'java.lang.Double.NaN'
+ if token.value == 'float.INFINITY':
+ return 'java.lang.Float.POSITIVE_INFINITY'
+ if token.value == 'float.NEGATIVE_INFINITY':
+ return 'java.lang.Float.NEGATIVE_INFINITY'
+ if token.value == 'float.NAN':
+ return 'java.lang.Float.NaN'
+ return token
+
+def IsPointerArrayKind(kind):
+ if not mojom.IsAnyArrayKind(kind):
+ return False
+ sub_kind = kind.kind
+ return mojom.IsObjectKind(sub_kind)
+
+def GetResponseStructFromMethod(method):
+ return generator.GetDataHeader(
+ False, generator.GetResponseStructFromMethod(method))
+
+def GetStructFromMethod(method):
+ return generator.GetDataHeader(
+ False, generator.GetStructFromMethod(method))
+
+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')
+
+def GetMethodOrdinalName(method):
+ return ConstantStyle(method.name) + '_ORDINAL'
+
+def HasMethodWithResponse(interface):
+ for method in interface.methods:
+ if method.response_parameters is not None:
+ return True
+ return False
+
+def HasMethodWithoutResponse(interface):
+ for method in interface.methods:
+ if method.response_parameters is None:
+ return True
+ return False
+
+@contextlib.contextmanager
+def TempDir():
+ dirname = tempfile.mkdtemp()
+ try:
+ yield dirname
+ finally:
+ shutil.rmtree(dirname)
+
+def ZipContentInto(root, zip_filename):
+ with zipfile.ZipFile(zip_filename, 'w') as zip_file:
+ for dirname, _, files in os.walk(root):
+ for filename in files:
+ path = os.path.join(dirname, filename)
+ path_in_archive = os.path.relpath(path, root)
+ zip_file.write(path, path_in_archive)
+
+class Generator(generator.Generator):
+
+ java_filters = {
+ 'interface_response_name': GetInterfaceResponseName,
+ 'constant_value': ConstantValue,
+ 'default_value': DefaultValue,
+ 'decode_method': DecodeMethod,
+ 'expression_to_text': ExpressionToText,
+ 'encode_method': EncodeMethod,
+ 'has_method_with_response': HasMethodWithResponse,
+ 'has_method_without_response': HasMethodWithoutResponse,
+ 'is_fixed_array_kind': mojom.IsFixedArrayKind,
+ 'is_handle': mojom.IsNonInterfaceHandleKind,
+ 'is_nullable_kind': mojom.IsNullableKind,
+ 'is_pointer_array_kind': IsPointerArrayKind,
+ 'is_struct_kind': mojom.IsStructKind,
+ 'java_type': GetJavaType,
+ 'java_true_false': GetJavaTrueFalse,
+ 'method_ordinal_name': GetMethodOrdinalName,
+ 'name': GetNameForElement,
+ 'new_array': NewArray,
+ 'response_struct_from_method': GetResponseStructFromMethod,
+ 'struct_from_method': GetStructFromMethod,
+ 'struct_size': lambda ps: ps.GetTotalSize() + _HEADER_SIZE,
+ }
+
+ def GetJinjaExports(self):
+ return {
+ 'package': GetPackage(self.module),
+ }
+
+ def GetJinjaExportsForInterface(self, interface):
+ exports = self.GetJinjaExports()
+ exports.update({'interface': interface})
+ if interface.client:
+ for client in self.module.interfaces:
+ if client.name == interface.client:
+ exports.update({'client': client})
+ return exports
+
+ @UseJinja('java_templates/enum.java.tmpl', filters=java_filters)
+ def GenerateEnumSource(self, enum):
+ exports = self.GetJinjaExports()
+ exports.update({'enum': enum})
+ return exports
+
+ @UseJinja('java_templates/struct.java.tmpl', filters=java_filters)
+ def GenerateStructSource(self, struct):
+ exports = self.GetJinjaExports()
+ exports.update({'struct': struct})
+ return exports
+
+ @UseJinja('java_templates/interface.java.tmpl', filters=java_filters)
+ def GenerateInterfaceSource(self, interface):
+ return self.GetJinjaExportsForInterface(interface)
+
+ @UseJinja('java_templates/interface_internal.java.tmpl', filters=java_filters)
+ def GenerateInterfaceInternalSource(self, interface):
+ return self.GetJinjaExportsForInterface(interface)
+
+ @UseJinja('java_templates/constants.java.tmpl', filters=java_filters)
+ def GenerateConstantsSource(self, module):
+ exports = self.GetJinjaExports()
+ exports.update({'main_entity': GetConstantsMainEntityName(module),
+ 'constants': module.constants})
+ return exports
+
+ def DoGenerateFiles(self):
+ if not os.path.exists(self.output_dir):
+ try:
+ os.makedirs(self.output_dir)
+ except:
+ # Ignore errors on directory creation.
+ pass
+
+ # Keep this above the others as .GetStructs() changes the state of the
+ # module, annotating structs with required information.
+ for struct in self.GetStructs():
+ self.Write(self.GenerateStructSource(struct),
+ '%s.java' % GetNameForElement(struct))
+
+ for enum in self.module.enums:
+ self.Write(self.GenerateEnumSource(enum),
+ '%s.java' % GetNameForElement(enum))
+
+ for interface in self.module.interfaces:
+ self.Write(self.GenerateInterfaceSource(interface),
+ '%s.java' % GetNameForElement(interface))
+ self.Write(self.GenerateInterfaceInternalSource(interface),
+ '%s_Internal.java' % GetNameForElement(interface))
+
+ if self.module.constants:
+ self.Write(self.GenerateConstantsSource(self.module),
+ '%s.java' % GetConstantsMainEntityName(self.module))
+
+ def GenerateFiles(self, unparsed_args):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--java_output_directory', dest='java_output_directory')
+ args = parser.parse_args(unparsed_args)
+ package_path = GetPackage(self.module).replace('.', '/')
+
+ # Generate the java files in a temporary directory and place a single
+ # srcjar in the output directory.
+ zip_filename = os.path.join(self.output_dir,
+ "%s.srcjar" % self.module.name)
+ with TempDir() as temp_java_root:
+ self.output_dir = os.path.join(temp_java_root, package_path)
+ self.DoGenerateFiles();
+ ZipContentInto(temp_java_root, zip_filename)
+
+ if args.java_output_directory:
+ # If requested, generate the java files directly into indicated directory.
+ self.output_dir = os.path.join(args.java_output_directory, package_path)
+ self.DoGenerateFiles();
+
+ def GetJinjaParameters(self):
+ return {
+ 'lstrip_blocks': True,
+ 'trim_blocks': True,
+ }
+
+ def GetGlobals(self):
+ return {
+ 'namespace': self.module.namespace,
+ 'module': self.module,
+ }
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
new file mode 100644
index 0000000..c9109fb
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -0,0 +1,275 @@
+# 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.NULLABLE_HANDLE: "null",
+ mojom.NULLABLE_DCPIPE: "null",
+ mojom.NULLABLE_DPPIPE: "null",
+ mojom.NULLABLE_MSGPIPE: "null",
+ mojom.NULLABLE_SHAREDBUFFER: "null",
+ mojom.INT64: "0",
+ mojom.UINT64: "0",
+ mojom.DOUBLE: "0",
+ mojom.STRING: "null",
+ mojom.NULLABLE_STRING: "null"
+}
+
+
+def JavaScriptType(kind):
+ if kind.imported_from:
+ return kind.imported_from["unique_name"] + "." + kind.name
+ return kind.name
+
+
+def JavaScriptDefaultValue(field):
+ if field.default:
+ if mojom.IsStructKind(field.kind):
+ 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 mojom.IsStructKind(field.kind):
+ return "null"
+ if mojom.IsAnyArrayKind(field.kind):
+ return "null"
+ if mojom.IsInterfaceKind(field.kind) or \
+ mojom.IsInterfaceRequestKind(field.kind):
+ return _kind_to_javascript_default_value[mojom.MSGPIPE]
+ if mojom.IsEnumKind(field.kind):
+ 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.NULLABLE_HANDLE: "codec.NullableHandle",
+ mojom.NULLABLE_DCPIPE: "codec.NullableHandle",
+ mojom.NULLABLE_DPPIPE: "codec.NullableHandle",
+ mojom.NULLABLE_MSGPIPE: "codec.NullableHandle",
+ mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle",
+ mojom.INT64: "codec.Int64",
+ mojom.UINT64: "codec.Uint64",
+ mojom.DOUBLE: "codec.Double",
+ mojom.STRING: "codec.String",
+ mojom.NULLABLE_STRING: "codec.NullableString",
+}
+
+
+def CodecType(kind):
+ if kind in mojom.PRIMITIVES:
+ return _kind_to_codec_type[kind]
+ if mojom.IsStructKind(kind):
+ pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
+ else "PointerTo"
+ return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind))
+ if mojom.IsAnyArrayKind(kind):
+ array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf"
+ element_type = "codec.PackedBool" if mojom.IsBoolKind(kind.kind) \
+ else CodecType(kind.kind)
+ return "new codec.%s(%s)" % (array_type, element_type)
+ if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
+ return CodecType(mojom.MSGPIPE)
+ if mojom.IsEnumKind(kind):
+ return _kind_to_codec_type[mojom.INT32]
+ return kind
+
+
+def JavaScriptDecodeSnippet(kind):
+ if kind in mojom.PRIMITIVES:
+ return "decodeStruct(%s)" % CodecType(kind)
+ if mojom.IsStructKind(kind):
+ return "decodeStructPointer(%s)" % JavaScriptType(kind)
+ if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
+ return "decodeArrayPointer(codec.PackedBool)"
+ if mojom.IsAnyArrayKind(kind):
+ return "decodeArrayPointer(%s)" % CodecType(kind.kind)
+ if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
+ return JavaScriptDecodeSnippet(mojom.MSGPIPE)
+ if mojom.IsEnumKind(kind):
+ return JavaScriptDecodeSnippet(mojom.INT32)
+
+
+def JavaScriptEncodeSnippet(kind):
+ if kind in mojom.PRIMITIVES:
+ return "encodeStruct(%s, " % CodecType(kind)
+ if mojom.IsStructKind(kind):
+ return "encodeStructPointer(%s, " % JavaScriptType(kind)
+ if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
+ return "encodeArrayPointer(codec.PackedBool, ";
+ if mojom.IsAnyArrayKind(kind):
+ return "encodeArrayPointer(%s, " % CodecType(kind.kind)
+ if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
+ return JavaScriptEncodeSnippet(mojom.MSGPIPE)
+ if mojom.IsEnumKind(kind):
+ return JavaScriptEncodeSnippet(mojom.INT32)
+
+
+def JavaScriptFieldOffset(packed_field):
+ return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
+
+
+def JavaScriptNullableParam(packed_field):
+ return "true" if mojom.IsNullableKind(packed_field.field.kind) else "false"
+
+
+def JavaScriptValidateArrayParams(packed_field):
+ nullable = JavaScriptNullableParam(packed_field)
+ field_offset = JavaScriptFieldOffset(packed_field)
+ element_kind = packed_field.field.kind.kind
+ element_size = pack.PackedField.GetSizeForKind(element_kind)
+ element_count = generator.ExpectedArraySize(packed_field.field.kind)
+ element_type = "codec.PackedBool" if mojom.IsBoolKind(element_kind) \
+ else CodecType(element_kind)
+ return "%s, %s, %s, %s, %s" % \
+ (field_offset, element_size, element_count, element_type, nullable)
+
+
+def JavaScriptValidateStructParams(packed_field):
+ nullable = JavaScriptNullableParam(packed_field)
+ field_offset = JavaScriptFieldOffset(packed_field)
+ struct_type = JavaScriptType(packed_field.field.kind)
+ return "%s, %s, %s" % (field_offset, struct_type, nullable)
+
+
+def JavaScriptValidateStringParams(packed_field):
+ nullable = JavaScriptNullableParam(packed_field)
+ return "%s, %s" % (JavaScriptFieldOffset(packed_field), nullable)
+
+
+def JavaScriptValidateHandleParams(packed_field):
+ nullable = JavaScriptNullableParam(packed_field)
+ field_offset = JavaScriptFieldOffset(packed_field)
+ return "%s, %s" % (field_offset, nullable)
+
+
+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)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == "double.INFINITY" or token.value == "float.INFINITY":
+ return "Infinity";
+ if token.value == "double.NEGATIVE_INFINITY" or \
+ token.value == "float.NEGATIVE_INFINITY":
+ return "-Infinity";
+ if token.value == "double.NAN" or token.value == "float.NAN":
+ return "NaN";
+
+ return token
+
+
+def ExpressionToText(value):
+ return TranslateConstants(value)
+
+
+def IsArrayPointerField(field):
+ return mojom.IsAnyArrayKind(field.kind)
+
+def IsStringPointerField(field):
+ return mojom.IsStringKind(field.kind)
+
+def IsStructPointerField(field):
+ return mojom.IsStructKind(field.kind)
+
+def IsHandleField(field):
+ return mojom.IsAnyHandleKind(field.kind)
+
+
+class Generator(generator.Generator):
+
+ js_filters = {
+ "default_value": JavaScriptDefaultValue,
+ "payload_size": JavaScriptPayloadSize,
+ "decode_snippet": JavaScriptDecodeSnippet,
+ "encode_snippet": JavaScriptEncodeSnippet,
+ "expression_to_text": ExpressionToText,
+ "field_offset": JavaScriptFieldOffset,
+ "has_callbacks": mojom.HasCallbacks,
+ "is_array_pointer_field": IsArrayPointerField,
+ "is_struct_pointer_field": IsStructPointerField,
+ "is_string_pointer_field": IsStringPointerField,
+ "is_handle_field": IsHandleField,
+ "js_type": JavaScriptType,
+ "stylize_method": generator.StudlyCapsToCamel,
+ "validate_array_params": JavaScriptValidateArrayParams,
+ "validate_handle_params": JavaScriptValidateHandleParams,
+ "validate_string_params": JavaScriptValidateStringParams,
+ "validate_struct_params": JavaScriptValidateStructParams,
+ }
+
+ @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/mojo/public/tools/bindings/generators/mojom_python_generator.py b/mojo/public/tools/bindings/generators/mojom_python_generator.py
new file mode 100644
index 0000000..8034480
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_python_generator.py
@@ -0,0 +1,291 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generates Python source files from a mojom.Module."""
+
+import re
+from itertools import ifilter
+
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+from mojom.generate.template_expander import UseJinja
+
+_kind_to_type = {
+ mojom.BOOL: '_descriptor.TYPE_BOOL',
+ mojom.INT8: '_descriptor.TYPE_INT8',
+ mojom.UINT8: '_descriptor.TYPE_UINT8',
+ mojom.INT16: '_descriptor.TYPE_INT16',
+ mojom.UINT16: '_descriptor.TYPE_UINT16',
+ mojom.INT32: '_descriptor.TYPE_INT32',
+ mojom.UINT32: '_descriptor.TYPE_UINT32',
+ mojom.INT64: '_descriptor.TYPE_INT64',
+ mojom.UINT64: '_descriptor.TYPE_UINT64',
+ mojom.FLOAT: '_descriptor.TYPE_FLOAT',
+ mojom.DOUBLE: '_descriptor.TYPE_DOUBLE',
+ mojom.STRING: '_descriptor.TYPE_STRING',
+ mojom.NULLABLE_STRING: '_descriptor.TYPE_NULLABLE_STRING',
+ mojom.HANDLE: '_descriptor.TYPE_HANDLE',
+ mojom.DCPIPE: '_descriptor.TYPE_HANDLE',
+ mojom.DPPIPE: '_descriptor.TYPE_HANDLE',
+ mojom.MSGPIPE: '_descriptor.TYPE_HANDLE',
+ mojom.SHAREDBUFFER: '_descriptor.TYPE_HANDLE',
+ mojom.NULLABLE_HANDLE: '_descriptor.TYPE_NULLABLE_HANDLE',
+ mojom.NULLABLE_DCPIPE: '_descriptor.TYPE_NULLABLE_HANDLE',
+ mojom.NULLABLE_DPPIPE: '_descriptor.TYPE_NULLABLE_HANDLE',
+ mojom.NULLABLE_MSGPIPE: '_descriptor.TYPE_NULLABLE_HANDLE',
+ mojom.NULLABLE_SHAREDBUFFER: '_descriptor.TYPE_NULLABLE_HANDLE',
+}
+
+# int64 integers are not handled by array.array. int64/uint64 array are
+# supported but storage is not optimized (ie. they are plain python list, not
+# array.array)
+_kind_to_typecode_for_native_array = {
+ mojom.INT8: 'b',
+ mojom.UINT8: 'B',
+ mojom.INT16: 'h',
+ mojom.UINT16: 'H',
+ mojom.INT32: 'i',
+ mojom.UINT32: 'I',
+ mojom.FLOAT: 'f',
+ mojom.DOUBLE: 'd',
+}
+
+_kind_to_typecode = dict(_kind_to_typecode_for_native_array)
+_kind_to_typecode.update({
+ mojom.INT64: 'q',
+ mojom.UINT64: 'Q',
+ mojom.HANDLE: 'i',
+ mojom.DCPIPE: 'i',
+ mojom.DPPIPE: 'i',
+ mojom.MSGPIPE: 'i',
+ mojom.SHAREDBUFFER: 'i',
+ mojom.NULLABLE_HANDLE: 'i',
+ mojom.NULLABLE_DCPIPE: 'i',
+ mojom.NULLABLE_DPPIPE: 'i',
+ mojom.NULLABLE_MSGPIPE: 'i',
+ mojom.NULLABLE_SHAREDBUFFER: 'i',
+})
+
+
+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 UpperCamelCase(name):
+ return ''.join([x.capitalize() 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 (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
+ mojom.IsStructKind(element)):
+ return UpperCamelCase(element.name)
+ if isinstance(element, mojom.EnumValue):
+ return (GetNameForElement(element.enum) + '.' +
+ ConstantStyle(element.name))
+ if isinstance(element, (mojom.NamedValue,
+ mojom.Constant)):
+ return ConstantStyle(element.name)
+ raise Exception('Unexpected element: ' % element)
+
+def ExpressionToText(token):
+ if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
+ return str(token.computed_value)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == 'double.INFINITY' or token.value == 'float.INFINITY':
+ return 'float(\'inf\')';
+ if (token.value == 'double.NEGATIVE_INFINITY' or
+ token.value == 'float.NEGATIVE_INFINITY'):
+ return 'float(\'-inf\')'
+ if token.value == 'double.NAN' or token.value == 'float.NAN':
+ return 'float(\'nan\')';
+
+ if token in ['true', 'false']:
+ return str(token == 'true')
+
+ return token
+
+def GetStructClass(kind):
+ name = []
+ if kind.imported_from:
+ name.append(kind.imported_from['python_module'])
+ name.append(GetNameForElement(kind))
+ return '.'.join(name)
+
+def GetFieldType(kind, field=None):
+ if mojom.IsAnyArrayKind(kind):
+ arguments = []
+ if kind.kind in _kind_to_typecode_for_native_array:
+ arguments.append('%r' %_kind_to_typecode_for_native_array[kind.kind])
+ elif kind.kind != mojom.BOOL:
+ arguments.append(GetFieldType(kind.kind))
+ if mojom.IsNullableKind(kind):
+ arguments.append('nullable=True')
+ if mojom.IsFixedArrayKind(kind):
+ arguments.append('length=%d' % kind.length)
+ array_type = 'GenericArrayType'
+ if kind.kind == mojom.BOOL:
+ array_type = 'BooleanArrayType'
+ elif kind.kind in _kind_to_typecode_for_native_array:
+ array_type = 'NativeArrayType'
+ return '_descriptor.%s(%s)' % (array_type, ', '.join(arguments))
+
+ if mojom.IsStructKind(kind):
+ arguments = [ GetStructClass(kind) ]
+ if mojom.IsNullableKind(kind):
+ arguments.append('nullable=True')
+ return '_descriptor.StructType(%s)' % ', '.join(arguments)
+
+ if mojom.IsEnumKind(kind):
+ return GetFieldType(mojom.INT32)
+
+ return _kind_to_type.get(kind, '_descriptor.TYPE_NONE')
+
+def GetFieldDescriptor(packed_field):
+ field = packed_field.field
+ class_name = 'SingleFieldGroup'
+ if field.kind == mojom.BOOL:
+ class_name = 'FieldDescriptor'
+ arguments = [ '%r' % field.name ]
+ arguments.append(GetFieldType(field.kind, field))
+ arguments.append(str(packed_field.index))
+ arguments.append(str(packed_field.ordinal))
+ if field.default:
+ if mojom.IsStructKind(field.kind):
+ arguments.append('default_value=True')
+ else:
+ arguments.append('default_value=%s' % ExpressionToText(field.default))
+ return '_descriptor.%s(%s)' % (class_name, ', '.join(arguments))
+
+def GetFieldGroup(byte):
+ if len(byte.packed_fields) > 1:
+ descriptors = map(GetFieldDescriptor, byte.packed_fields)
+ return '_descriptor.BooleanGroup([%s])' % ', '.join(descriptors)
+ assert len(byte.packed_fields) == 1
+ return GetFieldDescriptor(byte.packed_fields[0])
+
+def ComputeStaticValues(module):
+ in_progress = set()
+ computed = set()
+
+ def GetComputedValue(named_value):
+ if isinstance(named_value, mojom.EnumValue):
+ field = next(ifilter(lambda field: field.name == named_value.name,
+ named_value.enum.fields), None)
+ if not field:
+ raise RuntimeError(
+ 'Unable to get computed value for field %s of enum %s' %
+ (named_value.name, named_value.enum.name))
+ if field not in computed:
+ ResolveEnum(named_value.enum)
+ return field.computed_value
+ elif isinstance(named_value, mojom.ConstantValue):
+ ResolveConstant(named_value.constant)
+ named_value.computed_value = named_value.constant.computed_value
+ return named_value.computed_value
+ else:
+ print named_value
+
+ def ResolveConstant(constant):
+ if constant in computed:
+ return
+ if constant in in_progress:
+ raise RuntimeError('Circular dependency for constant: %s' % constant.name)
+ in_progress.add(constant)
+ if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
+ computed_value = GetComputedValue(constant.value)
+ else:
+ computed_value = ExpressionToText(constant.value)
+ constant.computed_value = computed_value
+ in_progress.remove(constant)
+ computed.add(constant)
+
+ def ResolveEnum(enum):
+ def ResolveEnumField(enum, field, default_value):
+ if field in computed:
+ return
+ if field in in_progress:
+ raise RuntimeError('Circular dependency for enum: %s' % enum.name)
+ in_progress.add(field)
+ if field.value:
+ if isinstance(field.value, mojom.EnumValue):
+ computed_value = GetComputedValue(field.value)
+ elif isinstance(field.value, str):
+ computed_value = int(field.value, 0)
+ else:
+ raise RuntimeError('Unexpected value: %s' % field.value)
+ else:
+ computed_value = default_value
+ field.computed_value = computed_value
+ in_progress.remove(field)
+ computed.add(field)
+
+ current_value = 0
+ for field in enum.fields:
+ ResolveEnumField(enum, field, current_value)
+ current_value = field.computed_value + 1
+
+ for constant in module.constants:
+ ResolveConstant(constant)
+
+ for enum in module.enums:
+ ResolveEnum(enum)
+
+ for struct in module.structs:
+ for constant in struct.constants:
+ ResolveConstant(constant)
+ for enum in struct.enums:
+ ResolveEnum(enum)
+ for field in struct.fields:
+ if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
+ field.default.computed_value = GetComputedValue(field.default)
+
+ return module
+
+
+class Generator(generator.Generator):
+
+ python_filters = {
+ 'expression_to_text': ExpressionToText,
+ 'field_group': GetFieldGroup,
+ 'name': GetNameForElement,
+ }
+
+ @UseJinja('python_templates/module.py.tmpl', filters=python_filters)
+ def GeneratePythonModule(self):
+ return {
+ 'imports': self.GetImports(),
+ 'enums': self.module.enums,
+ 'module': ComputeStaticValues(self.module),
+ 'structs': self.GetStructs(),
+ }
+
+ def GenerateFiles(self, args):
+ self.Write(self.GeneratePythonModule(),
+ '%s.py' % self.module.name.replace('.mojom', '_mojom'))
+
+ def GetImports(self):
+ for each in self.module.imports:
+ each['python_module'] = each['module_name'].replace('.mojom', '_mojom')
+ return self.module.imports
+
+ def GetJinjaParameters(self):
+ return {
+ 'lstrip_blocks': True,
+ 'trim_blocks': True,
+ }
diff --git a/mojo/public/tools/bindings/generators/python_templates/module.py.tmpl b/mojo/public/tools/bindings/generators/python_templates/module.py.tmpl
new file mode 100644
index 0000000..d5bff30
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/python_templates/module.py.tmpl
@@ -0,0 +1,56 @@
+{% from "module_macros.tmpl" import enum_values %}
+# Copyright 2014 The Chromium 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.bindings.descriptor as _descriptor
+import mojo.bindings.reflection as _reflection
+{% if imports %}
+
+{% for import in imports %}
+import {{import.python_module}}
+{% endfor %}
+{% endif %}
+{#--- Constants #}
+{% if module.constants %}
+
+{% for constant in module.constants %}
+{{constant|name}} = {{constant.value|expression_to_text}}
+{% endfor %}
+{% endif %}
+{% for enum in module.enums %}
+
+class {{enum|name}}(object):
+ __metaclass__ = _reflection.MojoEnumType
+ VALUES = {{enum_values(enum)|indent(2)}}
+{% endfor %}
+{% for struct in module.structs %}
+
+class {{struct|name}}(object):
+ __metaclass__ = _reflection.MojoStructType
+ DESCRIPTOR = {
+{% if struct.constants %}
+ 'constants': {
+{% for constant in struct.constants %}
+ '{{constant|name}}': {{constant.value|expression_to_text}},
+{% endfor %}
+ },
+{% endif %}
+{% if struct.enums %}
+ 'enums': {
+{% for enum in struct.enums %}
+ '{{enum|name}}': {{enum_values(enum)|indent(6)}},
+{% endfor %}
+ },
+{% endif %}
+{% if struct.fields %}
+ 'fields': [
+{% for byte in struct.bytes %}
+{% if byte.packed_fields %}
+ {{byte|field_group}},
+{% endif %}
+{% endfor %}
+ ],
+{% endif %}
+ }
+{% endfor %}
diff --git a/mojo/public/tools/bindings/generators/python_templates/module_macros.tmpl b/mojo/public/tools/bindings/generators/python_templates/module_macros.tmpl
new file mode 100644
index 0000000..305b26a
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/python_templates/module_macros.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.
+
+{%- macro enum_values(enum) -%}
+[
+{% for field in enum.fields %}
+ ('{{field.name}}', {{field.computed_value}}),
+{% endfor %}
+]
+{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/run_cpp_generator.py b/mojo/public/tools/bindings/generators/run_cpp_generator.py
new file mode 100755
index 0000000..4c6f597
--- /dev/null
+++ b/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/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
new file mode 100644
index 0000000..e12d8e0
--- /dev/null
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -0,0 +1,130 @@
+# Copyright 2014 The Chromium 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. The output files
+# will go under the generated file directory tree with the same path as each
+# input file.
+#
+# Parameters:
+#
+# sources (required)
+# List of source .mojom files to compile.
+#
+# deps (optional)
+#
+# public_deps (optional)
+#
+# testonly (optional)
+#
+# visibility (optional)
+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/python_templates/module_macros.tmpl",
+ "$generator_root/generators/python_templates/module.py.tmpl",
+ "$generator_root/generators/mojom_cpp_generator.py",
+ "$generator_root/generators/mojom_js_generator.py",
+ "$generator_root/generators/mojom_python_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 = [
+ "{{source_gen_dir}}/{{source_name_part}}.mojom.cc",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom.h",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom-internal.h",
+ ]
+ generator_js_outputs = [
+ "{{source_gen_dir}}/{{source_name_part}}.mojom.js",
+ ]
+ generator_python_outputs = [
+ "{{source_gen_dir}}/{{source_name_part}}_mojom.py",
+ ]
+
+ if (defined(invoker.visibility)) {
+ # Need to save this because the the target_name is overwritten inside the
+ # action to be that of the action itself. Only define this in the case the
+ # var is used to avoid unused var error.
+ target_visibility = [ ":$target_name" ]
+ }
+
+ generator_target_name = target_name + "__generator"
+ action_foreach(generator_target_name) {
+ if (defined(invoker.visibility)) {
+ visibility = target_visibility + invoker.visibility
+ }
+ script = generator_script
+ inputs = generator_sources
+ sources = invoker.sources
+ outputs = generator_cpp_outputs +
+ generator_js_outputs +
+ generator_python_outputs
+ args = [
+ "{{source}}",
+ "--use_chromium_bundled_pylibs",
+ "-d", rebase_path("//", root_build_dir),
+ "-I", rebase_path("//", root_build_dir),
+ "-o", "{{source_gen_dir}}",
+ ]
+ }
+
+ source_set(target_name) {
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ 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",
+ ]
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ if (defined(invoker.public_deps)) {
+ public_deps = invoker.public_deps
+ }
+ }
+}
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.gypi b/mojo/public/tools/bindings/mojom_bindings_generator.gypi
new file mode 100644
index 0000000..c6e9f98
--- /dev/null
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.gypi
@@ -0,0 +1,111 @@
+# 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': '<(_target_name)_mojom_bindings_generator',
+ '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',
+ 'mojom_import_args%': [
+ '-I<(DEPTH)'
+ ],
+ },
+ '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/interface_definition.tmpl',
+ '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl',
+ '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl',
+ '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/struct_definition.tmpl',
+ '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/struct.java.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/python_templates/module_macros.tmpl',
+ '<(DEPTH)/mojo/public/tools/bindings/generators/python_templates/module.py.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/generators/mojom_python_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.py',
+ '<(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)',
+ '<@(mojom_import_args)',
+ '-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/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
new file mode 100755
index 0000000..53e3a33
--- /dev/null
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -0,0 +1,196 @@
+#!/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")
+ elif generator_name.lower() == "python":
+ generator_name = os.path.join(script_dir, "generators",
+ "mojom_python_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)]))
+
+
+def FindImportFile(dir_name, file_name, search_dirs):
+ for search_dir in [dir_name] + search_dirs:
+ path = os.path.join(search_dir, file_name)
+ if os.path.isfile(path):
+ return path
+ return os.path.join(dir_name, file_name)
+
+
+# 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 = FindImportFile(dirname,
+ import_data['filename'],
+ args.import_directories)
+ 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,python",
+ help="comma-separated list of generators")
+ parser.add_argument("--debug_print_intermediate", action="store_true",
+ help="print the intermediate representation")
+ parser.add_argument("-I", dest="import_directories", action="append",
+ metavar="directory", default=[],
+ help="add a directory to be searched for import files")
+ 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/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
new file mode 100644
index 0000000..de38856
--- /dev/null
+++ b/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/mojo/public/tools/bindings/pylib/mojom/__init__.py b/mojo/public/tools/bindings/pylib/mojom/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom/error.py b/mojo/public/tools/bindings/pylib/mojom/error.py
new file mode 100644
index 0000000..99522b9
--- /dev/null
+++ b/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/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/data.py b/mojo/public/tools/bindings/pylib/mojom/generate/data.py
new file mode 100644
index 0000000..fab7e2e
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/data.py
@@ -0,0 +1,383 @@
+# 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__
+
+ rv = IndexedString(string)
+ rv.__index__ = index
+ return rv
+
+builtin_values = frozenset([
+ "double.INFINITY",
+ "double.NEGATIVE_INFINITY",
+ "double.NAN",
+ "float.INFINITY",
+ "float.NEGATIVE_INFINITY",
+ "float.NAN"])
+
+def IsBuiltinValue(value):
+ return value in builtin_values
+
+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, kind):
+ """Like LookupKind, but for constant values."""
+ # If the type is an enum, the value can be specified as a qualified name, in
+ # which case the form EnumName.ENUM_VALUE must be used. We use the presence
+ # of a '.' in the requested name to identify this. Otherwise, we prepend the
+ # enum name.
+ if isinstance(kind, mojom.Enum) and '.' not in name:
+ name = '%s.%s' % (kind.spec.split(':', 1)[1], name)
+ for i in reversed(xrange(len(scope) + 1)):
+ test_spec = '.'.join(scope[:i])
+ if test_spec:
+ test_spec += '.'
+ test_spec += name
+ value = values.get(test_spec)
+ if value:
+ return value
+
+ return values.get(name)
+
+def FixupExpression(module, value, scope, kind):
+ """Translates an IDENTIFIER into a built-in value or structured NamedValue
+ object."""
+ if isinstance(value, tuple) and value[0] == 'IDENTIFIER':
+ # Allow user defined values to shadow builtins.
+ result = LookupValue(module.values, value[1], scope, kind)
+ if result:
+ if isinstance(result, tuple):
+ raise Exception('Unable to resolve expression: %r' % value[1])
+ return result
+ if IsBuiltinValue(value[1]):
+ return mojom.BuiltinValue(value[1])
+ 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('?'):
+ kind = KindFromData(kinds, data[1:], scope).MakeNullableKind()
+ elif data.startswith('a:'):
+ kind = mojom.Array(KindFromData(kinds, data[2:], scope))
+ elif data.startswith('r:'):
+ kind = mojom.InterfaceRequest(KindFromData(kinds, data[2:], scope))
+ elif data.startswith('a'):
+ colon = data.find(':')
+ length = int(data[1:colon])
+ kind = mojom.FixedArray(length, KindFromData(kinds, data[colon+1:], scope))
+ else:
+ kind = mojom.Kind(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.copy(original_kind)
+ # |shared_definition| is used to store various properties (see
+ # |AddSharedProperty()| in module.py), including |imported_from|. We don't
+ # want the copy to share these with the original, so copy it if necessary.
+ if hasattr(original_kind, 'shared_definition'):
+ kind.shared_definition = copy.copy(original_kind.shared_definition)
+ 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:
+ # Values don't have shared definitions (since they're not nullable), so no
+ # need to do anything special.
+ value = copy.copy(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), field.kind)
+ 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), enum)
+ else:
+ field.value = FixupExpression(
+ module, data['value'], (module.namespace, ), enum)
+ 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, None)
+
+ value = mojom.ConstantValue(module, parent_kind, constant)
+ 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)
+ 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/mojo/public/tools/bindings/pylib/mojom/generate/data_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/data_tests.py
new file mode 100644
index 0000000..096554c
--- /dev/null
+++ b/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/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
new file mode 100644
index 0000000..eb433bf
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
@@ -0,0 +1,85 @@
+# 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 re
+
+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 ExpectedArraySize(kind):
+ if mojom.IsFixedArrayKind(kind):
+ return kind.length
+ return 0
+
+def StudlyCapsToCamel(studly):
+ return studly[0].lower() + studly[1:]
+
+def CamelCaseToAllCaps(camel_case):
+ return '_'.join(
+ word for word in re.split(r'([A-Z][^A-Z]+)', camel_case) if word).upper()
+
+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")
+
+ def GetJinjaParameters(self):
+ """Returns default constructor parameters for the jinja environment."""
+ return {}
+
+ def GetGlobals(self):
+ """Returns global mappings for the template generation."""
+ return {}
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
new file mode 100644
index 0000000..7ae7a83
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
@@ -0,0 +1,448 @@
+# 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
+
+
+class ReferenceKind(Kind):
+ """ReferenceKind represents pointer types and handle types.
+ A type is nullable if null (for pointer types) or invalid handle (for handle
+ types) is a legal value for the type.
+ """
+
+ def __init__(self, spec=None, is_nullable=False):
+ assert spec is None or is_nullable == spec.startswith('?')
+ Kind.__init__(self, spec)
+ self.is_nullable = is_nullable
+ self.shared_definition = {}
+
+ def MakeNullableKind(self):
+ assert not self.is_nullable
+
+ if self == STRING:
+ return NULLABLE_STRING
+ if self == HANDLE:
+ return NULLABLE_HANDLE
+ if self == DCPIPE:
+ return NULLABLE_DCPIPE
+ if self == DPPIPE:
+ return NULLABLE_DPPIPE
+ if self == MSGPIPE:
+ return NULLABLE_MSGPIPE
+ if self == SHAREDBUFFER:
+ return NULLABLE_SHAREDBUFFER
+
+ nullable_kind = type(self)()
+ nullable_kind.shared_definition = self.shared_definition
+ if self.spec is not None:
+ nullable_kind.spec = '?' + self.spec
+ nullable_kind.is_nullable = True
+
+ return nullable_kind
+
+ @classmethod
+ def AddSharedProperty(cls, name):
+ """Adds a property |name| to |cls|, which accesses the corresponding item in
+ |shared_definition|.
+
+ The reason of adding such indirection is to enable sharing definition
+ between a reference kind and its nullable variation. For example:
+ a = Struct('test_struct_1')
+ b = a.MakeNullableKind()
+ a.name = 'test_struct_2'
+ print b.name # Outputs 'test_struct_2'.
+ """
+ def Get(self):
+ return self.shared_definition[name]
+
+ def Set(self, value):
+ self.shared_definition[name] = value
+
+ setattr(cls, name, property(Get, Set))
+
+
+# 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 = ReferenceKind('s')
+HANDLE = ReferenceKind('h')
+DCPIPE = ReferenceKind('h:d:c')
+DPPIPE = ReferenceKind('h:d:p')
+MSGPIPE = ReferenceKind('h:m')
+SHAREDBUFFER = ReferenceKind('h:s')
+NULLABLE_STRING = ReferenceKind('?s', True)
+NULLABLE_HANDLE = ReferenceKind('?h', True)
+NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True)
+NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True)
+NULLABLE_MSGPIPE = ReferenceKind('?h:m', True)
+NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True)
+
+
+# Collection of all Primitive types
+PRIMITIVES = (
+ BOOL,
+ INT8,
+ INT16,
+ INT32,
+ INT64,
+ UINT8,
+ UINT16,
+ UINT32,
+ UINT64,
+ FLOAT,
+ DOUBLE,
+ STRING,
+ HANDLE,
+ DCPIPE,
+ DPPIPE,
+ MSGPIPE,
+ SHAREDBUFFER,
+ NULLABLE_STRING,
+ NULLABLE_HANDLE,
+ NULLABLE_DCPIPE,
+ NULLABLE_DPPIPE,
+ NULLABLE_MSGPIPE,
+ NULLABLE_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 BuiltinValue(object):
+ def __init__(self, value):
+ self.value = value
+
+
+class ConstantValue(NamedValue):
+ def __init__(self, module, parent_kind, constant):
+ NamedValue.__init__(self, module, parent_kind, constant.name)
+ self.constant = constant
+
+
+class EnumValue(NamedValue):
+ def __init__(self, module, enum, field):
+ NamedValue.__init__(self, module, enum.parent_kind, field.name)
+ self.enum = enum
+
+ def GetSpec(self):
+ return (self.namespace + '.' +
+ (self.parent_kind and (self.parent_kind.name + '.') or "") +
+ self.enum.name + '.' + self.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(ReferenceKind):
+ ReferenceKind.AddSharedProperty('name')
+ ReferenceKind.AddSharedProperty('module')
+ ReferenceKind.AddSharedProperty('imported_from')
+ ReferenceKind.AddSharedProperty('fields')
+
+ def __init__(self, name=None, module=None):
+ if name is not None:
+ spec = 'x:' + name
+ else:
+ spec = None
+ ReferenceKind.__init__(self, spec)
+ self.name = name
+ self.module = module
+ self.imported_from = None
+ 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(ReferenceKind):
+ ReferenceKind.AddSharedProperty('kind')
+
+ def __init__(self, kind=None):
+ if kind is not None:
+ ReferenceKind.__init__(self, 'a:' + kind.spec)
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind
+
+
+class FixedArray(ReferenceKind):
+ ReferenceKind.AddSharedProperty('kind')
+ ReferenceKind.AddSharedProperty('length')
+
+ def __init__(self, length=-1, kind=None):
+ if kind is not None:
+ ReferenceKind.__init__(self, 'a%d:%s' % (length, kind.spec))
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind
+ self.length = length
+
+
+class InterfaceRequest(ReferenceKind):
+ ReferenceKind.AddSharedProperty('kind')
+
+ def __init__(self, kind=None):
+ if kind is not None:
+ ReferenceKind.__init__(self, 'r:' + kind.spec)
+ else:
+ ReferenceKind.__init__(self)
+ self.kind = kind
+
+
+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(ReferenceKind):
+ ReferenceKind.AddSharedProperty('module')
+ ReferenceKind.AddSharedProperty('name')
+ ReferenceKind.AddSharedProperty('imported_from')
+ ReferenceKind.AddSharedProperty('client')
+ ReferenceKind.AddSharedProperty('methods')
+
+ def __init__(self, name=None, client=None, module=None):
+ if name is not None:
+ spec = 'x:' + name
+ else:
+ spec = None
+ ReferenceKind.__init__(self, spec)
+ self.module = module
+ self.name = name
+ self.imported_from = None
+ 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 is not 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):
+ self.interfaces.append(Interface(name, module=self))
+ return interface
+
+ def AddStruct(self, name):
+ struct=Struct(name, module=self)
+ self.structs.append(struct)
+ return struct
+
+
+def IsBoolKind(kind):
+ return kind.spec == BOOL.spec
+
+
+def IsFloatKind(kind):
+ return kind.spec == FLOAT.spec
+
+
+def IsStringKind(kind):
+ return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec
+
+
+def IsHandleKind(kind):
+ return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec
+
+
+def IsDataPipeConsumerKind(kind):
+ return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec
+
+
+def IsDataPipeProducerKind(kind):
+ return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec
+
+
+def IsMessagePipeKind(kind):
+ return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec
+
+
+def IsSharedBufferKind(kind):
+ return (kind.spec == SHAREDBUFFER.spec or
+ kind.spec == NULLABLE_SHAREDBUFFER.spec)
+
+
+def IsStructKind(kind):
+ return isinstance(kind, Struct)
+
+
+def IsArrayKind(kind):
+ return isinstance(kind, Array)
+
+
+def IsFixedArrayKind(kind):
+ return isinstance(kind, FixedArray)
+
+
+def IsInterfaceKind(kind):
+ return isinstance(kind, Interface)
+
+
+def IsInterfaceRequestKind(kind):
+ return isinstance(kind, InterfaceRequest)
+
+
+def IsEnumKind(kind):
+ return isinstance(kind, Enum)
+
+
+def IsReferenceKind(kind):
+ return isinstance(kind, ReferenceKind)
+
+
+def IsNullableKind(kind):
+ return IsReferenceKind(kind) and kind.is_nullable
+
+
+def IsAnyArrayKind(kind):
+ return IsArrayKind(kind) or IsFixedArrayKind(kind)
+
+
+def IsObjectKind(kind):
+ return IsStructKind(kind) or IsAnyArrayKind(kind) or IsStringKind(kind)
+
+
+def IsNonInterfaceHandleKind(kind):
+ return (IsHandleKind(kind) or
+ IsDataPipeConsumerKind(kind) or
+ IsDataPipeProducerKind(kind) or
+ IsMessagePipeKind(kind) or
+ IsSharedBufferKind(kind))
+
+
+def IsAnyHandleKind(kind):
+ return (IsNonInterfaceHandleKind(kind) or
+ IsInterfaceKind(kind) or
+ IsInterfaceRequestKind(kind))
+
+
+def IsMoveOnlyKind(kind):
+ return IsObjectKind(kind) or IsAnyHandleKind(kind)
+
+
+def IsCloneableKind(kind):
+ def ContainsHandles(kind, visited_kinds):
+ if kind in visited_kinds:
+ # No need to examine the kind again.
+ return False
+ visited_kinds.add(kind)
+ if IsAnyHandleKind(kind):
+ return True
+ if IsAnyArrayKind(kind):
+ return ContainsHandles(kind.kind, visited_kinds)
+ if IsStructKind(kind):
+ for field in kind.fields:
+ if ContainsHandles(field.kind, visited_kinds):
+ return True
+ return False
+
+ return not ContainsHandles(kind, set())
+
+
+def HasCallbacks(interface):
+ for method in interface.methods:
+ if method.response_parameters != None:
+ return True
+ return False
+
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py
new file mode 100644
index 0000000..a887686
--- /dev/null
+++ b/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/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
new file mode 100644
index 0000000..4661941
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
@@ -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.
+
+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.NULLABLE_HANDLE: 4,
+ mojom.NULLABLE_MSGPIPE: 4,
+ mojom.NULLABLE_SHAREDBUFFER: 4,
+ mojom.NULLABLE_DCPIPE: 4,
+ mojom.NULLABLE_DPPIPE: 4,
+ mojom.INT64: 8,
+ mojom.UINT64: 8,
+ mojom.DOUBLE: 8,
+ mojom.STRING: 8,
+ mojom.NULLABLE_STRING: 8
+ }
+
+ @classmethod
+ def GetSizeForKind(cls, kind):
+ if isinstance(kind, (mojom.Array, mojom.Struct, mojom.FixedArray)):
+ 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, index, ordinal):
+ """
+ Args:
+ field: the original field.
+ index: the position of the original field in the struct.
+ ordinal: the ordinal of the field for serialization.
+ """
+ self.field = field
+ self.index = index
+ 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 index, field in enumerate(struct.fields):
+ if field.ordinal is not None:
+ ordinal = field.ordinal
+ src_fields.append(PackedField(field, index, 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/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py
new file mode 100644
index 0000000..a0f664b
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.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 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 TestNullableTypes():
+ kinds = (mojom.STRING.MakeNullableKind(),
+ mojom.HANDLE.MakeNullableKind(),
+ mojom.Struct('test_struct').MakeNullableKind(),
+ mojom.DCPIPE.MakeNullableKind(),
+ mojom.Array().MakeNullableKind(),
+ mojom.DPPIPE.MakeNullableKind(),
+ mojom.FixedArray(5).MakeNullableKind(),
+ mojom.MSGPIPE.MakeNullableKind(),
+ mojom.Interface('test_inteface').MakeNullableKind(),
+ mojom.SHAREDBUFFER.MakeNullableKind(),
+ mojom.InterfaceRequest().MakeNullableKind())
+ fields = (1, 2, 4, 3, 5, 6, 8, 7, 9, 10, 11)
+ offsets = (0, 8, 12, 16, 24, 32, 36, 40, 48, 52, 56)
+ return TestSequence(kinds, fields, offsets)
+
+
+def TestAllTypes():
+ 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(),
+ mojom.STRING.MakeNullableKind()),
+ (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17, 18),
+ (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80, 88))
+
+
+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(TestNullableTypes)
+ errors += RunTest(TestAllTypes)
+ errors += RunTest(TestPaddingPackedOutOfOrderByOrdinal)
+ errors += RunTest(TestBools)
+
+ return errors
+
+
+if __name__ == '__main__':
+ sys.exit(Main(sys.argv[1:]))
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py
new file mode 100755
index 0000000..41f11a2
--- /dev/null
+++ b/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/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
new file mode 100644
index 0000000..3801d43
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
@@ -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.
+
+# 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(mojo_generator, 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])
+ final_kwargs = dict(mojo_generator.GetJinjaParameters())
+ final_kwargs.update(kwargs)
+ jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True,
+ **final_kwargs)
+ jinja_env.globals.update(mojo_generator.GetGlobals())
+ 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(args[0], base_dir, path_to_template, parameters,
+ **kwargs)
+ GeneratorInternal.func_name = generator.func_name
+ return GeneratorInternal
+ return RealDecorator
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py
new file mode 100644
index 0000000..eb39461
--- /dev/null
+++ b/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/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/ast.py b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
new file mode 100644
index 0000000..1612d98
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
@@ -0,0 +1,354 @@
+# Copyright 2014 The Chromium 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 classes 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). You may also define __repr__() to help with analyzing test
+# failures, especially for more complex types.
+
+
+class NodeBase(object):
+ """Base class for nodes in the AST."""
+
+ def __init__(self, filename=None, lineno=None):
+ self.filename = filename
+ self.lineno = lineno
+
+ def __eq__(self, other):
+ return type(self) == type(other)
+
+ # Make != the inverse of ==. (Subclasses shouldn't have to override this.)
+ def __ne__(self, other):
+ return not self == other
+
+
+# TODO(vtl): Some of this is complicated enough that it should be tested.
+class NodeListBase(NodeBase):
+ """Represents a list of other nodes, all having the same type. (This is meant
+ to be subclassed, with subclasses defining _list_item_type to be the class (or
+ classes, in a tuple) of the members of the list.)"""
+
+ def __init__(self, item_or_items=None, **kwargs):
+ super(NodeListBase, self).__init__(**kwargs)
+ self.items = []
+ if item_or_items is None:
+ pass
+ elif isinstance(item_or_items, list):
+ for item in item_or_items:
+ assert isinstance(item, self._list_item_type)
+ self.Append(item)
+ else:
+ assert isinstance(item_or_items, self._list_item_type)
+ self.Append(item_or_items)
+
+ # Support iteration. For everything else, users should just access |items|
+ # directly. (We intentionally do NOT supply |__len__()| or |__nonzero__()|, so
+ # |bool(NodeListBase())| is true.)
+ def __iter__(self):
+ return self.items.__iter__()
+
+ def __eq__(self, other):
+ return super(NodeListBase, self).__eq__(other) and \
+ self.items == other.items
+
+ # Implement this so that on failure, we get slightly more sensible output.
+ def __repr__(self):
+ return self.__class__.__name__ + "([" + \
+ ", ".join([repr(elem) for elem in self.items]) + "])"
+
+ def Insert(self, item):
+ """Inserts item at the front of the list."""
+
+ assert isinstance(item, self._list_item_type)
+ self.items.insert(0, item)
+ self._UpdateFilenameAndLineno()
+
+ def Append(self, item):
+ """Appends item to the end of the list."""
+
+ assert isinstance(item, self._list_item_type)
+ self.items.append(item)
+ self._UpdateFilenameAndLineno()
+
+ def _UpdateFilenameAndLineno(self):
+ if self.items:
+ self.filename = self.items[0].filename
+ self.lineno = self.items[0].lineno
+
+
+class Definition(NodeBase):
+ """Represents a definition of anything that has a global name (e.g., enums,
+ enum values, consts, structs, struct fields, interfaces). (This does not
+ include parameter definitions.) This class is meant to be subclassed."""
+
+ def __init__(self, name, **kwargs):
+ assert isinstance(name, str)
+ NodeBase.__init__(self, **kwargs)
+ self.name = name
+
+
+################################################################################
+
+
+class Attribute(NodeBase):
+ """Represents an attribute."""
+
+ def __init__(self, key, value, **kwargs):
+ assert isinstance(key, str)
+ super(Attribute, self).__init__(**kwargs)
+ self.key = key
+ self.value = value
+
+ def __eq__(self, other):
+ return super(Attribute, self).__eq__(other) and \
+ self.key == other.key and \
+ self.value == other.value
+
+
+class AttributeList(NodeListBase):
+ """Represents a list attributes."""
+
+ _list_item_type = Attribute
+
+
+class Const(Definition):
+ """Represents a const definition."""
+
+ def __init__(self, name, typename, value, **kwargs):
+ # The typename is currently passed through as a string.
+ assert isinstance(typename, str)
+ # The value is either a literal (currently passed through as a string) or a
+ # "wrapped identifier".
+ assert isinstance(value, str) or isinstance(value, tuple)
+ super(Const, self).__init__(name, **kwargs)
+ self.typename = typename
+ self.value = value
+
+ def __eq__(self, other):
+ return super(Const, self).__eq__(other) and \
+ self.typename == other.typename and \
+ self.value == other.value
+
+
+class Enum(Definition):
+ """Represents an enum definition."""
+
+ def __init__(self, name, enum_value_list, **kwargs):
+ assert isinstance(enum_value_list, EnumValueList)
+ super(Enum, self).__init__(name, **kwargs)
+ self.enum_value_list = enum_value_list
+
+ def __eq__(self, other):
+ return super(Enum, self).__eq__(other) and \
+ self.enum_value_list == other.enum_value_list
+
+
+class EnumValue(Definition):
+ """Represents a definition of an enum value."""
+
+ def __init__(self, name, value, **kwargs):
+ # The optional value is either an int (which is current a string) or a
+ # "wrapped identifier".
+ assert value is None or isinstance(value, (str, tuple))
+ super(EnumValue, self).__init__(name, **kwargs)
+ self.value = value
+
+ def __eq__(self, other):
+ return super(EnumValue, self).__eq__(other) and \
+ self.value == other.value
+
+
+class EnumValueList(NodeListBase):
+ """Represents a list of enum value definitions (i.e., the "body" of an enum
+ definition)."""
+
+ _list_item_type = EnumValue
+
+
+class Import(NodeBase):
+ """Represents an import statement."""
+
+ def __init__(self, import_filename, **kwargs):
+ assert isinstance(import_filename, str)
+ super(Import, self).__init__(**kwargs)
+ self.import_filename = import_filename
+
+ def __eq__(self, other):
+ return super(Import, self).__eq__(other) and \
+ self.import_filename == other.import_filename
+
+
+class ImportList(NodeListBase):
+ """Represents a list (i.e., sequence) of import statements."""
+
+ _list_item_type = Import
+
+
+class Interface(Definition):
+ """Represents an interface definition."""
+
+ def __init__(self, name, attribute_list, body, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert isinstance(body, InterfaceBody)
+ super(Interface, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.body = body
+
+ def __eq__(self, other):
+ return super(Interface, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.body == other.body
+
+
+class Method(Definition):
+ """Represents a method definition."""
+
+ def __init__(self, name, ordinal, parameter_list, response_parameter_list,
+ **kwargs):
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(parameter_list, ParameterList)
+ assert response_parameter_list is None or \
+ isinstance(response_parameter_list, ParameterList)
+ super(Method, self).__init__(name, **kwargs)
+ self.ordinal = ordinal
+ self.parameter_list = parameter_list
+ self.response_parameter_list = response_parameter_list
+
+ def __eq__(self, other):
+ return super(Method, self).__eq__(other) and \
+ self.ordinal == other.ordinal and \
+ self.parameter_list == other.parameter_list and \
+ self.response_parameter_list == other.response_parameter_list
+
+
+# This needs to be declared after |Method|.
+class InterfaceBody(NodeListBase):
+ """Represents the body of (i.e., list of definitions inside) an interface."""
+
+ _list_item_type = (Const, Enum, Method)
+
+
+class Module(NodeBase):
+ """Represents a module statement."""
+
+ def __init__(self, name, attribute_list, **kwargs):
+ # |name| is either none or a "wrapped identifier".
+ assert name is None or isinstance(name, tuple)
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ super(Module, self).__init__(**kwargs)
+ self.name = name
+ self.attribute_list = attribute_list
+
+ def __eq__(self, other):
+ return super(Module, self).__eq__(other) and \
+ self.name == other.name and \
+ self.attribute_list == other.attribute_list
+
+
+class Mojom(NodeBase):
+ """Represents an entire .mojom file. (This is the root node."""
+
+ def __init__(self, module, import_list, definition_list, **kwargs):
+ assert module is None or isinstance(module, Module)
+ assert isinstance(import_list, ImportList)
+ assert isinstance(definition_list, list)
+ super(Mojom, self).__init__(**kwargs)
+ self.module = module
+ self.import_list = import_list
+ self.definition_list = definition_list
+
+ def __eq__(self, other):
+ return super(Mojom, self).__eq__(other) and \
+ self.module == other.module and \
+ self.import_list == other.import_list and \
+ self.definition_list == other.definition_list
+
+ def __repr__(self):
+ return "%s(%r, %r, %r)" % (self.__class__.__name__, self.module,
+ self.import_list, self.definition_list)
+
+
+class Ordinal(NodeBase):
+ """Represents an ordinal value labeling, e.g., a struct field."""
+
+ def __init__(self, value, **kwargs):
+ assert isinstance(value, int)
+ super(Ordinal, self).__init__(**kwargs)
+ self.value = value
+
+ def __eq__(self, other):
+ return super(Ordinal, self).__eq__(other) and \
+ self.value == other.value
+
+
+class Parameter(NodeBase):
+ """Represents a method request or response parameter."""
+
+ def __init__(self, name, ordinal, typename, **kwargs):
+ assert isinstance(name, str)
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(typename, str)
+ super(Parameter, self).__init__(**kwargs)
+ self.name = name
+ self.ordinal = ordinal
+ self.typename = typename
+
+ def __eq__(self, other):
+ return super(Parameter, self).__eq__(other) and \
+ self.name == other.name and \
+ self.ordinal == other.ordinal and \
+ self.typename == other.typename
+
+
+class ParameterList(NodeListBase):
+ """Represents a list of (method request or response) parameters."""
+
+ _list_item_type = Parameter
+
+
+class Struct(Definition):
+ """Represents a struct definition."""
+
+ def __init__(self, name, attribute_list, body, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
+ assert isinstance(body, StructBody)
+ super(Struct, self).__init__(name, **kwargs)
+ self.attribute_list = attribute_list
+ self.body = body
+
+ def __eq__(self, other):
+ return super(Struct, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
+ self.body == other.body
+
+
+class StructField(Definition):
+ """Represents a struct field definition."""
+
+ def __init__(self, name, ordinal, typename, default_value, **kwargs):
+ assert isinstance(name, str)
+ assert ordinal is None or isinstance(ordinal, Ordinal)
+ assert isinstance(typename, str)
+ # The optional default value is currently either a value as a string or a
+ # "wrapped identifier".
+ assert default_value is None or isinstance(default_value, (str, tuple))
+ super(StructField, self).__init__(name, **kwargs)
+ self.ordinal = ordinal
+ self.typename = typename
+ self.default_value = default_value
+
+ def __eq__(self, other):
+ return super(StructField, self).__eq__(other) and \
+ self.ordinal == other.ordinal and \
+ self.typename == other.typename and \
+ self.default_value == other.default_value
+
+
+# This needs to be declared after |StructField|.
+class StructBody(NodeListBase):
+ """Represents the body of (i.e., list of definitions inside) a struct."""
+
+ _list_item_type = (Const, Enum, StructField)
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
new file mode 100644
index 0000000..b13fac3
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
@@ -0,0 +1,251 @@
+# Copyright 2014 The Chromium 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
+
+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
+
+
+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',
+ 'ARRAY'
+ )
+
+ 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',
+
+ # String literals
+ 'STRING_LITERAL',
+
+ # Operators
+ 'MINUS',
+ 'PLUS',
+ 'AMP',
+ 'QSTN',
+
+ # 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+'))'
+
+ # 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_QSTN = 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
+
+ # 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/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
new file mode 100644
index 0000000..551c049
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
@@ -0,0 +1,383 @@
+# Copyright 2014 The Chromium 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
+
+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
+from . import ast
+from .lexer import Lexer
+
+
+_MAX_ORDINAL_VALUE = 0xffffffff
+_MAX_ARRAY_SIZE = 0xffffffff
+
+
+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
+
+ # Names of functions
+ #
+ # In general, we name functions after the left-hand-side of the rule(s) that
+ # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|.
+ #
+ # There may be multiple functions handling rules for the same left-hand-side;
+ # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|),
+ # where N is a number (numbered starting from 1). Note that using multiple
+ # functions is actually more efficient than having single functions handle
+ # multiple rules (and, e.g., distinguishing them by examining |len(p)|).
+ #
+ # It's also possible to have a function handling multiple rules with different
+ # left-hand-sides. We do not do this.
+ #
+ # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details.
+
+ # TODO(vtl): Get rid of the braces in the module "statement". (Consider
+ # renaming "module" -> "package".) Then we'll be able to have a single rule
+ # for root (by making module "optional").
+ def p_root_1(self, p):
+ """root : import_list module LBRACE definition_list RBRACE"""
+ p[0] = ast.Mojom(p[2], p[1], p[4])
+
+ def p_root_2(self, p):
+ """root : import_list definition_list"""
+ p[0] = ast.Mojom(None, p[1], p[2])
+
+ def p_import_list_1(self, p):
+ """import_list : """
+ p[0] = ast.ImportList()
+
+ def p_import_list_2(self, p):
+ """import_list : import_list import"""
+ p[0] = p[1]
+ p[0].Append(p[2])
+
+ def p_import(self, p):
+ """import : IMPORT STRING_LITERAL"""
+ # 'eval' the literal to strip the quotes.
+ # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.
+ p[0] = ast.Import(eval(p[2]))
+
+ def p_module(self, p):
+ """module : attribute_section MODULE identifier_wrapped """
+ p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2))
+
+ def p_definition_list(self, p):
+ """definition_list : definition definition_list
+ | """
+ if len(p) > 1:
+ p[0] = p[2]
+ p[0].insert(0, p[1])
+ else:
+ p[0] = []
+
+ def p_definition(self, p):
+ """definition : struct
+ | interface
+ | enum
+ | const"""
+ p[0] = p[1]
+
+ def p_attribute_section_1(self, p):
+ """attribute_section : """
+ p[0] = None
+
+ def p_attribute_section_2(self, p):
+ """attribute_section : LBRACKET attribute_list RBRACKET"""
+ p[0] = p[2]
+
+ def p_attribute_list_1(self, p):
+ """attribute_list : """
+ p[0] = ast.AttributeList()
+
+ def p_attribute_list_2(self, p):
+ """attribute_list : nonempty_attribute_list"""
+ p[0] = p[1]
+
+ def p_nonempty_attribute_list_1(self, p):
+ """nonempty_attribute_list : attribute"""
+ p[0] = ast.AttributeList(p[1])
+
+ def p_nonempty_attribute_list_2(self, p):
+ """nonempty_attribute_list : nonempty_attribute_list COMMA attribute"""
+ p[0] = p[1]
+ p[0].Append(p[3])
+
+ def p_attribute(self, p):
+ """attribute : NAME EQUALS evaled_literal
+ | NAME EQUALS NAME"""
+ p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
+
+ 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] = ast.Struct(p[3], p[1], p[5])
+
+ def p_struct_body_1(self, p):
+ """struct_body : """
+ p[0] = ast.StructBody()
+
+ def p_struct_body_2(self, p):
+ """struct_body : struct_body const
+ | struct_body enum
+ | struct_body struct_field"""
+ p[0] = p[1]
+ p[0].Append(p[2])
+
+ def p_struct_field(self, p):
+ """struct_field : typename NAME ordinal default SEMI"""
+ p[0] = ast.StructField(p[2], p[3], p[1], p[4])
+
+ def p_default_1(self, p):
+ """default : """
+ p[0] = None
+
+ def p_default_2(self, p):
+ """default : EQUALS constant"""
+ p[0] = p[2]
+
+ def p_interface(self, p):
+ """interface : attribute_section INTERFACE NAME LBRACE interface_body \
+ RBRACE SEMI"""
+ p[0] = ast.Interface(p[3], p[1], p[5])
+
+ def p_interface_body_1(self, p):
+ """interface_body : """
+ p[0] = ast.InterfaceBody()
+
+ def p_interface_body_2(self, p):
+ """interface_body : interface_body const
+ | interface_body enum
+ | interface_body method"""
+ p[0] = p[1]
+ p[0].Append(p[2])
+
+ def p_response_1(self, p):
+ """response : """
+ p[0] = None
+
+ def p_response_2(self, p):
+ """response : RESPONSE LPAREN parameter_list RPAREN"""
+ p[0] = p[3]
+
+ def p_method(self, p):
+ """method : NAME ordinal LPAREN parameter_list RPAREN response SEMI"""
+ p[0] = ast.Method(p[1], p[2], p[4], p[6])
+
+ def p_parameter_list_1(self, p):
+ """parameter_list : """
+ p[0] = ast.ParameterList()
+
+ def p_parameter_list_2(self, p):
+ """parameter_list : nonempty_parameter_list"""
+ p[0] = p[1]
+
+ def p_nonempty_parameter_list_1(self, p):
+ """nonempty_parameter_list : parameter"""
+ p[0] = ast.ParameterList(p[1])
+
+ def p_nonempty_parameter_list_2(self, p):
+ """nonempty_parameter_list : nonempty_parameter_list COMMA parameter"""
+ p[0] = p[1]
+ p[0].Append(p[3])
+
+ def p_parameter(self, p):
+ """parameter : typename NAME ordinal"""
+ p[0] = ast.Parameter(p[2], p[3], p[1],
+ filename=self.filename, lineno=p.lineno(2))
+
+ def p_typename(self, p):
+ """typename : nonnullable_typename QSTN
+ | nonnullable_typename"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = p[1] + "?"
+
+ def p_nonnullable_typename(self, p):
+ """nonnullable_typename : basictypename
+ | array
+ | fixed_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 : ARRAY LANGLE typename RANGLE"""
+ p[0] = p[3] + "[]"
+
+ def p_fixed_array(self, p):
+ """fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE"""
+ value = int(p[5])
+ if value == 0 or value > _MAX_ARRAY_SIZE:
+ raise ParseError(self.filename, "Fixed array size %d invalid" % value,
+ lineno=p.lineno(5),
+ snippet=self._GetSnippet(p.lineno(5)))
+ p[0] = p[3] + "[" + p[5] + "]"
+
+ def p_interfacerequest(self, p):
+ """interfacerequest : identifier AMP"""
+ p[0] = p[1] + "&"
+
+ def p_ordinal_1(self, p):
+ """ordinal : """
+ p[0] = None
+
+ def p_ordinal_2(self, p):
+ """ordinal : ORDINAL"""
+ 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))
+
+ def p_enum(self, p):
+ """enum : ENUM NAME LBRACE nonempty_enum_value_list RBRACE SEMI
+ | ENUM NAME LBRACE nonempty_enum_value_list COMMA RBRACE SEMI"""
+ p[0] = ast.Enum(p[2], p[4], filename=self.filename, lineno=p.lineno(1))
+
+ def p_nonempty_enum_value_list_1(self, p):
+ """nonempty_enum_value_list : enum_value"""
+ p[0] = ast.EnumValueList(p[1])
+
+ def p_nonempty_enum_value_list_2(self, p):
+ """nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value"""
+ p[0] = p[1]
+ p[0].Append(p[3])
+
+ def p_enum_value(self, p):
+ """enum_value : NAME
+ | NAME EQUALS int
+ | NAME EQUALS identifier_wrapped"""
+ p[0] = ast.EnumValue(p[1], p[3] if len(p) == 4 else None,
+ filename=self.filename, lineno=p.lineno(1))
+
+ def p_const(self, p):
+ """const : CONST typename NAME EQUALS constant SEMI"""
+ p[0] = ast.Const(p[3], p[2], 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])
+
+ # TODO(vtl): Make this produce a "wrapped" identifier (probably as an
+ # |ast.Identifier|, to be added) and get rid of identifier_wrapped.
+ def p_identifier(self, p):
+ """identifier : NAME
+ | NAME DOT identifier"""
+ p[0] = ''.join(p[1:])
+
+ def p_literal(self, p):
+ """literal : int
+ | float
+ | TRUE
+ | FALSE
+ | DEFAULT
+ | STRING_LITERAL"""
+ p[0] = p[1]
+
+ def p_int(self, p):
+ """int : int_const
+ | PLUS int_const
+ | MINUS int_const"""
+ p[0] = ''.join(p[1:])
+
+ def p_int_const(self, p):
+ """int_const : INT_CONST_DEC
+ | INT_CONST_HEX"""
+ p[0] = p[1]
+
+ def p_float(self, p):
+ """float : FLOAT_CONST
+ | PLUS FLOAT_CONST
+ | MINUS FLOAT_CONST"""
+ p[0] = ''.join(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/mojo/public/tools/bindings/pylib/mojom/parse/translate.py b/mojo/public/tools/bindings/pylib/mojom/parse/translate.py
new file mode 100644
index 0000000..77e92c5
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/translate.py
@@ -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.
+
+"""Translates parse tree to Mojom IR."""
+
+
+import re
+
+from . import ast
+
+
+def _MapTreeForType(func, tree, type_to_map):
+ assert isinstance(type_to_map, type)
+ if not tree:
+ return []
+ return [func(subtree) for subtree in tree if isinstance(subtree, type_to_map)]
+
+_FIXED_ARRAY_REGEXP = re.compile(r'\[[0-9]+\]')
+
+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('?'):
+ base_kind = _MapKind(kind[0:-1])
+ # NOTE: This doesn't rule out enum types. Those will be detected later, when
+ # cross-reference is established.
+ reference_kinds = ('s', 'h', 'a', 'r', 'x')
+ if base_kind[0] not in reference_kinds:
+ raise Exception(
+ 'A type (spec "%s") cannot be made nullable' % base_kind)
+ return '?' + base_kind
+ if kind.endswith('[]'):
+ typename = kind[0:-2]
+ if _FIXED_ARRAY_REGEXP.search(typename):
+ raise Exception('Arrays of fixed sized arrays not supported')
+ return 'a:' + _MapKind(typename)
+ if kind.endswith(']'):
+ lbracket = kind.rfind('[')
+ typename = kind[0:lbracket]
+ if typename.find('[') != -1:
+ raise Exception('Fixed sized arrays of arrays not supported')
+ return 'a' + kind[lbracket+1:-1] + ':' + _MapKind(typename)
+ if kind.endswith('&'):
+ return 'r:' + _MapKind(kind[0:-1])
+ if kind in map_to_kind:
+ return map_to_kind[kind]
+ return 'x:' + kind
+
+def _AttributeListToDict(attribute_list):
+ if attribute_list is None:
+ return {}
+ assert isinstance(attribute_list, ast.AttributeList)
+ # TODO(vtl): Check for duplicate keys here.
+ return dict([(attribute.key, attribute.value)
+ for attribute in attribute_list])
+
+def _EnumToDict(enum):
+ def EnumValueToDict(enum_value):
+ assert isinstance(enum_value, ast.EnumValue)
+ return {'name': enum_value.name,
+ 'value': enum_value.value}
+
+ assert isinstance(enum, ast.Enum)
+ return {'name': enum.name,
+ 'fields': map(EnumValueToDict, enum.enum_value_list)}
+
+def _ConstToDict(const):
+ assert isinstance(const, ast.Const)
+ return {'name': const.name,
+ 'kind': _MapKind(const.typename),
+ 'value': const.value}
+
+
+class _MojomBuilder(object):
+ def __init__(self):
+ self.mojom = {}
+
+ def Build(self, tree, name):
+ def StructToDict(struct):
+ def StructFieldToDict(struct_field):
+ assert isinstance(struct_field, ast.StructField)
+ return {'name': struct_field.name,
+ 'kind': _MapKind(struct_field.typename),
+ 'ordinal': struct_field.ordinal.value \
+ if struct_field.ordinal else None,
+ 'default': struct_field.default_value}
+
+ assert isinstance(struct, ast.Struct)
+ return {'name': struct.name,
+ 'attributes': _AttributeListToDict(struct.attribute_list),
+ 'fields': _MapTreeForType(StructFieldToDict, struct.body,
+ ast.StructField),
+ 'enums': _MapTreeForType(_EnumToDict, struct.body, ast.Enum),
+ 'constants': _MapTreeForType(_ConstToDict, struct.body,
+ ast.Const)}
+
+ def InterfaceToDict(interface):
+ def MethodToDict(method):
+ def ParameterToDict(param):
+ assert isinstance(param, ast.Parameter)
+ return {'name': param.name,
+ 'kind': _MapKind(param.typename),
+ 'ordinal': param.ordinal.value if param.ordinal else None}
+
+ assert isinstance(method, ast.Method)
+ rv = {'name': method.name,
+ 'parameters': map(ParameterToDict, method.parameter_list),
+ 'ordinal': method.ordinal.value if method.ordinal else None}
+ if method.response_parameter_list is not None:
+ rv['response_parameters'] = map(ParameterToDict,
+ method.response_parameter_list)
+ return rv
+
+ assert isinstance(interface, ast.Interface)
+ attributes = _AttributeListToDict(interface.attribute_list)
+ return {'name': interface.name,
+ 'attributes': attributes,
+ 'client': attributes.get('Client'),
+ 'methods': _MapTreeForType(MethodToDict, interface.body,
+ ast.Method),
+ 'enums': _MapTreeForType(_EnumToDict, interface.body, ast.Enum),
+ 'constants': _MapTreeForType(_ConstToDict, interface.body,
+ ast.Const)}
+
+ assert isinstance(tree, ast.Mojom)
+ self.mojom['name'] = name
+ self.mojom['namespace'] = tree.module.name[1] if tree.module else ''
+ self.mojom['imports'] = \
+ [{'filename': imp.import_filename} for imp in tree.import_list]
+ self.mojom['attributes'] = \
+ _AttributeListToDict(tree.module.attribute_list) if tree.module else {}
+ self.mojom['structs'] = \
+ _MapTreeForType(StructToDict, tree.definition_list, ast.Struct)
+ self.mojom['interfaces'] = \
+ _MapTreeForType(InterfaceToDict, tree.definition_list, ast.Interface)
+ self.mojom['enums'] = \
+ _MapTreeForType(_EnumToDict, tree.definition_list, ast.Enum)
+ self.mojom['constants'] = \
+ _MapTreeForType(_ConstToDict, tree.definition_list, ast.Const)
+ return self.mojom
+
+
+def Translate(tree, name):
+ return _MojomBuilder().Build(tree, name)
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py
new file mode 100644
index 0000000..dd28cdd
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py
@@ -0,0 +1,135 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import imp
+import os.path
+import sys
+import unittest
+
+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
+
+
+class _TestNode(ast.NodeBase):
+ """Node type for tests."""
+
+ def __init__(self, value, **kwargs):
+ super(_TestNode, self).__init__(**kwargs)
+ self.value = value
+
+ def __eq__(self, other):
+ return super(_TestNode, self).__eq__(other) and self.value == other.value
+
+
+class _TestNodeList(ast.NodeListBase):
+ """Node list type for tests."""
+
+ _list_item_type = _TestNode
+
+
+class ASTTest(unittest.TestCase):
+ """Tests various AST classes."""
+
+ def testNodeBase(self):
+ # Test |__eq__()|; this is only used for testing, where we want to do
+ # comparison by value and ignore filenames/line numbers (for convenience).
+ node1 = ast.NodeBase(filename="hello.mojom", lineno=123)
+ node2 = ast.NodeBase()
+ self.assertEquals(node1, node2)
+ self.assertEquals(node2, node1)
+
+ # Check that |__ne__()| just defers to |__eq__()| properly.
+ self.assertFalse(node1 != node2)
+ self.assertFalse(node2 != node1)
+
+ # Check that |filename| and |lineno| are set properly (and are None by
+ # default).
+ self.assertEquals(node1.filename, "hello.mojom")
+ self.assertEquals(node1.lineno, 123)
+ self.assertIsNone(node2.filename)
+ self.assertIsNone(node2.lineno)
+
+ # |NodeBase|'s |__eq__()| should compare types (and a subclass's |__eq__()|
+ # should first defer to its superclass's).
+ node3 = _TestNode(123)
+ self.assertNotEqual(node1, node3)
+ self.assertNotEqual(node3, node1)
+ # Also test |__eq__()| directly.
+ self.assertFalse(node1 == node3)
+ self.assertFalse(node3 == node1)
+
+ node4 = _TestNode(123, filename="world.mojom", lineno=123)
+ self.assertEquals(node4, node3)
+ node5 = _TestNode(456)
+ self.assertNotEquals(node5, node4)
+
+ def testNodeListBase(self):
+ node1 = _TestNode(1, filename="foo.mojom", lineno=1)
+ # Equal to, but not the same as, |node1|:
+ node1b = _TestNode(1, filename="foo.mojom", lineno=1)
+ node2 = _TestNode(2, filename="foo.mojom", lineno=2)
+
+ nodelist1 = _TestNodeList() # Contains: (empty).
+ self.assertEquals(nodelist1, nodelist1)
+ self.assertEquals(nodelist1.items, [])
+ self.assertIsNone(nodelist1.filename)
+ self.assertIsNone(nodelist1.lineno)
+
+ nodelist2 = _TestNodeList(node1) # Contains: 1.
+ self.assertEquals(nodelist2, nodelist2)
+ self.assertEquals(nodelist2.items, [node1])
+ self.assertNotEqual(nodelist2, nodelist1)
+ self.assertEquals(nodelist2.filename, "foo.mojom")
+ self.assertEquals(nodelist2.lineno, 1)
+
+ nodelist3 = _TestNodeList([node2]) # Contains: 2.
+ self.assertEquals(nodelist3.items, [node2])
+ self.assertNotEqual(nodelist3, nodelist1)
+ self.assertNotEqual(nodelist3, nodelist2)
+ self.assertEquals(nodelist3.filename, "foo.mojom")
+ self.assertEquals(nodelist3.lineno, 2)
+
+ nodelist1.Append(node1b) # Contains: 1.
+ self.assertEquals(nodelist1.items, [node1])
+ self.assertEquals(nodelist1, nodelist2)
+ self.assertNotEqual(nodelist1, nodelist3)
+ self.assertEquals(nodelist1.filename, "foo.mojom")
+ self.assertEquals(nodelist1.lineno, 1)
+
+ nodelist1.Append(node2) # Contains: 1, 2.
+ self.assertEquals(nodelist1.items, [node1, node2])
+ self.assertNotEqual(nodelist1, nodelist2)
+ self.assertNotEqual(nodelist1, nodelist3)
+ self.assertEquals(nodelist1.lineno, 1)
+
+ nodelist2.Append(node2) # Contains: 1, 2.
+ self.assertEquals(nodelist2.items, [node1, node2])
+ self.assertEquals(nodelist2, nodelist1)
+ self.assertNotEqual(nodelist2, nodelist3)
+ self.assertEquals(nodelist2.lineno, 1)
+
+ nodelist3.Insert(node1) # Contains: 1, 2.
+ self.assertEquals(nodelist3.items, [node1, node2])
+ self.assertEquals(nodelist3, nodelist1)
+ self.assertEquals(nodelist3, nodelist2)
+ self.assertEquals(nodelist3.lineno, 1)
+
+ # Test iteration:
+ i = 1
+ for item in nodelist1:
+ self.assertEquals(item.value, i)
+ i += 1
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
new file mode 100644
index 0000000..d24b7e2
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
@@ -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.
+
+import imp
+import os.path
+import sys
+import unittest
+
+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"))
+ self.assertEquals(self._SingleTokenForInput("array"),
+ _MakeLexTokenForKeyword("array"))
+
+ 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("\"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("QSTN", "?"))
+ 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/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
new file mode 100644
index 0000000..8671b1a
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
@@ -0,0 +1,1058 @@
+# Copyright 2014 The Chromium 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
+
+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 {
+ }
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ 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"
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ 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':\n *asdf1$"):
+ 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':\n *asdf2$"):
+ 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':\n *asdf3$"):
+ 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':\n *asdf4$"):
+ 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 = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, 'int32', None),
+ ast.StructField('b', None, 'double', 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 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, 'int32', None),
+ ast.StructField('b', None, 'double', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidStructDefinitions(self):
+ """Tests all types of definitions that can occur in a struct."""
+
+ source = """\
+ struct MyStruct {
+ enum MyEnum { VALUE };
+ const double kMyConst = 1.23;
+ int32 a;
+ SomeOtherStruct b; // Invalidity detected at another stage.
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.Enum('MyEnum',
+ ast.EnumValueList(ast.EnumValue('VALUE', None))),
+ ast.Const('kMyConst', 'double', '1.23'),
+ ast.StructField('a', None, 'int32', None),
+ ast.StructField('b', None, 'SomeOtherStruct', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidStructDefinitions(self):
+ """Tests that definitions that aren't allowed in a struct are correctly
+ detected."""
+
+ source1 = """\
+ struct MyStruct {
+ MyMethod(int32 a);
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\(':\n"
+ r" *MyMethod\(int32 a\);$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ struct MyInnerStruct {
+ int32 a;
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
+ r" *struct MyInnerStruct {$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ interface MyInterface {
+ MyMethod(int32 a);
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'interface':\n"
+ r" *interface MyInterface {$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ 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 '{':\n *module {$"):
+ 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 testEnums(self):
+ """Tests that enum statements are correctly parsed."""
+
+ source = """\
+ module my_module {
+ enum MyEnum1 { VALUE1, VALUE2 }; // No trailing comma.
+ enum MyEnum2 {
+ VALUE1 = -1,
+ VALUE2 = 0,
+ VALUE3 = + 987, // Check that space is allowed.
+ VALUE4 = 0xAF12,
+ VALUE5 = -0x09bcd,
+ VALUE6 = VALUE5,
+ VALUE7, // Leave trailing comma.
+ };
+ } // my_module
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Enum(
+ 'MyEnum1',
+ ast.EnumValueList([ast.EnumValue('VALUE1', None),
+ ast.EnumValue('VALUE2', None)])),
+ ast.Enum(
+ 'MyEnum2',
+ ast.EnumValueList([ast.EnumValue('VALUE1', '-1'),
+ ast.EnumValue('VALUE2', '0'),
+ ast.EnumValue('VALUE3', '+987'),
+ ast.EnumValue('VALUE4', '0xAF12'),
+ ast.EnumValue('VALUE5', '-0x09bcd'),
+ ast.EnumValue('VALUE6', ('IDENTIFIER',
+ 'VALUE5')),
+ ast.EnumValue('VALUE7', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidEnumInitializers(self):
+ """Tests that invalid enum initializers are correctly detected."""
+
+ # No values.
+ source1 = """\
+ enum MyEnum {
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '}':\n"
+ r" *};$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # Floating point value.
+ source2 = "enum MyEnum { VALUE = 0.123 };"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '0\.123':\n"
+ r"enum MyEnum { VALUE = 0\.123 };$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ # Boolean value.
+ source2 = "enum MyEnum { VALUE = true };"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected 'true':\n"
+ r"enum MyEnum { VALUE = true };$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ def testConsts(self):
+ """Tests some constants and struct members initialized with them."""
+
+ source = """\
+ module my_module {
+
+ struct MyStruct {
+ const int8 kNumber = -1;
+ int8 number@0 = kNumber;
+ };
+
+ } // my_module
+ """
+ expected = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct', None,
+ ast.StructBody(
+ [ast.Const('kNumber', 'int8', '-1'),
+ ast.StructField('number', ast.Ordinal(0), 'int8',
+ ('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(
+ parser.ParseError,
+ r"^my_file\.mojom:4: Error: Unexpected '\?':\n"
+ r" *MY_ENUM_1 = 1 \? 2 : 3$"):
+ 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 = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a0', ast.Ordinal(0), 'int32', None),
+ ast.StructField('a1', ast.Ordinal(1), 'int32', None),
+ ast.StructField('a2', ast.Ordinal(2), 'int32', None),
+ ast.StructField('a9', ast.Ordinal(9), 'int32', None),
+ ast.StructField('a10', ast.Ordinal(10), 'int32', None),
+ ast.StructField('a11', ast.Ordinal(11), 'int32', None),
+ ast.StructField('a29', ast.Ordinal(29), 'int32', None),
+ ast.StructField('a1234567890', ast.Ordinal(1234567890),
+ 'int32', 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 = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my.mod'), None),
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(ast.StructField('a', None, 'int32', 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 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, 'handle', None),
+ ast.StructField('b', None, 'handle<data_pipe_consumer>', None),
+ ast.StructField('c', None, 'handle<data_pipe_producer>', None),
+ ast.StructField('d', None, 'handle<message_pipe>', None),
+ ast.StructField('e', None, 'handle<shared_buffer>', 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 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a0', None, 'int16', '0'),
+ ast.StructField('a1', None, 'uint16', '0x0'),
+ ast.StructField('a2', None, 'uint16', '0x00'),
+ ast.StructField('a3', None, 'uint16', '0x01'),
+ ast.StructField('a4', None, 'uint16', '0xcd'),
+ ast.StructField('a5' , None, 'int32', '12345'),
+ ast.StructField('a6', None, 'int64', '-12345'),
+ ast.StructField('a7', None, 'int64', '+12345'),
+ ast.StructField('a8', None, 'uint32', '0x12cd3'),
+ ast.StructField('a9', None, 'uint32', '-0x12cD3'),
+ ast.StructField('a10', None, 'uint32', '+0x12CD3'),
+ ast.StructField('a11', None, 'bool', 'true'),
+ ast.StructField('a12', None, 'bool', 'false'),
+ ast.StructField('a13', None, 'float', '1.2345'),
+ ast.StructField('a14', None, 'float', '-1.2345'),
+ ast.StructField('a15', None, 'float', '+1.2345'),
+ ast.StructField('a16', None, 'float', '123.'),
+ ast.StructField('a17', None, 'float', '.123'),
+ ast.StructField('a18', None, 'double', '1.23E10'),
+ ast.StructField('a19', None, 'double', '1.E-10'),
+ ast.StructField('a20', None, 'double', '.5E+10'),
+ ast.StructField('a21', None, 'double', '-1.23E10'),
+ ast.StructField('a22', None, 'double', '+.123E10')]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidFixedSizeArray(self):
+ """Tests parsing a fixed size array."""
+
+ source = """\
+ struct MyStruct {
+ array<int32> normal_array;
+ array<int32, 1> fixed_size_array_one_entry;
+ array<int32, 10> fixed_size_array_ten_entries;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('normal_array', None, 'int32[]', None),
+ ast.StructField('fixed_size_array_one_entry', None, 'int32[1]',
+ None),
+ ast.StructField('fixed_size_array_ten_entries', None,
+ 'int32[10]', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testValidNestedArray(self):
+ """Tests parsing a nested array."""
+
+ source = "struct MyStruct { array<array<int32>> nested_array; };"
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ ast.StructField('nested_array', None, 'int32[][]', None)))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidFixedArraySize(self):
+ """Tests that invalid fixed array bounds are correctly detected."""
+
+ source1 = """\
+ struct MyStruct {
+ array<int32, 0> zero_size_array;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Fixed array size 0 invalid\n"
+ r" *array<int32, 0> zero_size_array;$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ array<int32, 999999999999> too_big_array;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid\n"
+ r" *array<int32, 999999999999> too_big_array;$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ array<int32, abcdefg> not_a_number;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n"
+ r" *array<int32, abcdefg> not_a_number;"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidMethod(self):
+ """Tests parsing method declarations."""
+
+ source1 = "interface MyInterface { MyMethod(int32 a); };"
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ None,
+ ast.ParameterList(ast.Parameter('a', None, 'int32')),
+ None)))])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ source2 = """\
+ interface MyInterface {
+ MyMethod1@0(int32 a@0, int64 b@1);
+ MyMethod2@1() => ();
+ };
+ """
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ [ast.Method(
+ 'MyMethod1',
+ ast.Ordinal(0),
+ ast.ParameterList([ast.Parameter('a', ast.Ordinal(0),
+ 'int32'),
+ ast.Parameter('b', ast.Ordinal(1),
+ 'int64')]),
+ None),
+ ast.Method(
+ 'MyMethod2',
+ ast.Ordinal(1),
+ ast.ParameterList(),
+ ast.ParameterList())]))])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ source3 = """\
+ interface MyInterface {
+ MyMethod(string a) => (int32 a, bool b);
+ };
+ """
+ expected3 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ ast.Method(
+ 'MyMethod',
+ None,
+ ast.ParameterList(ast.Parameter('a', None, 'string')),
+ ast.ParameterList([ast.Parameter('a', None, 'int32'),
+ ast.Parameter('b', None, 'bool')]))))])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ def testInvalidMethods(self):
+ """Tests that invalid method declarations are correctly detected."""
+
+ # No trailing commas.
+ source1 = """\
+ interface MyInterface {
+ MyMethod(string a,);
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\)':\n"
+ r" *MyMethod\(string a,\);$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # No leading commas.
+ source2 = """\
+ interface MyInterface {
+ MyMethod(, string a);
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected ',':\n"
+ r" *MyMethod\(, string a\);$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ def testValidInterfaceDefinitions(self):
+ """Tests all types of definitions that can occur in an interface."""
+
+ source = """\
+ interface MyInterface {
+ enum MyEnum { VALUE };
+ const int32 kMyConst = 123;
+ MyMethod(int32 x) => (MyEnum y);
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Interface(
+ 'MyInterface',
+ None,
+ ast.InterfaceBody(
+ [ast.Enum('MyEnum',
+ ast.EnumValueList(ast.EnumValue('VALUE', None))),
+ ast.Const('kMyConst', 'int32', '123'),
+ ast.Method(
+ 'MyMethod',
+ None,
+ ast.ParameterList(ast.Parameter('x', None, 'int32')),
+ ast.ParameterList(ast.Parameter('y', None, 'MyEnum')))]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidInterfaceDefinitions(self):
+ """Tests that definitions that aren't allowed in an interface are correctly
+ detected."""
+
+ source1 = """\
+ interface MyInterface {
+ struct MyStruct {
+ int32 a;
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'struct':\n"
+ r" *struct MyStruct {$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ interface MyInterface {
+ interface MyInnerInterface {
+ MyMethod(int32 x);
+ };
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'interface':\n"
+ r" *interface MyInnerInterface {$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ interface MyInterface {
+ int32 my_field;
+ };
+ """
+ # The parser thinks that "int32" is a plausible name for a method, so it's
+ # "my_field" that gives it away.
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'my_field':\n"
+ r" *int32 my_field;$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidAttributes(self):
+ """Tests parsing attributes (and attribute lists)."""
+
+ # Note: We use structs because they have (optional) attribute lists.
+
+ # Empty attribute list.
+ source1 = "[] struct MyStruct {};"
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ # One-element attribute list, with name value.
+ source2 = "[MyAttribute=MyName] struct MyStruct {};"
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ ast.AttributeList(ast.Attribute("MyAttribute", "MyName")),
+ ast.StructBody())])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ # Two-element attribute list, with one string value and one integer value.
+ source3 = "[MyAttribute1 = \"hello\", MyAttribute2 = 5] struct MyStruct {};"
+ expected3 = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ ast.AttributeList([ast.Attribute("MyAttribute1", "hello"),
+ ast.Attribute("MyAttribute2", 5)]),
+ ast.StructBody())])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()|
+ # literal (non-name) values, which is extremely dubious.)
+
+ def testInvalidAttributes(self):
+ """Tests that invalid attributes and attribute lists are correctly
+ detected."""
+
+ # Trailing commas not allowed.
+ source1 = "[MyAttribute=MyName,] struct MyStruct {};"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '\]':\n"
+ r"\[MyAttribute=MyName,\] struct MyStruct {};$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ # Missing value.
+ source2 = "[MyAttribute=] struct MyStruct {};"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '\]':\n"
+ r"\[MyAttribute=\] struct MyStruct {};$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ # Missing key.
+ source3 = "[=MyName] struct MyStruct {};"
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:1: Error: Unexpected '=':\n"
+ r"\[=MyName\] struct MyStruct {};$"):
+ parser.Parse(source3, "my_file.mojom")
+
+ def testValidImports(self):
+ """Tests parsing import statements."""
+
+ # One import (no module statement).
+ source1 = "import \"somedir/my.mojom\""
+ expected1 = ast.Mojom(
+ None,
+ ast.ImportList(ast.Import("somedir/my.mojom")),
+ [])
+ self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
+
+ # Two imports (no module statement).
+ source2 = """\
+ import "somedir/my1.mojom"
+ import "somedir/my2.mojom"
+ """
+ expected2 = ast.Mojom(
+ None,
+ ast.ImportList([ast.Import("somedir/my1.mojom"),
+ ast.Import("somedir/my2.mojom")]),
+ [])
+ self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
+
+ # Imports with module statement.
+ source3 = """\
+ import "somedir/my1.mojom"
+ import "somedir/my2.mojom"
+ module my_module {}
+ """
+ expected3 = ast.Mojom(
+ ast.Module(('IDENTIFIER', 'my_module'), None),
+ ast.ImportList([ast.Import("somedir/my1.mojom"),
+ ast.Import("somedir/my2.mojom")]),
+ [])
+ self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
+
+ def testInvalidImports(self):
+ """Tests that invalid import statements are correctly detected."""
+
+ source1 = """\
+ // Make the error occur on line 2.
+ import invalid
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'invalid':\n"
+ r" *import invalid$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ import // Missing string.
+ module {}
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected 'module':\n"
+ r" *module {}$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ def testValidNullableTypes(self):
+ """Tests parsing nullable types."""
+
+ source = """\
+ struct MyStruct {
+ int32? a; // This is actually invalid, but handled at a different
+ // level.
+ string? b;
+ array<int32> ? c;
+ array<string ? > ? d;
+ array<array<int32>?>? e;
+ array<int32, 1>? f;
+ array<string?, 1>? g;
+ some_struct? h;
+ handle? i;
+ handle<data_pipe_consumer>? j;
+ handle<data_pipe_producer>? k;
+ handle<message_pipe>? l;
+ handle<shared_buffer>? m;
+ some_interface&? n;
+ };
+ """
+ expected = ast.Mojom(
+ None,
+ ast.ImportList(),
+ [ast.Struct(
+ 'MyStruct',
+ None,
+ ast.StructBody(
+ [ast.StructField('a', None, 'int32?', None),
+ ast.StructField('b', None, 'string?', None),
+ ast.StructField('c', None, 'int32[]?', None),
+ ast.StructField('d', None, 'string?[]?', None),
+ ast.StructField('e', None, 'int32[]?[]?', None),
+ ast.StructField('f', None, 'int32[1]?', None),
+ ast.StructField('g', None, 'string?[1]?', None),
+ ast.StructField('h', None, 'some_struct?', None),
+ ast.StructField('i', None, 'handle?', None),
+ ast.StructField('j', None, 'handle<data_pipe_consumer>?',
+ None),
+ ast.StructField('k', None, 'handle<data_pipe_producer>?',
+ None),
+ ast.StructField('l', None, 'handle<message_pipe>?', None),
+ ast.StructField('m', None, 'handle<shared_buffer>?', None),
+ ast.StructField('n', None, 'some_interface&?', None)]))])
+ self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
+
+ def testInvalidNullableTypes(self):
+ """Tests that invalid nullable types are correctly detected."""
+ source1 = """\
+ struct MyStruct {
+ string?? a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '\?':\n"
+ r" *string\?\? a;$"):
+ parser.Parse(source1, "my_file.mojom")
+
+ source2 = """\
+ struct MyStruct {
+ handle?<data_pipe_consumer> a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '<':\n"
+ r" *handle\?<data_pipe_consumer> a;$"):
+ parser.Parse(source2, "my_file.mojom")
+
+ source3 = """\
+ struct MyStruct {
+ some_interface?& a;
+ };
+ """
+ with self.assertRaisesRegexp(
+ parser.ParseError,
+ r"^my_file\.mojom:2: Error: Unexpected '&':\n"
+ r" *some_interface\?& a;$"):
+ parser.Parse(source3, "my_file.mojom")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py
new file mode 100755
index 0000000..b160de6
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py
@@ -0,0 +1,36 @@
+#!/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))
+
+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/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py
new file mode 100755
index 0000000..899d40e
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py
@@ -0,0 +1,34 @@
+#!/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))
+
+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/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py
new file mode 100644
index 0000000..2a4b17b
--- /dev/null
+++ b/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.
+
+import fnmatch
+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 fnmatch.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/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py
new file mode 100644
index 0000000..20ef461
--- /dev/null
+++ b/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/mojo/python/BUILD.gn b/mojo/python/BUILD.gn
new file mode 100644
index 0000000..7b07f4f
--- /dev/null
+++ b/mojo/python/BUILD.gn
@@ -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("//third_party/cython/rules.gni")
+
+# GYP version: mojo/mojo.gyp:mojo_python
+group("python") {
+ deps = [
+ ":embedder",
+ "//mojo/public/python",
+ ]
+}
+
+# GYP version: mojo/mojo.gyp:mojo_python_embedder
+python_binary_module("embedder") {
+ python_base_module = "mojo"
+ sources = [
+ "system/mojo/embedder.pyx",
+ ]
+ deps = [
+ "//mojo/edk/system",
+ ]
+ datadeps = [
+ "//mojo/public/python:system",
+ ]
+}
diff --git a/mojo/python/system/mojo/embedder.pyx b/mojo/python/system/mojo/embedder.pyx
new file mode 100644
index 0000000..c409999
--- /dev/null
+++ b/mojo/python/system/mojo/embedder.pyx
@@ -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.
+
+# distutils: language = c++
+
+from libc.stdint cimport uintptr_t
+
+from mojo import system
+
+cdef extern from "third_party/cython/python_export.h":
+ pass
+
+cdef extern from "base/memory/scoped_ptr.h":
+ cdef cppclass scoped_ptr[T]:
+ scoped_ptr(T*)
+
+cdef extern from "mojo/edk/embedder/platform_support.h" \
+ namespace "mojo::embedder" nogil:
+ cdef cppclass PlatformSupport:
+ pass
+
+cdef extern from "mojo/edk/embedder/simple_platform_support.h" \
+ namespace "mojo::embedder" nogil:
+ cdef cppclass SimplePlatformSupport(PlatformSupport):
+ SimplePlatformSupport()
+
+cdef extern from "mojo/edk/embedder/embedder.h" nogil:
+ cdef void InitCEmbedder "mojo::embedder::Init"(
+ scoped_ptr[PlatformSupport] platform_support)
+
+cdef extern from "mojo/public/platform/native/system_thunks.h" nogil:
+ cdef struct MojoSystemThunks:
+ pass
+ cdef MojoSystemThunks MojoMakeSystemThunks()
+
+def Init():
+ InitCEmbedder(scoped_ptr[PlatformSupport](
+ new SimplePlatformSupport()))
+ cdef MojoSystemThunks thunks = MojoMakeSystemThunks()
+ system.SetSystemThunks(<uintptr_t>(&thunks))
diff --git a/mojo/python/tests/async_wait_unittest.py b/mojo/python/tests/async_wait_unittest.py
new file mode 100644
index 0000000..849b3cb
--- /dev/null
+++ b/mojo/python/tests/async_wait_unittest.py
@@ -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.
+
+import unittest
+
+# pylint: disable=F0401
+import mojo.embedder
+from mojo import system
+
+
+class AsyncWaitTest(unittest.TestCase):
+
+ def setUp(self):
+ mojo.embedder.Init()
+ self.loop = system.RunLoop()
+ self.array = []
+ self.handles = system.MessagePipe()
+ self.cancel = self.handles.handle0.AsyncWait(system.HANDLE_SIGNAL_READABLE,
+ system.DEADLINE_INDEFINITE,
+ self._OnResult)
+
+ def tearDown(self):
+ self.cancel()
+ self.handles = None
+ self.array = None
+ self.loop = None
+
+ def _OnResult(self, value):
+ self.array.append(value)
+
+ def _WriteToHandle(self):
+ self.handles.handle1.WriteMessage()
+
+ def _PostWriteAndRun(self):
+ self.loop.PostDelayedTask(self._WriteToHandle, 0)
+ self.loop.RunUntilIdle()
+
+ def testAsyncWait(self):
+ self._PostWriteAndRun()
+ self.assertEquals(len(self.array), 1)
+ self.assertEquals(system.RESULT_OK, self.array[0])
+
+ def testAsyncWaitCancel(self):
+ self.loop.PostDelayedTask(self.cancel, 0)
+ self._PostWriteAndRun()
+ self.assertEquals(len(self.array), 0)
+
+ def testAsyncWaitImmediateCancel(self):
+ self.cancel()
+ self._PostWriteAndRun()
+ self.assertEquals(len(self.array), 0)
diff --git a/mojo/python/tests/bindings_constants_unittest.py b/mojo/python/tests/bindings_constants_unittest.py
new file mode 100644
index 0000000..85e97c6
--- /dev/null
+++ b/mojo/python/tests/bindings_constants_unittest.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 math
+import unittest
+
+# Generated files
+# pylint: disable=F0401
+import sample_service_mojom
+
+
+class ConstantBindingsTest(unittest.TestCase):
+
+ def testConstantGeneration(self):
+ self.assertEquals(sample_service_mojom.TWELVE, 12)
+ self.assertEquals(sample_service_mojom.TOO_BIG_FOR_SIGNED_INT64,
+ 9999999999999999999)
+ self.assertEquals(sample_service_mojom.DOUBLE_INFINITY,
+ float('inf'))
+ self.assertEquals(sample_service_mojom.DOUBLE_NEGATIVE_INFINITY,
+ float('-inf'))
+ self.assertTrue(math.isnan(sample_service_mojom.DOUBLE_NA_N))
+ self.assertEquals(sample_service_mojom.FLOAT_INFINITY,
+ float('inf'))
+ self.assertEquals(sample_service_mojom.FLOAT_NEGATIVE_INFINITY,
+ float('-inf'))
+ self.assertTrue(math.isnan(sample_service_mojom.FLOAT_NA_N))
+
+ def testConstantOnStructGeneration(self):
+ self.assertEquals(sample_service_mojom.Foo.FOOBY, "Fooby")
+
+ def testStructImmutability(self):
+ with self.assertRaises(AttributeError):
+ sample_service_mojom.Foo.FOOBY = 0
+ with self.assertRaises(AttributeError):
+ del sample_service_mojom.Foo.FOOBY
+ with self.assertRaises(AttributeError):
+ sample_service_mojom.Foo.BAR = 1
diff --git a/mojo/python/tests/bindings_enums_unittest.py b/mojo/python/tests/bindings_enums_unittest.py
new file mode 100644
index 0000000..6ec5d8c
--- /dev/null
+++ b/mojo/python/tests/bindings_enums_unittest.py
@@ -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.
+
+import unittest
+
+# Generated files
+# pylint: disable=F0401
+import sample_import_mojom
+import sample_service_mojom
+
+
+class EnumBindingsTest(unittest.TestCase):
+
+ # Testing enum classes are in the right module.
+ def testModule(self):
+ self.assertEquals(sample_import_mojom.Shape.__module__,
+ 'sample_import_mojom')
+
+ # Testing that enum class have expected constant values.
+ def testTopLevelEnumGeneration(self):
+ self.assertEquals(sample_import_mojom.Shape.RECTANGLE, 1)
+ self.assertEquals(sample_import_mojom.Shape.CIRCLE, 2)
+ self.assertEquals(sample_import_mojom.Shape.TRIANGLE, 3)
+ self.assertEquals(sample_import_mojom.Shape.LAST,
+ sample_import_mojom.Shape.TRIANGLE)
+
+ self.assertEquals(sample_import_mojom.AnotherShape.RECTANGLE, 10)
+ self.assertEquals(sample_import_mojom.AnotherShape.CIRCLE, 11)
+ self.assertEquals(sample_import_mojom.AnotherShape.TRIANGLE, 12)
+
+ self.assertEquals(sample_import_mojom.YetAnotherShape.RECTANGLE, 20)
+ self.assertEquals(sample_import_mojom.YetAnotherShape.CIRCLE, 21)
+ self.assertEquals(sample_import_mojom.YetAnotherShape.TRIANGLE, 22)
+
+ # Testing that internal enum class have expected constant values.
+ def testInternalEnumGeneration(self):
+ self.assertEquals(sample_service_mojom.Bar.Type.VERTICAL, 1)
+ self.assertEquals(sample_service_mojom.Bar.Type.HORIZONTAL, 2)
+ self.assertEquals(sample_service_mojom.Bar.Type.BOTH, 3)
+ self.assertEquals(sample_service_mojom.Bar.Type.INVALID, 4)
+
+ # Testing an enum class cannot be instantiated.
+ def testNonInstantiableEnum(self):
+ with self.assertRaises(TypeError):
+ sample_import_mojom.Shape()
+
+ # Testing an enum does not contain the VALUES constant.
+ def testNoVALUESConstant(self):
+ with self.assertRaises(AttributeError):
+ # pylint: disable=W0104
+ sample_import_mojom.Shape.VALUES
+
+ # Testing enum values are frozen.
+ def testEnumFrozen(self):
+ with self.assertRaises(AttributeError):
+ sample_import_mojom.Shape.RECTANGLE = 2
+ with self.assertRaises(AttributeError):
+ del sample_import_mojom.Shape.RECTANGLE
+ with self.assertRaises(AttributeError):
+ sample_import_mojom.Shape.NewShape = 4
diff --git a/mojo/python/tests/bindings_serialization_deserialization_unittest.py b/mojo/python/tests/bindings_serialization_deserialization_unittest.py
new file mode 100644
index 0000000..8d86495
--- /dev/null
+++ b/mojo/python/tests/bindings_serialization_deserialization_unittest.py
@@ -0,0 +1,130 @@
+# Copyright 2014 The Chromium 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 math
+import unittest
+
+# pylint: disable=F0401
+import mojo.bindings.reflection as reflection
+import mojo.system
+
+# Generated files
+# pylint: disable=F0401
+import sample_import_mojom
+import sample_import2_mojom
+import sample_service_mojom
+
+
+def _NewHandle():
+ return mojo.system.MessagePipe().handle0
+
+
+def _TestEquality(x, y):
+ if x == y:
+ return True
+
+ if type(x) != type(y):
+ print '\n%r != %r. Element are not of the same type.' % (x, y)
+ return False
+
+ if isinstance(x, float) and math.isnan(x) and math.isnan(y):
+ return True
+
+ if hasattr(x, '__len__'):
+ if len(x) != len(y):
+ print '\n%r != %r. Iterables are not of the same size.' % (x, y)
+ return False
+ for (x1, y1) in zip(x, y):
+ if not _TestEquality(x1, y1):
+ return False
+ return True
+
+ if (hasattr(x, '__metaclass__') and
+ x.__metaclass__ == reflection.MojoStructType):
+ properties = [p for p in dir(x) if not p.startswith('_')]
+ for p in properties:
+ p1 = getattr(x, p)
+ p2 = getattr(y, p)
+ if not hasattr(p1, '__call__') and not _TestEquality(p1, p2):
+ print '\n%r != %r. Not equal for property %r.' % (x, y, p)
+ return False
+ return True
+
+ return False
+
+
+def _NewBar():
+ bar_instance = sample_service_mojom.Bar()
+ bar_instance.alpha = 22
+ bar_instance.beta = 87
+ bar_instance.gamma = 122
+ bar_instance.type = sample_service_mojom.Bar.Type.BOTH
+ return bar_instance
+
+
+def _NewFoo():
+ foo_instance = sample_service_mojom.Foo()
+ foo_instance.name = "Foo.name"
+ foo_instance.x = 23
+ foo_instance.y = -23
+ foo_instance.a = False
+ foo_instance.b = True
+ foo_instance.c = True
+ foo_instance.bar = _NewBar()
+ foo_instance.extra_bars = [
+ _NewBar(),
+ _NewBar(),
+ ]
+ foo_instance.data = 'Hello world'
+ foo_instance.source = _NewHandle()
+ foo_instance.input_streams = [ _NewHandle() ]
+ foo_instance.output_streams = [ _NewHandle(), _NewHandle() ]
+ foo_instance.array_of_array_of_bools = [ [ True, False ], [] ]
+ foo_instance.multi_array_of_strings = [
+ [
+ [ "1", "2" ],
+ [],
+ [ "3", "4" ],
+ ],
+ [],
+ ]
+ foo_instance.array_of_bools = [ True, 0, 1, 2, 0, 0, 0, 0, 0, True ]
+ return foo_instance
+
+
+class SerializationDeserializationTest(unittest.TestCase):
+
+ def testTestEquality(self):
+ self.assertFalse(_TestEquality(1, 2))
+
+ def testFooSerialization(self):
+ (data, _) = _NewFoo().Serialize()
+ self.assertTrue(len(data))
+ self.assertEquals(len(data) % 8, 0)
+
+ def testFooDeserialization(self):
+ (data, handles) = _NewFoo().Serialize()
+ self.assertTrue(
+ sample_service_mojom.Foo.Deserialize(data, handles))
+
+ def testFooSerializationDeserialization(self):
+ foo1 = _NewFoo()
+ (data, handles) = foo1.Serialize()
+ foo2 = sample_service_mojom.Foo.Deserialize(data, handles)
+ self.assertTrue(_TestEquality(foo1, foo2))
+
+ def testDefaultsTestSerializationDeserialization(self):
+ v1 = sample_service_mojom.DefaultsTest()
+ v1.a18 = []
+ v1.a19 = ""
+ v1.a21 = sample_import_mojom.Point()
+ v1.a22.location = sample_import_mojom.Point()
+ v1.a22.size = sample_import2_mojom.Size()
+ (data, handles) = v1.Serialize()
+ v2 = sample_service_mojom.DefaultsTest.Deserialize(data, handles)
+ self.assertTrue(_TestEquality(v1, v2))
+
+ def testFooDeserializationError(self):
+ with self.assertRaises(Exception):
+ sample_service_mojom.Foo.Deserialize("", [])
diff --git a/mojo/python/tests/bindings_structs_unittest.py b/mojo/python/tests/bindings_structs_unittest.py
new file mode 100644
index 0000000..97c1245
--- /dev/null
+++ b/mojo/python/tests/bindings_structs_unittest.py
@@ -0,0 +1,208 @@
+# Copyright 2014 The Chromium 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 math
+import unittest
+
+# pylint: disable=F0401
+import mojo.system
+
+# Generated files
+# pylint: disable=F0401
+import sample_import_mojom
+import sample_import2_mojom
+import sample_service_mojom
+
+
+class StructBindingsTest(unittest.TestCase):
+
+ def testModule(self):
+ self.assertEquals(sample_service_mojom.DefaultsTest.__module__,
+ 'sample_service_mojom')
+
+ def testDefaultsTest(self):
+ defaults_test = sample_service_mojom.DefaultsTest()
+ self.assertEquals(defaults_test.a0, -12)
+ self.assertEquals(defaults_test.a1, 12)
+ self.assertEquals(defaults_test.a2, 1234)
+ self.assertEquals(defaults_test.a3, 34567)
+ self.assertEquals(defaults_test.a4, 123456)
+ self.assertEquals(defaults_test.a5, 3456789012)
+ self.assertEquals(defaults_test.a6, -111111111111)
+ self.assertEquals(defaults_test.a7, 9999999999999999999)
+ self.assertEquals(defaults_test.a8, 0x12345)
+ self.assertEquals(defaults_test.a9, -0x12345)
+ self.assertEquals(defaults_test.a10, 1234)
+ self.assertEquals(defaults_test.a11, True)
+ self.assertEquals(defaults_test.a12, False)
+ self.assertEquals(defaults_test.a13, 123.25)
+ self.assertEquals(defaults_test.a14, 1234567890.123)
+ self.assertEquals(defaults_test.a15, 1E10)
+ self.assertEquals(defaults_test.a16, -1.2E+20)
+ self.assertEquals(defaults_test.a17, 1.23E-20)
+ self.assertEquals(defaults_test.a18, None)
+ self.assertEquals(defaults_test.a19, None)
+ self.assertEquals(defaults_test.a20, sample_service_mojom.Bar.Type.BOTH)
+ self.assertEquals(defaults_test.a21, None)
+ self.assertTrue(isinstance(defaults_test.a22, sample_import2_mojom.Thing))
+ self.assertEquals(defaults_test.a23, 0xFFFFFFFFFFFFFFFF)
+ self.assertEquals(defaults_test.a24, 0x123456789)
+ self.assertEquals(defaults_test.a25, -0x123456789)
+ self.assertEquals(defaults_test.a26, float('inf'))
+ self.assertEquals(defaults_test.a27, float('-inf'))
+ self.assertTrue(math.isnan(defaults_test.a28))
+ self.assertEquals(defaults_test.a29, float('inf'))
+ self.assertEquals(defaults_test.a30, float('-inf'))
+ self.assertTrue(math.isnan(defaults_test.a31))
+
+ def testNoAliasing(self):
+ foo1 = sample_service_mojom.Foo()
+ foo2 = sample_service_mojom.Foo()
+ foo1.name = "foo1"
+ foo2.name = "foo2"
+ self.assertEquals(foo1.name, "foo1")
+ self.assertEquals(foo2.name, "foo2")
+
+ defaults_test1 = sample_service_mojom.DefaultsTest()
+ defaults_test2 = sample_service_mojom.DefaultsTest()
+ self.assertNotEquals(defaults_test1.a22, defaults_test2.a22)
+
+ def testImmutableAttributeSet(self):
+ foo_instance = sample_service_mojom.Foo()
+ with self.assertRaises(AttributeError):
+ foo_instance.new_attribute = None
+ with self.assertRaises(AttributeError):
+ del foo_instance.name
+
+ def _TestIntegerField(self, entity, field_name, bits, signed):
+ if signed:
+ min_value = -(1 << (bits - 1))
+ max_value = (1 << (bits - 1)) - 1
+ else:
+ min_value = 0
+ max_value = (1 << bits) - 1
+ entity.__setattr__(field_name, min_value)
+ entity.__setattr__(field_name, max_value)
+ with self.assertRaises(TypeError):
+ entity.__setattr__(field_name, None)
+ with self.assertRaises(OverflowError):
+ entity.__setattr__(field_name, min_value - 1)
+ with self.assertRaises(OverflowError):
+ entity.__setattr__(field_name, max_value + 1)
+ with self.assertRaises(TypeError):
+ entity.__setattr__(field_name, 'hello world')
+
+ def testTypes(self):
+ defaults_test = sample_service_mojom.DefaultsTest()
+ # Integer types
+ self._TestIntegerField(defaults_test, 'a0', 8, True)
+ self._TestIntegerField(defaults_test, 'a1', 8, False)
+ self._TestIntegerField(defaults_test, 'a2', 16, True)
+ self._TestIntegerField(defaults_test, 'a3', 16, False)
+ self._TestIntegerField(defaults_test, 'a4', 32, True)
+ self._TestIntegerField(defaults_test, 'a5', 32, False)
+ self._TestIntegerField(defaults_test, 'a6', 64, True)
+ self._TestIntegerField(defaults_test, 'a7', 64, False)
+
+ # Boolean types
+ defaults_test.a11 = False
+ self.assertEquals(defaults_test.a11, False)
+ defaults_test.a11 = None
+ self.assertEquals(defaults_test.a11, False)
+ defaults_test.a11 = []
+ self.assertEquals(defaults_test.a11, False)
+ defaults_test.a12 = True
+ self.assertEquals(defaults_test.a12, True)
+ defaults_test.a12 = 1
+ self.assertEquals(defaults_test.a12, True)
+ defaults_test.a12 = [[]]
+ self.assertEquals(defaults_test.a12, True)
+
+ # Floating point types
+ with self.assertRaises(TypeError):
+ defaults_test.a13 = 'hello'
+ with self.assertRaises(TypeError):
+ defaults_test.a14 = 'hello'
+
+ # Array type
+ defaults_test.a18 = None
+ defaults_test.a18 = []
+ defaults_test.a18 = [ 0 ]
+ defaults_test.a18 = [ 255 ]
+ defaults_test.a18 = [ 0, 255 ]
+ with self.assertRaises(TypeError):
+ defaults_test.a18 = [[]]
+ with self.assertRaises(OverflowError):
+ defaults_test.a18 = [ -1 ]
+ with self.assertRaises(OverflowError):
+ defaults_test.a18 = [ 256 ]
+
+ # String type
+ defaults_test.a19 = None
+ defaults_test.a19 = ''
+ defaults_test.a19 = 'hello world'
+ with self.assertRaises(TypeError):
+ defaults_test.a19 = [[]]
+ with self.assertRaises(TypeError):
+ defaults_test.a19 = [ -1 ]
+ with self.assertRaises(TypeError):
+ defaults_test.a19 = [ 256 ]
+
+ # Structs
+ defaults_test.a21 = None
+ defaults_test.a21 = sample_import_mojom.Point()
+ with self.assertRaises(TypeError):
+ defaults_test.a21 = 1
+ with self.assertRaises(TypeError):
+ defaults_test.a21 = sample_import2_mojom.Thing()
+
+ # Handles
+ foo_instance = sample_service_mojom.Foo()
+ foo_instance.source = None
+ foo_instance.source = mojo.system.Handle()
+ with self.assertRaises(TypeError):
+ foo_instance.source = 1
+ with self.assertRaises(TypeError):
+ foo_instance.source = object()
+
+ def testConstructor(self):
+ bar_instance = sample_service_mojom.Bar()
+ foo_instance = sample_service_mojom.Foo(name="Foo",
+ x=-1,
+ y=5,
+ a=False,
+ bar=bar_instance)
+ self.assertEquals(foo_instance.name, "Foo")
+ self.assertEquals(foo_instance.x, -1)
+ self.assertEquals(foo_instance.y, 5)
+ self.assertEquals(foo_instance.a, False)
+ self.assertEquals(foo_instance.bar, bar_instance)
+
+ def testPositionalConstructor(self):
+ p = sample_import_mojom.Point()
+ self.assertEquals(p.x, 0)
+ self.assertEquals(p.y, 0)
+
+ p = sample_import_mojom.Point(34)
+ self.assertEquals(p.x, 34)
+ self.assertEquals(p.y, 0)
+
+ p = sample_import_mojom.Point(34, 12)
+ self.assertEquals(p.x, 34)
+ self.assertEquals(p.y, 12)
+
+ p = sample_import_mojom.Point(x=34, y=12)
+ self.assertEquals(p.x, 34)
+ self.assertEquals(p.y, 12)
+
+ p = sample_import_mojom.Point(34, y=12)
+ self.assertEquals(p.x, 34)
+ self.assertEquals(p.y, 12)
+
+ with self.assertRaises(TypeError):
+ p = sample_import_mojom.Point(0, 0, 0)
+ with self.assertRaises(TypeError):
+ p = sample_import_mojom.Point(0, x=0)
+ with self.assertRaises(TypeError):
+ p = sample_import_mojom.Point(c=0)
diff --git a/mojo/python/tests/messaging_unittest.py b/mojo/python/tests/messaging_unittest.py
new file mode 100644
index 0000000..2d08941
--- /dev/null
+++ b/mojo/python/tests/messaging_unittest.py
@@ -0,0 +1,207 @@
+# Copyright 2014 The Chromium 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
+
+# pylint: disable=F0401
+import mojo.embedder
+from mojo.bindings import messaging
+from mojo import system
+
+
+class _ForwardingConnectionErrorHandler(messaging.ConnectionErrorHandler):
+
+ def __init__(self, callback):
+ self._callback = callback
+
+ def OnError(self, result):
+ self._callback(result)
+
+
+class ConnectorTest(unittest.TestCase):
+
+ def setUp(self):
+ mojo.embedder.Init()
+ self.loop = system.RunLoop()
+ self.received_messages = []
+ self.received_errors = []
+ def _OnMessage(message):
+ self.received_messages.append(message)
+ return True
+ def _OnError(result):
+ self.received_errors.append(result)
+ handles = system.MessagePipe()
+ self.connector = messaging.Connector(handles.handle1)
+ self.connector.SetIncomingMessageReceiver(
+ messaging.ForwardingMessageReceiver(_OnMessage))
+ self.connector.SetErrorHandler(
+ _ForwardingConnectionErrorHandler(_OnError))
+ self.connector.Start()
+ self.handle = handles.handle0
+
+
+ def tearDown(self):
+ self.connector = None
+ self.handle = None
+ self.loop = None
+
+ def testConnectorRead(self):
+ self.handle.WriteMessage()
+ self.loop.RunUntilIdle()
+ self.assertTrue(self.received_messages)
+ self.assertFalse(self.received_errors)
+
+ def testConnectorWrite(self):
+ self.connector.Accept(messaging.Message())
+ (result, _, _) = self.handle.ReadMessage()
+ self.assertEquals(result, system.RESULT_OK)
+ self.assertFalse(self.received_errors)
+
+ def testConnectorCloseRemoteHandle(self):
+ self.handle.Close()
+ self.loop.RunUntilIdle()
+ self.assertFalse(self.received_messages)
+ self.assertTrue(self.received_errors)
+ self.assertEquals(self.received_errors[0],
+ system.RESULT_FAILED_PRECONDITION)
+
+ def testConnectorDeleteConnector(self):
+ self.connector = None
+ (result, _, _) = self.handle.ReadMessage()
+ self.assertEquals(result, system.RESULT_FAILED_PRECONDITION)
+
+
+class HeaderTest(unittest.TestCase):
+
+ def testSimpleMessageHeader(self):
+ header = messaging.MessageHeader(0xdeadbeaf, messaging.NO_FLAG)
+ self.assertEqual(header.message_type, 0xdeadbeaf)
+ self.assertFalse(header.has_request_id)
+ self.assertFalse(header.expects_response)
+ self.assertFalse(header.is_response)
+ data = header.Serialize()
+ other_header = messaging.MessageHeader.Deserialize(data)
+ self.assertEqual(other_header.message_type, 0xdeadbeaf)
+ self.assertFalse(other_header.has_request_id)
+ self.assertFalse(other_header.expects_response)
+ self.assertFalse(other_header.is_response)
+
+ def testMessageHeaderWithRequestID(self):
+ # Request message.
+ header = messaging.MessageHeader(0xdeadbeaf,
+ messaging.MESSAGE_EXPECTS_RESPONSE_FLAG)
+
+ self.assertEqual(header.message_type, 0xdeadbeaf)
+ self.assertTrue(header.has_request_id)
+ self.assertTrue(header.expects_response)
+ self.assertFalse(header.is_response)
+ self.assertEqual(header.request_id, 0)
+
+ data = header.Serialize()
+ other_header = messaging.MessageHeader.Deserialize(data)
+
+ self.assertEqual(other_header.message_type, 0xdeadbeaf)
+ self.assertTrue(other_header.has_request_id)
+ self.assertTrue(other_header.expects_response)
+ self.assertFalse(other_header.is_response)
+ self.assertEqual(other_header.request_id, 0)
+
+ header.request_id = 0xdeadbeafdeadbeaf
+ data = header.Serialize()
+ other_header = messaging.MessageHeader.Deserialize(data)
+
+ self.assertEqual(other_header.request_id, 0xdeadbeafdeadbeaf)
+
+ # Response message.
+ header = messaging.MessageHeader(0xdeadbeaf,
+ messaging.MESSAGE_IS_RESPONSE_FLAG,
+ 0xdeadbeafdeadbeaf)
+
+ self.assertEqual(header.message_type, 0xdeadbeaf)
+ self.assertTrue(header.has_request_id)
+ self.assertFalse(header.expects_response)
+ self.assertTrue(header.is_response)
+ self.assertEqual(header.request_id, 0xdeadbeafdeadbeaf)
+
+ data = header.Serialize()
+ other_header = messaging.MessageHeader.Deserialize(data)
+
+ self.assertEqual(other_header.message_type, 0xdeadbeaf)
+ self.assertTrue(other_header.has_request_id)
+ self.assertFalse(other_header.expects_response)
+ self.assertTrue(other_header.is_response)
+ self.assertEqual(other_header.request_id, 0xdeadbeafdeadbeaf)
+
+
+class RouterTest(unittest.TestCase):
+
+ def setUp(self):
+ mojo.embedder.Init()
+ self.loop = system.RunLoop()
+ self.received_messages = []
+ self.received_errors = []
+ def _OnMessage(message):
+ self.received_messages.append(message)
+ return True
+ def _OnError(result):
+ self.received_errors.append(result)
+ handles = system.MessagePipe()
+ self.router = messaging.Router(handles.handle1)
+ self.router.SetIncomingMessageReceiver(
+ messaging.ForwardingMessageReceiver(_OnMessage))
+ self.router.SetErrorHandler(
+ _ForwardingConnectionErrorHandler(_OnError))
+ self.router.Start()
+ self.handle = handles.handle0
+
+ def tearDown(self):
+ self.router = None
+ self.handle = None
+ self.loop = None
+
+ def testSimpleMessage(self):
+ header_data = messaging.MessageHeader(0, messaging.NO_FLAG).Serialize()
+ message = messaging.Message(header_data)
+ self.router.Accept(message)
+ self.loop.RunUntilIdle()
+ self.assertFalse(self.received_errors)
+ self.assertFalse(self.received_messages)
+ (res, data, _) = self.handle.ReadMessage(bytearray(len(header_data)))
+ self.assertEquals(system.RESULT_OK, res)
+ self.assertEquals(data[0], header_data)
+
+ def testSimpleReception(self):
+ header_data = messaging.MessageHeader(0, messaging.NO_FLAG).Serialize()
+ self.handle.WriteMessage(header_data)
+ self.loop.RunUntilIdle()
+ self.assertFalse(self.received_errors)
+ self.assertEquals(len(self.received_messages), 1)
+ self.assertEquals(self.received_messages[0].data, header_data)
+
+ def testRequestResponse(self):
+ header_data = messaging.MessageHeader(
+ 0, messaging.MESSAGE_EXPECTS_RESPONSE_FLAG).Serialize()
+ message = messaging.Message(header_data)
+ back_messages = []
+ def OnBackMessage(message):
+ back_messages.append(message)
+ self.router.AcceptWithResponder(message,
+ messaging.ForwardingMessageReceiver(
+ OnBackMessage))
+ self.loop.RunUntilIdle()
+ self.assertFalse(self.received_errors)
+ self.assertFalse(self.received_messages)
+ (res, data, _) = self.handle.ReadMessage(bytearray(len(header_data)))
+ self.assertEquals(system.RESULT_OK, res)
+ message_header = messaging.MessageHeader.Deserialize(data[0])
+ self.assertNotEquals(message_header.request_id, 0)
+ response_header_data = messaging.MessageHeader(
+ 0,
+ messaging.MESSAGE_IS_RESPONSE_FLAG,
+ message_header.request_id).Serialize()
+ self.handle.WriteMessage(response_header_data)
+ self.loop.RunUntilIdle()
+ self.assertFalse(self.received_errors)
+ self.assertEquals(len(back_messages), 1)
+ self.assertEquals(back_messages[0].data, response_header_data)
diff --git a/mojo/python/tests/promise_unittest.py b/mojo/python/tests/promise_unittest.py
new file mode 100644
index 0000000..44427ba
--- /dev/null
+++ b/mojo/python/tests/promise_unittest.py
@@ -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.
+
+import unittest
+
+# pylint: disable=F0401
+from mojo.bindings import promise
+
+
+class PromiseTest(unittest.TestCase):
+
+ def setUp(self):
+ self.accumulated = []
+
+ def _AddToAccumulated(self, res):
+ self.accumulated.append(res)
+ return res
+
+ def testResolve(self):
+ p = promise.Promise.Resolve(0)
+ self.assertEquals(p.state, promise.Promise.STATE_FULLFILLED)
+ p.Then(self._AddToAccumulated)
+ self.assertEquals(self.accumulated, [0])
+
+ def testResolveToPromise(self):
+ p = promise.Promise.Resolve(0)
+ self.assertEquals(p.state, promise.Promise.STATE_FULLFILLED)
+ q = promise.Promise.Resolve(p)
+ self.assertEquals(p.state, promise.Promise.STATE_FULLFILLED)
+ q.Then(self._AddToAccumulated)
+ self.assertEquals(self.accumulated, [0])
+
+ def testReject(self):
+ p = promise.Promise.Reject(0)
+ self.assertEquals(p.state, promise.Promise.STATE_REJECTED)
+ p.Then(onRejected=self._AddToAccumulated)
+ self.assertEquals(self.accumulated, [0])
+
+ def testGeneratorFunctionResolve(self):
+ (p, resolve, _) = _GetPromiseAndFunctions()
+ self.assertEquals(p.state, promise.Promise.STATE_PENDING)
+ p.Then(self._AddToAccumulated)
+ resolve(0)
+ self.assertEquals(p.state, promise.Promise.STATE_FULLFILLED)
+ self.assertEquals(self.accumulated, [0])
+
+ def testGeneratorFunctionReject(self):
+ (p, _, reject) = _GetPromiseAndFunctions()
+ self.assertEquals(p.state, promise.Promise.STATE_PENDING)
+ p.Then(None, self._AddToAccumulated)
+ reject(0)
+ self.assertEquals(p.state, promise.Promise.STATE_REJECTED)
+ self.assertEquals(self.accumulated, [0])
+
+ def testGeneratorFunctionResolveToPromise(self):
+ (p1, resolve, _) = _GetPromiseAndFunctions()
+ p2 = promise.Promise(lambda x, y: x(p1))
+ self.assertEquals(p2.state, promise.Promise.STATE_PENDING)
+ p2.Then(self._AddToAccumulated)
+ resolve(promise.Promise.Resolve(0))
+ self.assertEquals(self.accumulated, [0])
+
+ def testComputation(self):
+ (p, resolve, _) = _GetPromiseAndFunctions()
+ p.Then(lambda x: x+1).Then(lambda x: x+2).Then(self._AddToAccumulated)
+ self.assertEquals(self.accumulated, [])
+ resolve(0)
+ self.assertEquals(self.accumulated, [3])
+
+ def testRecoverAfterException(self):
+ (p, resolve, _) = _GetPromiseAndFunctions()
+ p.Then(_ThrowException).Catch(self._AddToAccumulated)
+ self.assertEquals(self.accumulated, [])
+ resolve(0)
+ self.assertEquals(len(self.accumulated), 1)
+ self.assertIsInstance(self.accumulated[0], RuntimeError)
+ self.assertEquals(self.accumulated[0].message, 0)
+
+ def testMultipleRejectResolve(self):
+ (p, resolve, reject) = _GetPromiseAndFunctions()
+ p.Then(self._AddToAccumulated, self._AddToAccumulated)
+ resolve(0)
+ self.assertEquals(self.accumulated, [0])
+ resolve(0)
+ self.assertEquals(self.accumulated, [0])
+ reject(0)
+ self.assertEquals(self.accumulated, [0])
+
+ self.accumulated = []
+ (p, resolve, reject) = _GetPromiseAndFunctions()
+ p.Then(self._AddToAccumulated, self._AddToAccumulated)
+ reject(0)
+ self.assertEquals(self.accumulated, [0])
+ resolve(0)
+ self.assertEquals(self.accumulated, [0])
+ reject(0)
+ self.assertEquals(self.accumulated, [0])
+
+ def testAll(self):
+ promises_and_functions = [_GetPromiseAndFunctions() for x in xrange(10)]
+ promises = [x[0] for x in promises_and_functions]
+ all_promise = promise.Promise.All(*promises)
+ res = []
+ def AddToRes(values):
+ res.append(values)
+ all_promise.Then(AddToRes, AddToRes)
+ for i, (_, resolve, _) in enumerate(promises_and_functions):
+ self.assertEquals(len(res), 0)
+ resolve(i)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(res[0], [i for i in xrange(10)])
+ self.assertEquals(all_promise.state, promise.Promise.STATE_FULLFILLED)
+
+ def testAllFailure(self):
+ promises_and_functions = [_GetPromiseAndFunctions() for x in xrange(10)]
+ promises = [x[0] for x in promises_and_functions]
+ all_promise = promise.Promise.All(*promises)
+ res = []
+ def AddToRes(values):
+ res.append(values)
+ all_promise.Then(AddToRes, AddToRes)
+ for i in xrange(10):
+ if i <= 5:
+ self.assertEquals(len(res), 0)
+ else:
+ self.assertEquals(len(res), 1)
+ if i != 5:
+ promises_and_functions[i][1](i)
+ else:
+ promises_and_functions[i][2]('error')
+ self.assertEquals(len(res), 1)
+ self.assertEquals(res[0], 'error')
+ self.assertEquals(all_promise.state, promise.Promise.STATE_REJECTED)
+
+ def testRace(self):
+ promises_and_functions = [_GetPromiseAndFunctions() for x in xrange(10)]
+ promises = [x[0] for x in promises_and_functions]
+ race_promise = promise.Promise.Race(*promises)
+ res = []
+ def AddToRes(values):
+ res.append(values)
+ race_promise.Then(AddToRes, AddToRes)
+ self.assertEquals(len(res), 0)
+ promises_and_functions[7][1]('success')
+ self.assertEquals(len(res), 1)
+ for i, (f) in enumerate(promises_and_functions):
+ f[1 + (i % 2)](i)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(res[0], 'success')
+ self.assertEquals(race_promise.state, promise.Promise.STATE_FULLFILLED)
+
+ def testRaceFailure(self):
+ promises_and_functions = [_GetPromiseAndFunctions() for x in xrange(10)]
+ promises = [x[0] for x in promises_and_functions]
+ race_promise = promise.Promise.Race(*promises)
+ res = []
+ def AddToRes(values):
+ res.append(values)
+ race_promise.Then(AddToRes, AddToRes)
+ self.assertEquals(len(res), 0)
+ promises_and_functions[7][2]('error')
+ self.assertEquals(len(res), 1)
+ for i, (f) in enumerate(promises_and_functions):
+ f[1 + (i % 2)](i)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(res[0], 'error')
+ self.assertEquals(race_promise.state, promise.Promise.STATE_REJECTED)
+
+
+def _GetPromiseAndFunctions():
+ functions = {}
+ def GeneratorFunction(resolve, reject):
+ functions['resolve'] = resolve
+ functions['reject'] = reject
+ p = promise.Promise(GeneratorFunction)
+ return (p, functions['resolve'], functions['reject'])
+
+
+def _ThrowException(x):
+ raise RuntimeError(x)
diff --git a/mojo/python/tests/runloop_unittest.py b/mojo/python/tests/runloop_unittest.py
new file mode 100644
index 0000000..3549dbe
--- /dev/null
+++ b/mojo/python/tests/runloop_unittest.py
@@ -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.
+
+import unittest
+
+# pylint: disable=F0401
+import mojo.embedder
+from mojo import system
+
+
+def _Increment(array):
+ def _Closure():
+ array.append(0)
+ return _Closure
+
+
+class RunLoopTest(unittest.TestCase):
+
+ def setUp(self):
+ mojo.embedder.Init()
+
+ def testRunLoop(self):
+ loop = system.RunLoop()
+ array = []
+ for _ in xrange(10):
+ loop.PostDelayedTask(_Increment(array))
+ loop.RunUntilIdle()
+ self.assertEquals(len(array), 10)
+
+ def testRunLoopWithException(self):
+ loop = system.RunLoop()
+ def Throw():
+ raise Exception("error")
+ array = []
+ loop.PostDelayedTask(Throw)
+ loop.PostDelayedTask(_Increment(array))
+ with self.assertRaisesRegexp(Exception, '^error$'):
+ loop.Run()
+ self.assertEquals(len(array), 0)
+ loop.RunUntilIdle()
+ self.assertEquals(len(array), 1)
diff --git a/mojo/python/tests/system_unittest.py b/mojo/python/tests/system_unittest.py
new file mode 100644
index 0000000..2f0e36a
--- /dev/null
+++ b/mojo/python/tests/system_unittest.py
@@ -0,0 +1,310 @@
+# Copyright 2014 The Chromium 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 random
+import sys
+import time
+import unittest
+
+# pylint: disable=F0401
+import mojo.embedder
+from mojo import system
+
+DATA_SIZE = 1024
+
+
+def _GetRandomBuffer(size):
+ random.seed(size)
+ return bytearray(''.join(chr(random.randint(0, 255)) for i in xrange(size)))
+
+
+class BaseMojoTest(unittest.TestCase):
+
+ def setUp(self):
+ mojo.embedder.Init()
+
+
+class CoreTest(BaseMojoTest):
+
+ def testResults(self):
+ self.assertEquals(system.RESULT_OK, 0)
+ self.assertLess(system.RESULT_CANCELLED, 0)
+ self.assertLess(system.RESULT_UNKNOWN, 0)
+ self.assertLess(system.RESULT_INVALID_ARGUMENT, 0)
+ self.assertLess(system.RESULT_DEADLINE_EXCEEDED, 0)
+ self.assertLess(system.RESULT_NOT_FOUND, 0)
+ self.assertLess(system.RESULT_ALREADY_EXISTS, 0)
+ self.assertLess(system.RESULT_PERMISSION_DENIED, 0)
+ self.assertLess(system.RESULT_RESOURCE_EXHAUSTED, 0)
+ self.assertLess(system.RESULT_FAILED_PRECONDITION, 0)
+ self.assertLess(system.RESULT_ABORTED, 0)
+ self.assertLess(system.RESULT_OUT_OF_RANGE, 0)
+ self.assertLess(system.RESULT_UNIMPLEMENTED, 0)
+ self.assertLess(system.RESULT_INTERNAL, 0)
+ self.assertLess(system.RESULT_UNAVAILABLE, 0)
+ self.assertLess(system.RESULT_DATA_LOSS, 0)
+ self.assertLess(system.RESULT_BUSY, 0)
+ self.assertLess(system.RESULT_SHOULD_WAIT, 0)
+
+ def testConstants(self):
+ self.assertGreaterEqual(system.DEADLINE_INDEFINITE, 0)
+ self.assertGreaterEqual(system.HANDLE_SIGNAL_NONE, 0)
+ self.assertGreaterEqual(system.HANDLE_SIGNAL_READABLE, 0)
+ self.assertGreaterEqual(system.HANDLE_SIGNAL_WRITABLE, 0)
+ self.assertGreaterEqual(system.WRITE_MESSAGE_FLAG_NONE, 0)
+ self.assertGreaterEqual(system.READ_MESSAGE_FLAG_NONE, 0)
+ self.assertGreaterEqual(system.READ_MESSAGE_FLAG_MAY_DISCARD, 0)
+ self.assertGreaterEqual(system.WRITE_DATA_FLAG_NONE, 0)
+ self.assertGreaterEqual(system.WRITE_DATA_FLAG_ALL_OR_NONE, 0)
+ self.assertGreaterEqual(system.READ_DATA_FLAG_NONE, 0)
+ self.assertGreaterEqual(system.READ_DATA_FLAG_ALL_OR_NONE, 0)
+ self.assertGreaterEqual(system.READ_DATA_FLAG_DISCARD, 0)
+ self.assertGreaterEqual(system.READ_DATA_FLAG_QUERY, 0)
+ self.assertGreaterEqual(system.MAP_BUFFER_FLAG_NONE, 0)
+
+ def testGetTimeTicksNow(self):
+ pt1 = time.time()
+ v1 = system.GetTimeTicksNow()
+ time.sleep(1e-3)
+ v2 = system.GetTimeTicksNow()
+ pt2 = time.time()
+ self.assertGreater(v1, 0)
+ self.assertGreater(v2, v1 + 1000)
+ self.assertGreater(1e6 * (pt2 - pt1), v2 - v1)
+
+ def _testHandlesCreation(self, *args):
+ for handle in args:
+ self.assertTrue(handle.IsValid())
+ handle.Close()
+ self.assertFalse(handle.IsValid())
+
+ def _TestMessageHandleCreation(self, handles):
+ self._testHandlesCreation(handles.handle0, handles.handle1)
+
+ def testCreateMessagePipe(self):
+ self._TestMessageHandleCreation(system.MessagePipe())
+
+ def testCreateMessagePipeWithNoneOptions(self):
+ self._TestMessageHandleCreation(system.MessagePipe(None))
+
+ def testCreateMessagePipeWithOptions(self):
+ self._TestMessageHandleCreation(
+ system.MessagePipe(system.CreateMessagePipeOptions()))
+
+ def testWaitOverMessagePipe(self):
+ handles = system.MessagePipe()
+ handle = handles.handle0
+
+ self.assertEquals(system.RESULT_OK, handle.Wait(
+ system.HANDLE_SIGNAL_WRITABLE, system.DEADLINE_INDEFINITE))
+ self.assertEquals(system.RESULT_DEADLINE_EXCEEDED,
+ handle.Wait(system.HANDLE_SIGNAL_READABLE, 0))
+
+ handles.handle1.WriteMessage()
+
+ self.assertEquals(
+ system.RESULT_OK,
+ handle.Wait(
+ system.HANDLE_SIGNAL_READABLE,
+ system.DEADLINE_INDEFINITE))
+
+ def testWaitOverManyMessagePipe(self):
+ handles = system.MessagePipe()
+ handle0 = handles.handle0
+ handle1 = handles.handle1
+
+ self.assertEquals(
+ 0,
+ system.WaitMany(
+ [(handle0, system.HANDLE_SIGNAL_WRITABLE),
+ (handle1, system.HANDLE_SIGNAL_WRITABLE)],
+ system.DEADLINE_INDEFINITE))
+ self.assertEquals(
+ system.RESULT_DEADLINE_EXCEEDED,
+ system.WaitMany(
+ [(handle0, system.HANDLE_SIGNAL_READABLE),
+ (handle1, system.HANDLE_SIGNAL_READABLE)], 0))
+
+ handle0.WriteMessage()
+
+ self.assertEquals(
+ 1,
+ system.WaitMany(
+ [(handle0, system.HANDLE_SIGNAL_READABLE),
+ (handle1, system.HANDLE_SIGNAL_READABLE)],
+ system.DEADLINE_INDEFINITE))
+
+ def testSendBytesOverMessagePipe(self):
+ handles = system.MessagePipe()
+ data = _GetRandomBuffer(DATA_SIZE)
+ handles.handle0.WriteMessage(data)
+ (res, buffers, next_message) = handles.handle1.ReadMessage()
+ self.assertEquals(system.RESULT_RESOURCE_EXHAUSTED, res)
+ self.assertEquals(None, buffers)
+ self.assertEquals((DATA_SIZE, 0), next_message)
+ result = bytearray(DATA_SIZE)
+ (res, buffers, next_message) = handles.handle1.ReadMessage(result)
+ self.assertEquals(system.RESULT_OK, res)
+ self.assertEquals(None, next_message)
+ self.assertEquals((data, []), buffers)
+
+ def testSendEmptyDataOverMessagePipe(self):
+ handles = system.MessagePipe()
+ handles.handle0.WriteMessage(None)
+ (res, buffers, next_message) = handles.handle1.ReadMessage()
+
+ self.assertEquals(system.RESULT_OK, res)
+ self.assertEquals(None, next_message)
+ self.assertEquals((None, []), buffers)
+
+ def testSendHandleOverMessagePipe(self):
+ handles = system.MessagePipe()
+ handles_to_send = system.MessagePipe()
+ handles.handle0.WriteMessage(handles=[handles_to_send.handle0,
+ handles_to_send.handle1])
+ (res, buffers, next_message) = handles.handle1.ReadMessage(
+ max_number_of_handles=2)
+
+ self.assertFalse(handles_to_send.handle0.IsValid())
+ self.assertFalse(handles_to_send.handle1.IsValid())
+ self.assertEquals(system.RESULT_OK, res)
+ self.assertEquals(None, next_message)
+ self.assertEquals(None, buffers[0])
+ self.assertEquals(2, len(buffers[1]))
+
+ handles = buffers[1]
+ for handle in handles:
+ self.assertTrue(handle.IsValid())
+ (res, buffers, next_message) = handle.ReadMessage()
+ self.assertEquals(system.RESULT_SHOULD_WAIT, res)
+
+ for handle in handles:
+ handle.WriteMessage()
+
+ for handle in handles:
+ (res, buffers, next_message) = handle.ReadMessage()
+ self.assertEquals(system.RESULT_OK, res)
+
+ def _TestDataHandleCreation(self, handles):
+ self._testHandlesCreation(
+ handles.producer_handle, handles.consumer_handle)
+
+ def testCreateDataPipe(self):
+ self._TestDataHandleCreation(system.DataPipe())
+
+ def testCreateDataPipeWithNoneOptions(self):
+ self._TestDataHandleCreation(system.DataPipe(None))
+
+ def testCreateDataPipeWithDefaultOptions(self):
+ self._TestDataHandleCreation(
+ system.DataPipe(system.CreateDataPipeOptions()))
+
+ def testCreateDataPipeWithDiscardFlag(self):
+ options = system.CreateDataPipeOptions()
+ options.flags = system.CreateDataPipeOptions.FLAG_MAY_DISCARD
+ self._TestDataHandleCreation(system.DataPipe(options))
+
+ def testCreateDataPipeWithElementSize(self):
+ options = system.CreateDataPipeOptions()
+ options.element_num_bytes = 5
+ self._TestDataHandleCreation(system.DataPipe(options))
+
+ def testCreateDataPipeWithCapacity(self):
+ options = system.CreateDataPipeOptions()
+ options.element_capacity_num_bytes = DATA_SIZE
+ self._TestDataHandleCreation(system.DataPipe(options))
+
+ def testCreateDataPipeWithIncorrectParameters(self):
+ options = system.CreateDataPipeOptions()
+ options.element_num_bytes = 5
+ options.capacity_num_bytes = DATA_SIZE
+ with self.assertRaises(system.MojoException) as cm:
+ self._TestDataHandleCreation(system.DataPipe(options))
+ self.assertEquals(system.RESULT_INVALID_ARGUMENT, cm.exception.mojo_result)
+
+ def testSendEmptyDataOverDataPipe(self):
+ pipes = system.DataPipe()
+ self.assertEquals((system.RESULT_OK, 0), pipes.producer_handle.WriteData())
+ self.assertEquals(
+ (system.RESULT_OK, None), pipes.consumer_handle.ReadData())
+
+ def testSendDataOverDataPipe(self):
+ pipes = system.DataPipe()
+ data = _GetRandomBuffer(DATA_SIZE)
+ self.assertEquals((system.RESULT_OK, DATA_SIZE),
+ pipes.producer_handle.WriteData(data))
+ self.assertEquals((system.RESULT_OK, data),
+ pipes.consumer_handle.ReadData(bytearray(DATA_SIZE)))
+
+ def testTwoPhaseWriteOnDataPipe(self):
+ pipes = system.DataPipe()
+ (res, buf) = pipes.producer_handle.BeginWriteData(DATA_SIZE)
+ self.assertEquals(system.RESULT_OK, res)
+ self.assertGreaterEqual(len(buf.buffer), DATA_SIZE)
+ data = _GetRandomBuffer(DATA_SIZE)
+ buf.buffer[0:DATA_SIZE] = data
+ self.assertEquals(system.RESULT_OK, buf.End(DATA_SIZE))
+ self.assertEquals((system.RESULT_OK, data),
+ pipes.consumer_handle.ReadData(bytearray(DATA_SIZE)))
+
+ def testTwoPhaseReadOnDataPipe(self):
+ pipes = system.DataPipe()
+ data = _GetRandomBuffer(DATA_SIZE)
+ self.assertEquals((system.RESULT_OK, DATA_SIZE),
+ pipes.producer_handle.WriteData(data))
+ (res, buf) = pipes.consumer_handle.BeginReadData()
+ self.assertEquals(system.RESULT_OK, res)
+ self.assertEquals(DATA_SIZE, len(buf.buffer))
+ self.assertEquals(data, buf.buffer)
+ self.assertEquals(system.RESULT_OK, buf.End(DATA_SIZE))
+
+ def testCreateSharedBuffer(self):
+ self._testHandlesCreation(system.CreateSharedBuffer(DATA_SIZE))
+
+ def testCreateSharedBufferWithNoneOptions(self):
+ self._testHandlesCreation(system.CreateSharedBuffer(DATA_SIZE, None))
+
+ def testCreateSharedBufferWithDefaultOptions(self):
+ self._testHandlesCreation(
+ system.CreateSharedBuffer(
+ DATA_SIZE,
+ system.CreateSharedBufferOptions()))
+
+ def testDuplicateSharedBuffer(self):
+ handle = system.CreateSharedBuffer(DATA_SIZE)
+ self._testHandlesCreation(handle.Duplicate())
+
+ def testDuplicateSharedBufferWithNoneOptions(self):
+ handle = system.CreateSharedBuffer(DATA_SIZE)
+ self._testHandlesCreation(handle.Duplicate(None))
+
+ def testDuplicateSharedBufferWithDefaultOptions(self):
+ handle = system.CreateSharedBuffer(DATA_SIZE)
+ self._testHandlesCreation(
+ handle.Duplicate(system.DuplicateSharedBufferOptions()))
+
+ def testSendBytesOverSharedBuffer(self):
+ handle = system.CreateSharedBuffer(DATA_SIZE)
+ duplicated = handle.Duplicate()
+ data = _GetRandomBuffer(DATA_SIZE)
+ (res1, buf1) = handle.Map(0, DATA_SIZE)
+ (res2, buf2) = duplicated.Map(0, DATA_SIZE)
+ self.assertEquals(system.RESULT_OK, res1)
+ self.assertEquals(system.RESULT_OK, res2)
+ self.assertEquals(DATA_SIZE, len(buf1.buffer))
+ self.assertEquals(DATA_SIZE, len(buf2.buffer))
+ self.assertEquals(buf1.buffer, buf2.buffer)
+
+ buf1.buffer[:] = data
+ self.assertEquals(data, buf1.buffer)
+ self.assertEquals(data, buf2.buffer)
+ self.assertEquals(buf1.buffer, buf2.buffer)
+
+
+if __name__ == '__main__':
+ suite = unittest.TestLoader().loadTestsFromTestCase(CoreTest)
+ test_results = unittest.TextTestRunner(verbosity=0).run(suite)
+ if not test_results.wasSuccessful():
+ sys.exit(1)
+ sys.exit(0)
diff --git a/mojo/services/BUILD.gn b/mojo/services/BUILD.gn
new file mode 100644
index 0000000..5b4498c
--- /dev/null
+++ b/mojo/services/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+group("services") {
+ deps = [
+ "//mojo/services/clipboard",
+ "//mojo/services/gles2:bindings",
+ "//mojo/services/html_viewer",
+ "//mojo/services/network",
+ "//mojo/services/public/interfaces/clipboard",
+ "//mojo/services/public/interfaces/content_handler",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/native_viewport",
+ "//mojo/services/public/interfaces/navigation",
+ "//mojo/services/public/interfaces/network",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/surfaces",
+ "//mojo/services/test_service:bindings",
+ ]
+ if (!is_android) {
+ deps += ["//mojo/services/native_viewport"]
+ }
+ if (use_aura) {
+ deps += [
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/services/public/interfaces/window_manager",
+ "//mojo/services/view_manager",
+ "//mojo/services/window_manager",
+ ]
+ }
+}
diff --git a/mojo/services/DEPS b/mojo/services/DEPS
new file mode 100644
index 0000000..2bbe62d
--- /dev/null
+++ b/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/mojo/services/clipboard/BUILD.gn b/mojo/services/clipboard/BUILD.gn
new file mode 100644
index 0000000..5c3b78e
--- /dev/null
+++ b/mojo/services/clipboard/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# GYP version: mojo/mojo_services.gypi:mojo_clipboard
+component("clipboard") {
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_component",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/public/interfaces/clipboard",
+ "//ui/base",
+ ]
+
+ sources = [
+ "clipboard_standalone_impl.cc",
+ "clipboard_standalone_impl.h",
+ "main.cc",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_clipboard_unittests
+test("mojo_clipboard_unittests") {
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/application",
+ "//mojo/application_manager",
+ "//mojo/common",
+ "//mojo/common/test:run_all_unittests",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/interfaces/clipboard:clipboard",
+ "//mojo/shell:test_support",
+ "//testing/gtest",
+ ]
+
+ sources = [
+ "clipboard_standalone_unittest.cc",
+ ]
+}
diff --git a/mojo/services/clipboard/DEPS b/mojo/services/clipboard/DEPS
new file mode 100644
index 0000000..79cfc5d
--- /dev/null
+++ b/mojo/services/clipboard/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+mojo/application",
+ "+mojo/services",
+]
diff --git a/mojo/services/clipboard/clipboard_standalone_impl.cc b/mojo/services/clipboard/clipboard_standalone_impl.cc
new file mode 100644
index 0000000..e1c7e2c
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_impl.cc
@@ -0,0 +1,100 @@
+// 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/services/clipboard/clipboard_standalone_impl.h"
+
+namespace mojo {
+
+typedef std::vector<uint8_t> ByteVector;
+
+// ClipboardData contains data copied to the Clipboard for a variety of formats.
+// It mostly just provides APIs to cleanly access and manipulate this data.
+class ClipboardStandaloneImpl::ClipboardData {
+ public:
+ ClipboardData() {}
+ ~ClipboardData() {}
+
+ std::vector<std::string> GetMimeTypes() const {
+ std::vector<std::string> types;
+ for (std::map<std::string, ByteVector>::const_iterator it =
+ data_types_.begin();
+ it != data_types_.end();
+ ++it) {
+ types.push_back(it->first);
+ }
+
+ return types;
+ }
+
+ void SetData(std::map<std::string, ByteVector>* data) {
+ std::swap(data_types_, *data);
+ }
+
+ bool GetData(const std::string& mime_type, ByteVector* data) const {
+ std::map<std::string, ByteVector>::const_iterator it =
+ data_types_.find(mime_type);
+ if (it != data_types_.end()) {
+ *data = it->second;
+ return true;
+ }
+
+ return false;
+ }
+
+ private:
+ std::map<std::string, ByteVector> data_types_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardData);
+};
+
+ClipboardStandaloneImpl::ClipboardStandaloneImpl() {
+ for (int i = 0; i < kNumClipboards; ++i) {
+ sequence_number_[i] = 0;
+ clipboard_state_[i].reset(new ClipboardData);
+ }
+}
+
+ClipboardStandaloneImpl::~ClipboardStandaloneImpl() {
+}
+
+void ClipboardStandaloneImpl::GetSequenceNumber(
+ Clipboard::Type clipboard_type,
+ const mojo::Callback<void(uint64_t)>& callback) {
+ callback.Run(sequence_number_[clipboard_type]);
+}
+
+void ClipboardStandaloneImpl::GetAvailableMimeTypes(
+ Clipboard::Type clipboard_type,
+ const mojo::Callback<void(mojo::Array<mojo::String>)>& callback) {
+ mojo::Array<mojo::String> types = mojo::Array<mojo::String>::From(
+ clipboard_state_[clipboard_type]->GetMimeTypes());
+ callback.Run(types.Pass());
+}
+
+void ClipboardStandaloneImpl::ReadMimeType(
+ Clipboard::Type clipboard_type,
+ const mojo::String& mime_type,
+ const mojo::Callback<void(mojo::Array<uint8_t>)>& callback) {
+ ByteVector mime_data;
+ if (clipboard_state_[clipboard_type]->GetData(
+ mime_type.To<std::string>(), &mime_data)) {
+ callback.Run(mojo::Array<uint8_t>::From(mime_data).Pass());
+ return;
+ }
+
+ callback.Run(mojo::Array<uint8_t>().Pass());
+}
+
+void ClipboardStandaloneImpl::WriteClipboardData(
+ Clipboard::Type clipboard_type,
+ mojo::Array<MimeTypePairPtr> data) {
+ std::map<std::string, ByteVector> mime_data;
+ for (size_t i = 0; i < data.size(); ++i)
+ mime_data[data[i]->mime_type] = data[i]->data;
+
+ sequence_number_[clipboard_type]++;
+ clipboard_state_[clipboard_type]->SetData(&mime_data);
+}
+
+} // namespace mojo
diff --git a/mojo/services/clipboard/clipboard_standalone_impl.h b/mojo/services/clipboard/clipboard_standalone_impl.h
new file mode 100644
index 0000000..09d7424
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_impl.h
@@ -0,0 +1,58 @@
+// 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_SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
+#define MOJO_SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
+
+#include <base/memory/scoped_ptr.h>
+#include <string>
+
+#include "mojo/services/public/interfaces/clipboard/clipboard.mojom.h"
+
+namespace mojo {
+
+// Stub clipboard implementation.
+//
+// Eventually, we'll actually want to interact with the system clipboard, but
+// that's hard today because the system clipboard is asynchronous (on X11), the
+// ui::Clipboard interface is synchronous (which is what we'd use), mojo is
+// asynchronous across processes, and the WebClipboard interface is synchronous
+// (which is at least tractable).
+class ClipboardStandaloneImpl : public InterfaceImpl<mojo::Clipboard> {
+ public:
+ // mojo::Clipboard exposes three possible clipboards.
+ static const int kNumClipboards = 3;
+
+ ClipboardStandaloneImpl();
+ virtual ~ClipboardStandaloneImpl();
+
+ // InterfaceImpl<mojo::Clipboard> implementation.
+ virtual void GetSequenceNumber(
+ Clipboard::Type clipboard_type,
+ const mojo::Callback<void(uint64_t)>& callback) override;
+ virtual void GetAvailableMimeTypes(
+ Clipboard::Type clipboard_types,
+ const mojo::Callback<void(mojo::Array<mojo::String>)>& callback) override;
+ virtual void ReadMimeType(
+ Clipboard::Type clipboard_type,
+ const mojo::String& mime_type,
+ const mojo::Callback<void(mojo::Array<uint8_t>)>& callback) override;
+ virtual void WriteClipboardData(Clipboard::Type clipboard_type,
+ mojo::Array<MimeTypePairPtr> data) override;
+
+ private:
+ uint64_t sequence_number_[kNumClipboards];
+
+ // Internal struct which stores the current state of the clipboard.
+ class ClipboardData;
+
+ // The current clipboard state. This is what is read from.
+ scoped_ptr<ClipboardData> clipboard_state_[kNumClipboards];
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardStandaloneImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
diff --git a/mojo/services/clipboard/clipboard_standalone_unittest.cc b/mojo/services/clipboard/clipboard_standalone_unittest.cc
new file mode 100644
index 0000000..c297b94
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_unittest.cc
@@ -0,0 +1,186 @@
+// 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 "base/at_exit.h"
+#include "base/bind.h"
+#include "base/macros.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/services/public/interfaces/clipboard/clipboard.mojom.h"
+#include "mojo/shell/shell_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void CopyUint64AndEndRunloop(uint64_t* output,
+ base::RunLoop* run_loop,
+ uint64_t input) {
+ *output = input;
+ run_loop->Quit();
+}
+
+void CopyStringAndEndRunloop(std::string* output,
+ bool* string_is_null,
+ base::RunLoop* run_loop,
+ const mojo::Array<uint8_t>& input) {
+ *string_is_null = input.is_null();
+ *output = input.is_null() ? "" : input.To<std::string>();
+ run_loop->Quit();
+}
+
+void CopyVectorStringAndEndRunloop(std::vector<std::string>* output,
+ base::RunLoop* run_loop,
+ const mojo::Array<mojo::String>& input) {
+ *output = input.To<std::vector<std::string> >();
+ run_loop->Quit();
+}
+
+const char* kUninitialized = "Uninitialized data";
+const char* kPlainTextData = "Some plain data";
+const char* kHtmlData = "<html>data</html>";
+
+} // namespace
+
+namespace mojo {
+namespace service {
+
+class ClipboardStandaloneTest : public testing::Test {
+ public:
+ ClipboardStandaloneTest() {}
+ virtual ~ClipboardStandaloneTest() {}
+
+ virtual void SetUp() override {
+ test_helper_.Init();
+
+ test_helper_.application_manager()->ConnectToService(
+ GURL("mojo:mojo_clipboard"), &clipboard_);
+ }
+
+ uint64_t GetSequenceNumber() {
+ base::RunLoop run_loop;
+ uint64_t sequence_num = 999999;
+ clipboard_->GetSequenceNumber(
+ mojo::Clipboard::TYPE_COPY_PASTE,
+ base::Bind(&CopyUint64AndEndRunloop, &sequence_num, &run_loop));
+ run_loop.Run();
+ return sequence_num;
+ }
+
+ std::vector<std::string> GetAvailableFormatMimeTypes() {
+ base::RunLoop run_loop;
+ std::vector<std::string> types;
+ types.push_back(kUninitialized);
+ clipboard_->GetAvailableMimeTypes(
+ mojo::Clipboard::TYPE_COPY_PASTE,
+ base::Bind(&CopyVectorStringAndEndRunloop, &types, &run_loop));
+ run_loop.Run();
+ return types;
+ }
+
+ bool GetDataOfType(const std::string& mime_type, std::string* data) {
+ base::RunLoop run_loop;
+ bool is_null = false;
+ clipboard_->ReadMimeType(
+ mojo::Clipboard::TYPE_COPY_PASTE,
+ mime_type,
+ base::Bind(&CopyStringAndEndRunloop, data, &is_null, &run_loop));
+ run_loop.Run();
+ return !is_null;
+ }
+
+ void SetStringText(const std::string& data) {
+ Array<MimeTypePairPtr> mime_data;
+ MimeTypePairPtr text_data(MimeTypePair::New());
+ text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+ text_data->data = Array<uint8_t>::From(data).Pass();
+ mime_data.push_back(text_data.Pass());
+ clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+ mime_data.Pass());
+ }
+
+ protected:
+ base::ShadowingAtExitManager at_exit_;
+ shell::ShellTestHelper test_helper_;
+
+ ClipboardPtr clipboard_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardStandaloneTest);
+};
+
+TEST_F(ClipboardStandaloneTest, EmptyClipboardOK) {
+ EXPECT_EQ(0ul, GetSequenceNumber());
+ EXPECT_TRUE(GetAvailableFormatMimeTypes().empty());
+ std::string data;
+ EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+TEST_F(ClipboardStandaloneTest, CanReadBackText) {
+ std::string data;
+ EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(0ul, GetSequenceNumber());
+
+ SetStringText(kPlainTextData);
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+}
+
+TEST_F(ClipboardStandaloneTest, CanSetMultipleDataTypesAtOnce) {
+ Array<MimeTypePairPtr> mime_data;
+ MimeTypePairPtr text_data(MimeTypePair::New());
+ text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+ text_data->data = Array<uint8_t>::From(std::string(kPlainTextData)).Pass();
+ mime_data.push_back(text_data.Pass());
+
+ MimeTypePairPtr html_data(MimeTypePair::New());
+ html_data->mime_type = mojo::Clipboard::MIME_TYPE_HTML;
+ html_data->data = Array<uint8_t>::From(std::string(kHtmlData)).Pass();
+ mime_data.push_back(html_data.Pass());
+
+ clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+ mime_data.Pass());
+
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ std::string data;
+ EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+ EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_HTML, &data));
+ EXPECT_EQ(kHtmlData, data);
+}
+
+TEST_F(ClipboardStandaloneTest, CanClearClipboardWithNull) {
+ std::string data;
+ SetStringText(kPlainTextData);
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+
+ Array<MimeTypePairPtr> mime_data;
+ clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+ mime_data.Pass());
+
+ EXPECT_EQ(2ul, GetSequenceNumber());
+ EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+TEST_F(ClipboardStandaloneTest, CanClearClipboardWithZeroArray) {
+ std::string data;
+ SetStringText(kPlainTextData);
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+
+ Array<MimeTypePairPtr> mime_data(0);
+ clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+ mime_data.Pass());
+
+ EXPECT_EQ(2ul, GetSequenceNumber());
+ EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/clipboard/main.cc b/mojo/services/clipboard/main.cc
new file mode 100644
index 0000000..fcc33d1
--- /dev/null
+++ b/mojo/services/clipboard/main.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/at_exit.h"
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/services/clipboard/clipboard_standalone_impl.h"
+
+class Delegate : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::Clipboard> {
+ public:
+ Delegate() {}
+ virtual ~Delegate() {}
+
+ // mojo::ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // mojo::InterfaceFactory<mojo::Clipboard> implementation.
+ virtual void Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::Clipboard> request) override {
+ // TODO(erg): Write native implementations of the clipboard. For now, we
+ // just build a clipboard which doesn't interact with the system.
+ mojo::BindToRequest(new mojo::ClipboardStandaloneImpl(), &request);
+ }
+};
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new Delegate);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/gles2/BUILD.gn b/mojo/services/gles2/BUILD.gn
new file mode 100644
index 0000000..173d7df
--- /dev/null
+++ b/mojo/services/gles2/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_gles2_service
+source_set("gles2") {
+ public_deps = [
+ ":bindings",
+ ]
+ deps = [
+ "//base",
+ "//gpu/command_buffer/service",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ ]
+
+ sources = [
+ "command_buffer_impl.cc",
+ "command_buffer_impl.h",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_gles2_bindings
+mojom("interfaces") {
+ sources = [
+ "command_buffer.mojom",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_gles2_bindings
+source_set("bindings") {
+ sources = [
+ "command_buffer_type_conversions.cc",
+ "command_buffer_type_conversions.h",
+ "mojo_buffer_backing.cc",
+ "mojo_buffer_backing.h",
+ ]
+
+ public_deps = [
+ ":interfaces",
+ ]
+ deps = [
+ "//base",
+ "//gpu/command_buffer/common",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/gles2:interfaces",
+ ]
+}
diff --git a/mojo/services/gles2/DEPS b/mojo/services/gles2/DEPS
new file mode 100644
index 0000000..acd992d
--- /dev/null
+++ b/mojo/services/gles2/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+gpu/command_buffer",
+ "+ui/gfx",
+ "+ui/gl",
+]
diff --git a/mojo/services/gles2/command_buffer.mojom b/mojo/services/gles2/command_buffer.mojom
new file mode 100644
index 0000000..302dcf4
--- /dev/null
+++ b/mojo/services/gles2/command_buffer.mojom
@@ -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.
+
+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): sync points
+};
+
+interface CommandBufferClient {
+ DidDestroy();
+ LostContext(int32 lost_reason); // TODO(piman): enum
+};
+
+}
diff --git a/mojo/services/gles2/command_buffer_impl.cc b/mojo/services/gles2/command_buffer_impl.cc
new file mode 100644
index 0000000..d221750
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_impl.cc
@@ -0,0 +1,204 @@
+// 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/macros.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_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 {
+
+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::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager)
+ : widget_(gfx::kNullAcceleratedWidget),
+ size_(1, 1),
+ share_group_(share_group),
+ mailbox_manager_(mailbox_manager) {
+}
+
+CommandBufferImpl::CommandBufferImpl(
+ gfx::AcceleratedWidget widget,
+ const gfx::Size& size,
+ gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager)
+ : widget_(widget),
+ size_(size),
+ share_group_(share_group),
+ mailbox_manager_(mailbox_manager) {
+}
+
+CommandBufferImpl::~CommandBufferImpl() {
+ client()->DidDestroy();
+ if (decoder_) {
+ bool have_context = decoder_->MakeCurrent();
+ decoder_->Destroy(have_context);
+ }
+}
+
+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) {
+ if (widget_ == gfx::kNullAcceleratedWidget)
+ surface_ = gfx::GLSurface::CreateOffscreenGLSurface(size_);
+ else
+ surface_ = gfx::GLSurface::CreateViewGLSurface(widget_);
+ if (!surface_.get())
+ return false;
+
+ // TODO(piman): virtual contexts, gpu preference.
+ context_ = gfx::GLContext::CreateGLContext(
+ share_group_.get(), 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(mailbox_manager_.get(),
+ 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());
+ decoder_->SetResizeCallback(
+ base::Bind(&CommandBufferImpl::OnResize, base::Unretained(this)));
+
+ 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;
+
+ 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)
+ 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) {
+ if (!context_->MakeCurrent(surface_.get())) {
+ DLOG(WARNING) << "Context lost";
+ client()->LostContext(gpu::error::kUnknown);
+ return;
+ }
+ 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) {
+ 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::OnParseError() {
+ gpu::CommandBuffer::State state = command_buffer_->GetLastState();
+ client()->LostContext(state.context_lost_reason);
+}
+
+void CommandBufferImpl::OnResize(gfx::Size size, float scale_factor) {
+ surface_->Resize(size);
+}
+
+} // namespace mojo
diff --git a/mojo/services/gles2/command_buffer_impl.h b/mojo/services/gles2/command_buffer_impl.h
new file mode 100644
index 0000000..5233e90
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_impl.h
@@ -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.
+
+#ifndef MOJO_SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_
+#define MOJO_SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_
+
+#include "base/macros.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;
+class MailboxManager;
+}
+}
+
+namespace gfx {
+class GLContext;
+class GLShareGroup;
+class GLSurface;
+}
+
+namespace mojo {
+
+class CommandBufferImpl : public InterfaceImpl<CommandBuffer> {
+ public:
+ // Offscreen.
+ CommandBufferImpl(gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager);
+ // Onscreen.
+ CommandBufferImpl(gfx::AcceleratedWidget widget,
+ const gfx::Size& size,
+ gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager);
+ virtual ~CommandBufferImpl();
+
+ 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;
+
+ private:
+ bool DoInitialize(mojo::ScopedSharedBufferHandle shared_state);
+
+ void OnResize(gfx::Size size, float scale_factor);
+
+ void OnParseError();
+
+ 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_refptr<gfx::GLContext> context_;
+ scoped_refptr<gfx::GLSurface> surface_;
+ scoped_refptr<gfx::GLShareGroup> share_group_;
+ scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(CommandBufferImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_
diff --git a/mojo/services/gles2/command_buffer_type_conversions.cc b/mojo/services/gles2/command_buffer_type_conversions.cc
new file mode 100644
index 0000000..a144f88
--- /dev/null
+++ b/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>::Convert(
+ 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<gpu::CommandBuffer::State, CommandBufferStatePtr>::Convert(
+ 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/mojo/services/gles2/command_buffer_type_conversions.h b/mojo/services/gles2/command_buffer_type_conversions.h
new file mode 100644
index 0000000..2579681
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_type_conversions.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_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 <>
+struct TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State> {
+ static CommandBufferStatePtr Convert(const gpu::CommandBuffer::State& input);
+};
+
+template <>
+struct TypeConverter<gpu::CommandBuffer::State, CommandBufferStatePtr> {
+ static gpu::CommandBuffer::State Convert(const CommandBufferStatePtr& input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_
diff --git a/mojo/services/gles2/mojo_buffer_backing.cc b/mojo/services/gles2/mojo_buffer_backing.cc
new file mode 100644
index 0000000..3c90878
--- /dev/null
+++ b/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/mojo/services/gles2/mojo_buffer_backing.h b/mojo/services/gles2/mojo_buffer_backing.h
new file mode 100644
index 0000000..9304df7
--- /dev/null
+++ b/mojo/services/gles2/mojo_buffer_backing.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_SERVICES_GLES2_MOJO_BUFFER_BACKING_H_
+#define MOJO_SERVICES_GLES2_MOJO_BUFFER_BACKING_H_
+
+#include "base/macros.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/mojo/services/html_viewer/BUILD.gn b/mojo/services/html_viewer/BUILD.gn
new file mode 100644
index 0000000..ca20a24
--- /dev/null
+++ b/mojo/services/html_viewer/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_services.gypi:mojo_html_viewer
+shared_library("html_viewer") {
+ output_name = "mojo_html_viewer"
+
+ sources = [
+ "blink_basic_type_converters.cc",
+ "blink_basic_type_converters.h",
+ "blink_input_events_type_converters.cc",
+ "blink_input_events_type_converters.h",
+ "blink_platform_impl.cc",
+ "blink_platform_impl.h",
+ "blink_url_request_type_converters.cc",
+ "blink_url_request_type_converters.h",
+ "html_viewer.cc",
+ "html_document_view.cc",
+ "html_document_view.h",
+ "webclipboard_impl.cc",
+ "webclipboard_impl.h",
+ "webcookiejar_impl.cc",
+ "webcookiejar_impl.h",
+ "webmediaplayer_factory.cc",
+ "webmediaplayer_factory.h",
+ "webmimeregistry_impl.cc",
+ "webmimeregistry_impl.h",
+ "websockethandle_impl.cc",
+ "websockethandle_impl.h",
+ "webstoragenamespace_impl.cc",
+ "webstoragenamespace_impl.h",
+ "webthemeengine_impl.cc",
+ "webthemeengine_impl.h",
+ "webthread_impl.cc",
+ "webthread_impl.h",
+ "weburlloader_impl.cc",
+ "weburlloader_impl.h",
+ "weblayertreeview_impl.cc",
+ "weblayertreeview_impl.h",
+ ]
+
+ include_dirs = [ "third_party/WebKit" ]
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//cc",
+ "//cc/blink",
+ "//cc/surfaces",
+ "//media",
+ "//media/audio",
+ "//media/base",
+ "//media/blink",
+ "//mojo/cc",
+ "//mojo/common",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/utility",
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/cpp/network",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/clipboard",
+ "//mojo/services/public/interfaces/content_handler",
+ "//mojo/services/public/interfaces/gpu",
+ "//mojo/services/public/interfaces/input_events:input_events",
+ "//mojo/services/public/interfaces/navigation",
+ "//mojo/services/public/interfaces/network",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/cpp/surfaces",
+ "//net",
+ "//skia",
+ "//third_party/WebKit/public:blink",
+ "//ui/native_theme",
+ "//url",
+ ]
+}
diff --git a/mojo/services/html_viewer/DEPS b/mojo/services/html_viewer/DEPS
new file mode 100644
index 0000000..ca538ee
--- /dev/null
+++ b/mojo/services/html_viewer/DEPS
@@ -0,0 +1,14 @@
+include_rules = [
+ "+cc",
+ "+media",
+ "+mojo/cc",
+ "+mojo/application",
+ "+mojo/services",
+ "+net/base",
+ "+skia",
+ "+third_party/WebKit/public",
+ "+third_party/skia/include",
+ "+ui/base",
+ "+ui/events",
+ "+ui/native_theme",
+]
diff --git a/mojo/services/html_viewer/blink_basic_type_converters.cc b/mojo/services/html_viewer/blink_basic_type_converters.cc
new file mode 100644
index 0000000..eb52652
--- /dev/null
+++ b/mojo/services/html_viewer/blink_basic_type_converters.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/services/html_viewer/blink_basic_type_converters.h"
+
+#include "mojo/public/cpp/bindings/string.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+
+using blink::WebString;
+
+namespace mojo {
+
+// static
+String TypeConverter<String, WebString>::Convert(const WebString& str) {
+ return String(str.utf8());
+}
+
+// static
+WebString TypeConverter<WebString, String>::Convert(const String& str) {
+ return WebString::fromUTF8(str.get());
+}
+
+// static
+Array<uint8_t> TypeConverter<Array<uint8_t>, blink::WebString>::Convert(
+ const blink::WebString& input) {
+ std::string utf8 = input.utf8();
+ Array<uint8_t> result(utf8.size());
+ for (size_t i = 0; i < utf8.size(); ++i)
+ result[i] = utf8[i];
+ return result.Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/blink_basic_type_converters.h b/mojo/services/html_viewer/blink_basic_type_converters.h
new file mode 100644
index 0000000..60ddeee
--- /dev/null
+++ b/mojo/services/html_viewer/blink_basic_type_converters.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_HTML_VIEWER_BLINK_BASIC_TYPE_CONVERTERS_H_
+#define MOJO_SERVICES_HTML_VIEWER_BLINK_BASIC_TYPE_CONVERTERS_H_
+
+#include "mojo/public/cpp/bindings/type_converter.h"
+
+#include "mojo/public/cpp/bindings/array.h"
+#include "third_party/WebKit/public/platform/WebVector.h"
+
+namespace blink {
+class WebString;
+}
+
+namespace mojo {
+class String;
+
+template<>
+struct TypeConverter<String, blink::WebString> {
+ static String Convert(const blink::WebString& str);
+};
+template<>
+struct TypeConverter<blink::WebString, String> {
+ static blink::WebString Convert(const String& str);
+};
+template <>
+struct TypeConverter<Array<uint8_t>, blink::WebString> {
+ static Array<uint8_t> Convert(const blink::WebString& input);
+};
+
+template<typename T, typename U>
+struct TypeConverter<Array<T>, blink::WebVector<U> > {
+ static Array<T> Convert(const blink::WebVector<U>& vector) {
+ Array<T> array(vector.size());
+ for (size_t i = 0; i < vector.size(); ++i)
+ array[i] = TypeConverter<T, U>::Convert(vector[i]);
+ return array.Pass();
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_BLINK_BASIC_TYPE_CONVERTERS_H_
diff --git a/mojo/services/html_viewer/blink_input_events_type_converters.cc b/mojo/services/html_viewer/blink_input_events_type_converters.cc
new file mode 100644
index 0000000..ff1a2cb
--- /dev/null
+++ b/mojo/services/html_viewer/blink_input_events_type_converters.cc
@@ -0,0 +1,203 @@
+// Copyright 2014 The Chromium 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/html_viewer/blink_input_events_type_converters.h"
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "mojo/services/public/interfaces/input_events/input_event_constants.mojom.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace mojo {
+namespace {
+
+// Used for scrolling. This matches Firefox behavior.
+const int kPixelsPerTick = 53;
+
+int EventFlagsToWebEventModifiers(int flags) {
+ int modifiers = 0;
+
+ if (flags & mojo::EVENT_FLAGS_SHIFT_DOWN)
+ modifiers |= blink::WebInputEvent::ShiftKey;
+ if (flags & mojo::EVENT_FLAGS_CONTROL_DOWN)
+ modifiers |= blink::WebInputEvent::ControlKey;
+ if (flags & mojo::EVENT_FLAGS_ALT_DOWN)
+ modifiers |= blink::WebInputEvent::AltKey;
+ // TODO(beng): MetaKey/META_MASK
+ if (flags & mojo::EVENT_FLAGS_LEFT_MOUSE_BUTTON)
+ modifiers |= blink::WebInputEvent::LeftButtonDown;
+ if (flags & mojo::EVENT_FLAGS_MIDDLE_MOUSE_BUTTON)
+ modifiers |= blink::WebInputEvent::MiddleButtonDown;
+ if (flags & mojo::EVENT_FLAGS_RIGHT_MOUSE_BUTTON)
+ modifiers |= blink::WebInputEvent::RightButtonDown;
+ if (flags & mojo::EVENT_FLAGS_CAPS_LOCK_DOWN)
+ modifiers |= blink::WebInputEvent::CapsLockOn;
+ return modifiers;
+}
+
+int EventFlagsToWebInputEventModifiers(int flags) {
+ return
+ (flags & mojo::EVENT_FLAGS_SHIFT_DOWN ?
+ blink::WebInputEvent::ShiftKey : 0) |
+ (flags & mojo::EVENT_FLAGS_CONTROL_DOWN ?
+ blink::WebInputEvent::ControlKey : 0) |
+ (flags & mojo::EVENT_FLAGS_CAPS_LOCK_DOWN ?
+ blink::WebInputEvent::CapsLockOn : 0) |
+ (flags & mojo::EVENT_FLAGS_ALT_DOWN ?
+ blink::WebInputEvent::AltKey : 0);
+}
+
+int GetClickCount(int flags) {
+ if (flags & mojo::MOUSE_EVENT_FLAGS_IS_TRIPLE_CLICK)
+ return 3;
+ else if (flags & mojo::MOUSE_EVENT_FLAGS_IS_DOUBLE_CLICK)
+ return 2;
+
+ return 1;
+}
+
+scoped_ptr<blink::WebInputEvent> BuildWebMouseEventFrom(const EventPtr& event) {
+ scoped_ptr<blink::WebMouseEvent> web_event(new blink::WebMouseEvent);
+ web_event->x = event->location_data->in_view_location->x;
+ web_event->y = event->location_data->in_view_location->y;
+
+ // TODO(erg): Remove this if check once we can rely on screen_location
+ // actually being passed to us. As written today, getting the screen
+ // location from ui::Event objects can only be done by querying the
+ // underlying native events, so all synthesized events don't have screen
+ // locations.
+ if (!event->location_data->screen_location.is_null()) {
+ web_event->globalX = event->location_data->screen_location->x;
+ web_event->globalY = event->location_data->screen_location->y;
+ }
+
+ web_event->modifiers = EventFlagsToWebEventModifiers(event->flags);
+ web_event->timeStampSeconds =
+ base::TimeDelta::FromInternalValue(event->time_stamp).InSecondsF();
+
+ web_event->button = blink::WebMouseEvent::ButtonNone;
+ if (event->flags & mojo::EVENT_FLAGS_LEFT_MOUSE_BUTTON)
+ web_event->button = blink::WebMouseEvent::ButtonLeft;
+ if (event->flags & mojo::EVENT_FLAGS_MIDDLE_MOUSE_BUTTON)
+ web_event->button = blink::WebMouseEvent::ButtonMiddle;
+ if (event->flags & mojo::EVENT_FLAGS_RIGHT_MOUSE_BUTTON)
+ web_event->button = blink::WebMouseEvent::ButtonRight;
+
+ switch (event->action) {
+ case EVENT_TYPE_MOUSE_PRESSED:
+ web_event->type = blink::WebInputEvent::MouseDown;
+ break;
+ case EVENT_TYPE_MOUSE_RELEASED:
+ web_event->type = blink::WebInputEvent::MouseUp;
+ break;
+ case EVENT_TYPE_MOUSE_ENTERED:
+ web_event->type = blink::WebInputEvent::MouseLeave;
+ web_event->button = blink::WebMouseEvent::ButtonNone;
+ break;
+ case EVENT_TYPE_MOUSE_EXITED:
+ case EVENT_TYPE_MOUSE_MOVED:
+ case EVENT_TYPE_MOUSE_DRAGGED:
+ web_event->type = blink::WebInputEvent::MouseMove;
+ break;
+ default:
+ NOTIMPLEMENTED() << "Received unexpected event: " << event->action;
+ break;
+ }
+
+ web_event->clickCount = GetClickCount(event->flags);
+
+ return web_event.PassAs<blink::WebInputEvent>();
+}
+
+scoped_ptr<blink::WebInputEvent> BuildWebKeyboardEvent(
+ const EventPtr& event) {
+ scoped_ptr<blink::WebKeyboardEvent> web_event(new blink::WebKeyboardEvent);
+
+ web_event->modifiers = EventFlagsToWebInputEventModifiers(event->flags);
+ web_event->timeStampSeconds =
+ base::TimeDelta::FromInternalValue(event->time_stamp).InSecondsF();
+
+ switch (event->action) {
+ case EVENT_TYPE_KEY_PRESSED:
+ web_event->type = event->key_data->is_char ? blink::WebInputEvent::Char :
+ blink::WebInputEvent::RawKeyDown;
+ break;
+ case EVENT_TYPE_KEY_RELEASED:
+ web_event->type = blink::WebInputEvent::KeyUp;
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ if (web_event->modifiers & blink::WebInputEvent::AltKey)
+ web_event->isSystemKey = true;
+
+ web_event->windowsKeyCode = event->key_data->windows_key_code;
+ web_event->nativeKeyCode = event->key_data->native_key_code;
+ web_event->text[0] = event->key_data->text;
+ web_event->unmodifiedText[0] = event->key_data->unmodified_text;
+
+ web_event->setKeyIdentifierFromWindowsKeyCode();
+ return web_event.PassAs<blink::WebInputEvent>();
+}
+
+scoped_ptr<blink::WebInputEvent> BuildWebMouseWheelEventFrom(
+ const EventPtr& event) {
+ scoped_ptr<blink::WebMouseWheelEvent> web_event(
+ new blink::WebMouseWheelEvent);
+ web_event->type = blink::WebInputEvent::MouseWheel;
+ web_event->button = blink::WebMouseEvent::ButtonNone;
+ web_event->modifiers = EventFlagsToWebEventModifiers(event->flags);
+ web_event->timeStampSeconds =
+ base::TimeDelta::FromInternalValue(event->time_stamp).InSecondsF();
+
+ web_event->x = event->location_data->in_view_location->x;
+ web_event->y = event->location_data->in_view_location->y;
+
+ // TODO(erg): Remove this null check as parallel to above.
+ if (!event->location_data->screen_location.is_null()) {
+ web_event->globalX = event->location_data->screen_location->x;
+ web_event->globalY = event->location_data->screen_location->y;
+ }
+
+ if ((event->flags & mojo::EVENT_FLAGS_SHIFT_DOWN) != 0 &&
+ event->wheel_data->x_offset == 0) {
+ web_event->deltaX = event->wheel_data->y_offset;
+ web_event->deltaY = 0;
+ } else {
+ web_event->deltaX = event->wheel_data->x_offset;
+ web_event->deltaY = event->wheel_data->y_offset;
+ }
+
+ web_event->wheelTicksX = web_event->deltaX / kPixelsPerTick;
+ web_event->wheelTicksY = web_event->deltaY / kPixelsPerTick;
+
+ return web_event.PassAs<blink::WebInputEvent>();
+}
+
+} // namespace
+
+// static
+scoped_ptr<blink::WebInputEvent>
+TypeConverter<scoped_ptr<blink::WebInputEvent>, EventPtr>::Convert(
+ const EventPtr& event) {
+ if (event->action == EVENT_TYPE_MOUSE_PRESSED ||
+ event->action == EVENT_TYPE_MOUSE_RELEASED ||
+ event->action == EVENT_TYPE_MOUSE_ENTERED ||
+ event->action == EVENT_TYPE_MOUSE_EXITED ||
+ event->action == EVENT_TYPE_MOUSE_MOVED ||
+ event->action == EVENT_TYPE_MOUSE_DRAGGED) {
+ return BuildWebMouseEventFrom(event);
+ } else if ((event->action == EVENT_TYPE_KEY_PRESSED ||
+ event->action == EVENT_TYPE_KEY_RELEASED) &&
+ event->key_data) {
+ return BuildWebKeyboardEvent(event);
+ } else if (event->action == EVENT_TYPE_MOUSEWHEEL) {
+ return BuildWebMouseWheelEventFrom(event);
+ }
+
+ return scoped_ptr<blink::WebInputEvent>();
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/blink_input_events_type_converters.h b/mojo/services/html_viewer/blink_input_events_type_converters.h
new file mode 100644
index 0000000..3d5860d
--- /dev/null
+++ b/mojo/services/html_viewer/blink_input_events_type_converters.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_SERVICES_HTML_VIEWER_BLINK_INPUT_EVENTS_TYPE_CONVERTERS_H_
+#define MOJO_SERVICES_HTML_VIEWER_BLINK_INPUT_EVENTS_TYPE_CONVERTERS_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+
+namespace blink {
+class WebInputEvent;
+}
+
+namespace mojo {
+
+template <>
+struct TypeConverter<scoped_ptr<blink::WebInputEvent>, EventPtr> {
+ static scoped_ptr<blink::WebInputEvent> Convert(const EventPtr& input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_BLINK_INPUT_EVENTS_TYPE_CONVERTERS_H_
diff --git a/mojo/services/html_viewer/blink_platform_impl.cc b/mojo/services/html_viewer/blink_platform_impl.cc
new file mode 100644
index 0000000..8a7555b
--- /dev/null
+++ b/mojo/services/html_viewer/blink_platform_impl.cc
@@ -0,0 +1,251 @@
+// Copyright 2014 The Chromium 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/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/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/html_viewer/webclipboard_impl.h"
+#include "mojo/services/html_viewer/webcookiejar_impl.h"
+#include "mojo/services/html_viewer/websockethandle_impl.h"
+#include "mojo/services/html_viewer/webthread_impl.h"
+#include "mojo/services/html_viewer/weburlloader_impl.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 {
+
+// 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(ApplicationImpl* 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->ConnectToService("mojo:mojo_network_service", &network_service_);
+
+ CookieStorePtr cookie_store;
+ network_service_->GetCookieStore(Get(&cookie_store));
+ cookie_jar_.reset(new WebCookieJarImpl(cookie_store.Pass()));
+
+ ClipboardPtr clipboard;
+ app->ConnectToService("mojo:mojo_clipboard", &clipboard);
+ clipboard_.reset(new WebClipboardImpl(clipboard.Pass()));
+}
+
+BlinkPlatformImpl::~BlinkPlatformImpl() {
+}
+
+blink::WebCookieJar* BlinkPlatformImpl::cookieJar() {
+ return cookie_jar_.get();
+}
+
+blink::WebClipboard* BlinkPlatformImpl::clipboard() {
+ return clipboard_.get();
+}
+
+blink::WebMimeRegistry* BlinkPlatformImpl::mimeRegistry() {
+ return &mime_registry_;
+}
+
+blink::WebThemeEngine* BlinkPlatformImpl::themeEngine() {
+ return &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));
+}
+
+bool BlinkPlatformImpl::isThreadedCompositingEnabled() {
+ return true;
+}
+
+blink::WebCompositorSupport* BlinkPlatformImpl::compositorSupport() {
+ return &compositor_support_;
+}
+
+blink::WebScrollbarBehavior* BlinkPlatformImpl::scrollbarBehavior() {
+ return &scrollbar_behavior_;
+}
+
+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::WebSocketHandle* BlinkPlatformImpl::createWebSocketHandle() {
+ return new WebSocketHandleImpl(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;
+}
+
+void BlinkPlatformImpl::yieldCurrentThread() {
+ base::PlatformThread::YieldCurrentThread();
+}
+
+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 mojo
diff --git a/mojo/services/html_viewer/blink_platform_impl.h b/mojo/services/html_viewer/blink_platform_impl.h
new file mode 100644
index 0000000..9abd336
--- /dev/null
+++ b/mojo/services/html_viewer/blink_platform_impl.h
@@ -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.
+
+#ifndef MOJO_SERVICES_HTML_VIEWER_BLINK_PLATFORM_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_BLINK_PLATFORM_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread_local_storage.h"
+#include "base/timer/timer.h"
+#include "cc/blink/web_compositor_support_impl.h"
+#include "mojo/services/html_viewer/webmimeregistry_impl.h"
+#include "mojo/services/html_viewer/webthemeengine_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/WebScrollbarBehavior.h"
+
+namespace mojo {
+class ApplicationImpl;
+class WebClipboardImpl;
+class WebCookieJarImpl;
+
+class BlinkPlatformImpl : public blink::Platform {
+ public:
+ explicit BlinkPlatformImpl(ApplicationImpl* app);
+ virtual ~BlinkPlatformImpl();
+
+ // blink::Platform methods:
+ virtual blink::WebCookieJar* cookieJar();
+ virtual blink::WebClipboard* clipboard();
+ 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 bool isThreadedCompositingEnabled();
+ virtual blink::WebCompositorSupport* compositorSupport();
+ virtual blink::WebURLLoader* createURLLoader();
+ virtual blink::WebSocketHandle* createWebSocketHandle();
+ 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 void yieldCurrentThread();
+ virtual blink::WebWaitableEvent* createWaitableEvent();
+ virtual blink::WebWaitableEvent* waitMultipleEvents(
+ const blink::WebVector<blink::WebWaitableEvent*>& events);
+ virtual blink::WebScrollbarBehavior* scrollbarBehavior();
+ 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_;
+ cc_blink::WebCompositorSupportImpl compositor_support_;
+ WebThemeEngineImpl theme_engine_;
+ scoped_ptr<WebCookieJarImpl> cookie_jar_;
+ scoped_ptr<WebClipboardImpl> clipboard_;
+ WebMimeRegistryImpl mime_registry_;
+ blink::WebScrollbarBehavior scrollbar_behavior_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlinkPlatformImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_BLINK_PLATFORM_IMPL_H_
diff --git a/mojo/services/html_viewer/blink_url_request_type_converters.cc b/mojo/services/html_viewer/blink_url_request_type_converters.cc
new file mode 100644
index 0000000..9428d92
--- /dev/null
+++ b/mojo/services/html_viewer/blink_url_request_type_converters.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/services/html_viewer/blink_url_request_type_converters.h"
+
+#include "base/strings/string_util.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "third_party/WebKit/public/platform/WebHTTPHeaderVisitor.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+
+namespace mojo {
+namespace {
+
+// Ripped from web_url_loader_impl.cc.
+class HeaderFlattener : public blink::WebHTTPHeaderVisitor {
+ public:
+ HeaderFlattener() : has_accept_header_(false) {}
+
+ virtual void visitHeader(const blink::WebString& name,
+ const blink::WebString& value) {
+ // Headers are latin1.
+ const std::string& name_latin1 = name.latin1();
+ const std::string& value_latin1 = value.latin1();
+
+ // Skip over referrer headers found in the header map because we already
+ // pulled it out as a separate parameter.
+ if (LowerCaseEqualsASCII(name_latin1, "referer"))
+ return;
+
+ if (LowerCaseEqualsASCII(name_latin1, "accept"))
+ has_accept_header_ = true;
+
+ buffer_.push_back(name_latin1 + ": " + value_latin1);
+ }
+
+ Array<String> GetBuffer() {
+ // In some cases, WebKit doesn't add an Accept header, but not having the
+ // header confuses some web servers. See bug 808613.
+ if (!has_accept_header_) {
+ buffer_.push_back("Accept: */*");
+ has_accept_header_ = true;
+ }
+ return buffer_.Pass();
+ }
+
+ private:
+ Array<String> buffer_;
+ bool has_accept_header_;
+};
+
+void AddRequestBody(URLRequest* url_request,
+ const blink::WebURLRequest& request) {
+ if (request.httpBody().isNull())
+ return;
+
+ uint32_t i = 0;
+ blink::WebHTTPBody::Element element;
+ while (request.httpBody().elementAt(i++, element)) {
+ switch (element.type) {
+ case blink::WebHTTPBody::Element::TypeData:
+ if (!element.data.isEmpty()) {
+ // WebKit sometimes gives up empty data to append. These aren't
+ // necessary so we just optimize those out here.
+ uint32_t num_bytes = static_cast<uint32_t>(element.data.size());
+ MojoCreateDataPipeOptions options;
+ options.struct_size = sizeof(MojoCreateDataPipeOptions);
+ options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = num_bytes;
+ DataPipe data_pipe(options);
+ url_request->body.push_back(
+ data_pipe.consumer_handle.Pass());
+ WriteDataRaw(data_pipe.producer_handle.get(),
+ element.data.data(),
+ &num_bytes,
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+ }
+ break;
+ case blink::WebHTTPBody::Element::TypeFile:
+ case blink::WebHTTPBody::Element::TypeFileSystemURL:
+ case blink::WebHTTPBody::Element::TypeBlob:
+ // TODO(mpcomplete): handle these.
+ NOTIMPLEMENTED();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+}
+
+} // namespace
+
+URLRequestPtr TypeConverter<URLRequestPtr, blink::WebURLRequest>::Convert(
+ const blink::WebURLRequest& request) {
+ URLRequestPtr url_request(URLRequest::New());
+ url_request->url = request.url().string().utf8();
+ url_request->method = request.httpMethod().utf8();
+
+ HeaderFlattener flattener;
+ request.visitHTTPHeaderFields(&flattener);
+ url_request->headers = flattener.GetBuffer().Pass();
+
+ AddRequestBody(url_request.get(), request);
+
+ return url_request.Pass();
+}
+
+} // namespace mojo
+
diff --git a/mojo/services/html_viewer/blink_url_request_type_converters.h b/mojo/services/html_viewer/blink_url_request_type_converters.h
new file mode 100644
index 0000000..583d0ba
--- /dev/null
+++ b/mojo/services/html_viewer/blink_url_request_type_converters.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_SERVICES_HTML_VIEWER_BLINK_URL_REQUEST_TYPE_CONVERTERS_H_
+#define MOJO_SERVICES_HTML_VIEWER_BLINK_URL_REQUEST_TYPE_CONVERTERS_H_
+
+#include "mojo/services/public/interfaces/network/url_loader.mojom.h"
+
+namespace blink {
+class WebURLRequest;
+}
+
+namespace mojo {
+
+template <>
+struct TypeConverter<URLRequestPtr, blink::WebURLRequest> {
+ static URLRequestPtr Convert(const blink::WebURLRequest& request);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_BLINK_URL_REQUEST_TYPE_CONVERTERS_H_
diff --git a/mojo/services/html_viewer/html_document_view.cc b/mojo/services/html_viewer/html_document_view.cc
new file mode 100644
index 0000000..7056501
--- /dev/null
+++ b/mojo/services/html_viewer/html_document_view.cc
@@ -0,0 +1,260 @@
+// Copyright 2014 The Chromium 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/html_viewer/html_document_view.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/html_viewer/blink_input_events_type_converters.h"
+#include "mojo/services/html_viewer/blink_url_request_type_converters.h"
+#include "mojo/services/html_viewer/weblayertreeview_impl.h"
+#include "mojo/services/html_viewer/webmediaplayer_factory.h"
+#include "mojo/services/html_viewer/webstoragenamespace_impl.h"
+#include "mojo/services/html_viewer/weburlloader_impl.h"
+#include "mojo/services/public/cpp/view_manager/view.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "skia/ext/refptr.h"
+#include "third_party/WebKit/public/platform/Platform.h"
+#include "third_party/WebKit/public/platform/WebHTTPHeaderVisitor.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/WebInputEvent.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 {
+
+void ConfigureSettings(blink::WebSettings* settings) {
+ settings->setCookieEnabled(true);
+ settings->setDefaultFixedFontSize(13);
+ settings->setDefaultFontSize(16);
+ settings->setLoadsImagesAutomatically(true);
+ settings->setJavaScriptEnabled(true);
+}
+
+Target WebNavigationPolicyToNavigationTarget(
+ blink::WebNavigationPolicy policy) {
+ switch (policy) {
+ case blink::WebNavigationPolicyCurrentTab:
+ return TARGET_SOURCE_NODE;
+ case blink::WebNavigationPolicyNewBackgroundTab:
+ case blink::WebNavigationPolicyNewForegroundTab:
+ case blink::WebNavigationPolicyNewWindow:
+ case blink::WebNavigationPolicyNewPopup:
+ return TARGET_NEW_NODE;
+ default:
+ return TARGET_DEFAULT;
+ }
+}
+
+bool CanNavigateLocally(blink::WebFrame* frame,
+ const blink::WebURLRequest& request) {
+ // For now, we just load child frames locally.
+ // TODO(aa): In the future, this should use embedding to connect to a
+ // different instance of Blink if the frame is cross-origin.
+ if (frame->parent())
+ return true;
+
+ // If we have extraData() it means we already have the url response
+ // (presumably because we are being called via Navigate()). In that case we
+ // can go ahead and navigate locally.
+ if (request.extraData())
+ return true;
+
+ // Otherwise we don't know if we're the right app to handle this request. Ask
+ // host to do the navigation for us.
+ return false;
+}
+
+} // namespace
+
+HTMLDocumentView::HTMLDocumentView(
+ URLResponsePtr response,
+ InterfaceRequest<ServiceProvider> service_provider_request,
+ Shell* shell,
+ scoped_refptr<base::MessageLoopProxy> compositor_thread,
+ WebMediaPlayerFactory* web_media_player_factory)
+ : shell_(shell),
+ web_view_(NULL),
+ root_(NULL),
+ view_manager_client_factory_(shell, this),
+ compositor_thread_(compositor_thread),
+ web_media_player_factory_(web_media_player_factory),
+ weak_factory_(this) {
+ ServiceProviderImpl* exported_services = new ServiceProviderImpl();
+ exported_services->AddService(&view_manager_client_factory_);
+ BindToRequest(exported_services, &service_provider_request);
+ Load(response.Pass());
+}
+
+HTMLDocumentView::~HTMLDocumentView() {
+ if (web_view_)
+ web_view_->close();
+ if (root_)
+ root_->RemoveObserver(this);
+}
+
+void HTMLDocumentView::OnEmbed(
+ ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* embedee_service_provider_impl,
+ scoped_ptr<ServiceProvider> embedder_service_provider) {
+ root_ = root;
+ embedder_service_provider_ = embedder_service_provider.Pass();
+ navigator_host_.set_service_provider(embedder_service_provider_.get());
+
+ web_view_->resize(root_->bounds().size());
+ web_layer_tree_view_impl_->setViewportSize(root_->bounds().size());
+ web_layer_tree_view_impl_->set_view(root_);
+ root_->AddObserver(this);
+}
+
+void HTMLDocumentView::OnViewManagerDisconnected(ViewManager* view_manager) {
+ // TODO(aa): Need to figure out how shutdown works.
+}
+
+void HTMLDocumentView::Load(URLResponsePtr response) {
+ web_view_ = blink::WebView::create(this);
+ web_layer_tree_view_impl_->set_widget(web_view_);
+ ConfigureSettings(web_view_->settings());
+ web_view_->setMainFrame(blink::WebLocalFrame::create(this));
+
+ GURL url(response->url);
+
+ WebURLRequestExtraData* extra_data = new WebURLRequestExtraData;
+ extra_data->synthetic_response = response.Pass();
+
+ blink::WebURLRequest web_request;
+ web_request.initialize();
+ web_request.setURL(url);
+ web_request.setExtraData(extra_data);
+
+ web_view_->mainFrame()->loadRequest(web_request);
+}
+
+blink::WebStorageNamespace* HTMLDocumentView::createSessionStorageNamespace() {
+ return new WebStorageNamespaceImpl();
+}
+
+void HTMLDocumentView::initializeLayerTreeView() {
+ ServiceProviderPtr surfaces_service_provider;
+ shell_->ConnectToApplication("mojo:mojo_surfaces_service",
+ Get(&surfaces_service_provider));
+ InterfacePtr<SurfacesService> surfaces_service;
+ ConnectToService(surfaces_service_provider.get(), &surfaces_service);
+
+ ServiceProviderPtr gpu_service_provider;
+ // TODO(jamesr): Should be mojo:mojo_gpu_service
+ shell_->ConnectToApplication("mojo:mojo_native_viewport_service",
+ Get(&gpu_service_provider));
+ InterfacePtr<Gpu> gpu_service;
+ ConnectToService(gpu_service_provider.get(), &gpu_service);
+ web_layer_tree_view_impl_.reset(new WebLayerTreeViewImpl(
+ compositor_thread_, surfaces_service.Pass(), gpu_service.Pass()));
+}
+
+blink::WebLayerTreeView* HTMLDocumentView::layerTreeView() {
+ return web_layer_tree_view_impl_.get();
+}
+
+blink::WebMediaPlayer* HTMLDocumentView::createMediaPlayer(
+ blink::WebLocalFrame* frame,
+ const blink::WebURL& url,
+ blink::WebMediaPlayerClient* client) {
+ return web_media_player_factory_->CreateMediaPlayer(frame, url, client);
+}
+
+blink::WebMediaPlayer* HTMLDocumentView::createMediaPlayer(
+ blink::WebLocalFrame* frame,
+ const blink::WebURL& url,
+ blink::WebMediaPlayerClient* client,
+ blink::WebContentDecryptionModule* initial_cdm) {
+ return createMediaPlayer(frame, url, client);
+}
+
+blink::WebFrame* HTMLDocumentView::createChildFrame(
+ blink::WebLocalFrame* parent,
+ const blink::WebString& frameName) {
+ blink::WebLocalFrame* web_frame = blink::WebLocalFrame::create(this);
+ parent->appendChild(web_frame);
+ return web_frame;
+}
+
+void HTMLDocumentView::frameDetached(blink::WebFrame* frame) {
+ if (frame->parent())
+ frame->parent()->removeChild(frame);
+
+ // |frame| is invalid after here.
+ frame->close();
+}
+
+blink::WebCookieJar* HTMLDocumentView::cookieJar(blink::WebLocalFrame* frame) {
+ // TODO(darin): Blink does not fallback to the Platform provided WebCookieJar.
+ // Either it should, as it once did, or we should find another solution here.
+ return blink::Platform::current()->cookieJar();
+}
+
+blink::WebNavigationPolicy HTMLDocumentView::decidePolicyForNavigation(
+ blink::WebLocalFrame* frame, blink::WebDataSource::ExtraData* data,
+ const blink::WebURLRequest& request, blink::WebNavigationType nav_type,
+ blink::WebNavigationPolicy default_policy, bool is_redirect) {
+ if (CanNavigateLocally(frame, request))
+ return default_policy;
+
+ navigator_host_->RequestNavigate(
+ WebNavigationPolicyToNavigationTarget(default_policy),
+ URLRequest::From(request).Pass());
+
+ return blink::WebNavigationPolicyIgnore;
+}
+
+void HTMLDocumentView::didAddMessageToConsole(
+ const blink::WebConsoleMessage& message,
+ const blink::WebString& source_name,
+ unsigned source_line,
+ const blink::WebString& stack_trace) {
+}
+
+void HTMLDocumentView::didNavigateWithinPage(
+ blink::WebLocalFrame* frame, const blink::WebHistoryItem& history_item,
+ blink::WebHistoryCommitType commit_type) {
+ navigator_host_->DidNavigateLocally(history_item.urlString().utf8());
+}
+
+void HTMLDocumentView::OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ DCHECK_EQ(view, root_);
+ web_view_->resize(view->bounds().size());
+}
+
+void HTMLDocumentView::OnViewDestroyed(View* view) {
+ DCHECK_EQ(view, root_);
+ view->RemoveObserver(this);
+ root_ = NULL;
+}
+
+void HTMLDocumentView::OnViewInputEvent(View* view, const EventPtr& event) {
+ scoped_ptr<blink::WebInputEvent> web_event =
+ event.To<scoped_ptr<blink::WebInputEvent> >();
+ if (web_event)
+ web_view_->handleInputEvent(*web_event);
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/html_document_view.h b/mojo/services/html_viewer/html_document_view.h
new file mode 100644
index 0000000..e2407bd
--- /dev/null
+++ b/mojo/services/html_viewer/html_document_view.h
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium 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_HTML_VIEWER_HTML_DOCUMENT_VIEW_H_
+#define MOJO_SERVICES_HTML_VIEWER_HTML_DOCUMENT_VIEW_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/application/lazy_interface_ptr.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_client_factory.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/content_handler/content_handler.mojom.h"
+#include "mojo/services/public/interfaces/navigation/navigation.mojom.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 base {
+class MessageLoopProxy;
+}
+
+namespace mojo {
+
+class WebMediaPlayerFactory;
+class ViewManager;
+class View;
+class WebLayerTreeViewImpl;
+
+// A view for a single HTML document.
+class HTMLDocumentView : public blink::WebViewClient,
+ public blink::WebFrameClient,
+ public ViewManagerDelegate,
+ public ViewObserver {
+ public:
+ // Load a new HTMLDocument with |response|.
+ //
+ // |service_provider_request| should be used to implement a
+ // ServiceProvider which exposes services to the connecting application.
+ // Commonly, the connecting application is the ViewManager and it will
+ // request ViewManagerClient.
+ //
+ // |shell| is the Shell connection for this mojo::Application.
+ HTMLDocumentView(URLResponsePtr response,
+ InterfaceRequest<ServiceProvider> service_provider_request,
+ Shell* shell,
+ scoped_refptr<base::MessageLoopProxy> compositor_thread,
+ WebMediaPlayerFactory* web_media_player_factory);
+ virtual ~HTMLDocumentView();
+
+ private:
+ // WebViewClient methods:
+ virtual blink::WebStorageNamespace* createSessionStorageNamespace();
+
+ // WebWidgetClient methods:
+ virtual void initializeLayerTreeView();
+ virtual blink::WebLayerTreeView* layerTreeView();
+
+ // WebFrameClient methods:
+ virtual blink::WebMediaPlayer* createMediaPlayer(
+ blink::WebLocalFrame* frame,
+ const blink::WebURL& url,
+ blink::WebMediaPlayerClient* client);
+ virtual blink::WebMediaPlayer* createMediaPlayer(
+ blink::WebLocalFrame* frame,
+ const blink::WebURL& url,
+ blink::WebMediaPlayerClient* client,
+ blink::WebContentDecryptionModule* initial_cdm);
+ virtual blink::WebFrame* createChildFrame(blink::WebLocalFrame* parent,
+ const blink::WebString& frameName);
+ virtual void frameDetached(blink::WebFrame*);
+ virtual blink::WebCookieJar* cookieJar(blink::WebLocalFrame* frame);
+ virtual blink::WebNavigationPolicy decidePolicyForNavigation(
+ blink::WebLocalFrame* frame, blink::WebDataSource::ExtraData* data,
+ const blink::WebURLRequest& request, blink::WebNavigationType nav_type,
+ blink::WebNavigationPolicy default_policy, bool isRedirect);
+ virtual void didAddMessageToConsole(
+ const blink::WebConsoleMessage& message,
+ const blink::WebString& source_name,
+ unsigned source_line,
+ const blink::WebString& stack_trace);
+ virtual void didNavigateWithinPage(
+ blink::WebLocalFrame* frame,
+ const blink::WebHistoryItem& history_item,
+ blink::WebHistoryCommitType commit_type);
+
+ // ViewManagerDelegate methods:
+ virtual void OnEmbed(
+ ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* embedee_service_provider_impl,
+ scoped_ptr<ServiceProvider> embedder_service_provider) override;
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override;
+
+ // ViewObserver methods:
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+ virtual void OnViewDestroyed(View* view) override;
+ virtual void OnViewInputEvent(View* view, const EventPtr& event) override;
+
+ void Load(URLResponsePtr response);
+
+ URLResponsePtr response_;
+ scoped_ptr<ServiceProvider> embedder_service_provider_;
+ Shell* shell_;
+ LazyInterfacePtr<NavigatorHost> navigator_host_;
+ blink::WebView* web_view_;
+ View* root_;
+ ViewManagerClientFactory view_manager_client_factory_;
+ scoped_ptr<WebLayerTreeViewImpl> web_layer_tree_view_impl_;
+ scoped_refptr<base::MessageLoopProxy> compositor_thread_;
+ WebMediaPlayerFactory* web_media_player_factory_;
+
+ base::WeakPtrFactory<HTMLDocumentView> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(HTMLDocumentView);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_HTML_DOCUMENT_VIEW_H_
diff --git a/mojo/services/html_viewer/html_viewer.cc b/mojo/services/html_viewer/html_viewer.cc
new file mode 100644
index 0000000..ebde85f
--- /dev/null
+++ b/mojo/services/html_viewer/html_viewer.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 "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/services/html_viewer/blink_platform_impl.h"
+#include "mojo/services/html_viewer/html_document_view.h"
+#include "mojo/services/html_viewer/webmediaplayer_factory.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+#include "third_party/WebKit/public/web/WebKit.h"
+
+#if !defined(COMPONENT_BUILD)
+#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"
+#endif
+
+namespace mojo {
+
+class HTMLViewer;
+
+class ContentHandlerImpl : public InterfaceImpl<ContentHandler> {
+ public:
+ ContentHandlerImpl(Shell* shell,
+ scoped_refptr<base::MessageLoopProxy> compositor_thread,
+ WebMediaPlayerFactory* web_media_player_factory)
+ : shell_(shell),
+ compositor_thread_(compositor_thread),
+ web_media_player_factory_(web_media_player_factory) {}
+ virtual ~ContentHandlerImpl() {}
+
+ private:
+ // Overridden from ContentHandler:
+ virtual void OnConnect(
+ const mojo::String& url,
+ URLResponsePtr response,
+ InterfaceRequest<ServiceProvider> service_provider_request) override {
+ new HTMLDocumentView(response.Pass(),
+ service_provider_request.Pass(),
+ shell_,
+ compositor_thread_,
+ web_media_player_factory_);
+ }
+
+ Shell* shell_;
+ scoped_refptr<base::MessageLoopProxy> compositor_thread_;
+ WebMediaPlayerFactory* web_media_player_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
+};
+
+class HTMLViewer : public ApplicationDelegate,
+ public InterfaceFactory<ContentHandler> {
+ public:
+ HTMLViewer() : compositor_thread_("compositor thread") {}
+
+ virtual ~HTMLViewer() { blink::shutdown(); }
+
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ shell_ = app->shell();
+ blink_platform_impl_.reset(new BlinkPlatformImpl(app));
+ blink::initialize(blink_platform_impl_.get());
+#if !defined(COMPONENT_BUILD)
+ 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);
+#endif
+
+ compositor_thread_.Start();
+ web_media_player_factory_.reset(new WebMediaPlayerFactory(
+ compositor_thread_.message_loop_proxy()));
+ }
+
+ virtual bool ConfigureIncomingConnection(ApplicationConnection* connection)
+ override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // Overridden from InterfaceFactory<ContentHandler>
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<ContentHandler> request) override {
+ BindToRequest(
+ new ContentHandlerImpl(shell_, compositor_thread_.message_loop_proxy(),
+ web_media_player_factory_.get()),
+ &request);
+ }
+
+ scoped_ptr<BlinkPlatformImpl> blink_platform_impl_;
+ Shell* shell_;
+ base::Thread compositor_thread_;
+ scoped_ptr<WebMediaPlayerFactory> web_media_player_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HTMLViewer);
+};
+
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::HTMLViewer);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/html_viewer/webclipboard_impl.cc b/mojo/services/html_viewer/webclipboard_impl.cc
new file mode 100644
index 0000000..94b16ca
--- /dev/null
+++ b/mojo/services/html_viewer/webclipboard_impl.cc
@@ -0,0 +1,222 @@
+// Copyright 2014 The Chromium 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/html_viewer/webclipboard_impl.h"
+
+#include "base/bind.h"
+#include "mojo/services/html_viewer/blink_basic_type_converters.h"
+
+namespace mojo {
+namespace {
+
+void CopyUint64(uint64_t* output, uint64_t input) {
+ *output = input;
+}
+
+void CopyWebString(blink::WebString* output,
+ const mojo::Array<uint8_t>& input) {
+ // blink does not differentiate between the requested data type not existing
+ // and the empty string.
+ if (input.is_null()) {
+ output->reset();
+ } else {
+ *output = blink::WebString::fromUTF8(
+ reinterpret_cast<const char*>(&input.front()),
+ input.size());
+ }
+}
+
+void CopyURL(blink::WebURL* pageURL,
+ const mojo::Array<uint8_t>& input) {
+ if (input.is_null()) {
+ *pageURL = blink::WebURL();
+ } else {
+ *pageURL = GURL(std::string(reinterpret_cast<const char*>(&input.front()),
+ input.size()));
+ }
+}
+
+void CopyVectorString(std::vector<std::string>* output,
+ const Array<String>& input) {
+ *output = input.To<std::vector<std::string> >();
+}
+
+template <typename T, typename U>
+bool Contains(const std::vector<T>& v, const U& item) {
+ return std::find(v.begin(), v.end(), item) != v.end();
+}
+
+const char kMimeTypeWebkitSmartPaste[] = "chromium/x-webkit-paste";
+
+} // namespace
+
+WebClipboardImpl::WebClipboardImpl(ClipboardPtr clipboard)
+ : clipboard_(clipboard.Pass()) {
+}
+
+WebClipboardImpl::~WebClipboardImpl() {
+}
+
+uint64_t WebClipboardImpl::sequenceNumber(Buffer buffer) {
+ mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+ uint64_t number = 0;
+ clipboard_->GetSequenceNumber(clipboard_type,
+ base::Bind(&CopyUint64, &number));
+
+ // Force this to be synchronous.
+ clipboard_.WaitForIncomingMethodCall();
+ return number;
+}
+
+bool WebClipboardImpl::isFormatAvailable(Format format, Buffer buffer) {
+ mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+ std::vector<std::string> types;
+ clipboard_->GetAvailableMimeTypes(
+ clipboard_type, base::Bind(&CopyVectorString, &types));
+
+ // Force this to be synchronous.
+ clipboard_.WaitForIncomingMethodCall();
+
+ switch (format) {
+ case FormatPlainText:
+ return Contains(types, mojo::Clipboard::MIME_TYPE_TEXT);
+ case FormatHTML:
+ return Contains(types, mojo::Clipboard::MIME_TYPE_HTML);
+ case FormatSmartPaste:
+ return Contains(types, kMimeTypeWebkitSmartPaste);
+ case FormatBookmark:
+ // This might be difficult.
+ return false;
+ }
+
+ return false;
+}
+
+blink::WebVector<blink::WebString> WebClipboardImpl::readAvailableTypes(
+ Buffer buffer,
+ bool* containsFilenames) {
+ mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+ std::vector<std::string> types;
+ clipboard_->GetAvailableMimeTypes(
+ clipboard_type, base::Bind(&CopyVectorString, &types));
+
+ // Force this to be synchronous.
+ clipboard_.WaitForIncomingMethodCall();
+
+ // AFAICT, every instance of setting containsFilenames is false.
+ *containsFilenames = false;
+
+ blink::WebVector<blink::WebString> output(types.size());
+ for (size_t i = 0; i < types.size(); ++i) {
+ output[i] = blink::WebString::fromUTF8(types[i]);
+ }
+
+ return output;
+}
+
+blink::WebString WebClipboardImpl::readPlainText(Buffer buffer) {
+ mojo::Clipboard::Type type = ConvertBufferType(buffer);
+
+ blink::WebString text;
+ clipboard_->ReadMimeType(
+ type, mojo::Clipboard::MIME_TYPE_TEXT, base::Bind(&CopyWebString, &text));
+
+ // Force this to be synchronous.
+ clipboard_.WaitForIncomingMethodCall();
+
+ return text;
+}
+
+blink::WebString WebClipboardImpl::readHTML(Buffer buffer,
+ blink::WebURL* pageURL,
+ unsigned* fragmentStart,
+ unsigned* fragmentEnd) {
+ mojo::Clipboard::Type type = ConvertBufferType(buffer);
+
+ blink::WebString html;
+ clipboard_->ReadMimeType(
+ type, mojo::Clipboard::MIME_TYPE_HTML, base::Bind(&CopyWebString, &html));
+ clipboard_.WaitForIncomingMethodCall();
+
+ *fragmentStart = 0;
+ *fragmentEnd = static_cast<unsigned>(html.length());
+
+ clipboard_->ReadMimeType(
+ type, mojo::Clipboard::MIME_TYPE_URL, base::Bind(&CopyURL, pageURL));
+ clipboard_.WaitForIncomingMethodCall();
+
+ return html;
+}
+
+blink::WebString WebClipboardImpl::readCustomData(
+ Buffer buffer,
+ const blink::WebString& mime_type) {
+ mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+ blink::WebString data;
+ clipboard_->ReadMimeType(
+ clipboard_type, mime_type.utf8(), base::Bind(&CopyWebString, &data));
+
+ // Force this to be synchronous.
+ clipboard_.WaitForIncomingMethodCall();
+
+ return data;
+}
+
+void WebClipboardImpl::writePlainText(const blink::WebString& text) {
+ Array<MimeTypePairPtr> data;
+ MimeTypePairPtr text_data(MimeTypePair::New());
+ text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+ text_data->data = Array<uint8_t>::From(text).Pass();
+ data.push_back(text_data.Pass());
+
+ clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE, data.Pass());
+}
+
+void WebClipboardImpl::writeHTML(const blink::WebString& htmlText,
+ const blink::WebURL& url,
+ const blink::WebString& plainText,
+ bool writeSmartPaste) {
+ Array<MimeTypePairPtr> data;
+ MimeTypePairPtr text_data(MimeTypePair::New());
+ text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+ text_data->data = Array<uint8_t>::From(plainText).Pass();
+ data.push_back(text_data.Pass());
+
+ MimeTypePairPtr html_data(MimeTypePair::New());
+ text_data->mime_type = mojo::Clipboard::MIME_TYPE_HTML;
+ text_data->data = Array<uint8_t>::From(htmlText).Pass();
+ data.push_back(html_data.Pass());
+
+ MimeTypePairPtr url_data(MimeTypePair::New());
+ url_data->mime_type = mojo::Clipboard::MIME_TYPE_URL;
+ url_data->data = Array<uint8_t>::From(url.string()).Pass();
+ data.push_back(url_data.Pass());
+
+ if (writeSmartPaste) {
+ MimeTypePairPtr smart_paste(MimeTypePair::New());
+ url_data->mime_type = kMimeTypeWebkitSmartPaste;
+ url_data->data = Array<uint8_t>::From(blink::WebString()).Pass();
+ data.push_back(smart_paste.Pass());
+ }
+
+ clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE, data.Pass());
+}
+
+mojo::Clipboard::Type WebClipboardImpl::ConvertBufferType(Buffer buffer) {
+ switch (buffer) {
+ case BufferStandard:
+ return mojo::Clipboard::TYPE_COPY_PASTE;
+ case BufferSelection:
+ return mojo::Clipboard::TYPE_SELECTION;
+ }
+
+ NOTREACHED();
+ return mojo::Clipboard::TYPE_COPY_PASTE;
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/webclipboard_impl.h b/mojo/services/html_viewer/webclipboard_impl.h
new file mode 100644
index 0000000..0c307c5
--- /dev/null
+++ b/mojo/services/html_viewer/webclipboard_impl.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_SERVICES_HTML_VIEWER_WEBCLIPBOARD_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBCLIPBOARD_IMPL_H_
+
+#include "mojo/services/public/interfaces/clipboard/clipboard.mojom.h"
+#include "third_party/WebKit/public/platform/WebClipboard.h"
+
+namespace mojo {
+
+class WebClipboardImpl : public blink::WebClipboard {
+ public:
+ WebClipboardImpl(ClipboardPtr clipboard);
+ virtual ~WebClipboardImpl();
+
+ // blink::WebClipboard methods:
+ virtual uint64_t sequenceNumber(Buffer);
+ virtual bool isFormatAvailable(Format, Buffer);
+ virtual blink::WebVector<blink::WebString> readAvailableTypes(
+ Buffer,
+ bool* containsFilenames);
+ virtual blink::WebString readPlainText(Buffer);
+ virtual blink::WebString readHTML(Buffer buffer,
+ blink::WebURL* pageURL,
+ unsigned* fragmentStart,
+ unsigned* fragmentEnd);
+ // TODO(erg): readImage()
+ virtual blink::WebString readCustomData(Buffer, const blink::WebString& type);
+ virtual void writePlainText(const blink::WebString&);
+ virtual void writeHTML(const blink::WebString& htmlText,
+ const blink::WebURL&,
+ const blink::WebString& plainText,
+ bool writeSmartPaste);
+
+ private:
+ // Changes webkit buffers to mojo Clipboard::Types.
+ mojo::Clipboard::Type ConvertBufferType(Buffer buffer);
+
+ ClipboardPtr clipboard_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebClipboardImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBCLIPBOARD_IMPL_H_
diff --git a/mojo/services/html_viewer/webcookiejar_impl.cc b/mojo/services/html_viewer/webcookiejar_impl.cc
new file mode 100644
index 0000000..0d86480
--- /dev/null
+++ b/mojo/services/html_viewer/webcookiejar_impl.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/services/html_viewer/webcookiejar_impl.h"
+
+#include "base/bind.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+
+namespace mojo {
+namespace {
+
+void CopyBool(bool* output, bool input) {
+ *output = input;
+}
+
+void CopyString(String* output, const String& input) {
+ *output = input;
+}
+
+} // namespace
+
+WebCookieJarImpl::WebCookieJarImpl(CookieStorePtr store)
+ : store_(store.Pass()) {
+}
+
+WebCookieJarImpl::~WebCookieJarImpl() {
+}
+
+void WebCookieJarImpl::setCookie(const blink::WebURL& url,
+ const blink::WebURL& first_party_for_cookies,
+ const blink::WebString& cookie) {
+ bool success;
+ store_->Set(url.string().utf8(), cookie.utf8(),
+ base::Bind(&CopyBool, &success));
+
+ // Wait to ensure the cookie was set before advancing. That way any
+ // subsequent URL request will see the changes to the cookie store.
+ //
+ // TODO(darin): Consider using associated message pipes for the CookieStore
+ // and URLLoader, so that we could let this method call run asynchronously
+ // without suffering an ordering problem. See crbug/386825.
+ //
+ store_.WaitForIncomingMethodCall();
+}
+
+blink::WebString WebCookieJarImpl::cookies(
+ const blink::WebURL& url,
+ const blink::WebURL& first_party_for_cookies) {
+ String result;
+ store_->Get(url.string().utf8(), base::Bind(&CopyString, &result));
+
+ // Wait for the result. Since every outbound request we make to the cookie
+ // store is followed up with WaitForIncomingMethodCall, we can be sure that
+ // the next incoming method call will be the response to our request.
+ store_.WaitForIncomingMethodCall();
+ if (!result)
+ return blink::WebString();
+
+ return blink::WebString::fromUTF8(result);
+}
+
+blink::WebString WebCookieJarImpl::cookieRequestHeaderFieldValue(
+ const blink::WebURL& url,
+ const blink::WebURL& first_party_for_cookies) {
+ return cookies(url, first_party_for_cookies);
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/webcookiejar_impl.h b/mojo/services/html_viewer/webcookiejar_impl.h
new file mode 100644
index 0000000..cfa0055
--- /dev/null
+++ b/mojo/services/html_viewer/webcookiejar_impl.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_HTML_VIEWER_WEBCOOKIEJAR_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBCOOKIEJAR_IMPL_H_
+
+#include "mojo/services/public/interfaces/network/cookie_store.mojom.h"
+#include "third_party/WebKit/public/platform/WebCookieJar.h"
+
+namespace mojo {
+
+class WebCookieJarImpl : public blink::WebCookieJar {
+ public:
+ explicit WebCookieJarImpl(CookieStorePtr store);
+ virtual ~WebCookieJarImpl();
+
+ // blink::WebCookieJar methods:
+ virtual void setCookie(
+ const blink::WebURL& url,
+ const blink::WebURL& first_party_for_cookies,
+ const blink::WebString& cookie);
+ virtual blink::WebString cookies(
+ const blink::WebURL& url,
+ const blink::WebURL& first_party_for_cookies);
+ virtual blink::WebString cookieRequestHeaderFieldValue(
+ const blink::WebURL& url,
+ const blink::WebURL& first_party_for_cookies);
+
+ private:
+ CookieStorePtr store_;
+ DISALLOW_COPY_AND_ASSIGN(WebCookieJarImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBCOOKIEJAR_IMPL_H_
diff --git a/mojo/services/html_viewer/weblayertreeview_impl.cc b/mojo/services/html_viewer/weblayertreeview_impl.cc
new file mode 100644
index 0000000..56ebc34
--- /dev/null
+++ b/mojo/services/html_viewer/weblayertreeview_impl.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/services/html_viewer/weblayertreeview_impl.h"
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "cc/blink/web_layer_impl.h"
+#include "cc/layers/layer.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/trees/layer_tree_host.h"
+#include "mojo/cc/context_provider_mojo.h"
+#include "mojo/cc/output_surface_mojo.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/cpp/view_manager/view.h"
+#include "third_party/WebKit/public/web/WebWidget.h"
+
+namespace mojo {
+
+WebLayerTreeViewImpl::WebLayerTreeViewImpl(
+ scoped_refptr<base::MessageLoopProxy> compositor_message_loop_proxy,
+ SurfacesServicePtr surfaces_service,
+ GpuPtr gpu_service)
+ : widget_(NULL),
+ view_(NULL),
+ surfaces_service_(surfaces_service.Pass()),
+ gpu_service_(gpu_service.Pass()),
+ main_thread_compositor_task_runner_(base::MessageLoopProxy::current()),
+ weak_factory_(this) {
+ main_thread_bound_weak_ptr_ = weak_factory_.GetWeakPtr();
+ surfaces_service_->CreateSurfaceConnection(
+ base::Bind(&WebLayerTreeViewImpl::OnSurfaceConnectionCreated,
+ main_thread_bound_weak_ptr_));
+
+ cc::LayerTreeSettings settings;
+
+ // For web contents, layer transforms should scale up the contents of layers
+ // to keep content always crisp when possible.
+ settings.layer_transforms_should_scale_layer_contents = true;
+
+ cc::SharedBitmapManager* shared_bitmap_manager = NULL;
+
+ layer_tree_host_ =
+ cc::LayerTreeHost::CreateThreaded(this,
+ shared_bitmap_manager,
+ settings,
+ base::MessageLoopProxy::current(),
+ compositor_message_loop_proxy);
+ DCHECK(layer_tree_host_);
+}
+
+WebLayerTreeViewImpl::~WebLayerTreeViewImpl() {
+}
+
+void WebLayerTreeViewImpl::WillBeginMainFrame(int frame_id) {
+}
+
+void WebLayerTreeViewImpl::DidBeginMainFrame() {
+}
+
+void WebLayerTreeViewImpl::BeginMainFrame(const cc::BeginFrameArgs& args) {
+ VLOG(2) << "WebLayerTreeViewImpl::BeginMainFrame";
+ double frame_time_sec = (args.frame_time - base::TimeTicks()).InSecondsF();
+ double deadline_sec = (args.deadline - base::TimeTicks()).InSecondsF();
+ double interval_sec = args.interval.InSecondsF();
+ blink::WebBeginFrameArgs web_begin_frame_args(
+ frame_time_sec, deadline_sec, interval_sec);
+ widget_->beginFrame(web_begin_frame_args);
+}
+
+void WebLayerTreeViewImpl::Layout() {
+ widget_->layout();
+}
+
+void WebLayerTreeViewImpl::ApplyViewportDeltas(
+ const gfx::Vector2d& inner_delta,
+ const gfx::Vector2d& outer_delta,
+ float page_scale,
+ float top_controls_delta) {
+ widget_->applyViewportDeltas(
+ inner_delta,
+ outer_delta,
+ page_scale,
+ top_controls_delta);
+}
+
+void WebLayerTreeViewImpl::ApplyViewportDeltas(
+ const gfx::Vector2d& scroll_delta,
+ float page_scale,
+ float top_controls_delta) {
+ widget_->applyViewportDeltas(scroll_delta, page_scale, top_controls_delta);
+}
+
+void WebLayerTreeViewImpl::RequestNewOutputSurface(bool fallback) {
+ layer_tree_host_->SetOutputSurface(output_surface_.Pass());
+}
+
+void WebLayerTreeViewImpl::DidInitializeOutputSurface() {
+}
+
+void WebLayerTreeViewImpl::WillCommit() {
+}
+
+void WebLayerTreeViewImpl::DidCommit() {
+ widget_->didCommitFrameToCompositor();
+}
+
+void WebLayerTreeViewImpl::DidCommitAndDrawFrame() {
+}
+
+void WebLayerTreeViewImpl::DidCompleteSwapBuffers() {
+}
+
+void WebLayerTreeViewImpl::setSurfaceReady() {
+}
+
+void WebLayerTreeViewImpl::setRootLayer(const blink::WebLayer& layer) {
+ layer_tree_host_->SetRootLayer(
+ static_cast<const cc_blink::WebLayerImpl*>(&layer)->layer());
+}
+
+void WebLayerTreeViewImpl::clearRootLayer() {
+ layer_tree_host_->SetRootLayer(scoped_refptr<cc::Layer>());
+}
+
+void WebLayerTreeViewImpl::setViewportSize(
+ const blink::WebSize& device_viewport_size) {
+ layer_tree_host_->SetViewportSize(device_viewport_size);
+}
+
+blink::WebSize WebLayerTreeViewImpl::deviceViewportSize() const {
+ return layer_tree_host_->device_viewport_size();
+}
+
+void WebLayerTreeViewImpl::setDeviceScaleFactor(float device_scale_factor) {
+ layer_tree_host_->SetDeviceScaleFactor(device_scale_factor);
+}
+
+float WebLayerTreeViewImpl::deviceScaleFactor() const {
+ return layer_tree_host_->device_scale_factor();
+}
+
+void WebLayerTreeViewImpl::setBackgroundColor(blink::WebColor color) {
+ layer_tree_host_->set_background_color(color);
+}
+
+void WebLayerTreeViewImpl::setHasTransparentBackground(
+ bool has_transparent_background) {
+ layer_tree_host_->set_has_transparent_background(has_transparent_background);
+}
+
+void WebLayerTreeViewImpl::setOverhangBitmap(const SkBitmap& bitmap) {
+ layer_tree_host_->SetOverhangBitmap(bitmap);
+}
+
+void WebLayerTreeViewImpl::setVisible(bool visible) {
+ layer_tree_host_->SetVisible(visible);
+}
+
+void WebLayerTreeViewImpl::setPageScaleFactorAndLimits(float page_scale_factor,
+ float minimum,
+ float maximum) {
+ layer_tree_host_->SetPageScaleFactorAndLimits(
+ page_scale_factor, minimum, maximum);
+}
+
+void WebLayerTreeViewImpl::registerForAnimations(blink::WebLayer* layer) {
+ cc::Layer* cc_layer = static_cast<cc_blink::WebLayerImpl*>(layer)->layer();
+ cc_layer->layer_animation_controller()->SetAnimationRegistrar(
+ layer_tree_host_->animation_registrar());
+}
+
+void WebLayerTreeViewImpl::registerViewportLayers(
+ const blink::WebLayer* pageScaleLayer,
+ const blink::WebLayer* innerViewportScrollLayer,
+ const blink::WebLayer* outerViewportScrollLayer) {
+ layer_tree_host_->RegisterViewportLayers(
+ static_cast<const cc_blink::WebLayerImpl*>(pageScaleLayer)->layer(),
+ static_cast<const cc_blink::WebLayerImpl*>(innerViewportScrollLayer)
+ ->layer(),
+ // The outer viewport layer will only exist when using pinch virtual
+ // viewports.
+ outerViewportScrollLayer ? static_cast<const cc_blink::WebLayerImpl*>(
+ outerViewportScrollLayer)->layer()
+ : NULL);
+}
+
+void WebLayerTreeViewImpl::clearViewportLayers() {
+ layer_tree_host_->RegisterViewportLayers(scoped_refptr<cc::Layer>(),
+ scoped_refptr<cc::Layer>(),
+ scoped_refptr<cc::Layer>());
+}
+
+void WebLayerTreeViewImpl::startPageScaleAnimation(
+ const blink::WebPoint& destination,
+ bool use_anchor,
+ float new_page_scale,
+ double duration_sec) {
+ base::TimeDelta duration = base::TimeDelta::FromMicroseconds(
+ duration_sec * base::Time::kMicrosecondsPerSecond);
+ layer_tree_host_->StartPageScaleAnimation(
+ gfx::Vector2d(destination.x, destination.y),
+ use_anchor,
+ new_page_scale,
+ duration);
+}
+
+void WebLayerTreeViewImpl::setNeedsAnimate() {
+ layer_tree_host_->SetNeedsAnimate();
+}
+
+bool WebLayerTreeViewImpl::commitRequested() const {
+ return layer_tree_host_->CommitRequested();
+}
+
+void WebLayerTreeViewImpl::finishAllRendering() {
+ layer_tree_host_->FinishAllRendering();
+}
+
+void WebLayerTreeViewImpl::OnSurfaceConnectionCreated(SurfacePtr surface,
+ uint32_t id_namespace) {
+ CommandBufferPtr cb;
+ gpu_service_->CreateOffscreenGLES2Context(Get(&cb));
+ scoped_refptr<cc::ContextProvider> context_provider(
+ new ContextProviderMojo(cb.PassMessagePipe()));
+ output_surface_.reset(new OutputSurfaceMojo(
+ this, context_provider, surface.Pass(), id_namespace));
+ layer_tree_host_->SetLayerTreeHostClientReady();
+}
+
+void WebLayerTreeViewImpl::DidCreateSurface(cc::SurfaceId id) {
+ main_thread_compositor_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&WebLayerTreeViewImpl::DidCreateSurfaceOnMainThread,
+ main_thread_bound_weak_ptr_,
+ id));
+}
+
+void WebLayerTreeViewImpl::DidCreateSurfaceOnMainThread(cc::SurfaceId id) {
+ view_->SetSurfaceId(SurfaceId::From(id));
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/weblayertreeview_impl.h b/mojo/services/html_viewer/weblayertreeview_impl.h
new file mode 100644
index 0000000..5221902
--- /dev/null
+++ b/mojo/services/html_viewer/weblayertreeview_impl.h
@@ -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.
+
+#ifndef MOJO_SERVICES_HTML_VIEWER_WEBLAYERTREEVIEW_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBLAYERTREEVIEW_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "cc/trees/layer_tree_host_client.h"
+#include "mojo/cc/output_surface_mojo.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "third_party/WebKit/public/platform/WebLayerTreeView.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace blink {
+class WebWidget;
+}
+
+namespace cc {
+class LayerTreeHost;
+}
+
+namespace mojo {
+class View;
+
+class WebLayerTreeViewImpl : public blink::WebLayerTreeView,
+ public cc::LayerTreeHostClient,
+ public OutputSurfaceMojoClient {
+ public:
+ WebLayerTreeViewImpl(
+ scoped_refptr<base::MessageLoopProxy> compositor_message_loop_proxy,
+ SurfacesServicePtr surfaces_service,
+ GpuPtr gpu_service);
+ virtual ~WebLayerTreeViewImpl();
+
+ void set_widget(blink::WebWidget* widget) { widget_ = widget; }
+ void set_view(View* view) { view_ = view; }
+
+ // cc::LayerTreeHostClient implementation.
+ virtual void WillBeginMainFrame(int frame_id) override;
+ virtual void DidBeginMainFrame() override;
+ virtual void BeginMainFrame(const cc::BeginFrameArgs& args) override;
+ virtual void Layout() override;
+ virtual void ApplyViewportDeltas(const gfx::Vector2d& inner_delta,
+ const gfx::Vector2d& outer_delta,
+ float page_scale,
+ float top_controls_delta) override;
+ virtual void ApplyViewportDeltas(const gfx::Vector2d& scroll_delta,
+ float page_scale,
+ float top_controls_delta) override;
+ virtual void RequestNewOutputSurface(bool fallback) override;
+ virtual void DidInitializeOutputSurface() override;
+ virtual void WillCommit() override;
+ virtual void DidCommit() override;
+ virtual void DidCommitAndDrawFrame() override;
+ virtual void DidCompleteSwapBuffers() override;
+ virtual void RateLimitSharedMainThreadContext() override {}
+
+ // blink::WebLayerTreeView implementation.
+ virtual void setSurfaceReady() override;
+ virtual void setRootLayer(const blink::WebLayer& layer) override;
+ virtual void clearRootLayer() override;
+ virtual void setViewportSize(
+ const blink::WebSize& device_viewport_size) override;
+ virtual blink::WebSize deviceViewportSize() const override;
+ virtual void setDeviceScaleFactor(float) override;
+ virtual float deviceScaleFactor() const override;
+ virtual void setBackgroundColor(blink::WebColor color) override;
+ virtual void setHasTransparentBackground(
+ bool has_transparent_background) override;
+ virtual void setOverhangBitmap(const SkBitmap& bitmap) override;
+ virtual void setVisible(bool visible) override;
+ virtual void setPageScaleFactorAndLimits(float page_scale_factor,
+ float minimum,
+ float maximum) override;
+ virtual void startPageScaleAnimation(const blink::WebPoint& destination,
+ bool use_anchor,
+ float new_page_scale,
+ double duration_sec) override;
+ virtual void heuristicsForGpuRasterizationUpdated(bool matches_heuristic) {}
+ virtual void setTopControlsContentOffset(float offset) {}
+ virtual void setNeedsAnimate() override;
+ virtual bool commitRequested() const override;
+ virtual void didStopFlinging() {}
+ virtual void compositeAndReadbackAsync(
+ blink::WebCompositeAndReadbackAsyncCallback* callback) {}
+ virtual void finishAllRendering() override;
+ virtual void setDeferCommits(bool defer_commits) {}
+ virtual void registerForAnimations(blink::WebLayer* layer) override;
+ virtual void registerViewportLayers(
+ const blink::WebLayer* page_scale_layer,
+ const blink::WebLayer* inner_viewport_scroll_layer,
+ const blink::WebLayer* outer_viewport_scroll_layer) override;
+ virtual void clearViewportLayers() override;
+ virtual void registerSelection(const blink::WebSelectionBound& start,
+ const blink::WebSelectionBound& end) {}
+ virtual void clearSelection() {}
+ virtual void setShowFPSCounter(bool) {}
+ virtual void setShowPaintRects(bool) {}
+ virtual void setShowDebugBorders(bool) {}
+ virtual void setContinuousPaintingEnabled(bool) {}
+ virtual void setShowScrollBottleneckRects(bool) {}
+
+ // OutputSurfaceMojoClient implementation.
+ virtual void DidCreateSurface(cc::SurfaceId id) override;
+
+ private:
+ void OnSurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace);
+ void DidCreateSurfaceOnMainThread(cc::SurfaceId id);
+
+ // widget_ and view_ will outlive us.
+ blink::WebWidget* widget_;
+ View* view_;
+ scoped_ptr<cc::LayerTreeHost> layer_tree_host_;
+ SurfacesServicePtr surfaces_service_;
+ scoped_ptr<cc::OutputSurface> output_surface_;
+ GpuPtr gpu_service_;
+ scoped_refptr<base::SingleThreadTaskRunner>
+ main_thread_compositor_task_runner_;
+ base::WeakPtr<WebLayerTreeViewImpl> main_thread_bound_weak_ptr_;
+
+ base::WeakPtrFactory<WebLayerTreeViewImpl> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(WebLayerTreeViewImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBLAYERTREEVIEW_IMPL_H_
diff --git a/mojo/services/html_viewer/webmediaplayer_factory.cc b/mojo/services/html_viewer/webmediaplayer_factory.cc
new file mode 100644
index 0000000..bbf8c81
--- /dev/null
+++ b/mojo/services/html_viewer/webmediaplayer_factory.cc
@@ -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.
+
+#include "mojo/services/html_viewer/webmediaplayer_factory.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/threading/thread.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/null_audio_sink.h"
+#include "media/base/audio_hardware_config.h"
+#include "media/base/media.h"
+#include "media/base/media_log.h"
+#include "media/blink/null_encrypted_media_player_support.h"
+#include "media/blink/webmediaplayer_impl.h"
+#include "media/blink/webmediaplayer_params.h"
+#include "media/filters/gpu_video_accelerator_factories.h"
+
+namespace mojo {
+
+WebMediaPlayerFactory::WebMediaPlayerFactory(
+ const scoped_refptr<base::SingleThreadTaskRunner>& compositor_task_runner)
+ : compositor_task_runner_(compositor_task_runner),
+ media_thread_("Media"),
+ audio_manager_(media::AudioManager::Create(&fake_audio_log_factory_)),
+ audio_hardware_config_(
+ audio_manager_->GetInputStreamParameters(
+ media::AudioManagerBase::kDefaultDeviceId),
+ audio_manager_->GetDefaultOutputStreamParameters()) {
+
+ if (!media::IsMediaLibraryInitialized()) {
+ base::FilePath module_dir;
+ CHECK(PathService::Get(base::DIR_EXE, &module_dir));
+ CHECK(media::InitializeMediaLibrary(module_dir));
+ }
+}
+
+WebMediaPlayerFactory::~WebMediaPlayerFactory() {
+}
+
+blink::WebMediaPlayer* WebMediaPlayerFactory::CreateMediaPlayer(
+ blink::WebLocalFrame* frame,
+ const blink::WebURL& url,
+ blink::WebMediaPlayerClient* client) {
+#if defined(OS_ANDROID)
+ return NULL;
+#else
+
+ media::WebMediaPlayerParams params(
+ media::WebMediaPlayerParams::DeferLoadCB(),
+ CreateAudioRendererSink(),
+ GetAudioHardwareConfig(),
+ new media::MediaLog(),
+ scoped_refptr<media::GpuVideoAcceleratorFactories>(),
+ GetMediaThreadTaskRunner(),
+ compositor_task_runner_,
+ base::Bind(&media::NullEncryptedMediaPlayerSupport::Create),
+ NULL);
+ base::WeakPtr<media::WebMediaPlayerDelegate> delegate;
+
+ return new media::WebMediaPlayerImpl(frame, client, delegate, params);
+#endif
+}
+
+const media::AudioHardwareConfig&
+WebMediaPlayerFactory::GetAudioHardwareConfig() {
+ return audio_hardware_config_;
+}
+
+scoped_refptr<media::AudioRendererSink>
+WebMediaPlayerFactory::CreateAudioRendererSink() {
+ // TODO(acolwell): Replace this with an AudioRendererSink implementation
+ // that actually talks to the audio device or an audio mojo service.
+ return new media::NullAudioSink(GetMediaThreadTaskRunner());
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+WebMediaPlayerFactory::GetMediaThreadTaskRunner() {
+ if (!media_thread_.IsRunning())
+ media_thread_.Start();
+
+ return media_thread_.message_loop_proxy();
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/webmediaplayer_factory.h b/mojo/services/html_viewer/webmediaplayer_factory.h
new file mode 100644
index 0000000..db85370
--- /dev/null
+++ b/mojo/services/html_viewer/webmediaplayer_factory.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_HTML_VIEWER_WEBMEDIAPLAYER_FACTORY_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBMEDIAPLAYER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/base/audio_hardware_config.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace blink {
+class WebMediaPlayer;
+class WebLocalFrame;
+class WebURL;
+class WebMediaPlayerClient;
+}
+
+namespace media {
+class AudioManager;
+class AudioRendererSink;
+}
+
+namespace mojo {
+
+// Helper class used to create blink::WebMediaPlayer objects.
+// This class stores the "global state" shared across all WebMediaPlayer
+// instances.
+class WebMediaPlayerFactory {
+ public:
+ explicit WebMediaPlayerFactory(const scoped_refptr<
+ base::SingleThreadTaskRunner>& compositor_task_runner);
+ ~WebMediaPlayerFactory();
+
+ blink::WebMediaPlayer* CreateMediaPlayer(blink::WebLocalFrame* frame,
+ const blink::WebURL& url,
+ blink::WebMediaPlayerClient* client);
+
+ private:
+ const media::AudioHardwareConfig& GetAudioHardwareConfig();
+ scoped_refptr<media::AudioRendererSink> CreateAudioRendererSink();
+ scoped_refptr<base::SingleThreadTaskRunner> GetMediaThreadTaskRunner();
+
+ scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_;
+ base::Thread media_thread_;
+ media::FakeAudioLogFactory fake_audio_log_factory_;
+ scoped_ptr<media::AudioManager> audio_manager_;
+ media::AudioHardwareConfig audio_hardware_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerFactory);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBMEDIAPLAYER_FACTORY_H_
diff --git a/mojo/services/html_viewer/webmimeregistry_impl.cc b/mojo/services/html_viewer/webmimeregistry_impl.cc
new file mode 100644
index 0000000..2faac17
--- /dev/null
+++ b/mojo/services/html_viewer/webmimeregistry_impl.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium 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/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 {
+
+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::supportsImagePrefixedMIMEType(
+ const blink::WebString& mime_type) {
+ std::string ascii_mime_type = ToASCIIOrEmpty(mime_type);
+ return (net::IsSupportedImageMimeType(ascii_mime_type) ||
+ (StartsWithASCII(ascii_mime_type, "image/", true) &&
+ net::IsSupportedNonImageMimeType(ascii_mime_type)))
+ ? WebMimeRegistry::IsSupported
+ : 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) {
+ const std::string mime_type_ascii = ToASCIIOrEmpty(mime_type);
+ // Not supporting the container is a flat-out no.
+ if (!net::IsSupportedMediaMimeType(mime_type_ascii))
+ return IsNotSupported;
+
+ // Mojo does not currently support any key systems.
+ if (!key_system.isEmpty())
+ return IsNotSupported;
+
+ // Check list of strict codecs to see if it is supported.
+ if (net::IsStrictMediaMimeType(mime_type_ascii)) {
+ // Check if the codecs are a perfect match.
+ std::vector<std::string> strict_codecs;
+ net::ParseCodecString(ToASCIIOrEmpty(codecs), &strict_codecs, false);
+ return static_cast<WebMimeRegistry::SupportsType>(
+ net::IsSupportedStrictMediaMimeType(mime_type_ascii, strict_codecs));
+ }
+
+ // If we don't recognize the codec, it's possible we support it.
+ std::vector<std::string> parsed_codecs;
+ net::ParseCodecString(ToASCIIOrEmpty(codecs), &parsed_codecs, true);
+ if (!net::AreSupportedMediaCodecs(parsed_codecs))
+ return MayBeSupported;
+
+ // Otherwise we have a perfect match.
+ return IsSupported;
+}
+
+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 mojo
diff --git a/mojo/services/html_viewer/webmimeregistry_impl.h b/mojo/services/html_viewer/webmimeregistry_impl.h
new file mode 100644
index 0000000..ba25ec9
--- /dev/null
+++ b/mojo/services/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_SERVICES_HTML_VIEWER_WEBMIMEREGISTRY_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBMIMEREGISTRY_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "third_party/WebKit/public/platform/WebMimeRegistry.h"
+
+namespace mojo {
+
+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 supportsImagePrefixedMIMEType(
+ 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 mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBMIMEREGISTRY_IMPL_H_
diff --git a/mojo/services/html_viewer/websockethandle_impl.cc b/mojo/services/html_viewer/websockethandle_impl.cc
new file mode 100644
index 0000000..b927b10
--- /dev/null
+++ b/mojo/services/html_viewer/websockethandle_impl.cc
@@ -0,0 +1,218 @@
+// Copyright 2014 The Chromium 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/html_viewer/websockethandle_impl.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/services/html_viewer/blink_basic_type_converters.h"
+#include "mojo/services/public/cpp/network/web_socket_read_queue.h"
+#include "mojo/services/public/cpp/network/web_socket_write_queue.h"
+#include "mojo/services/public/interfaces/network/network_service.mojom.h"
+#include "third_party/WebKit/public/platform/WebSerializedOrigin.h"
+#include "third_party/WebKit/public/platform/WebSocketHandleClient.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+#include "third_party/WebKit/public/platform/WebVector.h"
+
+using blink::WebSerializedOrigin;
+using blink::WebSocketHandle;
+using blink::WebSocketHandleClient;
+using blink::WebString;
+using blink::WebURL;
+using blink::WebVector;
+
+namespace mojo {
+
+template<>
+struct TypeConverter<WebSocket::MessageType, WebSocketHandle::MessageType> {
+ static WebSocket::MessageType Convert(WebSocketHandle::MessageType type) {
+ DCHECK(type == WebSocketHandle::MessageTypeContinuation ||
+ type == WebSocketHandle::MessageTypeText ||
+ type == WebSocketHandle::MessageTypeBinary);
+ typedef WebSocket::MessageType MessageType;
+ COMPILE_ASSERT(
+ static_cast<MessageType>(WebSocketHandle::MessageTypeContinuation) ==
+ WebSocket::MESSAGE_TYPE_CONTINUATION,
+ enum_values_must_match_for_message_type);
+ COMPILE_ASSERT(
+ static_cast<MessageType>(WebSocketHandle::MessageTypeText) ==
+ WebSocket::MESSAGE_TYPE_TEXT,
+ enum_values_must_match_for_message_type);
+ COMPILE_ASSERT(
+ static_cast<MessageType>(WebSocketHandle::MessageTypeBinary) ==
+ WebSocket::MESSAGE_TYPE_BINARY,
+ enum_values_must_match_for_message_type);
+ return static_cast<WebSocket::MessageType>(type);
+ }
+};
+
+template<>
+struct TypeConverter<WebSocketHandle::MessageType, WebSocket::MessageType> {
+ static WebSocketHandle::MessageType Convert(WebSocket::MessageType type) {
+ DCHECK(type == WebSocket::MESSAGE_TYPE_CONTINUATION ||
+ type == WebSocket::MESSAGE_TYPE_TEXT ||
+ type == WebSocket::MESSAGE_TYPE_BINARY);
+ return static_cast<WebSocketHandle::MessageType>(type);
+ }
+};
+
+// This class forms a bridge from the mojo WebSocketClient interface and the
+// Blink WebSocketHandleClient interface.
+class WebSocketClientImpl : public InterfaceImpl<WebSocketClient> {
+ public:
+ explicit WebSocketClientImpl(WebSocketHandleImpl* handle,
+ blink::WebSocketHandleClient* client)
+ : handle_(handle), client_(client) {}
+ virtual ~WebSocketClientImpl() {}
+
+ private:
+ // WebSocketClient methods:
+ virtual void DidConnect(bool fail,
+ const String& selected_subprotocol,
+ const String& extensions,
+ ScopedDataPipeConsumerHandle receive_stream)
+ override {
+ blink::WebSocketHandleClient* client = client_;
+ WebSocketHandleImpl* handle = handle_;
+ receive_stream_ = receive_stream.Pass();
+ read_queue_.reset(new WebSocketReadQueue(receive_stream_.get()));
+ if (fail)
+ handle->Disconnect(); // deletes |this|
+ client->didConnect(handle,
+ fail,
+ selected_subprotocol.To<WebString>(),
+ extensions.To<WebString>());
+ // |handle| can be deleted here.
+ }
+
+ virtual void DidReceiveData(bool fin,
+ WebSocket::MessageType type,
+ uint32_t num_bytes) override {
+ read_queue_->Read(num_bytes,
+ base::Bind(&WebSocketClientImpl::DidReadFromReceiveStream,
+ base::Unretained(this),
+ fin, type, num_bytes));
+ }
+
+ virtual void DidReceiveFlowControl(int64_t quota) override {
+ client_->didReceiveFlowControl(handle_, quota);
+ // |handle| can be deleted here.
+ }
+
+ virtual void DidFail(const String& message) override {
+ blink::WebSocketHandleClient* client = client_;
+ WebSocketHandleImpl* handle = handle_;
+ handle->Disconnect(); // deletes |this|
+ client->didFail(handle, message.To<WebString>());
+ // |handle| can be deleted here.
+ }
+
+ virtual void DidClose(bool was_clean,
+ uint16_t code,
+ const String& reason) override {
+ blink::WebSocketHandleClient* client = client_;
+ WebSocketHandleImpl* handle = handle_;
+ handle->Disconnect(); // deletes |this|
+ client->didClose(handle, was_clean, code, reason.To<WebString>());
+ // |handle| can be deleted here.
+ }
+
+ void DidReadFromReceiveStream(bool fin,
+ WebSocket::MessageType type,
+ uint32_t num_bytes,
+ const char* data) {
+ client_->didReceiveData(handle_,
+ fin,
+ ConvertTo<WebSocketHandle::MessageType>(type),
+ data,
+ num_bytes);
+ // |handle_| can be deleted here.
+ }
+
+ // |handle_| owns this object, so it is guaranteed to outlive us.
+ WebSocketHandleImpl* handle_;
+ blink::WebSocketHandleClient* client_;
+ ScopedDataPipeConsumerHandle receive_stream_;
+ scoped_ptr<WebSocketReadQueue> read_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketClientImpl);
+};
+
+WebSocketHandleImpl::WebSocketHandleImpl(NetworkService* network_service)
+ : did_close_(false) {
+ network_service->CreateWebSocket(Get(&web_socket_));
+}
+
+WebSocketHandleImpl::~WebSocketHandleImpl() {
+ if (!did_close_) {
+ // The connection is abruptly disconnected by the renderer without
+ // closing handshake.
+ web_socket_->Close(WebSocket::kAbnormalCloseCode, String());
+ }
+}
+
+void WebSocketHandleImpl::connect(const WebURL& url,
+ const WebVector<WebString>& protocols,
+ const WebSerializedOrigin& origin,
+ WebSocketHandleClient* client) {
+ client_.reset(new WebSocketClientImpl(this, client));
+ WebSocketClientPtr client_ptr;
+ // TODO(mpcomplete): Is this the right ownership model? Or should mojo own
+ // |client_|?
+ WeakBindToProxy(client_.get(), &client_ptr);
+
+ DataPipe data_pipe;
+ send_stream_ = data_pipe.producer_handle.Pass();
+ write_queue_.reset(new WebSocketWriteQueue(send_stream_.get()));
+ web_socket_->Connect(url.string().utf8(),
+ Array<String>::From(protocols),
+ origin.string().utf8(),
+ data_pipe.consumer_handle.Pass(),
+ client_ptr.Pass());
+}
+
+void WebSocketHandleImpl::send(bool fin,
+ WebSocketHandle::MessageType type,
+ const char* data,
+ size_t size) {
+ if (!client_)
+ return;
+
+ uint32_t size32 = static_cast<uint32_t>(size);
+ write_queue_->Write(
+ data, size32,
+ base::Bind(&WebSocketHandleImpl::DidWriteToSendStream,
+ base::Unretained(this),
+ fin, type, size32));
+}
+
+void WebSocketHandleImpl::flowControl(int64_t quota) {
+ if (!client_)
+ return;
+
+ web_socket_->FlowControl(quota);
+}
+
+void WebSocketHandleImpl::close(unsigned short code, const WebString& reason) {
+ web_socket_->Close(code, reason.utf8());
+}
+
+void WebSocketHandleImpl::DidWriteToSendStream(
+ bool fin,
+ WebSocketHandle::MessageType type,
+ uint32_t num_bytes,
+ const char* data) {
+ web_socket_->Send(fin, ConvertTo<WebSocket::MessageType>(type), num_bytes);
+}
+
+void WebSocketHandleImpl::Disconnect() {
+ did_close_ = true;
+ client_.reset();
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/websockethandle_impl.h b/mojo/services/html_viewer/websockethandle_impl.h
new file mode 100644
index 0000000..f035dd9
--- /dev/null
+++ b/mojo/services/html_viewer/websockethandle_impl.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_SERVICES_HTML_VIEWER_WEBSOCKETHANDLE_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBSOCKETHANDLE_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/common/handle_watcher.h"
+#include "mojo/services/public/interfaces/network/web_socket.mojom.h"
+#include "third_party/WebKit/public/platform/WebSocketHandle.h"
+
+namespace mojo {
+class NetworkService;
+class WebSocketClientImpl;
+class WebSocketWriteQueue;
+
+// Implements WebSocketHandle by talking to the mojo WebSocket interface.
+class WebSocketHandleImpl : public blink::WebSocketHandle {
+ public:
+ explicit WebSocketHandleImpl(NetworkService* network_service);
+
+ private:
+ friend class WebSocketClientImpl;
+
+ virtual ~WebSocketHandleImpl();
+
+ // blink::WebSocketHandle methods:
+ virtual void connect(const blink::WebURL& url,
+ const blink::WebVector<blink::WebString>& protocols,
+ const blink::WebSerializedOrigin& origin,
+ blink::WebSocketHandleClient*) override;
+ virtual void send(bool fin,
+ MessageType,
+ const char* data,
+ size_t size) override;
+ virtual void flowControl(int64_t quota) override;
+ virtual void close(unsigned short code,
+ const blink::WebString& reason) override;
+
+ // Called when we finished writing to |send_stream_|.
+ void DidWriteToSendStream(bool fin,
+ WebSocketHandle::MessageType type,
+ uint32_t num_bytes,
+ const char* data);
+
+ // Called when the socket is closed.
+ void Disconnect();
+
+ WebSocketPtr web_socket_;
+ scoped_ptr<WebSocketClientImpl> client_;
+ ScopedDataPipeProducerHandle send_stream_;
+ scoped_ptr<WebSocketWriteQueue> write_queue_;
+ // True if close() was called.
+ bool did_close_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandleImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBSOCKETHANDLE_IMPL_H_
diff --git a/mojo/services/html_viewer/webstoragenamespace_impl.cc b/mojo/services/html_viewer/webstoragenamespace_impl.cc
new file mode 100644
index 0000000..ea1f0eb
--- /dev/null
+++ b/mojo/services/html_viewer/webstoragenamespace_impl.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/services/html_viewer/webstoragenamespace_impl.h"
+
+#include <stdio.h>
+
+#include "third_party/WebKit/public/platform/WebStorageArea.h"
+
+namespace mojo {
+namespace {
+
+class DummyWebStorageAreaImpl : public blink::WebStorageArea {
+ public:
+ virtual unsigned length() {
+ return 0;
+ }
+ virtual blink::WebString key(unsigned index) {
+ return blink::WebString();
+ }
+ virtual blink::WebString getItem(const blink::WebString& key) {
+ return blink::WebString();
+ }
+};
+
+} // namespace
+
+WebStorageNamespaceImpl::WebStorageNamespaceImpl() {
+}
+
+WebStorageNamespaceImpl::~WebStorageNamespaceImpl() {
+}
+
+blink::WebStorageArea* WebStorageNamespaceImpl::createStorageArea(
+ const blink::WebString& origin) {
+ return new DummyWebStorageAreaImpl();
+}
+
+bool WebStorageNamespaceImpl::isSameNamespace(
+ const blink::WebStorageNamespace&) const {
+ return false;
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/webstoragenamespace_impl.h b/mojo/services/html_viewer/webstoragenamespace_impl.h
new file mode 100644
index 0000000..7e11069
--- /dev/null
+++ b/mojo/services/html_viewer/webstoragenamespace_impl.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_HTML_VIEWER_WEBSTORAGENAMESPACE_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBSTORAGENAMESPACE_IMPL_H_
+
+#include "base/macros.h"
+#include "third_party/WebKit/public/platform/WebStorageNamespace.h"
+
+namespace mojo {
+
+class WebStorageNamespaceImpl : public blink::WebStorageNamespace {
+ public:
+ WebStorageNamespaceImpl();
+
+ private:
+ virtual ~WebStorageNamespaceImpl();
+
+ // blink::WebStorageNamespace methods:
+ virtual blink::WebStorageArea* createStorageArea(
+ const blink::WebString& origin);
+ virtual bool isSameNamespace(const blink::WebStorageNamespace&) const;
+
+ DISALLOW_COPY_AND_ASSIGN(WebStorageNamespaceImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBSTORAGENAMESPACE_IMPL_H_
diff --git a/mojo/services/html_viewer/webthemeengine_impl.cc b/mojo/services/html_viewer/webthemeengine_impl.cc
new file mode 100644
index 0000000..24906e5
--- /dev/null
+++ b/mojo/services/html_viewer/webthemeengine_impl.cc
@@ -0,0 +1,202 @@
+// Copyright 2014 The Chromium 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/html_viewer/webthemeengine_impl.h"
+
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/public/platform/WebRect.h"
+#include "third_party/WebKit/public/platform/WebSize.h"
+#include "ui/native_theme/native_theme.h"
+
+using blink::WebCanvas;
+using blink::WebColor;
+using blink::WebRect;
+using blink::WebThemeEngine;
+
+namespace mojo {
+
+static ui::NativeTheme::Part NativeThemePart(
+ WebThemeEngine::Part part) {
+ switch (part) {
+ case WebThemeEngine::PartScrollbarDownArrow:
+ return ui::NativeTheme::kScrollbarDownArrow;
+ case WebThemeEngine::PartScrollbarLeftArrow:
+ return ui::NativeTheme::kScrollbarLeftArrow;
+ case WebThemeEngine::PartScrollbarRightArrow:
+ return ui::NativeTheme::kScrollbarRightArrow;
+ case WebThemeEngine::PartScrollbarUpArrow:
+ return ui::NativeTheme::kScrollbarUpArrow;
+ case WebThemeEngine::PartScrollbarHorizontalThumb:
+ return ui::NativeTheme::kScrollbarHorizontalThumb;
+ case WebThemeEngine::PartScrollbarVerticalThumb:
+ return ui::NativeTheme::kScrollbarVerticalThumb;
+ case WebThemeEngine::PartScrollbarHorizontalTrack:
+ return ui::NativeTheme::kScrollbarHorizontalTrack;
+ case WebThemeEngine::PartScrollbarVerticalTrack:
+ return ui::NativeTheme::kScrollbarVerticalTrack;
+ case WebThemeEngine::PartScrollbarCorner:
+ return ui::NativeTheme::kScrollbarCorner;
+ case WebThemeEngine::PartCheckbox:
+ return ui::NativeTheme::kCheckbox;
+ case WebThemeEngine::PartRadio:
+ return ui::NativeTheme::kRadio;
+ case WebThemeEngine::PartButton:
+ return ui::NativeTheme::kPushButton;
+ case WebThemeEngine::PartTextField:
+ return ui::NativeTheme::kTextField;
+ case WebThemeEngine::PartMenuList:
+ return ui::NativeTheme::kMenuList;
+ case WebThemeEngine::PartSliderTrack:
+ return ui::NativeTheme::kSliderTrack;
+ case WebThemeEngine::PartSliderThumb:
+ return ui::NativeTheme::kSliderThumb;
+ case WebThemeEngine::PartInnerSpinButton:
+ return ui::NativeTheme::kInnerSpinButton;
+ case WebThemeEngine::PartProgressBar:
+ return ui::NativeTheme::kProgressBar;
+ default:
+ return ui::NativeTheme::kScrollbarDownArrow;
+ }
+}
+
+static ui::NativeTheme::State NativeThemeState(
+ WebThemeEngine::State state) {
+ switch (state) {
+ case WebThemeEngine::StateDisabled:
+ return ui::NativeTheme::kDisabled;
+ case WebThemeEngine::StateHover:
+ return ui::NativeTheme::kHovered;
+ case WebThemeEngine::StateNormal:
+ return ui::NativeTheme::kNormal;
+ case WebThemeEngine::StatePressed:
+ return ui::NativeTheme::kPressed;
+ default:
+ return ui::NativeTheme::kDisabled;
+ }
+}
+
+static void GetNativeThemeExtraParams(
+ WebThemeEngine::Part part,
+ WebThemeEngine::State state,
+ const WebThemeEngine::ExtraParams* extra_params,
+ ui::NativeTheme::ExtraParams* native_theme_extra_params) {
+ switch (part) {
+ case WebThemeEngine::PartScrollbarHorizontalTrack:
+ case WebThemeEngine::PartScrollbarVerticalTrack:
+ native_theme_extra_params->scrollbar_track.track_x =
+ extra_params->scrollbarTrack.trackX;
+ native_theme_extra_params->scrollbar_track.track_y =
+ extra_params->scrollbarTrack.trackY;
+ native_theme_extra_params->scrollbar_track.track_width =
+ extra_params->scrollbarTrack.trackWidth;
+ native_theme_extra_params->scrollbar_track.track_height =
+ extra_params->scrollbarTrack.trackHeight;
+ break;
+ case WebThemeEngine::PartCheckbox:
+ native_theme_extra_params->button.checked = extra_params->button.checked;
+ native_theme_extra_params->button.indeterminate =
+ extra_params->button.indeterminate;
+ break;
+ case WebThemeEngine::PartRadio:
+ native_theme_extra_params->button.checked = extra_params->button.checked;
+ break;
+ case WebThemeEngine::PartButton:
+ native_theme_extra_params->button.is_default =
+ extra_params->button.isDefault;
+ native_theme_extra_params->button.has_border =
+ extra_params->button.hasBorder;
+ // Native buttons have a different focus style.
+ native_theme_extra_params->button.is_focused = false;
+ native_theme_extra_params->button.background_color =
+ extra_params->button.backgroundColor;
+ break;
+ case WebThemeEngine::PartTextField:
+ native_theme_extra_params->text_field.is_text_area =
+ extra_params->textField.isTextArea;
+ native_theme_extra_params->text_field.is_listbox =
+ extra_params->textField.isListbox;
+ native_theme_extra_params->text_field.background_color =
+ extra_params->textField.backgroundColor;
+ break;
+ case WebThemeEngine::PartMenuList:
+ native_theme_extra_params->menu_list.has_border =
+ extra_params->menuList.hasBorder;
+ native_theme_extra_params->menu_list.has_border_radius =
+ extra_params->menuList.hasBorderRadius;
+ native_theme_extra_params->menu_list.arrow_x =
+ extra_params->menuList.arrowX;
+ native_theme_extra_params->menu_list.arrow_y =
+ extra_params->menuList.arrowY;
+ native_theme_extra_params->menu_list.background_color =
+ extra_params->menuList.backgroundColor;
+ break;
+ case WebThemeEngine::PartSliderTrack:
+ case WebThemeEngine::PartSliderThumb:
+ native_theme_extra_params->slider.vertical =
+ extra_params->slider.vertical;
+ native_theme_extra_params->slider.in_drag = extra_params->slider.inDrag;
+ break;
+ case WebThemeEngine::PartInnerSpinButton:
+ native_theme_extra_params->inner_spin.spin_up =
+ extra_params->innerSpin.spinUp;
+ native_theme_extra_params->inner_spin.read_only =
+ extra_params->innerSpin.readOnly;
+ break;
+ case WebThemeEngine::PartProgressBar:
+ native_theme_extra_params->progress_bar.determinate =
+ extra_params->progressBar.determinate;
+ native_theme_extra_params->progress_bar.value_rect_x =
+ extra_params->progressBar.valueRectX;
+ native_theme_extra_params->progress_bar.value_rect_y =
+ extra_params->progressBar.valueRectY;
+ native_theme_extra_params->progress_bar.value_rect_width =
+ extra_params->progressBar.valueRectWidth;
+ native_theme_extra_params->progress_bar.value_rect_height =
+ extra_params->progressBar.valueRectHeight;
+ break;
+ default:
+ break; // Parts that have no extra params get here.
+ }
+}
+
+blink::WebSize WebThemeEngineImpl::getSize(WebThemeEngine::Part part) {
+ ui::NativeTheme::ExtraParams extra;
+ return ui::NativeTheme::instance()->GetPartSize(NativeThemePart(part),
+ ui::NativeTheme::kNormal,
+ extra);
+}
+
+void WebThemeEngineImpl::paint(
+ blink::WebCanvas* canvas,
+ WebThemeEngine::Part part,
+ WebThemeEngine::State state,
+ const blink::WebRect& rect,
+ const WebThemeEngine::ExtraParams* extra_params) {
+ ui::NativeTheme::ExtraParams native_theme_extra_params;
+ GetNativeThemeExtraParams(
+ part, state, extra_params, &native_theme_extra_params);
+ ui::NativeTheme::instance()->Paint(
+ canvas,
+ NativeThemePart(part),
+ NativeThemeState(state),
+ gfx::Rect(rect),
+ native_theme_extra_params);
+}
+
+void WebThemeEngineImpl::paintStateTransition(blink::WebCanvas* canvas,
+ WebThemeEngine::Part part,
+ WebThemeEngine::State startState,
+ WebThemeEngine::State endState,
+ double progress,
+ const blink::WebRect& rect) {
+ ui::NativeTheme::instance()->PaintStateTransition(
+ canvas,
+ NativeThemePart(part),
+ NativeThemeState(startState),
+ NativeThemeState(endState),
+ progress,
+ gfx::Rect(rect));
+}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/webthemeengine_impl.h b/mojo/services/html_viewer/webthemeengine_impl.h
new file mode 100644
index 0000000..a3eda9e
--- /dev/null
+++ b/mojo/services/html_viewer/webthemeengine_impl.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_HTML_VIEWER_WEBTHEMEENGINE_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBTHEMEENGINE_IMPL_H_
+
+#include "third_party/WebKit/public/platform/WebThemeEngine.h"
+
+namespace mojo {
+
+class WebThemeEngineImpl : public blink::WebThemeEngine {
+ public:
+ // WebThemeEngine methods:
+ virtual blink::WebSize getSize(blink::WebThemeEngine::Part);
+ virtual void paint(
+ blink::WebCanvas* canvas,
+ blink::WebThemeEngine::Part part,
+ blink::WebThemeEngine::State state,
+ const blink::WebRect& rect,
+ const blink::WebThemeEngine::ExtraParams* extra_params);
+ virtual void paintStateTransition(blink::WebCanvas* canvas,
+ blink::WebThemeEngine::Part part,
+ blink::WebThemeEngine::State startState,
+ blink::WebThemeEngine::State endState,
+ double progress,
+ const blink::WebRect& rect);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBTHEMEENGINE_IMPL_H_
diff --git a/mojo/services/html_viewer/webthread_impl.cc b/mojo/services/html_viewer/webthread_impl.cc
new file mode 100644
index 0000000..678d22e
--- /dev/null
+++ b/mojo/services/html_viewer/webthread_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.
+
+// An implementation of WebThread in terms of base::MessageLoop and
+// base::Thread
+
+#include "mojo/services/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 {
+
+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();
+}
+
+blink::PlatformThreadId WebThreadImpl::threadId() const {
+ return thread_->thread_id();
+}
+
+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();
+}
+
+blink::PlatformThreadId WebThreadImplForMessageLoop::threadId() const {
+ return thread_id_;
+}
+
+WebThreadImplForMessageLoop::~WebThreadImplForMessageLoop() {}
+
+} // namespace mojo
diff --git a/mojo/services/html_viewer/webthread_impl.h b/mojo/services/html_viewer/webthread_impl.h
new file mode 100644
index 0000000..93d7aeb
--- /dev/null
+++ b/mojo/services/html_viewer/webthread_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_SERVICES_HTML_VIEWER_WEBTHREAD_IMPL_H_
+#define MOJO_SERVICES_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 {
+
+class WebThreadBase : public blink::WebThread {
+ public:
+ virtual ~WebThreadBase();
+
+ virtual void addTaskObserver(TaskObserver* observer);
+ virtual void removeTaskObserver(TaskObserver* observer);
+
+ virtual bool isCurrentThread() const = 0;
+ virtual blink::PlatformThreadId threadId() 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;
+ virtual blink::PlatformThreadId threadId() 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;
+ virtual blink::PlatformThreadId threadId() const;
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+ blink::PlatformThreadId thread_id_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBTHREAD_IMPL_H_
diff --git a/mojo/services/html_viewer/weburlloader_impl.cc b/mojo/services/html_viewer/weburlloader_impl.cc
new file mode 100644
index 0000000..fb82bcf
--- /dev/null
+++ b/mojo/services/html_viewer/weburlloader_impl.cc
@@ -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.
+
+#include "mojo/services/html_viewer/weburlloader_impl.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/services/html_viewer/blink_url_request_type_converters.h"
+#include "mojo/services/public/interfaces/network/network_service.mojom.h"
+#include "net/base/net_errors.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+#include "third_party/WebKit/public/platform/WebURLLoadTiming.h"
+#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+
+namespace mojo {
+namespace {
+
+static blink::WebURLResponse::HTTPVersion StatusLineToHTTPVersion(
+ const String& status_line) {
+ if (status_line.is_null())
+ return blink::WebURLResponse::HTTP_0_9;
+
+ if (StartsWithASCII(status_line, "HTTP/1.0", true))
+ return blink::WebURLResponse::HTTP_1_0;
+
+ if (StartsWithASCII(status_line, "HTTP/1.1", true))
+ return blink::WebURLResponse::HTTP_1_1;
+
+ return blink::WebURLResponse::Unknown;
+}
+
+blink::WebURLResponse ToWebURLResponse(const URLResponsePtr& url_response) {
+ blink::WebURLResponse result;
+ result.initialize();
+ result.setURL(GURL(url_response->url));
+ result.setMIMEType(blink::WebString::fromUTF8(url_response->mime_type));
+ result.setTextEncodingName(blink::WebString::fromUTF8(url_response->charset));
+ result.setHTTPVersion(StatusLineToHTTPVersion(url_response->status_line));
+ result.setHTTPStatusCode(url_response->status_code);
+
+ // TODO(darin): Initialize timing properly.
+ blink::WebURLLoadTiming timing;
+ timing.initialize();
+ result.setLoadTiming(timing);
+
+ for (size_t i = 0; i < url_response->headers.size(); ++i) {
+ const std::string& header_line = url_response->headers[i];
+ size_t first_colon = header_line.find(":");
+
+ if (first_colon == std::string::npos || first_colon == 0)
+ continue;
+
+ std::string value;
+ TrimWhitespaceASCII(header_line.substr(first_colon + 1),
+ base::TRIM_LEADING,
+ &value);
+ result.setHTTPHeaderField(
+ blink::WebString::fromUTF8(header_line.substr(0, first_colon)),
+ blink::WebString::fromUTF8(value));
+ }
+
+ return result;
+}
+
+} // namespace
+
+WebURLRequestExtraData::WebURLRequestExtraData() {
+}
+
+WebURLRequestExtraData::~WebURLRequestExtraData() {
+}
+
+WebURLLoaderImpl::WebURLLoaderImpl(NetworkService* network_service)
+ : client_(NULL),
+ weak_factory_(this) {
+ network_service->CreateURLLoader(Get(&url_loader_));
+}
+
+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;
+ url_ = request.url();
+
+ URLRequestPtr url_request = URLRequest::From(request);
+ url_request->auto_follow_redirects = false;
+
+ if (request.extraData()) {
+ WebURLRequestExtraData* extra_data =
+ static_cast<WebURLRequestExtraData*>(request.extraData());
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebURLLoaderImpl::OnReceivedResponse,
+ weak_factory_.GetWeakPtr(),
+ base::Passed(&extra_data->synthetic_response)));
+ } else {
+ url_loader_->Start(url_request.Pass(),
+ base::Bind(&WebURLLoaderImpl::OnReceivedResponse,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void WebURLLoaderImpl::cancel() {
+ url_loader_.reset();
+ response_body_stream_.reset();
+
+ URLResponsePtr failed_response(URLResponse::New());
+ failed_response->url = String::From(url_);
+ failed_response->error = NetworkError::New();
+ failed_response->error->code = net::ERR_ABORTED;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebURLLoaderImpl::OnReceivedResponse,
+ weak_factory_.GetWeakPtr(),
+ base::Passed(&failed_response)));
+}
+
+void WebURLLoaderImpl::setDefersLoading(bool defers_loading) {
+ NOTIMPLEMENTED();
+}
+
+void WebURLLoaderImpl::OnReceivedResponse(URLResponsePtr url_response) {
+ url_ = GURL(url_response->url);
+
+ if (url_response->error) {
+ OnReceivedError(url_response.Pass());
+ } else if (url_response->redirect_url) {
+ OnReceivedRedirect(url_response.Pass());
+ } else {
+ base::WeakPtr<WebURLLoaderImpl> self(weak_factory_.GetWeakPtr());
+ client_->didReceiveResponse(this, ToWebURLResponse(url_response));
+
+ // We may have been deleted during didReceiveResponse.
+ if (!self)
+ return;
+
+ // Start streaming data
+ response_body_stream_ = url_response->body.Pass();
+ ReadMore();
+ }
+}
+
+void WebURLLoaderImpl::OnReceivedError(URLResponsePtr url_response) {
+ blink::WebURLError web_error;
+ web_error.domain = blink::WebString::fromUTF8(net::kErrorDomain);
+ web_error.reason = url_response->error->code;
+ web_error.unreachableURL = GURL(url_response->url);
+ web_error.staleCopyInCache = false;
+ web_error.isCancellation =
+ url_response->error->code == net::ERR_ABORTED ? true : false;
+
+ client_->didFail(this, web_error);
+}
+
+void WebURLLoaderImpl::OnReceivedRedirect(URLResponsePtr url_response) {
+ blink::WebURLRequest new_request;
+ new_request.initialize();
+ new_request.setURL(GURL(url_response->redirect_url));
+ new_request.setHTTPMethod(
+ blink::WebString::fromUTF8(url_response->redirect_method));
+
+ client_->willSendRequest(this, new_request, ToWebURLResponse(url_response));
+ // TODO(darin): Check if new_request was rejected.
+
+ url_loader_->FollowRedirect(
+ base::Bind(&WebURLLoaderImpl::OnReceivedResponse,
+ weak_factory_.GetWeakPtr()));
+}
+
+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 mojo
diff --git a/mojo/services/html_viewer/weburlloader_impl.h b/mojo/services/html_viewer/weburlloader_impl.h
new file mode 100644
index 0000000..6bb30df
--- /dev/null
+++ b/mojo/services/html_viewer/weburlloader_impl.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_SERVICES_HTML_VIEWER_WEBURLLOADER_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBURLLOADER_IMPL_H_
+
+#include "base/macros.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"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+
+namespace mojo {
+class NetworkService;
+
+// The concrete type of WebURLRequest::ExtraData.
+class WebURLRequestExtraData : public blink::WebURLRequest::ExtraData {
+ public:
+ WebURLRequestExtraData();
+ virtual ~WebURLRequestExtraData();
+
+ URLResponsePtr synthetic_response;
+};
+
+class WebURLLoaderImpl : public blink::WebURLLoader {
+ 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;
+
+ void OnReceivedResponse(URLResponsePtr response);
+ void OnReceivedError(URLResponsePtr response);
+ void OnReceivedRedirect(URLResponsePtr response);
+ void ReadMore();
+ void WaitToReadMore();
+ void OnResponseBodyStreamReady(MojoResult result);
+
+ blink::WebURLLoaderClient* client_;
+ GURL url_;
+ URLLoaderPtr url_loader_;
+ ScopedDataPipeConsumerHandle response_body_stream_;
+ common::HandleWatcher handle_watcher_;
+
+ base::WeakPtrFactory<WebURLLoaderImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebURLLoaderImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_HTML_VIEWER_WEBURLLOADER_IMPL_H_
diff --git a/mojo/services/native_viewport/BUILD.gn b/mojo/services/native_viewport/BUILD.gn
new file mode 100644
index 0000000..036e8e9
--- /dev/null
+++ b/mojo/services/native_viewport/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+if (!is_android) {
+ shared_library("native_viewport") {
+ output_name = "mojo_native_viewport_service"
+
+ deps = [
+ ":lib",
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings:bindings",
+ "//mojo/services/public/interfaces/native_viewport",
+ "//ui/gl",
+ ]
+
+ sources = [ "main.cc" ]
+ }
+}
+
+source_set("lib") {
+ deps = [
+ "//base",
+ "//cc/surfaces",
+ "//gpu/command_buffer/service",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/services/gles2",
+ "//mojo/services/gles2:interfaces",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/surfaces",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/native_viewport",
+ "//mojo/services/public/interfaces/surfaces",
+ "//ui/events",
+ "//ui/events/platform",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ "//ui/platform_window",
+ ]
+
+ sources = [
+ "gpu_impl.cc",
+ "gpu_impl.h",
+ "native_viewport_impl.cc",
+ "native_viewport_impl.h",
+ "platform_viewport.h",
+ "platform_viewport_android.cc",
+ "platform_viewport_android.h",
+ "platform_viewport_mac.mm",
+ "platform_viewport_headless.cc",
+ "platform_viewport_headless.h",
+ "platform_viewport_win.cc",
+ "viewport_surface.cc",
+ "viewport_surface.h",
+ ]
+
+ if (is_ios) {
+ sources += [ "platform_viewport_stub.cc" ]
+ }
+
+ if (is_android) {
+ deps += [ "//mojo:jni_headers" ]
+ }
+
+ if (use_x11) {
+ sources += [ "platform_viewport_x11.cc" ]
+ deps += [
+ "//ui/events/platform/x11",
+ "//ui/platform_window/x11",
+ ]
+ }
+
+ if (use_ozone) {
+ sources += [ "platform_viewport_ozone.cc" ]
+ }
+}
diff --git a/mojo/services/native_viewport/DEPS b/mojo/services/native_viewport/DEPS
new file mode 100644
index 0000000..abf7ef8
--- /dev/null
+++ b/mojo/services/native_viewport/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+ "+cc/surfaces",
+ "+gpu/command_buffer/service/mailbox_manager.h",
+ "+mojo/application",
+ "+mojo/services/public/cpp/geometry",
+ "+mojo/services/public/cpp/input_events",
+ "+mojo/services/public/cpp/surfaces",
+ "+mojo/services/public/interfaces/gpu",
+ "+mojo/services/public/interfaces/native_viewport",
+ "+mojo/services/public/interfaces/geometry",
+ "+mojo/services/public/interfaces/surfaces",
+ "+mojo/services/gles2",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/gl",
+ "+ui/ozone/public",
+ "+ui/platform_window",
+]
diff --git a/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java b/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java
new file mode 100644
index 0000000..c3f8075
--- /dev/null
+++ b/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java
@@ -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.
+
+package org.chromium.mojo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Exposes SurfaceView to native code.
+ */
+@JNINamespace("mojo")
+public class PlatformViewportAndroid extends SurfaceView {
+
+ private long mNativeMojoViewport;
+ private final SurfaceHolder.Callback mSurfaceCallback;
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static void createForActivity(Activity activity, long nativeViewport) {
+ activity.setContentView(new PlatformViewportAndroid(activity, nativeViewport));
+ }
+
+ public PlatformViewportAndroid(Context context, long nativeViewport) {
+ super(context);
+
+ mNativeMojoViewport = nativeViewport;
+ assert mNativeMojoViewport != 0;
+
+ mSurfaceCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ assert mNativeMojoViewport != 0;
+ nativeSurfaceSetSize(mNativeMojoViewport, width, height);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ assert mNativeMojoViewport != 0;
+ nativeSurfaceCreated(mNativeMojoViewport, holder.getSurface());
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ assert mNativeMojoViewport != 0;
+ nativeSurfaceDestroyed(mNativeMojoViewport);
+ }
+ };
+ getHolder().addCallback(mSurfaceCallback);
+
+ }
+
+ // TODO(abarth): Someone needs to call destroy at some point.
+ public void destroy() {
+ getHolder().removeCallback(mSurfaceCallback);
+ nativeDestroy(mNativeMojoViewport);
+ mNativeMojoViewport = 0;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return nativeTouchEvent(mNativeMojoViewport,
+ event.getPointerId(0),
+ event.getAction(),
+ event.getX(), event.getY(),
+ event.getEventTime());
+ }
+
+ private static native void nativeDestroy(long nativePlatformViewportAndroid);
+ private static native void nativeSurfaceCreated(
+ long nativePlatformViewportAndroid, Surface surface);
+ private static native void nativeSurfaceDestroyed(
+ long nativePlatformViewportAndroid);
+ private static native void nativeSurfaceSetSize(
+ long nativePlatformViewportAndroid,
+ int width, int height);
+ private static native boolean nativeTouchEvent(
+ long nativePlatformViewportAndroid,
+ int pointerId,
+ int action,
+ float x, float y,
+ long timeMs);
+};
diff --git a/mojo/services/native_viewport/gpu_impl.cc b/mojo/services/native_viewport/gpu_impl.cc
new file mode 100644
index 0000000..de0b69e
--- /dev/null
+++ b/mojo/services/native_viewport/gpu_impl.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 "mojo/services/native_viewport/gpu_impl.h"
+
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "mojo/services/gles2/command_buffer_impl.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "ui/gl/gl_share_group.h"
+
+namespace mojo {
+
+GpuImpl::GpuImpl(
+ const scoped_refptr<gfx::GLShareGroup>& share_group,
+ const scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager)
+ : share_group_(share_group), mailbox_manager_(mailbox_manager) {
+}
+
+GpuImpl::~GpuImpl() {
+}
+
+void GpuImpl::CreateOnscreenGLES2Context(
+ uint64_t native_viewport_id,
+ SizePtr size,
+ InterfaceRequest<CommandBuffer> command_buffer_request) {
+ gfx::AcceleratedWidget widget = bit_cast<gfx::AcceleratedWidget>(
+ static_cast<uintptr_t>(native_viewport_id));
+ BindToRequest(new CommandBufferImpl(widget,
+ size.To<gfx::Size>(),
+ share_group_.get(),
+ mailbox_manager_.get()),
+ &command_buffer_request);
+}
+
+void GpuImpl::CreateOffscreenGLES2Context(
+ InterfaceRequest<CommandBuffer> command_buffer_request) {
+ BindToRequest(
+ new CommandBufferImpl(share_group_.get(), mailbox_manager_.get()),
+ &command_buffer_request);
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/gpu_impl.h b/mojo/services/native_viewport/gpu_impl.h
new file mode 100644
index 0000000..ac0f435
--- /dev/null
+++ b/mojo/services/native_viewport/gpu_impl.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.
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/public/cpp/bindings/interface_impl.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/services/gles2/command_buffer.mojom.h"
+#include "mojo/services/public/interfaces/geometry/geometry.mojom.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+
+namespace gfx {
+class GLShareGroup;
+}
+
+namespace gpu {
+namespace gles2 {
+class MailboxManager;
+}
+}
+
+namespace mojo {
+
+class GpuImpl : public InterfaceImpl<Gpu> {
+ public:
+ GpuImpl(const scoped_refptr<gfx::GLShareGroup>& share_group,
+ const scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager);
+
+ virtual ~GpuImpl();
+
+ virtual void CreateOnscreenGLES2Context(
+ uint64_t native_viewport_id,
+ SizePtr size,
+ InterfaceRequest<CommandBuffer> command_buffer_request) override;
+
+ virtual void CreateOffscreenGLES2Context(
+ InterfaceRequest<CommandBuffer> command_buffer_request) override;
+
+ private:
+ // We need to share these across all NativeViewport instances so that contexts
+ // they create can share resources with each other via mailboxes.
+ scoped_refptr<gfx::GLShareGroup> share_group_;
+ scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuImpl);
+};
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/main.cc b/mojo/services/native_viewport/main.cc
new file mode 100644
index 0000000..0185e67
--- /dev/null
+++ b/mojo/services/native_viewport/main.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 "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/services/native_viewport/gpu_impl.h"
+#include "mojo/services/native_viewport/native_viewport_impl.h"
+#include "ui/gl/gl_share_group.h"
+#include "ui/gl/gl_surface.h"
+
+namespace mojo {
+
+class NativeViewportAppDelegate
+ : public ApplicationDelegate,
+ public InterfaceFactory<NativeViewport>,
+ public InterfaceFactory<Gpu>,
+ public InterfaceFactory<NativeViewportConfig> {
+ public:
+ NativeViewportAppDelegate()
+ : share_group_(new gfx::GLShareGroup),
+ mailbox_manager_(new gpu::gles2::MailboxManager),
+ is_test_(false),
+ is_headless_(false),
+ is_initialized_(false) {}
+ virtual ~NativeViewportAppDelegate() {}
+
+ private:
+ class NativeViewportConfigImpl : public InterfaceImpl<NativeViewportConfig> {
+ public:
+ NativeViewportConfigImpl(NativeViewportAppDelegate* app_delegate)
+ : app_delegate_(app_delegate) {}
+
+ virtual void UseTestConfig(
+ const Callback<void()>& callback) override {
+ app_delegate_->is_test_ = true;
+ callback.Run();
+ }
+
+ virtual void UseHeadlessConfig(
+ const Callback<void()>& callback) override {
+ app_delegate_->is_headless_ = true;
+ callback.Run();
+ }
+
+ private:
+ NativeViewportAppDelegate* app_delegate_;
+ };
+
+ // ApplicationDelegate implementation.
+ virtual void Initialize(ApplicationImpl* application) override {
+ app_ = application;
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService<NativeViewport>(this);
+ connection->AddService<Gpu>(this);
+ connection->AddService<NativeViewportConfig>(this);
+ return true;
+ }
+
+ // InterfaceFactory<NativeViewport> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<NativeViewport> request) override {
+#if !defined(COMPONENT_BUILD)
+ if (!is_initialized_) {
+ if (is_test_)
+ gfx::GLSurface::InitializeOneOffForTests();
+ else
+ gfx::GLSurface::InitializeOneOff();
+ is_initialized_ = true;
+ }
+#endif
+ BindToRequest(new NativeViewportImpl(app_, is_headless_), &request);
+ }
+
+ // InterfaceFactory<Gpu> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Gpu> request) override {
+ BindToRequest(new GpuImpl(share_group_.get(), mailbox_manager_.get()),
+ &request);
+ }
+
+ // InterfaceFactory<NVTestConfig> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<NativeViewportConfig> request) override {
+ BindToRequest(new NativeViewportConfigImpl(this), &request);
+ }
+
+ ApplicationImpl* app_;
+ scoped_refptr<gfx::GLShareGroup> share_group_;
+ scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_;
+ bool is_test_;
+ bool is_headless_;
+ bool is_initialized_;
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportAppDelegate);
+};
+}
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::NativeViewportAppDelegate);
+ runner.set_message_loop_type(base::MessageLoop::TYPE_UI);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/native_viewport/native_viewport_impl.cc b/mojo/services/native_viewport/native_viewport_impl.cc
new file mode 100644
index 0000000..3006612
--- /dev/null
+++ b/mojo/services/native_viewport/native_viewport_impl.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/services/native_viewport/native_viewport_impl.h"
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/native_viewport/platform_viewport_headless.h"
+#include "mojo/services/native_viewport/viewport_surface.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/cpp/surfaces/surfaces_type_converters.h"
+#include "ui/events/event.h"
+
+namespace mojo {
+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;
+}
+
+} // namespace
+
+NativeViewportImpl::NativeViewportImpl(ApplicationImpl* app, bool is_headless)
+ : is_headless_(is_headless),
+ widget_id_(0u),
+ waiting_for_event_ack_(false),
+ weak_factory_(this) {
+ app->ConnectToService("mojo:mojo_surfaces_service", &surfaces_service_);
+ // TODO(jamesr): Should be mojo_gpu_service
+ app->ConnectToService("mojo:mojo_native_viewport_service", &gpu_service_);
+}
+
+NativeViewportImpl::~NativeViewportImpl() {
+ // Destroy the NativeViewport early on as it may call us back during
+ // destruction and we want to be in a known state.
+ platform_viewport_.reset();
+}
+
+void NativeViewportImpl::Create(SizePtr size,
+ const Callback<void(uint64_t)>& callback) {
+ create_callback_ = callback;
+ size_ = size.To<gfx::Size>();
+ if (is_headless_)
+ platform_viewport_ = PlatformViewportHeadless::Create(this);
+ else
+ platform_viewport_ = PlatformViewport::Create(this);
+ platform_viewport_->Init(gfx::Rect(size.To<gfx::Size>()));
+}
+
+void NativeViewportImpl::Show() {
+ platform_viewport_->Show();
+}
+
+void NativeViewportImpl::Hide() {
+ platform_viewport_->Hide();
+}
+
+void NativeViewportImpl::Close() {
+ DCHECK(platform_viewport_);
+ platform_viewport_->Close();
+}
+
+void NativeViewportImpl::SetSize(SizePtr size) {
+ platform_viewport_->SetBounds(gfx::Rect(size.To<gfx::Size>()));
+}
+
+void NativeViewportImpl::SubmittedFrame(SurfaceIdPtr child_surface_id) {
+ if (child_surface_id_.is_null()) {
+ // If this is the first indication that the client will use surfaces,
+ // initialize that system.
+ // TODO(jamesr): When everything is converted to surfaces initialize this
+ // eagerly.
+ viewport_surface_.reset(
+ new ViewportSurface(surfaces_service_.get(),
+ gpu_service_.get(),
+ size_,
+ child_surface_id.To<cc::SurfaceId>()));
+ if (widget_id_)
+ viewport_surface_->SetWidgetId(widget_id_);
+ }
+ child_surface_id_ = child_surface_id.To<cc::SurfaceId>();
+ if (viewport_surface_)
+ viewport_surface_->SetChildId(child_surface_id_);
+}
+
+void NativeViewportImpl::OnBoundsChanged(const gfx::Rect& bounds) {
+ if (size_ == bounds.size())
+ return;
+
+ size_ = bounds.size();
+
+ // Wait for the accelerated widget before telling the client of the bounds.
+ if (create_callback_.is_null())
+ ProcessOnBoundsChanged();
+}
+
+void NativeViewportImpl::OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) {
+ widget_id_ = static_cast<uint64_t>(bit_cast<uintptr_t>(widget));
+ // TODO(jamesr): Remove once everything is converted to surfaces.
+ create_callback_.Run(widget_id_);
+ create_callback_.reset();
+ // Immediately tell the client of the size. The size may be wrong, if so we'll
+ // get the right one in the next OnBoundsChanged() call.
+ ProcessOnBoundsChanged();
+ if (viewport_surface_)
+ viewport_surface_->SetWidgetId(widget_id_);
+}
+
+bool NativeViewportImpl::OnEvent(ui::Event* ui_event) {
+ // Must not return early before updating capture.
+ switch (ui_event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ case ui::ET_TOUCH_PRESSED:
+ platform_viewport_->SetCapture();
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ case ui::ET_TOUCH_RELEASED:
+ platform_viewport_->ReleaseCapture();
+ break;
+ default:
+ break;
+ }
+
+ if (waiting_for_event_ack_ && IsRateLimitedEventType(ui_event))
+ return false;
+
+ client()->OnEvent(
+ Event::From(*ui_event),
+ base::Bind(&NativeViewportImpl::AckEvent, weak_factory_.GetWeakPtr()));
+ waiting_for_event_ack_ = true;
+ return false;
+}
+
+void NativeViewportImpl::OnDestroyed() {
+ client()->OnDestroyed();
+}
+
+void NativeViewportImpl::AckEvent() {
+ waiting_for_event_ack_ = false;
+}
+
+void NativeViewportImpl::ProcessOnBoundsChanged() {
+ client()->OnSizeChanged(Size::From(size_));
+ if (viewport_surface_)
+ viewport_surface_->SetSize(size_);
+}
+
+} // namespace mojo
+
diff --git a/mojo/services/native_viewport/native_viewport_impl.h b/mojo/services/native_viewport/native_viewport_impl.h
new file mode 100644
index 0000000..03918cb
--- /dev/null
+++ b/mojo/services/native_viewport/native_viewport_impl.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_SERVICES_NATIVE_VIEWPORT_IMPL_H_
+#define MOJO_SERVICES_NATIVE_VIEWPORT_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/surfaces/surface_id.h"
+#include "mojo/services/native_viewport/platform_viewport.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace ui {
+class Event;
+}
+
+namespace mojo {
+class ApplicationImpl;
+class ViewportSurface;
+
+class NativeViewportImpl : public InterfaceImpl<NativeViewport>,
+ public PlatformViewport::Delegate {
+ public:
+ NativeViewportImpl(ApplicationImpl* app, bool is_headless);
+ virtual ~NativeViewportImpl();
+
+ // InterfaceImpl<NativeViewport> implementation.
+ virtual void Create(SizePtr size,
+ const Callback<void(uint64_t)>& callback) override;
+ virtual void Show() override;
+ virtual void Hide() override;
+ virtual void Close() override;
+ virtual void SetSize(SizePtr size) override;
+ virtual void SubmittedFrame(SurfaceIdPtr surface_id) override;
+
+ // PlatformViewport::Delegate implementation.
+ virtual void OnBoundsChanged(const gfx::Rect& bounds) override;
+ virtual void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) override;
+ virtual bool OnEvent(ui::Event* ui_event) override;
+ virtual void OnDestroyed() override;
+
+ void AckEvent();
+
+ private:
+ void ProcessOnBoundsChanged();
+
+ bool is_headless_;
+ scoped_ptr<PlatformViewport> platform_viewport_;
+ scoped_ptr<ViewportSurface> viewport_surface_;
+ uint64_t widget_id_;
+ gfx::Size size_;
+ GpuPtr gpu_service_;
+ SurfacesServicePtr surfaces_service_;
+ cc::SurfaceId child_surface_id_;
+ bool waiting_for_event_ack_;
+ Callback<void(uint64_t)> create_callback_;
+ base::WeakPtrFactory<NativeViewportImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NATIVE_VIEWPORT_IMPL_H_
diff --git a/mojo/services/native_viewport/platform_viewport.h b/mojo/services/native_viewport/platform_viewport.h
new file mode 100644
index 0000000..a3ba3dc
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport.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_SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_
+#define MOJO_SERVICES_NATIVE_VIEWPORT_PLATFORM_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 {
+
+// Encapsulation of platform-specific Viewport.
+class PlatformViewport {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual void OnBoundsChanged(const gfx::Rect& rect) = 0;
+ virtual void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) = 0;
+ virtual bool OnEvent(ui::Event* ui_event) = 0;
+ virtual void OnDestroyed() = 0;
+ };
+
+ virtual ~PlatformViewport() {}
+
+ 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;
+
+ static scoped_ptr<PlatformViewport> Create(Delegate* delegate);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_
diff --git a/mojo/services/native_viewport/platform_viewport_android.cc b/mojo/services/native_viewport/platform_viewport_android.cc
new file mode 100644
index 0000000..bbe22c9
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_android.cc
@@ -0,0 +1,156 @@
+// 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/platform_viewport_android.h"
+
+#include <android/input.h>
+#include <android/native_window_jni.h>
+
+#include "base/android/jni_android.h"
+#include "jni/PlatformViewportAndroid_jni.h"
+#include "ui/events/event.h"
+#include "ui/gfx/point.h"
+
+namespace mojo {
+
+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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewportAndroid, public:
+
+// static
+bool PlatformViewportAndroid::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+PlatformViewportAndroid::PlatformViewportAndroid(Delegate* delegate)
+ : delegate_(delegate),
+ window_(NULL),
+ id_generator_(0),
+ weak_factory_(this) {
+}
+
+PlatformViewportAndroid::~PlatformViewportAndroid() {
+ if (window_)
+ ReleaseWindow();
+}
+
+void PlatformViewportAndroid::Destroy(JNIEnv* env, jobject obj) {
+ delegate_->OnDestroyed();
+}
+
+void PlatformViewportAndroid::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 PlatformViewportAndroid::SurfaceDestroyed(JNIEnv* env, jobject obj) {
+ DCHECK(window_);
+ ReleaseWindow();
+}
+
+void PlatformViewportAndroid::SurfaceSetSize(JNIEnv* env, jobject obj,
+ jint width, jint height) {
+ bounds_ = gfx::Rect(width, height);
+ delegate_->OnBoundsChanged(bounds_);
+}
+
+bool PlatformViewportAndroid::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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewportAndroid, PlatformViewport implementation:
+
+void PlatformViewportAndroid::Init(const gfx::Rect& bounds) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_PlatformViewportAndroid_createForActivity(
+ env,
+ base::android::GetApplicationContext(),
+ reinterpret_cast<jlong>(this));
+}
+
+void PlatformViewportAndroid::Show() {
+ // Nothing to do. View is created visible.
+}
+
+void PlatformViewportAndroid::Hide() {
+ // Nothing to do. View is always visible.
+}
+
+void PlatformViewportAndroid::Close() {
+ // TODO(beng): close activity containing MojoView?
+
+ // TODO(beng): perform this in response to view destruction.
+ delegate_->OnDestroyed();
+}
+
+gfx::Size PlatformViewportAndroid::GetSize() {
+ return bounds_.size();
+}
+
+void PlatformViewportAndroid::SetBounds(const gfx::Rect& bounds) {
+ NOTIMPLEMENTED();
+}
+
+void PlatformViewportAndroid::SetCapture() {
+ NOTIMPLEMENTED();
+}
+
+void PlatformViewportAndroid::ReleaseCapture() {
+ NOTIMPLEMENTED();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewportAndroid, private:
+
+void PlatformViewportAndroid::ReleaseWindow() {
+ ANativeWindow_release(window_);
+ window_ = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewport, public:
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(
+ new PlatformViewportAndroid(delegate)).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_android.h b/mojo/services/native_viewport/platform_viewport_android.h
new file mode 100644
index 0000000..31b0312
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_android.h
@@ -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.
+
+#ifndef MOJO_SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_
+#define MOJO_SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_
+
+#include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/native_viewport/platform_viewport.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 {
+
+class PlatformViewportAndroid : public PlatformViewport {
+ public:
+ static bool Register(JNIEnv* env);
+
+ explicit PlatformViewportAndroid(Delegate* delegate);
+ virtual ~PlatformViewportAndroid();
+
+ 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 PlatformViewport:
+ 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();
+
+ Delegate* delegate_;
+ ANativeWindow* window_;
+ gfx::Rect bounds_;
+ ui::SequentialIDGenerator id_generator_;
+
+ base::WeakPtrFactory<PlatformViewportAndroid> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportAndroid);
+};
+
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_
diff --git a/mojo/services/native_viewport/platform_viewport_headless.cc b/mojo/services/native_viewport/platform_viewport_headless.cc
new file mode 100644
index 0000000..1c34711
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_headless.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/services/native_viewport/platform_viewport_headless.h"
+
+namespace mojo {
+
+PlatformViewportHeadless::PlatformViewportHeadless(Delegate* delegate)
+ : delegate_(delegate) {
+}
+
+PlatformViewportHeadless::~PlatformViewportHeadless() {
+}
+
+void PlatformViewportHeadless::Init(const gfx::Rect& bounds) {
+ bounds_ = bounds;
+}
+
+void PlatformViewportHeadless::Show() {
+}
+
+void PlatformViewportHeadless::Hide() {
+}
+
+void PlatformViewportHeadless::Close() {
+ delegate_->OnDestroyed();
+}
+
+gfx::Size PlatformViewportHeadless::GetSize() {
+ return bounds_.size();
+}
+
+void PlatformViewportHeadless::SetBounds(const gfx::Rect& bounds) {
+ bounds_ = bounds;
+ delegate_->OnBoundsChanged(bounds_);
+}
+
+void PlatformViewportHeadless::SetCapture() {
+}
+
+void PlatformViewportHeadless::ReleaseCapture() {
+}
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewportHeadless::Create(
+ Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(
+ new PlatformViewportHeadless(delegate)).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_headless.h b/mojo/services/native_viewport/platform_viewport_headless.h
new file mode 100644
index 0000000..9b9743f
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_headless.h
@@ -0,0 +1,36 @@
+// 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/macros.h"
+#include "mojo/services/native_viewport/platform_viewport.h"
+#include "ui/gfx/rect.h"
+
+namespace mojo {
+
+class PlatformViewportHeadless : public PlatformViewport {
+ public:
+ virtual ~PlatformViewportHeadless();
+
+ static scoped_ptr<PlatformViewport> Create(Delegate* delegate);
+
+ private:
+ explicit PlatformViewportHeadless(Delegate* delegate);
+
+ // Overridden from PlatformViewport:
+ 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;
+
+ Delegate* delegate_;
+ gfx::Rect bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportHeadless);
+};
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_mac.mm b/mojo/services/native_viewport/platform_viewport_mac.mm
new file mode 100644
index 0000000..9d11052
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_mac.mm
@@ -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 "mojo/services/native_viewport/platform_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 {
+
+class PlatformViewportMac : public PlatformViewport {
+ public:
+ PlatformViewportMac(Delegate* delegate)
+ : delegate_(delegate),
+ window_(nil) {
+ }
+
+ virtual ~PlatformViewportMac() {
+ [window_ orderOut:nil];
+ [window_ close];
+ }
+
+ private:
+ // Overridden from PlatformViewport:
+ 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();
+ }
+
+ Delegate* delegate_;
+ NSWindow* window_;
+ gfx::Rect rect_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportMac);
+};
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(new PlatformViewportMac(delegate)).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_ozone.cc b/mojo/services/native_viewport/platform_viewport_ozone.cc
new file mode 100644
index 0000000..b8076fb
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_ozone.cc
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium 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/platform_viewport.h"
+
+#include "ui/events/event.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/ozone/public/cursor_factory_ozone.h"
+#include "ui/ozone/public/ozone_platform.h"
+#include "ui/ozone/public/surface_factory_ozone.h"
+#include "ui/platform_window/platform_window.h"
+#include "ui/platform_window/platform_window_delegate.h"
+
+namespace mojo {
+
+// TODO(spang): Deduplicate with PlatformViewportX11.. but there's a hack
+// in there that prevents this.
+class PlatformViewportOzone : public PlatformViewport,
+ public ui::PlatformWindowDelegate {
+ public:
+ explicit PlatformViewportOzone(Delegate* delegate) : delegate_(delegate) {
+ ui::OzonePlatform::InitializeForUI();
+ }
+
+ virtual ~PlatformViewportOzone() {
+ // Destroy the platform-window while |this| is still alive.
+ platform_window_.reset();
+ }
+
+ private:
+ // Overridden from PlatformViewport:
+ virtual void Init(const gfx::Rect& bounds) OVERRIDE {
+ platform_window_ =
+ ui::OzonePlatform::GetInstance()->CreatePlatformWindow(this, bounds);
+ }
+
+ virtual void Show() OVERRIDE { platform_window_->Show(); }
+
+ virtual void Hide() OVERRIDE { platform_window_->Hide(); }
+
+ virtual void Close() OVERRIDE { platform_window_->Close(); }
+
+ virtual gfx::Size GetSize() OVERRIDE {
+ return platform_window_->GetBounds().size();
+ }
+
+ virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE {
+ platform_window_->SetBounds(bounds);
+ }
+
+ virtual void SetCapture() OVERRIDE { platform_window_->SetCapture(); }
+
+ virtual void ReleaseCapture() OVERRIDE { platform_window_->ReleaseCapture(); }
+
+ // ui::PlatformWindowDelegate:
+ virtual void OnBoundsChanged(const gfx::Rect& new_bounds) OVERRIDE {
+ delegate_->OnBoundsChanged(new_bounds);
+ }
+
+ virtual void OnDamageRect(const gfx::Rect& damaged_region) OVERRIDE {}
+
+ virtual void DispatchEvent(ui::Event* event) OVERRIDE {
+ delegate_->OnEvent(event);
+ }
+
+ virtual void OnCloseRequest() OVERRIDE { platform_window_->Close(); }
+
+ virtual void OnClosed() OVERRIDE { delegate_->OnDestroyed(); }
+
+ virtual void OnWindowStateChanged(ui::PlatformWindowState state) OVERRIDE {}
+
+ virtual void OnLostCapture() OVERRIDE {}
+
+ virtual void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) OVERRIDE {
+ delegate_->OnAcceleratedWidgetAvailable(widget);
+ }
+
+ virtual void OnActivationChanged(bool active) OVERRIDE {}
+
+ scoped_ptr<ui::PlatformWindow> platform_window_;
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportOzone);
+};
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(
+ new PlatformViewportOzone(delegate)).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_stub.cc b/mojo/services/native_viewport/platform_viewport_stub.cc
new file mode 100644
index 0000000..80a7e94
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_stub.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 "mojo/services/native_viewport/platform_viewport_headless.h"
+
+namespace mojo {
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return PlatformViewportHeadless::Create(delegate);
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_win.cc b/mojo/services/native_viewport/platform_viewport_win.cc
new file mode 100644
index 0000000..2227112
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_win.cc
@@ -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.
+
+#include "mojo/services/native_viewport/platform_viewport.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/rect.h"
+#include "ui/platform_window/platform_window_delegate.h"
+#include "ui/platform_window/win/win_window.h"
+
+namespace mojo {
+
+class PlatformViewportWin : public PlatformViewport,
+ public ui::PlatformWindowDelegate {
+ public:
+ explicit PlatformViewportWin(Delegate* delegate)
+ : delegate_(delegate) {
+ }
+
+ virtual ~PlatformViewportWin() {
+ // Destroy the platform-window while |this| is still alive.
+ platform_window_.reset();
+ }
+
+ private:
+ // Overridden from PlatformViewport:
+ virtual void Init(const gfx::Rect& bounds) OVERRIDE {
+ platform_window_.reset(new ui::WinWindow(this, bounds));
+ }
+
+ virtual void Show() OVERRIDE {
+ platform_window_->Show();
+ }
+
+ virtual void Hide() OVERRIDE {
+ platform_window_->Hide();
+ }
+
+ virtual void Close() OVERRIDE {
+ platform_window_->Close();
+ }
+
+ virtual gfx::Size GetSize() OVERRIDE {
+ return platform_window_->GetBounds().size();
+ }
+
+ virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE {
+ platform_window_->SetBounds(bounds);
+ }
+
+ virtual void SetCapture() OVERRIDE {
+ platform_window_->SetCapture();
+ }
+
+ virtual void ReleaseCapture() OVERRIDE {
+ platform_window_->ReleaseCapture();
+ }
+
+ // ui::PlatformWindowDelegate:
+ virtual void OnBoundsChanged(const gfx::Rect& new_bounds) OVERRIDE {
+ delegate_->OnBoundsChanged(new_bounds);
+ }
+
+ virtual void OnDamageRect(const gfx::Rect& damaged_region) OVERRIDE {
+ }
+
+ virtual void DispatchEvent(ui::Event* event) OVERRIDE {
+ delegate_->OnEvent(event);
+ }
+
+ virtual void OnCloseRequest() OVERRIDE {
+ platform_window_->Close();
+ }
+
+ virtual void OnClosed() OVERRIDE {
+ delegate_->OnDestroyed();
+ }
+
+ virtual void OnWindowStateChanged(ui::PlatformWindowState state) OVERRIDE {
+ }
+
+ virtual void OnLostCapture() OVERRIDE {
+ }
+
+ virtual void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) OVERRIDE {
+ delegate_->OnAcceleratedWidgetAvailable(widget);
+ }
+
+ virtual void OnActivationChanged(bool active) OVERRIDE {}
+
+ scoped_ptr<ui::PlatformWindow> platform_window_;
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportWin);
+};
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(new PlatformViewportWin(delegate)).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_x11.cc b/mojo/services/native_viewport/platform_viewport_x11.cc
new file mode 100644
index 0000000..d4914c7
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_x11.cc
@@ -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.
+
+#include "mojo/services/native_viewport/platform_viewport.h"
+
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.h"
+#include "ui/events/event.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/gfx/rect.h"
+#include "ui/platform_window/platform_window.h"
+#include "ui/platform_window/platform_window_delegate.h"
+#include "ui/platform_window/x11/x11_window.h"
+
+namespace mojo {
+
+class PlatformViewportX11 : public PlatformViewport,
+ public ui::PlatformWindowDelegate {
+ public:
+ explicit PlatformViewportX11(Delegate* delegate) : delegate_(delegate) {
+ }
+
+ virtual ~PlatformViewportX11() {
+ // Destroy the platform-window while |this| is still alive.
+ platform_window_.reset();
+ }
+
+ private:
+ // Overridden from PlatformViewport:
+ virtual void Init(const gfx::Rect& bounds) OVERRIDE {
+ CHECK(!event_source_);
+ CHECK(!platform_window_);
+
+ event_source_ = ui::PlatformEventSource::CreateDefault();
+
+ platform_window_.reset(new ui::X11Window(this));
+ platform_window_->SetBounds(bounds);
+ }
+
+ virtual void Show() OVERRIDE {
+ platform_window_->Show();
+ }
+
+ virtual void Hide() OVERRIDE {
+ platform_window_->Hide();
+ }
+
+ virtual void Close() OVERRIDE {
+ platform_window_->Close();
+ }
+
+ virtual gfx::Size GetSize() OVERRIDE {
+ return bounds_.size();
+ }
+
+ virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE {
+ platform_window_->SetBounds(bounds);
+ }
+
+ virtual void SetCapture() OVERRIDE {
+ platform_window_->SetCapture();
+ }
+
+ virtual void ReleaseCapture() OVERRIDE {
+ platform_window_->ReleaseCapture();
+ }
+
+ // ui::PlatformWindowDelegate:
+ virtual void OnBoundsChanged(const gfx::Rect& new_bounds) OVERRIDE {
+ bounds_ = new_bounds;
+ delegate_->OnBoundsChanged(new_bounds);
+ }
+
+ virtual void OnDamageRect(const gfx::Rect& damaged_region) OVERRIDE {
+ }
+
+ virtual void DispatchEvent(ui::Event* event) OVERRIDE {
+ delegate_->OnEvent(event);
+
+ // We want to emulate the WM_CHAR generation behaviour of Windows.
+ //
+ // On Linux, we've previously inserted characters by having
+ // InputMethodAuraLinux take all key down events and send a character event
+ // to the TextInputClient. This causes a mismatch in code that has to be
+ // shared between Windows and Linux, including blink code. Now that we're
+ // trying to have one way of doing things, we need to standardize on and
+ // emulate Windows character events.
+ //
+ // This is equivalent to what we're doing in the current Linux port, but
+ // done once instead of done multiple times in different places.
+ if (event->type() == ui::ET_KEY_PRESSED) {
+ ui::KeyEvent* key_press_event = static_cast<ui::KeyEvent*>(event);
+ ui::KeyEvent char_event(key_press_event->GetCharacter(),
+ key_press_event->key_code(),
+ key_press_event->flags());
+
+ DCHECK_EQ(key_press_event->GetCharacter(), char_event.GetCharacter());
+ DCHECK_EQ(key_press_event->key_code(), char_event.key_code());
+ DCHECK_EQ(key_press_event->flags(), char_event.flags());
+
+ char_event.SetExtendedKeyEventData(scoped_ptr<ui::ExtendedKeyEventData>(
+ new MojoExtendedKeyEventData(
+ key_press_event->GetLocatedWindowsKeyboardCode(),
+ key_press_event->GetText(),
+ key_press_event->GetUnmodifiedText())));
+ char_event.set_platform_keycode(key_press_event->platform_keycode());
+
+ delegate_->OnEvent(&char_event);
+ }
+ }
+
+ virtual void OnCloseRequest() OVERRIDE {
+ platform_window_->Close();
+ }
+
+ virtual void OnClosed() OVERRIDE {
+ delegate_->OnDestroyed();
+ }
+
+ virtual void OnWindowStateChanged(ui::PlatformWindowState state) OVERRIDE {
+ }
+
+ virtual void OnLostCapture() OVERRIDE {
+ }
+
+ virtual void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) OVERRIDE {
+ delegate_->OnAcceleratedWidgetAvailable(widget);
+ }
+
+ virtual void OnActivationChanged(bool active) OVERRIDE {}
+
+ scoped_ptr<ui::PlatformEventSource> event_source_;
+ scoped_ptr<ui::PlatformWindow> platform_window_;
+ Delegate* delegate_;
+ gfx::Rect bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportX11);
+};
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(new PlatformViewportX11(delegate)).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/viewport_surface.cc b/mojo/services/native_viewport/viewport_surface.cc
new file mode 100644
index 0000000..8786e8c
--- /dev/null
+++ b/mojo/services/native_viewport/viewport_surface.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/services/native_viewport/viewport_surface.h"
+
+#include "base/bind.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_utils.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+
+ViewportSurface::ViewportSurface(SurfacesService* surfaces_service,
+ Gpu* gpu_service,
+ const gfx::Size& size,
+ cc::SurfaceId child_id)
+ : gpu_service_(gpu_service),
+ widget_id_(0u),
+ size_(size),
+ child_id_(child_id),
+ weak_factory_(this) {
+ surfaces_service->CreateSurfaceConnection(
+ base::Bind(&ViewportSurface::OnSurfaceConnectionCreated,
+ weak_factory_.GetWeakPtr()));
+}
+
+ViewportSurface::~ViewportSurface() {
+}
+
+void ViewportSurface::SetWidgetId(uint64_t widget_id) {
+ widget_id_ = widget_id;
+ if (id_allocator_)
+ BindSurfaceToNativeViewport();
+}
+
+void ViewportSurface::SetSize(const gfx::Size& size) {
+ if (size_ == size)
+ return;
+
+ if (id_.is_null())
+ return;
+
+ surface_->DestroySurface(SurfaceId::From(id_));
+ if (widget_id_)
+ BindSurfaceToNativeViewport();
+}
+
+void ViewportSurface::SetChildId(cc::SurfaceId child_id) {
+ child_id_ = child_id;
+ SubmitFrame();
+}
+
+void ViewportSurface::OnSurfaceConnectionCreated(SurfacePtr surface,
+ uint32_t id_namespace) {
+ surface_ = surface.Pass();
+ surface_.set_client(this);
+ id_allocator_.reset(new cc::SurfaceIdAllocator(id_namespace));
+ if (widget_id_ != 0u)
+ BindSurfaceToNativeViewport();
+}
+
+void ViewportSurface::BindSurfaceToNativeViewport() {
+ CommandBufferPtr cb;
+ gpu_service_->CreateOnscreenGLES2Context(
+ widget_id_, Size::From(size_), Get(&cb));
+
+ id_ = id_allocator_->GenerateId();
+ surface_->CreateGLES2BoundSurface(
+ cb.Pass(), SurfaceId::From(id_), Size::From(size_));
+
+ SubmitFrame();
+}
+
+void ViewportSurface::SubmitFrame() {
+ if (child_id_.is_null() || id_.is_null())
+ return;
+
+ SurfaceQuadStatePtr surface_quad_state = SurfaceQuadState::New();
+ surface_quad_state->surface = SurfaceId::From(child_id_);
+
+ gfx::Rect bounds(size_);
+
+ QuadPtr surface_quad = Quad::New();
+ surface_quad->material = Material::MATERIAL_SURFACE_CONTENT;
+ surface_quad->rect = Rect::From(bounds);
+ surface_quad->opaque_rect = Rect::From(bounds);
+ surface_quad->visible_rect = Rect::From(bounds);
+ surface_quad->needs_blending = true;
+ surface_quad->shared_quad_state_index = 0;
+ surface_quad->surface_quad_state = surface_quad_state.Pass();
+
+ PassPtr pass = CreateDefaultPass(1, bounds);
+
+ pass->quads.push_back(surface_quad.Pass());
+ pass->shared_quad_states.push_back(CreateDefaultSQS(size_));
+
+ FramePtr frame = Frame::New();
+ frame->passes.push_back(pass.Pass());
+ frame->resources.resize(0u);
+ surface_->SubmitFrame(SurfaceId::From(id_), frame.Pass());
+}
+
+void ViewportSurface::ReturnResources(Array<ReturnedResourcePtr> resources) {
+ // We never submit resources so we should never get any back.
+ DCHECK_EQ(0u, resources.size());
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/viewport_surface.h b/mojo/services/native_viewport/viewport_surface.h
new file mode 100644
index 0000000..2ca8105
--- /dev/null
+++ b/mojo/services/native_viewport/viewport_surface.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_NATIVE_VIEWPORT_VIEWPORT_SURFACE_H_
+#define MOJO_SERVICES_NATIVE_VIEWPORT_VIEWPORT_SURFACE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/surfaces/surface_id.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+class SurfaceIdAllocator;
+}
+
+namespace mojo {
+
+// This manages the surface that draws to a particular NativeViewport instance.
+class ViewportSurface : public SurfaceClient {
+ public:
+ ViewportSurface(SurfacesService* surfaces_service,
+ Gpu* gpu_service,
+ const gfx::Size& size,
+ cc::SurfaceId child_id);
+ virtual ~ViewportSurface();
+
+ void SetWidgetId(uint64_t widget_id);
+ void SetSize(const gfx::Size& size);
+ void SetChildId(cc::SurfaceId child_id);
+
+ private:
+ void OnSurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace);
+ void BindSurfaceToNativeViewport();
+ void SubmitFrame();
+
+ // SurfaceClient implementation.
+ virtual void ReturnResources(Array<ReturnedResourcePtr> resources) OVERRIDE;
+
+ SurfacePtr surface_;
+ Gpu* gpu_service_;
+ uint64_t widget_id_;
+ gfx::Size size_;
+ scoped_ptr<cc::SurfaceIdAllocator> id_allocator_;
+ cc::SurfaceId id_;
+ cc::SurfaceId child_id_;
+ base::WeakPtrFactory<ViewportSurface> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewportSurface);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NATIVE_VIEWPORT_VIEWPORT_SURFACE_H_
diff --git a/mojo/services/network/BUILD.gn b/mojo/services/network/BUILD.gn
new file mode 100644
index 0000000..d6ae427
--- /dev/null
+++ b/mojo/services/network/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/mojo_services.gypi:mojo_network_service
+shared_library("network") {
+ output_name = "mojo_network_service"
+
+ deps = [
+ ":lib",
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings:bindings",
+ "//mojo/services/public/cpp/network",
+ "//mojo/services/public/interfaces/network",
+ ]
+
+ sources = [ "main.cc" ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_network_service_lib
+source_set("lib") {
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/cpp/network",
+ "//mojo/services/public/interfaces/network",
+ "//net",
+ "//url",
+ ]
+
+ sources = [
+ "cookie_store_impl.cc",
+ "cookie_store_impl.h",
+ "network_context.cc",
+ "network_context.h",
+ "network_service_impl.cc",
+ "network_service_impl.h",
+ "url_loader_impl.cc",
+ "url_loader_impl.h",
+ "web_socket_impl.cc",
+ "web_socket_impl.h",
+ ]
+}
diff --git a/mojo/services/network/DEPS b/mojo/services/network/DEPS
new file mode 100644
index 0000000..cb237df
--- /dev/null
+++ b/mojo/services/network/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+mojo/application",
+ "+mojo/services",
+ "+net",
+]
diff --git a/mojo/services/network/cookie_store_impl.cc b/mojo/services/network/cookie_store_impl.cc
new file mode 100644
index 0000000..0340933
--- /dev/null
+++ b/mojo/services/network/cookie_store_impl.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/network/cookie_store_impl.h"
+
+#include "mojo/common/common_type_converters.h"
+#include "mojo/services/network/network_context.h"
+#include "net/cookies/cookie_store.h"
+#include "net/url_request/url_request_context.h"
+
+namespace mojo {
+namespace {
+
+void AdaptGetCookiesCallback(const Callback<void(String)>& callback,
+ const std::string& cookies) {
+ callback.Run(cookies);
+}
+
+void AdaptSetCookieCallback(const Callback<void(bool)>& callback,
+ bool success) {
+ callback.Run(success);
+}
+
+} // namespace
+
+CookieStoreImpl::CookieStoreImpl(NetworkContext* context,
+ const GURL& origin)
+ : context_(context),
+ origin_(origin) {
+}
+
+CookieStoreImpl::~CookieStoreImpl() {
+}
+
+void CookieStoreImpl::Get(const String& url,
+ const Callback<void(String)>& callback) {
+ // TODO(darin): Perform origin restriction.
+ net::CookieStore* store = context_->url_request_context()->cookie_store();
+ if (!store) {
+ callback.Run(String());
+ return;
+ }
+ store->GetCookiesWithOptionsAsync(
+ url.To<GURL>(),
+ net::CookieOptions(),
+ base::Bind(&AdaptGetCookiesCallback, callback));
+}
+
+void CookieStoreImpl::Set(const String& url,
+ const String& cookie,
+ const Callback<void(bool)>& callback) {
+ // TODO(darin): Perform origin restriction.
+ net::CookieStore* store = context_->url_request_context()->cookie_store();
+ if (!store) {
+ callback.Run(false);
+ return;
+ }
+ store->SetCookieWithOptionsAsync(
+ url.To<GURL>(),
+ cookie,
+ net::CookieOptions(),
+ base::Bind(&AdaptSetCookieCallback, callback));
+}
+
+} // namespace mojo
diff --git a/mojo/services/network/cookie_store_impl.h b/mojo/services/network/cookie_store_impl.h
new file mode 100644
index 0000000..a2ff0b2
--- /dev/null
+++ b/mojo/services/network/cookie_store_impl.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_SERVICES_NETWORK_COOKIE_STORE_IMPL_H_
+#define MOJO_SERVICES_NETWORK_COOKIE_STORE_IMPL_H_
+
+#include "mojo/services/public/interfaces/network/cookie_store.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+class NetworkContext;
+
+class CookieStoreImpl : public InterfaceImpl<CookieStore> {
+ public:
+ CookieStoreImpl(NetworkContext* context, const GURL& origin);
+ virtual ~CookieStoreImpl();
+
+ private:
+ // CookieStore methods:
+ virtual void Get(const String& url,
+ const Callback<void(String)>& callback) OVERRIDE;
+ virtual void Set(const String& url, const String& cookie,
+ const Callback<void(bool)>& callback) OVERRIDE;
+
+ NetworkContext* context_;
+ GURL origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieStoreImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NETWORK_COOKIE_STORE_IMPL_H_
diff --git a/mojo/services/network/main.cc b/mojo/services/network/main.cc
new file mode 100644
index 0000000..8d637ac
--- /dev/null
+++ b/mojo/services/network/main.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.
+
+#include "base/at_exit.h"
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/services/network/network_context.h"
+#include "mojo/services/network/network_service_impl.h"
+
+class Delegate : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::NetworkService> {
+ public:
+ Delegate() {}
+
+ virtual void Initialize(mojo::ApplicationImpl* app) OVERRIDE {
+ base::FilePath base_path;
+ CHECK(PathService::Get(base::DIR_TEMP, &base_path));
+ base_path = base_path.Append(FILE_PATH_LITERAL("network_service"));
+ context_.reset(new mojo::NetworkContext(base_path));
+ }
+
+ // mojo::ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) OVERRIDE {
+ DCHECK(context_);
+ connection->AddService(this);
+ return true;
+ }
+
+ // mojo::InterfaceFactory<mojo::NetworkService> implementation.
+ virtual void Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::NetworkService> request) OVERRIDE {
+ mojo::BindToRequest(
+ new mojo::NetworkServiceImpl(connection, context_.get()), &request);
+ }
+
+ private:
+ scoped_ptr<mojo::NetworkContext> context_;
+};
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new Delegate);
+ runner.set_message_loop_type(base::MessageLoop::TYPE_IO);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/network/network_context.cc b/mojo/services/network/network_context.cc
new file mode 100644
index 0000000..a996522
--- /dev/null
+++ b/mojo/services/network/network_context.cc
@@ -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.
+
+#include "mojo/services/network/network_context.h"
+
+#include "base/base_paths.h"
+#include "base/path_service.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+
+namespace mojo {
+
+NetworkContext::NetworkContext(const base::FilePath& base_path) {
+ net::URLRequestContextBuilder builder;
+ builder.set_accept_language("en-us,en");
+ // TODO(darin): This is surely the wrong UA string.
+ builder.set_user_agent("Mojo/0.1");
+ builder.set_proxy_service(net::ProxyService::CreateDirect());
+ builder.set_transport_security_persister_path(base_path);
+
+ net::URLRequestContextBuilder::HttpCacheParams cache_params;
+ cache_params.path = base_path.Append(FILE_PATH_LITERAL("Cache"));
+ cache_params.type = net::URLRequestContextBuilder::HttpCacheParams::DISK;
+ builder.EnableHttpCache(cache_params);
+
+ builder.set_file_enabled(true);
+
+ url_request_context_.reset(builder.Build());
+}
+
+NetworkContext::~NetworkContext() {
+ // TODO(darin): Be careful about destruction order of member variables?
+}
+
+} // namespace mojo
diff --git a/mojo/services/network/network_context.h b/mojo/services/network/network_context.h
new file mode 100644
index 0000000..19245f4
--- /dev/null
+++ b/mojo/services/network/network_context.h
@@ -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.
+
+#ifndef MOJO_SERVICES_NETWORK_NETWORK_CONTEXT_H_
+#define MOJO_SERVICES_NETWORK_NETWORK_CONTEXT_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+class URLRequestContext;
+}
+
+namespace mojo {
+
+class NetworkContext {
+ public:
+ explicit NetworkContext(const base::FilePath& base_path);
+ ~NetworkContext();
+
+ net::URLRequestContext* url_request_context() {
+ return url_request_context_.get();
+ }
+
+ private:
+ scoped_ptr<net::URLRequestContext> url_request_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkContext);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NETWORK_NETWORK_CONTEXT_H_
diff --git a/mojo/services/network/network_service_impl.cc b/mojo/services/network/network_service_impl.cc
new file mode 100644
index 0000000..a576975
--- /dev/null
+++ b/mojo/services/network/network_service_impl.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/services/network/network_service_impl.h"
+
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/services/network/cookie_store_impl.h"
+#include "mojo/services/network/url_loader_impl.h"
+#include "mojo/services/network/web_socket_impl.h"
+
+namespace mojo {
+
+NetworkServiceImpl::NetworkServiceImpl(ApplicationConnection* connection,
+ NetworkContext* context)
+ : context_(context),
+ origin_(GURL(connection->GetRemoteApplicationURL()).GetOrigin()) {
+}
+
+NetworkServiceImpl::~NetworkServiceImpl() {
+}
+
+void NetworkServiceImpl::CreateURLLoader(InterfaceRequest<URLLoader> loader) {
+ // TODO(darin): Plumb origin_. Use for CORS.
+ BindToRequest(new URLLoaderImpl(context_), &loader);
+}
+
+void NetworkServiceImpl::GetCookieStore(InterfaceRequest<CookieStore> store) {
+ BindToRequest(new CookieStoreImpl(context_, origin_), &store);
+}
+
+void NetworkServiceImpl::CreateWebSocket(InterfaceRequest<WebSocket> socket) {
+ BindToRequest(new WebSocketImpl(context_), &socket);
+}
+
+void NetworkServiceImpl::CreateTCPBoundSocket(
+ NetAddressPtr local_address,
+ InterfaceRequest<TCPBoundSocket> bound_socket,
+ const Callback<void(NetworkErrorPtr, NetAddressPtr)>& callback) {
+ // TODO(brettw) implement this.
+ callback.Run(NetworkErrorPtr(), NetAddressPtr());
+}
+
+void NetworkServiceImpl::CreateTCPClientSocket(
+ NetAddressPtr remote_address,
+ ScopedDataPipeConsumerHandle send_stream,
+ ScopedDataPipeProducerHandle receive_stream,
+ InterfaceRequest<TCPClientSocket> client_socket,
+ const Callback<void(NetworkErrorPtr, NetAddressPtr)>& callback) {
+ // TODO(brettw) implement this.
+ callback.Run(NetworkErrorPtr(), NetAddressPtr());
+}
+
+} // namespace mojo
diff --git a/mojo/services/network/network_service_impl.h b/mojo/services/network/network_service_impl.h
new file mode 100644
index 0000000..9088e36
--- /dev/null
+++ b/mojo/services/network/network_service_impl.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_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"
+#include "url/gurl.h"
+
+namespace mojo {
+class ApplicationConnection;
+class NetworkContext;
+
+class NetworkServiceImpl : public InterfaceImpl<NetworkService> {
+ public:
+ NetworkServiceImpl(ApplicationConnection* connection,
+ NetworkContext* context);
+ virtual ~NetworkServiceImpl();
+
+ // NetworkService methods:
+ virtual void CreateURLLoader(InterfaceRequest<URLLoader> loader) override;
+ virtual void GetCookieStore(InterfaceRequest<CookieStore> store) override;
+ virtual void CreateWebSocket(InterfaceRequest<WebSocket> socket) override;
+ virtual void CreateTCPBoundSocket(
+ NetAddressPtr local_address,
+ InterfaceRequest<TCPBoundSocket> bound_socket,
+ const Callback<void(NetworkErrorPtr, NetAddressPtr)>& callback) override;
+ virtual void CreateTCPClientSocket(
+ NetAddressPtr remote_address,
+ ScopedDataPipeConsumerHandle send_stream,
+ ScopedDataPipeProducerHandle receive_stream,
+ InterfaceRequest<TCPClientSocket> client_socket,
+ const Callback<void(NetworkErrorPtr, NetAddressPtr)>& callback) override;
+
+ private:
+ NetworkContext* context_;
+ GURL origin_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NETWORK_NETWORK_SERVICE_IMPL_H_
diff --git a/mojo/services/network/url_loader_impl.cc b/mojo/services/network/url_loader_impl.cc
new file mode 100644
index 0000000..b5cbd0f
--- /dev/null
+++ b/mojo/services/network/url_loader_impl.cc
@@ -0,0 +1,384 @@
+// Copyright 2014 The Chromium 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 "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.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/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/redirect_info.h"
+#include "net/url_request/url_request_context.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 = String::From(url_request->url());
+
+ 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);
+ }
+
+ std::string mime_type;
+ url_request->GetMimeType(&mime_type);
+ response->mime_type = mime_type;
+
+ std::string charset;
+ url_request->GetCharset(&charset);
+ response->charset = charset;
+
+ return response.Pass();
+}
+
+NetworkErrorPtr MakeNetworkError(int error_code) {
+ NetworkErrorPtr error = NetworkError::New();
+ error->code = error_code;
+ error->description = net::ErrorToString(error_code);
+ return error.Pass();
+}
+
+// Reads the request body upload data from a DataPipe.
+class UploadDataPipeElementReader : public net::UploadElementReader {
+ public:
+ UploadDataPipeElementReader(ScopedDataPipeConsumerHandle pipe)
+ : pipe_(pipe.Pass()), num_bytes_(0) {}
+ virtual ~UploadDataPipeElementReader() {}
+
+ // UploadElementReader overrides:
+ virtual int Init(const net::CompletionCallback& callback) OVERRIDE {
+ offset_ = 0;
+ ReadDataRaw(pipe_.get(), NULL, &num_bytes_, MOJO_READ_DATA_FLAG_QUERY);
+ return net::OK;
+ }
+ virtual uint64 GetContentLength() const OVERRIDE {
+ return num_bytes_;
+ }
+ virtual uint64 BytesRemaining() const OVERRIDE {
+ return num_bytes_ - offset_;
+ }
+ virtual bool IsInMemory() const OVERRIDE {
+ return false;
+ }
+ virtual int Read(net::IOBuffer* buf,
+ int buf_length,
+ const net::CompletionCallback& callback) OVERRIDE {
+ uint32_t bytes_read =
+ std::min(static_cast<uint32_t>(BytesRemaining()),
+ static_cast<uint32_t>(buf_length));
+ if (bytes_read > 0) {
+ ReadDataRaw(pipe_.get(), buf->data(), &bytes_read,
+ MOJO_READ_DATA_FLAG_NONE);
+ }
+
+ offset_ += bytes_read;
+ return bytes_read;
+ }
+
+ private:
+ ScopedDataPipeConsumerHandle pipe_;
+ uint32_t num_bytes_;
+ uint32_t offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadDataPipeElementReader);
+};
+
+} // 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) {
+ }
+
+ MojoResult 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;
+ }
+
+ 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),
+ response_body_buffer_size_(0),
+ auto_follow_redirects_(true),
+ weak_ptr_factory_(this) {
+}
+
+URLLoaderImpl::~URLLoaderImpl() {
+}
+
+void URLLoaderImpl::Start(URLRequestPtr request,
+ const Callback<void(URLResponsePtr)>& callback) {
+ if (url_request_) {
+ SendError(net::ERR_UNEXPECTED, callback);
+ return;
+ }
+
+ if (!request) {
+ SendError(net::ERR_INVALID_ARGUMENT, callback);
+ return;
+ }
+
+ url_request_ = context_->url_request_context()->CreateRequest(
+ GURL(request->url),
+ net::DEFAULT_PRIORITY,
+ this,
+ NULL);
+ 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->body) {
+ ScopedVector<net::UploadElementReader> element_readers;
+ for (size_t i = 0; i < request->body.size(); ++i) {
+ element_readers.push_back(
+ new UploadDataPipeElementReader(request->body[i].Pass()));
+ }
+ url_request_->set_upload(make_scoped_ptr(
+ new net::UploadDataStream(element_readers.Pass(), 0)));
+ }
+ if (request->bypass_cache)
+ url_request_->SetLoadFlags(net::LOAD_BYPASS_CACHE);
+
+ callback_ = callback;
+ response_body_buffer_size_ = request->response_body_buffer_size;
+ auto_follow_redirects_ = request->auto_follow_redirects;
+
+ url_request_->Start();
+}
+
+void URLLoaderImpl::FollowRedirect(
+ const Callback<void(URLResponsePtr)>& callback) {
+ if (!url_request_) {
+ SendError(net::ERR_UNEXPECTED, callback);
+ return;
+ }
+
+ if (auto_follow_redirects_) {
+ DLOG(ERROR) << "Spurious call to FollowRedirect";
+ SendError(net::ERR_UNEXPECTED, callback);
+ return;
+ }
+
+ // TODO(darin): Verify that it makes sense to call FollowDeferredRedirect.
+ url_request_->FollowDeferredRedirect();
+}
+
+void URLLoaderImpl::QueryStatus(
+ const Callback<void(URLLoaderStatusPtr)>& callback) {
+ URLLoaderStatusPtr status(URLLoaderStatus::New());
+ if (url_request_) {
+ status->is_loading = url_request_->is_pending();
+ if (!url_request_->status().is_success())
+ status->error = MakeNetworkError(url_request_->status().error());
+ } else {
+ status->is_loading = false;
+ }
+ // TODO(darin): Populate more status fields.
+ callback.Run(status.Pass());
+}
+
+void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* url_request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) {
+ DCHECK(url_request == url_request_.get());
+ DCHECK(url_request->status().is_success());
+
+ if (auto_follow_redirects_)
+ return;
+
+ // Send the redirect response to the client, allowing them to inspect it and
+ // optionally follow the redirect.
+ *defer_redirect = true;
+
+ URLResponsePtr response = MakeURLResponse(url_request);
+ response->redirect_method = redirect_info.new_method;
+ response->redirect_url = String::From(redirect_info.new_url);
+
+ SendResponse(response.Pass());
+}
+
+void URLLoaderImpl::OnResponseStarted(net::URLRequest* url_request) {
+ DCHECK(url_request == url_request_.get());
+
+ if (!url_request->status().is_success()) {
+ SendError(url_request->status().error(), callback_);
+ callback_ = Callback<void(URLResponsePtr)>();
+ return;
+ }
+
+ // TODO(darin): Add support for optional MIME sniffing.
+
+ DataPipe data_pipe;
+ // TODO(darin): Honor given buffer size.
+
+ URLResponsePtr response = MakeURLResponse(url_request);
+ response->body = data_pipe.consumer_handle.Pass();
+ response_body_stream_ = data_pipe.producer_handle.Pass();
+
+ SendResponse(response.Pass());
+
+ // Start reading...
+ ReadMore();
+}
+
+void URLLoaderImpl::OnReadCompleted(net::URLRequest* url_request,
+ int bytes_read) {
+ DCHECK(url_request == url_request_.get());
+
+ if (url_request->status().is_success()) {
+ DidRead(static_cast<uint32_t>(bytes_read), false);
+ } else {
+ pending_write_ = NULL; // This closes the data pipe.
+ }
+}
+
+void URLLoaderImpl::SendError(
+ int error_code,
+ const Callback<void(URLResponsePtr)>& callback) {
+ URLResponsePtr response(URLResponse::New());
+ if (url_request_)
+ response->url = String::From(url_request_->url());
+ response->error = MakeNetworkError(error_code);
+ callback.Run(response.Pass());
+}
+
+void URLLoaderImpl::SendResponse(URLResponsePtr response) {
+ Callback<void(URLResponsePtr)> callback;
+ std::swap(callback_, callback);
+ callback.Run(response.Pass());
+}
+
+void URLLoaderImpl::OnResponseBodyStreamReady(MojoResult result) {
+ // TODO(darin): Handle a bad |result| value.
+ ReadMore();
+}
+
+void URLLoaderImpl::WaitToReadMore() {
+ handle_watcher_.Start(response_body_stream_.get(),
+ MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ base::Bind(&URLLoaderImpl::OnResponseBodyStreamReady,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void URLLoaderImpl::ReadMore() {
+ DCHECK(!pending_write_.get());
+
+ pending_write_ = new PendingWriteToDataPipe(response_body_stream_.Pass());
+
+ uint32_t num_bytes;
+ MojoResult result = pending_write_->BeginWrite(&num_bytes);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ // The pipe is full. We need to wait for it to have more space.
+ response_body_stream_ = pending_write_->Complete(num_bytes);
+ pending_write_ = NULL;
+ WaitToReadMore();
+ return;
+ }
+ if (result != MOJO_RESULT_OK) {
+ // The response body stream is in a bad state. Bail.
+ // TODO(darin): How should this be communicated to our client?
+ return;
+ }
+ CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes);
+
+ scoped_refptr<net::IOBuffer> buf =
+ new DependentIOBuffer(pending_write_.get());
+
+ int bytes_read;
+ url_request_->Read(buf.get(), 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.
+ }
+}
+
+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/mojo/services/network/url_loader_impl.h b/mojo/services/network/url_loader_impl.h
new file mode 100644
index 0000000..5c5af8a
--- /dev/null
+++ b/mojo/services/network/url_loader_impl.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_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/common/handle_watcher.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;
+
+ // URLLoader methods:
+ virtual void Start(
+ URLRequestPtr request,
+ const Callback<void(URLResponsePtr)>& callback) OVERRIDE;
+ virtual void FollowRedirect(
+ const Callback<void(URLResponsePtr)>& callback) OVERRIDE;
+ virtual void QueryStatus(
+ const Callback<void(URLLoaderStatusPtr)>& callback) OVERRIDE;
+
+ // net::URLRequest::Delegate methods:
+ virtual void OnReceivedRedirect(net::URLRequest* url_request,
+ const net::RedirectInfo& redirect_info,
+ 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,
+ const Callback<void(URLResponsePtr)>& callback);
+ void SendResponse(URLResponsePtr response);
+ void OnResponseBodyStreamReady(MojoResult result);
+ void WaitToReadMore();
+ void ReadMore();
+ void DidRead(uint32_t num_bytes, bool completed_synchronously);
+
+ NetworkContext* context_;
+ scoped_ptr<net::URLRequest> url_request_;
+ Callback<void(URLResponsePtr)> callback_;
+ ScopedDataPipeProducerHandle response_body_stream_;
+ scoped_refptr<PendingWriteToDataPipe> pending_write_;
+ common::HandleWatcher handle_watcher_;
+ uint32 response_body_buffer_size_;
+ bool auto_follow_redirects_;
+
+ base::WeakPtrFactory<URLLoaderImpl> weak_ptr_factory_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NETWORK_URL_LOADER_IMPL_H_
diff --git a/mojo/services/network/web_socket_impl.cc b/mojo/services/network/web_socket_impl.cc
new file mode 100644
index 0000000..7ba9b22
--- /dev/null
+++ b/mojo/services/network/web_socket_impl.cc
@@ -0,0 +1,239 @@
+// Copyright 2014 The Chromium 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/web_socket_impl.h"
+
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/common/handle_watcher.h"
+#include "mojo/services/network/network_context.h"
+#include "mojo/services/public/cpp/network/web_socket_read_queue.h"
+#include "mojo/services/public/cpp/network/web_socket_write_queue.h"
+#include "net/websockets/websocket_channel.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_frame.h" // for WebSocketFrameHeader::OpCode
+#include "net/websockets/websocket_handshake_request_info.h"
+#include "net/websockets/websocket_handshake_response_info.h"
+#include "url/origin.h"
+
+namespace mojo {
+
+template <>
+struct TypeConverter<net::WebSocketFrameHeader::OpCode,
+ WebSocket::MessageType> {
+ static net::WebSocketFrameHeader::OpCode Convert(
+ WebSocket::MessageType type) {
+ DCHECK(type == WebSocket::MESSAGE_TYPE_CONTINUATION ||
+ type == WebSocket::MESSAGE_TYPE_TEXT ||
+ type == WebSocket::MESSAGE_TYPE_BINARY);
+ typedef net::WebSocketFrameHeader::OpCode OpCode;
+ // These compile asserts verify that the same underlying values are used for
+ // both types, so we can simply cast between them.
+ COMPILE_ASSERT(static_cast<OpCode>(WebSocket::MESSAGE_TYPE_CONTINUATION) ==
+ net::WebSocketFrameHeader::kOpCodeContinuation,
+ enum_values_must_match_for_opcode_continuation);
+ COMPILE_ASSERT(static_cast<OpCode>(WebSocket::MESSAGE_TYPE_TEXT) ==
+ net::WebSocketFrameHeader::kOpCodeText,
+ enum_values_must_match_for_opcode_text);
+ COMPILE_ASSERT(static_cast<OpCode>(WebSocket::MESSAGE_TYPE_BINARY) ==
+ net::WebSocketFrameHeader::kOpCodeBinary,
+ enum_values_must_match_for_opcode_binary);
+ return static_cast<OpCode>(type);
+ }
+};
+
+template <>
+struct TypeConverter<WebSocket::MessageType,
+ net::WebSocketFrameHeader::OpCode> {
+ static WebSocket::MessageType Convert(
+ net::WebSocketFrameHeader::OpCode type) {
+ DCHECK(type == net::WebSocketFrameHeader::kOpCodeContinuation ||
+ type == net::WebSocketFrameHeader::kOpCodeText ||
+ type == net::WebSocketFrameHeader::kOpCodeBinary);
+ return static_cast<WebSocket::MessageType>(type);
+ }
+};
+
+namespace {
+
+typedef net::WebSocketEventInterface::ChannelState ChannelState;
+
+struct WebSocketEventHandler : public net::WebSocketEventInterface {
+ public:
+ WebSocketEventHandler(WebSocketClientPtr client)
+ : client_(client.Pass()) {
+ }
+ virtual ~WebSocketEventHandler() {}
+
+ private:
+ // net::WebSocketEventInterface methods:
+ virtual ChannelState OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_subprotocol,
+ const std::string& extensions) OVERRIDE;
+ virtual ChannelState OnDataFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) OVERRIDE;
+ virtual ChannelState OnClosingHandshake() OVERRIDE;
+ virtual ChannelState OnFlowControl(int64 quota) OVERRIDE;
+ virtual ChannelState OnDropChannel(bool was_clean,
+ uint16 code,
+ const std::string& reason) OVERRIDE;
+ virtual ChannelState OnFailChannel(const std::string& message) OVERRIDE;
+ virtual ChannelState OnStartOpeningHandshake(
+ scoped_ptr<net::WebSocketHandshakeRequestInfo> request) OVERRIDE;
+ virtual ChannelState OnFinishOpeningHandshake(
+ scoped_ptr<net::WebSocketHandshakeResponseInfo> response) OVERRIDE;
+ virtual ChannelState OnSSLCertificateError(
+ scoped_ptr<net::WebSocketEventInterface::SSLErrorCallbacks> callbacks,
+ const GURL& url,
+ const net::SSLInfo& ssl_info,
+ bool fatal) OVERRIDE;
+
+ // Called once we've written to |receive_stream_|.
+ void DidWriteToReceiveStream(bool fin,
+ net::WebSocketFrameHeader::OpCode type,
+ uint32_t num_bytes,
+ const char* buffer);
+ WebSocketClientPtr client_;
+ ScopedDataPipeProducerHandle receive_stream_;
+ scoped_ptr<WebSocketWriteQueue> write_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketEventHandler);
+};
+
+ChannelState WebSocketEventHandler::OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_protocol,
+ const std::string& extensions) {
+ DataPipe data_pipe;
+ receive_stream_ = data_pipe.producer_handle.Pass();
+ write_queue_.reset(new WebSocketWriteQueue(receive_stream_.get()));
+ client_->DidConnect(
+ fail, selected_protocol, extensions, data_pipe.consumer_handle.Pass());
+ if (fail)
+ return WebSocketEventInterface::CHANNEL_DELETED;
+ return WebSocketEventInterface::CHANNEL_ALIVE;
+}
+
+ChannelState WebSocketEventHandler::OnDataFrame(
+ bool fin,
+ net::WebSocketFrameHeader::OpCode type,
+ const std::vector<char>& data) {
+ uint32_t size = static_cast<uint32_t>(data.size());
+ write_queue_->Write(
+ &data[0], size,
+ base::Bind(&WebSocketEventHandler::DidWriteToReceiveStream,
+ base::Unretained(this),
+ fin, type, size));
+ return WebSocketEventInterface::CHANNEL_ALIVE;
+}
+
+ChannelState WebSocketEventHandler::OnClosingHandshake() {
+ return WebSocketEventInterface::CHANNEL_ALIVE;
+}
+
+ChannelState WebSocketEventHandler::OnFlowControl(int64 quota) {
+ client_->DidReceiveFlowControl(quota);
+ return WebSocketEventInterface::CHANNEL_ALIVE;
+}
+
+ChannelState WebSocketEventHandler::OnDropChannel(bool was_clean,
+ uint16 code,
+ const std::string& reason) {
+ client_->DidClose(was_clean, code, reason);
+ return WebSocketEventInterface::CHANNEL_DELETED;
+}
+
+ChannelState WebSocketEventHandler::OnFailChannel(const std::string& message) {
+ client_->DidFail(message);
+ return WebSocketEventInterface::CHANNEL_DELETED;
+}
+
+ChannelState WebSocketEventHandler::OnStartOpeningHandshake(
+ scoped_ptr<net::WebSocketHandshakeRequestInfo> request) {
+ return WebSocketEventInterface::CHANNEL_ALIVE;
+}
+
+ChannelState WebSocketEventHandler::OnFinishOpeningHandshake(
+ scoped_ptr<net::WebSocketHandshakeResponseInfo> response) {
+ return WebSocketEventInterface::CHANNEL_ALIVE;
+}
+
+ChannelState WebSocketEventHandler::OnSSLCertificateError(
+ scoped_ptr<net::WebSocketEventInterface::SSLErrorCallbacks> callbacks,
+ const GURL& url,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ client_->DidFail("SSL Error");
+ return WebSocketEventInterface::CHANNEL_DELETED;
+}
+
+void WebSocketEventHandler::DidWriteToReceiveStream(
+ bool fin,
+ net::WebSocketFrameHeader::OpCode type,
+ uint32_t num_bytes,
+ const char* buffer) {
+ client_->DidReceiveData(
+ fin, ConvertTo<WebSocket::MessageType>(type), num_bytes);
+}
+
+} // namespace mojo
+
+WebSocketImpl::WebSocketImpl(NetworkContext* context) : context_(context) {
+}
+
+WebSocketImpl::~WebSocketImpl() {
+}
+
+void WebSocketImpl::Connect(const String& url,
+ Array<String> protocols,
+ const String& origin,
+ ScopedDataPipeConsumerHandle send_stream,
+ WebSocketClientPtr client) {
+ DCHECK(!channel_);
+ send_stream_ = send_stream.Pass();
+ read_queue_.reset(new WebSocketReadQueue(send_stream_.get()));
+ scoped_ptr<net::WebSocketEventInterface> event_interface(
+ new WebSocketEventHandler(client.Pass()));
+ channel_.reset(new net::WebSocketChannel(event_interface.Pass(),
+ context_->url_request_context()));
+ channel_->SendAddChannelRequest(GURL(url.get()),
+ protocols.To<std::vector<std::string> >(),
+ url::Origin(origin.get()));
+}
+
+void WebSocketImpl::Send(bool fin,
+ WebSocket::MessageType type,
+ uint32_t num_bytes) {
+ DCHECK(channel_);
+ read_queue_->Read(num_bytes,
+ base::Bind(&WebSocketImpl::DidReadFromSendStream,
+ base::Unretained(this),
+ fin, type, num_bytes));
+}
+
+void WebSocketImpl::FlowControl(int64_t quota) {
+ DCHECK(channel_);
+ channel_->SendFlowControl(quota);
+}
+
+void WebSocketImpl::Close(uint16_t code, const String& reason) {
+ DCHECK(channel_);
+ channel_->StartClosingHandshake(code, reason);
+}
+
+void WebSocketImpl::DidReadFromSendStream(bool fin,
+ WebSocket::MessageType type,
+ uint32_t num_bytes,
+ const char* data) {
+ std::vector<char> buffer(num_bytes);
+ memcpy(&buffer[0], data, num_bytes);
+ DCHECK(channel_);
+ channel_->SendFrame(
+ fin, ConvertTo<net::WebSocketFrameHeader::OpCode>(type), buffer);
+}
+
+} // namespace mojo
diff --git a/mojo/services/network/web_socket_impl.h b/mojo/services/network/web_socket_impl.h
new file mode 100644
index 0000000..bd8a27d
--- /dev/null
+++ b/mojo/services/network/web_socket_impl.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_SERVICES_NETWORK_WEB_SOCKET_IMPL_H_
+#define MOJO_SERVICES_NETWORK_WEB_SOCKET_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/interface_impl.h"
+#include "mojo/services/public/interfaces/network/web_socket.mojom.h"
+
+namespace net {
+class WebSocketChannel;
+} // namespace net
+
+namespace mojo {
+class NetworkContext;
+class WebSocketReadQueue;
+
+// Forms a bridge between the WebSocket mojo interface and the net::WebSocket
+// implementation.
+class WebSocketImpl : public InterfaceImpl<WebSocket> {
+ public:
+ explicit WebSocketImpl(NetworkContext* context);
+ virtual ~WebSocketImpl();
+
+ private:
+ // WebSocket methods:
+ virtual void Connect(const String& url,
+ Array<String> protocols,
+ const String& origin,
+ ScopedDataPipeConsumerHandle send_stream,
+ WebSocketClientPtr client) OVERRIDE;
+ virtual void Send(bool fin,
+ WebSocket::MessageType type,
+ uint32_t num_bytes) OVERRIDE;
+ virtual void FlowControl(int64_t quota) OVERRIDE;
+ virtual void Close(uint16_t code, const String& reason) OVERRIDE;
+
+ // Called with the data to send once it has been read from |send_stream_|.
+ void DidReadFromSendStream(bool fin,
+ WebSocket::MessageType type,
+ uint32_t num_bytes,
+ const char* data);
+
+ // The channel we use to send events to the network.
+ scoped_ptr<net::WebSocketChannel> channel_;
+ ScopedDataPipeConsumerHandle send_stream_;
+ scoped_ptr<WebSocketReadQueue> read_queue_;
+ NetworkContext* context_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_NETWORK_WEB_SOCKET_IMPL_H_
diff --git a/mojo/services/public/cpp/geometry/BUILD.gn b/mojo/services/public/cpp/geometry/BUILD.gn
new file mode 100644
index 0000000..c87adf3
--- /dev/null
+++ b/mojo/services/public/cpp/geometry/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_services.gypi:mojo_geometry_lib
+component("geometry") {
+ output_name = "mojo_geometry_lib"
+
+ public_deps = [
+ "//ui/gfx",
+ ]
+ deps = [
+ "//skia",
+ "//ui/gfx/geometry",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_component",
+ "//mojo/services/public/interfaces/geometry",
+ ]
+
+ defines = [
+ "MOJO_GEOMETRY_IMPLEMENTATION",
+ ]
+
+ sources = [
+ "lib/geometry_type_converters.cc",
+ "geometry_type_converters.h",
+ "mojo_geometry_export.h",
+ ]
+}
diff --git a/mojo/services/public/cpp/geometry/DEPS b/mojo/services/public/cpp/geometry/DEPS
new file mode 100644
index 0000000..3e03810
--- /dev/null
+++ b/mojo/services/public/cpp/geometry/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+ui/gfx/geometry",
+ "+ui/gfx/transform.h",
+]
diff --git a/mojo/services/public/cpp/geometry/geometry_type_converters.h b/mojo/services/public/cpp/geometry/geometry_type_converters.h
new file mode 100644
index 0000000..2f2cc1b
--- /dev/null
+++ b/mojo/services/public/cpp/geometry/geometry_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_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/point.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<PointPtr, gfx::Point> {
+ static PointPtr Convert(const gfx::Point& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::Point, PointPtr> {
+ static gfx::Point Convert(const PointPtr& input);
+};
+
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<PointFPtr, gfx::PointF> {
+ static PointFPtr Convert(const gfx::PointF& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::PointF, PointFPtr> {
+ static gfx::PointF Convert(const PointFPtr& input);
+};
+
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<SizePtr, gfx::Size> {
+ static SizePtr Convert(const gfx::Size& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::Size, SizePtr> {
+ static gfx::Size Convert(const SizePtr& input);
+};
+
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<RectPtr, gfx::Rect> {
+ static RectPtr Convert(const gfx::Rect& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::Rect, RectPtr> {
+ static gfx::Rect Convert(const RectPtr& input);
+};
+
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<RectFPtr, gfx::RectF> {
+ static RectFPtr Convert(const gfx::RectF& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::RectF, RectFPtr> {
+ static gfx::RectF Convert(const RectFPtr& input);
+};
+
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<TransformPtr, gfx::Transform> {
+ static TransformPtr Convert(const gfx::Transform& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::Transform, TransformPtr> {
+ static gfx::Transform Convert(const TransformPtr& input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_GEOMETRY_GEOMETRY_TYPE_CONVERTERS_H_
diff --git a/mojo/services/public/cpp/geometry/lib/geometry_type_converters.cc b/mojo/services/public/cpp/geometry/lib/geometry_type_converters.cc
new file mode 100644
index 0000000..9a40c15
--- /dev/null
+++ b/mojo/services/public/cpp/geometry/lib/geometry_type_converters.cc
@@ -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.
+
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+
+namespace mojo {
+
+// static
+PointPtr TypeConverter<PointPtr, gfx::Point>::Convert(const gfx::Point& input) {
+ PointPtr point(Point::New());
+ point->x = input.x();
+ point->y = input.y();
+ return point.Pass();
+}
+
+// static
+gfx::Point TypeConverter<gfx::Point, PointPtr>::Convert(const PointPtr& input) {
+ if (input.is_null())
+ return gfx::Point();
+ return gfx::Point(input->x, input->y);
+}
+
+// static
+PointFPtr TypeConverter<PointFPtr, gfx::PointF>::Convert(
+ const gfx::PointF& input) {
+ PointFPtr point(PointF::New());
+ point->x = input.x();
+ point->y = input.y();
+ return point.Pass();
+}
+
+// static
+gfx::PointF TypeConverter<gfx::PointF, PointFPtr>::Convert(
+ const PointFPtr& input) {
+ if (input.is_null())
+ return gfx::PointF();
+ return gfx::PointF(input->x, input->y);
+}
+
+// static
+SizePtr TypeConverter<SizePtr, gfx::Size>::Convert(const gfx::Size& input) {
+ SizePtr size(Size::New());
+ size->width = input.width();
+ size->height = input.height();
+ return size.Pass();
+}
+
+// static
+gfx::Size TypeConverter<gfx::Size, SizePtr>::Convert(const SizePtr& input) {
+ if (input.is_null())
+ return gfx::Size();
+ return gfx::Size(input->width, input->height);
+}
+
+// static
+RectPtr TypeConverter<RectPtr, gfx::Rect>::Convert(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<gfx::Rect, RectPtr>::Convert(const RectPtr& input) {
+ if (input.is_null())
+ return gfx::Rect();
+ return gfx::Rect(input->x, input->y, input->width, input->height);
+}
+
+// static
+RectFPtr TypeConverter<RectFPtr, gfx::RectF>::Convert(const gfx::RectF& input) {
+ RectFPtr rect(RectF::New());
+ rect->x = input.x();
+ rect->y = input.y();
+ rect->width = input.width();
+ rect->height = input.height();
+ return rect.Pass();
+}
+
+// static
+gfx::RectF TypeConverter<gfx::RectF, RectFPtr>::Convert(const RectFPtr& input) {
+ if (input.is_null())
+ return gfx::RectF();
+ return gfx::RectF(input->x, input->y, input->width, input->height);
+}
+
+// static
+TransformPtr TypeConverter<TransformPtr, gfx::Transform>::Convert(
+ const gfx::Transform& input) {
+ std::vector<float> storage(16);
+ input.matrix().asRowMajorf(&storage[0]);
+ mojo::Array<float> matrix;
+ matrix.Swap(&storage);
+ TransformPtr transform(Transform::New());
+ transform->matrix = matrix.Pass();
+ return transform.Pass();
+}
+
+// static
+gfx::Transform TypeConverter<gfx::Transform, TransformPtr>::Convert(
+ const TransformPtr& input) {
+ if (input.is_null())
+ return gfx::Transform();
+ gfx::Transform transform(gfx::Transform::kSkipInitialization);
+ transform.matrix().setRowMajorf(&input->matrix.storage()[0]);
+ return transform;
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/geometry/mojo_geometry_export.h b/mojo/services/public/cpp/geometry/mojo_geometry_export.h
new file mode 100644
index 0000000..f21aebc
--- /dev/null
+++ b/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/mojo/services/public/cpp/input_events/BUILD.gn b/mojo/services/public/cpp/input_events/BUILD.gn
new file mode 100644
index 0000000..2b47ece
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/BUILD.gn
@@ -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.
+
+component("input_events") {
+ sources = [
+ "lib/input_events_type_converters.cc",
+ "lib/mojo_extended_key_event_data.cc",
+ "lib/mojo_extended_key_event_data.h",
+ "mojo_input_events_export.h",
+ ]
+
+ defines = [
+ "MOJO_INPUT_EVENTS_IMPLEMENTATION",
+ ]
+
+ deps = [
+ "//base",
+ "//ui/events",
+ "//ui/gfx/geometry",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_component",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ ]
+}
diff --git a/mojo/services/public/cpp/input_events/DEPS b/mojo/services/public/cpp/input_events/DEPS
new file mode 100644
index 0000000..fe1d98e
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/events",
+]
diff --git a/mojo/services/public/cpp/input_events/input_events_type_converters.h b/mojo/services/public/cpp/input_events/input_events_type_converters.h
new file mode 100644
index 0000000..bbbbe10
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/input_events_type_converters.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_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 <>
+struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventType, ui::EventType> {
+ static EventType Convert(ui::EventType type);
+};
+
+template <>
+struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<ui::EventType, EventType> {
+ static ui::EventType Convert(EventType type);
+};
+
+template <>
+struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::Event> {
+ static EventPtr Convert(const ui::Event& input);
+};
+
+template <>
+struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::KeyEvent> {
+ static EventPtr Convert(const ui::KeyEvent& input);
+};
+
+template <>
+struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<scoped_ptr<ui::Event>, EventPtr> {
+ static scoped_ptr<ui::Event> Convert(const EventPtr& input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_INPUT_EVENTS_TYPE_CONVERTERS_H_
diff --git a/mojo/services/public/cpp/input_events/lib/input_event_names.h b/mojo/services/public/cpp/input_events/lib/input_event_names.h
new file mode 100644
index 0000000..3fd6ef6
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/lib/input_event_names.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.
+
+MOJO_INPUT_EVENT_NAME(UNKNOWN);
+MOJO_INPUT_EVENT_NAME(MOUSE_PRESSED);
+MOJO_INPUT_EVENT_NAME(MOUSE_DRAGGED);
+MOJO_INPUT_EVENT_NAME(MOUSE_RELEASED);
+MOJO_INPUT_EVENT_NAME(MOUSE_MOVED);
+MOJO_INPUT_EVENT_NAME(MOUSE_ENTERED);
+MOJO_INPUT_EVENT_NAME(MOUSE_EXITED);
+MOJO_INPUT_EVENT_NAME(KEY_PRESSED);
+MOJO_INPUT_EVENT_NAME(KEY_RELEASED);
+MOJO_INPUT_EVENT_NAME(MOUSEWHEEL);
+MOJO_INPUT_EVENT_NAME(MOUSE_CAPTURE_CHANGED);
+MOJO_INPUT_EVENT_NAME(TOUCH_RELEASED);
+MOJO_INPUT_EVENT_NAME(TOUCH_PRESSED);
+MOJO_INPUT_EVENT_NAME(TOUCH_MOVED);
+MOJO_INPUT_EVENT_NAME(TOUCH_CANCELLED);
+MOJO_INPUT_EVENT_NAME(DROP_TARGET_EVENT);
+MOJO_INPUT_EVENT_NAME(TRANSLATED_KEY_PRESS);
+MOJO_INPUT_EVENT_NAME(TRANSLATED_KEY_RELEASE);
+MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_BEGIN);
+MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_END);
+MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_UPDATE);
+MOJO_INPUT_EVENT_NAME(GESTURE_TAP);
+MOJO_INPUT_EVENT_NAME(GESTURE_TAP_DOWN);
+MOJO_INPUT_EVENT_NAME(GESTURE_TAP_CANCEL);
+MOJO_INPUT_EVENT_NAME(GESTURE_TAP_UNCONFIRMED);
+MOJO_INPUT_EVENT_NAME(GESTURE_DOUBLE_TAP);
+MOJO_INPUT_EVENT_NAME(GESTURE_BEGIN);
+MOJO_INPUT_EVENT_NAME(GESTURE_END);
+MOJO_INPUT_EVENT_NAME(GESTURE_TWO_FINGER_TAP);
+MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_BEGIN);
+MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_END);
+MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_UPDATE);
+MOJO_INPUT_EVENT_NAME(GESTURE_LONG_PRESS);
+MOJO_INPUT_EVENT_NAME(GESTURE_LONG_TAP);
+MOJO_INPUT_EVENT_NAME(GESTURE_SWIPE);
+MOJO_INPUT_EVENT_NAME(GESTURE_SHOW_PRESS);
+MOJO_INPUT_EVENT_NAME(GESTURE_WIN8_EDGE_SWIPE);
+MOJO_INPUT_EVENT_NAME(SCROLL);
+MOJO_INPUT_EVENT_NAME(SCROLL_FLING_START);
+MOJO_INPUT_EVENT_NAME(SCROLL_FLING_CANCEL);
+MOJO_INPUT_EVENT_NAME(CANCEL_MODE);
+MOJO_INPUT_EVENT_NAME(UMA_DATA);
diff --git a/mojo/services/public/cpp/input_events/lib/input_events_type_converters.cc b/mojo/services/public/cpp/input_events/lib/input_events_type_converters.cc
new file mode 100644
index 0000000..340e200
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/lib/input_events_type_converters.cc
@@ -0,0 +1,245 @@
+// Copyright 2014 The Chromium 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"
+
+#if defined(USE_X11)
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#endif
+
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace mojo {
+
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_NONE) ==
+ static_cast<int32>(ui::EF_NONE),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_CAPS_LOCK_DOWN) ==
+ static_cast<int32>(ui::EF_CAPS_LOCK_DOWN),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_SHIFT_DOWN) ==
+ static_cast<int32>(ui::EF_SHIFT_DOWN),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_CONTROL_DOWN) ==
+ static_cast<int32>(ui::EF_CONTROL_DOWN),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_ALT_DOWN) ==
+ static_cast<int32>(ui::EF_ALT_DOWN),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_LEFT_MOUSE_BUTTON) ==
+ static_cast<int32>(ui::EF_LEFT_MOUSE_BUTTON),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_MIDDLE_MOUSE_BUTTON) ==
+ static_cast<int32>(ui::EF_MIDDLE_MOUSE_BUTTON),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_RIGHT_MOUSE_BUTTON) ==
+ static_cast<int32>(ui::EF_RIGHT_MOUSE_BUTTON),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_COMMAND_DOWN) ==
+ static_cast<int32>(ui::EF_COMMAND_DOWN),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_EXTENDED) ==
+ static_cast<int32>(ui::EF_EXTENDED),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_IS_SYNTHESIZED) ==
+ static_cast<int32>(ui::EF_IS_SYNTHESIZED),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_ALTGR_DOWN) ==
+ static_cast<int32>(ui::EF_ALTGR_DOWN),
+ event_flags_should_match);
+COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_MOD3_DOWN) ==
+ static_cast<int32>(ui::EF_MOD3_DOWN),
+ event_flags_should_match);
+
+
+// static
+EventType TypeConverter<EventType, ui::EventType>::Convert(ui::EventType type) {
+#define MOJO_INPUT_EVENT_NAME(name) case ui::ET_##name: return EVENT_TYPE_##name
+
+ switch (type) {
+#include "mojo/services/public/cpp/input_events/lib/input_event_names.h"
+ case ui::ET_LAST:
+ NOTREACHED();
+ break;
+ }
+
+#undef MOJO_INPUT_EVENT_NAME
+
+ NOTREACHED();
+ return EVENT_TYPE_UNKNOWN;
+}
+
+// static
+ui::EventType TypeConverter<ui::EventType, EventType>::Convert(EventType type) {
+#define MOJO_INPUT_EVENT_NAME(name) case EVENT_TYPE_##name: return ui::ET_##name
+
+ switch (type) {
+#include "mojo/services/public/cpp/input_events/lib/input_event_names.h"
+ }
+
+#undef MOJO_INPUT_EVENT_NAME
+
+ NOTREACHED();
+ return ui::ET_UNKNOWN;
+}
+
+// static
+EventPtr TypeConverter<EventPtr, ui::Event>::Convert(const ui::Event& input) {
+ EventPtr event(Event::New());
+ event->action = ConvertTo<EventType>(input.type());
+ event->flags = EventFlags(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);
+
+ LocationDataPtr location_data(LocationData::New());
+ location_data->in_view_location = Point::From(located_event->location());
+ if (input.HasNativeEvent()) {
+ location_data->screen_location =
+ Point::From(ui::EventSystemLocationFromNative(input.native_event()));
+ }
+
+ event->location_data = location_data.Pass();
+ }
+
+ 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->GetConflatedWindowsKeyCode();
+ key_data->native_key_code = key_event->platform_keycode();
+ key_data->is_char = key_event->is_char();
+ key_data->character = key_event->GetCharacter();
+
+ if (key_event->extended_key_event_data()) {
+ const MojoExtendedKeyEventData* data =
+ static_cast<const MojoExtendedKeyEventData*>(
+ key_event->extended_key_event_data());
+ key_data->windows_key_code = static_cast<mojo::KeyboardCode>(
+ data->windows_key_code());
+ key_data->text = data->text();
+ key_data->unmodified_text = data->unmodified_text();
+ } else {
+ key_data->windows_key_code = static_cast<mojo::KeyboardCode>(
+ key_event->GetLocatedWindowsKeyboardCode());
+ key_data->text = key_event->GetText();
+ key_data->unmodified_text = key_event->GetUnmodifiedText();
+ }
+
+ event->key_data = key_data.Pass();
+ } else if (input.IsMouseWheelEvent()) {
+ const ui::MouseWheelEvent* wheel_event =
+ static_cast<const ui::MouseWheelEvent*>(&input);
+ MouseWheelDataPtr wheel_data(MouseWheelData::New());
+ wheel_data->x_offset = wheel_event->x_offset();
+ wheel_data->y_offset = wheel_event->y_offset();
+ event->wheel_data = wheel_data.Pass();
+ }
+ return event.Pass();
+}
+
+// static
+EventPtr TypeConverter<EventPtr, ui::KeyEvent>::Convert(
+ const ui::KeyEvent& input) {
+ return Event::From(static_cast<const ui::Event&>(input));
+}
+
+// static
+scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert(
+ const EventPtr& input) {
+ scoped_ptr<ui::Event> ui_event;
+ ui::EventType ui_event_type = ConvertTo<ui::EventType>(input->action);
+
+ gfx::Point location;
+ if (!input->location_data.is_null() &&
+ !input->location_data->in_view_location.is_null()) {
+ location = input->location_data->in_view_location.To<gfx::Point>();
+ }
+
+ switch (input->action) {
+ case ui::ET_KEY_PRESSED:
+ case ui::ET_KEY_RELEASED: {
+ scoped_ptr<ui::KeyEvent> key_event;
+ if (input->key_data->is_char) {
+ key_event.reset(new ui::KeyEvent(
+ static_cast<base::char16>(input->key_data->character),
+ static_cast<ui::KeyboardCode>(
+ input->key_data->key_code),
+ input->flags));
+ } else {
+ key_event.reset(new ui::KeyEvent(
+ ui_event_type,
+ static_cast<ui::KeyboardCode>(
+ input->key_data->key_code),
+ input->flags));
+ }
+ key_event->SetExtendedKeyEventData(scoped_ptr<ui::ExtendedKeyEventData>(
+ new MojoExtendedKeyEventData(
+ static_cast<int32_t>(input->key_data->windows_key_code),
+ input->key_data->text,
+ input->key_data->unmodified_text)));
+ key_event->set_platform_keycode(input->key_data->native_key_code);
+ ui_event = key_event.PassAs<ui::KeyEvent>();
+ break;
+ }
+ case EVENT_TYPE_MOUSE_PRESSED:
+ case EVENT_TYPE_MOUSE_DRAGGED:
+ case EVENT_TYPE_MOUSE_RELEASED:
+ case EVENT_TYPE_MOUSE_MOVED:
+ case EVENT_TYPE_MOUSE_ENTERED:
+ case EVENT_TYPE_MOUSE_EXITED: {
+ // TODO: last flags isn't right. Need to send changed_flags.
+ ui_event.reset(new ui::MouseEvent(
+ ui_event_type,
+ location,
+ location,
+ ui::EventFlags(input->flags),
+ ui::EventFlags(input->flags)));
+ break;
+ }
+ case EVENT_TYPE_MOUSEWHEEL: {
+ const gfx::Vector2d offset(input->wheel_data->x_offset,
+ input->wheel_data->y_offset);
+ ui_event.reset(new ui::MouseWheelEvent(offset,
+ location,
+ location,
+ ui::EventFlags(input->flags),
+ ui::EventFlags(input->flags)));
+ break;
+ }
+ case EVENT_TYPE_TOUCH_MOVED:
+ case EVENT_TYPE_TOUCH_PRESSED:
+ case EVENT_TYPE_TOUCH_CANCELLED:
+ case EVENT_TYPE_TOUCH_RELEASED: {
+ ui_event.reset(new ui::TouchEvent(
+ ui_event_type,
+ location,
+ ui::EventFlags(input->flags),
+ input->touch_data->pointer_id,
+ base::TimeDelta::FromInternalValue(input->time_stamp),
+ 0.f, 0.f, 0.f, 0.f));
+ break;
+ }
+ default:
+ // TODO: support other types.
+ // NOTIMPLEMENTED();
+ ;
+ }
+ // TODO: need to support time_stamp.
+ return ui_event.Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.cc b/mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.cc
new file mode 100644
index 0000000..fa93751
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.cc
@@ -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.
+
+#include "mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.h"
+
+namespace mojo {
+
+MojoExtendedKeyEventData::MojoExtendedKeyEventData(int32_t windows_key_code,
+ uint16_t text,
+ uint16_t unmodified_text)
+ : windows_key_code_(windows_key_code),
+ text_(text),
+ unmodified_text_(unmodified_text) {
+}
+
+MojoExtendedKeyEventData::~MojoExtendedKeyEventData() {}
+
+ui::ExtendedKeyEventData* MojoExtendedKeyEventData::Clone() const {
+ return new MojoExtendedKeyEventData(windows_key_code_,
+ text_,
+ unmodified_text_);
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.h b/mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.h
new file mode 100644
index 0000000..cd5be0f
--- /dev/null
+++ b/mojo/services/public/cpp/input_events/lib/mojo_extended_key_event_data.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_SERVICES_PUBLIC_CPP_INPUT_EVENTS_LIB_MOJO_EXTENDED_KEY_EVENT_DATA_H_
+#define MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_LIB_MOJO_EXTENDED_KEY_EVENT_DATA_H_
+
+#include "ui/events/event.h"
+#include "mojo/services/public/cpp/input_events/mojo_input_events_export.h"
+
+namespace mojo {
+
+// A structure to store all mojo specific data on a KeyEvent.
+class MOJO_INPUT_EVENTS_EXPORT MojoExtendedKeyEventData
+ : public ui::ExtendedKeyEventData {
+ public:
+ MojoExtendedKeyEventData(int32_t windows_key_code,
+ uint16_t text,
+ uint16_t unmodified_text);
+ virtual ~MojoExtendedKeyEventData();
+
+ int32_t windows_key_code() const { return windows_key_code_; }
+ uint16_t text() const { return text_; }
+ uint16_t unmodified_text() const { return unmodified_text_; }
+
+ // ui::ExtendedKeyEventData:
+ virtual ui::ExtendedKeyEventData* Clone() const OVERRIDE;
+
+ private:
+ const int32_t windows_key_code_;
+ const uint16_t text_;
+ const uint16_t unmodified_text_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoExtendedKeyEventData);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_INPUT_EVENTS_LIB_MOJO_EXTENDED_KEY_EVENT_DATA_H_
diff --git a/mojo/services/public/cpp/input_events/mojo_input_events_export.h b/mojo/services/public/cpp/input_events/mojo_input_events_export.h
new file mode 100644
index 0000000..d7b193e
--- /dev/null
+++ b/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/mojo/services/public/cpp/network/BUILD.gn b/mojo/services/public/cpp/network/BUILD.gn
new file mode 100644
index 0000000..81e64b4
--- /dev/null
+++ b/mojo/services/public/cpp/network/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# GYP version: mojo/mojo_services.gypi:mojo_network_utility
+source_set("network") {
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_component",
+ ]
+
+ sources = [
+ "web_socket_read_queue.cc",
+ "web_socket_read_queue.h",
+ "web_socket_write_queue.cc",
+ "web_socket_write_queue.h",
+ ]
+}
diff --git a/mojo/services/public/cpp/network/web_socket_read_queue.cc b/mojo/services/public/cpp/network/web_socket_read_queue.cc
new file mode 100644
index 0000000..66b5a66
--- /dev/null
+++ b/mojo/services/public/cpp/network/web_socket_read_queue.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/services/public/cpp/network/web_socket_read_queue.h"
+
+#include "base/bind.h"
+
+namespace mojo {
+
+struct WebSocketReadQueue::Operation {
+ uint32_t num_bytes_;
+ base::Callback<void(const char*)> callback_;
+};
+
+WebSocketReadQueue::WebSocketReadQueue(DataPipeConsumerHandle handle)
+ : handle_(handle), is_waiting_(false) {
+}
+
+WebSocketReadQueue::~WebSocketReadQueue() {
+}
+
+void WebSocketReadQueue::Read(uint32_t num_bytes,
+ base::Callback<void(const char*)> callback) {
+ Operation* op = new Operation;
+ op->num_bytes_ = num_bytes;
+ op->callback_ = callback;
+ queue_.push_back(op);
+
+ if (!is_waiting_)
+ TryToRead();
+}
+
+void WebSocketReadQueue::TryToRead() {
+ Operation* op = queue_[0];
+ const void* buffer = NULL;
+ uint32_t bytes_read = op->num_bytes_;
+ MojoResult result = BeginReadDataRaw(
+ handle_, &buffer, &bytes_read, MOJO_READ_DATA_FLAG_ALL_OR_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ EndReadDataRaw(handle_, bytes_read);
+ Wait();
+ return;
+ }
+
+ // Ensure |op| is deleted, whether or not |this| goes away.
+ scoped_ptr<Operation> op_deleter(op);
+ queue_.weak_erase(queue_.begin());
+ if (result != MOJO_RESULT_OK)
+ return;
+ DataPipeConsumerHandle handle = handle_;
+ op->callback_.Run(static_cast<const char*>(buffer)); // may delete |this|
+ EndReadDataRaw(handle, bytes_read);
+}
+
+void WebSocketReadQueue::Wait() {
+ is_waiting_ = true;
+ handle_watcher_.Start(
+ handle_,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ base::Bind(&WebSocketReadQueue::OnHandleReady, base::Unretained(this)));
+}
+
+void WebSocketReadQueue::OnHandleReady(MojoResult result) {
+ is_waiting_ = false;
+ TryToRead();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/network/web_socket_read_queue.h b/mojo/services/public/cpp/network/web_socket_read_queue.h
new file mode 100644
index 0000000..6108e49
--- /dev/null
+++ b/mojo/services/public/cpp/network/web_socket_read_queue.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_PUBLIC_CPP_NETWORK_WEB_SOCKET_READ_QUEUE_H_
+#define MOJO_SERVICES_PUBLIC_CPP_NETWORK_WEB_SOCKET_READ_QUEUE_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/common/handle_watcher.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+
+namespace mojo {
+
+// This class simplifies the handling of multiple Reads on a DataPipe. It reads
+// the data in the expected chunk size, calling the callback once a full chunk
+// is ready. Callbacks are owned by this class, and are guaranteed not to be
+// called after this class is destroyed.
+// See also: WebSocketWriteQueue
+class WebSocketReadQueue {
+ public:
+ WebSocketReadQueue(DataPipeConsumerHandle handle);
+ ~WebSocketReadQueue();
+
+ void Read(uint32_t num_bytes, base::Callback<void(const char*)> callback);
+
+ private:
+ struct Operation;
+
+ void TryToRead();
+ void Wait();
+ void OnHandleReady(MojoResult result);
+
+ DataPipeConsumerHandle handle_;
+ common::HandleWatcher handle_watcher_;
+ ScopedVector<Operation> queue_;
+ bool is_waiting_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_NETWORK_WEB_SOCKET_READ_QUEUE_H_
diff --git a/mojo/services/public/cpp/network/web_socket_write_queue.cc b/mojo/services/public/cpp/network/web_socket_write_queue.cc
new file mode 100644
index 0000000..fe3f674
--- /dev/null
+++ b/mojo/services/public/cpp/network/web_socket_write_queue.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/services/public/cpp/network/web_socket_write_queue.h"
+
+#include "base/bind.h"
+
+namespace mojo {
+
+struct WebSocketWriteQueue::Operation {
+ uint32_t num_bytes_;
+ base::Callback<void(const char*)> callback_;
+
+ const char* data_;
+ // Only initialized if the initial Write fails. This saves a copy in
+ // the common case.
+ std::vector<char> data_copy_;
+};
+
+WebSocketWriteQueue::WebSocketWriteQueue(DataPipeProducerHandle handle)
+ : handle_(handle), is_waiting_(false) {
+}
+
+WebSocketWriteQueue::~WebSocketWriteQueue() {
+}
+
+void WebSocketWriteQueue::Write(const char* data,
+ uint32_t num_bytes,
+ base::Callback<void(const char*)> callback) {
+ Operation* op = new Operation;
+ op->num_bytes_ = num_bytes;
+ op->callback_ = callback;
+ op->data_ = data;
+ queue_.push_back(op);
+
+ MojoResult result = MOJO_RESULT_SHOULD_WAIT;
+ if (!is_waiting_)
+ result = TryToWrite();
+
+ // If we have to wait, make a local copy of the data so we know it will
+ // live until we need it.
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ op->data_copy_.resize(num_bytes);
+ memcpy(&op->data_copy_[0], data, num_bytes);
+ op->data_ = &op->data_copy_[0];
+ }
+}
+
+MojoResult WebSocketWriteQueue::TryToWrite() {
+ Operation* op = queue_[0];
+ uint32_t bytes_written = op->num_bytes_;
+ MojoResult result = WriteDataRaw(
+ handle_, op->data_, &bytes_written, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ Wait();
+ return result;
+ }
+
+ // Ensure |op| is deleted, whether or not |this| goes away.
+ scoped_ptr<Operation> op_deleter(op);
+ queue_.weak_erase(queue_.begin());
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ op->callback_.Run(op->data_); // may delete |this|
+ return result;
+}
+
+void WebSocketWriteQueue::Wait() {
+ is_waiting_ = true;
+ handle_watcher_.Start(handle_,
+ MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ base::Bind(&WebSocketWriteQueue::OnHandleReady,
+ base::Unretained(this)));
+}
+
+void WebSocketWriteQueue::OnHandleReady(MojoResult result) {
+ is_waiting_ = false;
+ TryToWrite();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/network/web_socket_write_queue.h b/mojo/services/public/cpp/network/web_socket_write_queue.h
new file mode 100644
index 0000000..7d5f5fa
--- /dev/null
+++ b/mojo/services/public/cpp/network/web_socket_write_queue.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_NETWORK_WEB_SOCKET_WRITE_QUEUE_H_
+#define MOJO_SERVICES_PUBLIC_CPP_NETWORK_WEB_SOCKET_WRITE_QUEUE_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/common/handle_watcher.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+
+namespace mojo {
+
+// This class simplifies the handling of multiple Writes on a DataPipe. It
+// writes each chunk all at once (or waits until the pipe is ready before
+// writing), calling the callback when finished. Callbacks are owned by this
+// class, and are guaranteed not to be called after this class is destroyed.
+// See also: WebSocketReadQueue
+class WebSocketWriteQueue {
+ public:
+ WebSocketWriteQueue(DataPipeProducerHandle handle);
+ ~WebSocketWriteQueue();
+
+ void Write(const char* data,
+ uint32_t num_bytes,
+ base::Callback<void(const char*)> callback);
+
+ private:
+ struct Operation;
+
+ MojoResult TryToWrite();
+ void Wait();
+ void OnHandleReady(MojoResult result);
+
+ DataPipeProducerHandle handle_;
+ common::HandleWatcher handle_watcher_;
+ ScopedVector<Operation> queue_;
+ bool is_waiting_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_NETWORK_WEB_SOCKET_WRITE_QUEUE_H_
diff --git a/mojo/services/public/cpp/surfaces/BUILD.gn b/mojo/services/public/cpp/surfaces/BUILD.gn
new file mode 100644
index 0000000..5bd6729
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_services.gypi:mojo_surfaces_lib
+component("surfaces") {
+ output_name = "mojo_surfaces_lib"
+
+ sources = [
+ "lib/surfaces_type_converters.cc",
+ "lib/surfaces_utils.cc",
+ "mojo_surfaces_export.h",
+ "surfaces_type_converters.h",
+ "surfaces_utils.h",
+ ]
+
+ defines = [ "MOJO_SURFACES_IMPLEMENTATION" ]
+
+ public_deps = [
+ "//mojo/services/public/cpp/geometry",
+ "//ui/gfx",
+ ]
+
+ deps = [
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//gpu",
+ "//ui/gfx/geometry",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_component",
+ "//mojo/services/public/interfaces/surfaces:surface_id",
+ "//mojo/services/public/interfaces/surfaces",
+ ]
+}
diff --git a/mojo/services/public/cpp/surfaces/DEPS b/mojo/services/public/cpp/surfaces/DEPS
new file mode 100644
index 0000000..bce2d34
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+ "+cc/output",
+ "+cc/quads",
+ "+cc/resources",
+ "+cc/surfaces",
+ "+gpu/command_buffer/common/mailbox.h",
+ "+gpu/command_buffer/common/mailbox_holder.h",
+ "+third_party/skia/include",
+ "+ui/gfx/geometry",
+ "+ui/gfx/transform.h",
+]
diff --git a/mojo/services/public/cpp/surfaces/lib/surfaces_type_converters.cc b/mojo/services/public/cpp/surfaces/lib/surfaces_type_converters.cc
new file mode 100644
index 0000000..7d1798e
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/lib/surfaces_type_converters.cc
@@ -0,0 +1,576 @@
+// Copyright 2014 The Chromium 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/surfaces/surfaces_type_converters.h"
+
+#include "base/macros.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/quads/yuv_video_draw_quad.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+
+namespace mojo {
+
+#define ASSERT_ENUM_VALUES_EQUAL(value) \
+ COMPILE_ASSERT(cc::DrawQuad::value == static_cast<cc::DrawQuad::Material>( \
+ MATERIAL_##value), \
+ value##_enum_value_matches)
+
+ASSERT_ENUM_VALUES_EQUAL(CHECKERBOARD);
+ASSERT_ENUM_VALUES_EQUAL(DEBUG_BORDER);
+ASSERT_ENUM_VALUES_EQUAL(IO_SURFACE_CONTENT);
+ASSERT_ENUM_VALUES_EQUAL(PICTURE_CONTENT);
+ASSERT_ENUM_VALUES_EQUAL(RENDER_PASS);
+ASSERT_ENUM_VALUES_EQUAL(SOLID_COLOR);
+ASSERT_ENUM_VALUES_EQUAL(STREAM_VIDEO_CONTENT);
+ASSERT_ENUM_VALUES_EQUAL(SURFACE_CONTENT);
+ASSERT_ENUM_VALUES_EQUAL(TEXTURE_CONTENT);
+ASSERT_ENUM_VALUES_EQUAL(TILED_CONTENT);
+ASSERT_ENUM_VALUES_EQUAL(YUV_VIDEO_CONTENT);
+
+COMPILE_ASSERT(
+ cc::YUVVideoDrawQuad::REC_601 ==
+ static_cast<cc::YUVVideoDrawQuad::ColorSpace>(YUV_COLOR_SPACE_REC_601),
+ rec_601_enum_matches);
+COMPILE_ASSERT(cc::YUVVideoDrawQuad::REC_601_JPEG ==
+ static_cast<cc::YUVVideoDrawQuad::ColorSpace>(
+ YUV_COLOR_SPACE_REC_601_JPEG),
+ rec_601_jpeg_enum_matches);
+
+namespace {
+
+cc::SharedQuadState* ConvertSharedQuadState(const SharedQuadStatePtr& input,
+ cc::RenderPass* render_pass) {
+ cc::SharedQuadState* state = render_pass->CreateAndAppendSharedQuadState();
+ state->SetAll(input->content_to_target_transform.To<gfx::Transform>(),
+ input->content_bounds.To<gfx::Size>(),
+ input->visible_content_rect.To<gfx::Rect>(),
+ input->clip_rect.To<gfx::Rect>(),
+ input->is_clipped,
+ input->opacity,
+ static_cast<::SkXfermode::Mode>(input->blend_mode),
+ input->sorting_context_id);
+ return state;
+}
+
+bool ConvertDrawQuad(const QuadPtr& input,
+ cc::SharedQuadState* sqs,
+ cc::RenderPass* render_pass) {
+ switch (input->material) {
+ case MATERIAL_RENDER_PASS: {
+ cc::RenderPassDrawQuad* render_pass_quad =
+ render_pass->CreateAndAppendDrawQuad<cc::RenderPassDrawQuad>();
+ RenderPassQuadState* render_pass_quad_state =
+ input->render_pass_quad_state.get();
+ gfx::PointF filter_scale_as_point =
+ render_pass_quad_state->filters_scale.To<gfx::PointF>();
+ render_pass_quad->SetAll(
+ sqs,
+ input->rect.To<gfx::Rect>(),
+ input->opaque_rect.To<gfx::Rect>(),
+ input->visible_rect.To<gfx::Rect>(),
+ input->needs_blending,
+ render_pass_quad_state->render_pass_id.To<cc::RenderPassId>(),
+ render_pass_quad_state->mask_resource_id,
+ render_pass_quad_state->mask_uv_rect.To<gfx::RectF>(),
+ cc::FilterOperations(), // TODO(jamesr): filters
+ gfx::Vector2dF(filter_scale_as_point.x(), filter_scale_as_point.y()),
+ cc::FilterOperations()); // TODO(jamesr): background_filters
+ break;
+ }
+ case MATERIAL_SOLID_COLOR: {
+ if (input->solid_color_quad_state.is_null())
+ return false;
+ cc::SolidColorDrawQuad* color_quad =
+ render_pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ color_quad->SetAll(
+ sqs,
+ input->rect.To<gfx::Rect>(),
+ input->opaque_rect.To<gfx::Rect>(),
+ input->visible_rect.To<gfx::Rect>(),
+ input->needs_blending,
+ input->solid_color_quad_state->color.To<SkColor>(),
+ input->solid_color_quad_state->force_anti_aliasing_off);
+ break;
+ }
+ case MATERIAL_SURFACE_CONTENT: {
+ if (input->surface_quad_state.is_null())
+ return false;
+ cc::SurfaceDrawQuad* surface_quad =
+ render_pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ surface_quad->SetAll(
+ sqs,
+ input->rect.To<gfx::Rect>(),
+ input->opaque_rect.To<gfx::Rect>(),
+ input->visible_rect.To<gfx::Rect>(),
+ input->needs_blending,
+ input->surface_quad_state->surface.To<cc::SurfaceId>());
+ break;
+ }
+ case MATERIAL_TEXTURE_CONTENT: {
+ TextureQuadStatePtr& texture_quad_state =
+ input->texture_quad_state;
+ if (texture_quad_state.is_null() ||
+ texture_quad_state->vertex_opacity.is_null() ||
+ texture_quad_state->background_color.is_null())
+ return false;
+ cc::TextureDrawQuad* texture_quad =
+ render_pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>();
+ texture_quad->SetAll(
+ sqs,
+ input->rect.To<gfx::Rect>(),
+ input->opaque_rect.To<gfx::Rect>(),
+ input->visible_rect.To<gfx::Rect>(),
+ input->needs_blending,
+ texture_quad_state->resource_id,
+ texture_quad_state->premultiplied_alpha,
+ texture_quad_state->uv_top_left.To<gfx::PointF>(),
+ texture_quad_state->uv_bottom_right.To<gfx::PointF>(),
+ texture_quad_state->background_color.To<SkColor>(),
+ &texture_quad_state->vertex_opacity.storage()[0],
+ texture_quad_state->flipped);
+ break;
+ }
+ case MATERIAL_TILED_CONTENT: {
+ TileQuadStatePtr& tile_state = input->tile_quad_state;
+ if (tile_state.is_null())
+ return false;
+ cc::TileDrawQuad* tile_quad =
+ render_pass->CreateAndAppendDrawQuad<cc::TileDrawQuad>();
+ tile_quad->SetAll(sqs,
+ input->rect.To<gfx::Rect>(),
+ input->opaque_rect.To<gfx::Rect>(),
+ input->visible_rect.To<gfx::Rect>(),
+ input->needs_blending,
+ tile_state->resource_id,
+ tile_state->tex_coord_rect.To<gfx::RectF>(),
+ tile_state->texture_size.To<gfx::Size>(),
+ tile_state->swizzle_contents);
+ break;
+ }
+ case MATERIAL_YUV_VIDEO_CONTENT: {
+ YUVVideoQuadStatePtr& yuv_state = input->yuv_video_quad_state;
+ if (yuv_state.is_null())
+ return false;
+ cc::YUVVideoDrawQuad* yuv_quad =
+ render_pass->CreateAndAppendDrawQuad<cc::YUVVideoDrawQuad>();
+ yuv_quad->SetAll(sqs,
+ input->rect.To<gfx::Rect>(),
+ input->opaque_rect.To<gfx::Rect>(),
+ input->visible_rect.To<gfx::Rect>(),
+ input->needs_blending,
+ yuv_state->tex_coord_rect.To<gfx::RectF>(),
+ yuv_state->y_plane_resource_id,
+ yuv_state->u_plane_resource_id,
+ yuv_state->v_plane_resource_id,
+ yuv_state->a_plane_resource_id,
+ static_cast<cc::YUVVideoDrawQuad::ColorSpace>(
+ yuv_state->color_space));
+ break;
+ }
+ default:
+ NOTREACHED() << "Unsupported material " << input->material;
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+// static
+SurfaceIdPtr TypeConverter<SurfaceIdPtr, cc::SurfaceId>::Convert(
+ const cc::SurfaceId& input) {
+ SurfaceIdPtr id(SurfaceId::New());
+ id->id = input.id;
+ return id.Pass();
+}
+
+// static
+cc::SurfaceId TypeConverter<cc::SurfaceId, SurfaceIdPtr>::Convert(
+ const SurfaceIdPtr& input) {
+ return cc::SurfaceId(input->id);
+}
+
+// static
+ColorPtr TypeConverter<ColorPtr, SkColor>::Convert(const SkColor& input) {
+ ColorPtr color(Color::New());
+ color->rgba = input;
+ return color.Pass();
+}
+
+// static
+SkColor TypeConverter<SkColor, ColorPtr>::Convert(const ColorPtr& input) {
+ return input->rgba;
+}
+
+// static
+RenderPassIdPtr TypeConverter<RenderPassIdPtr, cc::RenderPassId>::Convert(
+ const cc::RenderPassId& input) {
+ RenderPassIdPtr pass_id(RenderPassId::New());
+ pass_id->layer_id = input.layer_id;
+ pass_id->index = input.index;
+ return pass_id.Pass();
+}
+
+// static
+cc::RenderPassId TypeConverter<cc::RenderPassId, RenderPassIdPtr>::Convert(
+ const RenderPassIdPtr& input) {
+ return cc::RenderPassId(input->layer_id, input->index);
+}
+
+// static
+QuadPtr TypeConverter<QuadPtr, cc::DrawQuad>::Convert(
+ const cc::DrawQuad& input) {
+ QuadPtr quad = Quad::New();
+ quad->material = static_cast<Material>(input.material);
+ quad->rect = Rect::From(input.rect);
+ quad->opaque_rect = Rect::From(input.opaque_rect);
+ quad->visible_rect = Rect::From(input.visible_rect);
+ quad->needs_blending = input.needs_blending;
+ // This is intentionally left set to an invalid value here. It's set when
+ // converting an entire pass since it's an index into the pass' shared quad
+ // state list.
+ quad->shared_quad_state_index = -1;
+ switch (input.material) {
+ case cc::DrawQuad::RENDER_PASS: {
+ const cc::RenderPassDrawQuad* render_pass_quad =
+ cc::RenderPassDrawQuad::MaterialCast(&input);
+ RenderPassQuadStatePtr pass_state = RenderPassQuadState::New();
+ pass_state->render_pass_id =
+ RenderPassId::From(render_pass_quad->render_pass_id);
+ pass_state->mask_resource_id = render_pass_quad->mask_resource_id;
+ pass_state->mask_uv_rect = RectF::From(render_pass_quad->mask_uv_rect);
+ // TODO(jamesr): pass_state->filters
+ pass_state->filters_scale = PointF::From(
+ gfx::PointAtOffsetFromOrigin(render_pass_quad->filters_scale));
+ // TODO(jamesr): pass_state->background_filters
+ quad->render_pass_quad_state = pass_state.Pass();
+ break;
+ }
+ case cc::DrawQuad::SOLID_COLOR: {
+ const cc::SolidColorDrawQuad* color_quad =
+ cc::SolidColorDrawQuad::MaterialCast(&input);
+ SolidColorQuadStatePtr color_state = SolidColorQuadState::New();
+ color_state->color = Color::From(color_quad->color);
+ color_state->force_anti_aliasing_off =
+ color_quad->force_anti_aliasing_off;
+ quad->solid_color_quad_state = color_state.Pass();
+ break;
+ }
+ case cc::DrawQuad::SURFACE_CONTENT: {
+ const cc::SurfaceDrawQuad* surface_quad =
+ cc::SurfaceDrawQuad::MaterialCast(&input);
+ SurfaceQuadStatePtr surface_state =
+ SurfaceQuadState::New();
+ surface_state->surface = SurfaceId::From(surface_quad->surface_id);
+ quad->surface_quad_state = surface_state.Pass();
+ break;
+ }
+ case cc::DrawQuad::TEXTURE_CONTENT: {
+ const cc::TextureDrawQuad* texture_quad =
+ cc::TextureDrawQuad::MaterialCast(&input);
+ TextureQuadStatePtr texture_state = TextureQuadState::New();
+ texture_state->resource_id = texture_quad->resource_id;
+ texture_state->premultiplied_alpha = texture_quad->premultiplied_alpha;
+ texture_state->uv_top_left = PointF::From(texture_quad->uv_top_left);
+ texture_state->uv_bottom_right =
+ PointF::From(texture_quad->uv_bottom_right);
+ texture_state->background_color =
+ Color::From(texture_quad->background_color);
+ Array<float> vertex_opacity(4);
+ for (size_t i = 0; i < 4; ++i) {
+ vertex_opacity[i] = texture_quad->vertex_opacity[i];
+ }
+ texture_state->vertex_opacity = vertex_opacity.Pass();
+ texture_state->flipped = texture_quad->flipped;
+ quad->texture_quad_state = texture_state.Pass();
+ break;
+ }
+ case cc::DrawQuad::TILED_CONTENT: {
+ const cc::TileDrawQuad* tile_quad =
+ cc::TileDrawQuad::MaterialCast(&input);
+ TileQuadStatePtr tile_state = TileQuadState::New();
+ tile_state->tex_coord_rect = RectF::From(tile_quad->tex_coord_rect);
+ tile_state->texture_size = Size::From(tile_quad->texture_size);
+ tile_state->swizzle_contents = tile_quad->swizzle_contents;
+ tile_state->resource_id = tile_quad->resource_id;
+ quad->tile_quad_state = tile_state.Pass();
+ break;
+ }
+ case cc::DrawQuad::YUV_VIDEO_CONTENT: {
+ const cc::YUVVideoDrawQuad* yuv_quad =
+ cc::YUVVideoDrawQuad::MaterialCast(&input);
+ YUVVideoQuadStatePtr yuv_state = YUVVideoQuadState::New();
+ yuv_state->tex_coord_rect = RectF::From(yuv_quad->tex_coord_rect);
+ yuv_state->y_plane_resource_id = yuv_quad->y_plane_resource_id;
+ yuv_state->u_plane_resource_id = yuv_quad->u_plane_resource_id;
+ yuv_state->v_plane_resource_id = yuv_quad->v_plane_resource_id;
+ yuv_state->a_plane_resource_id = yuv_quad->a_plane_resource_id;
+ yuv_state->color_space =
+ static_cast<YUVColorSpace>(yuv_quad->color_space);
+ quad->yuv_video_quad_state = yuv_state.Pass();
+ break;
+ }
+
+ default:
+ NOTREACHED() << "Unsupported material " << input.material;
+ }
+ return quad.Pass();
+}
+
+// static
+SharedQuadStatePtr
+TypeConverter<SharedQuadStatePtr, cc::SharedQuadState>::Convert(
+ const cc::SharedQuadState& input) {
+ SharedQuadStatePtr state = SharedQuadState::New();
+ state->content_to_target_transform =
+ Transform::From(input.content_to_target_transform);
+ state->content_bounds = Size::From(input.content_bounds);
+ state->visible_content_rect = Rect::From(input.visible_content_rect);
+ state->clip_rect = Rect::From(input.clip_rect);
+ state->is_clipped = input.is_clipped;
+ state->opacity = input.opacity;
+ state->blend_mode = static_cast<SkXfermode>(input.blend_mode);
+ state->sorting_context_id = input.sorting_context_id;
+ return state.Pass();
+}
+
+// static
+PassPtr TypeConverter<PassPtr, cc::RenderPass>::Convert(
+ const cc::RenderPass& input) {
+ PassPtr pass = Pass::New();
+ pass->id = input.id.index;
+ pass->output_rect = Rect::From(input.output_rect);
+ pass->damage_rect = Rect::From(input.damage_rect);
+ pass->transform_to_root_target =
+ Transform::From(input.transform_to_root_target);
+ pass->has_transparent_background = input.has_transparent_background;
+ Array<QuadPtr> quads(input.quad_list.size());
+ Array<SharedQuadStatePtr> shared_quad_state(
+ input.shared_quad_state_list.size());
+ int sqs_i = -1;
+ const cc::SharedQuadState* last_sqs = NULL;
+ size_t i = 0;
+ for (cc::QuadList::ConstIterator iter = input.quad_list.begin();
+ iter != input.quad_list.end();
+ ++iter) {
+ const cc::DrawQuad& quad = *iter;
+ quads[i] = Quad::From(quad);
+ if (quad.shared_quad_state != last_sqs) {
+ sqs_i++;
+ shared_quad_state[sqs_i] =
+ SharedQuadState::From(*input.shared_quad_state_list[sqs_i]);
+ last_sqs = quad.shared_quad_state;
+ }
+ quads[i]->shared_quad_state_index = sqs_i;
+ ++i;
+ }
+ // We should copy all shared quad states.
+ DCHECK_EQ(static_cast<size_t>(sqs_i + 1), shared_quad_state.size());
+ pass->quads = quads.Pass();
+ pass->shared_quad_states = shared_quad_state.Pass();
+ return pass.Pass();
+}
+
+// static
+scoped_ptr<cc::RenderPass>
+TypeConverter<scoped_ptr<cc::RenderPass>, PassPtr>::Convert(
+ const PassPtr& input) {
+ // TODO(weiliangc): RenderPass will have a constructor that takes in preset
+ // size of quad list and shared quad state list size in upcoming CL.
+ scoped_ptr<cc::RenderPass> pass = cc::RenderPass::Create();
+ pass->SetAll(cc::RenderPassId(1, input->id),
+ input->output_rect.To<gfx::Rect>(),
+ input->damage_rect.To<gfx::Rect>(),
+ input->transform_to_root_target.To<gfx::Transform>(),
+ input->has_transparent_background);
+ cc::SharedQuadStateList& sqs_list = pass->shared_quad_state_list;
+ sqs_list.reserve(input->shared_quad_states.size());
+ for (size_t i = 0; i < input->shared_quad_states.size(); ++i) {
+ ConvertSharedQuadState(input->shared_quad_states[i], pass.get());
+ }
+ for (size_t i = 0; i < input->quads.size(); ++i) {
+ QuadPtr quad = input->quads[i].Pass();
+ if (!ConvertDrawQuad(
+ quad, sqs_list[quad->shared_quad_state_index], pass.get()))
+ return scoped_ptr<cc::RenderPass>();
+ }
+ return pass.Pass();
+}
+
+// static
+MailboxPtr TypeConverter<MailboxPtr, gpu::Mailbox>::Convert(
+ const gpu::Mailbox& input) {
+ Array<int8_t> name(64);
+ for (int i = 0; i < 64; ++i) {
+ name[i] = input.name[i];
+ }
+ MailboxPtr mailbox(Mailbox::New());
+ mailbox->name = name.Pass();
+ return mailbox.Pass();
+}
+
+// static
+gpu::Mailbox TypeConverter<gpu::Mailbox, MailboxPtr>::Convert(
+ const MailboxPtr& input) {
+ gpu::Mailbox mailbox;
+ if (!input->name.is_null())
+ mailbox.SetName(&input->name.storage()[0]);
+ return mailbox;
+}
+
+// static
+MailboxHolderPtr TypeConverter<MailboxHolderPtr, gpu::MailboxHolder>::Convert(
+ const gpu::MailboxHolder& input) {
+ MailboxHolderPtr holder(MailboxHolder::New());
+ holder->mailbox = Mailbox::From<gpu::Mailbox>(input.mailbox);
+ holder->texture_target = input.texture_target;
+ holder->sync_point = input.sync_point;
+ return holder.Pass();
+}
+
+// static
+gpu::MailboxHolder TypeConverter<gpu::MailboxHolder, MailboxHolderPtr>::Convert(
+ const MailboxHolderPtr& input) {
+ gpu::MailboxHolder holder;
+ holder.mailbox = input->mailbox.To<gpu::Mailbox>();
+ holder.texture_target = input->texture_target;
+ holder.sync_point = input->sync_point;
+ return holder;
+}
+
+// static
+TransferableResourcePtr
+TypeConverter<TransferableResourcePtr, cc::TransferableResource>::Convert(
+ const cc::TransferableResource& input) {
+ TransferableResourcePtr transferable = TransferableResource::New();
+ transferable->id = input.id;
+ transferable->format = static_cast<ResourceFormat>(input.format);
+ transferable->filter = input.filter;
+ transferable->size = Size::From(input.size);
+ transferable->mailbox_holder = MailboxHolder::From(input.mailbox_holder);
+ transferable->is_repeated = input.is_repeated;
+ transferable->is_software = input.is_software;
+ return transferable.Pass();
+}
+
+// static
+cc::TransferableResource
+TypeConverter<cc::TransferableResource, TransferableResourcePtr>::Convert(
+ const TransferableResourcePtr& input) {
+ cc::TransferableResource transferable;
+ transferable.id = input->id;
+ transferable.format = static_cast<cc::ResourceFormat>(input->format);
+ transferable.filter = input->filter;
+ transferable.size = input->size.To<gfx::Size>();
+ transferable.mailbox_holder = input->mailbox_holder.To<gpu::MailboxHolder>();
+ transferable.is_repeated = input->is_repeated;
+ transferable.is_software = input->is_software;
+ return transferable;
+}
+
+// static
+Array<TransferableResourcePtr> TypeConverter<
+ Array<TransferableResourcePtr>,
+ cc::TransferableResourceArray>::Convert(const cc::TransferableResourceArray&
+ input) {
+ Array<TransferableResourcePtr> resources(input.size());
+ for (size_t i = 0; i < input.size(); ++i) {
+ resources[i] = TransferableResource::From(input[i]);
+ }
+ return resources.Pass();
+}
+
+// static
+cc::TransferableResourceArray
+TypeConverter<cc::TransferableResourceArray, Array<TransferableResourcePtr> >::
+ Convert(const Array<TransferableResourcePtr>& input) {
+ cc::TransferableResourceArray resources(input.size());
+ for (size_t i = 0; i < input.size(); ++i) {
+ resources[i] = input[i].To<cc::TransferableResource>();
+ }
+ return resources;
+}
+
+// static
+ReturnedResourcePtr
+TypeConverter<ReturnedResourcePtr, cc::ReturnedResource>::Convert(
+ const cc::ReturnedResource& input) {
+ ReturnedResourcePtr returned = ReturnedResource::New();
+ returned->id = input.id;
+ returned->sync_point = input.sync_point;
+ returned->count = input.count;
+ returned->lost = input.lost;
+ return returned.Pass();
+}
+
+// static
+cc::ReturnedResource
+TypeConverter<cc::ReturnedResource, ReturnedResourcePtr>::Convert(
+ const ReturnedResourcePtr& input) {
+ cc::ReturnedResource returned;
+ returned.id = input->id;
+ returned.sync_point = input->sync_point;
+ returned.count = input->count;
+ returned.lost = input->lost;
+ return returned;
+}
+
+// static
+Array<ReturnedResourcePtr>
+TypeConverter<Array<ReturnedResourcePtr>, cc::ReturnedResourceArray>::Convert(
+ const cc::ReturnedResourceArray& input) {
+ Array<ReturnedResourcePtr> resources(input.size());
+ for (size_t i = 0; i < input.size(); ++i) {
+ resources[i] = ReturnedResource::From(input[i]);
+ }
+ return resources.Pass();
+}
+
+// static
+FramePtr TypeConverter<FramePtr, cc::CompositorFrame>::Convert(
+ const cc::CompositorFrame& input) {
+ FramePtr frame = Frame::New();
+ DCHECK(input.delegated_frame_data);
+ cc::DelegatedFrameData* frame_data = input.delegated_frame_data.get();
+ frame->resources =
+ Array<TransferableResourcePtr>::From(frame_data->resource_list);
+ const cc::RenderPassList& pass_list = frame_data->render_pass_list;
+ frame->passes = Array<PassPtr>::New(pass_list.size());
+ for (size_t i = 0; i < pass_list.size(); ++i) {
+ frame->passes[i] = Pass::From(*pass_list[i]);
+ }
+ return frame.Pass();
+}
+
+// static
+scoped_ptr<cc::CompositorFrame>
+TypeConverter<scoped_ptr<cc::CompositorFrame>, FramePtr>::Convert(
+ const FramePtr& input) {
+ scoped_ptr<cc::DelegatedFrameData> frame_data(new cc::DelegatedFrameData);
+ frame_data->device_scale_factor = 1.f;
+ frame_data->resource_list =
+ input->resources.To<cc::TransferableResourceArray>();
+ frame_data->render_pass_list.reserve(input->passes.size());
+ for (size_t i = 0; i < input->passes.size(); ++i) {
+ scoped_ptr<cc::RenderPass> pass =
+ input->passes[i].To<scoped_ptr<cc::RenderPass> >();
+ if (!pass)
+ return scoped_ptr<cc::CompositorFrame>();
+ frame_data->render_pass_list.push_back(pass.Pass());
+ }
+ scoped_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame);
+ frame->delegated_frame_data = frame_data.Pass();
+ return frame.Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/surfaces/lib/surfaces_utils.cc b/mojo/services/public/cpp/surfaces/lib/surfaces_utils.cc
new file mode 100644
index 0000000..c35a77a
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/lib/surfaces_utils.cc
@@ -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.
+
+#include "mojo/services/public/cpp/surfaces/surfaces_utils.h"
+
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/transform.h"
+
+namespace mojo {
+
+SharedQuadStatePtr CreateDefaultSQS(const gfx::Size& size) {
+ SharedQuadStatePtr sqs = SharedQuadState::New();
+ sqs->content_to_target_transform = Transform::From(gfx::Transform());
+ sqs->content_bounds = Size::From(size);
+ sqs->visible_content_rect = Rect::From(gfx::Rect(size));
+ sqs->clip_rect = Rect::From(gfx::Rect(size));
+ sqs->is_clipped = false;
+ sqs->opacity = 1.f;
+ sqs->blend_mode = mojo::SK_XFERMODE_kSrc_Mode;
+ sqs->sorting_context_id = 0;
+ return sqs.Pass();
+}
+
+PassPtr CreateDefaultPass(int id, const gfx::Rect& rect) {
+ PassPtr pass = Pass::New();
+ pass->id = id;
+ pass->output_rect = Rect::From(rect);
+ pass->damage_rect = Rect::From(rect);
+ pass->transform_to_root_target = Transform::From(gfx::Transform());
+ pass->has_transparent_background = false;
+ return pass.Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/surfaces/mojo_surfaces_export.h b/mojo/services/public/cpp/surfaces/mojo_surfaces_export.h
new file mode 100644
index 0000000..31b408f
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/mojo_surfaces_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_SURFACES_MOJO_SURFACES_EXPORT_H_
+#define MOJO_SERVICES_PUBLIC_CPP_SURFACES_MOJO_SURFACES_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WIN32)
+
+#if defined(MOJO_SURFACES_IMPLEMENTATION)
+#define MOJO_SURFACES_EXPORT __declspec(dllexport)
+#else
+#define MOJO_SURFACES_EXPORT __declspec(dllimport)
+#endif
+
+#else // !defined(WIN32)
+
+#if defined(MOJO_SURFACES_IMPLEMENTATION)
+#define MOJO_SURFACES_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_SURFACES_EXPORT
+#endif
+
+#endif // defined(WIN32)
+
+#else // !defined(COMPONENT_BUILD)
+#define MOJO_SURFACES_EXPORT
+#endif
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_SURFACES_MOJO_SURFACES_EXPORT_H_
diff --git a/mojo/services/public/cpp/surfaces/surfaces_type_converters.h b/mojo/services/public/cpp/surfaces/surfaces_type_converters.h
new file mode 100644
index 0000000..4ef0e2c
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/surfaces_type_converters.h
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium 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_SURFACES_SURFACES_TYPE_CONVERTERS_H_
+#define MOJO_SERVICES_PUBLIC_CPP_SURFACES_SURFACES_TYPE_CONVERTERS_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/resources/returned_resource.h"
+#include "cc/resources/transferable_resource.h"
+#include "cc/surfaces/surface_id.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "gpu/command_buffer/common/mailbox_holder.h"
+#include "mojo/services/public/cpp/surfaces/mojo_surfaces_export.h"
+#include "mojo/services/public/interfaces/surfaces/quads.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+class CompositorFrame;
+class DrawQuad;
+class RenderPass;
+class RenderPassId;
+class SharedQuadState;
+} // namespace cc
+
+namespace mojo {
+
+// Types from surface_id.mojom
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<SurfaceIdPtr, cc::SurfaceId> {
+ static SurfaceIdPtr Convert(const cc::SurfaceId& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<cc::SurfaceId, SurfaceIdPtr> {
+ static cc::SurfaceId Convert(const SurfaceIdPtr& input);
+};
+
+// Types from quads.mojom
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<ColorPtr, SkColor> {
+ static ColorPtr Convert(const SkColor& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<SkColor, ColorPtr> {
+ static SkColor Convert(const ColorPtr& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<RenderPassIdPtr, cc::RenderPassId> {
+ static RenderPassIdPtr Convert(const cc::RenderPassId& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<cc::RenderPassId, RenderPassIdPtr> {
+ static cc::RenderPassId Convert(const RenderPassIdPtr& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<QuadPtr, cc::DrawQuad> {
+ static QuadPtr Convert(const cc::DrawQuad& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<SharedQuadStatePtr, cc::SharedQuadState> {
+ static SharedQuadStatePtr Convert(const cc::SharedQuadState& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<PassPtr, cc::RenderPass> {
+ static PassPtr Convert(const cc::RenderPass& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<scoped_ptr<cc::RenderPass>, PassPtr> {
+ static scoped_ptr<cc::RenderPass> Convert(const PassPtr& input);
+};
+
+// Types from surfaces.mojom
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<MailboxPtr, gpu::Mailbox> {
+ static MailboxPtr Convert(const gpu::Mailbox& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<gpu::Mailbox, MailboxPtr> {
+ static gpu::Mailbox Convert(const MailboxPtr& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<MailboxHolderPtr, gpu::MailboxHolder> {
+ static MailboxHolderPtr Convert(const gpu::MailboxHolder& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<gpu::MailboxHolder, MailboxHolderPtr> {
+ static gpu::MailboxHolder Convert(const MailboxHolderPtr& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<TransferableResourcePtr, cc::TransferableResource> {
+ static TransferableResourcePtr Convert(const cc::TransferableResource& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<cc::TransferableResource, TransferableResourcePtr> {
+ static cc::TransferableResource Convert(const TransferableResourcePtr& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<Array<TransferableResourcePtr>, cc::TransferableResourceArray> {
+ static Array<TransferableResourcePtr> Convert(
+ const cc::TransferableResourceArray& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<cc::TransferableResourceArray, Array<TransferableResourcePtr> > {
+ static cc::TransferableResourceArray Convert(
+ const Array<TransferableResourcePtr>& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<ReturnedResourcePtr, cc::ReturnedResource> {
+ static ReturnedResourcePtr Convert(const cc::ReturnedResource& input);
+};
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<cc::ReturnedResource, ReturnedResourcePtr> {
+ static cc::ReturnedResource Convert(const ReturnedResourcePtr& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<Array<ReturnedResourcePtr>, cc::ReturnedResourceArray> {
+ static Array<ReturnedResourcePtr> Convert(
+ const cc::ReturnedResourceArray& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT TypeConverter<FramePtr, cc::CompositorFrame> {
+ static FramePtr Convert(const cc::CompositorFrame& input);
+};
+
+template <>
+struct MOJO_SURFACES_EXPORT
+TypeConverter<scoped_ptr<cc::CompositorFrame>, FramePtr> {
+ static scoped_ptr<cc::CompositorFrame> Convert(const FramePtr& input);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_SURFACES_SURFACES_TYPE_CONVERTERS_H_
diff --git a/mojo/services/public/cpp/surfaces/surfaces_utils.h b/mojo/services/public/cpp/surfaces/surfaces_utils.h
new file mode 100644
index 0000000..e4c3c83
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/surfaces_utils.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_SURFACES_SURFACES_UTILS_H_
+#define MOJO_SERVICES_PUBLIC_CPP_SURFACES_SURFACES_UTILS_H_
+
+#include "mojo/services/public/cpp/surfaces/mojo_surfaces_export.h"
+#include "mojo/services/public/interfaces/surfaces/quads.mojom.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace mojo {
+
+MOJO_SURFACES_EXPORT SharedQuadStatePtr CreateDefaultSQS(const gfx::Size& size);
+
+// Constructs a pass with the given id, output_rect and damage_rect set to rect,
+// transform_to_root_target set to identity and has_transparent_background set
+// to false.
+MOJO_SURFACES_EXPORT PassPtr CreateDefaultPass(int id, const gfx::Rect& rect);
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_SURFACES_SURFACES_UTILS_H_
diff --git a/mojo/services/public/cpp/surfaces/tests/BUILD.gn b/mojo/services/public/cpp/surfaces/tests/BUILD.gn
new file mode 100644
index 0000000..10810fe
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/tests/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_services.gypi:mojo_surfaces_lib_unittests
+test("mojo_surfaces_lib_unittests") {
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//cc",
+ "//cc/surfaces",
+ "//gpu",
+ "//skia",
+ "//testing/gtest",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gfx:test_support",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/surfaces",
+ "//mojo/common/test:run_all_unittests",
+ ]
+
+ sources = [ "surface_unittest.cc" ]
+}
diff --git a/mojo/services/public/cpp/surfaces/tests/surface_unittest.cc b/mojo/services/public/cpp/surfaces/tests/surface_unittest.cc
new file mode 100644
index 0000000..93cf571
--- /dev/null
+++ b/mojo/services/public/cpp/surfaces/tests/surface_unittest.cc
@@ -0,0 +1,460 @@
+// Copyright 2014 The Chromium 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 "cc/quads/render_pass.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "gpu/command_buffer/common/mailbox_holder.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkXfermode.h"
+
+namespace mojo {
+namespace {
+
+TEST(SurfaceLibTest, SurfaceIdConverterNullId) {
+ cc::SurfaceId null_id;
+ cc::SurfaceId round_trip = SurfaceId::From(null_id).To<cc::SurfaceId>();
+ EXPECT_TRUE(round_trip.is_null());
+}
+
+TEST(SurfaceLibTest, SurfaceIdConverterValidId) {
+ cc::SurfaceId valid_id(7);
+ cc::SurfaceId round_trip = SurfaceId::From(valid_id).To<cc::SurfaceId>();
+ EXPECT_FALSE(round_trip.is_null());
+ EXPECT_EQ(valid_id, round_trip);
+}
+
+TEST(SurfaceLibTest, Color) {
+ SkColor arbitrary_color = SK_ColorMAGENTA;
+ SkColor round_trip = Color::From(arbitrary_color).To<SkColor>();
+ EXPECT_EQ(arbitrary_color, round_trip);
+}
+
+class SurfaceLibQuadTest : public testing::Test {
+ public:
+ SurfaceLibQuadTest()
+ : rect(5, 7, 13, 19),
+ opaque_rect(rect),
+ visible_rect(9, 11, 5, 7),
+ needs_blending(false) {
+ pass = cc::RenderPass::Create();
+ sqs = pass->CreateAndAppendSharedQuadState();
+ }
+
+ protected:
+ gfx::Rect rect;
+ gfx::Rect opaque_rect;
+ gfx::Rect visible_rect;
+ bool needs_blending;
+ scoped_ptr<cc::RenderPass> pass;
+ cc::SharedQuadState* sqs;
+};
+
+TEST_F(SurfaceLibQuadTest, ColorQuad) {
+ cc::SolidColorDrawQuad* color_quad =
+ pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ SkColor arbitrary_color = SK_ColorGREEN;
+ bool force_anti_aliasing_off = true;
+ color_quad->SetAll(sqs,
+ rect,
+ opaque_rect,
+ visible_rect,
+ needs_blending,
+ arbitrary_color,
+ force_anti_aliasing_off);
+
+ QuadPtr mojo_quad = Quad::From<cc::DrawQuad>(*color_quad);
+ ASSERT_FALSE(mojo_quad.is_null());
+ EXPECT_EQ(MATERIAL_SOLID_COLOR, mojo_quad->material);
+ EXPECT_EQ(Rect::From(rect), mojo_quad->rect);
+ EXPECT_EQ(Rect::From(opaque_rect), mojo_quad->opaque_rect);
+ EXPECT_EQ(Rect::From(visible_rect), mojo_quad->visible_rect);
+ EXPECT_EQ(needs_blending, mojo_quad->needs_blending);
+ ASSERT_TRUE(mojo_quad->solid_color_quad_state);
+ SolidColorQuadStatePtr& mojo_color_state = mojo_quad->solid_color_quad_state;
+ EXPECT_EQ(Color::From(arbitrary_color), mojo_color_state->color);
+ EXPECT_EQ(force_anti_aliasing_off, mojo_color_state->force_anti_aliasing_off);
+}
+
+TEST_F(SurfaceLibQuadTest, SurfaceQuad) {
+ cc::SurfaceDrawQuad* surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ cc::SurfaceId arbitrary_id(5);
+ surface_quad->SetAll(
+ sqs, rect, opaque_rect, visible_rect, needs_blending, arbitrary_id);
+
+ QuadPtr mojo_quad = Quad::From<cc::DrawQuad>(*surface_quad);
+ ASSERT_FALSE(mojo_quad.is_null());
+ EXPECT_EQ(MATERIAL_SURFACE_CONTENT, mojo_quad->material);
+ ASSERT_TRUE(mojo_quad->surface_quad_state);
+ SurfaceQuadStatePtr& mojo_surface_state = mojo_quad->surface_quad_state;
+ EXPECT_EQ(SurfaceId::From(arbitrary_id),
+ mojo_surface_state->surface);
+}
+
+TEST_F(SurfaceLibQuadTest, TextureQuad) {
+ cc::TextureDrawQuad* texture_quad =
+ pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>();
+ unsigned resource_id = 9;
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(1.7f, 2.1f);
+ gfx::PointF uv_bottom_right(-7.f, 16.3f);
+ SkColor background_color = SK_ColorYELLOW;
+ float vertex_opacity[4] = {0.1f, 0.5f, 0.4f, 0.8f};
+ bool flipped = false;
+ texture_quad->SetAll(sqs,
+ rect,
+ opaque_rect,
+ visible_rect,
+ needs_blending,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ background_color,
+ vertex_opacity,
+ flipped);
+
+ QuadPtr mojo_quad = Quad::From<cc::DrawQuad>(*texture_quad);
+ ASSERT_FALSE(mojo_quad.is_null());
+ EXPECT_EQ(MATERIAL_TEXTURE_CONTENT, mojo_quad->material);
+ ASSERT_TRUE(mojo_quad->texture_quad_state);
+ TextureQuadStatePtr& mojo_texture_state = mojo_quad->texture_quad_state;
+ EXPECT_EQ(resource_id, mojo_texture_state->resource_id);
+ EXPECT_EQ(premultiplied_alpha, mojo_texture_state->premultiplied_alpha);
+ EXPECT_EQ(PointF::From(uv_top_left), mojo_texture_state->uv_top_left);
+ EXPECT_EQ(PointF::From(uv_bottom_right), mojo_texture_state->uv_bottom_right);
+ EXPECT_EQ(Color::From(background_color),
+ mojo_texture_state->background_color);
+ for (size_t i = 0; i < 4; ++i) {
+ EXPECT_EQ(vertex_opacity[i], mojo_texture_state->vertex_opacity[i]) << i;
+ }
+ EXPECT_EQ(flipped, mojo_texture_state->flipped);
+}
+
+TEST_F(SurfaceLibQuadTest, TextureQuadEmptyVertexOpacity) {
+ QuadPtr mojo_texture_quad = Quad::New();
+ mojo_texture_quad->material = MATERIAL_TEXTURE_CONTENT;
+ TextureQuadStatePtr mojo_texture_state = TextureQuadState::New();
+ mojo_texture_state->background_color = Color::New();
+ mojo_texture_quad->texture_quad_state = mojo_texture_state.Pass();
+ PassPtr mojo_pass = Pass::New();
+ mojo_pass->quads.push_back(mojo_texture_quad.Pass());
+ SharedQuadStatePtr mojo_sqs = SharedQuadState::New();
+ mojo_pass->shared_quad_states.push_back(mojo_sqs.Pass());
+
+ scoped_ptr<cc::RenderPass> pass = mojo_pass.To<scoped_ptr<cc::RenderPass> >();
+
+ EXPECT_FALSE(pass);
+}
+
+TEST_F(SurfaceLibQuadTest, TextureQuadEmptyBackgroundColor) {
+ QuadPtr mojo_texture_quad = Quad::New();
+ mojo_texture_quad->material = MATERIAL_TEXTURE_CONTENT;
+ TextureQuadStatePtr mojo_texture_state = TextureQuadState::New();
+ mojo_texture_state->vertex_opacity = mojo::Array<float>::New(4);
+ mojo_texture_quad->texture_quad_state = mojo_texture_state.Pass();
+ PassPtr mojo_pass = Pass::New();
+ mojo_pass->quads.push_back(mojo_texture_quad.Pass());
+ SharedQuadStatePtr mojo_sqs = SharedQuadState::New();
+ mojo_pass->shared_quad_states.push_back(mojo_sqs.Pass());
+
+ scoped_ptr<cc::RenderPass> pass = mojo_pass.To<scoped_ptr<cc::RenderPass> >();
+ EXPECT_FALSE(pass);
+}
+
+TEST(SurfaceLibTest, SharedQuadState) {
+ gfx::Transform content_to_target_transform;
+ content_to_target_transform.Scale3d(0.3f, 0.7f, 0.9f);
+ gfx::Size content_bounds(57, 39);
+ gfx::Rect visible_content_rect(3, 7, 28, 42);
+ gfx::Rect clip_rect(9, 12, 21, 31);
+ bool is_clipped = true;
+ float opacity = 0.65f;
+ int sorting_context_id = 13;
+ ::SkXfermode::Mode blend_mode = ::SkXfermode::kSrcOver_Mode;
+ scoped_ptr<cc::RenderPass> pass = cc::RenderPass::Create();
+ cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
+ sqs->SetAll(content_to_target_transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity,
+ blend_mode,
+ sorting_context_id);
+
+ SharedQuadStatePtr mojo_sqs = SharedQuadState::From(*sqs);
+ ASSERT_FALSE(mojo_sqs.is_null());
+ EXPECT_EQ(Transform::From(content_to_target_transform),
+ mojo_sqs->content_to_target_transform);
+ EXPECT_EQ(Size::From(content_bounds), mojo_sqs->content_bounds);
+ EXPECT_EQ(Rect::From(visible_content_rect), mojo_sqs->visible_content_rect);
+ EXPECT_EQ(Rect::From(clip_rect), mojo_sqs->clip_rect);
+ EXPECT_EQ(is_clipped, mojo_sqs->is_clipped);
+ EXPECT_EQ(opacity, mojo_sqs->opacity);
+ EXPECT_EQ(sorting_context_id, mojo_sqs->sorting_context_id);
+}
+
+TEST(SurfaceLibTest, RenderPass) {
+ scoped_ptr<cc::RenderPass> pass = cc::RenderPass::Create();
+ cc::RenderPassId pass_id(1, 6);
+ gfx::Rect output_rect(4, 9, 13, 71);
+ gfx::Rect damage_rect(9, 17, 41, 45);
+ gfx::Transform transform_to_root_target;
+ transform_to_root_target.SkewY(43.0);
+ bool has_transparent_background = false;
+ pass->SetAll(pass_id,
+ output_rect,
+ damage_rect,
+ transform_to_root_target,
+ has_transparent_background);
+
+ gfx::Transform content_to_target_transform;
+ content_to_target_transform.Scale3d(0.3f, 0.7f, 0.9f);
+ gfx::Size content_bounds(57, 39);
+ gfx::Rect visible_content_rect(3, 7, 28, 42);
+ gfx::Rect clip_rect(9, 12, 21, 31);
+ bool is_clipped = true;
+ float opacity = 0.65f;
+ int sorting_context_id = 13;
+ ::SkXfermode::Mode blend_mode = ::SkXfermode::kSrcOver_Mode;
+ cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
+ sqs->SetAll(content_to_target_transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity,
+ blend_mode,
+ sorting_context_id);
+
+ gfx::Rect rect(5, 7, 13, 19);
+ gfx::Rect opaque_rect(rect);
+ gfx::Rect visible_rect(9, 11, 5, 7);
+ bool needs_blending = false;
+
+ cc::SolidColorDrawQuad* color_quad =
+ pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ SkColor arbitrary_color = SK_ColorGREEN;
+ bool force_anti_aliasing_off = true;
+ color_quad->SetAll(pass->shared_quad_state_list.back(),
+ rect,
+ opaque_rect,
+ visible_rect,
+ needs_blending,
+ arbitrary_color,
+ force_anti_aliasing_off);
+
+ cc::SurfaceDrawQuad* surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ cc::SurfaceId arbitrary_id(5);
+ surface_quad->SetAll(
+ sqs, rect, opaque_rect, visible_rect, needs_blending, arbitrary_id);
+
+ cc::TextureDrawQuad* texture_quad =
+ pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>();
+ unsigned resource_id = 9;
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(1.7f, 2.1f);
+ gfx::PointF uv_bottom_right(-7.f, 16.3f);
+ SkColor background_color = SK_ColorYELLOW;
+ float vertex_opacity[4] = {0.1f, 0.5f, 0.4f, 0.8f};
+ bool flipped = false;
+ texture_quad->SetAll(sqs,
+ rect,
+ opaque_rect,
+ visible_rect,
+ needs_blending,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ background_color,
+ vertex_opacity,
+ flipped);
+
+ PassPtr mojo_pass = Pass::From(*pass);
+ ASSERT_FALSE(mojo_pass.is_null());
+ EXPECT_EQ(6, mojo_pass->id);
+ EXPECT_EQ(Rect::From(output_rect), mojo_pass->output_rect);
+ EXPECT_EQ(Rect::From(damage_rect), mojo_pass->damage_rect);
+ EXPECT_EQ(Transform::From(transform_to_root_target),
+ mojo_pass->transform_to_root_target);
+ EXPECT_EQ(has_transparent_background, mojo_pass->has_transparent_background);
+ ASSERT_EQ(1u, mojo_pass->shared_quad_states.size());
+ ASSERT_EQ(3u, mojo_pass->quads.size());
+ EXPECT_EQ(0, mojo_pass->quads[0]->shared_quad_state_index);
+
+ scoped_ptr<cc::RenderPass> round_trip_pass =
+ mojo_pass.To<scoped_ptr<cc::RenderPass> >();
+ EXPECT_EQ(pass_id, round_trip_pass->id);
+ EXPECT_EQ(output_rect, round_trip_pass->output_rect);
+ EXPECT_EQ(damage_rect, round_trip_pass->damage_rect);
+ EXPECT_EQ(transform_to_root_target,
+ round_trip_pass->transform_to_root_target);
+ EXPECT_EQ(has_transparent_background,
+ round_trip_pass->has_transparent_background);
+ ASSERT_EQ(1u, round_trip_pass->shared_quad_state_list.size());
+ ASSERT_EQ(3u, round_trip_pass->quad_list.size());
+ EXPECT_EQ(round_trip_pass->shared_quad_state_list.front(),
+ round_trip_pass->quad_list.front()->shared_quad_state);
+
+ cc::SharedQuadState* round_trip_sqs =
+ round_trip_pass->shared_quad_state_list.front();
+ EXPECT_EQ(content_to_target_transform,
+ round_trip_sqs->content_to_target_transform);
+ EXPECT_EQ(content_bounds, round_trip_sqs->content_bounds);
+ EXPECT_EQ(visible_content_rect, round_trip_sqs->visible_content_rect);
+ EXPECT_EQ(clip_rect, round_trip_sqs->clip_rect);
+ EXPECT_EQ(is_clipped, round_trip_sqs->is_clipped);
+ EXPECT_EQ(opacity, round_trip_sqs->opacity);
+ EXPECT_EQ(sorting_context_id, round_trip_sqs->sorting_context_id);
+
+ cc::QuadList::Iterator dq_iter = round_trip_pass->quad_list.begin();
+ // First is solid color quad.
+ ASSERT_EQ(cc::DrawQuad::SOLID_COLOR, dq_iter->material);
+ EXPECT_EQ(rect, dq_iter->rect);
+ EXPECT_EQ(opaque_rect, dq_iter->opaque_rect);
+ EXPECT_EQ(visible_rect, dq_iter->visible_rect);
+ EXPECT_EQ(needs_blending, dq_iter->needs_blending);
+ const cc::SolidColorDrawQuad* round_trip_color_quad =
+ cc::SolidColorDrawQuad::MaterialCast(&*dq_iter);
+ EXPECT_EQ(arbitrary_color, round_trip_color_quad->color);
+ EXPECT_EQ(force_anti_aliasing_off,
+ round_trip_color_quad->force_anti_aliasing_off);
+
+ ++dq_iter;
+ // Second is surface quad.
+ ASSERT_EQ(cc::DrawQuad::SURFACE_CONTENT, dq_iter->material);
+ const cc::SurfaceDrawQuad* round_trip_surface_quad =
+ cc::SurfaceDrawQuad::MaterialCast(&*dq_iter);
+ EXPECT_EQ(arbitrary_id, round_trip_surface_quad->surface_id);
+
+ ++dq_iter;
+ // Third is texture quad.
+ ASSERT_EQ(cc::DrawQuad::TEXTURE_CONTENT, dq_iter->material);
+ const cc::TextureDrawQuad* round_trip_texture_quad =
+ cc::TextureDrawQuad::MaterialCast(&*dq_iter);
+ EXPECT_EQ(resource_id, round_trip_texture_quad->resource_id);
+ EXPECT_EQ(premultiplied_alpha, round_trip_texture_quad->premultiplied_alpha);
+ EXPECT_EQ(uv_top_left, round_trip_texture_quad->uv_top_left);
+ EXPECT_EQ(uv_bottom_right, round_trip_texture_quad->uv_bottom_right);
+ EXPECT_EQ(background_color, round_trip_texture_quad->background_color);
+ for (size_t i = 0; i < 4; ++i) {
+ EXPECT_EQ(vertex_opacity[i], round_trip_texture_quad->vertex_opacity[i])
+ << i;
+ }
+ EXPECT_EQ(flipped, round_trip_texture_quad->flipped);
+}
+
+TEST(SurfaceLibTest, Mailbox) {
+ gpu::Mailbox mailbox;
+ mailbox.Generate();
+
+ MailboxPtr mojo_mailbox = Mailbox::From(mailbox);
+ EXPECT_EQ(0, memcmp(mailbox.name, &mojo_mailbox->name.storage()[0], 64));
+
+ gpu::Mailbox round_trip_mailbox = mojo_mailbox.To<gpu::Mailbox>();
+ EXPECT_EQ(mailbox, round_trip_mailbox);
+}
+
+TEST(SurfaceLibTest, MailboxEmptyName) {
+ MailboxPtr mojo_mailbox = Mailbox::New();
+
+ gpu::Mailbox converted_mailbox = mojo_mailbox.To<gpu::Mailbox>();
+ EXPECT_TRUE(converted_mailbox.IsZero());
+}
+
+TEST(SurfaceLibTest, MailboxHolder) {
+ gpu::Mailbox mailbox;
+ mailbox.Generate();
+ uint32_t texture_target = GL_TEXTURE_2D;
+ uint32_t sync_point = 7u;
+ gpu::MailboxHolder holder(mailbox, texture_target, sync_point);
+
+ MailboxHolderPtr mojo_holder = MailboxHolder::From(holder);
+ EXPECT_EQ(texture_target, mojo_holder->texture_target);
+ EXPECT_EQ(sync_point, mojo_holder->sync_point);
+
+ gpu::MailboxHolder round_trip_holder = mojo_holder.To<gpu::MailboxHolder>();
+ EXPECT_EQ(mailbox, round_trip_holder.mailbox);
+ EXPECT_EQ(texture_target, round_trip_holder.texture_target);
+ EXPECT_EQ(sync_point, round_trip_holder.sync_point);
+}
+
+TEST(SurfaceLibTest, TransferableResource) {
+ uint32_t id = 7u;
+ cc::ResourceFormat format = cc::BGRA_8888;
+ uint32_t filter = 123u;
+ gfx::Size size(17, 18);
+ gpu::MailboxHolder mailbox_holder;
+ bool is_repeated = false;
+ ;
+ bool is_software = false;
+ cc::TransferableResource resource;
+ resource.id = id;
+ resource.format = format;
+ resource.filter = filter;
+ resource.size = size;
+ resource.mailbox_holder = mailbox_holder;
+ resource.is_repeated = is_repeated;
+ resource.is_software = is_software;
+
+ TransferableResourcePtr mojo_resource = TransferableResource::From(resource);
+ EXPECT_EQ(id, mojo_resource->id);
+ EXPECT_EQ(static_cast<ResourceFormat>(format),
+ mojo_resource->format);
+ EXPECT_EQ(filter, mojo_resource->filter);
+ EXPECT_EQ(Size::From(size), mojo_resource->size);
+ EXPECT_EQ(is_repeated, mojo_resource->is_repeated);
+ EXPECT_EQ(is_software, mojo_resource->is_software);
+
+ cc::TransferableResource round_trip_resource =
+ mojo_resource.To<cc::TransferableResource>();
+ EXPECT_EQ(id, round_trip_resource.id);
+ EXPECT_EQ(format, round_trip_resource.format);
+ EXPECT_EQ(filter, round_trip_resource.filter);
+ EXPECT_EQ(size, round_trip_resource.size);
+ EXPECT_EQ(mailbox_holder.mailbox, round_trip_resource.mailbox_holder.mailbox);
+ EXPECT_EQ(mailbox_holder.texture_target,
+ round_trip_resource.mailbox_holder.texture_target);
+ EXPECT_EQ(mailbox_holder.sync_point,
+ round_trip_resource.mailbox_holder.sync_point);
+ EXPECT_EQ(is_repeated, round_trip_resource.is_repeated);
+ EXPECT_EQ(is_software, round_trip_resource.is_software);
+}
+
+TEST(SurfaceLibTest, ReturnedResource) {
+ uint32_t id = 5u;
+ uint32_t sync_point = 24u;
+ int count = 2;
+ bool lost = false;
+ cc::ReturnedResource resource;
+ resource.id = id;
+ resource.sync_point = sync_point;
+ resource.count = count;
+ resource.lost = lost;
+
+ ReturnedResourcePtr mojo_resource = ReturnedResource::From(resource);
+ EXPECT_EQ(id, mojo_resource->id);
+ EXPECT_EQ(sync_point, mojo_resource->sync_point);
+ EXPECT_EQ(count, mojo_resource->count);
+ EXPECT_EQ(lost, mojo_resource->lost);
+
+ cc::ReturnedResource round_trip_resource =
+ mojo_resource.To<cc::ReturnedResource>();
+ EXPECT_EQ(id, round_trip_resource.id);
+ EXPECT_EQ(sync_point, round_trip_resource.sync_point);
+ EXPECT_EQ(count, round_trip_resource.count);
+ EXPECT_EQ(lost, round_trip_resource.lost);
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/BUILD.gn b/mojo/services/public/cpp/view_manager/BUILD.gn
new file mode 100644
index 0000000..cab9b73
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_services.gypi:mojo_view_manager_lib
+source_set("view_manager") {
+ sources = [
+ "lib/bitmap_uploader.cc",
+ "lib/bitmap_uploader.h",
+ "lib/view.cc",
+ "lib/view_manager_client_factory.cc",
+ "lib/view_manager_client_impl.cc",
+ "lib/view_manager_client_impl.h",
+ "lib/view_manager_context.cc",
+ "lib/view_observer.cc",
+ "lib/view_private.cc",
+ "lib/view_private.h",
+ "view.h",
+ "view_manager.h",
+ "view_manager_client_factory.h",
+ "view_manager_context.h",
+ "view_manager_delegate.h",
+ "view_observer.h",
+ "window_manager_delegate.h",
+ ]
+
+ public_deps = [
+ ":common",
+ "//skia",
+ ]
+ deps = [
+ "//base",
+ "//cc/surfaces",
+ "//gpu",
+ "//mojo/application",
+ "//mojo/public/c/gles2",
+ "//mojo/public/cpp/bindings:bindings",
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/surfaces",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/gpu",
+ "//mojo/services/public/interfaces/input_events:input_events",
+ "//mojo/services/public/interfaces/native_viewport",
+ "//mojo/services/public/interfaces/surfaces:surface_id",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/services/public/interfaces/window_manager",
+ "//skia",
+ "//ui/events",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+}
+
+source_set("common") {
+ sources = [
+ "ids.h",
+ "types.h",
+ ]
+ public_deps = [
+ "//base",
+ ]
+}
diff --git a/mojo/services/public/cpp/view_manager/DEPS b/mojo/services/public/cpp/view_manager/DEPS
new file mode 100644
index 0000000..4460c19
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+third_party/skia/include/core",
+ "+ui/gfx/geometry"
+]
diff --git a/mojo/services/public/cpp/view_manager/lib/BUILD.gn b/mojo/services/public/cpp/view_manager/lib/BUILD.gn
new file mode 100644
index 0000000..df44a34
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_view_manager_run_unittests
+source_set("run_unittests") {
+ testonly = true
+ sources = [
+ "view_manager_test_suite.cc",
+ "view_manager_test_suite.h",
+ "view_manager_unittests.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ ]
+
+ if (use_x11) {
+ deps += ["//ui/gfx/x"]
+ }
+
+ if (is_component_build) {
+ deps += ["//ui/gl"]
+ }
+
+}
diff --git a/mojo/services/public/cpp/view_manager/lib/DEPS b/mojo/services/public/cpp/view_manager/lib/DEPS
new file mode 100644
index 0000000..0af5fad
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+ "+cc/surfaces/surface_id.h",
+ "+cc/surfaces/surface_id_allocator.h",
+ "+gpu/GLES2",
+ "+gpu/command_buffer/common/mailbox.h",
+ "+mojo/geometry",
+ "+third_party/khronos/GLES2",
+ "+third_party/skia",
+ "+ui/gfx",
+]
+
+specific_include_rules = {
+ "view_manager_test_suite.cc": [
+ "+mojo/services/native_viewport/native_viewport.h",
+ "+ui/gl",
+ ],
+}
diff --git a/mojo/services/public/cpp/view_manager/lib/bitmap_uploader.cc b/mojo/services/public/cpp/view_manager/lib/bitmap_uploader.cc
new file mode 100644
index 0000000..80447ed
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/bitmap_uploader.cc
@@ -0,0 +1,232 @@
+// Copyright 2014 The Chromium 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/bitmap_uploader.h"
+
+#ifndef GL_GLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#endif // GL_GLEXT_PROTOTYPES
+
+#include "base/bind.h"
+#include "cc/surfaces/surface_id.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "gpu/GLES2/gl2chromium.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "mojo/public/c/gles2/gles2.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_utils.h"
+#include "mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace mojo {
+
+namespace {
+void LostContext(void*) {
+ DCHECK(false);
+}
+
+uint32_t TextureFormat() {
+ return SK_B32_SHIFT ? GL_RGBA : GL_BGRA_EXT;
+}
+}
+
+BitmapUploader::BitmapUploader(ViewManagerClientImpl* client,
+ Id view_id,
+ SurfacesServicePtr surfaces_service,
+ GpuPtr gpu_service)
+ : client_(client),
+ view_id_(view_id),
+ surfaces_service_(surfaces_service.Pass()),
+ gpu_service_(gpu_service.Pass()),
+ color_(SK_ColorTRANSPARENT),
+ next_resource_id_(1u),
+ weak_factory_(this) {
+ surfaces_service_->CreateSurfaceConnection(base::Bind(
+ &BitmapUploader::OnSurfaceConnectionCreated, weak_factory_.GetWeakPtr()));
+ CommandBufferPtr gles2_client;
+ gpu_service_->CreateOffscreenGLES2Context(Get(&gles2_client));
+ gles2_context_ =
+ MojoGLES2CreateContext(gles2_client.PassMessagePipe().release().value(),
+ &LostContext,
+ NULL,
+ Environment::GetDefaultAsyncWaiter());
+ MojoGLES2MakeCurrent(gles2_context_);
+}
+
+BitmapUploader::~BitmapUploader() {
+ MojoGLES2DestroyContext(gles2_context_);
+}
+
+void BitmapUploader::SetSize(const gfx::Size& size) {
+ if (size_ == size)
+ return;
+ size_ = size;
+}
+
+void BitmapUploader::SetColor(SkColor color) {
+ if (color_ == color)
+ return;
+ color_ = color;
+ if (surface_)
+ Upload();
+}
+
+void BitmapUploader::SetBitmap(SkBitmap bitmap) {
+ bitmap_ = bitmap;
+ if (surface_)
+ Upload();
+}
+
+void BitmapUploader::Upload() {
+ if (size_.width() == 0 || size_.height() == 0) {
+ client_->SetSurfaceId(view_id_, SurfaceId::New());
+ return;
+ }
+ if (!surface_) { // Can't upload yet, store for later.
+ done_callback_ = done_callback_;
+ return;
+ }
+ if (id_.is_null() || size_ != surface_size_) {
+ if (!id_.is_null())
+ surface_->DestroySurface(SurfaceId::From(id_));
+ id_ = id_allocator_->GenerateId();
+ surface_->CreateSurface(SurfaceId::From(id_), Size::From(size_));
+ client_->SetSurfaceId(view_id_, SurfaceId::From(id_));
+ surface_size_ = size_;
+ }
+
+ gfx::Rect bounds(size_);
+ PassPtr pass = CreateDefaultPass(1, bounds);
+ FramePtr frame = Frame::New();
+ frame->resources.resize(0u);
+
+ pass->quads.resize(0u);
+ pass->shared_quad_states.push_back(CreateDefaultSQS(size_));
+
+ MojoGLES2MakeCurrent(gles2_context_);
+ if (!bitmap_.isNull()) {
+ gfx::Size bitmap_size(bitmap_.width(), bitmap_.height());
+ GLuint texture_id = BindTextureForSize(bitmap_size);
+ bitmap_.lockPixels();
+ glTexSubImage2D(GL_TEXTURE_2D,
+ 0,
+ 0,
+ 0,
+ bitmap_size.width(),
+ bitmap_size.height(),
+ TextureFormat(),
+ GL_UNSIGNED_BYTE,
+ bitmap_.getPixels());
+ bitmap_.unlockPixels();
+
+ gpu::Mailbox mailbox = gpu::Mailbox::Generate();
+ glProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ GLuint sync_point = glInsertSyncPointCHROMIUM();
+
+ TransferableResourcePtr resource = TransferableResource::New();
+ resource->id = next_resource_id_++;
+ resource_to_texture_id_map_[resource->id] = texture_id;
+ resource->format = mojo::RESOURCE_FORMAT_RGBA_8888;
+ resource->filter = GL_LINEAR;
+ resource->size = Size::From(bitmap_size);
+ MailboxHolderPtr mailbox_holder = MailboxHolder::New();
+ mailbox_holder->mailbox = Mailbox::From(mailbox);
+ mailbox_holder->texture_target = GL_TEXTURE_2D;
+ mailbox_holder->sync_point = sync_point;
+ resource->mailbox_holder = mailbox_holder.Pass();
+ resource->is_repeated = false;
+ resource->is_software = false;
+
+ QuadPtr quad = Quad::New();
+ quad->material = MATERIAL_TEXTURE_CONTENT;
+ quad->rect = Rect::From(bounds);
+ quad->opaque_rect = Rect::From(bounds);
+ quad->visible_rect = Rect::From(bounds);
+ quad->needs_blending = true;
+ quad->shared_quad_state_index = 0u;
+
+ TextureQuadStatePtr texture_state = TextureQuadState::New();
+ texture_state->resource_id = resource->id;
+ texture_state->premultiplied_alpha = true;
+ texture_state->uv_top_left = PointF::From(gfx::PointF(0.f, 0.f));
+ texture_state->uv_bottom_right = PointF::From(gfx::PointF(1.f, 1.f));
+ texture_state->background_color = Color::From(SkColor(SK_ColorTRANSPARENT));
+ for (int i = 0; i < 4; ++i)
+ texture_state->vertex_opacity.push_back(1.f);
+ texture_state->flipped = false;
+
+ frame->resources.push_back(resource.Pass());
+ quad->texture_quad_state = texture_state.Pass();
+ pass->quads.push_back(quad.Pass());
+ }
+
+ if (color_ != SK_ColorTRANSPARENT) {
+ QuadPtr quad = Quad::New();
+ quad->material = MATERIAL_SOLID_COLOR;
+ quad->rect = Rect::From(bounds);
+ quad->opaque_rect = Rect::From(gfx::Rect());
+ quad->visible_rect = Rect::From(bounds);
+ quad->needs_blending = true;
+ quad->shared_quad_state_index = 0u;
+
+ SolidColorQuadStatePtr color_state = SolidColorQuadState::New();
+ color_state->color = Color::From(color_);
+ color_state->force_anti_aliasing_off = false;
+
+ quad->solid_color_quad_state = color_state.Pass();
+ pass->quads.push_back(quad.Pass());
+ }
+
+ frame->passes.push_back(pass.Pass());
+
+ surface_->SubmitFrame(SurfaceId::From(id_), frame.Pass());
+}
+
+void BitmapUploader::ReturnResources(Array<ReturnedResourcePtr> resources) {
+ if (!resources.size())
+ return;
+ MojoGLES2MakeCurrent(gles2_context_);
+ // TODO(jamesr): Recycle.
+ for (size_t i = 0; i < resources.size(); ++i) {
+ ReturnedResourcePtr resource = resources[i].Pass();
+ DCHECK_EQ(1, resource->count);
+ glWaitSyncPointCHROMIUM(resource->sync_point);
+ uint32_t texture_id = resource_to_texture_id_map_[resource->id];
+ DCHECK_NE(0u, texture_id);
+ resource_to_texture_id_map_.erase(resource->id);
+ glDeleteTextures(1, &texture_id);
+ }
+}
+
+void BitmapUploader::OnSurfaceConnectionCreated(SurfacePtr surface,
+ uint32_t id_namespace) {
+ surface_ = surface.Pass();
+ surface_.set_client(this);
+ id_allocator_.reset(new cc::SurfaceIdAllocator(id_namespace));
+ if (color_ != SK_ColorTRANSPARENT || !bitmap_.isNull())
+ Upload();
+}
+
+uint32_t BitmapUploader::BindTextureForSize(const gfx::Size size) {
+ // TODO(jamesr): Recycle textures.
+ GLuint texture = 0u;
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ TextureFormat(),
+ size.width(),
+ size.height(),
+ 0,
+ TextureFormat(),
+ GL_UNSIGNED_BYTE,
+ 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ return texture;
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/bitmap_uploader.h b/mojo/services/public/cpp/view_manager/lib/bitmap_uploader.h
new file mode 100644
index 0000000..f049409
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/bitmap_uploader.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_PUBLIC_CPP_VIEW_MANAGER_LIB_BITMAP_UPLOADER_H_
+#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_BITMAP_UPLOADER_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/surfaces/surface_id.h"
+#include "mojo/public/c/gles2/gles2.h"
+#include "mojo/services/public/cpp/view_manager/types.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace cc {
+class SurfaceIdAllocator;
+}
+
+namespace mojo {
+class ViewManagerClientImpl;
+
+class BitmapUploader : public SurfaceClient {
+ public:
+ BitmapUploader(ViewManagerClientImpl* client,
+ Id view_id,
+ SurfacesServicePtr surfaces_service,
+ GpuPtr gpu_service);
+ virtual ~BitmapUploader();
+
+ void SetSize(const gfx::Size& size);
+ void SetColor(SkColor color);
+ void SetBitmap(SkBitmap bitmap);
+ void SetDoneCallback(const base::Callback<void(SurfaceIdPtr)>& done_callback);
+
+ private:
+ void Upload();
+ void OnSurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace);
+ uint32_t BindTextureForSize(const gfx::Size size);
+
+ // SurfaceClient implementation.
+ virtual void ReturnResources(Array<ReturnedResourcePtr> resources) OVERRIDE;
+
+ ViewManagerClientImpl* client_;
+ Id view_id_;
+ SurfacesServicePtr surfaces_service_;
+ GpuPtr gpu_service_;
+ MojoGLES2Context gles2_context_;
+
+ gfx::Size size_;
+ SkColor color_;
+ SkBitmap bitmap_;
+ base::Callback<void(SurfaceIdPtr)> done_callback_;
+ SurfacePtr surface_;
+ cc::SurfaceId id_;
+ scoped_ptr<cc::SurfaceIdAllocator> id_allocator_;
+ gfx::Size surface_size_;
+ uint32_t next_resource_id_;
+ base::hash_map<uint32_t, uint32_t> resource_to_texture_id_map_;
+
+ base::WeakPtrFactory<BitmapUploader> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BitmapUploader);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_BITMAP_UPLOADER_H_
diff --git a/mojo/services/public/cpp/view_manager/lib/view.cc b/mojo/services/public/cpp/view_manager/lib/view.cc
new file mode 100644
index 0000000..fe45ffc
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view.cc
@@ -0,0 +1,456 @@
+// Copyright 2014 The Chromium 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/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/public/cpp/view_manager/lib/bitmap_uploader.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/view_observer.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "ui/gfx/canvas.h"
+
+namespace mojo {
+
+namespace {
+
+void NotifyViewTreeChangeAtReceiver(
+ View* receiver,
+ const ViewObserver::TreeChangeParams& params,
+ bool change_applied) {
+ ViewObserver::TreeChangeParams local_params = params;
+ local_params.receiver = receiver;
+ if (change_applied) {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(receiver).observers(),
+ OnTreeChanged(local_params));
+ } else {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(receiver).observers(),
+ OnTreeChanging(local_params));
+ }
+}
+
+void NotifyViewTreeChangeUp(
+ View* start_at,
+ const ViewObserver::TreeChangeParams& params,
+ bool change_applied) {
+ for (View* current = start_at; current; current = current->parent())
+ NotifyViewTreeChangeAtReceiver(current, params, change_applied);
+}
+
+void NotifyViewTreeChangeDown(
+ View* start_at,
+ const ViewObserver::TreeChangeParams& params,
+ bool change_applied) {
+ NotifyViewTreeChangeAtReceiver(start_at, params, change_applied);
+ View::Children::const_iterator it = start_at->children().begin();
+ for (; it != start_at->children().end(); ++it)
+ NotifyViewTreeChangeDown(*it, params, change_applied);
+}
+
+void NotifyViewTreeChange(
+ const ViewObserver::TreeChangeParams& params,
+ bool change_applied) {
+ NotifyViewTreeChangeDown(params.target, params, change_applied);
+ if (params.old_parent)
+ NotifyViewTreeChangeUp(params.old_parent, params, change_applied);
+ if (params.new_parent)
+ NotifyViewTreeChangeUp(params.new_parent, params, change_applied);
+}
+
+class ScopedTreeNotifier {
+ public:
+ ScopedTreeNotifier(View* target, View* old_parent, View* new_parent) {
+ params_.target = target;
+ params_.old_parent = old_parent;
+ params_.new_parent = new_parent;
+ NotifyViewTreeChange(params_, false);
+ }
+ ~ScopedTreeNotifier() {
+ NotifyViewTreeChange(params_, true);
+ }
+
+ private:
+ ViewObserver::TreeChangeParams params_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTreeNotifier);
+};
+
+void RemoveChildImpl(View* child, View::Children* children) {
+ View::Children::iterator it =
+ std::find(children->begin(), children->end(), child);
+ if (it != children->end()) {
+ children->erase(it);
+ ViewPrivate(child).ClearParent();
+ }
+}
+
+class ScopedOrderChangedNotifier {
+ public:
+ ScopedOrderChangedNotifier(View* view,
+ View* relative_view,
+ OrderDirection direction)
+ : view_(view),
+ relative_view_(relative_view),
+ direction_(direction) {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(view_).observers(),
+ OnViewReordering(view_, relative_view_, direction_));
+ }
+ ~ScopedOrderChangedNotifier() {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(view_).observers(),
+ OnViewReordered(view_, relative_view_, direction_));
+ }
+
+ private:
+ View* view_;
+ View* relative_view_;
+ OrderDirection direction_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedOrderChangedNotifier);
+};
+
+// Returns true if the order actually changed.
+bool ReorderImpl(View::Children* children,
+ View* view,
+ View* relative,
+ OrderDirection direction) {
+ DCHECK(relative);
+ DCHECK_NE(view, relative);
+ DCHECK_EQ(view->parent(), relative->parent());
+
+ const size_t child_i =
+ std::find(children->begin(), children->end(), view) - children->begin();
+ const size_t target_i =
+ std::find(children->begin(), children->end(), relative) -
+ children->begin();
+ if ((direction == ORDER_DIRECTION_ABOVE && child_i == target_i + 1) ||
+ (direction == ORDER_DIRECTION_BELOW && child_i + 1 == target_i)) {
+ return false;
+ }
+
+ ScopedOrderChangedNotifier notifier(view, relative, direction);
+
+ const size_t dest_i = direction == ORDER_DIRECTION_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, view);
+
+ return true;
+}
+
+class ScopedSetBoundsNotifier {
+ public:
+ ScopedSetBoundsNotifier(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds)
+ : view_(view),
+ old_bounds_(old_bounds),
+ new_bounds_(new_bounds) {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(view_).observers(),
+ OnViewBoundsChanging(view_, old_bounds_, new_bounds_));
+ }
+ ~ScopedSetBoundsNotifier() {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(view_).observers(),
+ OnViewBoundsChanged(view_, old_bounds_, new_bounds_));
+ }
+
+ private:
+ View* view_;
+ const gfx::Rect old_bounds_;
+ const gfx::Rect new_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSetBoundsNotifier);
+};
+
+// Some operations are only permitted in the connection that created the view.
+bool OwnsView(ViewManager* manager, View* view) {
+ return !manager ||
+ static_cast<ViewManagerClientImpl*>(manager)->OwnsView(view->id());
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// View, public:
+
+// static
+View* View::Create(ViewManager* view_manager) {
+ View* view = new View(view_manager);
+ static_cast<ViewManagerClientImpl*>(view_manager)->AddView(view);
+ return view;
+}
+
+void View::Destroy() {
+ if (!OwnsView(manager_, this))
+ return;
+
+ if (manager_)
+ static_cast<ViewManagerClientImpl*>(manager_)->DestroyView(id_);
+ while (!children_.empty()) {
+ View* child = children_.front();
+ if (!OwnsView(manager_, child)) {
+ ViewPrivate(child).ClearParent();
+ children_.erase(children_.begin());
+ } else {
+ child->Destroy();
+ DCHECK(std::find(children_.begin(), children_.end(), child) ==
+ children_.end());
+ }
+ }
+ LocalDestroy();
+}
+
+void View::SetBounds(const gfx::Rect& bounds) {
+ if (!OwnsView(manager_, this))
+ return;
+
+ if (manager_)
+ static_cast<ViewManagerClientImpl*>(manager_)->SetBounds(id_, bounds);
+ LocalSetBounds(bounds_, bounds);
+}
+
+void View::SetVisible(bool value) {
+ if (visible_ == value)
+ return;
+
+ if (manager_)
+ static_cast<ViewManagerClientImpl*>(manager_)->SetVisible(id_, value);
+ FOR_EACH_OBSERVER(ViewObserver, observers_, OnViewVisibilityChanging(this));
+ visible_ = value;
+ FOR_EACH_OBSERVER(ViewObserver, observers_, OnViewVisibilityChanged(this));
+}
+
+bool View::IsDrawn() const {
+ if (!visible_)
+ return false;
+ return parent_ ? parent_->IsDrawn() : drawn_;
+}
+
+void View::AddObserver(ViewObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void View::RemoveObserver(ViewObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void View::AddChild(View* child) {
+ // TODO(beng): not necessarily valid to all connections, but possibly to the
+ // embeddee in an embedder-embeddee relationship.
+ if (manager_)
+ CHECK_EQ(ViewPrivate(child).view_manager(), manager_);
+ LocalAddChild(child);
+ if (manager_)
+ static_cast<ViewManagerClientImpl*>(manager_)->AddChild(child->id(), id_);
+}
+
+void View::RemoveChild(View* child) {
+ // TODO(beng): not necessarily valid to all connections, but possibly to the
+ // embeddee in an embedder-embeddee relationship.
+ if (manager_)
+ CHECK_EQ(ViewPrivate(child).view_manager(), manager_);
+ LocalRemoveChild(child);
+ if (manager_) {
+ static_cast<ViewManagerClientImpl*>(manager_)->RemoveChild(child->id(),
+ id_);
+ }
+}
+
+void View::MoveToFront() {
+ if (!parent_ || parent_->children_.back() == this)
+ return;
+ Reorder(parent_->children_.back(), ORDER_DIRECTION_ABOVE);
+}
+
+void View::MoveToBack() {
+ if (!parent_ || parent_->children_.front() == this)
+ return;
+ Reorder(parent_->children_.front(), ORDER_DIRECTION_BELOW);
+}
+
+void View::Reorder(View* relative, OrderDirection direction) {
+ if (!LocalReorder(relative, direction))
+ return;
+ if (manager_) {
+ static_cast<ViewManagerClientImpl*>(manager_)->Reorder(id_,
+ relative->id(),
+ direction);
+ }
+}
+
+bool View::Contains(View* child) const {
+ if (manager_)
+ CHECK_EQ(ViewPrivate(child).view_manager(), manager_);
+ for (View* p = child->parent(); p; p = p->parent()) {
+ if (p == this)
+ return true;
+ }
+ return false;
+}
+
+View* View::GetChildById(Id id) {
+ if (id == id_)
+ return this;
+ // TODO(beng): this could be improved depending on how we decide to own views.
+ Children::const_iterator it = children_.begin();
+ for (; it != children_.end(); ++it) {
+ View* view = (*it)->GetChildById(id);
+ if (view)
+ return view;
+ }
+ return NULL;
+}
+
+void View::SetSurfaceId(SurfaceIdPtr id) {
+ if (manager_) {
+ static_cast<ViewManagerClientImpl*>(manager_)->SetSurfaceId(id_, id.Pass());
+ }
+}
+
+void View::SetContents(const SkBitmap& contents) {
+ if (manager_) {
+ if (!bitmap_uploader_)
+ CreateBitmapUploader();
+ bitmap_uploader_->SetSize(bounds_.size());
+ bitmap_uploader_->SetBitmap(contents);
+ }
+}
+
+void View::SetColor(SkColor color) {
+ if (manager_) {
+ if (!bitmap_uploader_)
+ CreateBitmapUploader();
+ bitmap_uploader_->SetSize(bounds_.size());
+ bitmap_uploader_->SetColor(color);
+ }
+}
+
+void View::SetFocus() {
+ if (manager_)
+ static_cast<ViewManagerClientImpl*>(manager_)->SetFocus(id_);
+}
+
+void View::Embed(const String& url) {
+ static_cast<ViewManagerClientImpl*>(manager_)->Embed(url, id_);
+}
+
+scoped_ptr<ServiceProvider>
+ View::Embed(const String& url,
+ scoped_ptr<ServiceProviderImpl> exported_services) {
+ scoped_ptr<ServiceProvider> imported_services;
+ // BindToProxy() takes ownership of |exported_services|.
+ ServiceProviderImpl* registry = exported_services.release();
+ ServiceProviderPtr sp;
+ if (registry) {
+ BindToProxy(registry, &sp);
+ imported_services.reset(registry->CreateRemoteServiceProvider());
+ }
+ static_cast<ViewManagerClientImpl*>(manager_)->Embed(url, id_, sp.Pass());
+ return imported_services.Pass();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// View, protected:
+
+View::View()
+ : manager_(NULL),
+ id_(static_cast<Id>(-1)),
+ parent_(NULL),
+ visible_(true),
+ drawn_(false) {
+}
+
+View::~View() {
+ FOR_EACH_OBSERVER(ViewObserver, observers_, OnViewDestroying(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_)->RemoveView(id_);
+ FOR_EACH_OBSERVER(ViewObserver, observers_, OnViewDestroyed(this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// View, private:
+
+View::View(ViewManager* manager)
+ : manager_(manager),
+ id_(static_cast<ViewManagerClientImpl*>(manager_)->CreateView()),
+ parent_(NULL),
+ visible_(true),
+ drawn_(false) {
+}
+
+void View::LocalDestroy() {
+ delete this;
+}
+
+void View::LocalAddChild(View* child) {
+ ScopedTreeNotifier notifier(child, child->parent(), this);
+ if (child->parent())
+ RemoveChildImpl(child, &child->parent_->children_);
+ children_.push_back(child);
+ child->parent_ = this;
+}
+
+void View::LocalRemoveChild(View* child) {
+ DCHECK_EQ(this, child->parent());
+ ScopedTreeNotifier notifier(child, this, NULL);
+ RemoveChildImpl(child, &children_);
+}
+
+bool View::LocalReorder(View* relative, OrderDirection direction) {
+ return ReorderImpl(&parent_->children_, this, relative, direction);
+}
+
+void View::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;
+}
+
+void View::LocalSetDrawn(bool value) {
+ if (drawn_ == value)
+ return;
+
+ // As IsDrawn() is derived from |visible_| and |drawn_|, only send drawn
+ // notification is the value of IsDrawn() is really changing.
+ if (IsDrawn() == value) {
+ drawn_ = value;
+ return;
+ }
+ FOR_EACH_OBSERVER(ViewObserver, observers_, OnViewDrawnChanging(this));
+ drawn_ = value;
+ FOR_EACH_OBSERVER(ViewObserver, observers_, OnViewDrawnChanged(this));
+}
+
+void View::CreateBitmapUploader() {
+ ViewManagerClientImpl* vmci = static_cast<ViewManagerClientImpl*>(manager_);
+ SurfacesServicePtr surfaces_service;
+ InterfacePtr<ServiceProvider> surfaces_service_provider;
+ vmci->shell()->ConnectToApplication("mojo:mojo_surfaces_service",
+ Get(&surfaces_service_provider));
+ ConnectToService(surfaces_service_provider.get(), &surfaces_service);
+ GpuPtr gpu_service;
+ InterfacePtr<ServiceProvider> gpu_service_provider;
+ vmci->shell()->ConnectToApplication("mojo:mojo_native_viewport_service",
+ Get(&gpu_service_provider));
+ ConnectToService(gpu_service_provider.get(), &gpu_service);
+ bitmap_uploader_.reset(new BitmapUploader(
+ vmci, id_, surfaces_service.Pass(), gpu_service.Pass()));
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_client_factory.cc b/mojo/services/public/cpp/view_manager/lib/view_manager_client_factory.cc
new file mode 100644
index 0000000..8b8ada1
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_manager_client_factory.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/public/cpp/view_manager/view_manager_client_factory.h"
+
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h"
+
+namespace mojo {
+
+ViewManagerClientFactory::ViewManagerClientFactory(
+ Shell* shell,
+ ViewManagerDelegate* delegate)
+ : shell_(shell), delegate_(delegate) {
+}
+
+ViewManagerClientFactory::~ViewManagerClientFactory() {
+}
+
+// InterfaceFactory<ViewManagerClient> implementation.
+void ViewManagerClientFactory::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerClient> request) {
+ BindToRequest(new ViewManagerClientImpl(delegate_, shell_), &request);
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.cc b/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.cc
new file mode 100644
index 0000000..19955b2
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.cc
@@ -0,0 +1,428 @@
+// Copyright 2014 The Chromium 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/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/public/cpp/view_manager/lib/view_private.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 "mojo/services/public/cpp/view_manager/window_manager_delegate.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/png_codec.h"
+
+namespace mojo {
+
+Id MakeTransportId(ConnectionSpecificId connection_id,
+ ConnectionSpecificId local_id) {
+ return (connection_id << 16) | local_id;
+}
+
+// Helper called to construct a local view object from transport data.
+View* AddViewToViewManager(ViewManagerClientImpl* client,
+ View* parent,
+ const ViewDataPtr& view_data) {
+ // 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 view.
+ View* view = ViewPrivate::LocalCreate();
+ ViewPrivate private_view(view);
+ private_view.set_view_manager(client);
+ private_view.set_id(view_data->view_id);
+ private_view.set_visible(view_data->visible);
+ private_view.set_drawn(view_data->drawn);
+ client->AddView(view);
+ private_view.LocalSetBounds(gfx::Rect(), view_data->bounds.To<gfx::Rect>());
+ if (parent)
+ ViewPrivate(parent).LocalAddChild(view);
+ return view;
+}
+
+View* BuildViewTree(ViewManagerClientImpl* client,
+ const Array<ViewDataPtr>& views,
+ View* initial_parent) {
+ std::vector<View*> parents;
+ View* root = NULL;
+ View* last_view = NULL;
+ if (initial_parent)
+ parents.push_back(initial_parent);
+ for (size_t i = 0; i < views.size(); ++i) {
+ if (last_view && views[i]->parent_id == last_view->id()) {
+ parents.push_back(last_view);
+ } else if (!parents.empty()) {
+ while (parents.back()->id() != views[i]->parent_id)
+ parents.pop_back();
+ }
+ View* view = AddViewToViewManager(
+ client, !parents.empty() ? parents.back() : NULL, views[i]);
+ if (!last_view)
+ root = view;
+ last_view = view;
+ }
+ return root;
+}
+
+// Responsible for removing a root from the ViewManager when that view is
+// destroyed.
+class RootObserver : public ViewObserver {
+ public:
+ explicit RootObserver(View* root) : root_(root) {}
+ virtual ~RootObserver() {}
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewDestroyed(View* view) OVERRIDE {
+ DCHECK_EQ(view, root_);
+ static_cast<ViewManagerClientImpl*>(
+ ViewPrivate(root_).view_manager())->RemoveRoot(root_);
+ view->RemoveObserver(this);
+ delete this;
+ }
+
+ View* root_;
+
+ DISALLOW_COPY_AND_ASSIGN(RootObserver);
+};
+
+ViewManagerClientImpl::ViewManagerClientImpl(ViewManagerDelegate* delegate,
+ Shell* shell)
+ : connected_(false),
+ connection_id_(0),
+ next_id_(1),
+ delegate_(delegate),
+ window_manager_delegate_(NULL),
+ shell_(shell) {
+ // TODO(beng): Come up with a better way of establishing a configuration for
+ // what the active window manager is.
+ std::string window_manager_url = "mojo:mojo_window_manager";
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch("window-manager")) {
+ window_manager_url =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ "window-manager");
+ }
+ InterfacePtr<ServiceProvider> sp;
+ shell->ConnectToApplication(window_manager_url, Get(&sp));
+ ConnectToService(sp.get(), &window_manager_);
+ window_manager_.set_client(this);
+}
+
+ViewManagerClientImpl::~ViewManagerClientImpl() {
+ std::vector<View*> non_owned;
+ while (!views_.empty()) {
+ IdToViewMap::iterator it = views_.begin();
+ if (OwnsView(it->second->id())) {
+ it->second->Destroy();
+ } else {
+ non_owned.push_back(it->second);
+ views_.erase(it);
+ }
+ }
+ // Delete the non-owned views last. In the typical case these are roots. The
+ // exception is the window manager, which may know aboutother random views
+ // that it doesn't own.
+ // NOTE: we manually delete as we're a friend.
+ for (size_t i = 0; i < non_owned.size(); ++i)
+ delete non_owned[i];
+
+ delegate_->OnViewManagerDisconnected(this);
+}
+
+Id ViewManagerClientImpl::CreateView() {
+ DCHECK(connected_);
+ const Id view_id = MakeTransportId(connection_id_, ++next_id_);
+ service_->CreateView(view_id, ActionCompletedCallbackWithErrorCode());
+ return view_id;
+}
+
+void ViewManagerClientImpl::DestroyView(Id view_id) {
+ DCHECK(connected_);
+ service_->DeleteView(view_id, ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::AddChild(Id child_id, Id parent_id) {
+ DCHECK(connected_);
+ service_->AddView(parent_id, child_id, ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::RemoveChild(Id child_id, Id parent_id) {
+ DCHECK(connected_);
+ service_->RemoveViewFromParent(child_id, ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::Reorder(
+ Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) {
+ DCHECK(connected_);
+ service_->ReorderView(view_id, relative_view_id, direction,
+ ActionCompletedCallback());
+}
+
+bool ViewManagerClientImpl::OwnsView(Id id) const {
+ return HiWord(id) == connection_id_;
+}
+
+void ViewManagerClientImpl::SetBounds(Id view_id, const gfx::Rect& bounds) {
+ DCHECK(connected_);
+ service_->SetViewBounds(view_id, Rect::From(bounds),
+ ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::SetSurfaceId(Id view_id, SurfaceIdPtr surface_id) {
+ DCHECK(connected_);
+ if (surface_id.is_null())
+ return;
+ service_->SetViewSurfaceId(
+ view_id, surface_id.Pass(), ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::SetFocus(Id view_id) {
+ window_manager_->FocusWindow(view_id, ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::SetVisible(Id view_id, bool visible) {
+ DCHECK(connected_);
+ service_->SetViewVisibility(view_id, visible, ActionCompletedCallback());
+}
+
+void ViewManagerClientImpl::Embed(const String& url, Id view_id) {
+ ServiceProviderPtr sp;
+ BindToProxy(new ServiceProviderImpl, &sp);
+ Embed(url, view_id, sp.Pass());
+}
+
+void ViewManagerClientImpl::Embed(
+ const String& url,
+ Id view_id,
+ ServiceProviderPtr service_provider) {
+ DCHECK(connected_);
+ service_->Embed(url, view_id, service_provider.Pass(),
+ ActionCompletedCallback());
+}
+
+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:
+
+void ViewManagerClientImpl::SetWindowManagerDelegate(
+ WindowManagerDelegate* window_manager_delegate) {
+ CHECK(NULL != GetViewById(1));
+ CHECK(!window_manager_delegate_);
+ window_manager_delegate_ = window_manager_delegate;
+}
+
+void ViewManagerClientImpl::DispatchEvent(View* target, EventPtr event) {
+ CHECK(window_manager_delegate_);
+ service_->DispatchOnViewInputEvent(target->id(), event.Pass());
+}
+
+const std::string& ViewManagerClientImpl::GetEmbedderURL() const {
+ return creator_url_;
+}
+
+const std::vector<View*>& ViewManagerClientImpl::GetRoots() const {
+ return roots_;
+}
+
+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::OnEmbed(
+ ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root_data,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ if (!connected_) {
+ connected_ = true;
+ connection_id_ = connection_id;
+ creator_url_ = String::From(creator_url);
+ } else {
+ DCHECK_EQ(connection_id_, connection_id);
+ DCHECK_EQ(creator_url_, creator_url);
+ }
+
+ // A new root must not already exist as a root or be contained by an existing
+ // hierarchy visible to this view manager.
+ View* root = AddViewToViewManager(this, NULL, root_data);
+ roots_.push_back(root);
+ root->AddObserver(new RootObserver(root));
+
+ // BindToRequest() binds the lifetime of |exported_services| to the pipe.
+ ServiceProviderImpl* exported_services = new ServiceProviderImpl;
+ BindToRequest(exported_services, &service_provider);
+ scoped_ptr<ServiceProvider> remote(
+ exported_services->CreateRemoteServiceProvider());
+ delegate_->OnEmbed(this, root, exported_services, remote.Pass());
+}
+
+void ViewManagerClientImpl::OnViewBoundsChanged(Id view_id,
+ RectPtr old_bounds,
+ RectPtr new_bounds) {
+ View* view = GetViewById(view_id);
+ ViewPrivate(view).LocalSetBounds(old_bounds.To<gfx::Rect>(),
+ new_bounds.To<gfx::Rect>());
+}
+
+void ViewManagerClientImpl::OnViewHierarchyChanged(
+ Id view_id,
+ Id new_parent_id,
+ Id old_parent_id,
+ mojo::Array<ViewDataPtr> views) {
+ View* initial_parent = views.size() ?
+ GetViewById(views[0]->parent_id) : NULL;
+
+ BuildViewTree(this, views, initial_parent);
+
+ View* new_parent = GetViewById(new_parent_id);
+ View* old_parent = GetViewById(old_parent_id);
+ View* view = GetViewById(view_id);
+ if (new_parent)
+ ViewPrivate(new_parent).LocalAddChild(view);
+ else
+ ViewPrivate(old_parent).LocalRemoveChild(view);
+}
+
+void ViewManagerClientImpl::OnViewReordered(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) {
+ View* view = GetViewById(view_id);
+ View* relative_view = GetViewById(relative_view_id);
+ if (view && relative_view)
+ ViewPrivate(view).LocalReorder(relative_view, direction);
+}
+
+void ViewManagerClientImpl::OnViewDeleted(Id view_id) {
+ View* view = GetViewById(view_id);
+ if (view)
+ ViewPrivate(view).LocalDestroy();
+}
+
+void ViewManagerClientImpl::OnViewVisibilityChanged(Id view_id, bool visible) {
+ // TODO(sky): there is a race condition here. If this client and another
+ // client change the visibility at the same time the wrong value may be set.
+ // Deal with this some how.
+ View* view = GetViewById(view_id);
+ if (view)
+ view->SetVisible(visible);
+}
+
+void ViewManagerClientImpl::OnViewDrawnStateChanged(Id view_id, bool drawn) {
+ View* view = GetViewById(view_id);
+ if (view)
+ ViewPrivate(view).LocalSetDrawn(drawn);
+}
+
+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::Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ if (window_manager_delegate_)
+ window_manager_delegate_->Embed(url, service_provider.Pass());
+}
+
+void ViewManagerClientImpl::DispatchOnViewInputEvent(EventPtr event) {
+ if (window_manager_delegate_)
+ window_manager_delegate_->DispatchEvent(event.Pass());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ViewManagerClientImpl, WindowManagerClient implementation:
+
+void ViewManagerClientImpl::OnWindowManagerReady() {}
+
+void ViewManagerClientImpl::OnCaptureChanged(Id old_capture_view_id,
+ Id new_capture_view_id) {}
+
+void ViewManagerClientImpl::OnFocusChanged(Id old_focused_view_id,
+ Id new_focused_view_id) {
+ View* focused = GetViewById(new_focused_view_id);
+ View* blurred = GetViewById(old_focused_view_id);
+ if (blurred) {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(blurred).observers(),
+ OnViewFocusChanged(focused, blurred));
+ }
+ if (focused) {
+ FOR_EACH_OBSERVER(ViewObserver,
+ *ViewPrivate(focused).observers(),
+ OnViewFocusChanged(focused, blurred));
+ }
+}
+
+void ViewManagerClientImpl::OnActiveWindowChanged(Id old_focused_window,
+ Id new_focused_window) {}
+
+////////////////////////////////////////////////////////////////////////////////
+// ViewManagerClientImpl, private:
+
+void ViewManagerClientImpl::RemoveRoot(View* root) {
+ std::vector<View*>::iterator it =
+ std::find(roots_.begin(), roots_.end(), root);
+ if (it != roots_.end())
+ roots_.erase(it);
+}
+
+void ViewManagerClientImpl::OnActionCompleted(bool success) {
+ if (!change_acked_callback_.is_null())
+ change_acked_callback_.Run();
+}
+
+void ViewManagerClientImpl::OnActionCompletedWithErrorCode(ErrorCode code) {
+ OnActionCompleted(code == ERROR_CODE_NONE);
+}
+
+base::Callback<void(bool)> ViewManagerClientImpl::ActionCompletedCallback() {
+ return base::Bind(&ViewManagerClientImpl::OnActionCompleted,
+ base::Unretained(this));
+}
+
+base::Callback<void(ErrorCode)>
+ ViewManagerClientImpl::ActionCompletedCallbackWithErrorCode() {
+ return base::Bind(&ViewManagerClientImpl::OnActionCompletedWithErrorCode,
+ base::Unretained(this));
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h b/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h
new file mode 100644
index 0000000..bd09a58
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_manager_client_impl.h
@@ -0,0 +1,167 @@
+// Copyright 2014 The Chromium 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/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/interfaces/view_manager/view_manager.mojom.h"
+#include "mojo/services/public/interfaces/window_manager/window_manager.mojom.h"
+
+class SkBitmap;
+
+namespace mojo {
+class ViewManager;
+class ViewManagerDelegate;
+class ViewManagerTransaction;
+class Shell;
+
+// Manages the connection with the View Manager service.
+class ViewManagerClientImpl : public ViewManager,
+ public InterfaceImpl<ViewManagerClient>,
+ public WindowManagerClient {
+ public:
+ ViewManagerClientImpl(ViewManagerDelegate* delegate, Shell* shell);
+ virtual ~ViewManagerClientImpl();
+
+ bool connected() const { return connected_; }
+ ConnectionSpecificId connection_id() const { return connection_id_; }
+
+ // API exposed to the view implementations that pushes local changes to the
+ // service.
+ 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 view.
+ void AddChild(Id child_id, Id parent_id);
+ void RemoveChild(Id child_id, Id parent_id);
+
+ void Reorder(Id view_id, Id relative_view_id, OrderDirection direction);
+
+ // Returns true if the specified view was created by this connection.
+ bool OwnsView(Id id) const;
+
+ void SetBounds(Id view_id, const gfx::Rect& bounds);
+ void SetSurfaceId(Id view_id, SurfaceIdPtr surface_id);
+ void SetFocus(Id view_id);
+ void SetVisible(Id view_id, bool visible);
+
+ void Embed(const String& url, Id view_id);
+ void Embed(const String& url,
+ Id view_id,
+ ServiceProviderPtr service_provider);
+
+ void set_change_acked_callback(const base::Callback<void(void)>& callback) {
+ change_acked_callback_ = callback;
+ }
+ void ClearChangeAckedCallback() {
+ change_acked_callback_ = base::Callback<void(void)>();
+ }
+
+ // Start/stop tracking views. While tracked, they can be retrieved via
+ // ViewManager::GetViewById.
+ void AddView(View* view);
+ void RemoveView(Id view_id);
+
+ Shell* shell() { return shell_; }
+
+ private:
+ friend class RootObserver;
+
+ typedef std::map<Id, View*> IdToViewMap;
+
+ // Overridden from ViewManager:
+ virtual void SetWindowManagerDelegate(
+ WindowManagerDelegate* delegate) OVERRIDE;
+ virtual void DispatchEvent(View* target, EventPtr event) OVERRIDE;
+ virtual const std::string& GetEmbedderURL() const OVERRIDE;
+ virtual const std::vector<View*>& GetRoots() const OVERRIDE;
+ virtual View* GetViewById(Id id) OVERRIDE;
+
+ // Overridden from InterfaceImpl:
+ virtual void OnConnectionEstablished() OVERRIDE;
+
+ // Overridden from ViewManagerClient:
+ virtual void OnEmbed(ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root,
+ InterfaceRequest<ServiceProvider> services) OVERRIDE;
+ virtual void OnViewBoundsChanged(Id view_id,
+ RectPtr old_bounds,
+ RectPtr new_bounds) OVERRIDE;
+ virtual void OnViewHierarchyChanged(Id view_id,
+ Id new_parent_id,
+ Id old_parent_id,
+ Array<ViewDataPtr> views) OVERRIDE;
+ virtual void OnViewReordered(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) OVERRIDE;
+ virtual void OnViewDeleted(Id view_id) OVERRIDE;
+ virtual void OnViewVisibilityChanged(Id view_id, bool visible) OVERRIDE;
+ virtual void OnViewDrawnStateChanged(Id view_id, bool drawn) OVERRIDE;
+ virtual void OnViewInputEvent(Id view_id,
+ EventPtr event,
+ const Callback<void()>& callback) OVERRIDE;
+ virtual void Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) OVERRIDE;
+ virtual void DispatchOnViewInputEvent(EventPtr event) OVERRIDE;
+
+ // Overridden from WindowManagerClient:
+ virtual void OnWindowManagerReady() OVERRIDE;
+ virtual void OnCaptureChanged(Id old_capture_view_id,
+ Id new_capture_view_id) OVERRIDE;
+ virtual void OnFocusChanged(Id old_focused_view_id,
+ Id new_focused_view_id) OVERRIDE;
+ virtual void OnActiveWindowChanged(Id old_focused_window,
+ Id new_focused_window) OVERRIDE;
+
+ void RemoveRoot(View* root);
+
+ void OnActionCompleted(bool success);
+ void OnActionCompletedWithErrorCode(ErrorCode code);
+
+ BitmapUploader* BitmapUploaderForView(Id view_id);
+
+ base::Callback<void(bool)> ActionCompletedCallback();
+ base::Callback<void(ErrorCode)> ActionCompletedCallbackWithErrorCode();
+
+ bool connected_;
+ ConnectionSpecificId connection_id_;
+ ConnectionSpecificId next_id_;
+
+ std::string creator_url_;
+
+ base::Callback<void(void)> change_acked_callback_;
+
+ ViewManagerDelegate* delegate_;
+ WindowManagerDelegate* window_manager_delegate_;
+
+ std::vector<View*> roots_;
+
+ IdToViewMap views_;
+
+ ViewManagerService* service_;
+
+ WindowManagerServicePtr window_manager_;
+
+ // TODO(jamesr): Remove once all callers switch from SetContents to
+ // SetSurfaceId.
+ Shell* shell_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_CLIENT_IMPL_H_
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_context.cc b/mojo/services/public/cpp/view_manager/lib/view_manager_context.cc
new file mode 100644
index 0000000..284286e
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_manager_context.cc
@@ -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.
+
+#include "mojo/services/public/cpp/view_manager/view_manager_context.h"
+
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+
+namespace mojo {
+class ApplicationImpl;
+
+void ConnectCallback(bool success) {}
+
+class ViewManagerContext::InternalState {
+ public:
+ InternalState(ApplicationImpl* application_impl) {
+ application_impl->ConnectToService("mojo:mojo_view_manager",
+ &init_service_);
+ }
+ ~InternalState() {}
+
+ ViewManagerInitService* init_service() { return init_service_.get(); }
+
+ private:
+ ViewManagerInitServicePtr init_service_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(InternalState);
+};
+
+ViewManagerContext::ViewManagerContext(ApplicationImpl* application_impl)
+ : state_(new InternalState(application_impl)) {}
+ViewManagerContext::~ViewManagerContext() {}
+
+void ViewManagerContext::Embed(const String& url) {
+ scoped_ptr<ServiceProviderImpl> spi(new ServiceProviderImpl);
+ Embed(url, spi.Pass());
+}
+
+scoped_ptr<ServiceProvider> ViewManagerContext::Embed(
+ const String& url,
+ scoped_ptr<ServiceProviderImpl> exported_services) {
+ scoped_ptr<ServiceProvider> imported_services;
+ // BindToProxy() takes ownership of |exported_services|.
+ ServiceProviderImpl* registry = exported_services.release();
+ ServiceProviderPtr sp;
+ if (registry) {
+ BindToProxy(registry, &sp);
+ imported_services.reset(registry->CreateRemoteServiceProvider());
+ }
+ state_->init_service()->Embed(url, sp.Pass(), base::Bind(&ConnectCallback));
+ return imported_services.Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.cc b/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.cc
new file mode 100644
index 0000000..abe0906
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.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/services/public/cpp/view_manager/lib/view_manager_test_suite.h"
+
+#include "base/i18n/icu_util.h"
+#include "ui/gl/gl_surface.h"
+
+#if defined(USE_X11)
+#include "ui/gfx/x/x11_connection.h"
+#endif
+
+namespace mojo {
+
+ViewManagerTestSuite::ViewManagerTestSuite(int argc, char** argv)
+ : TestSuite(argc, argv) {}
+
+ViewManagerTestSuite::~ViewManagerTestSuite() {
+}
+
+void ViewManagerTestSuite::Initialize() {
+#if defined(USE_X11)
+ // Each test ends up creating a new thread for the native viewport service.
+ // In other words we'll use X on different threads, so tell it that.
+ gfx::InitializeThreadedX11();
+#endif
+
+#if defined(COMPONENT_BUILD)
+ gfx::GLSurface::InitializeOneOffForTests();
+#endif
+
+ base::TestSuite::Initialize();
+
+ // base::TestSuite and ViewsInit both try to load icu. That's ok for tests.
+ base::i18n::AllowMultipleInitializeCallsForTesting();
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h b/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.h
new file mode 100644
index 0000000..e073bbd
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_manager_test_suite.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_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 {
+
+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 mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_MANAGER_TEST_SUITE_H_
diff --git a/mojo/services/public/cpp/view_manager/lib/view_manager_unittests.cc b/mojo/services/public/cpp/view_manager/lib/view_manager_unittests.cc
new file mode 100644
index 0000000..9f4f109
--- /dev/null
+++ b/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::ViewManagerTestSuite test_suite(argc, argv);
+
+ return base::LaunchUnitTests(
+ argc, argv, base::Bind(&TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/services/public/cpp/view_manager/lib/view_observer.cc b/mojo/services/public/cpp/view_manager/lib/view_observer.cc
new file mode 100644
index 0000000..812c028
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_observer.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/services/public/cpp/view_manager/view_observer.h"
+
+#include "base/basictypes.h"
+
+namespace mojo {
+
+////////////////////////////////////////////////////////////////////////////////
+// ViewObserver, public:
+
+ViewObserver::TreeChangeParams::TreeChangeParams()
+ : target(NULL),
+ old_parent(NULL),
+ new_parent(NULL),
+ receiver(NULL) {}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_private.cc b/mojo/services/public/cpp/view_manager/lib/view_private.cc
new file mode 100644
index 0000000..5125c7d
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_private.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/public/cpp/view_manager/lib/view_private.h"
+
+namespace mojo {
+
+ViewPrivate::ViewPrivate(View* view)
+ : view_(view) {
+}
+
+ViewPrivate::~ViewPrivate() {
+}
+
+// static
+View* ViewPrivate::LocalCreate() {
+ return new View;
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/lib/view_private.h b/mojo/services/public/cpp/view_manager/lib/view_private.h
new file mode 100644
index 0000000..73138e2
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/lib/view_private.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium 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 {
+
+// This class is a friend of a View and contains functions to mutate internal
+// state of View.
+class ViewPrivate {
+ public:
+ explicit ViewPrivate(View* view);
+ ~ViewPrivate();
+
+ // Creates and returns a new View. Caller owns the return value.
+ static View* LocalCreate();
+
+ ObserverList<ViewObserver>* observers() { return &view_->observers_; }
+
+ void ClearParent() { view_->parent_ = NULL; }
+
+ void set_visible(bool visible) { view_->visible_ = visible; }
+
+ void set_drawn(bool drawn) { view_->drawn_ = drawn; }
+
+ void set_id(Id id) { view_->id_ = id; }
+
+ ViewManager* view_manager() { return view_->manager_; }
+ void set_view_manager(ViewManager* manager) {
+ view_->manager_ = manager;
+ }
+
+ void LocalDestroy() {
+ view_->LocalDestroy();
+ }
+ void LocalAddChild(View* child) {
+ view_->LocalAddChild(child);
+ }
+ void LocalRemoveChild(View* child) {
+ view_->LocalRemoveChild(child);
+ }
+ void LocalReorder(View* relative, OrderDirection direction) {
+ view_->LocalReorder(relative, direction);
+ }
+ void LocalSetBounds(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ view_->LocalSetBounds(old_bounds, new_bounds);
+ }
+ void LocalSetDrawn(bool drawn) { view_->LocalSetDrawn(drawn); }
+
+ private:
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewPrivate);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_LIB_VIEW_PRIVATE_H_
diff --git a/mojo/services/public/cpp/view_manager/tests/BUILD.gn b/mojo/services/public/cpp/view_manager/tests/BUILD.gn
new file mode 100644
index 0000000..a468a92
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/tests/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.
+
+import("//build/config/ui.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_view_manager_lib_unittests
+test("mojo_view_manager_lib_unittests") {
+ sources = [
+ "view_unittest.cc",
+ "view_manager_unittest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ "//ui/gfx",
+ "//ui/gfx:test_support",
+ "//mojo/application_manager",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/shell:test_support",
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/services/public/cpp/view_manager",
+ ]
+ if (use_aura) {
+ deps += [ "//mojo/services/public/cpp/view_manager/lib:run_unittests" ]
+ } else {
+ deps += [ "//mojo/common/test:run_all_unittests" ]
+ }
+}
diff --git a/mojo/services/public/cpp/view_manager/tests/DEPS b/mojo/services/public/cpp/view_manager/tests/DEPS
new file mode 100644
index 0000000..2ecf3db
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/tests/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/application_manager",
+]
diff --git a/mojo/services/public/cpp/view_manager/tests/view_manager_unittest.cc b/mojo/services/public/cpp/view_manager/tests/view_manager_unittest.cc
new file mode 100644
index 0000000..693e91a
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/tests/view_manager_unittest.cc
@@ -0,0 +1,650 @@
+// Copyright 2014 The Chromium 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_manager.h"
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.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/util.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
+#include "mojo/services/public/cpp/view_manager/view_observer.h"
+#include "mojo/shell/shell_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const char kWindowManagerURL[] = "mojo:window_manager";
+const char kEmbeddedApp1URL[] = "mojo:embedded_app_1";
+
+base::RunLoop* current_run_loop = NULL;
+
+void DoRunLoop() {
+ base::RunLoop run_loop;
+ current_run_loop = &run_loop;
+ current_run_loop->Run();
+ current_run_loop = NULL;
+}
+
+void QuitRunLoop() {
+ current_run_loop->Quit();
+}
+
+class ConnectApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public ViewManagerDelegate {
+ public:
+ typedef base::Callback<void(ViewManager*, View*)> LoadedCallback;
+
+ explicit ConnectApplicationLoader(const LoadedCallback& callback)
+ : callback_(callback) {}
+ virtual ~ConnectApplicationLoader() {}
+
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ // Overridden from ApplicationLoader:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) OVERRIDE {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+ scoped_ptr<ApplicationImpl> app(new ApplicationImpl(this,
+ shell_handle.Pass()));
+ apps_.push_back(app.release());
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) OVERRIDE {}
+
+ virtual bool ConfigureIncomingConnection(ApplicationConnection* connection)
+ OVERRIDE {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) OVERRIDE {
+ callback_.Run(view_manager, root);
+ }
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) OVERRIDE {}
+
+ ScopedVector<ApplicationImpl> apps_;
+ LoadedCallback callback_;
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectApplicationLoader);
+};
+
+class BoundsChangeObserver : public ViewObserver {
+ public:
+ explicit BoundsChangeObserver(View* view) : view_(view) {}
+ virtual ~BoundsChangeObserver() {}
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ DCHECK_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver);
+};
+
+// Wait until the bounds of the supplied view change.
+void WaitForBoundsToChange(View* view) {
+ BoundsChangeObserver observer(view);
+ view->AddObserver(&observer);
+ DoRunLoop();
+ view->RemoveObserver(&observer);
+}
+
+// Spins a runloop until the tree beginning at |root| has |tree_size| views
+// (including |root|).
+class TreeSizeMatchesObserver : public ViewObserver {
+ public:
+ TreeSizeMatchesObserver(View* tree, size_t tree_size)
+ : tree_(tree),
+ tree_size_(tree_size) {}
+ virtual ~TreeSizeMatchesObserver() {}
+
+ bool IsTreeCorrectSize() {
+ return CountViews(tree_) == tree_size_;
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnTreeChanged(const TreeChangeParams& params) OVERRIDE {
+ if (IsTreeCorrectSize())
+ QuitRunLoop();
+ }
+
+ size_t CountViews(const View* view) const {
+ size_t count = 1;
+ View::Children::const_iterator it = view->children().begin();
+ for (; it != view->children().end(); ++it)
+ count += CountViews(*it);
+ return count;
+ }
+
+ View* tree_;
+ size_t tree_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver);
+};
+
+void WaitForTreeSizeToMatch(View* view, size_t tree_size) {
+ TreeSizeMatchesObserver observer(view, tree_size);
+ if (observer.IsTreeCorrectSize())
+ return;
+ view->AddObserver(&observer);
+ DoRunLoop();
+ view->RemoveObserver(&observer);
+}
+
+// Utility class that waits for the destruction of some number of views and
+// views.
+class DestructionObserver : public ViewObserver {
+ public:
+ // |views| or |views| can be NULL.
+ explicit DestructionObserver(std::set<Id>* views) : views_(views) {}
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewDestroyed(View* view) OVERRIDE {
+ std::set<Id>::iterator it = views_->find(view->id());
+ if (it != views_->end())
+ views_->erase(it);
+ if (CanQuit())
+ QuitRunLoop();
+ }
+
+ bool CanQuit() {
+ return !views_ || views_->empty();
+ }
+
+ std::set<Id>* views_;
+
+ DISALLOW_COPY_AND_ASSIGN(DestructionObserver);
+};
+
+void WaitForDestruction(ViewManager* view_manager, std::set<Id>* views) {
+ DestructionObserver observer(views);
+ DCHECK(views);
+ if (views) {
+ for (std::set<Id>::const_iterator it = views->begin();
+ it != views->end(); ++it) {
+ view_manager->GetViewById(*it)->AddObserver(&observer);
+ }
+ }
+ DoRunLoop();
+}
+
+class OrderChangeObserver : public ViewObserver {
+ public:
+ OrderChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ virtual ~OrderChangeObserver() {
+ view_->RemoveObserver(this);
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewReordered(View* view,
+ View* relative_view,
+ OrderDirection direction) OVERRIDE {
+ DCHECK_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver);
+};
+
+void WaitForOrderChange(ViewManager* view_manager, View* view) {
+ OrderChangeObserver observer(view);
+ DoRunLoop();
+}
+
+// Tracks a view's destruction. Query is_valid() for current state.
+class ViewTracker : public ViewObserver {
+ public:
+ explicit ViewTracker(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ virtual ~ViewTracker() {
+ if (view_)
+ view_->RemoveObserver(this);
+ }
+
+ bool is_valid() const { return !!view_; }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewDestroyed(View* view) OVERRIDE {
+ DCHECK_EQ(view, view_);
+ view_ = NULL;
+ }
+
+ int id_;
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewTracker);
+};
+
+} // namespace
+
+// ViewManager -----------------------------------------------------------------
+
+// These tests model synchronization of two peer connections to the view manager
+// service, that are given access to some root view.
+
+class ViewManagerTest : public testing::Test {
+ public:
+ ViewManagerTest()
+ : connect_loop_(NULL),
+ loaded_view_manager_(NULL),
+ window_manager_(NULL),
+ commit_count_(0) {}
+
+ protected:
+ ViewManager* window_manager() { return window_manager_; }
+
+ View* CreateViewInParent(View* parent) {
+ ViewManager* parent_manager = ViewPrivate(parent).view_manager();
+ View* view = View::Create(parent_manager);
+ parent->AddChild(view);
+ return view;
+ }
+
+ // Embeds another version of the test app @ view.
+ ViewManager* Embed(ViewManager* view_manager, View* view) {
+ DCHECK_EQ(view_manager, ViewPrivate(view).view_manager());
+ view->Embed(kEmbeddedApp1URL);
+ RunRunLoop();
+ return GetLoadedViewManager();
+ }
+
+ ViewManager* GetLoadedViewManager() {
+ ViewManager* view_manager = loaded_view_manager_;
+ loaded_view_manager_ = NULL;
+ return view_manager;
+ }
+
+ void UnloadApplication(const GURL& url) {
+ test_helper_.SetLoaderForURL(scoped_ptr<ApplicationLoader>(), url);
+ }
+
+ private:
+ // Overridden from testing::Test:
+ virtual void SetUp() OVERRIDE {
+ ConnectApplicationLoader::LoadedCallback ready_callback = base::Bind(
+ &ViewManagerTest::OnViewManagerLoaded, base::Unretained(this));
+ test_helper_.Init();
+ test_helper_.SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(
+ new ConnectApplicationLoader(ready_callback)),
+ GURL(kWindowManagerURL));
+ test_helper_.SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(
+ new ConnectApplicationLoader(ready_callback)),
+ GURL(kEmbeddedApp1URL));
+
+ test_helper_.application_manager()->ConnectToService(
+ GURL("mojo:mojo_view_manager"), &view_manager_init_);
+ ASSERT_TRUE(EmbedRoot(view_manager_init_.get(), kWindowManagerURL));
+ }
+
+ void EmbedRootCallback(bool* result_cache, bool result) {
+ *result_cache = result;
+ }
+
+ bool EmbedRoot(ViewManagerInitService* view_manager_init,
+ const std::string& url) {
+ bool result = false;
+ ServiceProviderPtr sp;
+ BindToProxy(new ServiceProviderImpl, &sp);
+ view_manager_init->Embed(
+ url, sp.Pass(),
+ base::Bind(&ViewManagerTest::EmbedRootCallback, base::Unretained(this),
+ &result));
+ RunRunLoop();
+ window_manager_ = GetLoadedViewManager();
+ return result;
+ }
+
+ void OnViewManagerLoaded(ViewManager* view_manager, View* root) {
+ loaded_view_manager_ = view_manager;
+ connect_loop_->Quit();
+ }
+
+ void RunRunLoop() {
+ base::RunLoop run_loop;
+ connect_loop_ = &run_loop;
+ connect_loop_->Run();
+ connect_loop_ = NULL;
+ }
+
+ base::RunLoop* connect_loop_;
+ shell::ShellTestHelper test_helper_;
+ ViewManagerInitServicePtr view_manager_init_;
+ // Used to receive the most recent view manager loaded by an embed action.
+ ViewManager* loaded_view_manager_;
+ // The View Manager connection held by the window manager (app running at the
+ // root view).
+ ViewManager* window_manager_;
+ int commit_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerTest);
+};
+
+// TODO(sky): all of these tests are disabled as each test triggers running
+// ViewsInit, which tries to register the same set of paths with the
+// PathService, triggering a DCHECK.
+TEST_F(ViewManagerTest, DISABLED_SetUp) {}
+
+TEST_F(ViewManagerTest, DISABLED_Embed) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ EXPECT_TRUE(NULL != embedded);
+
+ View* view_in_embedded = embedded->GetRoots().front();
+ EXPECT_EQ(view->parent(), window_manager()->GetRoots().front());
+ EXPECT_EQ(NULL, view_in_embedded->parent());
+}
+
+// Window manager has two views, N1 and N11. Embeds A at N1. A should not see
+// N11.
+// TODO(sky): Update client lib to match server.
+TEST_F(ViewManagerTest, DISABLED_EmbeddedDoesntSeeChild) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ View* nested = View::Create(window_manager());
+ view->AddChild(nested);
+
+ ViewManager* embedded = Embed(window_manager(), view);
+ EXPECT_EQ(embedded->GetRoots().front()->children().front()->id(),
+ nested->id());
+ EXPECT_TRUE(embedded->GetRoots().front()->children().empty());
+ EXPECT_TRUE(nested->parent() == NULL);
+}
+
+// http://crbug.com/396300
+TEST_F(ViewManagerTest, DISABLED_ViewManagerDestroyed_CleanupView) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+
+ Id view_id = view->id();
+
+ UnloadApplication(GURL(kWindowManagerURL));
+
+ std::set<Id> views;
+ views.insert(view_id);
+ WaitForDestruction(embedded, &views);
+
+ EXPECT_TRUE(embedded->GetRoots().empty());
+}
+
+// TODO(beng): write a replacement test for the one that once existed here:
+// This test validates the following scenario:
+// - a view originating from one connection
+// - a view originating from a second connection
+// + the connection originating the view is destroyed
+// -> the view should still exist (since the second connection is live) but
+// should be disconnected from any views.
+// http://crbug.com/396300
+//
+// TODO(beng): The new test should validate the scenario as described above
+// except that the second connection still has a valid tree.
+
+// Verifies that bounds changes applied to a view hierarchy in one connection
+// are reflected to another.
+TEST_F(ViewManagerTest, DISABLED_SetBounds) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+
+ View* view_in_embedded = embedded->GetViewById(view->id());
+ EXPECT_EQ(view->bounds(), view_in_embedded->bounds());
+
+ view->SetBounds(gfx::Rect(100, 100));
+ EXPECT_NE(view->bounds(), view_in_embedded->bounds());
+ WaitForBoundsToChange(view_in_embedded);
+ EXPECT_EQ(view->bounds(), view_in_embedded->bounds());
+}
+
+// Verifies that bounds changes applied to a view owned by a different
+// connection are refused.
+TEST_F(ViewManagerTest, DISABLED_SetBoundsSecurity) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+
+ View* view_in_embedded = embedded->GetViewById(view->id());
+ view->SetBounds(gfx::Rect(800, 600));
+ WaitForBoundsToChange(view_in_embedded);
+
+ view_in_embedded->SetBounds(gfx::Rect(1024, 768));
+ // Bounds change should have been rejected.
+ EXPECT_EQ(view->bounds(), view_in_embedded->bounds());
+}
+
+// Verifies that a view can only be destroyed by the connection that created it.
+TEST_F(ViewManagerTest, DISABLED_DestroySecurity) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+
+ View* view_in_embedded = embedded->GetViewById(view->id());
+
+ ViewTracker tracker2(view_in_embedded);
+ view_in_embedded->Destroy();
+ // View should not have been destroyed.
+ EXPECT_TRUE(tracker2.is_valid());
+
+ ViewTracker tracker1(view);
+ view->Destroy();
+ EXPECT_FALSE(tracker1.is_valid());
+}
+
+TEST_F(ViewManagerTest, DISABLED_MultiRoots) {
+ View* view1 = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view1);
+ View* view2 = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view2);
+ ViewManager* embedded1 = Embed(window_manager(), view1);
+ ViewManager* embedded2 = Embed(window_manager(), view2);
+ EXPECT_EQ(embedded1, embedded2);
+}
+
+TEST_F(ViewManagerTest, DISABLED_EmbeddingIdentity) {
+ View* view = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ EXPECT_EQ(kWindowManagerURL, embedded->GetEmbedderURL());
+}
+
+TEST_F(ViewManagerTest, DISABLED_Reorder) {
+ View* view1 = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view1);
+
+ ViewManager* embedded = Embed(window_manager(), view1);
+
+ View* view11 = View::Create(embedded);
+ embedded->GetRoots().front()->AddChild(view11);
+ View* view12 = View::Create(embedded);
+ embedded->GetRoots().front()->AddChild(view12);
+
+ View* view1_in_wm = window_manager()->GetViewById(view1->id());
+
+ {
+ WaitForTreeSizeToMatch(view1, 2u);
+ view11->MoveToFront();
+ WaitForOrderChange(window_manager(),
+ window_manager()->GetViewById(view11->id()));
+
+ EXPECT_EQ(view1_in_wm->children().front(),
+ window_manager()->GetViewById(view12->id()));
+ EXPECT_EQ(view1_in_wm->children().back(),
+ window_manager()->GetViewById(view11->id()));
+ }
+
+ {
+ view11->MoveToBack();
+ WaitForOrderChange(window_manager(),
+ window_manager()->GetViewById(view11->id()));
+
+ EXPECT_EQ(view1_in_wm->children().front(),
+ window_manager()->GetViewById(view11->id()));
+ EXPECT_EQ(view1_in_wm->children().back(),
+ window_manager()->GetViewById(view12->id()));
+ }
+}
+
+namespace {
+
+class VisibilityChangeObserver : public ViewObserver {
+ public:
+ explicit VisibilityChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ virtual ~VisibilityChangeObserver() { view_->RemoveObserver(this); }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewVisibilityChanged(View* view) OVERRIDE {
+ EXPECT_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewManagerTest, DISABLED_Visible) {
+ View* view1 = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view1);
+
+ // Embed another app and verify initial state.
+ ViewManager* embedded = Embed(window_manager(), view1);
+ ASSERT_EQ(1u, embedded->GetRoots().size());
+ View* embedded_root = embedded->GetRoots().front();
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_TRUE(embedded_root->IsDrawn());
+
+ // Change the visible state from the first connection and verify its mirrored
+ // correctly to the embedded app.
+ {
+ VisibilityChangeObserver observer(embedded_root);
+ view1->SetVisible(false);
+ DoRunLoop();
+ }
+
+ EXPECT_FALSE(view1->visible());
+ EXPECT_FALSE(view1->IsDrawn());
+
+ EXPECT_FALSE(embedded_root->visible());
+ EXPECT_FALSE(embedded_root->IsDrawn());
+
+ // Make the node visible again.
+ {
+ VisibilityChangeObserver observer(embedded_root);
+ view1->SetVisible(true);
+ DoRunLoop();
+ }
+
+ EXPECT_TRUE(view1->visible());
+ EXPECT_TRUE(view1->IsDrawn());
+
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_TRUE(embedded_root->IsDrawn());
+}
+
+namespace {
+
+class DrawnChangeObserver : public ViewObserver {
+ public:
+ explicit DrawnChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ virtual ~DrawnChangeObserver() { view_->RemoveObserver(this); }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewDrawnChanged(View* view) OVERRIDE {
+ EXPECT_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewManagerTest, DISABLED_Drawn) {
+ View* view1 = View::Create(window_manager());
+ window_manager()->GetRoots().front()->AddChild(view1);
+
+ // Embed another app and verify initial state.
+ ViewManager* embedded = Embed(window_manager(), view1);
+ ASSERT_EQ(1u, embedded->GetRoots().size());
+ View* embedded_root = embedded->GetRoots().front();
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_TRUE(embedded_root->IsDrawn());
+
+ // Change the visibility of the root, this should propagate a drawn state
+ // change to |embedded|.
+ {
+ DrawnChangeObserver observer(embedded_root);
+ window_manager()->GetRoots().front()->SetVisible(false);
+ DoRunLoop();
+ }
+
+ EXPECT_TRUE(view1->visible());
+ EXPECT_FALSE(view1->IsDrawn());
+
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_FALSE(embedded_root->IsDrawn());
+}
+
+// TODO(beng): tests for view event dispatcher.
+// - verify that we see events for all views.
+
+// TODO(beng): tests for focus:
+// - focus between two views known to a connection
+// - focus between views unknown to one of the connections.
+// - focus between views unknown to either connection.
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/tests/view_unittest.cc b/mojo/services/public/cpp/view_manager/tests/view_unittest.cc
new file mode 100644
index 0000000..3f08075
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/tests/view_unittest.cc
@@ -0,0 +1,619 @@
+// Copyright 2014 The Chromium 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 "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/services/public/cpp/view_manager/lib/view_private.h"
+#include "mojo/services/public/cpp/view_manager/util.h"
+#include "mojo/services/public/cpp/view_manager/view_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+// View ------------------------------------------------------------------------
+
+typedef testing::Test ViewTest;
+
+// Subclass with public ctor/dtor.
+class TestView : public View {
+ public:
+ TestView() {
+ ViewPrivate(this).set_id(1);
+ }
+ ~TestView() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestView);
+};
+
+TEST_F(ViewTest, AddChild) {
+ TestView v1;
+ TestView v11;
+ v1.AddChild(&v11);
+ EXPECT_EQ(1U, v1.children().size());
+}
+
+TEST_F(ViewTest, RemoveChild) {
+ TestView v1;
+ TestView v11;
+ v1.AddChild(&v11);
+ EXPECT_EQ(1U, v1.children().size());
+ v1.RemoveChild(&v11);
+ EXPECT_EQ(0U, v1.children().size());
+}
+
+TEST_F(ViewTest, Reparent) {
+ TestView v1;
+ TestView v2;
+ TestView v11;
+ v1.AddChild(&v11);
+ EXPECT_EQ(1U, v1.children().size());
+ v2.AddChild(&v11);
+ EXPECT_EQ(1U, v2.children().size());
+ EXPECT_EQ(0U, v1.children().size());
+}
+
+TEST_F(ViewTest, Contains) {
+ TestView v1;
+
+ // Direct descendant.
+ TestView v11;
+ v1.AddChild(&v11);
+ EXPECT_TRUE(v1.Contains(&v11));
+
+ // Indirect descendant.
+ TestView v111;
+ v11.AddChild(&v111);
+ EXPECT_TRUE(v1.Contains(&v111));
+}
+
+TEST_F(ViewTest, GetChildById) {
+ TestView v1;
+ ViewPrivate(&v1).set_id(1);
+ TestView v11;
+ ViewPrivate(&v11).set_id(11);
+ v1.AddChild(&v11);
+ TestView v111;
+ ViewPrivate(&v111).set_id(111);
+ v11.AddChild(&v111);
+
+ // Find direct & indirect descendents.
+ EXPECT_EQ(&v11, v1.GetChildById(v11.id()));
+ EXPECT_EQ(&v111, v1.GetChildById(v111.id()));
+}
+
+TEST_F(ViewTest, DrawnAndVisible) {
+ TestView v1;
+ EXPECT_TRUE(v1.visible());
+ EXPECT_FALSE(v1.IsDrawn());
+
+ ViewPrivate(&v1).set_drawn(true);
+
+ TestView v11;
+ v1.AddChild(&v11);
+ EXPECT_TRUE(v11.visible());
+ EXPECT_TRUE(v11.IsDrawn());
+
+ v1.RemoveChild(&v11);
+ EXPECT_TRUE(v11.visible());
+ EXPECT_FALSE(v11.IsDrawn());
+}
+
+// ViewObserver --------------------------------------------------------
+
+typedef testing::Test ViewObserverTest;
+
+bool TreeChangeParamsMatch(const ViewObserver::TreeChangeParams& lhs,
+ const ViewObserver::TreeChangeParams& rhs) {
+ return lhs.target == rhs.target && lhs.old_parent == rhs.old_parent &&
+ lhs.new_parent == rhs.new_parent && lhs.receiver == rhs.receiver;
+}
+
+class TreeChangeObserver : public ViewObserver {
+ public:
+ explicit TreeChangeObserver(View* observee) : observee_(observee) {
+ observee_->AddObserver(this);
+ }
+ virtual ~TreeChangeObserver() {
+ observee_->RemoveObserver(this);
+ }
+
+ void Reset() {
+ received_params_.clear();
+ }
+
+ const std::vector<TreeChangeParams>& received_params() {
+ return received_params_;
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnTreeChanging(const TreeChangeParams& params) OVERRIDE {
+ received_params_.push_back(params);
+ }
+ virtual void OnTreeChanged(const TreeChangeParams& params) OVERRIDE {
+ received_params_.push_back(params);
+ }
+
+ View* observee_;
+ std::vector<TreeChangeParams> received_params_;
+
+ DISALLOW_COPY_AND_ASSIGN(TreeChangeObserver);
+};
+
+// Adds/Removes v11 to v1.
+TEST_F(ViewObserverTest, TreeChange_SimpleAddRemove) {
+ TestView v1;
+ TreeChangeObserver o1(&v1);
+ EXPECT_TRUE(o1.received_params().empty());
+
+ TestView v11;
+ TreeChangeObserver o11(&v11);
+ EXPECT_TRUE(o11.received_params().empty());
+
+ // Add.
+
+ v1.AddChild(&v11);
+
+ EXPECT_EQ(2U, o1.received_params().size());
+ ViewObserver::TreeChangeParams p1;
+ p1.target = &v11;
+ p1.receiver = &v1;
+ p1.old_parent = NULL;
+ p1.new_parent = &v1;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().back()));
+
+ EXPECT_EQ(2U, o11.received_params().size());
+ ViewObserver::TreeChangeParams p11 = p1;
+ p11.receiver = &v11;
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().back()));
+
+ o1.Reset();
+ o11.Reset();
+ EXPECT_TRUE(o1.received_params().empty());
+ EXPECT_TRUE(o11.received_params().empty());
+
+ // Remove.
+
+ v1.RemoveChild(&v11);
+
+ EXPECT_EQ(2U, o1.received_params().size());
+ p1.target = &v11;
+ p1.receiver = &v1;
+ p1.old_parent = &v1;
+ p1.new_parent = NULL;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().front()));
+
+ EXPECT_EQ(2U, o11.received_params().size());
+ p11 = p1;
+ p11.receiver = &v11;
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().back()));
+}
+
+// Creates these two trees:
+// v1
+// +- v11
+// v111
+// +- v1111
+// +- v1112
+// Then adds/removes v111 from v11.
+TEST_F(ViewObserverTest, TreeChange_NestedAddRemove) {
+ TestView v1, v11, v111, v1111, v1112;
+
+ // Root tree.
+ v1.AddChild(&v11);
+
+ // Tree to be attached.
+ v111.AddChild(&v1111);
+ v111.AddChild(&v1112);
+
+ TreeChangeObserver o1(&v1), o11(&v11), o111(&v111), o1111(&v1111),
+ o1112(&v1112);
+ ViewObserver::TreeChangeParams p1, p11, p111, p1111, p1112;
+
+ // Add.
+
+ v11.AddChild(&v111);
+
+ EXPECT_EQ(2U, o1.received_params().size());
+ p1.target = &v111;
+ p1.receiver = &v1;
+ p1.old_parent = NULL;
+ p1.new_parent = &v11;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().back()));
+
+ EXPECT_EQ(2U, o11.received_params().size());
+ p11 = p1;
+ p11.receiver = &v11;
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().back()));
+
+ EXPECT_EQ(2U, o111.received_params().size());
+ p111 = p11;
+ p111.receiver = &v111;
+ EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().back()));
+
+ EXPECT_EQ(2U, o1111.received_params().size());
+ p1111 = p111;
+ p1111.receiver = &v1111;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().back()));
+
+ EXPECT_EQ(2U, o1112.received_params().size());
+ p1112 = p111;
+ p1112.receiver = &v1112;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().back()));
+
+ // Remove.
+ o1.Reset();
+ o11.Reset();
+ o111.Reset();
+ o1111.Reset();
+ o1112.Reset();
+ EXPECT_TRUE(o1.received_params().empty());
+ EXPECT_TRUE(o11.received_params().empty());
+ EXPECT_TRUE(o111.received_params().empty());
+ EXPECT_TRUE(o1111.received_params().empty());
+ EXPECT_TRUE(o1112.received_params().empty());
+
+ v11.RemoveChild(&v111);
+
+ EXPECT_EQ(2U, o1.received_params().size());
+ p1.target = &v111;
+ p1.receiver = &v1;
+ p1.old_parent = &v11;
+ p1.new_parent = NULL;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().front()));
+
+ EXPECT_EQ(2U, o11.received_params().size());
+ p11 = p1;
+ p11.receiver = &v11;
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
+
+ EXPECT_EQ(2U, o111.received_params().size());
+ p111 = p11;
+ p111.receiver = &v111;
+ EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().back()));
+
+ EXPECT_EQ(2U, o1111.received_params().size());
+ p1111 = p111;
+ p1111.receiver = &v1111;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().back()));
+
+ EXPECT_EQ(2U, o1112.received_params().size());
+ p1112 = p111;
+ p1112.receiver = &v1112;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().back()));
+}
+
+TEST_F(ViewObserverTest, TreeChange_Reparent) {
+ TestView v1, v11, v12, v111;
+ v1.AddChild(&v11);
+ v1.AddChild(&v12);
+ v11.AddChild(&v111);
+
+ TreeChangeObserver o1(&v1), o11(&v11), o12(&v12), o111(&v111);
+
+ // Reparent.
+ v12.AddChild(&v111);
+
+ // v1 (root) should see both changing and changed notifications.
+ EXPECT_EQ(4U, o1.received_params().size());
+ ViewObserver::TreeChangeParams p1;
+ p1.target = &v111;
+ p1.receiver = &v1;
+ p1.old_parent = &v11;
+ p1.new_parent = &v12;
+ EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().back()));
+
+ // v11 should see changing notifications.
+ EXPECT_EQ(2U, o11.received_params().size());
+ ViewObserver::TreeChangeParams p11;
+ p11 = p1;
+ p11.receiver = &v11;
+ EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
+
+ // v12 should see changed notifications.
+ EXPECT_EQ(2U, o12.received_params().size());
+ ViewObserver::TreeChangeParams p12;
+ p12 = p1;
+ p12.receiver = &v12;
+ EXPECT_TRUE(TreeChangeParamsMatch(p12, o12.received_params().back()));
+
+ // v111 should see both changing and changed notifications.
+ EXPECT_EQ(2U, o111.received_params().size());
+ ViewObserver::TreeChangeParams p111;
+ p111 = p1;
+ p111.receiver = &v111;
+ EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().front()));
+ EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().back()));
+}
+
+namespace {
+
+class OrderChangeObserver : public ViewObserver {
+ public:
+ struct Change {
+ View* view;
+ View* relative_view;
+ OrderDirection direction;
+ };
+ typedef std::vector<Change> Changes;
+
+ explicit OrderChangeObserver(View* observee) : observee_(observee) {
+ observee_->AddObserver(this);
+ }
+ virtual ~OrderChangeObserver() {
+ observee_->RemoveObserver(this);
+ }
+
+ Changes GetAndClearChanges() {
+ Changes changes;
+ changes_.swap(changes);
+ return changes;
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewReordering(View* view,
+ View* relative_view,
+ OrderDirection direction) OVERRIDE {
+ OnViewReordered(view, relative_view, direction);
+ }
+
+ virtual void OnViewReordered(View* view,
+ View* relative_view,
+ OrderDirection direction) OVERRIDE {
+ Change change;
+ change.view = view;
+ change.relative_view = relative_view;
+ change.direction = direction;
+ changes_.push_back(change);
+ }
+
+ View* observee_;
+ Changes changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewObserverTest, Order) {
+ TestView v1, v11, v12, v13;
+ v1.AddChild(&v11);
+ v1.AddChild(&v12);
+ v1.AddChild(&v13);
+
+ // Order: v11, v12, v13
+ EXPECT_EQ(3U, v1.children().size());
+ EXPECT_EQ(&v11, v1.children().front());
+ EXPECT_EQ(&v13, v1.children().back());
+
+ {
+ OrderChangeObserver observer(&v11);
+
+ // Move v11 to front.
+ // Resulting order: v12, v13, v11
+ v11.MoveToFront();
+ EXPECT_EQ(&v12, v1.children().front());
+ EXPECT_EQ(&v11, v1.children().back());
+
+ OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
+ ASSERT_EQ(2U, changes.size());
+ EXPECT_EQ(&v11, changes[0].view);
+ EXPECT_EQ(&v13, changes[0].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_ABOVE, changes[0].direction);
+
+ EXPECT_EQ(&v11, changes[1].view);
+ EXPECT_EQ(&v13, changes[1].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_ABOVE, changes[1].direction);
+ }
+
+ {
+ OrderChangeObserver observer(&v11);
+
+ // Move v11 to back.
+ // Resulting order: v11, v12, v13
+ v11.MoveToBack();
+ EXPECT_EQ(&v11, v1.children().front());
+ EXPECT_EQ(&v13, v1.children().back());
+
+ OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
+ ASSERT_EQ(2U, changes.size());
+ EXPECT_EQ(&v11, changes[0].view);
+ EXPECT_EQ(&v12, changes[0].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_BELOW, changes[0].direction);
+
+ EXPECT_EQ(&v11, changes[1].view);
+ EXPECT_EQ(&v12, changes[1].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_BELOW, changes[1].direction);
+ }
+
+ {
+ OrderChangeObserver observer(&v11);
+
+ // Move v11 above v12.
+ // Resulting order: v12. v11, v13
+ v11.Reorder(&v12, ORDER_DIRECTION_ABOVE);
+ EXPECT_EQ(&v12, v1.children().front());
+ EXPECT_EQ(&v13, v1.children().back());
+
+ OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
+ ASSERT_EQ(2U, changes.size());
+ EXPECT_EQ(&v11, changes[0].view);
+ EXPECT_EQ(&v12, changes[0].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_ABOVE, changes[0].direction);
+
+ EXPECT_EQ(&v11, changes[1].view);
+ EXPECT_EQ(&v12, changes[1].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_ABOVE, changes[1].direction);
+ }
+
+ {
+ OrderChangeObserver observer(&v11);
+
+ // Move v11 below v12.
+ // Resulting order: v11, v12, v13
+ v11.Reorder(&v12, ORDER_DIRECTION_BELOW);
+ EXPECT_EQ(&v11, v1.children().front());
+ EXPECT_EQ(&v13, v1.children().back());
+
+ OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
+ ASSERT_EQ(2U, changes.size());
+ EXPECT_EQ(&v11, changes[0].view);
+ EXPECT_EQ(&v12, changes[0].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_BELOW, changes[0].direction);
+
+ EXPECT_EQ(&v11, changes[1].view);
+ EXPECT_EQ(&v12, changes[1].relative_view);
+ EXPECT_EQ(ORDER_DIRECTION_BELOW, changes[1].direction);
+ }
+}
+
+namespace {
+
+typedef std::vector<std::string> Changes;
+
+std::string ViewIdToString(Id id) {
+ return (id == 0) ? "null" :
+ base::StringPrintf("%d,%d", HiWord(id), LoWord(id));
+}
+
+std::string RectToString(const gfx::Rect& rect) {
+ return base::StringPrintf("%d,%d %dx%d",
+ rect.x(), rect.y(), rect.width(), rect.height());
+}
+
+class BoundsChangeObserver : public ViewObserver {
+ public:
+ explicit BoundsChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ virtual ~BoundsChangeObserver() {
+ view_->RemoveObserver(this);
+ }
+
+ Changes GetAndClearChanges() {
+ Changes changes;
+ changes.swap(changes_);
+ return changes;
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewBoundsChanging(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ changes_.push_back(
+ base::StringPrintf(
+ "view=%s old_bounds=%s new_bounds=%s phase=changing",
+ ViewIdToString(view->id()).c_str(),
+ RectToString(old_bounds).c_str(),
+ RectToString(new_bounds).c_str()));
+ }
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ changes_.push_back(
+ base::StringPrintf(
+ "view=%s old_bounds=%s new_bounds=%s phase=changed",
+ ViewIdToString(view->id()).c_str(),
+ RectToString(old_bounds).c_str(),
+ RectToString(new_bounds).c_str()));
+ }
+
+ View* view_;
+ Changes changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewObserverTest, SetBounds) {
+ TestView v1;
+ {
+ BoundsChangeObserver observer(&v1);
+ v1.SetBounds(gfx::Rect(0, 0, 100, 100));
+
+ Changes changes = observer.GetAndClearChanges();
+ ASSERT_EQ(2U, changes.size());
+ EXPECT_EQ(
+ "view=0,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100 phase=changing",
+ changes[0]);
+ EXPECT_EQ(
+ "view=0,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100 phase=changed",
+ changes[1]);
+ }
+}
+
+namespace {
+
+class VisibilityChangeObserver : public ViewObserver {
+ public:
+ explicit VisibilityChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ virtual ~VisibilityChangeObserver() { view_->RemoveObserver(this); }
+
+ Changes GetAndClearChanges() {
+ Changes changes;
+ changes.swap(changes_);
+ return changes;
+ }
+
+ private:
+ // Overridden from ViewObserver:
+ virtual void OnViewVisibilityChanging(View* view) OVERRIDE {
+ changes_.push_back(
+ base::StringPrintf("view=%s phase=changing visibility=%s",
+ ViewIdToString(view->id()).c_str(),
+ view->visible() ? "true" : "false"));
+ }
+ virtual void OnViewVisibilityChanged(View* view) OVERRIDE {
+ changes_.push_back(base::StringPrintf("view=%s phase=changed visibility=%s",
+ ViewIdToString(view->id()).c_str(),
+ view->visible() ? "true" : "false"));
+ }
+
+ View* view_;
+ Changes changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewObserverTest, SetVisible) {
+ TestView v1;
+ EXPECT_TRUE(v1.visible());
+ {
+ // Change visibility from true to false and make sure we get notifications.
+ VisibilityChangeObserver observer(&v1);
+ v1.SetVisible(false);
+
+ Changes changes = observer.GetAndClearChanges();
+ ASSERT_EQ(2U, changes.size());
+ EXPECT_EQ("view=0,1 phase=changing visibility=true", changes[0]);
+ EXPECT_EQ("view=0,1 phase=changed visibility=false", changes[1]);
+ }
+ {
+ // Set visible to existing value and verify no notifications.
+ VisibilityChangeObserver observer(&v1);
+ v1.SetVisible(false);
+ EXPECT_TRUE(observer.GetAndClearChanges().empty());
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/services/public/cpp/view_manager/types.h b/mojo/services/public/cpp/view_manager/types.h
new file mode 100644
index 0000000..d72236f
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/types.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_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 {
+
+// Used to identify views and change ids.
+typedef uint32_t Id;
+
+// Used to identify a connection as well as a connection specific view id. For
+// example, the Id for a view consists of the ConnectionSpecificId of the
+// connection and the ConnectionSpecificId of the view.
+typedef uint16_t ConnectionSpecificId;
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_TYPES_H_
diff --git a/mojo/services/public/cpp/view_manager/util.h b/mojo/services/public/cpp/view_manager/util.h
new file mode 100644
index 0000000..72c904d
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/util.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_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 {
+
+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 mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_UTIL_H_
diff --git a/mojo/services/public/cpp/view_manager/view.h b/mojo/services/public/cpp/view_manager/view.h
new file mode 100644
index 0000000..f7afa49
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/view.h
@@ -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.
+
+#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_H_
+#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/public/cpp/view_manager/types.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager_constants.mojom.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/geometry/rect.h"
+
+class SkBitmap;
+
+namespace mojo {
+
+class BitmapUploader;
+class ServiceProviderImpl;
+class View;
+class ViewManager;
+class ViewObserver;
+
+// Views are owned by the ViewManager.
+// TODO(beng): Right now, you'll have to implement a ViewObserver to track
+// destruction and NULL any pointers you have.
+// Investigate some kind of smart pointer or weak pointer for these.
+class View {
+ public:
+ typedef std::vector<View*> Children;
+
+ static View* Create(ViewManager* view_manager);
+
+ // Destroys this view and all its children.
+ void Destroy();
+
+ ViewManager* view_manager() { return manager_; }
+
+ // Configuration.
+ Id id() const { return id_; }
+
+ // Geometric disposition.
+ const gfx::Rect& bounds() const { return bounds_; }
+ void SetBounds(const gfx::Rect& bounds);
+
+ // Visibility (also see IsDrawn()).
+ bool visible() const { return visible_; }
+ void SetVisible(bool value);
+
+ // A View is drawn if the View and all its ancestors are visible and the
+ // View is attached to the root.
+ bool IsDrawn() const;
+
+ // Observation.
+ void AddObserver(ViewObserver* observer);
+ void RemoveObserver(ViewObserver* observer);
+
+ // Tree.
+ View* parent() { return parent_; }
+ const View* parent() const { return parent_; }
+ const Children& children() const { return children_; }
+
+ void AddChild(View* child);
+ void RemoveChild(View* child);
+
+ void Reorder(View* relative, OrderDirection direction);
+ void MoveToFront();
+ void MoveToBack();
+
+ bool Contains(View* child) const;
+
+ View* GetChildById(Id id);
+
+ void SetSurfaceId(SurfaceIdPtr id);
+
+ // TODO(beng): temporary only.
+ void SetContents(const SkBitmap& contents);
+ void SetColor(SkColor color);
+
+ // Focus.
+ void SetFocus();
+
+ // Embedding.
+ void Embed(const String& url);
+ scoped_ptr<ServiceProvider> Embed(
+ const String& url,
+ scoped_ptr<ServiceProviderImpl> exported_services);
+
+ protected:
+ // This class is subclassed only by test classes that provide a public ctor.
+ View();
+ ~View();
+
+ private:
+ friend class ViewPrivate;
+ friend class ViewManagerClientImpl;
+
+ explicit View(ViewManager* manager);
+
+ void LocalDestroy();
+ void LocalAddChild(View* child);
+ void LocalRemoveChild(View* child);
+ // Returns true if the order actually changed.
+ bool LocalReorder(View* relative, OrderDirection direction);
+ void LocalSetBounds(const gfx::Rect& old_bounds, const gfx::Rect& new_bounds);
+ void LocalSetDrawn(bool drawn);
+ void CreateBitmapUploader();
+
+ ViewManager* manager_;
+ Id id_;
+ View* parent_;
+ Children children_;
+
+ ObserverList<ViewObserver> observers_;
+
+ gfx::Rect bounds_;
+
+ bool visible_;
+
+ // Drawn state is derived from the visible state and the parent's visible
+ // state. This field is only used if the view has no parent (eg it's a root).
+ bool drawn_;
+
+ // TODO(jamesr): Temporary, remove when all clients are using surfaces
+ // directly.
+ scoped_ptr<BitmapUploader> bitmap_uploader_;
+
+ DISALLOW_COPY_AND_ASSIGN(View);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_H_
diff --git a/mojo/services/public/cpp/view_manager/view_manager.h b/mojo/services/public/cpp/view_manager/view_manager.h
new file mode 100644
index 0000000..d153283
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/view_manager.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_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"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+
+namespace mojo {
+class View;
+class ViewManagerDelegate;
+class WindowManagerDelegate;
+
+// Encapsulates a connection to the view manager service.
+// A unique connection is made for every unique embed path for an app. e.g. for
+// app B embed by the following paths: A->B, A->C->B - there are two connections
+// and thus two instances of this class.
+class ViewManager {
+ public:
+ // Sets the window manager delegate. Can only be called by the app embedded at
+ // the service root view. Can only be called once.
+ virtual void SetWindowManagerDelegate(
+ WindowManagerDelegate* window_manager_delegate) = 0;
+
+ // Dispatches the supplied event to the specified View. Can be called only
+ // by the application that called SetWindowManagerDelegate().
+ virtual void DispatchEvent(View* target, EventPtr event) = 0;
+
+ // Returns the URL of the application that embedded this application.
+ virtual const std::string& GetEmbedderURL() const = 0;
+
+ // Returns all root views known to this connection.
+ virtual const std::vector<View*>& GetRoots() const = 0;
+
+ // Returns a View known to this connection.
+ virtual View* GetViewById(Id id) = 0;
+
+ protected:
+ virtual ~ViewManager() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_H_
diff --git a/mojo/services/public/cpp/view_manager/view_manager_client_factory.h b/mojo/services/public/cpp/view_manager/view_manager_client_factory.h
new file mode 100644
index 0000000..d2ed548
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/view_manager_client_factory.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_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_CLIENT_FACTORY_H_
+#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_CLIENT_FACTORY_H_
+
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+
+namespace mojo {
+
+class ViewManagerDelegate;
+class Shell;
+
+// Add an instance of this class to an incoming connection to allow it to
+// instantiate ViewManagerClient implementations in response to
+// ViewManagerClient requests.
+class ViewManagerClientFactory : public InterfaceFactory<ViewManagerClient> {
+ public:
+ ViewManagerClientFactory(Shell* shell, ViewManagerDelegate* delegate);
+ virtual ~ViewManagerClientFactory();
+
+ // InterfaceFactory<ViewManagerClient> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerClient> request) override;
+
+ private:
+ Shell* shell_;
+ ViewManagerDelegate* delegate_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_CLIENT_FACTORY_H_
diff --git a/mojo/services/public/cpp/view_manager/view_manager_context.h b/mojo/services/public/cpp/view_manager/view_manager_context.h
new file mode 100644
index 0000000..0bc51a4
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/view_manager_context.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_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_CONTEXT_H_
+#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_CONTEXT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+
+namespace mojo {
+class ApplicationImpl;
+
+class ViewManagerContext {
+ public:
+ explicit ViewManagerContext(ApplicationImpl* application_impl);
+ ~ViewManagerContext();
+
+ // Embed an application @ |url| at an appropriate View.
+ // The first time this method is called in the life time of the View Manager
+ // service the "appropriate View" is defined as being the service' root View.
+ // Subsequent times, the implementation of this method is delegated to the
+ // application embedded at the service root View. This application will have a
+ // specific definition of where within its View hierarchy to embed an
+ // un-parented URL. |exported_services| encapsulates services offered by the
+ // application calling Embed() to the application being embedded. Returns
+ // an object implementing ServiceProvider encapsulating services offered by
+ // the embedded application to the embedder.
+ void Embed(const String& url);
+ scoped_ptr<ServiceProvider> Embed(
+ const String& url,
+ scoped_ptr<ServiceProviderImpl> exported_services);
+
+ private:
+ class InternalState;
+ scoped_ptr<InternalState> state_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerContext);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_CONTEXT_H_
diff --git a/mojo/services/public/cpp/view_manager/view_manager_delegate.h b/mojo/services/public/cpp/view_manager/view_manager_delegate.h
new file mode 100644
index 0000000..bbb9867
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/view_manager_delegate.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_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_DELEGATE_H_
+#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_DELEGATE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace mojo {
+
+class ServiceProviderImpl;
+class View;
+class ViewManager;
+
+// Interface implemented by an application using the view manager.
+class ViewManagerDelegate {
+ public:
+ // Called when the application implementing this interface is embedded at
+ // |root|. |view_manager| is an implementation of an object bound to a
+ // connection to the view manager service. The object is valid until
+ // OnViewManagerDisconnected() is called with the same object.
+ // This function is called for every embed, but there will be a unique
+ // instance of |view_manager| only for every unique connection. See
+ // the class description of ViewManager for some rules about when a new
+ // connection is made.
+ // |exported_services| is an object that the delegate can add services to to
+ // expose to the embedder. |imported_services| exposes the services offered by
+ // the embedder to the delegate. Note that if a different application is
+ // subsequently embedded at |root|, the pipe(s) connecting |imported_services|
+ // to the embedder and any services obtained from it are not broken and will
+ // continue to be valid.
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) = 0;
+
+ // Called when a connection to the view manager service is closed.
+ // |view_manager| is not valid after this function returns.
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) = 0;
+
+ protected:
+ virtual ~ViewManagerDelegate() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_MANAGER_DELEGATE_H_
diff --git a/mojo/services/public/cpp/view_manager/view_observer.h b/mojo/services/public/cpp/view_manager/view_observer.h
new file mode 100644
index 0000000..1a2ab7e
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/view_observer.h
@@ -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.
+
+#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/cpp/view_manager/view.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace mojo {
+
+class View;
+
+// A note on -ing and -ed suffixes:
+//
+// -ing methods are called before changes are applied to the local view model.
+// -ed methods are called after changes are applied to the local view model.
+//
+// If the change originated from another connection to the view manager, it's
+// possible that the change has already been applied to the service-side model
+// prior to being called, so for example in the case of OnViewDestroying(), it's
+// possible the view has already been destroyed on the service side.
+
+class ViewObserver {
+ public:
+ struct TreeChangeParams {
+ TreeChangeParams();
+ View* target;
+ View* old_parent;
+ View* new_parent;
+ View* receiver;
+ };
+
+ virtual void OnTreeChanging(const TreeChangeParams& params) {}
+ virtual void OnTreeChanged(const TreeChangeParams& params) {}
+
+ virtual void OnViewReordering(View* view,
+ View* relative_view,
+ OrderDirection direction) {}
+ virtual void OnViewReordered(View* view,
+ View* relative_view,
+ OrderDirection direction) {}
+
+ virtual void OnViewDestroying(View* view) {}
+ virtual void OnViewDestroyed(View* view) {}
+
+ virtual void OnViewBoundsChanging(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {}
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {}
+
+ virtual void OnViewFocusChanged(View* gained_focus, View* lost_focus) {}
+
+ virtual void OnViewInputEvent(View* view, const EventPtr& event) {}
+
+ virtual void OnViewVisibilityChanging(View* view) {}
+ virtual void OnViewVisibilityChanged(View* view) {}
+
+ // Sent when the drawn state changes. This is only sent for the root nodes
+ // when embedded.
+ virtual void OnViewDrawnChanging(View* view) {}
+ virtual void OnViewDrawnChanged(View* view) {}
+
+ protected:
+ virtual ~ViewObserver() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_OBSERVER_H_
diff --git a/mojo/services/public/cpp/view_manager/window_manager_delegate.h b/mojo/services/public/cpp/view_manager/window_manager_delegate.h
new file mode 100644
index 0000000..0ab271c
--- /dev/null
+++ b/mojo/services/public/cpp/view_manager/window_manager_delegate.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_PUBLIC_CPP_VIEW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
+#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
+
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+
+namespace mojo {
+
+class View;
+
+// A WindowManagerDelegate is provided by the application embedded at the
+// service root view.
+class WindowManagerDelegate {
+ public:
+ // Create an appropriate view to embed |url|.
+ virtual void Embed(const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) {}
+
+ // Dispatch the supplied input event to the appropriate view (taking into
+ // account focus, activation, modality, etc.).
+ virtual void DispatchEvent(EventPtr event) = 0;
+
+ protected:
+ virtual ~WindowManagerDelegate() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
diff --git a/mojo/services/public/interfaces/clipboard/BUILD.gn b/mojo/services/public/interfaces/clipboard/BUILD.gn
new file mode 100644
index 0000000..65dd7d3
--- /dev/null
+++ b/mojo/services/public/interfaces/clipboard/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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_clipboard_bindings
+mojom("clipboard") {
+ sources = [
+ "clipboard.mojom",
+ ]
+}
diff --git a/mojo/services/public/interfaces/clipboard/clipboard.mojom b/mojo/services/public/interfaces/clipboard/clipboard.mojom
new file mode 100644
index 0000000..eca47b9
--- /dev/null
+++ b/mojo/services/public/interfaces/clipboard/clipboard.mojom
@@ -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.
+
+module mojo {
+
+// A wrapper type which is just a Key/Value pair. Workaround until we get
+// proper maps in mojom.
+struct MimeTypePair {
+ string mime_type;
+ array<uint8> data;
+};
+
+interface Clipboard {
+ enum Type {
+ COPY_PASTE = 0,
+ SELECTION = 1,
+ DRAG = 2
+ };
+
+ // Mime type constants
+ const string MIME_TYPE_TEXT = "text/plain";
+ const string MIME_TYPE_HTML = "text/html";
+ const string MIME_TYPE_URL = "text/url";
+
+ // Returns a sequence number which uniquely identifies clipboard state.
+ // Clients are able to assume that the clipboard contents are unchanged as
+ // long as this number has not changed. This number is monotonically
+ // increasing, is increased when the clipboard state changes, and is
+ // provided by Windows, Linux, and Mac.
+ GetSequenceNumber(Type clipboard_type) => (uint64 sequence);
+
+ // Returns the available mime types. (Note: the chrome interface has a
+ // |contains_filenames| parameter here, but it appears to always be set
+ // to false.)
+ GetAvailableMimeTypes(Type clipboard_types) => (array<string> types);
+
+ // Returns the data associated with a Mime type, returning NULL if that data
+ // doesn't exist. Note: because of the inherit raciness of clipboard access,
+ // this may return NULL even if you just verified that it exists with
+ // GetAvailableFormatMimeTypes(). We don't want to provide one API to return
+ // the entire clipboard state because the combined size of the clipboard can
+ // be megabytes, especially when image data is involved.
+ ReadMimeType(Type clipboard_type, string mime_type) => (array<uint8>? data);
+
+ // Writes a set of mime types to the clipboard. This will increment the
+ // sequence number. In the case of an empty or NULL list, this will just
+ // clear the clipboard.
+ WriteClipboardData(Type clipboard_type, array<MimeTypePair>? data);
+};
+
+} // module mojo
diff --git a/mojo/services/public/interfaces/content_handler/BUILD.gn b/mojo/services/public/interfaces/content_handler/BUILD.gn
new file mode 100644
index 0000000..c72cc9a
--- /dev/null
+++ b/mojo/services/public/interfaces/content_handler/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_content_handler_bindings
+mojom("content_handler") {
+ sources = [
+ "content_handler.mojom",
+ ]
+
+ deps = [
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/interfaces/network",
+ ]
+}
diff --git a/mojo/services/public/interfaces/content_handler/content_handler.mojom b/mojo/services/public/interfaces/content_handler/content_handler.mojom
new file mode 100644
index 0000000..4383401
--- /dev/null
+++ b/mojo/services/public/interfaces/content_handler/content_handler.mojom
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium 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/interfaces/application/service_provider.mojom"
+import "mojo/services/public/interfaces/network/url_loader.mojom"
+
+module mojo {
+
+interface ContentHandler {
+ OnConnect(string url,
+ URLResponse response,
+ ServiceProvider&? service_provider);
+};
+
+}
diff --git a/mojo/services/public/interfaces/geometry/BUILD.gn b/mojo/services/public/interfaces/geometry/BUILD.gn
new file mode 100644
index 0000000..3cd9bcd
--- /dev/null
+++ b/mojo/services/public/interfaces/geometry/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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_geometry_bindings
+mojom("geometry") {
+ sources = [
+ "geometry.mojom",
+ ]
+}
diff --git a/mojo/services/public/interfaces/geometry/geometry.mojom b/mojo/services/public/interfaces/geometry/geometry.mojom
new file mode 100644
index 0000000..72e4246
--- /dev/null
+++ b/mojo/services/public/interfaces/geometry/geometry.mojom
@@ -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.
+
+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 RectF {
+ float x;
+ float y;
+ float width;
+ float height;
+};
+
+struct Transform {
+ // Row major order.
+ array<float, 16>? matrix;
+};
+
+}
diff --git a/mojo/services/public/interfaces/gpu/BUILD.gn b/mojo/services/public/interfaces/gpu/BUILD.gn
new file mode 100644
index 0000000..1e56abc
--- /dev/null
+++ b/mojo/services/public/interfaces/gpu/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_gpu_bindings
+mojom("gpu") {
+ sources = [
+ "gpu.mojom",
+ ]
+
+ deps = [
+ "//mojo/services/gles2:interfaces",
+ "//mojo/services/public/interfaces/geometry",
+ ]
+}
diff --git a/mojo/services/public/interfaces/gpu/gpu.mojom b/mojo/services/public/interfaces/gpu/gpu.mojom
new file mode 100644
index 0000000..881c6be
--- /dev/null
+++ b/mojo/services/public/interfaces/gpu/gpu.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 "mojo/services/gles2/command_buffer.mojom"
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+
+module mojo {
+
+interface Gpu {
+ CreateOnscreenGLES2Context(uint64 native_viewport_id, Size? size, CommandBuffer&? gles2_client);
+ CreateOffscreenGLES2Context(CommandBuffer&? gles2_client);
+};
+
+}
diff --git a/mojo/services/public/interfaces/input_events/BUILD.gn b/mojo/services/public/interfaces/input_events/BUILD.gn
new file mode 100644
index 0000000..6c179c6
--- /dev/null
+++ b/mojo/services/public/interfaces/input_events/BUILD.gn
@@ -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("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_input_events_bindings
+mojom("input_events") {
+ sources = [
+ "input_events.mojom",
+ "input_event_constants.mojom",
+ "input_key_codes.mojom",
+ ]
+
+ deps = [
+ "//mojo/services/public/interfaces/geometry",
+ ]
+}
diff --git a/mojo/services/public/interfaces/input_events/input_event_constants.mojom b/mojo/services/public/interfaces/input_events/input_event_constants.mojom
new file mode 100644
index 0000000..0de69aa
--- /dev/null
+++ b/mojo/services/public/interfaces/input_events/input_event_constants.mojom
@@ -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.
+
+module mojo {
+
+// This mirrors ui::EventType
+enum EventType {
+ UNKNOWN ,
+ MOUSE_PRESSED,
+ MOUSE_DRAGGED,
+ MOUSE_RELEASED,
+ MOUSE_MOVED,
+ MOUSE_ENTERED,
+ MOUSE_EXITED,
+ KEY_PRESSED,
+ KEY_RELEASED,
+ MOUSEWHEEL,
+ MOUSE_CAPTURE_CHANGED,
+ TOUCH_RELEASED,
+ TOUCH_PRESSED,
+ TOUCH_MOVED,
+ TOUCH_CANCELLED,
+ DROP_TARGET_EVENT,
+ TRANSLATED_KEY_PRESS,
+ TRANSLATED_KEY_RELEASE,
+ GESTURE_SCROLL_BEGIN,
+ GESTURE_SCROLL_END,
+ GESTURE_SCROLL_UPDATE,
+ GESTURE_TAP,
+ GESTURE_TAP_DOWN,
+ GESTURE_TAP_CANCEL,
+ GESTURE_TAP_UNCONFIRMED,
+ GESTURE_DOUBLE_TAP,
+ GESTURE_BEGIN,
+ GESTURE_END,
+ GESTURE_TWO_FINGER_TAP,
+ GESTURE_PINCH_BEGIN,
+ GESTURE_PINCH_END,
+ GESTURE_PINCH_UPDATE,
+ GESTURE_LONG_PRESS,
+ GESTURE_LONG_TAP,
+ GESTURE_SWIPE,
+ GESTURE_SHOW_PRESS,
+ GESTURE_WIN8_EDGE_SWIPE,
+ SCROLL,
+ SCROLL_FLING_START,
+ SCROLL_FLING_CANCEL,
+ CANCEL_MODE,
+ UMA_DATA
+};
+
+// This mirrors ui::EventFlags
+// TODO(morrita): Use shift operator once it is available.
+enum EventFlags {
+ NONE = 0,
+ CAPS_LOCK_DOWN = 1,
+ SHIFT_DOWN = 2,
+ CONTROL_DOWN = 4,
+ ALT_DOWN = 8,
+ LEFT_MOUSE_BUTTON = 16,
+ MIDDLE_MOUSE_BUTTON = 32,
+ RIGHT_MOUSE_BUTTON = 64,
+ COMMAND_DOWN = 128,
+ EXTENDED = 256,
+ IS_SYNTHESIZED = 512,
+ ALTGR_DOWN = 1024,
+ MOD3_DOWN = 2048
+};
+
+enum MouseEventFlags {
+ IS_DOUBLE_CLICK = 65536,
+ IS_TRIPLE_CLICK = 131072,
+ IS_NON_CLIENT = 262144,
+
+ // TODO(erg): Move accessibility flags and maybe synthetic touch events here.
+};
+
+} // module mojo
diff --git a/mojo/services/public/interfaces/input_events/input_events.mojom b/mojo/services/public/interfaces/input_events/input_events.mojom
new file mode 100644
index 0000000..4067f68
--- /dev/null
+++ b/mojo/services/public/interfaces/input_events/input_events.mojom
@@ -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.
+
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+import "mojo/services/public/interfaces/input_events/input_event_constants.mojom"
+import "mojo/services/public/interfaces/input_events/input_key_codes.mojom"
+
+module mojo {
+
+struct LocationData {
+ Point? in_view_location;
+ Point? screen_location;
+};
+
+struct KeyData {
+ // The chromium event key code; these values are from the ui/ KeyCode enum,
+ // which has the fun property of being neither consistently the Windows key
+ // code, nor the X11 keycodes. (This value is consistent across platforms
+ // for basic ASCII characters; it will differ for modifiers. We don't define
+ // this as a mojo enum because mojom doesn't appear to have a platform
+ // dependent preprocessor yet.)
+ //
+ // TODO(erg): Remove this, and declare Win32 keycodes correct by fiat. We can
+ // not do this until we remove ui::Event usage from within mojo.
+ int32 key_code;
+
+ // Whether this is a character event, and the character value if it is. Note
+ // that this is different than |text|, which holds a value even when there
+ // isn't actually a character to insert. (For example, |text| will be set and
+ // have a value on backspace, and |character| won't.)
+ bool is_char;
+ uint16 character;
+
+ // The Win32 key code. Because of the web, this is the closest thing that we
+ // have to a cross platform key state.
+ KeyboardCode windows_key_code;
+
+ // The platform specific key code.
+ //
+ // TODO(erg): This exists only for NPAPI support, pepper USB keyboard support
+ // and IME on android support. Theoretically, we should be able to remove this
+ // in the medium to long term.
+ int32 native_key_code;
+
+ // The text generated by this keystroke. Corresponds to
+ // blink::WebKeyboardEvent::text.
+ uint16 text;
+
+ // Like |text|, but unmodified by concurrently held modifier keys (except
+ // shift). Corresponds to blink::WebKeyboardEvent::unmodifiedText.
+ uint16 unmodified_text;
+};
+
+struct TouchData {
+ int32 pointer_id;
+};
+
+struct MouseWheelData {
+ int32 x_offset;
+ int32 y_offset;
+};
+
+struct Event {
+ EventType action;
+ EventFlags flags;
+ int64 time_stamp;
+ LocationData? location_data;
+ KeyData? key_data;
+ TouchData? touch_data;
+ MouseWheelData? wheel_data;
+};
+
+}
diff --git a/mojo/services/public/interfaces/input_events/input_key_codes.mojom b/mojo/services/public/interfaces/input_events/input_key_codes.mojom
new file mode 100644
index 0000000..bd955aa
--- /dev/null
+++ b/mojo/services/public/interfaces/input_events/input_key_codes.mojom
@@ -0,0 +1,188 @@
+// Copyright 2014 The Chromium 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 {
+
+// Cross platform keyboard codes.
+//
+// Because the web has standardized on Win32 keyboard codes, so does mojo.
+enum KeyboardCode {
+ BACK = 0x08,
+ TAB = 0x09,
+ CLEAR = 0x0C,
+ RETURN = 0x0D,
+ SHIFT = 0x10,
+ CONTROL = 0x11,
+ MENU = 0x12, // a.k.a. ALT
+ PAUSE = 0x13,
+ CAPITAL = 0x14,
+ KANA = 0x15,
+ HANGUL = 0x15,
+ JUNJA = 0x17,
+ FINAL = 0x18,
+ HANJA = 0x19,
+ KANJI = 0x19,
+ ESCAPE = 0x1B,
+ CONVERT = 0x1C,
+ NONCONVERT = 0x1D,
+ ACCEPT = 0x1E,
+ MODECHANGE = 0x1F,
+ SPACE = 0x20,
+ PRIOR = 0x21,
+ NEXT = 0x22,
+ END = 0x23,
+ HOME = 0x24,
+ LEFT = 0x25,
+ UP = 0x26,
+ RIGHT = 0x27,
+ DOWN = 0x28,
+ SELECT = 0x29,
+ PRINT = 0x2A,
+ EXECUTE = 0x2B,
+ SNAPSHOT = 0x2C,
+ INSERT = 0x2D,
+ DELETE = 0x2E,
+ HELP = 0x2F,
+ NUM_0 = 0x30,
+ NUM_1 = 0x31,
+ NUM_2 = 0x32,
+ NUM_3 = 0x33,
+ NUM_4 = 0x34,
+ NUM_5 = 0x35,
+ NUM_6 = 0x36,
+ NUM_7 = 0x37,
+ NUM_8 = 0x38,
+ NUM_9 = 0x39,
+ A = 0x41,
+ B = 0x42,
+ C = 0x43,
+ D = 0x44,
+ E = 0x45,
+ F = 0x46,
+ G = 0x47,
+ H = 0x48,
+ I = 0x49,
+ J = 0x4A,
+ K = 0x4B,
+ L = 0x4C,
+ M = 0x4D,
+ N = 0x4E,
+ O = 0x4F,
+ P = 0x50,
+ Q = 0x51,
+ R = 0x52,
+ S = 0x53,
+ T = 0x54,
+ U = 0x55,
+ V = 0x56,
+ W = 0x57,
+ X = 0x58,
+ Y = 0x59,
+ Z = 0x5A,
+ LWIN = 0x5B,
+ COMMAND = 0x5B, // Provide the Mac name for convenience.
+ RWIN = 0x5C,
+ APPS = 0x5D,
+ SLEEP = 0x5F,
+ NUMPAD0 = 0x60,
+ NUMPAD1 = 0x61,
+ NUMPAD2 = 0x62,
+ NUMPAD3 = 0x63,
+ NUMPAD4 = 0x64,
+ NUMPAD5 = 0x65,
+ NUMPAD6 = 0x66,
+ NUMPAD7 = 0x67,
+ NUMPAD8 = 0x68,
+ NUMPAD9 = 0x69,
+ MULTIPLY = 0x6A,
+ ADD = 0x6B,
+ SEPARATOR = 0x6C,
+ SUBTRACT = 0x6D,
+ DECIMAL = 0x6E,
+ DIVIDE = 0x6F,
+ F1 = 0x70,
+ F2 = 0x71,
+ F3 = 0x72,
+ F4 = 0x73,
+ F5 = 0x74,
+ F6 = 0x75,
+ F7 = 0x76,
+ F8 = 0x77,
+ F9 = 0x78,
+ F10 = 0x79,
+ F11 = 0x7A,
+ F12 = 0x7B,
+ F13 = 0x7C,
+ F14 = 0x7D,
+ F15 = 0x7E,
+ F16 = 0x7F,
+ F17 = 0x80,
+ F18 = 0x81,
+ F19 = 0x82,
+ F20 = 0x83,
+ F21 = 0x84,
+ F22 = 0x85,
+ F23 = 0x86,
+ F24 = 0x87,
+ NUMLOCK = 0x90,
+ SCROLL = 0x91,
+ LSHIFT = 0xA0,
+ RSHIFT = 0xA1,
+ LCONTROL = 0xA2,
+ RCONTROL = 0xA3,
+ LMENU = 0xA4,
+ RMENU = 0xA5,
+ BROWSER_BACK = 0xA6,
+ BROWSER_FORWARD = 0xA7,
+ BROWSER_REFRESH = 0xA8,
+ BROWSER_STOP = 0xA9,
+ BROWSER_SEARCH = 0xAA,
+ BROWSER_FAVORITES = 0xAB,
+ BROWSER_HOME = 0xAC,
+ VOLUME_MUTE = 0xAD,
+ VOLUME_DOWN = 0xAE,
+ VOLUME_UP = 0xAF,
+ MEDIA_NEXT_TRACK = 0xB0,
+ MEDIA_PREV_TRACK = 0xB1,
+ MEDIA_STOP = 0xB2,
+ MEDIA_PLAY_PAUSE = 0xB3,
+ MEDIA_LAUNCH_MAIL = 0xB4,
+ MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
+ MEDIA_LAUNCH_APP1 = 0xB6,
+ MEDIA_LAUNCH_APP2 = 0xB7,
+
+ OEM_1 = 0xBA,
+ OEM_PLUS = 0xBB,
+ OEM_COMMA = 0xBC,
+ OEM_MINUS = 0xBD,
+ OEM_PERIOD = 0xBE,
+ OEM_2 = 0xBF,
+ OEM_3 = 0xC0,
+ OEM_4 = 0xDB,
+ OEM_5 = 0xDC,
+ OEM_6 = 0xDD,
+ OEM_7 = 0xDE,
+ OEM_8 = 0xDF,
+ OEM_102 = 0xE2,
+ PROCESSKEY = 0xE5,
+ PACKET = 0xE7,
+ DBE_SBCSCHAR = 0xF3,
+ DBE_DBCSCHAR = 0xF4,
+ ATTN = 0xF6,
+ CRSEL = 0xF7,
+ EXSEL = 0xF8,
+ EREOF = 0xF9,
+ PLAY = 0xFA,
+ ZOOM = 0xFB,
+ NONAME = 0xFC,
+ PA1 = 0xFD,
+ OEM_CLEAR = 0xFE,
+ UNKNOWN = 0,
+
+ // Windows does not have a specific key code for AltGr. We use the unused
+ // VK_OEM_AX to represent AltGr, matching the behaviour of Firefox on Linux.
+ ALTGR = 0xE1,
+};
+
+} // module mojo
diff --git a/mojo/services/public/interfaces/native_viewport/BUILD.gn b/mojo/services/public/interfaces/native_viewport/BUILD.gn
new file mode 100644
index 0000000..015fc89
--- /dev/null
+++ b/mojo/services/public/interfaces/native_viewport/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_native_viewport_bindings
+mojom("native_viewport") {
+ sources = [
+ "native_viewport.mojom",
+ ]
+
+ public_deps = [
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/gpu",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/surfaces:surface_id",
+ ]
+}
diff --git a/mojo/services/public/interfaces/native_viewport/native_viewport.mojom b/mojo/services/public/interfaces/native_viewport/native_viewport.mojom
new file mode 100644
index 0000000..6f05b72
--- /dev/null
+++ b/mojo/services/public/interfaces/native_viewport/native_viewport.mojom
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import "mojo/services/gles2/command_buffer.mojom"
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+import "mojo/services/public/interfaces/input_events/input_events.mojom"
+import "mojo/services/public/interfaces/surfaces/surface_id.mojom"
+
+module mojo {
+
+[Client=NativeViewportClient]
+interface NativeViewport {
+ // TODO(sky): having a create function is awkward. Should there be a factory
+ // to create the NativeViewport that takes the size?
+ Create(Size size) => (uint64 native_viewport_id);
+ Show();
+ Hide();
+ Close();
+ SetSize(Size size);
+ SubmittedFrame(SurfaceId surface_id);
+};
+
+interface NativeViewportClient {
+ // OnSizeChanged() is sent at least once after the callback from Create() is
+ // called.
+ OnSizeChanged(Size size);
+ OnDestroyed();
+ OnEvent(Event event) => ();
+};
+
+// Connect to this interface before any other connections are made to configure
+// the NativeViewport service.
+interface NativeViewportConfig {
+ // Call UseTestConfig() and block on the reply. This will ensure that the
+ // correct global initialization is done before subsequent connections.
+ UseTestConfig() => ();
+
+ // Call UseHeadlessConfig() and block on the reply. This will ensure that
+ // the native viewport is later created in headless mode.
+ UseHeadlessConfig() => ();
+};
+
+}
diff --git a/mojo/services/public/interfaces/navigation/BUILD.gn b/mojo/services/public/interfaces/navigation/BUILD.gn
new file mode 100644
index 0000000..73e0578
--- /dev/null
+++ b/mojo/services/public/interfaces/navigation/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2014 The Chromium 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")
+
+# GYP version: mojo/mojo_services.gypi:mojo_navigation_bindings
+mojom("navigation") {
+ sources = [
+ "navigation.mojom",
+ ]
+
+ deps = [
+ "//mojo/services/public/interfaces/network",
+ ]
+}
diff --git a/mojo/services/public/interfaces/navigation/navigation.mojom b/mojo/services/public/interfaces/navigation/navigation.mojom
new file mode 100644
index 0000000..6a9e94f
--- /dev/null
+++ b/mojo/services/public/interfaces/navigation/navigation.mojom
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import "mojo/services/public/interfaces/network/url_loader.mojom"
+
+module mojo {
+
+// Expresses a preference for where a navigation will be performed.
+enum Target {
+ // No preference
+ DEFAULT,
+
+ // In the same ViewManager node that the navigation was initiated
+ SOURCE_NODE,
+
+ // In a new ViewManager node
+ NEW_NODE
+};
+
+// Embedders that support navigation of implement this interface.
+interface NavigatorHost {
+ RequestNavigate(Target target, URLRequest request);
+
+ // Applications call this to inform hosts of navigations they performed
+ // locally. For example, pushState() navigations in an HTML application.
+ DidNavigateLocally(string url);
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/BUILD.gn b/mojo/services/public/interfaces/network/BUILD.gn
new file mode 100644
index 0000000..e55c6aa
--- /dev/null
+++ b/mojo/services/public/interfaces/network/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_network_bindings
+mojom("network") {
+ sources = [
+ "cookie_store.mojom",
+ "net_address.mojom",
+ "network_error.mojom",
+ "network_service.mojom",
+ "tcp_bound_socket.mojom",
+ "tcp_client_socket.mojom",
+ "tcp_server_socket.mojom",
+ "udp_socket.mojom",
+ "url_loader.mojom",
+ "web_socket.mojom",
+ ]
+}
diff --git a/mojo/services/public/interfaces/network/cookie_store.mojom b/mojo/services/public/interfaces/network/cookie_store.mojom
new file mode 100644
index 0000000..76b374a
--- /dev/null
+++ b/mojo/services/public/interfaces/network/cookie_store.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 {
+
+interface CookieStore {
+ Get(string? url) => (string? cookies);
+ Set(string? url, string? cookie) => (bool success);
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/net_address.mojom b/mojo/services/public/interfaces/network/net_address.mojom
new file mode 100644
index 0000000..337f251
--- /dev/null
+++ b/mojo/services/public/interfaces/network/net_address.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.
+
+module mojo {
+
+enum NetAddressFamily {
+ UNSPECIFIED,
+ IPV4,
+ IPV6
+};
+
+// All members are expressed in network byte order, i.e., the most significant
+// byte is stored in the smallest address. For example, if we store 127.0.0.1 in
+// |addr|, 127 should be at index 0.
+struct NetAddressIPv4 {
+ uint16 port;
+ array<uint8, 4> addr;
+};
+
+// All members are expressed in network byte order.
+struct NetAddressIPv6 {
+ uint16 port;
+ array<uint8, 16> addr;
+};
+
+struct NetAddress {
+ NetAddressFamily family = UNSPECIFIED;
+ // At most one of the following fields is non-NULL depending on the value of
+ // |family|.
+ NetAddressIPv4? ipv4;
+ NetAddressIPv6? ipv6;
+};
+
+}
+
diff --git a/mojo/services/public/interfaces/network/network_error.mojom b/mojo/services/public/interfaces/network/network_error.mojom
new file mode 100644
index 0000000..4af9935
--- /dev/null
+++ b/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/mojo/services/public/interfaces/network/network_service.mojom b/mojo/services/public/interfaces/network/network_service.mojom
new file mode 100644
index 0000000..7884144
--- /dev/null
+++ b/mojo/services/public/interfaces/network/network_service.mojom
@@ -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.
+
+import "mojo/services/public/interfaces/network/cookie_store.mojom"
+import "mojo/services/public/interfaces/network/net_address.mojom"
+import "mojo/services/public/interfaces/network/network_error.mojom"
+import "mojo/services/public/interfaces/network/tcp_bound_socket.mojom"
+import "mojo/services/public/interfaces/network/tcp_client_socket.mojom"
+import "mojo/services/public/interfaces/network/url_loader.mojom"
+import "mojo/services/public/interfaces/network/web_socket.mojom"
+
+module mojo {
+
+// TODO Darin suggfests that this should probably be two classes. One for
+// high-level origin-build requests like WebSockets and HTTP, and the other for
+// non-origin-bound low-level stuff like DNS, UDP, and TCP.
+interface NetworkService {
+ CreateURLLoader(URLLoader&? loader);
+
+ GetCookieStore(CookieStore&? cookie_store);
+
+ CreateWebSocket(WebSocket& socket);
+
+ // Creates a TCP socket bound to a given local address. This bound socket
+ // can be used for creating a client or server socket on that local address.
+ //
+ // If you want to create a client socket to connect to a server and are in
+ // the common case where you don't care about the local address it's bound
+ // to, use CreateTCPClientSocket.
+ //
+ // The local address can specify 0 for the port to specify that the OS should
+ // pick an available port for the given address, or it can pass 0 for the
+ // address and port for the OS to pick both the local address and port. In
+ // all success cases, the resulting local address will be passed to the
+ // callback as bound_to.
+ CreateTCPBoundSocket(NetAddress local_address,
+ TCPBoundSocket& bound_socket)
+ => (NetworkError result, NetAddress? bound_to);
+
+ // Creates a client socket connected to the given remote address. A local
+ // address and port will be allocated for the connection and passed to the
+ // callback on success.
+ //
+ // If you want control over the local address and port, instead use
+ // CreateTCPBoundSocket.
+ CreateTCPClientSocket(NetAddress remote_address,
+ handle<data_pipe_consumer> send_stream,
+ handle<data_pipe_producer> receive_stream,
+ TCPClientSocket& client_socket)
+ => (NetworkError result,
+ NetAddress? local_address);
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/tcp_bound_socket.mojom b/mojo/services/public/interfaces/network/tcp_bound_socket.mojom
new file mode 100644
index 0000000..0772846
--- /dev/null
+++ b/mojo/services/public/interfaces/network/tcp_bound_socket.mojom
@@ -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 "mojo/services/public/interfaces/network/net_address.mojom"
+import "mojo/services/public/interfaces/network/network_error.mojom"
+import "mojo/services/public/interfaces/network/tcp_client_socket.mojom"
+import "mojo/services/public/interfaces/network/tcp_server_socket.mojom"
+
+module mojo {
+
+// Represents a TCP socket that is bound to a local address and port, but
+// is not yet in a listening or connected state.
+//
+// A bound socket can be used to create a server socket listening on the
+// local address, or it can be used to create a client socket by connecting to
+// a remote host.
+interface TCPBoundSocket {
+ // Puts the socket into server mode, awaiting incoming connections.
+ //
+ // Once this function is called, neither StartListening nor Connect can be
+ // used on this socket again.
+ StartListening(TCPServerSocket& server);
+
+ // Puts this socket into client mode by connecting to a remote host. If you
+ // do not care about the local address or port, you can call
+ // NetworkService.CreateTCPClientSocket to connect directly and skip the
+ // "bound" state.
+ //
+ // Once this function is called, neither StartListening nor Connect can be
+ // used on this socket again.
+ Connect(NetAddress remote_address,
+ handle<data_pipe_consumer> send_stream,
+ handle<data_pipe_producer> receive_stream,
+ TCPClientSocket& client_socket)
+ => (NetworkError result);
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/tcp_client_socket.mojom b/mojo/services/public/interfaces/network/tcp_client_socket.mojom
new file mode 100644
index 0000000..2dd3544
--- /dev/null
+++ b/mojo/services/public/interfaces/network/tcp_client_socket.mojom
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium 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 {
+
+// Represents a TCP socket connected to a report system.
+//
+// Reading and writing over the connection are done via the data pipe supplied
+// by the caller when creating the socket.
+interface TCPClientSocket {
+ // TODO(brettw) here we put options and whatnot for controlling the
+ // connection.
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/tcp_server_socket.mojom b/mojo/services/public/interfaces/network/tcp_server_socket.mojom
new file mode 100644
index 0000000..4b2e596
--- /dev/null
+++ b/mojo/services/public/interfaces/network/tcp_server_socket.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.
+
+import "mojo/services/public/interfaces/network/net_address.mojom"
+import "mojo/services/public/interfaces/network/network_error.mojom"
+import "mojo/services/public/interfaces/network/tcp_client_socket.mojom"
+
+module mojo {
+
+// Represents a TCP server socket listening for incoming requests.
+[Client=TCPServerSocketClient]
+interface TCPServerSocket {
+ // Accepts an incoming connection request and hooks up a TCPClientSocket for
+ // connecting with the remote host. This function is called in reponse to
+ // OnConnectionAvailable().
+ //
+ // On success, the address of the remote host will be provided.
+ AcceptConnection(handle<data_pipe_consumer> send_stream,
+ handle<data_pipe_producer> receive_stream,
+ TCPClientSocket& client_socket)
+ => (NetworkError result, NetAddress? remote_address);
+};
+
+interface TCPServerSocketClient {
+ // Notifies the client that an incoming connection is available.
+ //
+ // The client should call AcceptConnection() to accept the request.
+ OnConnectionAvailable();
+
+ // TODO(brettw) probably need some error reporting function here.
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/udp_socket.mojom b/mojo/services/public/interfaces/network/udp_socket.mojom
new file mode 100644
index 0000000..b5b848d
--- /dev/null
+++ b/mojo/services/public/interfaces/network/udp_socket.mojom
@@ -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.
+
+import "mojo/services/public/interfaces/network/net_address.mojom"
+import "mojo/services/public/interfaces/network/network_error.mojom"
+
+module mojo {
+
+// UDPSocket and UDPSocketClient represent a UDP socket and its client. The
+// typical flow of using the interfaces is:
+// - Acquire a UDPSocket interface pointer and set a UDPSocketClient instance.
+// - (optional) Set options which are allowed prior to Bind().
+// - Bind the socket.
+// - (optional) Set options which are allowed after Bind().
+// - Send / request to receive packets. Received packets will be delivered to
+// UDPSocketClient.OnReceived().
+[Client=UDPSocketClient]
+interface UDPSocket {
+ // Allows the socket to share the local address to which it will be bound with
+ // other processes. Should be called before Bind().
+ // (This is equivalent to SO_REUSEADDR of the POSIX socket API.)
+ AllowAddressReuse() => (NetworkError result);
+
+ // Binds the socket to the given address.
+ // |bound_addr| is non-NULL on success. It might not be the same as |addr|.
+ // For example, if port 0 is used in |addr|, a random port is picked and
+ // returned in |bound_addr|.
+ Bind(NetAddress addr) => (NetworkError result, NetAddress? bound_addr);
+
+ // Sets the send buffer size (in bytes) for the socket. The socket must be
+ // bound.
+ //
+ // Note: This is only treated as a hint. Even if it succeeds, the service
+ // doesn't guarantee it will conform to the size.
+ SetSendBufferSize(uint32 size) => (NetworkError result);
+
+ // Sets the receive buffer size (in bytes) for the socket. The socket must be
+ // bound.
+ //
+ // Note: This is only treated as a hint. Even if it succeeds, the service
+ // doesn't guarantee it will conform to the size.
+ SetReceiveBufferSize(uint32 size) => (NetworkError result);
+
+ // Notifies that the client is ready to accept |number| of packets.
+ // Correspondingly, OnReceived() of the UDPSocketClient interface will be
+ // called |number| times (errors also count), unless the connection is closed
+ // before that. The socket must be bound.
+ //
+ // It is allowed to call this method again before the previous request is
+ // completely satisfied. For example:
+ // service->ReceiveMorePackets(3);
+ // ...
+ // // OnReceived() is called.
+ // // OnReceived() is called.
+ // ...
+ // service->ReceiveMorePackets(3);
+ // // The client expects 4 more calls to OnReceived().
+ ReceiveMorePackets(uint32 number);
+
+ // Sends data to the specified destination. The socket must be bound.
+ // The method doesn't report the result of the operation.
+ SendToAndForget(NetAddress addr, array<uint8> data);
+
+ // Sends data to the specified destination. The socket must be bound.
+ SendTo(NetAddress addr, array<uint8> data) => (NetworkError result);
+};
+
+interface UDPSocketClient {
+ // |addr| and |data| are non-NULL on success.
+ OnReceived(NetworkError result, NetAddress? addr, array<uint8>? data);
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/url_loader.mojom b/mojo/services/public/interfaces/network/url_loader.mojom
new file mode 100644
index 0000000..61cdc8f
--- /dev/null
+++ b/mojo/services/public/interfaces/network/url_loader.mojom
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium 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/services/public/interfaces/network/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.
+ array<string?>? headers;
+
+ // The payload for the request body, represented as a concatenation of data
+ // streams. For HTTP requests, the method must be set to "POST" or "PUT".
+ array<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;
+
+ // The buffer size of the data pipe returned in URLResponse's |body| member.
+ // A value of 0 indicates that the default buffer size should be used. This
+ // value is just a suggestion. The URLLoader may choose to ignore this value.
+ uint32 response_body_buffer_size = 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 {
+ // If the response resulted in a network level error, this field will be set.
+ NetworkError? error;
+
+ // The response body stream. Read from this data pipe to receive the bytes of
+ // response body.
+ handle<data_pipe_consumer>? body;
+
+ // 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.
+ array<string?>? headers;
+
+ // The MIME type of the response body.
+ string? mime_type;
+
+ // The character set of the response body.
+ string? charset;
+
+ // These fields are set to non-NULL if this response corresponds to a
+ // redirect. Call the |FollowRedirect| method on the URLLoader instance to
+ // follow this redirect.
+ string? redirect_method;
+ string? redirect_url;
+};
+
+struct URLLoaderStatus {
+ // If the loader has failed due to a network level error, this field will be
+ // set.
+ NetworkError? error;
+
+ // Set to true if the URLLoader is still working. Set to false once an error
+ // is encountered or the response body is completely copied to the response
+ // body stream.
+ bool is_loading;
+
+ // TODO(darin): Add further details about the stages of loading (e.g.,
+ // "resolving host") that happen prior to receiving bytes.
+};
+
+interface URLLoader {
+ // Loads the given |request|, asynchronously producing |response|. Consult
+ // |response| to determine if the request resulted in an error, was
+ // redirected, or has a response body to be consumed.
+ Start(URLRequest? request) => (URLResponse? response);
+
+ // If the request passed to |Start| had |auto_follow_redirects| set to false,
+ // then upon receiving an URLResponse with a non-NULL |redirect_url| field,
+ // |FollowRedirect| may be called to load the URL indicated by the redirect.
+ FollowRedirect() => (URLResponse? response);
+
+ // Query status about the URLLoader.
+ QueryStatus() => (URLLoaderStatus? status);
+};
+
+}
diff --git a/mojo/services/public/interfaces/network/web_socket.mojom b/mojo/services/public/interfaces/network/web_socket.mojom
new file mode 100644
index 0000000..d96fb26
--- /dev/null
+++ b/mojo/services/public/interfaces/network/web_socket.mojom
@@ -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.
+
+import "mojo/services/public/interfaces/network/network_error.mojom"
+
+module mojo {
+
+interface WebSocket {
+ enum MessageType {
+ CONTINUATION,
+ TEXT,
+ BINARY
+ };
+ const uint16 kAbnormalCloseCode = 1006; // stolen from websocket_bridge
+
+ // Initiates a WebSocket connection to the given url. |send_stream| is a data
+ // pipe which should remain open for the lifetime of the WebSocket. Data
+ // to send over the WebSocket should be written to the producer end of the
+ // |send_stream|.
+ Connect(string url,
+ array<string> protocols,
+ string origin,
+ handle<data_pipe_consumer> send_stream,
+ WebSocketClient client);
+
+ // Called after writing |num_bytes| worth of data to the WebSocket's
+ // |send_stream|.
+ Send(bool fin, MessageType type, uint32 num_bytes);
+
+ FlowControl(int64 quota);
+
+ Close(uint16 code, string reason);
+};
+
+interface WebSocketClient {
+ // Called in response to a WebSocket.Connect call to indicate success or
+ // failure. |receive_stream| is a data pipe which where incoming data from
+ // the server is written.
+ DidConnect(bool fail,
+ string selected_subprotocol,
+ string extensions,
+ handle<data_pipe_consumer> receive_stream);
+
+ // Called when there is |num_bytes| worth of incoming data available on the
+ // |receive_stream|.
+ DidReceiveData(bool fin, WebSocket.MessageType type, uint32 num_bytes);
+
+ DidReceiveFlowControl(int64 quota);
+
+ DidFail(string message);
+
+ DidClose(bool was_clean, uint16 code, string reason);
+
+ // Blink has 3 extra methods that we don't implement, because they are used
+ // for the inspector:
+ // didStartOpeningHandshake
+ // didFinishOpeningHandshake
+ // didStartClosingHandshake
+};
+
+}
diff --git a/mojo/services/public/interfaces/surfaces/BUILD.gn b/mojo/services/public/interfaces/surfaces/BUILD.gn
new file mode 100644
index 0000000..cd00cd5
--- /dev/null
+++ b/mojo/services/public/interfaces/surfaces/BUILD.gn
@@ -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("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_surfaces_bindings
+mojom("surfaces") {
+ sources = [
+ "quads.mojom",
+ "surfaces.mojom",
+ "surfaces_service.mojom",
+ ]
+
+ deps = [
+ ":surface_id",
+ "//mojo/services/gles2:interfaces",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/native_viewport",
+ ]
+}
+
+
+mojom("surface_id") {
+ sources = [
+ "surface_id.mojom",
+ ]
+}
diff --git a/mojo/services/public/interfaces/surfaces/quads.mojom b/mojo/services/public/interfaces/surfaces/quads.mojom
new file mode 100644
index 0000000..4f51be4
--- /dev/null
+++ b/mojo/services/public/interfaces/surfaces/quads.mojom
@@ -0,0 +1,210 @@
+// Copyright 2014 The Chromium 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/services/public/interfaces/geometry/geometry.mojom"
+import "mojo/services/public/interfaces/surfaces/surface_id.mojom"
+
+module mojo {
+
+struct Color {
+ uint32 rgba;
+};
+
+// TODO(jamesr): Populate subtype fields.
+struct CheckerboardQuadState {};
+
+struct DebugBorderQuadState {};
+
+struct IoSurfaceContentQuadState {};
+
+struct RenderPassId {
+ int32 layer_id;
+ int32 index;
+};
+
+struct RenderPassQuadState {
+ RenderPassId render_pass_id;
+
+ // If nonzero, resource id of mask to use when drawing this pass.
+ uint32 mask_resource_id;
+ RectF mask_uv_rect;
+
+ // Post-processing filters, applied to the pixels in the render pass' texture.
+ // TODO(jamesr): Support
+ // FilterOperations filters;
+
+ // The scale from layer space of the root layer of the render pass to
+ // the render pass physical pixels. This scale is applied to the filter
+ // parameters for pixel-moving filters. This scale should include
+ // content-to-target-space scale, and device pixel ratio.
+ PointF filters_scale;
+
+ // Post-processing filters, applied to the pixels showing through the
+ // background of the render pass, from behind it.
+ // TODO(jamesr): Support
+ // FilterOperations background_filters;
+};
+
+struct SolidColorQuadState {
+ Color color;
+ bool force_anti_aliasing_off;
+};
+
+struct SurfaceQuadState {
+ SurfaceId surface;
+};
+
+struct TextureQuadState {
+ uint32 resource_id;
+ bool premultiplied_alpha;
+ PointF uv_top_left;
+ PointF uv_bottom_right;
+ Color background_color;
+ array<float, 4> vertex_opacity;
+ bool flipped;
+};
+
+struct TileQuadState {
+ RectF tex_coord_rect;
+ Size texture_size;
+ bool swizzle_contents;
+ uint32 resource_id;
+};
+
+struct StreamVideoQuadState {};
+
+enum YUVColorSpace {
+ REC_601, // SDTV standard with restricted "studio swing" color range.
+ REC_601_JPEG, // Full color range [0, 255] variant of the above.
+};
+
+struct YUVVideoQuadState {
+ RectF tex_coord_rect;
+ uint32 y_plane_resource_id;
+ uint32 u_plane_resource_id;
+ uint32 v_plane_resource_id;
+ uint32 a_plane_resource_id;
+ YUVColorSpace color_space;
+};
+
+enum Material {
+ CHECKERBOARD = 1,
+ DEBUG_BORDER,
+ IO_SURFACE_CONTENT,
+ PICTURE_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.
+ Rect rect;
+
+ // This specifies the region of the quad that is opaque. This rect lives in
+ // content space.
+ 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.
+ 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;
+ TileQuadState? tile_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.
+ Transform content_to_target_transform;
+
+ // This size lives in the content space for the quad's originating layer.
+ Size content_bounds;
+
+ // This rect lives in the content space for the quad's originating layer.
+ Rect visible_content_rect;
+
+ // This rect lives in the target content space.
+ Rect clip_rect;
+
+ bool is_clipped;
+ float opacity;
+ SkXfermode blend_mode;
+ int32 sorting_context_id;
+};
+
+struct Pass {
+ int32 id;
+ Rect output_rect;
+ Rect damage_rect;
+ Transform transform_to_root_target;
+ bool has_transparent_background;
+ array<Quad> quads;
+ array<SharedQuadState> shared_quad_states;
+};
+
+}
diff --git a/mojo/services/public/interfaces/surfaces/surface_id.mojom b/mojo/services/public/interfaces/surfaces/surface_id.mojom
new file mode 100644
index 0000000..5a7686d
--- /dev/null
+++ b/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 {
+
+struct SurfaceId {
+ uint64 id;
+};
+
+}
diff --git a/mojo/services/public/interfaces/surfaces/surfaces.mojom b/mojo/services/public/interfaces/surfaces/surfaces.mojom
new file mode 100644
index 0000000..1c5b0b9
--- /dev/null
+++ b/mojo/services/public/interfaces/surfaces/surfaces.mojom
@@ -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.
+
+import "mojo/services/gles2/command_buffer.mojom"
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+import "mojo/services/public/interfaces/surfaces/quads.mojom"
+import "mojo/services/public/interfaces/surfaces/surface_id.mojom"
+
+module mojo {
+
+enum ResourceFormat {
+ RGBA_8888,
+ RGBA_4444,
+ BGRA_8888,
+ ALPHA_8,
+ LUMINANCE_8,
+ RGB_565,
+ ETC1,
+};
+
+struct Mailbox {
+ array<int8, 64> name;
+};
+
+struct MailboxHolder {
+ Mailbox mailbox;
+ uint32 texture_target;
+ uint32 sync_point;
+};
+
+struct TransferableResource {
+ uint32 id;
+ ResourceFormat format;
+ uint32 filter;
+ Size size;
+ MailboxHolder mailbox_holder;
+ bool is_repeated;
+ bool is_software;
+};
+
+struct ReturnedResource {
+ uint32 id;
+ uint32 sync_point;
+ int32 count;
+ bool lost;
+};
+
+struct Frame {
+ array<TransferableResource> resources;
+ array<Pass> passes;
+};
+
+interface SurfaceClient {
+ ReturnResources(array<ReturnedResource> resources);
+};
+
+[Client=SurfaceClient]
+interface Surface {
+ // The id is created by the client and must be unique and contain the
+ // connection's namespace in the upper 32 bits.
+ CreateSurface(SurfaceId id, Size size);
+
+ // The client can only submit frames to surfaces created with this connection.
+ SubmitFrame(SurfaceId id, Frame frame);
+ DestroySurface(SurfaceId id);
+
+ CreateGLES2BoundSurface(CommandBuffer gles2_client,
+ SurfaceId id,
+ Size size);
+};
+
+}
diff --git a/mojo/services/public/interfaces/surfaces/surfaces_service.mojom b/mojo/services/public/interfaces/surfaces/surfaces_service.mojom
new file mode 100644
index 0000000..76faeec
--- /dev/null
+++ b/mojo/services/public/interfaces/surfaces/surfaces_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 "mojo/services/public/interfaces/surfaces/surfaces.mojom"
+
+module mojo {
+
+// Use this interface to request connections to the surfaces service. Each
+// connection is associated with an id namespace
+interface SurfacesService {
+ CreateSurfaceConnection() => (Surface surface, uint32 id_namespace);
+};
+
+}
diff --git a/mojo/services/public/interfaces/view_manager/BUILD.gn b/mojo/services/public/interfaces/view_manager/BUILD.gn
new file mode 100644
index 0000000..0bc0920
--- /dev/null
+++ b/mojo/services/public/interfaces/view_manager/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_view_manager_bindings
+mojom("view_manager") {
+ sources = [
+ "view_manager.mojom",
+ "view_manager_constants.mojom",
+ ]
+
+ deps = [
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/surfaces:surface_id",
+ ]
+}
diff --git a/mojo/services/public/interfaces/view_manager/view_manager.mojom b/mojo/services/public/interfaces/view_manager/view_manager.mojom
new file mode 100644
index 0000000..094f5d3
--- /dev/null
+++ b/mojo/services/public/interfaces/view_manager/view_manager.mojom
@@ -0,0 +1,214 @@
+// Copyright 2014 The Chromium 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/interfaces/application/service_provider.mojom"
+import "mojo/services/public/interfaces/geometry/geometry.mojom"
+import "mojo/services/public/interfaces/input_events/input_events.mojom"
+import "mojo/services/public/interfaces/surfaces/surface_id.mojom"
+import "mojo/services/public/interfaces/view_manager/view_manager_constants.mojom"
+
+module mojo {
+
+struct ViewData {
+ uint32 parent_id;
+ uint32 view_id;
+ mojo.Rect bounds;
+ // True if this view is visible. The view may not be drawn on screen (see
+ // drawn for specifics).
+ bool visible;
+ // True if this view is drawn on screen. A view is drawn if attached to the
+ // root and all ancestors (including this view) are visible.
+ bool drawn;
+};
+
+enum ErrorCode {
+ NONE,
+ VALUE_IN_USE,
+ ILLEGAL_ARGUMENT,
+};
+
+// ViewManagerInitService is used to grant an application a connection to the
+// ViewManager by embedding it at an approriate View.
+interface ViewManagerInitService {
+ // Embed the application @ |url| at an appropriate View.
+ // The first time this method is called in the lifetime of a View Manager
+ // application instance, the "appropriate View" is defined as being the
+ // service root View.
+ // Subsequent times, implementation of this method is delegated to the
+ // application embedded at the service root View. This application is
+ // typically referred to as the "window manager", and will have a specific
+ // definition of where within its View hierarchy to embed an unparented URL.
+ // See ViewManagerService below for more details about |service_provider|.
+ Embed(string url, ServiceProvider? service_provider) => (bool success);
+};
+
+// 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 view is identified with a connection id of 0, and value of 1.
+[Client=ViewManagerClient]
+interface ViewManagerService {
+ // 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.
+ // Errors:
+ // ERROR_CODE_VALUE_IN_USE: a view already exists with the specified id.
+ // ERROR_CODE_ILLEGAL_ARGUMENT: The connection part of |view_id| does not
+ // match the connection id of the client.
+ CreateView(uint32 view_id) => (ErrorCode error_code);
+
+ // Deletes a view. This does not recurse. No hierarchy change notifications
+ // are sent as a result of this. Only the connection that created the view can
+ // delete it.
+ DeleteView(uint32 view_id) => (bool success);
+
+ // Sets the specified bounds of the specified view.
+ SetViewBounds(uint32 view_id, mojo.Rect bounds) => (bool success);
+
+ // Sets the visibility of the specified view to |visible|. Connections are
+ // allowed to change the visibility of any view they have created, as well as
+ // any of their roots.
+ SetViewVisibility(uint32 view_id, bool visible) => (bool success);
+
+ // Reparents a view.
+ // This fails for any of the following reasons:
+ // . |parent| or |child| does not identify a valid view.
+ // . |child| is an ancestor of |parent|.
+ // . |child| is already a child of |parent|.
+ //
+ // This may result in a connection getting OnViewDeleted(). See
+ // RemoveViewFromParent for details.
+ AddView(uint32 parent, uint32 child) => (bool success);
+
+ // Removes a view from its current parent. This fails if the view is not
+ // valid or the view already has no parent.
+ //
+ // Removing a view from a parent may result in OnViewDeleted() being sent to
+ // other connections. For example, connection A has views 1 and 2, with 2 a
+ // child of 1. Connection B has a root 1. If 2 is removed from 1 then B gets
+ // OnViewDeleted(). This is done as view 2 is effectively no longer visible to
+ // connection B.
+ RemoveViewFromParent(uint32 view_id) => (bool success);
+
+ // Reorders a view in its parent, relative to |relative_view_id| according to
+ // |direction|.
+ // Only the connection that created the view's parent can reorder its
+ // children.
+ ReorderView(uint32 view_id,
+ uint32 relative_view_id,
+ OrderDirection direction) => (bool success);
+
+ // Returns the views comprising the tree starting at |view_id|. |view_id| is
+ // the first result in the return value, unless |view_id| is invalid, in which
+ // case an empty vector is returned. The views are visited using a depth first
+ // search (pre-order).
+ GetViewTree(uint32 view_id) => (array<ViewData> views);
+
+ // Shows the surface in the specified view.
+ SetViewSurfaceId(uint32 view_id, SurfaceId surface_id) => (bool success);
+
+ // Embeds the app for |url| in the specified view. More specifically this
+ // creates a new connection to the specified url, expecting to get a
+ // ViewManagerClient and configures it with the root view |view|. Fails
+ // if |view| was not created by this connection.
+ //
+ // A view may only be a root of one connection at a time. Subsequent calls to
+ // Embed() for the same view result in the view being removed from the
+ // current connection. The current connection is told this by way of
+ // OnViewDeleted().
+ //
+ // When a connection embeds an app the connection no longer has priviledges
+ // to access or see any of the children of the view. If the view had existing
+ // children the children are removed. The one exception is the root
+ // connection.
+ //
+ // If |view_id| is 0, the View Manager delegates determination of what view to
+ // embed |url| at to the app embedded at the service root view (i.e. the
+ // window manager).
+ //
+ // |service_provider| encapsulates services offered by the embedder to the
+ // embedded app alongside this Embed() call. It also provides a means for
+ // the embedder to connect to services symmetrically exposed by the embedded
+ // app. Note that if a different app is subsequently embedded at |view_id|
+ // the |service_provider|'s connection to its client in the embedded app and
+ // any services it provided are not broken and continue to be valid.
+ Embed(string url,
+ uint32 view_id,
+ ServiceProvider? service_provider) => (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 views are not sent to the connection that originated the
+// change. For example, if connection 1 changes the bounds of a view by calling
+// SetBounds(), connection 1 does not receive OnViewBoundsChanged().
+[Client=ViewManagerService]
+interface ViewManagerClient {
+ // Invoked when the client application has been embedded at |root|.
+ // See Embed() on ViewManagerService for more details.
+ OnEmbed(uint16 connection_id,
+ string embedder_url,
+ ViewData root,
+ ServiceProvider&? service_provider);
+
+ // Invoked when a view's bounds have changed.
+ OnViewBoundsChanged(uint32 view,
+ 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 view. For example, if the old_parent is NULL, 0 is
+ // supplied.
+ // |views| contains any views that are that the client has not been told
+ // about. This is not sent for hierarchy changes of views not known to this
+ // client or not attached to the tree.
+ OnViewHierarchyChanged(uint32 view,
+ uint32 new_parent,
+ uint32 old_parent,
+ array<ViewData> views);
+
+ // Invoked when the order of views within a parent changes.
+ OnViewReordered(uint32 view_id,
+ uint32 relative_view_id,
+ OrderDirection direction);
+
+ // Invoked when a view is deleted.
+ OnViewDeleted(uint32 view);
+
+ // Invoked when the visibility of the specified view changes.
+ OnViewVisibilityChanged(uint32 view, bool visible);
+
+ // Invoked when a change to the visibility of |view| or one if it's ancestors
+ // is done such that the drawn state changes. This is only invoked for the
+ // top most view of a particular connection. For example, if you have the
+ // hierarchy: A -> B1 -> B2 (B2 is a child of B1 and B1 a child of A), B1/B2
+ // are from connection 2 and A from connection 1 with all views visible and
+ // drawn and the visiblity of A changes to false, then connection 2 is told
+ // the drawn state of B1 has changed (to false), but is not told anything
+ // about B2 as it's drawn state can be calculated from that of B1.
+ //
+ // NOTE: This is not invoked if OnViewVisibilityChanged() is invoked.
+ OnViewDrawnStateChanged(uint32 view, bool drawn);
+
+ // Invoked when an event is targeted at the specified view.
+ OnViewInputEvent(uint32 view, mojo.Event event) => ();
+
+ // TODO(sky): The following methods represent an interface between the view
+ // manager and the application embedded at the service root view
+ // (i.e. the window manager). These methods are not called on
+ // any other clients. They should be moved to a separate interface
+ // once support for derived FIFOs is landed.
+
+ // Requests the window manager create a "top level" view embedding |url|.
+ Embed(string url, ServiceProvider&? service_provider);
+
+ // Requests the view manager dispatch the event.
+ DispatchOnViewInputEvent(mojo.Event event);
+};
+
+}
diff --git a/mojo/services/public/interfaces/view_manager/view_manager_constants.mojom b/mojo/services/public/interfaces/view_manager/view_manager_constants.mojom
new file mode 100644
index 0000000..5d13805
--- /dev/null
+++ b/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 {
+
+enum OrderDirection {
+ ABOVE = 1,
+ BELOW,
+};
+
+}
diff --git a/mojo/services/public/interfaces/window_manager/BUILD.gn b/mojo/services/public/interfaces/window_manager/BUILD.gn
new file mode 100644
index 0000000..ee50bc5
--- /dev/null
+++ b/mojo/services/public/interfaces/window_manager/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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_core_window_manager_bindings
+mojom("window_manager") {
+ sources = [
+ "window_manager.mojom",
+ ]
+}
diff --git a/mojo/services/public/interfaces/window_manager/window_manager.mojom b/mojo/services/public/interfaces/window_manager/window_manager.mojom
new file mode 100644
index 0000000..41deb44
--- /dev/null
+++ b/mojo/services/public/interfaces/window_manager/window_manager.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.
+
+module mojo {
+
+[Client=WindowManagerClient]
+interface WindowManagerService {
+ SetCapture(uint32 view_id) => (bool success);
+ FocusWindow(uint32 view_id) => (bool success);
+ ActivateWindow(uint32 view_id) => (bool success);
+};
+
+[Client=WindowManagerService]
+interface WindowManagerClient {
+ // Called when the window manager is ready to use (in the event a client
+ // connects to it before it has been initialized).
+ OnWindowManagerReady();
+
+ // TODO(beng): how is the WM supposed to know if a view is known to a client
+ // or not?
+ OnCaptureChanged(uint32 old_capture_view_id, uint32 new_capture_view_id);
+
+ OnFocusChanged(uint32 old_focused_node_id, uint32 new_focused_node_id);
+ OnActiveWindowChanged(uint32 old_focused_window, uint32 new_focused_window);
+};
+
+}
diff --git a/mojo/services/surfaces/BUILD.gn b/mojo/services/surfaces/BUILD.gn
new file mode 100644
index 0000000..8c43ca5
--- /dev/null
+++ b/mojo/services/surfaces/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_services.gypi:mojo_surfaces_service
+shared_library("surfaces") {
+ output_name = "mojo_surfaces_service"
+
+ deps = [
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//ui/gfx/geometry",
+ "//mojo/application",
+ "//mojo/cc",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/gles2:for_shared_library",
+ "//mojo/services/gles2:interfaces",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/surfaces",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/surfaces",
+ ]
+
+ sources = [
+ "surfaces_impl.cc",
+ "surfaces_impl.h",
+ "surfaces_service_application.cc",
+ "surfaces_service_application.h",
+ "surfaces_service_impl.cc",
+ "surfaces_service_impl.h",
+ ]
+}
diff --git a/mojo/services/surfaces/DEPS b/mojo/services/surfaces/DEPS
new file mode 100644
index 0000000..3c07200
--- /dev/null
+++ b/mojo/services/surfaces/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+mojo/application",
+ "+mojo/cc",
+ "+mojo/services/gles2",
+ "+mojo/services/public",
+]
diff --git a/mojo/services/surfaces/surfaces_impl.cc b/mojo/services/surfaces/surfaces_impl.cc
new file mode 100644
index 0000000..9d79a7a
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_impl.cc
@@ -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.
+
+#include "mojo/services/surfaces/surfaces_impl.h"
+
+#include "cc/output/compositor_frame.h"
+#include "cc/resources/returned_resource.h"
+#include "cc/surfaces/display.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/cc/context_provider_mojo.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+
+namespace mojo {
+
+SurfacesImpl::SurfacesImpl(cc::SurfaceManager* manager,
+ uint32_t id_namespace,
+ Client* client)
+ : manager_(manager),
+ factory_(manager, this),
+ id_namespace_(id_namespace),
+ client_(client) {
+}
+
+SurfacesImpl::~SurfacesImpl() {
+}
+
+void SurfacesImpl::CreateSurface(SurfaceIdPtr id, mojo::SizePtr size) {
+ cc::SurfaceId cc_id = id.To<cc::SurfaceId>();
+ if (cc::SurfaceIdAllocator::NamespaceForId(cc_id) != id_namespace_) {
+ // Bad message, do something bad to the caller?
+ NOTREACHED();
+ return;
+ }
+ factory_.Create(id.To<cc::SurfaceId>(), size.To<gfx::Size>());
+}
+
+void SurfacesImpl::SubmitFrame(SurfaceIdPtr id, FramePtr frame_ptr) {
+ cc::SurfaceId cc_id = id.To<cc::SurfaceId>();
+ if (cc::SurfaceIdAllocator::NamespaceForId(cc_id) != id_namespace_) {
+ // Bad message, do something bad to the caller?
+ LOG(FATAL) << "Received frame for id " << cc_id.id << " namespace "
+ << cc::SurfaceIdAllocator::NamespaceForId(cc_id)
+ << " should be namespace " << id_namespace_;
+ return;
+ }
+ factory_.SubmitFrame(id.To<cc::SurfaceId>(),
+ frame_ptr.To<scoped_ptr<cc::CompositorFrame> >(),
+ base::Closure());
+ client_->FrameSubmitted();
+}
+
+void SurfacesImpl::DestroySurface(SurfaceIdPtr id) {
+ cc::SurfaceId cc_id = id.To<cc::SurfaceId>();
+ if (cc::SurfaceIdAllocator::NamespaceForId(cc_id) != id_namespace_) {
+ // Bad message, do something bad to the caller?
+ NOTREACHED();
+ return;
+ }
+ factory_.Destroy(id.To<cc::SurfaceId>());
+}
+
+void SurfacesImpl::CreateGLES2BoundSurface(CommandBufferPtr gles2_client,
+ SurfaceIdPtr id,
+ mojo::SizePtr size) {
+ command_buffer_handle_ = gles2_client.PassMessagePipe();
+
+ cc::SurfaceId cc_id = id.To<cc::SurfaceId>();
+ if (cc::SurfaceIdAllocator::NamespaceForId(cc_id) != id_namespace_) {
+ // Bad message, do something bad to the caller?
+ LOG(FATAL) << "Received request for id " << cc_id.id << " namespace "
+ << cc::SurfaceIdAllocator::NamespaceForId(cc_id)
+ << " should be namespace " << id_namespace_;
+ return;
+ }
+ if (!display_) {
+ display_.reset(new cc::Display(this, manager_, NULL));
+ client_->SetDisplay(display_.get());
+ display_->Initialize(make_scoped_ptr(new cc::OutputSurface(
+ new ContextProviderMojo(command_buffer_handle_.Pass()))));
+ }
+ factory_.Create(cc_id, size.To<gfx::Size>());
+ display_->Resize(cc_id, size.To<gfx::Size>());
+}
+
+void SurfacesImpl::ReturnResources(const cc::ReturnedResourceArray& resources) {
+ Array<ReturnedResourcePtr> ret(resources.size());
+ for (size_t i = 0; i < resources.size(); ++i) {
+ ret[i] = ReturnedResource::From(resources[i]);
+ }
+ client()->ReturnResources(ret.Pass());
+}
+
+void SurfacesImpl::DisplayDamaged() {
+}
+
+void SurfacesImpl::DidSwapBuffers() {
+}
+
+void SurfacesImpl::DidSwapBuffersComplete() {
+}
+
+void SurfacesImpl::CommitVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+}
+
+void SurfacesImpl::OutputSurfaceLost() {
+}
+
+void SurfacesImpl::SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) {
+}
+
+} // namespace mojo
diff --git a/mojo/services/surfaces/surfaces_impl.h b/mojo/services/surfaces/surfaces_impl.h
new file mode 100644
index 0000000..fb6ce53
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_impl.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_SERVICES_SURFACES_SURFACES_IMPL_H_
+#define MOJO_SERVICES_SURFACES_SURFACES_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "cc/surfaces/display_client.h"
+#include "cc/surfaces/surface_factory.h"
+#include "cc/surfaces/surface_factory_client.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/services/gles2/command_buffer.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+
+namespace cc {
+class Display;
+}
+
+namespace mojo {
+class ApplicationManager;
+
+class SurfaceNativeViewportClient;
+
+class SurfacesImpl : public InterfaceImpl<Surface>,
+ public cc::SurfaceFactoryClient,
+ public cc::DisplayClient {
+ public:
+ class Client {
+ public:
+ virtual void FrameSubmitted() = 0;
+ virtual void SetDisplay(cc::Display*) = 0;
+ };
+
+ SurfacesImpl(cc::SurfaceManager* manager,
+ uint32_t id_namespace,
+ Client* client);
+ virtual ~SurfacesImpl();
+
+ // Surface implementation.
+ virtual void CreateSurface(SurfaceIdPtr id, mojo::SizePtr size) OVERRIDE;
+ virtual void SubmitFrame(SurfaceIdPtr id, FramePtr frame) OVERRIDE;
+ virtual void DestroySurface(SurfaceIdPtr id) OVERRIDE;
+ virtual void CreateGLES2BoundSurface(CommandBufferPtr gles2_client,
+ SurfaceIdPtr id,
+ mojo::SizePtr size) OVERRIDE;
+
+ // SurfaceFactoryClient implementation.
+ virtual void ReturnResources(
+ const cc::ReturnedResourceArray& resources) OVERRIDE;
+
+ // DisplayClient implementation.
+ virtual void DisplayDamaged() OVERRIDE;
+ virtual void DidSwapBuffers() OVERRIDE;
+ virtual void DidSwapBuffersComplete() OVERRIDE;
+ virtual void CommitVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) OVERRIDE;
+ virtual void OutputSurfaceLost() OVERRIDE;
+ virtual void SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) OVERRIDE;
+
+ cc::SurfaceFactory* factory() { return &factory_; }
+
+ private:
+ cc::SurfaceManager* manager_;
+ cc::SurfaceFactory factory_;
+ uint32_t id_namespace_;
+ Client* client_;
+ scoped_ptr<cc::Display> display_;
+ ScopedMessagePipeHandle command_buffer_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_SURFACES_SURFACES_IMPL_H_
diff --git a/mojo/services/surfaces/surfaces_service_application.cc b/mojo/services/surfaces/surfaces_service_application.cc
new file mode 100644
index 0000000..8677be4
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_service_application.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/services/surfaces/surfaces_service_application.h"
+
+#include "cc/surfaces/display.h"
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/services/surfaces/surfaces_service_impl.h"
+
+namespace mojo {
+
+SurfacesServiceApplication::SurfacesServiceApplication()
+ : next_id_namespace_(1u), display_(NULL), draw_timer_(false, false) {
+}
+
+SurfacesServiceApplication::~SurfacesServiceApplication() {
+}
+
+bool SurfacesServiceApplication::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService(this);
+ return true;
+}
+
+void SurfacesServiceApplication::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<SurfacesService> request) {
+ BindToRequest(new SurfacesServiceImpl(&manager_, &next_id_namespace_, this),
+ &request);
+}
+
+void SurfacesServiceApplication::FrameSubmitted() {
+ if (!draw_timer_.IsRunning() && display_) {
+ draw_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(17),
+ base::Bind(base::IgnoreResult(&cc::Display::Draw),
+ base::Unretained(display_)));
+ }
+}
+
+void SurfacesServiceApplication::SetDisplay(cc::Display* display) {
+ display_ = display;
+}
+
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::SurfacesServiceApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/surfaces/surfaces_service_application.h b/mojo/services/surfaces/surfaces_service_application.h
new file mode 100644
index 0000000..27fd621
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_service_application.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_SURFACES_SURFACES_SERVICE_APPLICATION_H_
+#define MOJO_SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_
+
+#include "base/macros.h"
+#include "base/timer/timer.h"
+#include "cc/surfaces/surface_manager.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "mojo/services/surfaces/surfaces_impl.h"
+
+namespace mojo {
+class ApplicationConnection;
+
+class SurfacesServiceApplication : public ApplicationDelegate,
+ public InterfaceFactory<SurfacesService>,
+ public SurfacesImpl::Client {
+ public:
+ SurfacesServiceApplication();
+ virtual ~SurfacesServiceApplication();
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) OVERRIDE;
+
+ // InterfaceFactory<SurfacsServicee> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<SurfacesService> request) OVERRIDE;
+
+ // SurfacesImpl::Client implementation.
+ virtual void FrameSubmitted() OVERRIDE;
+ virtual void SetDisplay(cc::Display*) OVERRIDE;
+
+ private:
+ cc::SurfaceManager manager_;
+ uint32_t next_id_namespace_;
+ cc::Display* display_;
+ // TODO(jamesr): Integrate with real scheduler.
+ base::Timer draw_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesServiceApplication);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_
diff --git a/mojo/services/surfaces/surfaces_service_impl.cc b/mojo/services/surfaces/surfaces_service_impl.cc
new file mode 100644
index 0000000..919f8a5
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_service_impl.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/services/surfaces/surfaces_service_impl.h"
+
+namespace mojo {
+
+SurfacesServiceImpl::SurfacesServiceImpl(cc::SurfaceManager* manager,
+ uint32_t* next_id_namespace,
+ SurfacesImpl::Client* client)
+ : manager_(manager),
+ next_id_namespace_(next_id_namespace),
+ client_(client) {
+}
+SurfacesServiceImpl::~SurfacesServiceImpl() {
+}
+
+void SurfacesServiceImpl::CreateSurfaceConnection(
+ const Callback<void(SurfacePtr, uint32_t)>& callback) {
+ uint32_t id_namespace = (*next_id_namespace_)++;
+ SurfacePtr surface;
+ BindToProxy(new SurfacesImpl(manager_, id_namespace, client_), &surface);
+ callback.Run(surface.Pass(), id_namespace);
+}
+
+} // namespace mojo
diff --git a/mojo/services/surfaces/surfaces_service_impl.h b/mojo/services/surfaces/surfaces_service_impl.h
new file mode 100644
index 0000000..21dd185
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_service_impl.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_SERVICES_SURFACES_SURFACES_SERVICE_IMPL_H_
+#define MOJO_SERVICES_SURFACES_SURFACES_SERVICE_IMPL_H_
+
+#include "base/macros.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "mojo/services/surfaces/surfaces_impl.h"
+
+namespace cc {
+class SurfaceManager;
+}
+
+namespace mojo {
+
+class SurfacesServiceImpl : public InterfaceImpl<SurfacesService> {
+ public:
+ // The instances pointed to by |manager|, |next_id_namespace| and |client| are
+ // owned by the caller and must outlive the SurfacesServiceImpl instance.
+ SurfacesServiceImpl(cc::SurfaceManager* manager,
+ uint32_t* next_id_namespace,
+ SurfacesImpl::Client* client);
+ virtual ~SurfacesServiceImpl();
+
+ // InterfaceImpl<SurfacesService> implementation.
+ virtual void CreateSurfaceConnection(const mojo::Callback<
+ void(mojo::SurfacePtr, uint32_t)>& callback) OVERRIDE;
+
+ private:
+ cc::SurfaceManager* manager_;
+ uint32_t* next_id_namespace_;
+ SurfacesImpl::Client* client_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesServiceImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_SURFACES_SURFACES_SERVICE_IMPL_H_
diff --git a/mojo/services/test_service/BUILD.gn b/mojo/services/test_service/BUILD.gn
new file mode 100644
index 0000000..3c50ae6
--- /dev/null
+++ b/mojo/services/test_service/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_test_service_bindings
+mojom("bindings") {
+ sources = [
+ "test_request_tracker.mojom",
+ "test_service.mojom",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_test_app
+shared_library("mojo_test_app") {
+ deps = [
+ ":bindings",
+ "//base",
+ "//base:i18n",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/utility",
+ ]
+
+ sources = [
+ "test_request_tracker_client_impl.cc",
+ "test_request_tracker_client_impl.h",
+ "test_service_application.cc",
+ "test_service_application.h",
+ "test_service_impl.cc",
+ "test_service_impl.h",
+ "test_time_service_impl.cc",
+ "test_time_service_impl.h",
+ ]
+}
+
+# GYP version: //mojo/mojo_services.gypi:mojo_test_request_tracker_app
+shared_library("mojo_test_request_tracker_app") {
+ deps = [
+ ":bindings",
+ "//base",
+ "//base:i18n",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/application",
+ "//mojo/public/cpp/application:standalone",
+ "//mojo/public/cpp/utility",
+ ]
+
+ sources = [
+ "test_request_tracker_client_impl.cc",
+ "test_request_tracker_client_impl.h",
+ "test_request_tracker_application.cc",
+ "test_request_tracker_application.h",
+ "test_time_service_impl.cc",
+ "test_time_service_impl.h",
+ "test_request_tracker_impl.cc",
+ "test_request_tracker_impl.h",
+ ]
+}
diff --git a/mojo/services/test_service/test_request_tracker.mojom b/mojo/services/test_service/test_request_tracker.mojom
new file mode 100644
index 0000000..a750031
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker.mojom
@@ -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.
+
+module mojo.test {
+
+// Various counters that services can periodically send to a
+// TestTrackedRequestService for recording.
+struct ServiceStats {
+ uint64 num_new_requests;
+ double health;
+};
+
+// A per-service summary of all the ServiceStats the
+// TestTrackedRequestService has observed.
+struct ServiceReport {
+ string? service_name;
+ uint64 total_requests;
+ double mean_health;
+};
+
+// A simple interface to obtain a "report" from all services that have
+// opted to connect themselves to for request tracking.
+interface TestTrackedRequestService {
+ GetReport() => (array<ServiceReport?>? report);
+};
+
+// TestRequestTracker records ServiceStats for an individual service
+// connection for aggregation in a TestTrackedRequestService.
+[Client=TestRequestTrackerClient]
+interface TestRequestTracker {
+ // Upload a ServiceStats for tracking.
+ RecordStats(uint64 client_id, ServiceStats? stats);
+};
+
+// The client-side contract for uploading ServiceStats to TestRequestTracker.
+interface TestRequestTrackerClient {
+ const uint64 kInvalidId = 0;
+ // Handshake to tell the client its global identifier and get the name.
+ SetIdAndReturnName(uint64 id) => (string? service_name);
+};
+
+} // module mojo.test
diff --git a/mojo/services/test_service/test_request_tracker_application.cc b/mojo/services/test_service/test_request_tracker_application.cc
new file mode 100644
index 0000000..b533377
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_application.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/services/test_service/test_request_tracker_application.h"
+
+#include <assert.h>
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestRequestTrackerApplication::TestRequestTrackerApplication()
+ : test_tracked_request_factory_(&context_),
+ test_request_tracker_factory_(&context_) {
+}
+
+TestRequestTrackerApplication::~TestRequestTrackerApplication() {
+}
+
+bool TestRequestTrackerApplication::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ // Every instance of the service and recorder shares the context.
+ // Note, this app is single-threaded, so this is thread safe.
+ connection->AddService(&test_tracked_request_factory_);
+ connection->AddService(&test_request_tracker_factory_);
+ connection->AddService(this);
+ return true;
+}
+
+void TestRequestTrackerApplication::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) {
+ BindToRequest(new TestTimeServiceImpl(connection), &request);
+}
+
+} // namespace test
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(
+ new mojo::test::TestRequestTrackerApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/test_service/test_request_tracker_application.h b/mojo/services/test_service/test_request_tracker_application.h
new file mode 100644
index 0000000..b519a91
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_application.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_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_
+#define MOJO_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_request_tracker_impl.h"
+
+namespace mojo {
+namespace test {
+class TestTimeService;
+
+// Embeds TestRequestTracker mojo services into an application.
+class TestRequestTrackerApplication : public ApplicationDelegate,
+ public InterfaceFactory<TestTimeService> {
+ public:
+ TestRequestTrackerApplication();
+ virtual ~TestRequestTrackerApplication();
+
+ // ApplicationDelegate methods:
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override;
+
+ // InterfaceFactory<TestTimeService> methods:
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) override;
+
+ private:
+ TrackingContext context_;
+ typedef InterfaceFactoryImplWithContext<TestTrackedRequestServiceImpl,
+ TrackingContext>
+ TestTrackedRequestFactory;
+ TestTrackedRequestFactory test_tracked_request_factory_;
+ typedef InterfaceFactoryImplWithContext<TestRequestTrackerImpl,
+ TrackingContext>
+ TestRequestTrackerFactory;
+ TestRequestTrackerFactory test_request_tracker_factory_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestRequestTrackerApplication);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_
diff --git a/mojo/services/test_service/test_request_tracker_client_impl.cc b/mojo/services/test_service/test_request_tracker_client_impl.cc
new file mode 100644
index 0000000..c9e66c3
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_client_impl.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/services/test_service/test_request_tracker_client_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestRequestTrackerClientImpl::TestRequestTrackerClientImpl(
+ TestRequestTrackerPtr tracker,
+ const std::string& service_name,
+ const mojo::Callback<void()>& callback)
+ : id_(0),
+ requests_since_upload_(0),
+ service_name_(service_name),
+ tracker_(tracker.Pass()),
+ tracking_connected_callback_(callback) {
+ tracker_.set_client(this);
+}
+
+TestRequestTrackerClientImpl::~TestRequestTrackerClientImpl() {
+}
+
+void TestRequestTrackerClientImpl::RecordNewRequest() {
+ requests_since_upload_++;
+ if (id_ == kInvalidId)
+ return;
+ SendStats();
+}
+
+void TestRequestTrackerClientImpl::SendStats() {
+ ServiceStatsPtr stats(ServiceStats::New());
+ stats->num_new_requests = requests_since_upload_;
+ stats->health = 0.7;
+ tracker_->RecordStats(id_, stats.Pass());
+ requests_since_upload_ = 0;
+}
+
+void TestRequestTrackerClientImpl::SetIdAndReturnName(
+ uint64_t id,
+ const mojo::Callback<void(mojo::String)>& callback) {
+ assert(id != kInvalidId);
+ assert(id_ == kInvalidId);
+ id_ = id;
+ callback.Run(service_name_);
+ tracking_connected_callback_.Run();
+ if (requests_since_upload_ == 0)
+ return;
+ SendStats();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_request_tracker_client_impl.h b/mojo/services/test_service/test_request_tracker_client_impl.h
new file mode 100644
index 0000000..91340cb
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_client_impl.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_TEST_SERVICE_TEST_REQUEST_TRACKER_CLIENT_IMPL_H_
+#define MOJO_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_CLIENT_IMPL_H_
+
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+
+namespace mojo {
+namespace test {
+
+class TestRequestTrackerClientImpl : public TestRequestTrackerClient {
+ public:
+ TestRequestTrackerClientImpl(
+ TestRequestTrackerPtr tracker,
+ const std::string& service_name,
+ const mojo::Callback<void()>& tracking_connected_callback);
+ virtual ~TestRequestTrackerClientImpl();
+
+ // Call whenever an event happens that you want to be recorded.
+ void RecordNewRequest();
+
+ // TestRequestTrackerClient impl.
+ virtual void SetIdAndReturnName(
+ uint64_t id,
+ const mojo::Callback<void(mojo::String)>& callback) override;
+
+ private:
+ void SendStats();
+ uint64_t id_;
+ uint64_t requests_since_upload_;
+ const std::string service_name_;
+ TestRequestTrackerPtr tracker_;
+ mojo::Callback<void()> tracking_connected_callback_;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_CLIENT_IMPL_H_
diff --git a/mojo/services/test_service/test_request_tracker_impl.cc b/mojo/services/test_service/test_request_tracker_impl.cc
new file mode 100644
index 0000000..9af6266
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_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 "base/bind.h"
+#include "mojo/services/test_service/test_request_tracker_impl.h"
+
+namespace mojo {
+namespace test {
+
+ TrackingContext::TrackingContext() : next_id(1) {}
+ TrackingContext::~TrackingContext() {}
+
+ TestRequestTrackerImpl::TestRequestTrackerImpl(TrackingContext* context)
+ : context_(context), weak_factory_(this) {
+ }
+
+ TestRequestTrackerImpl::~TestRequestTrackerImpl() {
+ }
+
+void TestRequestTrackerImpl::RecordStats(
+ uint64_t client_id,
+ ServiceStatsPtr stats) {
+ assert(context_->ids_to_names.find(client_id) !=
+ context_->ids_to_names.end());
+ context_->records[client_id].push_back(*stats);
+}
+
+void TestRequestTrackerImpl::OnConnectionEstablished() {
+ uint64_t id = context_->next_id++;
+ client()->SetIdAndReturnName(id,
+ base::Bind(&TestRequestTrackerImpl::UploaderNameCallback,
+ weak_factory_.GetWeakPtr(),
+ id));
+}
+
+void TestRequestTrackerImpl::UploaderNameCallback(
+ uint64_t id, const mojo::String& name) {
+ DCHECK(context_->ids_to_names.find(id) == context_->ids_to_names.end());
+ context_->ids_to_names[id] = name;
+}
+
+TestTrackedRequestServiceImpl::TestTrackedRequestServiceImpl(
+ TrackingContext* context)
+ : context_(context) {
+}
+
+TestTrackedRequestServiceImpl::~TestTrackedRequestServiceImpl() {
+}
+
+void TestTrackedRequestServiceImpl::GetReport(
+ const mojo::Callback<void(mojo::Array<ServiceReportPtr>)>& callback) {
+ mojo::Array<ServiceReportPtr> reports;
+ for (AllRecordsMap::const_iterator it1 = context_->records.begin();
+ it1 != context_->records.end(); ++it1) {
+ ServiceReportPtr report(ServiceReport::New());
+ report->service_name = context_->ids_to_names[it1->first];
+ double mean_health_numerator = 0;
+ size_t num_samples = it1->second.size();
+ if (num_samples == 0)
+ continue;
+
+ for (std::vector<ServiceStats>::const_iterator it2 = it1->second.begin();
+ it2 != it1->second.end(); ++it2) {
+ report->total_requests += it2->num_new_requests;
+ mean_health_numerator += it2->health;
+ }
+ report->mean_health = mean_health_numerator / num_samples;
+ reports.push_back(report.Pass());
+ }
+ callback.Run(reports.Pass());
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_request_tracker_impl.h b/mojo/services/test_service/test_request_tracker_impl.h
new file mode 100644
index 0000000..736cc70
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_impl.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium 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_REQUEST_TRACKER_IMPL_H_
+#define MOJO_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+
+namespace mojo {
+class ApplicationConnection;
+namespace test {
+
+typedef std::map<uint64_t, std::vector<ServiceStats> > AllRecordsMap;
+
+// Shared state between all instances of TestRequestTrackerImpl
+// and the master TrackedRequestService.
+struct TrackingContext {
+ TrackingContext();
+ ~TrackingContext();
+ AllRecordsMap records;
+ std::map<uint64_t, std::string> ids_to_names;
+ uint64_t next_id;
+};
+
+class TestRequestTrackerImpl : public InterfaceImpl<TestRequestTracker> {
+ public:
+ explicit TestRequestTrackerImpl(TrackingContext* context);
+ virtual ~TestRequestTrackerImpl();
+
+ // TestRequestTracker.
+ virtual void RecordStats(uint64_t client_id, ServiceStatsPtr stats) override;
+
+ // InterfaceImpl override.
+ virtual void OnConnectionEstablished() override;
+
+ private:
+ void UploaderNameCallback(uint64_t id, const mojo::String& name);
+ TrackingContext* context_;
+ base::WeakPtrFactory<TestRequestTrackerImpl> weak_factory_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestRequestTrackerImpl);
+};
+
+class TestTrackedRequestServiceImpl
+ : public InterfaceImpl<TestTrackedRequestService> {
+ public:
+ explicit TestTrackedRequestServiceImpl(TrackingContext* context);
+ virtual ~TestTrackedRequestServiceImpl();
+
+ // |TestTrackedRequestService| implementation.
+ virtual void GetReport(
+ const mojo::Callback<void(mojo::Array<ServiceReportPtr>)>& callback)
+ override;
+
+ private:
+ TrackingContext* context_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestTrackedRequestServiceImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_
diff --git a/mojo/services/test_service/test_service.mojom b/mojo/services/test_service/test_service.mojom
new file mode 100644
index 0000000..bda3b68
--- /dev/null
+++ b/mojo/services/test_service/test_service.mojom
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo.test {
+
+interface TestService {
+ Ping() => ();
+ // Connect to a TestTimeService at |app_url| and ferry the data back
+ // in |response|.
+ ConnectToAppAndGetTime(string? app_url) => (int64 time_usec);
+ StartTrackingRequests() => ();
+};
+
+interface TestTimeService {
+ // Provides a constant time value.
+ GetPartyTime() => (int64 time_usec);
+ StartTrackingRequests() => ();
+};
+
+} // module mojo.test
diff --git a/mojo/services/test_service/test_service_application.cc b/mojo/services/test_service/test_service_application.cc
new file mode 100644
index 0000000..f08e183
--- /dev/null
+++ b/mojo/services/test_service/test_service_application.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/services/test_service/test_service_application.h"
+
+#include <assert.h>
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/services/test_service/test_service_impl.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestServiceApplication::TestServiceApplication() : ref_count_(0) {
+}
+
+TestServiceApplication::~TestServiceApplication() {
+}
+
+bool TestServiceApplication::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService<TestService>(this);
+ connection->AddService<TestTimeService>(this);
+ return true;
+}
+
+void TestServiceApplication::Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) {
+ BindToRequest(new TestServiceImpl(connection, this), &request);
+}
+
+void TestServiceApplication::Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) {
+ BindToRequest(new TestTimeServiceImpl(connection), &request);
+}
+
+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
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::test::TestServiceApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/test_service/test_service_application.h b/mojo/services/test_service/test_service_application.h
new file mode 100644
index 0000000..47743a1
--- /dev/null
+++ b/mojo/services/test_service/test_service_application.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_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_
+#define MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+class ApplicationConnection;
+
+namespace test {
+class TestService;
+class TestTimeService;
+
+class TestServiceApplication : public ApplicationDelegate,
+ public InterfaceFactory<TestService>,
+ public InterfaceFactory<TestTimeService> {
+ public:
+ TestServiceApplication();
+ virtual ~TestServiceApplication();
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override;
+
+ // InterfaceFactory<TestService> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) override;
+
+ // InterfaceFactory<TestTimeService> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) 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/mojo/services/test_service/test_service_impl.cc b/mojo/services/test_service/test_service_impl.cc
new file mode 100644
index 0000000..9e589a5
--- /dev/null
+++ b/mojo/services/test_service/test_service_impl.cc
@@ -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.
+
+#include "mojo/services/test_service/test_service_impl.h"
+
+#include "base/bind.h"
+#include "base/i18n/time_formatting.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/services/test_service/test_request_tracker_client_impl.h"
+#include "mojo/services/test_service/test_service_application.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestServiceImpl::TestServiceImpl(ApplicationConnection* connection,
+ TestServiceApplication* application)
+ : application_(application), connection_(connection) {
+}
+
+TestServiceImpl::~TestServiceImpl() {
+}
+
+void TestServiceImpl::OnConnectionEstablished() {
+ application_->AddRef();
+}
+
+void TestServiceImpl::OnConnectionError() {
+ application_->ReleaseRef();
+}
+
+void TestServiceImpl::Ping(const mojo::Callback<void()>& callback) {
+ if (tracking_)
+ tracking_->RecordNewRequest();
+ callback.Run();
+}
+
+void SendTimeResponse(
+ const mojo::Callback<void(int64_t)>& requestor_callback,
+ int64_t time_usec) {
+ requestor_callback.Run(time_usec);
+}
+
+void TestServiceImpl::ConnectToAppAndGetTime(
+ const mojo::String& app_url,
+ const mojo::Callback<void(int64_t)>& callback) {
+ connection_->ConnectToService(app_url, &time_service_);
+ if (tracking_) {
+ tracking_->RecordNewRequest();
+ time_service_->StartTrackingRequests(mojo::Callback<void()>());
+ }
+ time_service_->GetPartyTime(base::Bind(&SendTimeResponse, callback));
+}
+
+void TestServiceImpl::StartTrackingRequests(
+ const mojo::Callback<void()>& callback) {
+ TestRequestTrackerPtr tracker;
+ connection_->ConnectToService(
+ "mojo:mojo_test_request_tracker_app", &tracker);
+ tracking_.reset(
+ new TestRequestTrackerClientImpl(tracker.Pass(), Name_, callback));
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_service_impl.h b/mojo/services/test_service/test_service_impl.h
new file mode 100644
index 0000000..379f02e
--- /dev/null
+++ b/mojo/services/test_service/test_service_impl.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_SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_
+#define MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_service.mojom.h"
+
+namespace mojo {
+class ApplicationConnection;
+namespace test {
+
+class TestRequestTrackerClientImpl;
+class TestServiceApplication;
+
+class TestServiceImpl : public InterfaceImpl<TestService> {
+ public:
+ TestServiceImpl(ApplicationConnection* connection,
+ TestServiceApplication* application);
+ virtual ~TestServiceImpl();
+
+ // |TestService| methods:
+ virtual void OnConnectionEstablished() override;
+ virtual void OnConnectionError() override;
+ virtual void Ping(const mojo::Callback<void()>& callback) override;
+ virtual void ConnectToAppAndGetTime(
+ const mojo::String& app_url,
+ const mojo::Callback<void(int64_t)>& callback) override;
+ virtual void StartTrackingRequests(
+ const mojo::Callback<void()>& callback) override;
+
+ private:
+ TestServiceApplication* const application_;
+ ApplicationConnection* const connection_;
+ TestTimeServicePtr time_service_;
+ scoped_ptr<TestRequestTrackerClientImpl> tracking_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_
diff --git a/mojo/services/test_service/test_time_service_impl.cc b/mojo/services/test_service/test_time_service_impl.cc
new file mode 100644
index 0000000..f4adbaa
--- /dev/null
+++ b/mojo/services/test_service/test_time_service_impl.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/time/time.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+#include "mojo/services/test_service/test_request_tracker_client_impl.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestTimeServiceImpl::TestTimeServiceImpl(ApplicationConnection* application)
+ : application_(application) {
+}
+
+TestTimeServiceImpl::~TestTimeServiceImpl() {
+}
+
+void TestTimeServiceImpl::StartTrackingRequests(
+ const mojo::Callback<void()>& callback) {
+ TestRequestTrackerPtr tracker;
+ application_->ConnectToService(
+ "mojo:mojo_test_request_tracker_app", &tracker);
+ tracking_.reset(new TestRequestTrackerClientImpl(
+ tracker.Pass(), Name_, callback));
+}
+
+void TestTimeServiceImpl::GetPartyTime(
+ const mojo::Callback<void(int64_t)>& callback) {
+ if (tracking_)
+ tracking_->RecordNewRequest();
+ base::Time frozen_time(base::Time::UnixEpoch()
+ + base::TimeDelta::FromDays(10957)
+ + base::TimeDelta::FromHours(7)
+ + base::TimeDelta::FromMinutes(59));
+ int64 time(frozen_time.ToInternalValue());
+ callback.Run(time);
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_time_service_impl.h b/mojo/services/test_service/test_time_service_impl.h
new file mode 100644
index 0000000..c9eaea2
--- /dev/null
+++ b/mojo/services/test_service/test_time_service_impl.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_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_
+#define MOJO_SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_service.mojom.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace test {
+
+class TestRequestTrackerClientImpl;
+
+class TestTimeServiceImpl : public InterfaceImpl<TestTimeService> {
+ public:
+ explicit TestTimeServiceImpl(ApplicationConnection* application);
+ virtual ~TestTimeServiceImpl();
+
+ // |TestTimeService| methods:
+ virtual void GetPartyTime(
+ const mojo::Callback<void(int64_t time_usec)>& callback) override;
+ virtual void StartTrackingRequests(
+ const mojo::Callback<void()>& callback) override;
+
+ private:
+ ApplicationConnection* application_;
+ scoped_ptr<TestRequestTrackerClientImpl> tracking_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestTimeServiceImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // MOJO_SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_
diff --git a/mojo/services/view_manager/BUILD.gn b/mojo/services/view_manager/BUILD.gn
new file mode 100644
index 0000000..ddb76e2
--- /dev/null
+++ b/mojo/services/view_manager/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+# GYP version: mojo/mojo_services.gypi:mojo_view_manager
+shared_library("view_manager") {
+ output_name = "mojo_view_manager"
+
+ deps = [
+ "//base",
+ "//cc/surfaces",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/surfaces",
+ "//mojo/services/public/interfaces/geometry",
+ "//mojo/services/public/interfaces/input_events",
+ "//mojo/services/public/interfaces/native_viewport",
+ "//mojo/services/public/interfaces/surfaces",
+ "//mojo/services/public/interfaces/view_manager",
+ "//ui/base",
+ "//ui/events",
+ "//ui/events:events_base",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+
+ defines = [
+ "MOJO_VIEW_MANAGER_IMPLEMENTATION",
+ ]
+
+ sources = [
+ "access_policy.h",
+ "access_policy_delegate.h",
+ "connection_manager.cc",
+ "connection_manager.h",
+ "default_access_policy.cc",
+ "default_access_policy.h",
+ "display_manager.cc",
+ "display_manager.h",
+ "main.cc",
+ "server_view.cc",
+ "server_view.h",
+ "server_view_delegate.h",
+ "view_manager_export.h",
+ "view_manager_init_service_context.cc",
+ "view_manager_init_service_context.h",
+ "view_manager_init_service_impl.cc",
+ "view_manager_init_service_impl.h",
+ "view_manager_service_impl.cc",
+ "view_manager_service_impl.h",
+ "window_manager_access_policy.cc",
+ "window_manager_access_policy.h",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_view_manager_unittests
+test("mojo_view_manager_unittests") {
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/application",
+ "//mojo/application_manager",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/public/cpp/geometry",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/cpp/view_manager/lib:run_unittests",
+ "//mojo/shell:test_support",
+ "//testing/gtest",
+ "//ui/gfx/geometry",
+ ]
+
+ if (use_x11) {
+ deps += ["//ui/gfx/x"]
+ }
+
+ if (is_component_build) {
+ deps += ["//ui/gl"]
+ }
+
+ sources = [
+ "test_change_tracker.cc",
+ "test_change_tracker.h",
+ "view_manager_unittest.cc",
+ ]
+}
diff --git a/mojo/services/view_manager/DEPS b/mojo/services/view_manager/DEPS
new file mode 100644
index 0000000..d91b8be
--- /dev/null
+++ b/mojo/services/view_manager/DEPS
@@ -0,0 +1,23 @@
+include_rules = [
+ "+cc",
+ "-cc/blink",
+ "+gpu/command_buffer/service/mailbox_manager.h",
+ "+mojo/application",
+ "+mojo/cc",
+ "+mojo/geometry",
+ "+mojo/services",
+ "+third_party/skia",
+ "+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/application_manager/application_manager.h",
+ ],
+}
diff --git a/mojo/services/view_manager/access_policy.h b/mojo/services/view_manager/access_policy.h
new file mode 100644
index 0000000..cac5579
--- /dev/null
+++ b/mojo/services/view_manager/access_policy.h
@@ -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.
+
+#ifndef MOJO_SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_
+#define MOJO_SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_
+
+#include "mojo/services/public/interfaces/view_manager/view_manager_constants.mojom.h"
+#include "mojo/services/view_manager/ids.h"
+
+namespace mojo {
+namespace service {
+
+class ServerView;
+
+// AccessPolicy is used by ViewManagerServiceImpl to determine what a connection
+// is allowed to do.
+class AccessPolicy {
+ public:
+ virtual ~AccessPolicy() {}
+
+ // Unless otherwise mentioned all arguments have been validated. That is the
+ // |view| arguments are non-null unless otherwise stated (eg CanSetView() is
+ // allowed to take a NULL view).
+ virtual bool CanRemoveViewFromParent(const ServerView* view) const = 0;
+ virtual bool CanAddView(const ServerView* parent,
+ const ServerView* child) const = 0;
+ virtual bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const = 0;
+ virtual bool CanDeleteView(const ServerView* view) const = 0;
+ virtual bool CanGetViewTree(const ServerView* view) const = 0;
+ // Used when building a view tree (GetViewTree()) to decide if we should
+ // descend into |view|.
+ virtual bool CanDescendIntoViewForViewTree(const ServerView* view) const = 0;
+ virtual bool CanEmbed(const ServerView* view) const = 0;
+ virtual bool CanChangeViewVisibility(const ServerView* view) const = 0;
+ virtual bool CanSetViewSurfaceId(const ServerView* view) const = 0;
+ virtual bool CanSetViewBounds(const ServerView* view) const = 0;
+
+ // Returns whether the connection should notify on a hierarchy change.
+ // |new_parent| and |old_parent| are initially set to the new and old parents
+ // but may be altered so that the client only sees a certain set of views.
+ virtual bool ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const = 0;
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_
diff --git a/mojo/services/view_manager/access_policy_delegate.h b/mojo/services/view_manager/access_policy_delegate.h
new file mode 100644
index 0000000..12c7479
--- /dev/null
+++ b/mojo/services/view_manager/access_policy_delegate.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_SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_
+#define MOJO_SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_
+
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "mojo/services/view_manager/ids.h"
+
+namespace mojo {
+namespace service {
+
+class ServerView;
+
+// Delegate used by the AccessPolicy implementations to get state.
+class AccessPolicyDelegate {
+ public:
+ // Returns the ids of the roots views for this connection. That is, this is
+ // the set of views the connection was embedded at.
+ virtual const base::hash_set<Id>& GetRootsForAccessPolicy() const = 0;
+
+ // Returns true if |view| has been exposed to the client.
+ virtual bool IsViewKnownForAccessPolicy(const ServerView* view) const = 0;
+
+ // Returns true if Embed(view) has been invoked on |view|.
+ virtual bool IsViewRootOfAnotherConnectionForAccessPolicy(
+ const ServerView* view) const = 0;
+
+ protected:
+ virtual ~AccessPolicyDelegate() {}
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_
diff --git a/mojo/services/view_manager/connection_manager.cc b/mojo/services/view_manager/connection_manager.cc
new file mode 100644
index 0000000..6d9fe2a
--- /dev/null
+++ b/mojo/services/view_manager/connection_manager.cc
@@ -0,0 +1,297 @@
+// Copyright 2014 The Chromium 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/connection_manager.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/public/cpp/input_events/input_events_type_converters.h"
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+
+namespace mojo {
+namespace service {
+
+ConnectionManager::ScopedChange::ScopedChange(
+ ViewManagerServiceImpl* connection,
+ ConnectionManager* connection_manager,
+ bool is_delete_view)
+ : connection_manager_(connection_manager),
+ connection_id_(connection->id()),
+ is_delete_view_(is_delete_view) {
+ connection_manager_->PrepareForChange(this);
+}
+
+ConnectionManager::ScopedChange::~ScopedChange() {
+ connection_manager_->FinishChange();
+}
+
+ConnectionManager::ConnectionManager(
+ ApplicationConnection* app_connection,
+ const Callback<void()>& native_viewport_closed_callback)
+ : app_connection_(app_connection),
+ next_connection_id_(1),
+ display_manager_(app_connection,
+ this,
+ native_viewport_closed_callback),
+ root_(new ServerView(this, RootViewId())),
+ current_change_(NULL) {
+ root_->SetBounds(gfx::Rect(800, 600));
+}
+
+ConnectionManager::~ConnectionManager() {
+ while (!connections_created_by_connect_.empty())
+ delete *(connections_created_by_connect_.begin());
+ // All the connections should have been destroyed.
+ DCHECK(connection_map_.empty());
+ root_.reset();
+}
+
+ConnectionSpecificId ConnectionManager::GetAndAdvanceNextConnectionId() {
+ const ConnectionSpecificId id = next_connection_id_++;
+ DCHECK_LT(id, next_connection_id_);
+ return id;
+}
+
+void ConnectionManager::AddConnection(ViewManagerServiceImpl* connection) {
+ DCHECK_EQ(0u, connection_map_.count(connection->id()));
+ connection_map_[connection->id()] = connection;
+}
+
+void ConnectionManager::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 ConnectionManager::EmbedRoot(
+ const std::string& url,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ if (connection_map_.empty()) {
+ EmbedImpl(kInvalidConnectionId,
+ String::From(url),
+ RootViewId(),
+ service_provider.Pass());
+ return;
+ }
+ ViewManagerServiceImpl* connection = GetConnection(kWindowManagerConnection);
+ connection->client()->Embed(url, service_provider.Pass());
+}
+
+void ConnectionManager::Embed(
+ ConnectionSpecificId creator_id,
+ const String& url,
+ Id transport_view_id,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ EmbedImpl(creator_id,
+ url,
+ ViewIdFromTransportId(transport_view_id),
+ service_provider.Pass())->set_delete_on_connection_error();
+}
+
+ViewManagerServiceImpl* ConnectionManager::GetConnection(
+ ConnectionSpecificId connection_id) {
+ ConnectionMap::iterator i = connection_map_.find(connection_id);
+ return i == connection_map_.end() ? NULL : i->second;
+}
+
+ServerView* ConnectionManager::GetView(const ViewId& id) {
+ if (id == root_->id())
+ return root_.get();
+ ConnectionMap::iterator i = connection_map_.find(id.connection_id);
+ return i == connection_map_.end() ? NULL : i->second->GetView(id);
+}
+
+void ConnectionManager::OnConnectionMessagedClient(ConnectionSpecificId id) {
+ if (current_change_)
+ current_change_->MarkConnectionAsMessaged(id);
+}
+
+bool ConnectionManager::DidConnectionMessageClient(
+ ConnectionSpecificId id) const {
+ return current_change_ && current_change_->DidMessageConnection(id);
+}
+
+const ViewManagerServiceImpl* ConnectionManager::GetConnectionWithRoot(
+ const ViewId& id) const {
+ for (ConnectionMap::const_iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ if (i->second->HasRoot(id))
+ return i->second;
+ }
+ return NULL;
+}
+
+void ConnectionManager::DispatchViewInputEventToWindowManager(EventPtr 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(event.Pass());
+}
+
+void ConnectionManager::ProcessViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ for (ConnectionMap::iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ i->second->ProcessViewBoundsChanged(
+ view, old_bounds, new_bounds, IsChangeSource(i->first));
+ }
+}
+
+void ConnectionManager::ProcessWillChangeViewHierarchy(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ for (ConnectionMap::iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ i->second->ProcessWillChangeViewHierarchy(
+ view, new_parent, old_parent, IsChangeSource(i->first));
+ }
+}
+
+void ConnectionManager::ProcessViewHierarchyChanged(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ for (ConnectionMap::iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ i->second->ProcessViewHierarchyChanged(
+ view, new_parent, old_parent, IsChangeSource(i->first));
+ }
+}
+
+void ConnectionManager::ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ const OrderDirection direction) {
+ for (ConnectionMap::iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ i->second->ProcessViewReorder(
+ view, relative_view, direction, IsChangeSource(i->first));
+ }
+}
+
+void ConnectionManager::ProcessViewDeleted(const ViewId& view) {
+ for (ConnectionMap::iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ i->second->ProcessViewDeleted(view, IsChangeSource(i->first));
+ }
+}
+
+void ConnectionManager::PrepareForChange(ScopedChange* change) {
+ // Should only ever have one change in flight.
+ CHECK(!current_change_);
+ current_change_ = change;
+}
+
+void ConnectionManager::FinishChange() {
+ // PrepareForChange/FinishChange should be balanced.
+ CHECK(current_change_);
+ current_change_ = NULL;
+}
+
+ViewManagerServiceImpl* ConnectionManager::EmbedImpl(
+ const ConnectionSpecificId creator_id,
+ const String& url,
+ const ViewId& root_id,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ MessagePipe pipe;
+
+ ServiceProvider* view_manager_service_provider =
+ app_connection_->ConnectToApplication(url)->GetServiceProvider();
+ view_manager_service_provider->ConnectToService(
+ ViewManagerServiceImpl::Client::Name_, pipe.handle1.Pass());
+
+ 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>(),
+ root_id,
+ service_provider.Pass());
+ WeakBindToPipe(connection, pipe.handle0.Pass());
+ connections_created_by_connect_.insert(connection);
+ OnConnectionMessagedClient(connection->id());
+ return connection;
+}
+
+void ConnectionManager::OnViewDestroyed(const ServerView* view) {
+ ProcessViewDeleted(view->id());
+}
+
+void ConnectionManager::OnWillChangeViewHierarchy(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ if (!display_manager_.in_setup())
+ ProcessWillChangeViewHierarchy(view, new_parent, old_parent);
+}
+
+void ConnectionManager::OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ if (!display_manager_.in_setup())
+ ProcessViewHierarchyChanged(view, new_parent, old_parent);
+ // TODO(beng): optimize.
+ if (old_parent) {
+ display_manager_.SchedulePaint(old_parent,
+ gfx::Rect(old_parent->bounds().size()));
+ }
+ if (new_parent) {
+ display_manager_.SchedulePaint(new_parent,
+ gfx::Rect(new_parent->bounds().size()));
+ }
+}
+
+void ConnectionManager::OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ ProcessViewBoundsChanged(view, old_bounds, new_bounds);
+ if (!view->parent())
+ return;
+
+ // TODO(sky): optimize this.
+ display_manager_.SchedulePaint(view->parent(), old_bounds);
+ display_manager_.SchedulePaint(view->parent(), new_bounds);
+}
+
+void ConnectionManager::OnViewSurfaceIdChanged(const ServerView* view) {
+ display_manager_.SchedulePaint(view, gfx::Rect(view->bounds().size()));
+}
+
+void ConnectionManager::OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ OrderDirection direction) {
+ display_manager_.SchedulePaint(view, gfx::Rect(view->bounds().size()));
+}
+
+void ConnectionManager::OnWillChangeViewVisibility(const ServerView* view) {
+ for (ConnectionMap::iterator i = connection_map_.begin();
+ i != connection_map_.end();
+ ++i) {
+ i->second->ProcessWillChangeViewVisibility(view, IsChangeSource(i->first));
+ }
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/connection_manager.h b/mojo/services/view_manager/connection_manager.h
new file mode 100644
index 0000000..c92fd69
--- /dev/null
+++ b/mojo/services/view_manager/connection_manager.h
@@ -0,0 +1,206 @@
+// Copyright 2014 The Chromium 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_CONNECTION_MANAGER_H_
+#define MOJO_SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/services/view_manager/display_manager.h"
+#include "mojo/services/view_manager/ids.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/server_view_delegate.h"
+#include "mojo/services/view_manager/view_manager_export.h"
+
+namespace ui {
+class Event;
+}
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace service {
+
+class ViewManagerServiceImpl;
+
+// ConnectionManager manages the set of connections to the ViewManager (all the
+// ViewManagerServiceImpls) as well as providing the root of the hierarchy.
+class MOJO_VIEW_MANAGER_EXPORT ConnectionManager : public ServerViewDelegate {
+ public:
+ // Create when a ViewManagerServiceImpl is about to make a change. Ensures
+ // clients are notified correctly.
+ class ScopedChange {
+ public:
+ ScopedChange(ViewManagerServiceImpl* connection,
+ ConnectionManager* connection_manager,
+ bool is_delete_view);
+ ~ScopedChange();
+
+ ConnectionSpecificId connection_id() const { return connection_id_; }
+ bool is_delete_view() const { return is_delete_view_; }
+
+ // 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:
+ ConnectionManager* connection_manager_;
+ const ConnectionSpecificId connection_id_;
+ const bool is_delete_view_;
+
+ // See description of MarkConnectionAsMessaged/DidMessageConnection.
+ std::set<ConnectionSpecificId> message_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedChange);
+ };
+
+ ConnectionManager(ApplicationConnection* app_connection,
+ const Callback<void()>& native_viewport_closed_callback);
+ virtual ~ConnectionManager();
+
+ // Returns the id for the next ViewManagerServiceImpl.
+ ConnectionSpecificId GetAndAdvanceNextConnectionId();
+
+ 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,
+ InterfaceRequest<ServiceProvider> service_provider);
+
+ // See description of ViewManagerService::Embed() for details. This assumes
+ // |transport_view_id| is valid.
+ void Embed(ConnectionSpecificId creator_id,
+ const String& url,
+ Id transport_view_id,
+ InterfaceRequest<ServiceProvider> service_provider);
+
+ // Returns the connection by id.
+ ViewManagerServiceImpl* GetConnection(ConnectionSpecificId connection_id);
+
+ // Returns the View identified by |id|.
+ ServerView* GetView(const ViewId& id);
+
+ ServerView* root() { return root_.get(); }
+
+ bool IsProcessingChange() const { return current_change_ != NULL; }
+
+ bool is_processing_delete_view() const {
+ return current_change_ && current_change_->is_delete_view();
+ }
+
+ // 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;
+
+ // Returns the ViewManagerServiceImpl that has |id| as a root.
+ ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) {
+ return const_cast<ViewManagerServiceImpl*>(
+ const_cast<const ConnectionManager*>(this)->GetConnectionWithRoot(id));
+ }
+ const ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) const;
+
+ void DispatchViewInputEventToWindowManager(EventPtr event);
+
+ // These functions trivially delegate to all ViewManagerServiceImpls, which in
+ // term notify their clients.
+ void ProcessViewDestroyed(ServerView* view);
+ void ProcessViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds);
+ void ProcessWillChangeViewHierarchy(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent);
+ void ProcessViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent);
+ void ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ const OrderDirection direction);
+ void ProcessViewDeleted(const ViewId& view);
+
+ private:
+ 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 ViewId& root_id,
+ InterfaceRequest<ServiceProvider> service_provider);
+
+ // Overridden from ServerViewDelegate:
+ virtual void OnViewDestroyed(const ServerView* view) OVERRIDE;
+ virtual void OnWillChangeViewHierarchy(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) OVERRIDE;
+ virtual void OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) OVERRIDE;
+ virtual void OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void OnViewSurfaceIdChanged(const ServerView* view) OVERRIDE;
+ virtual void OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ OrderDirection direction) OVERRIDE;
+ virtual void OnWillChangeViewVisibility(const ServerView* view) OVERRIDE;
+
+ ApplicationConnection* app_connection_;
+
+ // ID to use for next ViewManagerServiceImpl.
+ ConnectionSpecificId next_connection_id_;
+
+ // Set of ViewManagerServiceImpls.
+ ConnectionMap connection_map_;
+
+ DisplayManager display_manager_;
+
+ scoped_ptr<ServerView> 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(ConnectionManager);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_
diff --git a/mojo/services/view_manager/default_access_policy.cc b/mojo/services/view_manager/default_access_policy.cc
new file mode 100644
index 0000000..a2443c3
--- /dev/null
+++ b/mojo/services/view_manager/default_access_policy.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium 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/default_access_policy.h"
+
+#include "mojo/services/view_manager/access_policy_delegate.h"
+#include "mojo/services/view_manager/server_view.h"
+
+namespace mojo {
+namespace service {
+
+DefaultAccessPolicy::DefaultAccessPolicy(ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate)
+ : connection_id_(connection_id),
+ delegate_(delegate) {
+}
+
+DefaultAccessPolicy::~DefaultAccessPolicy() {
+}
+
+bool DefaultAccessPolicy::CanRemoveViewFromParent(
+ const ServerView* view) const {
+ if (!WasCreatedByThisConnection(view))
+ return false; // Can only unparent views we created.
+
+ return IsViewInRoots(view->parent()) ||
+ WasCreatedByThisConnection(view->parent());
+}
+
+bool DefaultAccessPolicy::CanAddView(const ServerView* parent,
+ const ServerView* child) const {
+ return WasCreatedByThisConnection(child) &&
+ (IsViewInRoots(parent) ||
+ (WasCreatedByThisConnection(parent) &&
+ !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(parent)));
+}
+
+bool DefaultAccessPolicy::CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const {
+ return WasCreatedByThisConnection(view) &&
+ WasCreatedByThisConnection(relative_view);
+}
+
+bool DefaultAccessPolicy::CanDeleteView(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::CanGetViewTree(const ServerView* view) const {
+ return WasCreatedByThisConnection(view) || IsViewInRoots(view);
+}
+
+bool DefaultAccessPolicy::CanDescendIntoViewForViewTree(
+ const ServerView* view) const {
+ return WasCreatedByThisConnection(view) &&
+ !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view);
+}
+
+bool DefaultAccessPolicy::CanEmbed(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::CanChangeViewVisibility(
+ const ServerView* view) const {
+ return WasCreatedByThisConnection(view) || IsViewInRoots(view);
+}
+
+bool DefaultAccessPolicy::CanSetViewSurfaceId(const ServerView* view) const {
+ // Once a view embeds another app, the embedder app is no longer able to
+ // call SetViewSurfaceId() - this ability is transferred to the embedded app.
+ if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view))
+ return false;
+ return WasCreatedByThisConnection(view) || IsViewInRoots(view);
+}
+
+bool DefaultAccessPolicy::CanSetViewBounds(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const {
+ if (!WasCreatedByThisConnection(view))
+ return false;
+
+ if (*new_parent && !WasCreatedByThisConnection(*new_parent) &&
+ !IsViewInRoots(*new_parent)) {
+ *new_parent = NULL;
+ }
+
+ if (*old_parent && !WasCreatedByThisConnection(*old_parent) &&
+ !IsViewInRoots(*old_parent)) {
+ *old_parent = NULL;
+ }
+ return true;
+}
+
+bool DefaultAccessPolicy::IsViewInRoots(const ServerView* view) const {
+ return delegate_->GetRootsForAccessPolicy().count(
+ ViewIdToTransportId(view->id())) > 0;
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/default_access_policy.h b/mojo/services/view_manager/default_access_policy.h
new file mode 100644
index 0000000..197736a
--- /dev/null
+++ b/mojo/services/view_manager/default_access_policy.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_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_
+#define MOJO_SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_
+
+#include "base/basictypes.h"
+#include "mojo/services/view_manager/access_policy.h"
+
+namespace mojo {
+namespace service {
+
+class AccessPolicyDelegate;
+
+// AccessPolicy for all connections, except the window manager.
+class DefaultAccessPolicy : public AccessPolicy {
+ public:
+ DefaultAccessPolicy(ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate);
+ virtual ~DefaultAccessPolicy();
+
+ // AccessPolicy:
+ virtual bool CanRemoveViewFromParent(const ServerView* view) const OVERRIDE;
+ virtual bool CanAddView(const ServerView* parent,
+ const ServerView* child) const OVERRIDE;
+ virtual bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const OVERRIDE;
+ virtual bool CanDeleteView(const ServerView* view) const OVERRIDE;
+ virtual bool CanGetViewTree(const ServerView* view) const OVERRIDE;
+ virtual bool CanDescendIntoViewForViewTree(
+ const ServerView* view) const OVERRIDE;
+ virtual bool CanEmbed(const ServerView* view) const OVERRIDE;
+ virtual bool CanChangeViewVisibility(const ServerView* view) const OVERRIDE;
+ virtual bool CanSetViewSurfaceId(const ServerView* view) const OVERRIDE;
+ virtual bool CanSetViewBounds(const ServerView* view) const OVERRIDE;
+ virtual bool ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const OVERRIDE;
+
+ private:
+ bool IsViewInRoots(const ServerView* view) const;
+
+ template <typename T>
+ bool WasCreatedByThisConnection(const T* t) const {
+ return t->id().connection_id == connection_id_;
+ }
+
+ const ConnectionSpecificId connection_id_;
+ AccessPolicyDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultAccessPolicy);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_
diff --git a/mojo/services/view_manager/display_manager.cc b/mojo/services/view_manager/display_manager.cc
new file mode 100644
index 0000000..435951b
--- /dev/null
+++ b/mojo/services/view_manager/display_manager.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium 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/display_manager.h"
+
+#include "base/numerics/safe_conversions.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/services/public/cpp/geometry/geometry_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/public/cpp/surfaces/surfaces_utils.h"
+#include "mojo/services/public/interfaces/gpu/gpu.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/quads.mojom.h"
+#include "mojo/services/view_manager/connection_manager.h"
+
+namespace mojo {
+namespace service {
+namespace {
+
+gfx::Rect ConvertRectToRoot(const ServerView* view, const gfx::Rect& bounds) {
+ gfx::Point origin(bounds.origin());
+ while (view->parent()) {
+ origin += view->bounds().OffsetFromOrigin();
+ view = view->parent();
+ if (!view->visible())
+ return gfx::Rect();
+ }
+ return gfx::Rect(origin, bounds.size());
+}
+
+void DrawViewTree(Pass* pass, const ServerView* view, gfx::Vector2d offset) {
+ if (!view->visible())
+ return;
+
+ gfx::Rect node_bounds = view->bounds() + offset;
+ std::vector<const ServerView*> children(view->GetChildren());
+ for (std::vector<const ServerView*>::reverse_iterator it = children.rbegin();
+ it != children.rend();
+ ++it) {
+ DrawViewTree(pass, *it, offset + view->bounds().OffsetFromOrigin());
+ }
+
+ cc::SurfaceId node_id = view->surface_id();
+
+ SurfaceQuadStatePtr surface_quad_state = SurfaceQuadState::New();
+ surface_quad_state->surface = SurfaceId::From(node_id);
+
+ gfx::Transform node_transform;
+ node_transform.Translate(node_bounds.x(), node_bounds.y());
+
+ QuadPtr surface_quad = Quad::New();
+ surface_quad->material = Material::MATERIAL_SURFACE_CONTENT;
+ surface_quad->rect = Rect::From(node_bounds);
+ surface_quad->opaque_rect = Rect::From(node_bounds);
+ surface_quad->visible_rect = Rect::From(node_bounds);
+ surface_quad->needs_blending = true;
+ surface_quad->shared_quad_state_index =
+ base::saturated_cast<int32_t>(pass->shared_quad_states.size());
+ surface_quad->surface_quad_state = surface_quad_state.Pass();
+
+ SharedQuadStatePtr sqs = CreateDefaultSQS(node_bounds.size());
+ sqs->content_to_target_transform = Transform::From(node_transform);
+
+ pass->quads.push_back(surface_quad.Pass());
+ pass->shared_quad_states.push_back(sqs.Pass());
+}
+
+} // namespace
+
+DisplayManager::DisplayManager(
+ ApplicationConnection* app_connection,
+ ConnectionManager* connection_manager,
+ const Callback<void()>& native_viewport_closed_callback)
+ : connection_manager_(connection_manager),
+ in_setup_(false),
+ size_(800, 600),
+ draw_timer_(false, false),
+ weak_factory_(this) {
+ app_connection->ConnectToService("mojo:mojo_native_viewport_service",
+ &native_viewport_);
+ native_viewport_.set_client(this);
+ native_viewport_->Create(
+ Size::From(size_),
+ base::Bind(&DisplayManager::OnCreatedNativeViewport,
+ weak_factory_.GetWeakPtr()));
+ native_viewport_->Show();
+ app_connection->ConnectToService("mojo:mojo_surfaces_service",
+ &surfaces_service_);
+ surfaces_service_->CreateSurfaceConnection(base::Bind(
+ &DisplayManager::OnSurfaceConnectionCreated, weak_factory_.GetWeakPtr()));
+}
+
+DisplayManager::~DisplayManager() {
+}
+
+void DisplayManager::SchedulePaint(const ServerView* view,
+ const gfx::Rect& bounds) {
+ if (!view->visible())
+ return;
+ gfx::Rect root_relative_rect = ConvertRectToRoot(view, bounds);
+ if (root_relative_rect.IsEmpty())
+ return;
+ dirty_rect_.Union(root_relative_rect);
+ if (!draw_timer_.IsRunning()) {
+ draw_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta(),
+ base::Bind(&DisplayManager::Draw, base::Unretained(this)));
+ }
+}
+
+void DisplayManager::OnCreatedNativeViewport(uint64_t native_viewport_id) {
+}
+
+void DisplayManager::OnSurfaceConnectionCreated(SurfacePtr surface,
+ uint32_t id_namespace) {
+ surface_ = surface.Pass();
+ surface_.set_client(this);
+ surface_id_allocator_.reset(new cc::SurfaceIdAllocator(id_namespace));
+ Draw();
+}
+
+void DisplayManager::Draw() {
+ if (!surface_)
+ return;
+ if (surface_id_.is_null()) {
+ surface_id_ = surface_id_allocator_->GenerateId();
+ surface_->CreateSurface(SurfaceId::From(surface_id_), Size::From(size_));
+ }
+
+ PassPtr pass = CreateDefaultPass(1, gfx::Rect(size_));
+ pass->damage_rect = Rect::From(dirty_rect_);
+
+ DrawViewTree(pass.get(), connection_manager_->root(), gfx::Vector2d());
+
+ FramePtr frame = Frame::New();
+ frame->passes.push_back(pass.Pass());
+ frame->resources.resize(0u);
+ surface_->SubmitFrame(SurfaceId::From(surface_id_), frame.Pass());
+
+ native_viewport_->SubmittedFrame(SurfaceId::From(surface_id_));
+
+ dirty_rect_ = gfx::Rect();
+}
+
+void DisplayManager::OnDestroyed() {
+ native_viewport_closed_callback_.Run();
+}
+
+void DisplayManager::OnSizeChanged(SizePtr size) {
+ size_ = size.To<gfx::Size>();
+ connection_manager_->root()->SetBounds(gfx::Rect(size_));
+ if (surface_id_.is_null())
+ return;
+ surface_->DestroySurface(SurfaceId::From(surface_id_));
+ surface_id_ = cc::SurfaceId();
+ SchedulePaint(connection_manager_->root(), gfx::Rect(size_));
+}
+
+void DisplayManager::OnEvent(EventPtr event,
+ const mojo::Callback<void()>& callback) {
+ connection_manager_->DispatchViewInputEventToWindowManager(event.Pass());
+ callback.Run();
+}
+
+void DisplayManager::ReturnResources(Array<ReturnedResourcePtr> resources) {
+ DCHECK_EQ(0u, resources.size());
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/display_manager.h b/mojo/services/view_manager/display_manager.h
new file mode 100644
index 0000000..21009f3
--- /dev/null
+++ b/mojo/services/view_manager/display_manager.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.
+
+#ifndef MOJO_SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_
+#define MOJO_SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "cc/surfaces/surface_id.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/services/public/interfaces/native_viewport/native_viewport.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces.mojom.h"
+#include "mojo/services/public/interfaces/surfaces/surfaces_service.mojom.h"
+#include "mojo/services/view_manager/view_manager_export.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+class SurfaceIdAllocator;
+}
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace service {
+
+class ConnectionManager;
+class ServerView;
+
+// DisplayManager binds the root node to an actual display.
+class MOJO_VIEW_MANAGER_EXPORT DisplayManager
+ : NON_EXPORTED_BASE(public NativeViewportClient),
+ NON_EXPORTED_BASE(public SurfaceClient) {
+ public:
+ DisplayManager(ApplicationConnection* app_connection,
+ ConnectionManager* connection_manager,
+ const Callback<void()>& native_viewport_closed_callback);
+ virtual ~DisplayManager();
+
+ // Schedules a paint for the specified region of the specified view.
+ void SchedulePaint(const ServerView* view, const gfx::Rect& bounds);
+
+ // See description above field for details.
+ bool in_setup() const { return in_setup_; }
+
+ private:
+ void OnCreatedNativeViewport(uint64_t native_viewport_id);
+ void OnSurfaceConnectionCreated(SurfacePtr surface, uint32_t id_namespace);
+ void Draw();
+
+ // NativeViewportClient implementation.
+ virtual void OnDestroyed() OVERRIDE;
+ virtual void OnSizeChanged(SizePtr size) OVERRIDE;
+ virtual void OnEvent(EventPtr event,
+ const mojo::Callback<void()>& callback) OVERRIDE;
+
+ // SurfaceClient implementation.
+ virtual void ReturnResources(Array<ReturnedResourcePtr> resources) OVERRIDE;
+
+ ConnectionManager* connection_manager_;
+
+ // Returns true if adding the root view's window to |window_tree_host_|.
+ bool in_setup_;
+
+ gfx::Size size_;
+ gfx::Rect dirty_rect_;
+ base::Timer draw_timer_;
+
+ SurfacesServicePtr surfaces_service_;
+ SurfacePtr surface_;
+ scoped_ptr<cc::SurfaceIdAllocator> surface_id_allocator_;
+ cc::SurfaceId surface_id_;
+ NativeViewportPtr native_viewport_;
+ Callback<void()> native_viewport_closed_callback_;
+ base::WeakPtrFactory<DisplayManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayManager);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_
diff --git a/mojo/services/view_manager/ids.h b/mojo/services/view_manager/ids.h
new file mode 100644
index 0000000..0d1c486
--- /dev/null
+++ b/mojo/services/view_manager/ids.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_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"
+
+namespace mojo {
+namespace service {
+
+// Connection id is used to indicate no connection. That is, no
+// ViewManagerServiceImpl ever gets this id.
+const ConnectionSpecificId kInvalidConnectionId = 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 view ids.
+struct 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;
+};
+
+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 ViewId RootViewId() {
+ return ViewId(kInvalidConnectionId, 1);
+}
+
+// Returns a ViewId that is reserved to indicate no view. That is, no view will
+// ever be created with this id.
+inline ViewId InvalidViewId() {
+ return ViewId(kInvalidConnectionId, 0);
+}
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_IDS_H_
diff --git a/mojo/services/view_manager/main.cc b/mojo/services/view_manager/main.cc
new file mode 100644
index 0000000..86a7ba7
--- /dev/null
+++ b/mojo/services/view_manager/main.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/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/services/view_manager/view_manager_init_service_context.h"
+#include "mojo/services/view_manager/view_manager_init_service_impl.h"
+
+namespace mojo {
+namespace service {
+
+class ViewManagerApp : public ApplicationDelegate,
+ public InterfaceFactory<ViewManagerInitService> {
+ public:
+ ViewManagerApp() {}
+ virtual ~ViewManagerApp() {}
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) OVERRIDE {
+ context_.ConfigureIncomingConnection(connection);
+ // TODO(sky): this needs some sort of authentication as well as making sure
+ // we only ever have one active at a time.
+ connection->AddService(this);
+ return true;
+ }
+
+ virtual void Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerInitService> request) OVERRIDE {
+ BindToRequest(new ViewManagerInitServiceImpl(connection, &context_),
+ &request);
+ }
+
+ private:
+ ViewManagerInitServiceContext context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerApp);
+};
+
+} // namespace service
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::service::ViewManagerApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/view_manager/server_view.cc b/mojo/services/view_manager/server_view.cc
new file mode 100644
index 0000000..8f5a559
--- /dev/null
+++ b/mojo/services/view_manager/server_view.cc
@@ -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.
+
+#include "mojo/services/view_manager/server_view.h"
+
+#include "mojo/services/view_manager/server_view_delegate.h"
+
+namespace mojo {
+namespace service {
+
+ServerView::ServerView(ServerViewDelegate* delegate, const ViewId& id)
+ : delegate_(delegate), id_(id), parent_(NULL), visible_(true) {
+ DCHECK(delegate); // Must provide a delegate.
+}
+
+ServerView::~ServerView() {
+ while (!children_.empty())
+ children_.front()->parent()->Remove(children_.front());
+
+ if (parent_)
+ parent_->Remove(this);
+
+ delegate_->OnViewDestroyed(this);
+}
+
+void ServerView::Add(ServerView* child) {
+ // We assume validation checks happened already.
+ DCHECK(child);
+ DCHECK(child != this);
+ DCHECK(!child->Contains(this));
+ if (child->parent() == this) {
+ if (children_.size() == 1)
+ return; // Already in the right position.
+ Reorder(child, children_.back(), ORDER_DIRECTION_ABOVE);
+ return;
+ }
+
+ const ServerView* old_parent = child->parent();
+ child->delegate_->OnWillChangeViewHierarchy(child, this, old_parent);
+ if (child->parent())
+ child->parent()->RemoveImpl(child);
+
+ child->parent_ = this;
+ children_.push_back(child);
+ child->delegate_->OnViewHierarchyChanged(child, this, old_parent);
+}
+
+void ServerView::Remove(ServerView* child) {
+ // We assume validation checks happened else where.
+ DCHECK(child);
+ DCHECK(child != this);
+ DCHECK(child->parent() == this);
+
+ child->delegate_->OnWillChangeViewHierarchy(child, NULL, this);
+ RemoveImpl(child);
+ child->delegate_->OnViewHierarchyChanged(child, NULL, this);
+}
+
+void ServerView::Reorder(ServerView* child,
+ ServerView* relative,
+ OrderDirection direction) {
+ // We assume validation checks happened else where.
+ DCHECK(child);
+ DCHECK(child->parent() == this);
+ DCHECK_GT(children_.size(), 1u);
+ children_.erase(std::find(children_.begin(), children_.end(), child));
+ Views::iterator i = std::find(children_.begin(), children_.end(), relative);
+ if (direction == ORDER_DIRECTION_ABOVE) {
+ DCHECK(i != children_.end());
+ children_.insert(++i, child);
+ } else if (direction == ORDER_DIRECTION_BELOW) {
+ DCHECK(i != children_.end());
+ children_.insert(i, child);
+ }
+ delegate_->OnViewReordered(this, relative, direction);
+}
+
+void ServerView::SetBounds(const gfx::Rect& bounds) {
+ if (bounds_ == bounds)
+ return;
+
+ const gfx::Rect old_bounds = bounds_;
+ bounds_ = bounds;
+ delegate_->OnViewBoundsChanged(this, old_bounds, bounds);
+}
+
+const ServerView* ServerView::GetRoot() const {
+ const ServerView* view = this;
+ while (view && view->parent())
+ view = view->parent();
+ return view;
+}
+
+std::vector<const ServerView*> ServerView::GetChildren() const {
+ std::vector<const ServerView*> children;
+ children.reserve(children_.size());
+ for (size_t i = 0; i < children_.size(); ++i)
+ children.push_back(children_[i]);
+ return children;
+}
+
+std::vector<ServerView*> ServerView::GetChildren() {
+ // TODO(sky): rename to children() and fix return type.
+ return children_;
+}
+
+bool ServerView::Contains(const ServerView* view) const {
+ for (const ServerView* parent = view; parent; parent = parent->parent_) {
+ if (parent == this)
+ return true;
+ }
+ return false;
+}
+
+void ServerView::SetVisible(bool value) {
+ if (visible_ == value)
+ return;
+
+ delegate_->OnWillChangeViewVisibility(this);
+ visible_ = value;
+}
+
+bool ServerView::IsDrawn(const ServerView* root) const {
+ if (!root->visible_)
+ return false;
+ const ServerView* view = this;
+ while (view && view != root && view->visible_)
+ view = view->parent_;
+ return view == root;
+}
+
+void ServerView::SetSurfaceId(cc::SurfaceId surface_id) {
+ surface_id_ = surface_id;
+ delegate_->OnViewSurfaceIdChanged(this);
+}
+
+void ServerView::RemoveImpl(ServerView* view) {
+ view->parent_ = NULL;
+ children_.erase(std::find(children_.begin(), children_.end(), view));
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/server_view.h b/mojo/services/view_manager/server_view.h
new file mode 100644
index 0000000..af7b37d
--- /dev/null
+++ b/mojo/services/view_manager/server_view.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_SERVICES_VIEW_MANAGER_SERVER_VIEW_H_
+#define MOJO_SERVICES_VIEW_MANAGER_SERVER_VIEW_H_
+
+#include <vector>
+
+#include "base/logging.h"
+#include "cc/surfaces/surface_id.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/gfx/geometry/rect.h"
+
+namespace mojo {
+namespace service {
+
+class ServerViewDelegate;
+
+// Server side representation of a view. Delegate is informed of interesting
+// events.
+//
+// It is assumed that all functions that mutate the tree have validated the
+// mutation is possible before hand. For example, Reorder() assumes the supplied
+// view is a child and not already in position.
+class MOJO_VIEW_MANAGER_EXPORT ServerView {
+ public:
+ ServerView(ServerViewDelegate* delegate, const ViewId& id);
+ virtual ~ServerView();
+
+ const ViewId& id() const { return id_; }
+
+ void Add(ServerView* child);
+ void Remove(ServerView* child);
+ void Reorder(ServerView* child,
+ ServerView* relative,
+ OrderDirection direction);
+
+ const gfx::Rect& bounds() const { return bounds_; }
+ void SetBounds(const gfx::Rect& bounds);
+
+ const ServerView* parent() const { return parent_; }
+ ServerView* parent() { return parent_; }
+
+ const ServerView* GetRoot() const;
+ ServerView* GetRoot() {
+ return const_cast<ServerView*>(
+ const_cast<const ServerView*>(this)->GetRoot());
+ }
+
+ std::vector<const ServerView*> GetChildren() const;
+ std::vector<ServerView*> GetChildren();
+
+ // Returns true if this contains |view| or is |view|.
+ bool Contains(const ServerView* view) const;
+
+ // Returns true if the window is visible. This does not consider visibility
+ // of any ancestors.
+ bool visible() const { return visible_; }
+ void SetVisible(bool value);
+
+ // Returns true if this view is attached to |root| and all ancestors are
+ // visible.
+ bool IsDrawn(const ServerView* root) const;
+
+ void SetSurfaceId(cc::SurfaceId surface_id);
+ const cc::SurfaceId surface_id() const { return surface_id_; }
+
+ private:
+ typedef std::vector<ServerView*> Views;
+
+ // Implementation of removing a view. Doesn't send any notification.
+ void RemoveImpl(ServerView* view);
+
+ ServerViewDelegate* delegate_;
+ const ViewId id_;
+ ServerView* parent_;
+ Views children_;
+ bool visible_;
+ gfx::Rect bounds_;
+ cc::SurfaceId surface_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerView);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_SERVER_VIEW_H_
diff --git a/mojo/services/view_manager/server_view_delegate.h b/mojo/services/view_manager/server_view_delegate.h
new file mode 100644
index 0000000..24e5f8f
--- /dev/null
+++ b/mojo/services/view_manager/server_view_delegate.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_SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_
+#define MOJO_SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_
+
+#include "mojo/services/view_manager/view_manager_export.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace mojo {
+namespace service {
+
+class ServerView;
+
+class MOJO_VIEW_MANAGER_EXPORT ServerViewDelegate {
+ public:
+ // Invoked at the end of the View's destructor (after it has been removed from
+ // the hierarchy).
+ virtual void OnViewDestroyed(const ServerView* view) = 0;
+
+ virtual void OnWillChangeViewHierarchy(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) = 0;
+
+ virtual void OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) = 0;
+
+ virtual void OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) = 0;
+
+ virtual void OnViewSurfaceIdChanged(const ServerView* view) = 0;
+
+ virtual void OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ OrderDirection direction) = 0;
+
+ virtual void OnWillChangeViewVisibility(const ServerView* view) = 0;
+
+ protected:
+ virtual ~ServerViewDelegate() {}
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_
diff --git a/mojo/services/view_manager/test_change_tracker.cc b/mojo/services/view_manager/test_change_tracker.cc
new file mode 100644
index 0000000..bf95ac5
--- /dev/null
+++ b/mojo/services/view_manager/test_change_tracker.cc
@@ -0,0 +1,244 @@
+// Copyright 2014 The Chromium 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 service {
+
+std::string ViewIdToString(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_DIRECTION_ABOVE ? "above" : "below";
+}
+
+std::string ChangeToDescription1(const Change& change) {
+ switch (change.type) {
+ case CHANGE_TYPE_EMBED:
+ return base::StringPrintf("OnEmbed creator=%s",
+ change.creator_url.data());
+
+ case CHANGE_TYPE_NODE_BOUNDS_CHANGED:
+ return base::StringPrintf(
+ "BoundsChanged view=%s old_bounds=%s new_bounds=%s",
+ ViewIdToString(change.view_id).c_str(),
+ RectToString(change.bounds).c_str(),
+ RectToString(change.bounds2).c_str());
+
+ case CHANGE_TYPE_NODE_HIERARCHY_CHANGED:
+ return base::StringPrintf(
+ "HierarchyChanged view=%s new_parent=%s old_parent=%s",
+ ViewIdToString(change.view_id).c_str(),
+ ViewIdToString(change.view_id2).c_str(),
+ ViewIdToString(change.view_id3).c_str());
+
+ case CHANGE_TYPE_NODE_REORDERED:
+ return base::StringPrintf("Reordered view=%s relative=%s direction=%s",
+ ViewIdToString(change.view_id).c_str(),
+ ViewIdToString(change.view_id2).c_str(),
+ DirectionToString(change.direction).c_str());
+
+ case CHANGE_TYPE_NODE_DELETED:
+ return base::StringPrintf("ViewDeleted view=%s",
+ ViewIdToString(change.view_id).c_str());
+
+ case CHANGE_TYPE_NODE_VISIBILITY_CHANGED:
+ return base::StringPrintf("VisibilityChanged view=%s visible=%s",
+ ViewIdToString(change.view_id).c_str(),
+ change.bool_value ? "true" : "false");
+
+ case CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED:
+ return base::StringPrintf("DrawnStateChanged view=%s drawn=%s",
+ ViewIdToString(change.view_id).c_str(),
+ change.bool_value ? "true" : "false");
+
+ case CHANGE_TYPE_INPUT_EVENT:
+ return base::StringPrintf("InputEvent view=%s event_action=%d",
+ ViewIdToString(change.view_id).c_str(),
+ change.event_action);
+
+ case CHANGE_TYPE_DELEGATE_EMBED:
+ return base::StringPrintf("DelegateEmbed url=%s",
+ change.embed_url.data());
+ }
+ 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 ChangeViewDescription(const std::vector<Change>& changes) {
+ if (changes.size() != 1)
+ return std::string();
+ std::vector<std::string> view_strings(changes[0].views.size());
+ for (size_t i = 0; i < changes[0].views.size(); ++i)
+ view_strings[i] = "[" + changes[0].views[i].ToString() + "]";
+ return JoinString(view_strings, ',');
+}
+
+TestView ViewDataToTestView(const ViewDataPtr& data) {
+ TestView view;
+ view.parent_id = data->parent_id;
+ view.view_id = data->view_id;
+ view.visible = data->visible;
+ view.drawn = data->drawn;
+ return view;
+}
+
+void ViewDatasToTestViews(const Array<ViewDataPtr>& data,
+ std::vector<TestView>* test_views) {
+ for (size_t i = 0; i < data.size(); ++i)
+ test_views->push_back(ViewDataToTestView(data[i]));
+}
+
+Change::Change()
+ : type(CHANGE_TYPE_EMBED),
+ connection_id(0),
+ view_id(0),
+ view_id2(0),
+ view_id3(0),
+ event_action(0),
+ direction(ORDER_DIRECTION_ABOVE),
+ bool_value(false) {
+}
+
+Change::~Change() {
+}
+
+TestChangeTracker::TestChangeTracker()
+ : delegate_(NULL) {
+}
+
+TestChangeTracker::~TestChangeTracker() {
+}
+
+void TestChangeTracker::OnEmbed(ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root) {
+ Change change;
+ change.type = CHANGE_TYPE_EMBED;
+ change.connection_id = connection_id;
+ change.creator_url = creator_url;
+ change.views.push_back(ViewDataToTestView(root));
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewBoundsChanged(Id view_id,
+ RectPtr old_bounds,
+ RectPtr new_bounds) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED;
+ change.view_id = view_id;
+ change.bounds = old_bounds.To<gfx::Rect>();
+ change.bounds2 = new_bounds.To<gfx::Rect>();
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewHierarchyChanged(Id view_id,
+ Id new_parent_id,
+ Id old_parent_id,
+ Array<ViewDataPtr> views) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED;
+ change.view_id = view_id;
+ change.view_id2 = new_parent_id;
+ change.view_id3 = old_parent_id;
+ ViewDatasToTestViews(views, &change.views);
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewReordered(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_REORDERED;
+ change.view_id = view_id;
+ change.view_id2 = relative_view_id;
+ change.direction = direction;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewDeleted(Id view_id) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_DELETED;
+ change.view_id = view_id;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewVisibilityChanged(Id view_id, bool visible) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_VISIBILITY_CHANGED;
+ change.view_id = view_id;
+ change.bool_value = visible;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewDrawnStateChanged(Id view_id, bool drawn) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED;
+ change.view_id = view_id;
+ change.bool_value = drawn;
+ 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::DelegateEmbed(const String& url) {
+ Change change;
+ change.type = CHANGE_TYPE_DELEGATE_EMBED;
+ change.embed_url = url;
+ AddChange(change);
+}
+
+void TestChangeTracker::AddChange(const Change& change) {
+ changes_.push_back(change);
+ if (delegate_)
+ delegate_->OnChangeAdded();
+}
+
+std::string TestView::ToString() const {
+ return base::StringPrintf("view=%s parent=%s",
+ ViewIdToString(view_id).c_str(),
+ ViewIdToString(parent_id).c_str());
+}
+
+std::string TestView::ToString2() const {
+ return base::StringPrintf("view=%s parent=%s visible=%s drawn=%s",
+ ViewIdToString(view_id).c_str(),
+ ViewIdToString(parent_id).c_str(),
+ visible ? "true" : "false",
+ drawn ? "true" : "false");
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/test_change_tracker.h b/mojo/services/view_manager/test_change_tracker.h
new file mode 100644
index 0000000..791c8ad
--- /dev/null
+++ b/mojo/services/view_manager/test_change_tracker.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_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 service {
+
+enum ChangeType {
+ CHANGE_TYPE_EMBED,
+ // TODO(sky): NODE->VIEW.
+ CHANGE_TYPE_NODE_BOUNDS_CHANGED,
+ CHANGE_TYPE_NODE_HIERARCHY_CHANGED,
+ CHANGE_TYPE_NODE_REORDERED,
+ CHANGE_TYPE_NODE_VISIBILITY_CHANGED,
+ CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED,
+ CHANGE_TYPE_NODE_DELETED,
+ CHANGE_TYPE_INPUT_EVENT,
+ CHANGE_TYPE_DELEGATE_EMBED,
+};
+
+// TODO(sky): consider nuking and converting directly to ViewData.
+struct TestView {
+ // Returns a string description of this.
+ std::string ToString() const;
+
+ // Returns a string description that includes visible and drawn.
+ std::string ToString2() const;
+
+ Id parent_id;
+ Id view_id;
+ bool visible;
+ bool drawn;
+};
+
+// Tracks a call to ViewManagerClient. See the individual functions for the
+// fields that are used.
+struct Change {
+ Change();
+ ~Change();
+
+ ChangeType type;
+ ConnectionSpecificId connection_id;
+ std::vector<TestView> views;
+ Id view_id;
+ Id view_id2;
+ Id view_id3;
+ gfx::Rect bounds;
+ gfx::Rect bounds2;
+ int32 event_action;
+ String creator_url;
+ String embed_url;
+ OrderDirection direction;
+ bool bool_value;
+};
+
+// Converts Changes to string descriptions.
+std::vector<std::string> ChangesToDescription1(
+ const std::vector<Change>& changes);
+
+// Returns a string description of |changes[0].views|. Returns an empty string
+// if change.size() != 1.
+std::string ChangeViewDescription(const std::vector<Change>& changes);
+
+// Converts ViewDatas to TestViews.
+void ViewDatasToTestViews(const Array<ViewDataPtr>& data,
+ std::vector<TestView>* test_views);
+
+// 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 OnEmbed(ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root);
+ void OnViewBoundsChanged(Id view_id, RectPtr old_bounds, RectPtr new_bounds);
+ void OnViewHierarchyChanged(Id view_id,
+ Id new_parent_id,
+ Id old_parent_id,
+ Array<ViewDataPtr> views);
+ void OnViewReordered(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction);
+ void OnViewDeleted(Id view_id);
+ void OnViewVisibilityChanged(Id view_id, bool visible);
+ void OnViewDrawnStateChanged(Id view_id, bool drawn);
+ void OnViewInputEvent(Id view_id, EventPtr event);
+ void DelegateEmbed(const String& url);
+
+ private:
+ void AddChange(const Change& change);
+
+ Delegate* delegate_;
+ std::vector<Change> changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestChangeTracker);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
diff --git a/mojo/services/view_manager/view_manager_export.h b/mojo/services/view_manager/view_manager_export.h
new file mode 100644
index 0000000..29b0e62
--- /dev/null
+++ b/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/mojo/services/view_manager/view_manager_init_service_context.cc b/mojo/services/view_manager/view_manager_init_service_context.cc
new file mode 100644
index 0000000..79a7067
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_init_service_context.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/services/view_manager/view_manager_init_service_context.h"
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/view_manager_init_service_impl.h"
+
+namespace mojo {
+namespace service {
+
+ViewManagerInitServiceContext::ConnectParams::ConnectParams() {}
+
+ViewManagerInitServiceContext::ConnectParams::~ConnectParams() {}
+
+ViewManagerInitServiceContext::ViewManagerInitServiceContext()
+ : deleting_connection_(false) {
+}
+ViewManagerInitServiceContext::~ViewManagerInitServiceContext() {}
+
+void ViewManagerInitServiceContext::AddConnection(
+ ViewManagerInitServiceImpl* connection) {
+ DCHECK(std::find(connections_.begin(), connections_.end(), connection) ==
+ connections_.end());
+ connections_.push_back(connection);
+}
+
+void ViewManagerInitServiceContext::RemoveConnection(
+ ViewManagerInitServiceImpl* connection) {
+ if (!deleting_connection_) {
+ Connections::iterator it =
+ std::find(connections_.begin(), connections_.end(), connection);
+ DCHECK(it != connections_.end());
+ connections_.erase(it);
+ }
+
+ // This object is owned by an object that outlives the current thread's
+ // message loop, so we need to destroy the ConnectionManager earlier, as it
+ // may attempt to post tasks during its destruction.
+ if (connections_.empty())
+ connection_manager_.reset();
+}
+
+void ViewManagerInitServiceContext::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ if (!connection_manager_.get()) {
+ connection_manager_.reset(new ConnectionManager(
+ connection,
+ base::Bind(&ViewManagerInitServiceContext::OnNativeViewportDeleted,
+ base::Unretained(this))));
+ }
+}
+
+void ViewManagerInitServiceContext::Embed(
+ const String& url,
+ ServiceProviderPtr service_provider,
+ const Callback<void(bool)>& callback) {
+ connection_manager_->EmbedRoot(url, Get(&service_provider));
+ callback.Run(true);
+}
+
+void ViewManagerInitServiceContext::OnNativeViewportDeleted() {
+ // Prevent the connection from modifying the connection list during manual
+ // teardown.
+ base::AutoReset<bool> deleting_connection(&deleting_connection_, true);
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ delete *it;
+ }
+ connections_.clear();
+ connection_manager_.reset();
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/view_manager_init_service_context.h b/mojo/services/view_manager/view_manager_init_service_context.h
new file mode 100644
index 0000000..5019c68
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_init_service_context.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_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_CONTEXT_H_
+#define MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_CONTEXT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/view_manager/view_manager_export.h"
+
+namespace mojo {
+class ApplicationConnection;
+
+namespace service {
+
+class ConnectionManager;
+class ViewManagerInitServiceImpl;
+
+// State shared between all ViewManagerInitService impls.
+class MOJO_VIEW_MANAGER_EXPORT ViewManagerInitServiceContext {
+ public:
+ ViewManagerInitServiceContext();
+ virtual ~ViewManagerInitServiceContext();
+
+ void AddConnection(ViewManagerInitServiceImpl* connection);
+ void RemoveConnection(ViewManagerInitServiceImpl* connection);
+
+ void ConfigureIncomingConnection(ApplicationConnection* connection);
+
+ void Embed(const String& url,
+ ServiceProviderPtr service_provider,
+ const Callback<void(bool)>& callback);
+
+ ConnectionManager* connection_manager() { return connection_manager_.get(); }
+
+ private:
+ typedef std::vector<ViewManagerInitServiceImpl*> Connections;
+
+ struct ConnectParams {
+ ConnectParams();
+ ~ConnectParams();
+
+ std::string url;
+ InterfaceRequest<ServiceProvider> service_provider;
+ Callback<void(bool)> callback;
+ };
+
+ void OnNativeViewportDeleted();
+
+ scoped_ptr<ConnectionManager> connection_manager_;
+ Connections connections_;
+
+ bool deleting_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerInitServiceContext);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_CONTEXT_H_
diff --git a/mojo/services/view_manager/view_manager_init_service_impl.cc b/mojo/services/view_manager/view_manager_init_service_impl.cc
new file mode 100644
index 0000000..ab30718
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_init_service_impl.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/services/view_manager/view_manager_init_service_impl.h"
+
+#include "base/bind.h"
+#include "mojo/services/view_manager/ids.h"
+#include "mojo/services/view_manager/view_manager_init_service_context.h"
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+
+namespace mojo {
+namespace service {
+
+ViewManagerInitServiceImpl::ViewManagerInitServiceImpl(
+ ApplicationConnection* connection,
+ ViewManagerInitServiceContext* context)
+ : context_(context) {
+ context_->AddConnection(this);
+}
+
+ViewManagerInitServiceImpl::~ViewManagerInitServiceImpl() {
+ context_->RemoveConnection(this);
+}
+
+void ViewManagerInitServiceImpl::Embed(
+ const String& url,
+ ServiceProviderPtr service_provider,
+ const Callback<void(bool)>& callback) {
+ context_->Embed(url, service_provider.Pass(), callback);
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/view_manager_init_service_impl.h b/mojo/services/view_manager/view_manager_init_service_impl.h
new file mode 100644
index 0000000..49cc628
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_init_service_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_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/connection_manager.h"
+#include "mojo/services/view_manager/view_manager_export.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace service {
+
+class ViewManagerInitServiceContext;
+
+#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:
+ ViewManagerInitServiceImpl(ApplicationConnection* connection,
+ ViewManagerInitServiceContext* context);
+ virtual ~ViewManagerInitServiceImpl();
+
+ private:
+ // ViewManagerInitService overrides:
+ virtual void Embed(const String& url,
+ ServiceProviderPtr service_provider,
+ const Callback<void(bool)>& callback) OVERRIDE;
+
+ ViewManagerInitServiceContext* context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerInitServiceImpl);
+};
+
+#if defined(OS_WIN)
+#pragma warning(pop)
+#endif
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_INIT_SERVICE_IMPL_H_
diff --git a/mojo/services/view_manager/view_manager_service_impl.cc b/mojo/services/view_manager/view_manager_service_impl.cc
new file mode 100644
index 0000000..ad0df79
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_service_impl.cc
@@ -0,0 +1,564 @@
+// Copyright 2014 The Chromium 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 "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/cpp/surfaces/surfaces_type_converters.h"
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/default_access_policy.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/window_manager_access_policy.h"
+
+namespace mojo {
+namespace service {
+
+ViewManagerServiceImpl::ViewManagerServiceImpl(
+ ConnectionManager* connection_manager,
+ ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id,
+ InterfaceRequest<ServiceProvider> service_provider)
+ : connection_manager_(connection_manager),
+ id_(connection_manager_->GetAndAdvanceNextConnectionId()),
+ url_(url),
+ creator_id_(creator_id),
+ creator_url_(creator_url),
+ delete_on_connection_error_(false),
+ service_provider_(service_provider.Pass()) {
+ CHECK(GetView(root_id));
+ roots_.insert(ViewIdToTransportId(root_id));
+ if (root_id == RootViewId())
+ access_policy_.reset(new WindowManagerAccessPolicy(id_, this));
+ else
+ access_policy_.reset(new DefaultAccessPolicy(id_, this));
+}
+
+ViewManagerServiceImpl::~ViewManagerServiceImpl() {
+ // Delete any views we created.
+ if (!view_map_.empty()) {
+ ConnectionManager::ScopedChange change(this, connection_manager_, true);
+ while (!view_map_.empty())
+ delete view_map_.begin()->second;
+ }
+
+ connection_manager_->RemoveConnection(this);
+}
+
+const ServerView* 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 connection_manager_->GetView(id);
+}
+
+bool ViewManagerServiceImpl::HasRoot(const ViewId& id) const {
+ return roots_.find(ViewIdToTransportId(id)) != roots_.end();
+}
+
+void ViewManagerServiceImpl::OnViewManagerServiceImplDestroyed(
+ ConnectionSpecificId id) {
+ if (creator_id_ == id)
+ creator_id_ = kInvalidConnectionId;
+}
+
+void ViewManagerServiceImpl::ProcessViewBoundsChanged(
+ const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds,
+ bool originated_change) {
+ if (originated_change || !IsViewKnown(view))
+ return;
+ client()->OnViewBoundsChanged(ViewIdToTransportId(view->id()),
+ Rect::From(old_bounds),
+ Rect::From(new_bounds));
+}
+
+void ViewManagerServiceImpl::ProcessWillChangeViewHierarchy(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change) {
+ if (originated_change)
+ return;
+
+ const bool old_drawn = view->IsDrawn(connection_manager_->root());
+ const bool new_drawn = view->visible() && new_parent &&
+ new_parent->IsDrawn(connection_manager_->root());
+ if (old_drawn == new_drawn)
+ return;
+
+ NotifyDrawnStateChanged(view, new_drawn);
+}
+
+void ViewManagerServiceImpl::ProcessViewHierarchyChanged(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change) {
+ if (originated_change && !IsViewKnown(view) && new_parent &&
+ IsViewKnown(new_parent)) {
+ std::vector<const ServerView*> unused;
+ GetUnknownViewsFrom(view, &unused);
+ }
+ if (originated_change || connection_manager_->is_processing_delete_view() ||
+ connection_manager_->DidConnectionMessageClient(id_)) {
+ return;
+ }
+
+ if (!access_policy_->ShouldNotifyOnHierarchyChange(
+ view, &new_parent, &old_parent)) {
+ return;
+ }
+ // Inform the client of any new views and update the set of views we know
+ // about.
+ std::vector<const ServerView*> to_send;
+ if (!IsViewKnown(view))
+ GetUnknownViewsFrom(view, &to_send);
+ const ViewId new_parent_id(new_parent ? new_parent->id() : ViewId());
+ const ViewId old_parent_id(old_parent ? old_parent->id() : ViewId());
+ client()->OnViewHierarchyChanged(ViewIdToTransportId(view->id()),
+ ViewIdToTransportId(new_parent_id),
+ ViewIdToTransportId(old_parent_id),
+ ViewsToViewDatas(to_send));
+ connection_manager_->OnConnectionMessagedClient(id_);
+}
+
+void ViewManagerServiceImpl::ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction,
+ bool originated_change) {
+ if (originated_change || !IsViewKnown(view) || !IsViewKnown(relative_view))
+ return;
+
+ client()->OnViewReordered(ViewIdToTransportId(view->id()),
+ ViewIdToTransportId(relative_view->id()),
+ direction);
+}
+
+void ViewManagerServiceImpl::ProcessViewDeleted(const ViewId& view,
+ bool originated_change) {
+ view_map_.erase(view.view_id);
+
+ const bool in_known = known_views_.erase(ViewIdToTransportId(view)) > 0;
+ roots_.erase(ViewIdToTransportId(view));
+
+ if (originated_change)
+ return;
+
+ if (in_known) {
+ client()->OnViewDeleted(ViewIdToTransportId(view));
+ connection_manager_->OnConnectionMessagedClient(id_);
+ }
+}
+
+void ViewManagerServiceImpl::ProcessWillChangeViewVisibility(
+ const ServerView* view,
+ bool originated_change) {
+ if (originated_change)
+ return;
+
+ if (IsViewKnown(view)) {
+ client()->OnViewVisibilityChanged(ViewIdToTransportId(view->id()),
+ !view->visible());
+ return;
+ }
+
+ bool view_target_drawn_state;
+ if (view->visible()) {
+ // View is being hidden, won't be drawn.
+ view_target_drawn_state = false;
+ } else {
+ // View is being shown. View will be drawn if its parent is drawn.
+ view_target_drawn_state =
+ view->parent() && view->parent()->IsDrawn(connection_manager_->root());
+ }
+
+ NotifyDrawnStateChanged(view, view_target_drawn_state);
+}
+
+void ViewManagerServiceImpl::OnConnectionError() {
+ if (delete_on_connection_error_)
+ delete this;
+}
+
+bool ViewManagerServiceImpl::IsViewKnown(const ServerView* view) const {
+ return known_views_.count(ViewIdToTransportId(view->id())) > 0;
+}
+
+bool ViewManagerServiceImpl::CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const {
+ if (!view || !relative_view)
+ return false;
+
+ if (!view->parent() || view->parent() != relative_view->parent())
+ return false;
+
+ if (!access_policy_->CanReorderView(view, relative_view, direction))
+ return false;
+
+ std::vector<const ServerView*> children = view->parent()->GetChildren();
+ const size_t child_i =
+ std::find(children.begin(), children.end(), view) - children.begin();
+ const size_t target_i =
+ std::find(children.begin(), children.end(), relative_view) -
+ children.begin();
+ if ((direction == ORDER_DIRECTION_ABOVE && child_i == target_i + 1) ||
+ (direction == ORDER_DIRECTION_BELOW && child_i + 1 == target_i)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool ViewManagerServiceImpl::DeleteViewImpl(ViewManagerServiceImpl* source,
+ ServerView* view) {
+ DCHECK(view);
+ DCHECK_EQ(view->id().connection_id, id_);
+ ConnectionManager::ScopedChange change(source, connection_manager_, true);
+ delete view;
+ return true;
+}
+
+void ViewManagerServiceImpl::GetUnknownViewsFrom(
+ const ServerView* view,
+ std::vector<const ServerView*>* views) {
+ if (IsViewKnown(view) || !access_policy_->CanGetViewTree(view))
+ return;
+ views->push_back(view);
+ known_views_.insert(ViewIdToTransportId(view->id()));
+ if (!access_policy_->CanDescendIntoViewForViewTree(view))
+ return;
+ std::vector<const ServerView*> children(view->GetChildren());
+ for (size_t i = 0 ; i < children.size(); ++i)
+ GetUnknownViewsFrom(children[i], views);
+}
+
+void ViewManagerServiceImpl::RemoveFromKnown(
+ const ServerView* view,
+ std::vector<ServerView*>* local_views) {
+ if (view->id().connection_id == id_) {
+ if (local_views)
+ local_views->push_back(GetView(view->id()));
+ return;
+ }
+ known_views_.erase(ViewIdToTransportId(view->id()));
+ std::vector<const ServerView*> children = view->GetChildren();
+ for (size_t i = 0; i < children.size(); ++i)
+ RemoveFromKnown(children[i], local_views);
+}
+
+void ViewManagerServiceImpl::RemoveRoot(const ViewId& view_id) {
+ const Id transport_view_id(ViewIdToTransportId(view_id));
+ CHECK(roots_.count(transport_view_id) > 0);
+
+ roots_.erase(transport_view_id);
+
+ // No need to do anything if we created the view.
+ if (view_id.connection_id == id_)
+ return;
+
+ client()->OnViewDeleted(transport_view_id);
+ connection_manager_->OnConnectionMessagedClient(id_);
+
+ // This connection no longer knows about the view. Unparent any views that
+ // were parented to views in the root.
+ std::vector<ServerView*> local_views;
+ RemoveFromKnown(GetView(view_id), &local_views);
+ for (size_t i = 0; i < local_views.size(); ++i)
+ local_views[i]->parent()->Remove(local_views[i]);
+}
+
+void ViewManagerServiceImpl::RemoveChildrenAsPartOfEmbed(
+ const ViewId& view_id) {
+ ServerView* view = GetView(view_id);
+ CHECK(view);
+ CHECK(view->id().connection_id == view_id.connection_id);
+ std::vector<ServerView*> children = view->GetChildren();
+ for (size_t i = 0; i < children.size(); ++i)
+ view->Remove(children[i]);
+}
+
+Array<ViewDataPtr> ViewManagerServiceImpl::ViewsToViewDatas(
+ const std::vector<const ServerView*>& views) {
+ Array<ViewDataPtr> array(views.size());
+ for (size_t i = 0; i < views.size(); ++i)
+ array[i] = ViewToViewData(views[i]).Pass();
+ return array.Pass();
+}
+
+ViewDataPtr ViewManagerServiceImpl::ViewToViewData(const ServerView* view) {
+ DCHECK(IsViewKnown(view));
+ const ServerView* parent = view->parent();
+ // 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 && !IsViewKnown(parent))
+ parent = NULL;
+ ViewDataPtr view_data(ViewData::New());
+ view_data->parent_id = ViewIdToTransportId(parent ? parent->id() : ViewId());
+ view_data->view_id = ViewIdToTransportId(view->id());
+ view_data->bounds = Rect::From(view->bounds());
+ view_data->visible = view->visible();
+ view_data->drawn = view->IsDrawn(connection_manager_->root());
+ return view_data.Pass();
+}
+
+void ViewManagerServiceImpl::GetViewTreeImpl(
+ const ServerView* view,
+ std::vector<const ServerView*>* views) const {
+ DCHECK(view);
+
+ if (!access_policy_->CanGetViewTree(view))
+ return;
+
+ views->push_back(view);
+
+ if (!access_policy_->CanDescendIntoViewForViewTree(view))
+ return;
+
+ std::vector<const ServerView*> children(view->GetChildren());
+ for (size_t i = 0 ; i < children.size(); ++i)
+ GetViewTreeImpl(children[i], views);
+}
+
+void ViewManagerServiceImpl::NotifyDrawnStateChanged(const ServerView* view,
+ bool new_drawn_value) {
+ // Even though we don't know about view, it may be an ancestor of one of our
+ // roots, in which case the change may effect our roots drawn state.
+ for (ViewIdSet::iterator i = roots_.begin(); i != roots_.end(); ++i) {
+ const ServerView* root = GetView(ViewIdFromTransportId(*i));
+ DCHECK(root);
+ if (view->Contains(root) &&
+ (new_drawn_value != root->IsDrawn(connection_manager_->root()))) {
+ client()->OnViewDrawnStateChanged(ViewIdToTransportId(root->id()),
+ new_drawn_value);
+ }
+ }
+}
+
+void ViewManagerServiceImpl::CreateView(
+ Id transport_view_id,
+ const Callback<void(ErrorCode)>& callback) {
+ const ViewId view_id(ViewIdFromTransportId(transport_view_id));
+ ErrorCode error_code = ERROR_CODE_NONE;
+ if (view_id.connection_id != id_) {
+ error_code = ERROR_CODE_ILLEGAL_ARGUMENT;
+ } else if (view_map_.find(view_id.view_id) != view_map_.end()) {
+ error_code = ERROR_CODE_VALUE_IN_USE;
+ } else {
+ view_map_[view_id.view_id] = new ServerView(connection_manager_, view_id);
+ known_views_.insert(transport_view_id);
+ }
+ callback.Run(error_code);
+}
+
+void ViewManagerServiceImpl::DeleteView(
+ Id transport_view_id,
+ const Callback<void(bool)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(transport_view_id));
+ bool success = false;
+ if (view && access_policy_->CanDeleteView(view)) {
+ ViewManagerServiceImpl* connection =
+ connection_manager_->GetConnection(view->id().connection_id);
+ success = connection && connection->DeleteViewImpl(this, view);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::AddView(
+ Id parent_id,
+ Id child_id,
+ const Callback<void(bool)>& callback) {
+ bool success = false;
+ ServerView* parent = GetView(ViewIdFromTransportId(parent_id));
+ ServerView* child = GetView(ViewIdFromTransportId(child_id));
+ if (parent && child && child->parent() != parent &&
+ !child->Contains(parent) && access_policy_->CanAddView(parent, child)) {
+ success = true;
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ parent->Add(child);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::RemoveViewFromParent(
+ Id view_id,
+ const Callback<void(bool)>& callback) {
+ bool success = false;
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ if (view && view->parent() && access_policy_->CanRemoveViewFromParent(view)) {
+ success = true;
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->parent()->Remove(view);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::ReorderView(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction,
+ const Callback<void(bool)>& callback) {
+ bool success = false;
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ ServerView* relative_view = GetView(ViewIdFromTransportId(relative_view_id));
+ if (CanReorderView(view, relative_view, direction)) {
+ success = true;
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->parent()->Reorder(view, relative_view, direction);
+ connection_manager_->ProcessViewReorder(view, relative_view, direction);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::GetViewTree(
+ Id view_id,
+ const Callback<void(Array<ViewDataPtr>)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ std::vector<const ServerView*> views;
+ if (view) {
+ GetViewTreeImpl(view, &views);
+ // TODO(sky): this should map in views that weren't none.
+ }
+ callback.Run(ViewsToViewDatas(views));
+}
+
+void ViewManagerServiceImpl::SetViewSurfaceId(
+ Id view_id,
+ SurfaceIdPtr surface_id,
+ const Callback<void(bool)>& callback) {
+ // TODO(sky): add coverage of not being able to set for random node.
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ if (!view || !access_policy_->CanSetViewSurfaceId(view)) {
+ callback.Run(false);
+ return;
+ }
+ view->SetSurfaceId(surface_id.To<cc::SurfaceId>());
+ callback.Run(true);
+}
+
+void ViewManagerServiceImpl::SetViewBounds(
+ Id view_id,
+ RectPtr bounds,
+ const Callback<void(bool)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ const bool success = view && access_policy_->CanSetViewBounds(view);
+ if (success) {
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->SetBounds(bounds.To<gfx::Rect>());
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::SetViewVisibility(
+ Id transport_view_id,
+ bool visible,
+ const Callback<void(bool)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(transport_view_id));
+ if (!view || view->visible() == visible ||
+ !access_policy_->CanChangeViewVisibility(view)) {
+ callback.Run(false);
+ return;
+ }
+ {
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->SetVisible(visible);
+ }
+ callback.Run(true);
+}
+
+void ViewManagerServiceImpl::Embed(
+ const String& url,
+ Id transport_view_id,
+ ServiceProviderPtr service_provider,
+ const Callback<void(bool)>& callback) {
+ InterfaceRequest<ServiceProvider> spir;
+ spir.Bind(service_provider.PassMessagePipe());
+
+ if (ViewIdFromTransportId(transport_view_id) == InvalidViewId()) {
+ connection_manager_->EmbedRoot(url, spir.Pass());
+ callback.Run(true);
+ return;
+ }
+ const ServerView* view = GetView(ViewIdFromTransportId(transport_view_id));
+ if (!view || !access_policy_->CanEmbed(view)) {
+ callback.Run(false);
+ return;
+ }
+
+ // Only allow a node to be the root for one connection.
+ const ViewId view_id(ViewIdFromTransportId(transport_view_id));
+ ViewManagerServiceImpl* existing_owner =
+ connection_manager_->GetConnectionWithRoot(view_id);
+
+ ConnectionManager::ScopedChange change(this, connection_manager_, true);
+ RemoveChildrenAsPartOfEmbed(view_id);
+ if (existing_owner) {
+ // Never message the originating connection.
+ connection_manager_->OnConnectionMessagedClient(id_);
+ existing_owner->RemoveRoot(view_id);
+ }
+ connection_manager_->Embed(id_, url, transport_view_id, spir.Pass());
+ callback.Run(true);
+}
+
+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));
+
+ // If another app is embedded at this view, we forward the input event to the
+ // embedded app, rather than the app that created the view.
+ ViewManagerServiceImpl* connection =
+ connection_manager_->GetConnectionWithRoot(view_id);
+ if (!connection)
+ connection = connection_manager_->GetConnection(view_id.connection_id);
+ if (connection) {
+ connection->client()->OnViewInputEvent(
+ transport_view_id,
+ event.Pass(),
+ base::Bind(&base::DoNothing));
+ }
+}
+
+void ViewManagerServiceImpl::OnConnectionEstablished() {
+ connection_manager_->AddConnection(this);
+
+ std::vector<const ServerView*> to_send;
+ for (ViewIdSet::const_iterator i = roots_.begin(); i != roots_.end(); ++i)
+ GetUnknownViewsFrom(GetView(ViewIdFromTransportId(*i)), &to_send);
+
+ client()->OnEmbed(id_,
+ creator_url_,
+ ViewToViewData(to_send.front()),
+ service_provider_.Pass());
+}
+
+const base::hash_set<Id>&
+ViewManagerServiceImpl::GetRootsForAccessPolicy() const {
+ return roots_;
+}
+
+bool ViewManagerServiceImpl::IsViewKnownForAccessPolicy(
+ const ServerView* view) const {
+ return IsViewKnown(view);
+}
+
+bool ViewManagerServiceImpl::IsViewRootOfAnotherConnectionForAccessPolicy(
+ const ServerView* view) const {
+ ViewManagerServiceImpl* connection =
+ connection_manager_->GetConnectionWithRoot(view->id());
+ return connection && connection != this;
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/view_manager_service_impl.h b/mojo/services/view_manager/view_manager_service_impl.h
new file mode 100644
index 0000000..0295119
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_service_impl.h
@@ -0,0 +1,240 @@
+// Copyright 2014 The Chromium 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 "base/memory/scoped_ptr.h"
+#include "mojo/services/public/interfaces/surfaces/surface_id.mojom.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+#include "mojo/services/view_manager/access_policy_delegate.h"
+#include "mojo/services/view_manager/ids.h"
+#include "mojo/services/view_manager/view_manager_export.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace mojo {
+namespace service {
+
+class AccessPolicy;
+class ConnectionManager;
+class ServerView;
+
+#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 AccessPolicyDelegate {
+ public:
+ ViewManagerServiceImpl(ConnectionManager* connection_manager,
+ ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id,
+ InterfaceRequest<ServiceProvider> service_provider);
+ 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 View with the specified id.
+ ServerView* GetView(const ViewId& id) {
+ return const_cast<ServerView*>(
+ const_cast<const ViewManagerServiceImpl*>(this)->GetView(id));
+ }
+ const ServerView* GetView(const ViewId& id) const;
+
+ // Returns true if this has |id| as a root.
+ bool HasRoot(const ViewId& id) const;
+
+ // 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 ProcessViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds,
+ bool originated_change);
+ void ProcessWillChangeViewHierarchy(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change);
+ void ProcessViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change);
+ void ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction,
+ bool originated_change);
+ void ProcessViewDeleted(const ViewId& view, bool originated_change);
+ void ProcessWillChangeViewVisibility(const ServerView* view,
+ bool originated_change);
+
+ // TODO(sky): move this to private section (currently can't because of
+ // bindings).
+ // InterfaceImp overrides:
+ virtual void OnConnectionError() override;
+
+ private:
+ typedef std::map<ConnectionSpecificId, ServerView*> ViewMap;
+ typedef base::hash_set<Id> ViewIdSet;
+
+ bool IsViewKnown(const ServerView* view) const;
+
+ // These functions return true if the corresponding mojom function is allowed
+ // for this connection.
+ bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const;
+
+ // Deletes a view owned by this connection. Returns true on success. |source|
+ // is the connection that originated the change.
+ bool DeleteViewImpl(ViewManagerServiceImpl* source, ServerView* view);
+
+ // If |view| is known (in |known_views_|) does nothing. Otherwise adds |view|
+ // to |views|, marks |view| as known and recurses.
+ void GetUnknownViewsFrom(const ServerView* view,
+ std::vector<const ServerView*>* views);
+
+ // Removes |view| and all its descendants from |known_views_|. This does not
+ // recurse through views that were created by this connection. All views owned
+ // by this connection are added to |local_views|.
+ void RemoveFromKnown(const ServerView* view,
+ std::vector<ServerView*>* local_views);
+
+ // Removes |view_id| from the set of roots this connection knows about.
+ void RemoveRoot(const ViewId& view_id);
+
+ void RemoveChildrenAsPartOfEmbed(const ViewId& view_id);
+
+ // Converts View(s) to ViewData(s) for transport. This assumes all the views
+ // are valid for the client. The parent of views the client is not allowed to
+ // see are set to NULL (in the returned ViewData(s)).
+ Array<ViewDataPtr> ViewsToViewDatas(
+ const std::vector<const ServerView*>& views);
+ ViewDataPtr ViewToViewData(const ServerView* view);
+
+ // Implementation of GetViewTree(). Adds |view| to |views| and recurses if
+ // CanDescendIntoViewForViewTree() returns true.
+ void GetViewTreeImpl(const ServerView* view,
+ std::vector<const ServerView*>* views) const;
+
+ // Notify the client if the drawn state of any of the roots changes.
+ // |view| is the view that is changing to the drawn state |new_drawn_value|.
+ void NotifyDrawnStateChanged(const ServerView* view, bool new_drawn_value);
+
+ // ViewManagerService:
+ virtual void CreateView(Id transport_view_id,
+ const Callback<void(ErrorCode)>& callback) OVERRIDE;
+ virtual void DeleteView(Id transport_view_id,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void AddView(Id parent_id,
+ Id child_id,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void RemoveViewFromParent(
+ Id view_id,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void ReorderView(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void GetViewTree(
+ Id view_id,
+ const Callback<void(Array<ViewDataPtr>)>& callback) OVERRIDE;
+ virtual void SetViewSurfaceId(Id view_id,
+ SurfaceIdPtr surface_id,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void SetViewBounds(Id view_id,
+ RectPtr bounds,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void SetViewVisibility(Id view_id,
+ bool visible,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void Embed(const String& url,
+ Id view_id,
+ ServiceProviderPtr service_provider,
+ const Callback<void(bool)>& callback) OVERRIDE;
+ virtual void DispatchOnViewInputEvent(Id view_id, EventPtr event) OVERRIDE;
+
+ // InterfaceImpl:
+ virtual void OnConnectionEstablished() override;
+
+ // AccessPolicyDelegate:
+ virtual const base::hash_set<Id>& GetRootsForAccessPolicy() const OVERRIDE;
+ virtual bool IsViewKnownForAccessPolicy(
+ const ServerView* view) const OVERRIDE;
+ virtual bool IsViewRootOfAnotherConnectionForAccessPolicy(
+ const ServerView* view) const OVERRIDE;
+
+ ConnectionManager* connection_manager_;
+
+ // Id of this connection as assigned by ConnectionManager.
+ 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_;
+
+ scoped_ptr<AccessPolicy> access_policy_;
+
+ // The views and views created by this connection. This connection owns these
+ // objects.
+ ViewMap view_map_;
+
+ // The set of views that has been communicated to the client.
+ ViewIdSet known_views_;
+
+ // Set of root views from other connections. More specifically any time
+ // Embed() is invoked the id of the view is added to this set for the child
+ // connection. The connection Embed() was invoked on (the parent) doesn't
+ // directly track which connections are attached to which of its views. That
+ // information can be found by looking through the |roots_| of all
+ // connections.
+ ViewIdSet roots_;
+
+ // See description above setter.
+ bool delete_on_connection_error_;
+
+ InterfaceRequest<ServiceProvider> service_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceImpl);
+};
+
+#if defined(OS_WIN)
+#pragma warning(pop)
+#endif
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_
diff --git a/mojo/services/view_manager/view_manager_unittest.cc b/mojo/services/view_manager/view_manager_unittest.cc
new file mode 100644
index 0000000..cc972d4
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_unittest.cc
@@ -0,0 +1,1434 @@
+// Copyright 2014 The Chromium 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/application_manager/application_manager.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/bindings/lib/router.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.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"
+
+#if defined(OS_WIN)
+#include "ui/gfx/win/window_impl.h"
+#endif
+
+namespace mojo {
+namespace service {
+
+namespace {
+
+const char kTestServiceURL[] = "mojo:test_url";
+const char kTestServiceURL2[] = "mojo:test_url2";
+
+// 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),
+ main_loop_(NULL),
+ view_manager_(NULL),
+ quit_count_(0),
+ router_(NULL) {
+ SetInstance(this);
+ }
+
+ virtual ~ViewManagerProxy() {
+ }
+
+ // Returns true if in an initial state. If this returns false it means the
+ // last test didn't clean up properly, or most likely didn't invoke
+ // WaitForInstance() when it needed to.
+ static bool IsInInitialState() { return instance_ == NULL; }
+
+ // 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();
+ }
+
+ void ClearChanges() {
+ changes_.clear();
+ tracker_->changes()->clear();
+ }
+
+ void CopyChangesFromTracker() {
+ std::vector<Change> changes;
+ tracker_->changes()->swap(changes);
+ changes_.swap(changes);
+ }
+
+ // The following functions are cover methods for ViewManagerService. They
+ // block until the result is received.
+ bool CreateView(Id view_id) {
+ changes_.clear();
+ ErrorCode result = ERROR_CODE_NONE;
+ view_manager_->CreateView(
+ view_id,
+ base::Bind(&ViewManagerProxy::GotResultWithErrorCode,
+ base::Unretained(this),
+ &result));
+ RunMainLoop();
+ return result == ERROR_CODE_NONE;
+ }
+ ErrorCode CreateViewWithErrorCode(Id view_id) {
+ changes_.clear();
+ ErrorCode result = ERROR_CODE_NONE;
+ view_manager_->CreateView(
+ view_id,
+ base::Bind(&ViewManagerProxy::GotResultWithErrorCode,
+ base::Unretained(this),
+ &result));
+ RunMainLoop();
+ return result;
+ }
+ bool AddView(Id parent, Id child) {
+ changes_.clear();
+ bool result = false;
+ view_manager_->AddView(parent, child,
+ base::Bind(&ViewManagerProxy::GotResult,
+ base::Unretained(this), &result));
+ RunMainLoop();
+ return result;
+ }
+ bool RemoveViewFromParent(Id view_id) {
+ changes_.clear();
+ bool result = false;
+ view_manager_->RemoveViewFromParent(
+ view_id,
+ base::Bind(
+ &ViewManagerProxy::GotResult, base::Unretained(this), &result));
+ RunMainLoop();
+ return result;
+ }
+ bool ReorderView(Id view_id, Id relative_view_id, OrderDirection direction) {
+ changes_.clear();
+ bool result = false;
+ view_manager_->ReorderView(
+ view_id,
+ relative_view_id,
+ direction,
+ base::Bind(
+ &ViewManagerProxy::GotResult, base::Unretained(this), &result));
+ RunMainLoop();
+ return result;
+ }
+ void GetViewTree(Id view_id, std::vector<TestView>* views) {
+ changes_.clear();
+ view_manager_->GetViewTree(
+ view_id,
+ base::Bind(
+ &ViewManagerProxy::GotViewTree, base::Unretained(this), views));
+ RunMainLoop();
+ }
+ bool Embed(const Id view_id, const char* url) {
+ changes_.clear();
+ base::AutoReset<bool> auto_reset(&in_embed_, true);
+ bool result = false;
+ ServiceProviderPtr services;
+ view_manager_->Embed(
+ url,
+ view_id,
+ services.Pass(),
+ 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 SetViewBounds(Id view_id, const gfx::Rect& bounds) {
+ changes_.clear();
+ bool result = false;
+ view_manager_->SetViewBounds(
+ view_id,
+ Rect::From(bounds),
+ base::Bind(
+ &ViewManagerProxy::GotResult, base::Unretained(this), &result));
+ RunMainLoop();
+ return result;
+ }
+ bool SetViewVisibility(Id view_id, bool visible) {
+ changes_.clear();
+ bool result = false;
+ view_manager_->SetViewVisibility(
+ view_id,
+ visible,
+ 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();
+ }
+
+ 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 GotResultWithErrorCode(ErrorCode* error_code_cache,
+ ErrorCode error_code) {
+ *error_code_cache = error_code;
+ DCHECK(main_run_loop_);
+ main_run_loop_->Quit();
+ }
+
+ void GotViewTree(std::vector<TestView>* views, Array<ViewDataPtr> results) {
+ ViewDatasToTestViews(results, views);
+ 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_);
+ }
+
+ // InterfaceImpl:
+ virtual void OnConnectionEstablished() OVERRIDE {
+ connection_.set_router(internal_state()->router());
+ connection_.set_view_manager(client());
+ }
+
+ // ViewManagerClient:
+ virtual void OnEmbed(
+ ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root,
+ InterfaceRequest<ServiceProvider> services) OVERRIDE {
+ tracker_.OnEmbed(connection_id, creator_url, root.Pass());
+ }
+ virtual void OnViewBoundsChanged(Id view_id,
+ RectPtr old_bounds,
+ RectPtr new_bounds) OVERRIDE {
+ tracker_.OnViewBoundsChanged(view_id, old_bounds.Pass(), new_bounds.Pass());
+ }
+ virtual void OnViewHierarchyChanged(Id view,
+ Id new_parent,
+ Id old_parent,
+ Array<ViewDataPtr> views) OVERRIDE {
+ tracker_.OnViewHierarchyChanged(view, new_parent, old_parent, views.Pass());
+ }
+ virtual void OnViewReordered(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) OVERRIDE {
+ tracker_.OnViewReordered(view_id, relative_view_id, direction);
+ }
+ virtual void OnViewDeleted(Id view) OVERRIDE { tracker_.OnViewDeleted(view); }
+ virtual void OnViewVisibilityChanged(uint32_t view, bool visible) OVERRIDE {
+ tracker_.OnViewVisibilityChanged(view, visible);
+ }
+ virtual void OnViewDrawnStateChanged(uint32_t view, bool drawn) OVERRIDE {
+ tracker_.OnViewDrawnStateChanged(view, drawn);
+ }
+ virtual void OnViewInputEvent(Id view_id,
+ EventPtr event,
+ const Callback<void()>& callback) OVERRIDE {
+ tracker_.OnViewInputEvent(view_id, event.Pass());
+ }
+ virtual void Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) OVERRIDE {
+ tracker_.DelegateEmbed(url);
+ }
+ virtual void DispatchOnViewInputEvent(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 EmbedApplicationLoader : public ApplicationLoader,
+ ApplicationDelegate,
+ public InterfaceFactory<ViewManagerClient> {
+ public:
+ EmbedApplicationLoader() {}
+ virtual ~EmbedApplicationLoader() {}
+
+ // ApplicationLoader implementation:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) OVERRIDE {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+ scoped_ptr<ApplicationImpl> app(new ApplicationImpl(this,
+ shell_handle.Pass()));
+ apps_.push_back(app.release());
+ }
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) OVERRIDE {}
+
+ // ApplicationDelegate implementation:
+ virtual bool ConfigureIncomingConnection(ApplicationConnection* connection)
+ OVERRIDE {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory<ViewManagerClient> implementation:
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerClient> request) OVERRIDE {
+ BindToRequest(new TestViewManagerClientConnection, &request);
+ }
+
+ private:
+ ScopedVector<ApplicationImpl> apps_;
+
+ DISALLOW_COPY_AND_ASSIGN(EmbedApplicationLoader);
+};
+
+// 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 Embed(). |result| is the result of the
+// Embed() call and |run_loop| the nested RunLoop.
+void EmbedCallback(bool* result_cache, base::RunLoop* run_loop, bool result) {
+ *result_cache = result;
+ run_loop->Quit();
+}
+
+// Embed from an application that does not yet have a view manager connection.
+// Blocks until result is determined.
+bool InitEmbed(ViewManagerInitService* view_manager_init,
+ const std::string& url,
+ size_t number_of_calls) {
+ bool result = false;
+ base::RunLoop run_loop;
+ for (size_t i = 0; i < number_of_calls; ++i) {
+ ServiceProviderPtr sp;
+ view_manager_init->Embed(url, sp.Pass(),
+ base::Bind(&EmbedCallback, &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),
+ connection3_(NULL) {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(ViewManagerProxy::IsInInitialState());
+ test_helper_.Init();
+
+#if defined(OS_WIN)
+ // As we unload the wndproc of window classes we need to be sure to
+ // unregister them.
+ gfx::WindowImpl::UnregisterClassesAtExit();
+#endif
+
+ test_helper_.SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(new EmbedApplicationLoader()),
+ GURL(kTestServiceURL));
+
+ test_helper_.SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(new EmbedApplicationLoader()),
+ GURL(kTestServiceURL2));
+
+ test_helper_.application_manager()->ConnectToService(
+ GURL("mojo:mojo_view_manager"), &view_manager_init_);
+ ASSERT_TRUE(InitEmbed(view_manager_init_.get(), kTestServiceURL, 1));
+
+ connection_ = ViewManagerProxy::WaitForInstance();
+ ASSERT_TRUE(connection_ != NULL);
+ connection_->DoRunLoopUntilChangesCount(1);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (connection3_)
+ connection3_->Destroy();
+ if (connection2_)
+ connection2_->Destroy();
+ if (connection_)
+ connection_->Destroy();
+ }
+
+ protected:
+ void EstablishSecondConnectionWithRoot(Id root_id) {
+ ASSERT_TRUE(connection_->Embed(root_id, kTestServiceURL));
+ 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_view) {
+ if (create_initial_view)
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishSecondConnectionWithRoot(BuildViewId(1, 1)));
+ const std::vector<Change>& changes(connection2_->changes());
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("OnEmbed creator=mojo:test_url",
+ ChangesToDescription1(changes)[0]);
+ if (create_initial_view)
+ EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(changes));
+ }
+
+ void EstablishThirdConnection(ViewManagerProxy* owner, Id root_id) {
+ ASSERT_TRUE(connection3_ == NULL);
+ ASSERT_TRUE(owner->Embed(root_id, kTestServiceURL2));
+ connection3_ = ViewManagerProxy::WaitForInstance();
+ ASSERT_TRUE(connection3_ != NULL);
+ connection3_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection3_->changes().size());
+ EXPECT_EQ("OnEmbed creator=mojo:test_url",
+ ChangesToDescription1(connection3_->changes())[0]);
+ }
+
+ void DestroySecondConnection() {
+ connection2_->Destroy();
+ connection2_ = NULL;
+ }
+
+ base::ShadowingAtExitManager at_exit_;
+ shell::ShellTestHelper test_helper_;
+
+ ViewManagerInitServicePtr view_manager_init_;
+
+ // NOTE: this connection is the root. As such, it has special permissions.
+ ViewManagerProxy* connection_;
+ ViewManagerProxy* connection2_;
+ ViewManagerProxy* connection3_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerTest);
+};
+
+TEST_F(ViewManagerTest, SecondEmbedRoot_InitService) {
+ ASSERT_TRUE(InitEmbed(view_manager_init_.get(), kTestServiceURL, 1));
+ connection_->DoRunLoopUntilChangesCount(1);
+ EXPECT_EQ(kTestServiceURL, connection_->changes()[0].embed_url);
+}
+
+TEST_F(ViewManagerTest, SecondEmbedRoot_Service) {
+ ASSERT_TRUE(connection_->Embed(BuildViewId(0, 0), kTestServiceURL));
+ connection_->DoRunLoopUntilChangesCount(1);
+ EXPECT_EQ(kTestServiceURL, connection_->changes()[0].embed_url);
+}
+
+TEST_F(ViewManagerTest, MultipleEmbedRootsBeforeWTHReady) {
+ ASSERT_TRUE(InitEmbed(view_manager_init_.get(), kTestServiceURL, 2));
+ connection_->DoRunLoopUntilChangesCount(2);
+ EXPECT_EQ(kTestServiceURL, connection_->changes()[0].embed_url);
+ EXPECT_EQ(kTestServiceURL, connection_->changes()[1].embed_url);
+}
+
+// Verifies client gets a valid id.
+// http://crbug.com/396492
+TEST_F(ViewManagerTest, DISABLED_ValidId) {
+ // TODO(beng): this should really have the URL of the application that
+ // connected to ViewManagerInit.
+ EXPECT_EQ("OnEmbed 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);
+}
+
+// Verifies two clients/connections get different ids.
+TEST_F(ViewManagerTest, TwoClientsGetDifferentConnectionIds) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ EXPECT_EQ("OnEmbed creator=mojo:test_url",
+ ChangesToDescription1(connection2_->changes())[0]);
+
+ // It isn't strictly 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);
+}
+
+// Verifies when Embed() is invoked any child views are removed.
+TEST_F(ViewManagerTest, ViewsRemovedWhenEmbedding) {
+ // Two views 1 and 2. 2 is parented to 1.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 1), BuildViewId(1, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+ EXPECT_EQ("[view=1,1 parent=null]",
+ ChangeViewDescription(connection2_->changes()));
+
+ // Embed() removed view 2.
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(1, 2), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,2 parent=null", views[0].ToString());
+ }
+
+ // |connection2_| should not see view 2.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ }
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(1, 2), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Views 3 and 4 in connection 2.
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 3)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 4)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(2, 3), BuildViewId(2, 4)));
+
+ // Connection 3 rooted at 2.
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishThirdConnection(connection2_, BuildViewId(2, 3)));
+
+ // View 4 should no longer have a parent.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(2, 3), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=2,3 parent=null", views[0].ToString());
+
+ views.clear();
+ connection2_->GetViewTree(BuildViewId(2, 4), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=2,4 parent=null", views[0].ToString());
+ }
+
+ // And view 4 should not be visible to connection 3.
+ {
+ std::vector<TestView> views;
+ connection3_->GetViewTree(BuildViewId(2, 3), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=2,3 parent=null", views[0].ToString());
+ }
+}
+
+// Verifies once Embed() has been invoked the parent connection can't see any
+// children.
+TEST_F(ViewManagerTest, CantAccessChildrenOfEmbeddedView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishThirdConnection(connection2_, BuildViewId(2, 2)));
+
+ ASSERT_TRUE(connection3_->CreateView(BuildViewId(3, 3)));
+ ASSERT_TRUE(connection3_->AddView(BuildViewId(2, 2), BuildViewId(3, 3)));
+
+ // Even though 3 is a child of 2 connection 2 can't see 3 as it's from a
+ // different connection.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(2, 2), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=2,2 parent=1,1", views[0].ToString());
+ }
+
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(3, 3), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Connection 2 shouldn't be able to get view 3 at all.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(3, 3), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Connection 1 should be able to see it all (its the root).
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(3u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString());
+ EXPECT_EQ("view=3,3 parent=2,2", views[2].ToString());
+ }
+}
+
+// Verifies once Embed() has been invoked the parent can't mutate the children.
+TEST_F(ViewManagerTest, CantModifyChildrenOfEmbeddedView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishThirdConnection(connection2_, BuildViewId(2, 2)));
+
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 3)));
+ // Connection 2 shouldn't be able to add anything to the view anymore.
+ ASSERT_FALSE(connection2_->AddView(BuildViewId(2, 2), BuildViewId(2, 3)));
+
+ // Create view 3 in connection 3 and add it to view 3.
+ ASSERT_TRUE(connection3_->CreateView(BuildViewId(3, 3)));
+ ASSERT_TRUE(connection3_->AddView(BuildViewId(2, 2), BuildViewId(3, 3)));
+
+ // Connection 2 shouldn't be able to remove view 3.
+ ASSERT_FALSE(connection2_->RemoveViewFromParent(BuildViewId(3, 3)));
+}
+
+// Verifies client gets a valid id.
+TEST_F(ViewManagerTest, CreateView) {
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ EXPECT_TRUE(connection_->changes().empty());
+
+ // Can't create a view with the same id.
+ ASSERT_EQ(ERROR_CODE_VALUE_IN_USE,
+ connection_->CreateViewWithErrorCode(BuildViewId(1, 1)));
+ EXPECT_TRUE(connection_->changes().empty());
+
+ // Can't create a view with a bogus connection id.
+ EXPECT_EQ(ERROR_CODE_ILLEGAL_ARGUMENT,
+ connection_->CreateViewWithErrorCode(BuildViewId(2, 1)));
+ EXPECT_TRUE(connection_->changes().empty());
+}
+
+// Verifies AddView fails when view is already in position.
+TEST_F(ViewManagerTest, AddViewWithNoChange) {
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 3)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Make 3 a child of 2.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 2), BuildViewId(1, 3)));
+
+ // Try again, this should fail.
+ EXPECT_FALSE(connection_->AddView(BuildViewId(1, 2), BuildViewId(1, 3)));
+}
+
+// Verifies AddView fails when view is already in position.
+TEST_F(ViewManagerTest, AddAncestorFails) {
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 3)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Make 3 a child of 2.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 2), BuildViewId(1, 3)));
+
+ // Try to make 2 a child of 3, this should fail since 2 is an ancestor of 3.
+ EXPECT_FALSE(connection_->AddView(BuildViewId(1, 3), BuildViewId(1, 2)));
+}
+
+// Verifies adding to root sends right notifications.
+TEST_F(ViewManagerTest, AddToRoot) {
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 21)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 3)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Make 3 a child of 21.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 21), BuildViewId(1, 3)));
+
+ // Make 21 a child of 1.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 1), BuildViewId(1, 21)));
+
+ // Connection 2 should not be told anything (because the view is from a
+ // different connection). Create a view to ensure we got a response from
+ // the server.
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 100)));
+ connection2_->CopyChangesFromTracker();
+ EXPECT_TRUE(connection2_->changes().empty());
+}
+
+// Verifies HierarchyChanged is correctly sent for various adds/removes.
+TEST_F(ViewManagerTest, ViewHierarchyChangedViews) {
+ // 1,2->1,11.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 11)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 2), BuildViewId(1, 11)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // 1,1->1,2->1,11
+ {
+ // Client 2 should not get anything (1,2 is from another connection).
+ connection2_->ClearChanges();
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 1), BuildViewId(1, 2)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 100)));
+ connection2_->CopyChangesFromTracker();
+ EXPECT_TRUE(connection2_->changes().empty());
+ }
+
+ // 0,1->1,1->1,2->1,11.
+ {
+ // Client 2 is now connected to the root, so it should have gotten a drawn
+ // notification.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+
+ // 1,1->1,2->1,11.
+ {
+ // Client 2 is no longer connected to the root, should get drawn state
+ // changed.
+ ASSERT_TRUE(connection_->RemoveViewFromParent(BuildViewId(1, 1)));
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,1 drawn=false",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+
+ // 1,1->1,2->1,11->1,111.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 111)));
+ {
+ connection2_->ClearChanges();
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 11), BuildViewId(1, 111)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 103)));
+ connection2_->CopyChangesFromTracker();
+ EXPECT_TRUE(connection2_->changes().empty());
+ }
+
+ // 0,1->1,1->1,2->1,11->1,111
+ {
+ connection2_->ClearChanges();
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+}
+
+TEST_F(ViewManagerTest, ViewHierarchyChangedAddingKnownToUnknown) {
+ // Create the following structure: root -> 1 -> 11 and 2->21 (2 has no
+ // parent).
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 11)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 21)));
+
+ // Set up the hierarchy.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 11)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(2, 2), BuildViewId(2, 21)));
+
+ // Remove 11, should result in a hierarchy change for the root.
+ {
+ connection_->ClearChanges();
+ ASSERT_TRUE(connection2_->RemoveViewFromParent(BuildViewId(2, 11)));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("HierarchyChanged view=2,11 new_parent=null old_parent=1,1",
+ changes[0]);
+ }
+
+ // Add 2 to 1.
+ {
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ changes[0]);
+ EXPECT_EQ(
+ "[view=2,2 parent=1,1],"
+ "[view=2,21 parent=2,2]",
+ ChangeViewDescription(connection_->changes()));
+ }
+}
+
+TEST_F(ViewManagerTest, ReorderView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ Id view1_id = BuildViewId(2, 1);
+ Id view2_id = BuildViewId(2, 2);
+ Id view3_id = BuildViewId(2, 3);
+ Id view4_id = BuildViewId(1, 4); // Peer to 1,1
+ Id view5_id = BuildViewId(1, 5); // Peer to 1,1
+ Id view6_id = BuildViewId(2, 6); // Child of 1,2.
+ Id view7_id = BuildViewId(2, 7); // Unparented.
+ Id view8_id = BuildViewId(2, 8); // Unparented.
+ ASSERT_TRUE(connection2_->CreateView(view1_id));
+ ASSERT_TRUE(connection2_->CreateView(view2_id));
+ ASSERT_TRUE(connection2_->CreateView(view3_id));
+ ASSERT_TRUE(connection_->CreateView(view4_id));
+ ASSERT_TRUE(connection_->CreateView(view5_id));
+ ASSERT_TRUE(connection2_->CreateView(view6_id));
+ ASSERT_TRUE(connection2_->CreateView(view7_id));
+ ASSERT_TRUE(connection2_->CreateView(view8_id));
+ ASSERT_TRUE(connection2_->AddView(view1_id, view2_id));
+ ASSERT_TRUE(connection2_->AddView(view2_id, view6_id));
+ ASSERT_TRUE(connection2_->AddView(view1_id, view3_id));
+ ASSERT_TRUE(
+ connection_->AddView(ViewIdToTransportId(RootViewId()), view4_id));
+ ASSERT_TRUE(
+ connection_->AddView(ViewIdToTransportId(RootViewId()), view5_id));
+
+ ASSERT_TRUE(
+ connection_->AddView(ViewIdToTransportId(RootViewId()), view1_id));
+
+ {
+ ASSERT_TRUE(
+ connection2_->ReorderView(view2_id, view3_id, ORDER_DIRECTION_ABOVE));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=above", changes[0]);
+ }
+
+ {
+ ASSERT_TRUE(
+ connection2_->ReorderView(view2_id, view3_id, ORDER_DIRECTION_BELOW));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=below", changes[0]);
+ }
+
+ // view2 is already below view3.
+ EXPECT_FALSE(
+ connection2_->ReorderView(view2_id, view3_id, ORDER_DIRECTION_BELOW));
+
+ // view4 & 5 are unknown to connection2_.
+ EXPECT_FALSE(
+ connection2_->ReorderView(view4_id, view5_id, ORDER_DIRECTION_ABOVE));
+
+ // view6 & view3 have different parents.
+ EXPECT_FALSE(
+ connection_->ReorderView(view3_id, view6_id, ORDER_DIRECTION_ABOVE));
+
+ // Non-existent view-ids
+ EXPECT_FALSE(connection_->ReorderView(
+ BuildViewId(1, 27), BuildViewId(1, 28), ORDER_DIRECTION_ABOVE));
+
+ // view7 & view8 are un-parented.
+ EXPECT_FALSE(
+ connection_->ReorderView(view7_id, view8_id, ORDER_DIRECTION_ABOVE));
+}
+
+// Verifies DeleteView works.
+TEST_F(ViewManagerTest, DeleteView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+
+ // Make 2 a child of 1.
+ {
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ changes[0]);
+ }
+
+ // Delete 2.
+ {
+ ASSERT_TRUE(connection2_->DeleteView(BuildViewId(2, 2)));
+ EXPECT_TRUE(connection2_->changes().empty());
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("ViewDeleted view=2,2", changes[0]);
+ }
+}
+
+// Verifies DeleteView isn't allowed from a separate connection.
+TEST_F(ViewManagerTest, DeleteViewFromAnotherConnectionDisallowed) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ EXPECT_FALSE(connection2_->DeleteView(BuildViewId(1, 1)));
+}
+
+// Verifies if a view was deleted and then reused that other clients are
+// properly notified.
+TEST_F(ViewManagerTest, ReuseDeletedViewId) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+
+ // Add 2 to 1.
+ {
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ changes[0]);
+ EXPECT_EQ("[view=2,2 parent=1,1]",
+ ChangeViewDescription(connection_->changes()));
+ }
+
+ // Delete 2.
+ {
+ ASSERT_TRUE(connection2_->DeleteView(BuildViewId(2, 2)));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("ViewDeleted view=2,2", changes[0]);
+ }
+
+ // Create 2 again, and add it back to 1. Should get the same notification.
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+ {
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ changes[0]);
+ EXPECT_EQ("[view=2,2 parent=1,1]",
+ ChangeViewDescription(connection_->changes()));
+ }
+}
+
+// Assertions for GetViewTree.
+TEST_F(ViewManagerTest, GetViewTree) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Create 11 in first connection and make it a child of 1.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 11)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 1), BuildViewId(1, 11)));
+
+ // Create two views in second connection, 2 and 3, both children of 1.
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 2)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 3)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 2)));
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 1), BuildViewId(2, 3)));
+
+ // Verifies GetViewTree() on the root. The root connection sees all.
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(0, 1), &views);
+ ASSERT_EQ(5u, views.size());
+ EXPECT_EQ("view=0,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString());
+ EXPECT_EQ("view=1,11 parent=1,1", views[2].ToString());
+ EXPECT_EQ("view=2,2 parent=1,1", views[3].ToString());
+ EXPECT_EQ("view=2,3 parent=1,1", views[4].ToString());
+ }
+
+ // Verifies GetViewTree() on the view 1,1. This does not include any children
+ // as they are not from this connection.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ }
+
+ // Connection 2 shouldn't be able to get the root tree.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(0, 1), &views);
+ ASSERT_EQ(0u, views.size());
+ }
+}
+
+TEST_F(ViewManagerTest, SetViewBounds) {
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ ASSERT_TRUE(
+ connection_->SetViewBounds(BuildViewId(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 view=1,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100",
+ changes[0]);
+
+ // Should not be possible to change the bounds of a view created by another
+ // connection.
+ ASSERT_FALSE(
+ connection2_->SetViewBounds(BuildViewId(1, 1), gfx::Rect(0, 0, 0, 0)));
+}
+
+// Verify AddView fails when trying to manipulate views in other roots.
+TEST_F(ViewManagerTest, CantMoveViewsFromOtherRoot) {
+ // Create 1 and 2 in the first connection.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(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_->AddView(BuildViewId(1, 1), BuildViewId(1, 2)));
+
+ // Try to reparent 1 to the root. A connection is not allowed to reparent its
+ // roots.
+ ASSERT_FALSE(connection2_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+}
+
+// Verify RemoveViewFromParent fails for views that are descendants of the
+// roots.
+TEST_F(ViewManagerTest, CantRemoveViewsInOtherRoots) {
+ // Create 1 and 2 in the first connection and parent both to the root.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 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 view 2 or 1 from its parent.
+ ASSERT_FALSE(connection2_->RemoveViewFromParent(BuildViewId(1, 2)));
+ ASSERT_FALSE(connection2_->RemoveViewFromParent(BuildViewId(1, 1)));
+
+ // Create views 10 and 11 in 2.
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 10)));
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 11)));
+
+ // Parent 11 to 10.
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(2, 10), BuildViewId(2, 11)));
+ // Remove 11 from 10.
+ ASSERT_TRUE(connection2_->RemoveViewFromParent(BuildViewId(2, 11)));
+
+ // Verify nothing was actually removed.
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(0, 1), &views);
+ ASSERT_EQ(3u, views.size());
+ EXPECT_EQ("view=0,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString());
+ EXPECT_EQ("view=1,2 parent=0,1", views[2].ToString());
+ }
+}
+
+// Verify GetViewTree fails for views that are not descendants of the roots.
+TEST_F(ViewManagerTest, CantGetViewTreeOfOtherRoots) {
+ // Create 1 and 2 in the first connection and parent both to the root.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ std::vector<TestView> views;
+
+ // Should get nothing for the root.
+ connection2_->GetViewTree(BuildViewId(0, 1), &views);
+ ASSERT_TRUE(views.empty());
+
+ // Should get nothing for view 2.
+ connection2_->GetViewTree(BuildViewId(1, 2), &views);
+ ASSERT_TRUE(views.empty());
+
+ // Should get view 1 if asked for.
+ connection2_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+}
+
+TEST_F(ViewManagerTest, OnViewInput) {
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ // Dispatch an event to the view and verify its received.
+ {
+ EventPtr event(Event::New());
+ event->action = static_cast<EventType>(1);
+ connection_->view_manager()->DispatchOnViewInputEvent(BuildViewId(1, 1),
+ event.Pass());
+ connection2_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection2_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("InputEvent view=1,1 event_action=1", changes[0]);
+ }
+}
+
+TEST_F(ViewManagerTest, EmbedWithSameViewId) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishThirdConnection(connection_, BuildViewId(1, 1)));
+
+ // Connection2 should have been told the view was deleted.
+ {
+ connection2_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection2_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("ViewDeleted view=1,1", changes[0]);
+ }
+
+ // Connection2 has no root. Verify it can't see view 1,1 anymore.
+ {
+ std::vector<TestView> views;
+ connection2_->GetViewTree(BuildViewId(1, 1), &views);
+ EXPECT_TRUE(views.empty());
+ }
+}
+
+TEST_F(ViewManagerTest, EmbedWithSameViewId2) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishThirdConnection(connection_, BuildViewId(1, 1)));
+
+ // Connection2 should have been told the view was deleted.
+ connection2_->DoRunLoopUntilChangesCount(1);
+ connection2_->ClearChanges();
+
+ // Create a view in the third connection and parent it to the root.
+ ASSERT_TRUE(connection3_->CreateView(BuildViewId(3, 1)));
+ ASSERT_TRUE(connection3_->AddView(BuildViewId(1, 1), BuildViewId(3, 1)));
+
+ // Connection 1 should have been told about the add (it owns the view).
+ {
+ connection_->DoRunLoopUntilChangesCount(1);
+ const Changes changes(ChangesToDescription1(connection_->changes()));
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("HierarchyChanged view=3,1 new_parent=1,1 old_parent=null",
+ changes[0]);
+ }
+
+ // Embed 1,1 again.
+ {
+ // We should get a new connection for the new embedding.
+ ASSERT_TRUE(connection_->Embed(BuildViewId(1, 1), kTestServiceURL));
+ ViewManagerProxy* connection4 = ViewManagerProxy::WaitForInstance();
+ connection4->DoRunLoopUntilChangesCount(1);
+ const std::vector<Change>& changes(connection4->changes());
+ ASSERT_EQ(1u, changes.size());
+ EXPECT_EQ("OnEmbed creator=mojo:test_url",
+ ChangesToDescription1(changes)[0]);
+ EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(changes));
+
+ // And 3 should get a delete.
+ connection3_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection3_->changes().size());
+ EXPECT_EQ("ViewDeleted view=1,1",
+ ChangesToDescription1(connection3_->changes())[0]);
+ }
+
+ // Connection3_ has no root. Verify it can't see view 1,1 anymore.
+ {
+ std::vector<TestView> views;
+ connection3_->GetViewTree(BuildViewId(1, 1), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Verify 3,1 is no longer parented to 1,1. We have to do this from 1,1 as
+ // connection3_ can no longer see 1,1.
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ }
+
+ // Verify connection3_ can still see the view it created 3,1.
+ {
+ std::vector<TestView> views;
+ connection3_->GetViewTree(BuildViewId(3, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=3,1 parent=null", views[0].ToString());
+ }
+}
+
+// Assertions for SetViewVisibility.
+TEST_F(ViewManagerTest, SetViewVisibility) {
+ // Create 1 and 2 in the first connection and parent both to the root.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(0, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=0,1 parent=null visible=true drawn=true",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true",
+ views[1].ToString2());
+ }
+
+ // Hide 1.
+ ASSERT_TRUE(connection_->SetViewVisibility(BuildViewId(1, 1), false));
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false",
+ views[0].ToString2());
+ }
+
+ // Attach 2 to 1.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 1), BuildViewId(1, 2)));
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=false",
+ views[1].ToString2());
+ }
+
+ // Show 1.
+ ASSERT_TRUE(connection_->SetViewVisibility(BuildViewId(1, 1), true));
+ {
+ std::vector<TestView> views;
+ connection_->GetViewTree(BuildViewId(1, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=true",
+ views[1].ToString2());
+ }
+}
+
+// Assertions for SetViewVisibility sending notifications.
+TEST_F(ViewManagerTest, SetViewVisibilityNotifications) {
+ // Create 1,1 and 1,2, 1,2 and child of 1,1 and 1,1 a child of the root.
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->CreateView(BuildViewId(1, 2)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(connection_->AddView(BuildViewId(1, 1), BuildViewId(1, 2)));
+
+ // Establish the second connection at 1,2.
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishSecondConnectionWithRoot(BuildViewId(1, 2)));
+
+ // Add 2,3 as a child of 1,2.
+ ASSERT_TRUE(connection2_->CreateView(BuildViewId(2, 3)));
+ connection_->ClearChanges();
+ ASSERT_TRUE(connection2_->AddView(BuildViewId(1, 2), BuildViewId(2, 3)));
+ connection_->DoRunLoopUntilChangesCount(1);
+
+ // Hide 1,2 from connection 1. Connection 2 should see this.
+ ASSERT_TRUE(connection_->SetViewVisibility(BuildViewId(1, 2), false));
+ {
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("VisibilityChanged view=1,2 visible=false",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+
+ // Show 1,2 from connection 2, connection 1 should be notified.
+ ASSERT_TRUE(connection2_->SetViewVisibility(BuildViewId(1, 2), true));
+ {
+ connection_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection_->changes().size());
+ EXPECT_EQ("VisibilityChanged view=1,2 visible=true",
+ ChangesToDescription1(connection_->changes())[0]);
+ }
+
+ // Hide 1,1, connection 2 should be told the draw state changed.
+ ASSERT_TRUE(connection_->SetViewVisibility(BuildViewId(1, 1), false));
+ {
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+
+ // Show 1,1 from connection 1. Connection 2 should see this.
+ ASSERT_TRUE(connection_->SetViewVisibility(BuildViewId(1, 1), true));
+ {
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+
+ // Change visibility of 2,3, connection 1 should see this.
+ connection_->ClearChanges();
+ ASSERT_TRUE(connection2_->SetViewVisibility(BuildViewId(2, 3), false));
+ {
+ connection_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection_->changes().size());
+ EXPECT_EQ("VisibilityChanged view=2,3 visible=false",
+ ChangesToDescription1(connection_->changes())[0]);
+ }
+
+ // Remove 1,1 from the root, connection 2 should see drawn state changed.
+ ASSERT_TRUE(connection_->RemoveViewFromParent(BuildViewId(1, 1)));
+ {
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+
+ // Add 1,1 back to the root, connection 2 should see drawn state changed.
+ ASSERT_TRUE(connection_->AddView(BuildViewId(0, 1), BuildViewId(1, 1)));
+ {
+ connection2_->DoRunLoopUntilChangesCount(1);
+ ASSERT_EQ(1u, connection2_->changes().size());
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true",
+ ChangesToDescription1(connection2_->changes())[0]);
+ }
+}
+
+// TODO(sky): add coverage of test that destroys connections and ensures other
+// connections get deletion notification.
+
+// TODO(sky): need to better track changes to initial connection. For example,
+// that SetBounsdViews/AddView and the like don't result in messages to the
+// originating connection.
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/window_manager_access_policy.cc b/mojo/services/view_manager/window_manager_access_policy.cc
new file mode 100644
index 0000000..f781534
--- /dev/null
+++ b/mojo/services/view_manager/window_manager_access_policy.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/services/view_manager/window_manager_access_policy.h"
+
+#include "mojo/services/view_manager/access_policy_delegate.h"
+#include "mojo/services/view_manager/server_view.h"
+
+namespace mojo {
+namespace service {
+
+// TODO(sky): document why this differs from default for each case. Maybe want
+// to subclass DefaultAccessPolicy.
+
+WindowManagerAccessPolicy::WindowManagerAccessPolicy(
+ ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate)
+ : connection_id_(connection_id),
+ delegate_(delegate) {
+}
+
+WindowManagerAccessPolicy::~WindowManagerAccessPolicy() {
+}
+
+bool WindowManagerAccessPolicy::CanRemoveViewFromParent(
+ const ServerView* view) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanAddView(const ServerView* parent,
+ const ServerView* child) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanDeleteView(const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanGetViewTree(const ServerView* view) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanDescendIntoViewForViewTree(
+ const ServerView* view) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanEmbed(const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanChangeViewVisibility(
+ const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanSetViewSurfaceId(
+ const ServerView* view) const {
+ if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view))
+ return false;
+ return view->id().connection_id == connection_id_ ||
+ (delegate_->GetRootsForAccessPolicy().count(
+ ViewIdToTransportId(view->id())) > 0);
+}
+
+bool WindowManagerAccessPolicy::CanSetViewBounds(const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const {
+ // Notify if we've already told the window manager about the view, or if we've
+ // already told the window manager about the parent. The later handles the
+ // case of a view that wasn't parented to the root getting added to the root.
+ return IsViewKnown(view) || (*new_parent && IsViewKnown(*new_parent));
+}
+
+bool WindowManagerAccessPolicy::IsViewKnown(const ServerView* view) const {
+ return delegate_->IsViewKnownForAccessPolicy(view);
+}
+
+} // namespace service
+} // namespace mojo
diff --git a/mojo/services/view_manager/window_manager_access_policy.h b/mojo/services/view_manager/window_manager_access_policy.h
new file mode 100644
index 0000000..5730a18
--- /dev/null
+++ b/mojo/services/view_manager/window_manager_access_policy.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_SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_
+#define MOJO_SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_
+
+#include "base/basictypes.h"
+#include "mojo/services/view_manager/access_policy.h"
+
+namespace mojo {
+namespace service {
+
+class AccessPolicyDelegate;
+
+class WindowManagerAccessPolicy : public AccessPolicy {
+ public:
+ WindowManagerAccessPolicy(ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate);
+ virtual ~WindowManagerAccessPolicy();
+
+ // AccessPolicy:
+ virtual bool CanRemoveViewFromParent(const ServerView* view) const OVERRIDE;
+ virtual bool CanAddView(const ServerView* parent,
+ const ServerView* child) const OVERRIDE;
+ virtual bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const OVERRIDE;
+ virtual bool CanDeleteView(const ServerView* view) const OVERRIDE;
+ virtual bool CanGetViewTree(const ServerView* view) const OVERRIDE;
+ virtual bool CanDescendIntoViewForViewTree(
+ const ServerView* view) const OVERRIDE;
+ virtual bool CanEmbed(const ServerView* view) const OVERRIDE;
+ virtual bool CanChangeViewVisibility(const ServerView* view) const OVERRIDE;
+ virtual bool CanSetViewSurfaceId(const ServerView* view) const OVERRIDE;
+ virtual bool CanSetViewBounds(const ServerView* view) const OVERRIDE;
+ virtual bool ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const OVERRIDE;
+
+ private:
+ bool IsViewKnown(const ServerView* view) const;
+
+ const ConnectionSpecificId connection_id_;
+ AccessPolicyDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerAccessPolicy);
+};
+
+} // namespace service
+} // namespace mojo
+
+#endif // MOJO_SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_
diff --git a/mojo/services/window_manager/BUILD.gn b/mojo/services/window_manager/BUILD.gn
new file mode 100644
index 0000000..b439775
--- /dev/null
+++ b/mojo/services/window_manager/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+if (use_aura) {
+
+# GYP version: mojo/mojo_services.gypi:mojo_core_window_manager
+shared_library("window_manager") {
+ output_name = "mojo_core_window_manager"
+
+ sources = [ "main.cc" ]
+
+ public_deps = [
+ ":lib",
+ ]
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/services/public/cpp/view_manager",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_core_window_manager_lib
+source_set("lib") {
+ sources = [
+ "window_manager_app.cc",
+ "window_manager_app.h",
+ "window_manager_service_impl.cc",
+ "window_manager_service_impl.h",
+ ]
+
+ public_deps = [
+ "//mojo/aura",
+ ]
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/bindings",
+ "//mojo/services/public/cpp/input_events",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/window_manager",
+ "//ui/aura",
+ "//ui/base",
+ "//ui/events",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/wm",
+ ]
+}
+
+# GYP version: mojo/mojo_services.gypi:mojo_core_window_manager_unittests
+test("mojo_core_window_manager_unittests") {
+ sources = [
+ "window_manager_api_unittest.cc",
+ "window_manager_unittests.cc",
+ ]
+
+ deps = [
+ "//base/test:test_support",
+ "//mojo/application_manager",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/services/public/cpp/view_manager",
+ "//mojo/services/public/interfaces/view_manager",
+ "//mojo/services/public/interfaces/window_manager",
+ "//mojo/shell:test_support",
+ "//testing/gtest",
+ "//ui/gl",
+ ]
+ if (use_x11) {
+ deps += [ "//ui/gfx/x" ]
+ }
+}
+
+} # use_aura
diff --git a/mojo/services/window_manager/DEPS b/mojo/services/window_manager/DEPS
new file mode 100644
index 0000000..3421f3b
--- /dev/null
+++ b/mojo/services/window_manager/DEPS
@@ -0,0 +1,13 @@
+include_rules = [
+ "+mojo/aura",
+ "+mojo/application",
+ "+mojo/application_manager",
+ "+mojo/services/native_viewport",
+ "+mojo/services/public",
+ "+ui/aura",
+ "+ui/base",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/gl",
+ "+ui/wm",
+]
diff --git a/mojo/services/window_manager/main.cc b/mojo/services/window_manager/main.cc
new file mode 100644
index 0000000..ea0edb1
--- /dev/null
+++ b/mojo/services/window_manager/main.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 "base/memory/scoped_ptr.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/service_provider_impl.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/window_manager_delegate.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+
+// ApplicationDelegate implementation file for WindowManager users (e.g.
+// core window manager tests) that do not want to provide their own
+// ApplicationDelegate::Create().
+
+namespace mojo {
+
+class DefaultWindowManager : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public WindowManagerDelegate {
+ public:
+ DefaultWindowManager()
+ : window_manager_app_(new WindowManagerApp(this, this)),
+ view_manager_(NULL),
+ root_(NULL) {}
+ virtual ~DefaultWindowManager() {}
+
+ private:
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* impl) override {
+ window_manager_app_->Initialize(impl);
+ }
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ window_manager_app_->ConfigureIncomingConnection(connection);
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ view_manager_ = view_manager;
+ root_ = root;
+ view_manager_->SetWindowManagerDelegate(this);
+ }
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {}
+
+ // Overridden from WindowManagerDelegate:
+ virtual void Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) override {
+ View* view = View::Create(view_manager_);
+ root_->AddChild(view);
+ view->Embed(url, scoped_ptr<mojo::ServiceProviderImpl>(
+ new mojo::ServiceProviderImpl).Pass());
+ }
+ virtual void DispatchEvent(EventPtr event) override {}
+
+ scoped_ptr<WindowManagerApp> window_manager_app_;
+
+ ViewManager* view_manager_;
+ View* root_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DefaultWindowManager);
+};
+
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::DefaultWindowManager);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/window_manager/window_manager_api_unittest.cc b/mojo/services/window_manager/window_manager_api_unittest.cc
new file mode 100644
index 0000000..49bd98e
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_api_unittest.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 "base/bind.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.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_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+#include "mojo/services/public/interfaces/window_manager/window_manager.mojom.h"
+#include "mojo/shell/shell_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const char kTestServiceURL[] = "mojo:test_url";
+
+void EmptyResultCallback(bool result) {}
+
+// Callback from Embed(). |result| is the result of the Embed() call and
+// |run_loop| the nested RunLoop.
+void ResultCallback(bool* result_cache, base::RunLoop* run_loop, bool result) {
+ *result_cache = result;
+ run_loop->Quit();
+}
+
+// Responsible for establishing the initial ViewManagerService connection.
+// Blocks until result is determined.
+bool InitEmbed(ViewManagerInitService* view_manager_init,
+ const std::string& url) {
+ bool result = false;
+ base::RunLoop run_loop;
+ ServiceProviderPtr sp;
+ BindToProxy(new ServiceProviderImpl, &sp);
+ view_manager_init->Embed(url, sp.Pass(),
+ base::Bind(&ResultCallback, &result, &run_loop));
+ run_loop.Run();
+ return result;
+}
+
+class TestWindowManagerClient : public WindowManagerClient {
+ public:
+ typedef base::Callback<void(Id, Id)>
+ TwoNodeCallback;
+
+ explicit TestWindowManagerClient(base::RunLoop* run_loop)
+ : run_loop_(run_loop) {}
+ virtual ~TestWindowManagerClient() {}
+
+ void set_focus_changed_callback(const TwoNodeCallback& callback) {
+ focus_changed_callback_ = callback;
+ }
+ void set_active_window_changed_callback(const TwoNodeCallback& callback) {
+ active_window_changed_callback_ = callback;
+ }
+
+ private:
+ // Overridden from WindowManagerClient:
+ virtual void OnWindowManagerReady() override { run_loop_->Quit(); }
+ virtual void OnCaptureChanged(Id old_capture_node_id,
+ Id new_capture_node_id) override {}
+ virtual void OnFocusChanged(Id old_focused_node_id,
+ Id new_focused_node_id) override {
+ if (!focus_changed_callback_.is_null())
+ focus_changed_callback_.Run(old_focused_node_id, new_focused_node_id);
+ }
+ virtual void OnActiveWindowChanged(Id old_active_window,
+ Id new_active_window) override {
+ if (!active_window_changed_callback_.is_null())
+ active_window_changed_callback_.Run(old_active_window, new_active_window);
+ }
+
+ base::RunLoop* run_loop_;
+ TwoNodeCallback focus_changed_callback_;
+ TwoNodeCallback active_window_changed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindowManagerClient);
+};
+
+class TestApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public ViewManagerDelegate {
+ public:
+ typedef base::Callback<void(View*)> RootAddedCallback;
+
+ explicit TestApplicationLoader(const RootAddedCallback& root_added_callback)
+ : root_added_callback_(root_added_callback) {}
+ virtual ~TestApplicationLoader() {}
+
+ private:
+ // Overridden from ApplicationLoader:
+ virtual void Load(ApplicationManager* application_manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+ scoped_ptr<ApplicationImpl> app(
+ new ApplicationImpl(this, shell_handle.Pass()));
+ apps_.push_back(app.release());
+ }
+ virtual void OnApplicationError(ApplicationManager* application_manager,
+ const GURL& url) override {}
+
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override {
+ root_added_callback_.Run(root);
+ }
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {}
+
+ RootAddedCallback root_added_callback_;
+
+ ScopedVector<ApplicationImpl> apps_;
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader);
+};
+
+} // namespace
+
+class WindowManagerApiTest : public testing::Test {
+ public:
+ WindowManagerApiTest() {}
+ virtual ~WindowManagerApiTest() {}
+
+ protected:
+ typedef std::pair<Id, Id> TwoIds;
+
+ Id WaitForEmbed() {
+ Id id;
+ base::RunLoop run_loop;
+ root_added_callback_ = base::Bind(&WindowManagerApiTest::OnEmbed,
+ base::Unretained(this), &id, &run_loop);
+ run_loop.Run();
+ return id;
+ }
+
+ TwoIds WaitForFocusChange() {
+ TwoIds old_and_new;
+ base::RunLoop run_loop;
+ window_manager_client()->set_focus_changed_callback(
+ base::Bind(&WindowManagerApiTest::OnFocusChanged,
+ base::Unretained(this), &old_and_new, &run_loop));
+ run_loop.Run();
+ return old_and_new;
+ }
+
+ TwoIds WaitForActiveWindowChange() {
+ TwoIds old_and_new;
+ base::RunLoop run_loop;
+ window_manager_client()->set_active_window_changed_callback(
+ base::Bind(&WindowManagerApiTest::OnActiveWindowChanged,
+ base::Unretained(this), &old_and_new, &run_loop));
+ run_loop.Run();
+ return old_and_new;
+ }
+
+ Id OpenWindow() {
+ return OpenWindowWithURL(kTestServiceURL);
+ }
+
+ Id OpenWindowWithURL(const std::string& url) {
+ InitEmbed(view_manager_init_.get(), url);
+ return WaitForEmbed();
+ }
+
+ TestWindowManagerClient* window_manager_client() {
+ return window_manager_client_.get();
+ }
+
+ WindowManagerServicePtr window_manager_;
+
+ private:
+ // Overridden from testing::Test:
+ virtual void SetUp() override {
+ test_helper_.Init();
+ test_helper_.SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(new TestApplicationLoader(base::Bind(
+ &WindowManagerApiTest::OnRootAdded, base::Unretained(this)))),
+ GURL(kTestServiceURL));
+ test_helper_.application_manager()->ConnectToService(
+ GURL("mojo:mojo_view_manager"), &view_manager_init_);
+ ASSERT_TRUE(InitEmbed(view_manager_init_.get(),
+ "mojo:mojo_core_window_manager"));
+ ConnectToWindowManager();
+ }
+ virtual void TearDown() override {}
+
+ void ConnectToWindowManager() {
+ test_helper_.application_manager()->ConnectToService(
+ GURL("mojo:mojo_core_window_manager"), &window_manager_);
+ base::RunLoop connect_loop;
+ window_manager_client_.reset(new TestWindowManagerClient(&connect_loop));
+ window_manager_.set_client(window_manager_client());
+ connect_loop.Run();
+ }
+
+ void OnRootAdded(View* root) {
+ if (!root_added_callback_.is_null())
+ root_added_callback_.Run(root);
+ }
+
+ void OnEmbed(Id* root_id,
+ base::RunLoop* loop,
+ View* root) {
+ *root_id = root->id();
+ loop->Quit();
+ }
+
+ void OnFocusChanged(TwoIds* old_and_new,
+ base::RunLoop* run_loop,
+ Id old_focused_node_id,
+ Id new_focused_node_id) {
+ DCHECK(old_and_new);
+ old_and_new->first = old_focused_node_id;
+ old_and_new->second = new_focused_node_id;
+ run_loop->Quit();
+ }
+
+ void OnActiveWindowChanged(TwoIds* old_and_new,
+ base::RunLoop* run_loop,
+ Id old_focused_node_id,
+ Id new_focused_node_id) {
+ DCHECK(old_and_new);
+ old_and_new->first = old_focused_node_id;
+ old_and_new->second = new_focused_node_id;
+ run_loop->Quit();
+ }
+
+ shell::ShellTestHelper test_helper_;
+ ViewManagerInitServicePtr view_manager_init_;
+ scoped_ptr<TestWindowManagerClient> window_manager_client_;
+ TestApplicationLoader::RootAddedCallback root_added_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerApiTest);
+};
+
+TEST_F(WindowManagerApiTest, FocusAndActivateWindow) {
+ Id first_window = OpenWindow();
+ window_manager_->FocusWindow(first_window,
+ base::Bind(&EmptyResultCallback));
+ TwoIds ids = WaitForFocusChange();
+ EXPECT_TRUE(ids.first == 0);
+ EXPECT_EQ(ids.second, first_window);
+
+ Id second_window = OpenWindow();
+ window_manager_->ActivateWindow(second_window,
+ base::Bind(&EmptyResultCallback));
+ ids = WaitForActiveWindowChange();
+ EXPECT_EQ(ids.first, first_window);
+ EXPECT_EQ(ids.second, second_window);
+}
+
+} // namespace mojo
diff --git a/mojo/services/window_manager/window_manager_app.cc b/mojo/services/window_manager/window_manager_app.cc
new file mode 100644
index 0000000..a22acba
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app.cc
@@ -0,0 +1,342 @@
+// Copyright 2014 The Chromium 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/window_manager/window_manager_app.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "mojo/aura/aura_init.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/public/cpp/input_events/input_events_type_converters.h"
+#include "mojo/services/public/cpp/view_manager/view.h"
+#include "mojo/services/public/cpp/view_manager/view_manager.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/aura/window_property.h"
+#include "ui/base/hit_test.h"
+#include "ui/wm/core/capture_controller.h"
+#include "ui/wm/core/focus_controller.h"
+#include "ui/wm/public/activation_client.h"
+
+DECLARE_WINDOW_PROPERTY_TYPE(mojo::View*);
+
+namespace mojo {
+
+// The aura::Windows we use to track Views don't render, so we don't actually
+// need to supply a fully functional WindowDelegate. We do need to provide _a_
+// delegate however, otherwise the event dispatcher won't dispatch events to
+// these windows. (The aura WindowTargeter won't allow a delegate-less window
+// to be the target of an event, since the window delegate is considered the
+// "target handler").
+class DummyDelegate : public aura::WindowDelegate {
+ public:
+ DummyDelegate() {}
+ virtual ~DummyDelegate() {}
+
+ private:
+ // WindowDelegate overrides:
+ 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 {}
+ 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 {}
+
+ DISALLOW_COPY_AND_ASSIGN(DummyDelegate);
+};
+
+namespace {
+
+DEFINE_WINDOW_PROPERTY_KEY(View*, kViewKey, NULL);
+
+Id GetIdForWindow(aura::Window* window) {
+ return window ? WindowManagerApp::GetViewForWindow(window)->id() : 0;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, public:
+
+WindowManagerApp::WindowManagerApp(
+ ViewManagerDelegate* view_manager_delegate,
+ WindowManagerDelegate* window_manager_delegate)
+ : window_manager_service_factory_(this),
+ wrapped_view_manager_delegate_(view_manager_delegate),
+ wrapped_window_manager_delegate_(window_manager_delegate),
+ view_manager_(NULL),
+ root_(NULL),
+ dummy_delegate_(new DummyDelegate) {
+}
+
+WindowManagerApp::~WindowManagerApp() {}
+
+// static
+View* WindowManagerApp::GetViewForWindow(aura::Window* window) {
+ return window->GetProperty(kViewKey);
+}
+
+aura::Window* WindowManagerApp::GetWindowForViewId(Id view) {
+ ViewIdToWindowMap::const_iterator it = view_id_to_window_map_.find(view);
+ return it != view_id_to_window_map_.end() ? it->second : NULL;
+}
+
+void WindowManagerApp::AddConnection(WindowManagerServiceImpl* connection) {
+ DCHECK(connections_.find(connection) == connections_.end());
+ connections_.insert(connection);
+}
+
+void WindowManagerApp::RemoveConnection(WindowManagerServiceImpl* connection) {
+ DCHECK(connections_.find(connection) != connections_.end());
+ connections_.erase(connection);
+}
+
+void WindowManagerApp::SetCapture(Id view) {
+ capture_client_->capture_client()->SetCapture(GetWindowForViewId(view));
+ // TODO(beng): notify connected clients that capture has changed, probably
+ // by implementing some capture-client observer.
+}
+
+void WindowManagerApp::FocusWindow(Id view) {
+ aura::Window* window = GetWindowForViewId(view);
+ DCHECK(window);
+ focus_client_->FocusWindow(window);
+}
+
+void WindowManagerApp::ActivateWindow(Id view) {
+ aura::Window* window = GetWindowForViewId(view);
+ DCHECK(window);
+ activation_client_->ActivateWindow(window);
+}
+
+bool WindowManagerApp::IsReady() const {
+ return view_manager_ && root_;
+}
+
+void WindowManagerApp::InitFocus(wm::FocusRules* rules) {
+ wm::FocusController* focus_controller = new wm::FocusController(rules);
+ activation_client_ = focus_controller;
+ focus_client_.reset(focus_controller);
+ aura::client::SetFocusClient(window_tree_host_->window(), focus_controller);
+ aura::client::SetActivationClient(window_tree_host_->window(),
+ focus_controller);
+
+ focus_client_->AddObserver(this);
+ activation_client_->AddObserver(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ApplicationDelegate implementation:
+
+void WindowManagerApp::Initialize(ApplicationImpl* impl) {
+ aura_init_.reset(new AuraInit);
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(impl->shell(), this));
+}
+
+bool WindowManagerApp::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService(&window_manager_service_factory_);
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ViewManagerDelegate implementation:
+
+void WindowManagerApp::OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) {
+ DCHECK(!view_manager_ && !root_);
+ view_manager_ = view_manager;
+ view_manager_->SetWindowManagerDelegate(this);
+ root_ = root;
+
+ window_tree_host_.reset(new WindowTreeHostMojo(root_, this));
+ window_tree_host_->window()->SetBounds(root->bounds());
+ window_tree_host_->window()->Show();
+
+ RegisterSubtree(root_, window_tree_host_->window());
+
+ capture_client_.reset(
+ new wm::ScopedCaptureClient(window_tree_host_->window()));
+
+ if (wrapped_view_manager_delegate_) {
+ wrapped_view_manager_delegate_->OnEmbed(
+ view_manager, root, exported_services, imported_services.Pass());
+ }
+
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ (*it)->NotifyReady();
+ }
+}
+
+void WindowManagerApp::OnViewManagerDisconnected(
+ ViewManager* view_manager) {
+ DCHECK_EQ(view_manager_, view_manager);
+ if (wrapped_view_manager_delegate_)
+ wrapped_view_manager_delegate_->OnViewManagerDisconnected(view_manager);
+ view_manager_ = NULL;
+ base::MessageLoop::current()->Quit();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, WindowManagerDelegate implementation:
+
+void WindowManagerApp::Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) {
+ if (wrapped_window_manager_delegate_)
+ wrapped_window_manager_delegate_->Embed(url, service_provider.Pass());
+}
+
+void WindowManagerApp::DispatchEvent(EventPtr event) {
+ scoped_ptr<ui::Event> ui_event = event.To<scoped_ptr<ui::Event> >();
+ if (ui_event)
+ window_tree_host_->SendEventToProcessor(ui_event.get());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ViewObserver implementation:
+
+void WindowManagerApp::OnTreeChanged(
+ const ViewObserver::TreeChangeParams& params) {
+ if (params.receiver != root_)
+ return;
+ DCHECK(params.old_parent || params.new_parent);
+ if (!params.target)
+ return;
+
+ if (params.new_parent) {
+ if (view_id_to_window_map_.find(params.target->id()) ==
+ view_id_to_window_map_.end()) {
+ ViewIdToWindowMap::const_iterator it =
+ view_id_to_window_map_.find(params.new_parent->id());
+ DCHECK(it != view_id_to_window_map_.end());
+ RegisterSubtree(params.target, it->second);
+ }
+ } else if (params.old_parent) {
+ UnregisterSubtree(params.target);
+ }
+}
+
+void WindowManagerApp::OnViewDestroyed(View* view) {
+ if (view != root_)
+ return;
+ aura::Window* window = GetWindowForViewId(view->id());
+ window->RemovePreTargetHandler(this);
+ root_ = NULL;
+ STLDeleteValues(&view_id_to_window_map_);
+ if (focus_client_.get())
+ focus_client_->RemoveObserver(this);
+ if (activation_client_)
+ activation_client_->RemoveObserver(this);
+ window_tree_host_.reset();
+}
+
+void WindowManagerApp::OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ aura::Window* window = GetWindowForViewId(view->id());
+ window->SetBounds(new_bounds);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, WindowTreeHostMojoDelegate implementation:
+
+void WindowManagerApp::CompositorContentsChanged(const SkBitmap& bitmap) {
+ // We draw nothing.
+ NOTREACHED();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ui::EventHandler implementation:
+
+void WindowManagerApp::OnEvent(ui::Event* event) {
+ aura::Window* window = static_cast<aura::Window*>(event->target());
+ view_manager_->DispatchEvent(GetViewForWindow(window), Event::From(*event));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, aura::client::FocusChangeObserver implementation:
+
+void WindowManagerApp::OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) {
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ (*it)->NotifyViewFocused(GetIdForWindow(gained_focus),
+ GetIdForWindow(lost_focus));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, aura::client::ActivationChangeObserver implementation:
+
+void WindowManagerApp::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ (*it)->NotifyWindowActivated(GetIdForWindow(gained_active),
+ GetIdForWindow(lost_active));
+ }
+ if (gained_active) {
+ View* view = GetViewForWindow(gained_active);
+ view->MoveToFront();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, private:
+
+void WindowManagerApp::RegisterSubtree(View* view, aura::Window* parent) {
+ view->AddObserver(this);
+ DCHECK(view_id_to_window_map_.find(view->id()) ==
+ view_id_to_window_map_.end());
+ aura::Window* window = new aura::Window(dummy_delegate_.get());
+ window->set_id(view->id());
+ window->SetProperty(kViewKey, view);
+ // All events pass through the root during dispatch, so we only need a handler
+ // installed there.
+ if (view == root_)
+ window->AddPreTargetHandler(this);
+ parent->AddChild(window);
+ window->SetBounds(view->bounds());
+ window->Show();
+ view_id_to_window_map_[view->id()] = window;
+ View::Children::const_iterator it = view->children().begin();
+ for (; it != view->children().end(); ++it)
+ RegisterSubtree(*it, window);
+}
+
+void WindowManagerApp::UnregisterSubtree(View* view) {
+ view->RemoveObserver(this);
+ ViewIdToWindowMap::iterator it = view_id_to_window_map_.find(view->id());
+ DCHECK(it != view_id_to_window_map_.end());
+ scoped_ptr<aura::Window> window(it->second);
+ view_id_to_window_map_.erase(it);
+ View::Children::const_iterator child = view->children().begin();
+ for (; child != view->children().end(); ++child)
+ UnregisterSubtree(*child);
+}
+
+} // namespace mojo
diff --git a/mojo/services/window_manager/window_manager_app.h b/mojo/services/window_manager/window_manager_app.h
new file mode 100644
index 0000000..01221d6
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app.h
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium 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_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_
+#define MOJO_SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/aura/window_tree_host_mojo.h"
+#include "mojo/aura/window_tree_host_mojo_delegate.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/services/public/cpp/view_manager/types.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_client_factory.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/cpp/view_manager/window_manager_delegate.h"
+#include "mojo/services/window_manager/window_manager_service_impl.h"
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/events/event_handler.h"
+#include "ui/wm/public/activation_change_observer.h"
+
+namespace aura {
+namespace client {
+class ActivationClient;
+class FocusClient;
+}
+class Window;
+}
+
+namespace wm {
+class FocusRules;
+class ScopedCaptureClient;
+}
+
+namespace mojo {
+
+class AuraInit;
+class DummyDelegate;
+class WindowManagerServiceImpl;
+class WindowTreeHostMojo;
+
+// Implements core window manager functionality that could conceivably be shared
+// across multiple window managers implementing superficially different user
+// experiences. Establishes communication with the view manager.
+// A window manager wishing to use this core should create and own an instance
+// of this object. They may implement the associated ViewManager/WindowManager
+// delegate interfaces exposed by the view manager, this object provides the
+// canonical implementation of said interfaces but will call out to the wrapped
+// instances.
+// This object maintains an aura::WindowTreeHost containing a hierarchy of
+// aura::Windows. Window manager functionality (e.g. focus, activation,
+// modality, etc.) are implemented using aura core window manager components.
+class WindowManagerApp
+ : public ApplicationDelegate,
+ public ViewManagerDelegate,
+ public WindowManagerDelegate,
+ public ViewObserver,
+ public WindowTreeHostMojoDelegate,
+ public ui::EventHandler,
+ public aura::client::FocusChangeObserver,
+ public aura::client::ActivationChangeObserver {
+ public:
+ WindowManagerApp(ViewManagerDelegate* view_manager_delegate,
+ WindowManagerDelegate* window_manager_delegate);
+ virtual ~WindowManagerApp();
+
+ static View* GetViewForWindow(aura::Window* window);
+ aura::Window* GetWindowForViewId(Id view);
+
+ // Register/deregister new connections to the window manager service.
+ void AddConnection(WindowManagerServiceImpl* connection);
+ void RemoveConnection(WindowManagerServiceImpl* connection);
+
+ // These are canonical implementations of the window manager API methods.
+ void SetCapture(Id view);
+ void FocusWindow(Id view);
+ void ActivateWindow(Id view);
+
+ bool IsReady() const;
+
+ // A client of this object will use this accessor to gain access to the
+ // aura::Window hierarchy and attach event handlers.
+ aura::WindowTreeHost* host() { return window_tree_host_.get(); }
+
+ void InitFocus(wm::FocusRules* rules);
+
+ // Overridden from ApplicationDelegate:
+ virtual void Initialize(ApplicationImpl* impl) override;
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override;
+
+ private:
+ typedef std::set<WindowManagerServiceImpl*> Connections;
+ typedef std::map<Id, aura::Window*> ViewIdToWindowMap;
+
+ // Overridden from ViewManagerDelegate:
+ virtual void OnEmbed(ViewManager* view_manager,
+ View* root,
+ ServiceProviderImpl* exported_services,
+ scoped_ptr<ServiceProvider> imported_services) override;
+ virtual void OnViewManagerDisconnected(ViewManager* view_manager) override;
+
+ // Overridden from WindowManagerDelegate:
+ virtual void Embed(
+ const String& url,
+ InterfaceRequest<ServiceProvider> service_provider) OVERRIDE;
+ virtual void DispatchEvent(EventPtr event) OVERRIDE;
+
+ // Overridden from ViewObserver:
+ virtual void OnTreeChanged(
+ const ViewObserver::TreeChangeParams& params) override;
+ virtual void OnViewDestroyed(View* view) override;
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+
+ // Overridden from WindowTreeHostMojoDelegate:
+ virtual void CompositorContentsChanged(const SkBitmap& bitmap) override;
+
+ // Overridden from ui::EventHandler:
+ virtual void OnEvent(ui::Event* event) override;
+
+ // Overridden from aura::client::FocusChangeObserver:
+ virtual void OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) override;
+
+ // Overridden from aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) override;
+
+ // Creates an aura::Window for every view in the hierarchy beneath |view|,
+ // and adds to the registry so that it can be retrieved later via
+ // GetWindowForViewId().
+ // TODO(beng): perhaps View should have a property bag.
+ void RegisterSubtree(View* view, aura::Window* parent);
+ // Deletes the aura::Windows associated with the hierarchy beneath |id|,
+ // and removes from the registry.
+ void UnregisterSubtree(View* view);
+
+ InterfaceFactoryImplWithContext<WindowManagerServiceImpl, WindowManagerApp>
+ window_manager_service_factory_;
+
+ ViewManagerDelegate* wrapped_view_manager_delegate_;
+ WindowManagerDelegate* wrapped_window_manager_delegate_;
+
+ ViewManager* view_manager_;
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+ View* root_;
+
+ scoped_ptr<AuraInit> aura_init_;
+ scoped_ptr<WindowTreeHostMojo> window_tree_host_;
+
+ scoped_ptr<wm::ScopedCaptureClient> capture_client_;
+ scoped_ptr<aura::client::FocusClient> focus_client_;
+ aura::client::ActivationClient* activation_client_;
+
+ Connections connections_;
+ ViewIdToWindowMap view_id_to_window_map_;
+
+ scoped_ptr<DummyDelegate> dummy_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerApp);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_
diff --git a/mojo/services/window_manager/window_manager_service_impl.cc b/mojo/services/window_manager/window_manager_service_impl.cc
new file mode 100644
index 0000000..aa1be19
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_service_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/window_manager/window_manager_service_impl.h"
+
+#include "mojo/services/window_manager/window_manager_app.h"
+
+namespace mojo {
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerServiceImpl, public:
+
+WindowManagerServiceImpl::WindowManagerServiceImpl(
+ WindowManagerApp* window_manager)
+ : window_manager_(window_manager) {
+ window_manager_->AddConnection(this);
+}
+
+WindowManagerServiceImpl::~WindowManagerServiceImpl() {
+ window_manager_->RemoveConnection(this);
+}
+
+void WindowManagerServiceImpl::NotifyReady() {
+ client()->OnWindowManagerReady();
+}
+
+void WindowManagerServiceImpl::NotifyViewFocused(Id new_focused_id,
+ Id old_focused_id) {
+ client()->OnFocusChanged(old_focused_id, new_focused_id);
+}
+
+void WindowManagerServiceImpl::NotifyWindowActivated(Id new_active_id,
+ Id old_active_id) {
+ client()->OnActiveWindowChanged(old_active_id, new_active_id);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerServiceImpl, WindowManager implementation:
+
+void WindowManagerServiceImpl::SetCapture(
+ Id view,
+ const Callback<void(bool)>& callback) {
+ bool success = window_manager_->IsReady();
+ if (success)
+ window_manager_->SetCapture(view);
+ callback.Run(success);
+}
+
+void WindowManagerServiceImpl::FocusWindow(
+ Id view,
+ const Callback<void(bool)>& callback) {
+ bool success = window_manager_->IsReady();
+ if (success)
+ window_manager_->FocusWindow(view);
+ callback.Run(success);
+}
+
+void WindowManagerServiceImpl::ActivateWindow(
+ Id view,
+ const Callback<void(bool)>& callback) {
+ bool success = window_manager_->IsReady();
+ if (success)
+ window_manager_->ActivateWindow(view);
+ callback.Run(success);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerServiceImpl, InterfaceImpl overrides:
+
+void WindowManagerServiceImpl::OnConnectionEstablished() {
+ // If the connection was established prior to the window manager being
+ // embedded by the view manager, |window_manager_|'s ViewManagerDelegate
+ // impl will call NotifyReady() when it is.
+ if (window_manager_->IsReady())
+ NotifyReady();
+}
+
+} // namespace mojo
diff --git a/mojo/services/window_manager/window_manager_service_impl.h b/mojo/services/window_manager/window_manager_service_impl.h
new file mode 100644
index 0000000..2d662e4
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_service_impl.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_WINDOW_MANAGER_WINDOW_MANAGER_SERVICE_IMPL_H_
+#define MOJO_SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_SERVICE_IMPL_H_
+
+#include "base/basictypes.h"
+#include "mojo/services/public/cpp/view_manager/types.h"
+#include "mojo/services/public/interfaces/window_manager/window_manager.mojom.h"
+
+namespace mojo {
+
+class WindowManagerApp;
+
+class WindowManagerServiceImpl : public InterfaceImpl<WindowManagerService> {
+ public:
+ explicit WindowManagerServiceImpl(WindowManagerApp* manager);
+ virtual ~WindowManagerServiceImpl();
+
+ void NotifyReady();
+ void NotifyViewFocused(Id new_focused_id, Id old_focused_id);
+ void NotifyWindowActivated(Id new_active_id, Id old_active_id);
+
+ private:
+ // Overridden from WindowManagerService:
+ virtual void SetCapture(Id view,
+ const Callback<void(bool)>& callback) override;
+ virtual void FocusWindow(Id view,
+ const Callback<void(bool)>& callback) override;
+ virtual void ActivateWindow(Id view,
+ const Callback<void(bool)>& callback) override;
+
+ // Overridden from InterfaceImpl:
+ virtual void OnConnectionEstablished() override;
+
+ WindowManagerApp* window_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerServiceImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_SERVICE_IMPL_H_
diff --git a/mojo/services/window_manager/window_manager_unittests.cc b/mojo/services/window_manager/window_manager_unittests.cc
new file mode 100644
index 0000000..2ddceb8
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_unittests.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/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "ui/gl/gl_surface.h"
+
+#if defined(USE_X11)
+#include "ui/gfx/x/x11_connection.h"
+#endif
+
+namespace mojo {
+
+class WindowManagerTestSuite : public base::TestSuite {
+ public:
+ WindowManagerTestSuite(int argc, char** argv) : TestSuite(argc, argv) {}
+ virtual ~WindowManagerTestSuite() {}
+
+ protected:
+ virtual void Initialize() OVERRIDE {
+#if defined(USE_X11)
+ // Each test ends up creating a new thread for the native viewport service.
+ // In other words we'll use X on different threads, so tell it that.
+ gfx::InitializeThreadedX11();
+#endif
+ base::TestSuite::Initialize();
+ gfx::GLSurface::InitializeOneOffForTests();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerTestSuite);
+};
+
+} // namespace mojo
+
+int main(int argc, char** argv) {
+ mojo::WindowManagerTestSuite test_suite(argc, argv);
+
+ return base::LaunchUnitTests(
+ argc, argv, base::Bind(&TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/shell/BUILD.gn b/mojo/shell/BUILD.gn
new file mode 100644
index 0000000..f405bc9
--- /dev/null
+++ b/mojo/shell/BUILD.gn
@@ -0,0 +1,199 @@
+# Copyright 2014 The Chromium 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")
+import("//build/config/ui.gni")
+
+executable("mojo_shell") {
+ deps = [
+ ":lib",
+ "//base",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ ]
+
+ if (is_component_build) {
+ deps += ["//ui/gl"]
+ }
+
+ sources = [
+ "desktop/mojo_main.cc"
+ ]
+}
+
+# GYP version: mojo/mojo.gyp:mojo_shell_lib
+source_set("lib") {
+ deps = [
+ ":app_child_process_bindings",
+ ":external_application_registrar_bindings",
+ ":external_service_bindings",
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//base:base_static",
+ "//mojo/application",
+ "//mojo/application_manager",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/gles2",
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/interfaces/network",
+ "//mojo/spy",
+ ]
+
+ sources = [
+ "app_child_process.cc",
+ "app_child_process.h",
+ "app_child_process_host.cc",
+ "app_child_process_host.h",
+ "child_process.cc",
+ "child_process.h",
+ "child_process_host.cc",
+ "child_process_host.h",
+ "context.cc",
+ "context.h",
+ "dynamic_application_loader.cc",
+ "dynamic_application_loader.h",
+ "dynamic_service_runner.h",
+ "external_application_listener_posix.cc",
+ "external_application_listener_win.cc",
+ "external_application_listener.h",
+ "incoming_connection_listener_posix.cc",
+ "incoming_connection_listener_posix.h",
+ "init.cc",
+ "init.h",
+ "in_process_dynamic_service_runner.cc",
+ "in_process_dynamic_service_runner.h",
+ "mojo_url_resolver.cc",
+ "mojo_url_resolver.h",
+ "out_of_process_dynamic_service_runner.cc",
+ "out_of_process_dynamic_service_runner.h",
+ "switches.cc",
+ "switches.h",
+ "task_runners.cc",
+ "task_runners.h",
+ "test_child_process.cc",
+ "test_child_process.h",
+ "ui_application_loader_android.cc",
+ "ui_application_loader_android.h",
+ ]
+
+ if (is_android) {
+ deps += [
+ "//mojo/services/native_viewport:lib",
+ "//mojo/services/network:lib",
+ ]
+ sources += [
+ "network_application_loader.cc",
+ "network_application_loader.h",
+ ]
+ }
+}
+
+mojom("app_child_process_bindings") {
+ sources = [
+ "app_child_process.mojom"
+ ]
+}
+
+mojom("external_service_bindings") {
+ sources = [
+ "external_service.mojom"
+ ]
+}
+
+mojom("external_application_registrar_bindings") {
+ sources = [
+ "external_application_registrar.mojom"
+ ]
+
+ deps = [ "//mojo/public/interfaces/application" ]
+}
+
+source_set("external_application_registrar_connection") {
+ sources = [
+ "external_application_registrar_connection.cc",
+ "external_application_registrar_connection.h",
+ ]
+
+ deps = [
+ ":external_application_registrar_bindings",
+ "//net"
+ ]
+}
+
+# GYP version: mojo/mojo.gyp:mojo_shell_tests
+test("mojo_shell_tests") {
+ deps = [
+ ":lib",
+ "//base",
+ "//base:i18n",
+ "//base/test:test_support",
+ "//testing/gtest",
+ "//net:test_support",
+ "//url",
+ "//mojo/application_manager",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/services/test_service:bindings",
+ ]
+
+ datadeps = [
+ "//mojo/services/test_service:mojo_test_app",
+ "//mojo/services/test_service:mojo_test_request_tracker_app",
+ ]
+
+ sources = [
+ "child_process_host_unittest.cc",
+ "dynamic_application_loader_unittest.cc",
+ "in_process_dynamic_service_runner_unittest.cc",
+ "shell_test_base.cc",
+ "shell_test_base.h",
+ "shell_test_base_unittest.cc",
+ "shell_test_main.cc",
+ ]
+
+ if (is_android) {
+ deps += [
+ # TODO(GYP):
+ #'../testing/android/native_test.gyp:native_test_native_code',
+ ]
+ }
+}
+
+# GYP version: mojo/mojo.gyp:mojo_shell_test_support
+source_set("test_support") {
+ sources = [
+ "shell_test_helper.cc",
+ "shell_test_helper.h",
+ ]
+
+ deps = [
+ ":lib",
+ "//mojo/edk/system",
+ ]
+}
+
+# GYP version: mojo/mojo.gyp:external_application_tests
+test("mojo_external_application_tests") {
+ deps = [
+ ":lib",
+ ":external_application_registrar_connection",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ "//net:test_support",
+ "//url",
+ "//mojo/application_manager",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ ]
+
+ sources = [
+ "incoming_connection_listener_unittest.cc",
+ "external_application_listener_unittest.cc",
+ "external_application_test_main.cc",
+ ]
+}
diff --git a/mojo/shell/DEPS b/mojo/shell/DEPS
new file mode 100644
index 0000000..1370103
--- /dev/null
+++ b/mojo/shell/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+gpu",
+ "+mojo/edk/embedder",
+ "+net",
+ "+ui/gfx/switches.h",
+ "+ui/gl",
+]
diff --git a/mojo/shell/android/DEPS b/mojo/shell/android/DEPS
new file mode 100644
index 0000000..c80012b
--- /dev/null
+++ b/mojo/shell/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+jni",
+]
diff --git a/mojo/shell/android/apk/AndroidManifest.xml b/mojo/shell/android/apk/AndroidManifest.xml
new file mode 100644
index 0000000..32b48e9
--- /dev/null
+++ b/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="20" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/mojo/shell/android/apk/res/layout/mojo_shell_activity.xml b/mojo/shell/android/apk/res/layout/mojo_shell_activity.xml
new file mode 100644
index 0000000..d319941
--- /dev/null
+++ b/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/mojo/shell/android/apk/res/values/strings.xml b/mojo/shell/android/apk/res/values/strings.xml
new file mode 100644
index 0000000..ff3f8bb
--- /dev/null
+++ b/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/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java b/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java
new file mode 100644
index 0000000..7a27003
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java
@@ -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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.content.Context;
+
+import org.chromium.base.JNINamespace;
+
+/**
+ * A placeholder class to call native functions.
+ **/
+@JNINamespace("mojo")
+public class MojoMain {
+ /**
+ * A guard flag for calling nativeInit() only once.
+ **/
+ private static boolean sInitialized = false;
+
+ /**
+ * Initializes the native system.
+ **/
+ public static void ensureInitialized(Context context) {
+ if (sInitialized)
+ return;
+ nativeInit(context);
+ sInitialized = true;
+ }
+
+ /**
+ * Starts the specified application in the specified context.
+ **/
+ public static void start(final String appUrl) {
+ nativeStart(appUrl);
+ }
+
+ /**
+ * Initializes the native system. This API should be called only once per process.
+ **/
+ private static native void nativeInit(Context context);
+ private static native void nativeStart(String appUrl);
+};
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java b/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java
new file mode 100644
index 0000000..ba2a324
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java
@@ -0,0 +1,67 @@
+// 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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.EditText;
+
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.ProcessInitException;
+
+/**
+ * Activity for managing the Mojo Shell.
+ */
+public class MojoShellActivity extends Activity {
+ private static final String TAG = "MojoShellActivity";
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ try {
+ LibraryLoader.ensureInitialized();
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "libmojo_shell initialization failed.", e);
+ finish();
+ return;
+ }
+
+ MojoMain.ensureInitialized(this);
+
+ String appUrl = getUrlFromIntent(getIntent());
+ if (appUrl == null) {
+ Log.i(TAG, "No URL provided via intent, prompting user...");
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+ alert.setTitle("Enter a URL");
+ alert.setMessage("Enter a URL");
+ final EditText input = new EditText(this);
+ alert.setView(input);
+ alert.setPositiveButton("Load", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int button) {
+ String url = input.getText().toString();
+ startWithURL(url);
+ }
+ });
+ alert.show();
+ } else {
+ startWithURL(appUrl);
+ }
+ }
+
+ private static String getUrlFromIntent(Intent intent) {
+ return intent != null ? intent.getDataString() : null;
+ }
+
+ private void startWithURL(String url) {
+ MojoMain.start(url);
+ Log.i(TAG, "Mojo started: " + url);
+ }
+}
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java b/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java
new file mode 100644
index 0000000..df927ee
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java
@@ -0,0 +1,27 @@
+// 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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.util.Log;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.base.PathUtils;
+
+public class MojoShellApplication extends BaseChromiumApplication {
+ private static final String TAG = "MojoShellApplication";
+ private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "mojo_shell";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ initializeApplicationParameters();
+ }
+
+ public static void initializeApplicationParameters() {
+ PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+ Log.i(TAG, "MojoShellApplication.initializeApplicationParameters() success.");
+ }
+
+}
diff --git a/mojo/shell/android/library_loader.cc b/mojo/shell/android/library_loader.cc
new file mode 100644
index 0000000..26a99a5
--- /dev/null
+++ b/mojo/shell/android/library_loader.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 "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/platform_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 },
+ { "PlatformViewportAndroid",
+ mojo::PlatformViewportAndroid::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/mojo/shell/android/mojo_main.cc b/mojo/shell/android/mojo_main.cc
new file mode 100644
index 0000000..832e0aa
--- /dev/null
+++ b/mojo/shell/android/mojo_main.cc
@@ -0,0 +1,94 @@
+// 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/java_handler_thread.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/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/init.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;
+
+LazyInstance<scoped_ptr<base::android::JavaHandlerThread> > g_shell_thread =
+ LAZY_INSTANCE_INITIALIZER;
+
+void RunShell(std::vector<GURL> app_urls) {
+ shell::Context* context = g_context.Pointer()->get();
+ context->Init();
+ context->set_ui_loop(g_java_message_loop.Get().get());
+ for (std::vector<GURL>::const_iterator it = app_urls.begin();
+ it != app_urls.end(); ++it) {
+ context->Run(*it);
+ }
+}
+
+} // 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();
+
+ // We want ~MessageLoop to happen prior to ~Context. Initializing
+ // LazyInstances is akin to stack-allocating objects; their destructors
+ // will be invoked first-in-last-out.
+ shell::Context* shell_context = new shell::Context();
+ g_context.Get().reset(shell_context);
+ 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, 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
+
+ g_shell_thread.Get().reset(
+ new base::android::JavaHandlerThread("shell_thread"));
+ g_shell_thread.Get()->Start();
+ g_shell_thread.Get()->message_loop()->PostTask(
+ FROM_HERE, base::Bind(&RunShell, app_urls));
+}
+
+bool RegisterMojoMain(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace mojo
diff --git a/mojo/shell/android/mojo_main.h b/mojo/shell/android/mojo_main.h
new file mode 100644
index 0000000..161ca71
--- /dev/null
+++ b/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/mojo/shell/app_child_process.cc b/mojo/shell/app_child_process.cc
new file mode 100644
index 0000000..7bf5420
--- /dev/null
+++ b/mojo/shell/app_child_process.cc
@@ -0,0 +1,292 @@
+// Copyright 2014 The Chromium 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/memory/scoped_ptr.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/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.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(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+
+ // 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_.get());
+
+ // 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_.get());
+ }
+
+ 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/mojo/shell/app_child_process.h b/mojo/shell/app_child_process.h
new file mode 100644
index 0000000..9b3b8cf
--- /dev/null
+++ b/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/mojo/shell/app_child_process.mojom b/mojo/shell/app_child_process.mojom
new file mode 100644
index 0000000..d8cef12
--- /dev/null
+++ b/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/mojo/shell/app_child_process_host.cc b/mojo/shell/app_child_process_host.cc
new file mode 100644
index 0000000..8da4772
--- /dev/null
+++ b/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/edk/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/mojo/shell/app_child_process_host.h b/mojo/shell/app_child_process_host.h
new file mode 100644
index 0000000..f65e2a4
--- /dev/null
+++ b/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/mojo/shell/child_process.cc b/mojo/shell/child_process.cc
new file mode 100644
index 0000000..79b9940
--- /dev/null
+++ b/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/edk/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/mojo/shell/child_process.h b/mojo/shell/child_process.h
new file mode 100644
index 0000000..81780a3
--- /dev/null
+++ b/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/edk/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/mojo/shell/child_process_host.cc b/mojo/shell/child_process_host.cc
new file mode 100644
index 0000000..5d79758
--- /dev/null
+++ b/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;
+ // Note: |WaitForExitCode()| closes the process handle.
+ LOG_IF(ERROR, !base::WaitForExitCode(child_process_handle_, &rv))
+ << "Failed to wait for child process";
+ 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/mojo/shell/child_process_host.h b/mojo/shell/child_process_host.h
new file mode 100644
index 0000000..6e94b96
--- /dev/null
+++ b/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/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/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/mojo/shell/child_process_host_unittest.cc b/mojo/shell/child_process_host_unittest.cc
new file mode 100644
index 0000000..5223018
--- /dev/null
+++ b/mojo/shell/child_process_host_unittest.cc
@@ -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.
+
+// 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 "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 testing::Test ChildProcessHostTest;
+
+TEST_F(ChildProcessHostTest, Basic) {
+ Context context;
+ base::MessageLoop message_loop(
+ scoped_ptr<base::MessagePump>(new common::MessagePumpMojo()));
+ context.Init();
+ 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/mojo/shell/context.cc b/mojo/shell/context.cc
new file mode 100644
index 0000000..e1c8d53
--- /dev/null
+++ b/mojo/shell/context.cc
@@ -0,0 +1,268 @@
+// 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 <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_split.h"
+#include "build/build_config.h"
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/application_manager/background_shell_application_loader.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/shell/dynamic_application_loader.h"
+#include "mojo/shell/external_application_listener.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/shell/ui_application_loader_android.h"
+#include "mojo/spy/spy.h"
+
+#if defined(OS_ANDROID)
+#include "mojo/services/native_viewport/gpu_impl.h"
+#include "mojo/services/native_viewport/native_viewport_impl.h"
+#include "mojo/shell/network_application_loader.h"
+#include "ui/gl/gl_share_group.h"
+#endif // defined(OS_ANDROID)
+
+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(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+ }
+
+ ~Setup() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Setup);
+};
+
+static base::LazyInstance<Setup>::Leaky setup = LAZY_INSTANCE_INITIALIZER;
+
+void InitContentHandlers(DynamicApplicationLoader* loader,
+ base::CommandLine* command_line) {
+ // Default content handlers.
+ loader->RegisterContentHandler("image/png", GURL("mojo://mojo_png_viewer/"));
+ loader->RegisterContentHandler("text/html", GURL("mojo://mojo_html_viewer/"));
+
+ // Command-line-specified content handlers.
+ std::string handlers_spec = command_line->GetSwitchValueASCII(
+ switches::kContentHandlers);
+ if (handlers_spec.empty())
+ return;
+
+ std::vector<std::string> parts;
+ base::SplitString(handlers_spec, ',', &parts);
+ if (parts.size() % 2 != 0) {
+ LOG(ERROR) << "Invalid value for switch " << switches::kContentHandlers
+ << ": must be a comma-separated list of mimetype/url pairs.";
+ return;
+ }
+
+ for (size_t i = 0; i < parts.size(); i += 2) {
+ GURL url(parts[i + 1]);
+ if (!url.is_valid()) {
+ LOG(ERROR) << "Invalid value for switch " << switches::kContentHandlers
+ << ": '" << parts[i + 1] << "' is not a valid URL.";
+ return;
+ }
+ loader->RegisterContentHandler(parts[i], url);
+ }
+}
+
+class EmptyServiceProvider : public InterfaceImpl<ServiceProvider> {
+ private:
+ virtual void ConnectToService(
+ const mojo::String& service_name,
+ ScopedMessagePipeHandle client_handle) override {}
+};
+
+} // namespace
+
+#if defined(OS_ANDROID)
+class Context::NativeViewportApplicationLoader
+ : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<NativeViewport>,
+ public InterfaceFactory<Gpu> {
+ public:
+ NativeViewportApplicationLoader()
+ : share_group_(new gfx::GLShareGroup),
+ mailbox_manager_(new gpu::gles2::MailboxManager) {}
+ virtual ~NativeViewportApplicationLoader() {}
+
+ private:
+ // ApplicationLoader implementation.
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (shell_handle.is_valid())
+ app_.reset(new ApplicationImpl(this, shell_handle.Pass()));
+ }
+
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {}
+
+ // ApplicationDelegate implementation.
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService<NativeViewport>(this);
+ connection->AddService<Gpu>(this);
+ return true;
+ }
+
+ // InterfaceFactory<NativeViewport> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<NativeViewport> request) override {
+ BindToRequest(new NativeViewportImpl(app_.get(), false), &request);
+ }
+
+ // InterfaceFactory<Gpu> implementation.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<Gpu> request) override {
+ BindToRequest(new GpuImpl(share_group_.get(), mailbox_manager_.get()),
+ &request);
+ }
+
+ scoped_refptr<gfx::GLShareGroup> share_group_;
+ scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_;
+ scoped_ptr<ApplicationImpl> app_;
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportApplicationLoader);
+};
+#endif
+
+Context::Context() {
+ DCHECK(!base::MessageLoop::current());
+}
+
+Context::~Context() {
+ DCHECK(!base::MessageLoop::current());
+}
+
+void Context::Init() {
+ application_manager_.set_delegate(this);
+ setup.Get();
+ task_runners_.reset(
+ new TaskRunners(base::MessageLoop::current()->message_loop_proxy()));
+
+ for (size_t i = 0; i < arraysize(kLocalMojoURLs); ++i)
+ mojo_url_resolver_.AddLocalFileMapping(GURL(kLocalMojoURLs[i]));
+
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+
+ if (command_line->HasSwitch(switches::kEnableExternalApplications)) {
+ listener_ = ExternalApplicationListener::Create(
+ task_runners_->shell_runner(), task_runners_->io_runner());
+
+ base::FilePath socket_path =
+ command_line->GetSwitchValuePath(switches::kEnableExternalApplications);
+ if (socket_path.empty())
+ socket_path = ExternalApplicationListener::ConstructDefaultSocketPath();
+
+ listener_->ListenInBackground(
+ socket_path,
+ base::Bind(&ApplicationManager::RegisterExternalApplication,
+ base::Unretained(&application_manager_)));
+ }
+ scoped_ptr<DynamicServiceRunnerFactory> runner_factory;
+ if (command_line->HasSwitch(switches::kEnableMultiprocess))
+ runner_factory.reset(new OutOfProcessDynamicServiceRunnerFactory());
+ else
+ runner_factory.reset(new InProcessDynamicServiceRunnerFactory());
+
+ DynamicApplicationLoader* dynamic_application_loader =
+ new DynamicApplicationLoader(this, runner_factory.Pass());
+ InitContentHandlers(dynamic_application_loader, command_line);
+ application_manager_.set_default_loader(
+ scoped_ptr<ApplicationLoader>(dynamic_application_loader));
+
+ // 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.
+#if defined(OS_ANDROID)
+ application_manager_.SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(new UIApplicationLoader(
+ scoped_ptr<ApplicationLoader>(new NativeViewportApplicationLoader()),
+ this)),
+ GURL("mojo:mojo_native_viewport_service"));
+#endif
+
+ if (command_line->HasSwitch(switches::kSpy)) {
+ spy_.reset(
+ new mojo::Spy(&application_manager_,
+ command_line->GetSwitchValueASCII(switches::kSpy)));
+ }
+
+#if defined(OS_ANDROID)
+ // On android, the network service is bundled with the shell because the
+ // network stack depends on the android runtime.
+ {
+ scoped_ptr<BackgroundShellApplicationLoader> loader(
+ new BackgroundShellApplicationLoader(
+ scoped_ptr<ApplicationLoader>(new NetworkApplicationLoader()),
+ "network_service",
+ base::MessageLoop::TYPE_IO));
+ application_manager_.SetLoaderForURL(loader.PassAs<ApplicationLoader>(),
+ GURL("mojo:mojo_network_service"));
+ }
+#endif
+
+ if (listener_)
+ listener_->WaitForListening();
+}
+
+void Context::OnApplicationError(const GURL& gurl) {
+ if (app_urls_.find(gurl) != app_urls_.end()) {
+ app_urls_.erase(gurl);
+ if (app_urls_.empty() && base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+ }
+}
+
+void Context::Run(const GURL& url) {
+ EmptyServiceProvider* sp = new EmptyServiceProvider;
+ ServiceProviderPtr spp;
+ BindToProxy(sp, &spp);
+
+ app_urls_.insert(url);
+ application_manager_.ConnectToApplication(url, GURL(), spp.Pass());
+}
+
+ScopedMessagePipeHandle Context::ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& service_name) {
+ app_urls_.insert(application_url);
+ return application_manager_.ConnectToServiceByName(
+ application_url, service_name).Pass();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/context.h b/mojo/shell/context.h
new file mode 100644
index 0000000..1f098c7
--- /dev/null
+++ b/mojo/shell/context.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_SHELL_CONTEXT_H_
+#define MOJO_SHELL_CONTEXT_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "mojo/application_manager/application_manager.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 DynamicApplicationLoader;
+class ExternalApplicationListener;
+
+// The "global" context for the shell's main process.
+class Context : ApplicationManager::Delegate {
+ public:
+ Context();
+ virtual ~Context();
+
+ void Init();
+
+ // ApplicationManager::Delegate override.
+ virtual void OnApplicationError(const GURL& gurl) override;
+
+ void Run(const GURL& url);
+ ScopedMessagePipeHandle ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& service_name);
+
+ TaskRunners* task_runners() { return task_runners_.get(); }
+ ApplicationManager* application_manager() { return &application_manager_; }
+ MojoURLResolver* mojo_url_resolver() { return &mojo_url_resolver_; }
+
+#if defined(OS_ANDROID)
+ base::MessageLoop* ui_loop() const { return ui_loop_; }
+ void set_ui_loop(base::MessageLoop* ui_loop) { ui_loop_ = ui_loop; }
+#endif // defined(OS_ANDROID)
+
+ private:
+ class NativeViewportApplicationLoader;
+
+ std::set<GURL> app_urls_;
+ scoped_ptr<TaskRunners> task_runners_;
+ scoped_ptr<ExternalApplicationListener> listener_;
+ ApplicationManager application_manager_;
+ MojoURLResolver mojo_url_resolver_;
+ scoped_ptr<Spy> spy_;
+#if defined(OS_ANDROID)
+ base::MessageLoop* ui_loop_;
+#endif // defined(OS_ANDROID)
+
+ DISALLOW_COPY_AND_ASSIGN(Context);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_CONTEXT_H_
diff --git a/mojo/shell/desktop/mojo_main.cc b/mojo/shell/desktop/mojo_main.cc
new file mode 100644
index 0000000..0331d3e
--- /dev/null
+++ b/mojo/shell/desktop/mojo_main.cc
@@ -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.
+
+#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 "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/shell/child_process.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/init.h"
+#include "mojo/shell/switches.h"
+
+#if defined(COMPONENT_BUILD)
+#include "ui/gl/gl_surface.h"
+#endif
+
+namespace {
+
+#if defined(OS_LINUX)
+// Copied from ui/gfx/switches.cc to avoid a dependency on //ui/gfx
+const char kEnableHarfBuzzRenderText[] = "enable-harfbuzz-rendertext";
+#endif
+
+#if defined(OS_WIN)
+void SplitString(const base::string16& str, std::vector<std::string>* argv) {
+ base::SplitString(base::UTF16ToUTF8(str), ' ', argv);
+}
+#elif defined(OS_POSIX)
+void SplitString(const std::string& str, std::vector<std::string>* argv) {
+ base::SplitString(str, ' ', argv);
+}
+#endif
+
+// The value of app_url_and_args is "<mojo_app_url> [<args>...]", where args
+// is a list of "configuration" arguments separated by spaces. If one or more
+// arguments are specified they will be available when the Mojo application
+// is initialized. See ApplicationImpl::args().
+GURL GetAppURLAndSetArgs(const base::CommandLine::StringType& app_url_and_args,
+ mojo::shell::Context* context) {
+ std::vector<std::string> argv;
+ SplitString(app_url_and_args, &argv);
+ if (argv.empty())
+ return GURL::EmptyGURL();
+ GURL app_url(argv[0]);
+ if (argv.size() > 1)
+ context->application_manager()->SetArgsForURL(argv, app_url);
+ return app_url;
+}
+
+void RunApps(mojo::shell::Context* context) {
+ const auto& command_line = *base::CommandLine::ForCurrentProcess();
+ for (const auto& arg : command_line.GetArgs())
+ context->Run(GetAppURLAndSetArgs(arg, context));
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit;
+ base::CommandLine::Init(argc, argv);
+#if defined(OS_LINUX)
+ // We use gfx::RenderText from multiple threads concurrently and the pango
+ // backend (currently the default on linux) is not close to threadsafe. Force
+ // use of the harfbuzz backend for now.
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ kEnableHarfBuzzRenderText);
+#endif
+ 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 {
+#if defined(COMPONENT_BUILD)
+ gfx::GLSurface::InitializeOneOff();
+#endif
+ // We want the shell::Context to outlive the MessageLoop so that pipes are
+ // all gracefully closed / error-out before we try to shut the Context down.
+ mojo::shell::Context shell_context;
+ {
+ base::MessageLoop message_loop;
+ shell_context.Init();
+
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kOrigin)) {
+ shell_context.mojo_url_resolver()->SetBaseURL(
+ GURL(command_line.GetSwitchValueASCII(switches::kOrigin)));
+ }
+
+ for (const auto& kv : command_line.GetSwitches()) {
+ if (kv.first == switches::kArgsFor)
+ GetAppURLAndSetArgs(kv.second, &shell_context);
+ }
+
+ message_loop.PostTask(FROM_HERE, base::Bind(RunApps, &shell_context));
+ message_loop.Run();
+ }
+ }
+ return 0;
+}
diff --git a/mojo/shell/dynamic_application_loader.cc b/mojo/shell/dynamic_application_loader.cc
new file mode 100644
index 0000000..ac0686e
--- /dev/null
+++ b/mojo/shell/dynamic_application_loader.cc
@@ -0,0 +1,248 @@
+// Copyright 2014 The Chromium 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_application_loader.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/common/common_type_converters.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/switches.h"
+#include "net/base/filename_util.h"
+
+namespace mojo {
+namespace shell {
+
+// Encapsulates loading and running one individual application.
+//
+// Loaders are owned by DynamicApplicationLoader. DynamicApplicationLoader must
+// ensure that all the parameters passed to Loader subclasses stay valid through
+// Loader's lifetime.
+//
+// Async operations are done with WeakPtr to protect against
+// DynamicApplicationLoader going away (and taking all the Loaders with it)
+// while the async operation is outstanding.
+class DynamicApplicationLoader::Loader {
+ public:
+ Loader(Context* context,
+ DynamicServiceRunnerFactory* runner_factory,
+ scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
+ const LoaderCompleteCallback& loader_complete_callback)
+ : load_callbacks_(load_callbacks),
+ loader_complete_callback_(loader_complete_callback),
+ context_(context),
+ runner_factory_(runner_factory),
+ weak_ptr_factory_(this) {}
+
+ virtual ~Loader() {}
+
+ protected:
+ void RunLibrary(const base::FilePath& path, bool path_exists) {
+ ScopedMessagePipeHandle shell_handle =
+ load_callbacks_->RegisterApplication();
+ if (!shell_handle.is_valid()) {
+ LoaderComplete();
+ return;
+ }
+
+ if (!path_exists) {
+ DVLOG(1) << "Library not started because library path '" << path.value()
+ << "' does not exist.";
+ LoaderComplete();
+ return;
+ }
+
+ runner_ = runner_factory_->Create(context_);
+ runner_->Start(
+ path,
+ shell_handle.Pass(),
+ base::Bind(&Loader::LoaderComplete, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ void LoaderComplete() { loader_complete_callback_.Run(this); }
+
+ scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks_;
+ LoaderCompleteCallback loader_complete_callback_;
+ Context* context_;
+
+ private:
+ DynamicServiceRunnerFactory* runner_factory_;
+ scoped_ptr<DynamicServiceRunner> runner_;
+ base::WeakPtrFactory<Loader> weak_ptr_factory_;
+};
+
+// A loader for local files.
+class DynamicApplicationLoader::LocalLoader : public Loader {
+ public:
+ LocalLoader(const GURL& url,
+ Context* context,
+ DynamicServiceRunnerFactory* runner_factory,
+ scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
+ const LoaderCompleteCallback& loader_complete_callback)
+ : Loader(context,
+ runner_factory,
+ load_callbacks,
+ loader_complete_callback),
+ weak_ptr_factory_(this) {
+ base::FilePath path;
+ net::FileURLToFilePath(url, &path);
+
+ // Async for consistency with network case.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalLoader::RunLibrary,
+ weak_ptr_factory_.GetWeakPtr(),
+ path,
+ base::PathExists(path)));
+ }
+
+ virtual ~LocalLoader() {}
+
+ private:
+ base::WeakPtrFactory<LocalLoader> weak_ptr_factory_;
+};
+
+// A loader for network files.
+class DynamicApplicationLoader::NetworkLoader : public Loader {
+ public:
+ NetworkLoader(const GURL& url,
+ MimeTypeToURLMap* mime_type_to_url,
+ Context* context,
+ DynamicServiceRunnerFactory* runner_factory,
+ NetworkService* network_service,
+ scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
+ const LoaderCompleteCallback& loader_complete_callback)
+ : Loader(context,
+ runner_factory,
+ load_callbacks,
+ loader_complete_callback),
+ mime_type_to_url_(mime_type_to_url),
+ weak_ptr_factory_(this) {
+ URLRequestPtr request(URLRequest::New());
+ request->url = String::From(url);
+ request->auto_follow_redirects = true;
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableCache)) {
+ request->bypass_cache = true;
+ }
+
+ network_service->CreateURLLoader(Get(&url_loader_));
+ url_loader_->Start(request.Pass(),
+ base::Bind(&NetworkLoader::OnLoadComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ virtual ~NetworkLoader() {
+ if (!file_.empty())
+ base::DeleteFile(file_, false);
+ }
+
+ private:
+ void OnLoadComplete(URLResponsePtr response) {
+ if (response->error) {
+ LOG(ERROR) << "Error (" << response->error->code << ": "
+ << response->error->description << ") while fetching "
+ << response->url;
+ LoaderComplete();
+ return;
+ }
+
+ MimeTypeToURLMap::iterator iter =
+ mime_type_to_url_->find(response->mime_type);
+ if (iter != mime_type_to_url_->end()) {
+ load_callbacks_->LoadWithContentHandler(iter->second, response.Pass());
+ return;
+ }
+
+ base::CreateTemporaryFile(&file_);
+ common::CopyToFile(
+ response->body.Pass(),
+ file_,
+ context_->task_runners()->blocking_pool(),
+ base::Bind(
+ &NetworkLoader::RunLibrary, weak_ptr_factory_.GetWeakPtr(), file_));
+ }
+
+ MimeTypeToURLMap* mime_type_to_url_;
+ URLLoaderPtr url_loader_;
+ base::FilePath file_;
+ base::WeakPtrFactory<NetworkLoader> weak_ptr_factory_;
+};
+
+DynamicApplicationLoader::DynamicApplicationLoader(
+ Context* context,
+ scoped_ptr<DynamicServiceRunnerFactory> runner_factory)
+ : context_(context),
+ runner_factory_(runner_factory.Pass()),
+
+ // Unretained() is correct here because DynamicApplicationLoader owns the
+ // loaders that we pass this callback to.
+ loader_complete_callback_(
+ base::Bind(&DynamicApplicationLoader::LoaderComplete,
+ base::Unretained(this))) {
+}
+
+DynamicApplicationLoader::~DynamicApplicationLoader() {
+}
+
+void DynamicApplicationLoader::RegisterContentHandler(
+ const std::string& mime_type,
+ const GURL& content_handler_url) {
+ mime_type_to_url_[mime_type] = content_handler_url;
+}
+
+void DynamicApplicationLoader::Load(
+ ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> load_callbacks) {
+ GURL resolved_url;
+ if (url.SchemeIs("mojo")) {
+ resolved_url = context_->mojo_url_resolver()->Resolve(url);
+ } else {
+ resolved_url = url;
+ }
+
+ if (resolved_url.SchemeIsFile()) {
+ loaders_.push_back(new LocalLoader(resolved_url,
+ context_,
+ runner_factory_.get(),
+ load_callbacks,
+ loader_complete_callback_));
+ return;
+ }
+
+ if (!network_service_) {
+ context_->application_manager()->ConnectToService(
+ GURL("mojo:mojo_network_service"), &network_service_);
+ }
+
+ loaders_.push_back(new NetworkLoader(resolved_url,
+ &mime_type_to_url_,
+ context_,
+ runner_factory_.get(),
+ network_service_.get(),
+ load_callbacks,
+ loader_complete_callback_));
+}
+
+void DynamicApplicationLoader::OnApplicationError(ApplicationManager* 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?
+}
+
+void DynamicApplicationLoader::LoaderComplete(Loader* loader) {
+ loaders_.erase(std::find(loaders_.begin(), loaders_.end(), loader));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/dynamic_application_loader.h b/mojo/shell/dynamic_application_loader.h
new file mode 100644
index 0000000..6d8df9f
--- /dev/null
+++ b/mojo/shell/dynamic_application_loader.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_SHELL_DYNAMIC_APPLICATION_LOADER_H_
+#define MOJO_SHELL_DYNAMIC_APPLICATION_LOADER_H_
+
+#include <map>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/public/interfaces/network/network_service.mojom.h"
+#include "mojo/shell/dynamic_service_runner.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+class Context;
+class DynamicServiceRunnerFactory;
+class DynamicServiceRunner;
+
+// An implementation of ApplicationLoader that retrieves a dynamic library
+// containing the implementation of the service and loads/runs it (via a
+// DynamicServiceRunner).
+class DynamicApplicationLoader : public ApplicationLoader {
+ public:
+ DynamicApplicationLoader(
+ Context* context,
+ scoped_ptr<DynamicServiceRunnerFactory> runner_factory);
+ virtual ~DynamicApplicationLoader();
+
+ void RegisterContentHandler(const std::string& mime_type,
+ const GURL& content_handler_url);
+
+ // ApplicationLoader methods:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override;
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ private:
+ class Loader;
+ class LocalLoader;
+ class NetworkLoader;
+
+ typedef std::map<std::string, GURL> MimeTypeToURLMap;
+ typedef base::Callback<void(Loader*)> LoaderCompleteCallback;
+
+ void LoaderComplete(Loader* loader);
+
+ Context* const context_;
+ scoped_ptr<DynamicServiceRunnerFactory> runner_factory_;
+ NetworkServicePtr network_service_;
+ MimeTypeToURLMap mime_type_to_url_;
+ ScopedVector<Loader> loaders_;
+ LoaderCompleteCallback loader_complete_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DynamicApplicationLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_DYNAMIC_APPLICATION_LOADER_H_
diff --git a/mojo/shell/dynamic_application_loader_unittest.cc b/mojo/shell/dynamic_application_loader_unittest.cc
new file mode 100644
index 0000000..ea6fb1d
--- /dev/null
+++ b/mojo/shell/dynamic_application_loader_unittest.cc
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_temp_dir.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/dynamic_application_loader.h"
+#include "mojo/shell/dynamic_service_runner.h"
+#include "net/base/filename_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+struct TestState {
+ TestState()
+ : runner_was_created(false),
+ runner_was_started(false),
+ runner_was_destroyed(false) {}
+
+ bool runner_was_created;
+ bool runner_was_started;
+ bool runner_was_destroyed;
+};
+
+class TestDynamicServiceRunner : public DynamicServiceRunner {
+ public:
+ explicit TestDynamicServiceRunner(TestState* state) : state_(state) {
+ state_->runner_was_created = true;
+ }
+ virtual ~TestDynamicServiceRunner() {
+ state_->runner_was_destroyed = true;
+ base::MessageLoop::current()->Quit();
+ }
+ virtual void Start(const base::FilePath& app_path,
+ ScopedMessagePipeHandle service_handle,
+ const base::Closure& app_completed_callback) override {
+ state_->runner_was_started = true;
+ }
+
+ private:
+ TestState* state_;
+};
+
+class TestDynamicServiceRunnerFactory : public DynamicServiceRunnerFactory {
+ public:
+ explicit TestDynamicServiceRunnerFactory(TestState* state) : state_(state) {}
+ virtual ~TestDynamicServiceRunnerFactory() {}
+ virtual scoped_ptr<DynamicServiceRunner> Create(Context* context) override {
+ return scoped_ptr<DynamicServiceRunner>(
+ new TestDynamicServiceRunner(state_));
+ }
+
+ private:
+ TestState* state_;
+};
+
+} // namespace
+
+class DynamicApplicationLoaderTest : public testing::Test {
+ public:
+ DynamicApplicationLoaderTest() {}
+ virtual ~DynamicApplicationLoaderTest() {}
+ virtual void SetUp() override {
+ context_.Init();
+ scoped_ptr<DynamicServiceRunnerFactory> factory(
+ new TestDynamicServiceRunnerFactory(&state_));
+ loader_.reset(new DynamicApplicationLoader(&context_, factory.Pass()));
+ }
+
+ protected:
+ Context context_;
+ base::MessageLoop loop_;
+ scoped_ptr<DynamicApplicationLoader> loader_;
+ TestState state_;
+};
+
+TEST_F(DynamicApplicationLoaderTest, DoesNotExist) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath nonexistent_file(FILE_PATH_LITERAL("nonexistent.txt"));
+ GURL url(net::FilePathToFileURL(temp_dir.path().Append(nonexistent_file)));
+ MessagePipe pipe;
+ scoped_refptr<ApplicationLoader::SimpleLoadCallbacks> callbacks(
+ new ApplicationLoader::SimpleLoadCallbacks(pipe.handle0.Pass()));
+ loader_->Load(context_.application_manager(), url, callbacks);
+ EXPECT_FALSE(state_.runner_was_created);
+ EXPECT_FALSE(state_.runner_was_started);
+ EXPECT_FALSE(state_.runner_was_destroyed);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/dynamic_service_runner.h b/mojo/shell/dynamic_service_runner.h
new file mode 100644
index 0000000..aa392f8
--- /dev/null
+++ b/mojo/shell/dynamic_service_runner.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_SHELL_DYNAMIC_SERVICE_RUNNER_H_
+#define MOJO_SHELL_DYNAMIC_SERVICE_RUNNER_H_
+
+#include "base/callback_forward.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/mojo/shell/external_application_listener.h b/mojo/shell/external_application_listener.h
new file mode 100644
index 0000000..fed735e
--- /dev/null
+++ b/mojo/shell/external_application_listener.h
@@ -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.
+
+#ifndef MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_H_
+#define MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "net/socket/socket_descriptor.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+// In order to support Mojo apps whose lifetime is managed by
+// something other than mojo_shell, mojo_shell needs to support a
+// mechanism by which such an application can discover a running shell
+// instance, connect to it, and ask to be "registered" at a given
+// URL. Registration implies that the app can be connected to at that
+// URL from then on out, and that the app has received a usable ShellPtr.
+//
+// External applications can connect to the shell using the
+// ExternalApplicationRegistrarConnection class.
+class ExternalApplicationListener {
+ public:
+ // When run, a RegisterCallback should note that an app has asked to be
+ // registered at app_url and Bind the provided pipe handle to a ShellImpl.
+ typedef base::Callback<void(const GURL& app_url,
+ ScopedMessagePipeHandle shell)> RegisterCallback;
+ typedef base::Callback<void(int rv)> ErrorCallback;
+
+ virtual ~ExternalApplicationListener() {}
+
+ // Implementations of this class may use two threads, an IO thread for
+ // listening and accepting incoming sockets, and a "main" thread
+ // where all Mojo traffic is processed and provided callbacks are run.
+ static scoped_ptr<ExternalApplicationListener> Create(
+ const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& io_runner);
+
+ static base::FilePath ConstructDefaultSocketPath();
+
+ // Begin listening (on io_runner) to a socket at listen_socket_path.
+ // Incoming registration requests will be forwarded to register_callback.
+ // Errors are ignored.
+ virtual void ListenInBackground(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback) = 0;
+
+ // Begin listening (on io_runner) to a socket at listen_socket_path.
+ // Incoming registration requests will be forwarded to register_callback.
+ // Errors are reported via error_callback.
+ virtual void ListenInBackgroundWithErrorCallback(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback,
+ const ErrorCallback& error_callback) = 0;
+
+ // Block the current thread until listening has started on io_runner.
+ // If listening has already started, returns immediately.
+ virtual void WaitForListening() = 0;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_H_
diff --git a/mojo/shell/external_application_listener_posix.cc b/mojo/shell/external_application_listener_posix.cc
new file mode 100644
index 0000000..42afa25
--- /dev/null
+++ b/mojo/shell/external_application_listener_posix.cc
@@ -0,0 +1,201 @@
+// Copyright 2014 The Chromium 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/external_application_listener_posix.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_checker.h"
+#include "base/tracked_objects.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/edk/embedder/channel_init.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/shell/external_application_registrar.mojom.h"
+#include "mojo/shell/incoming_connection_listener_posix.h"
+#include "net/base/net_errors.h"
+#include "net/socket/socket_descriptor.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+const char kDefaultListenSocketPath[] = "/var/run/mojo/system_socket";
+} // namespace
+
+// static
+base::FilePath ExternalApplicationListener::ConstructDefaultSocketPath() {
+ return base::FilePath(FILE_PATH_LITERAL(kDefaultListenSocketPath));
+}
+
+// static
+scoped_ptr<ExternalApplicationListener> ExternalApplicationListener::Create(
+ const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& io_runner) {
+ return make_scoped_ptr(
+ new ExternalApplicationListenerPosix(shell_runner, io_runner));
+}
+
+class ExternalApplicationListenerPosix::RegistrarImpl
+ : public InterfaceImpl<ExternalApplicationRegistrar> {
+ public:
+ explicit RegistrarImpl(const RegisterCallback& callback);
+ virtual ~RegistrarImpl() override;
+
+ virtual void OnConnectionError() override;
+
+ embedder::ChannelInit channel_init;
+
+ private:
+ virtual void Register(const String& app_url,
+ InterfaceRequest<Shell> shell,
+ const mojo::Closure& callback) override;
+
+ const RegisterCallback register_callback_;
+};
+
+ExternalApplicationListenerPosix::ExternalApplicationListenerPosix(
+ const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& io_runner)
+ : shell_runner_(shell_runner),
+ io_runner_(io_runner),
+ signal_on_listening_(true, false),
+ weak_ptr_factory_(this) {
+ DCHECK(shell_runner_.get() && shell_runner_->RunsTasksOnCurrentThread());
+ DCHECK(io_runner_.get());
+ listener_thread_checker_.DetachFromThread(); // Will attach in StartListener.
+}
+
+ExternalApplicationListenerPosix::~ExternalApplicationListenerPosix() {
+ DCHECK(register_thread_checker_.CalledOnValidThread());
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ // listener_ needs to be destroyed on io_runner_, and it has to die before
+ // this object does, as it holds a pointer back to this instance.
+ base::WaitableEvent stop_listening_event(true, false);
+ io_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ExternalApplicationListenerPosix::StopListening,
+ base::Unretained(this),
+ &stop_listening_event));
+ stop_listening_event.Wait();
+}
+
+void ExternalApplicationListenerPosix::ListenInBackground(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback) {
+ DCHECK(register_thread_checker_.CalledOnValidThread());
+ ListenInBackgroundWithErrorCallback(
+ listen_socket_path, register_callback, ErrorCallback());
+}
+
+void ExternalApplicationListenerPosix::ListenInBackgroundWithErrorCallback(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback,
+ const ErrorCallback& error_callback) {
+ DCHECK(register_thread_checker_.CalledOnValidThread());
+ register_callback_ = register_callback;
+ error_callback_ = error_callback;
+
+ io_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ExternalApplicationListenerPosix::StartListening,
+ base::Unretained(this),
+ listen_socket_path));
+}
+
+void ExternalApplicationListenerPosix::WaitForListening() {
+ DCHECK(register_thread_checker_.CalledOnValidThread());
+ signal_on_listening_.Wait();
+}
+
+void ExternalApplicationListenerPosix::StartListening(
+ const base::FilePath& listen_socket_path) {
+ CHECK_EQ(base::MessageLoop::current()->type(), base::MessageLoop::TYPE_IO);
+ DCHECK(listener_thread_checker_.CalledOnValidThread());
+ listener_.reset(
+ new IncomingConnectionListenerPosix(listen_socket_path, this));
+ listener_->StartListening();
+}
+
+void ExternalApplicationListenerPosix::StopListening(
+ base::WaitableEvent* event) {
+ DCHECK(listener_thread_checker_.CalledOnValidThread());
+ listener_.reset();
+ event->Signal();
+}
+
+void ExternalApplicationListenerPosix::OnListening(int rv) {
+ DCHECK(listener_thread_checker_.CalledOnValidThread());
+ signal_on_listening_.Signal();
+ shell_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ExternalApplicationListenerPosix::RunErrorCallbackIfListeningFailed,
+ weak_ptr_factory_.GetWeakPtr(),
+ rv));
+}
+
+void ExternalApplicationListenerPosix::OnConnection(
+ net::SocketDescriptor incoming) {
+ DCHECK(listener_thread_checker_.CalledOnValidThread());
+ shell_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ExternalApplicationListenerPosix::CreatePipeAndBindToRegistrarImpl,
+ weak_ptr_factory_.GetWeakPtr(),
+ incoming));
+}
+
+void ExternalApplicationListenerPosix::RunErrorCallbackIfListeningFailed(
+ int rv) {
+ DCHECK(register_thread_checker_.CalledOnValidThread());
+ if (rv != net::OK && !error_callback_.is_null()) {
+ error_callback_.Run(rv);
+ }
+}
+
+void ExternalApplicationListenerPosix::CreatePipeAndBindToRegistrarImpl(
+ net::SocketDescriptor incoming_socket) {
+ DCHECK(register_thread_checker_.CalledOnValidThread());
+
+ DVLOG(1) << "Accepted incoming connection";
+ scoped_ptr<RegistrarImpl> registrar(new RegistrarImpl(register_callback_));
+ // Passes ownership of incoming_socket to registrar->channel_init.
+ mojo::ScopedMessagePipeHandle message_pipe =
+ registrar->channel_init.Init(incoming_socket, io_runner_);
+ CHECK(message_pipe.is_valid());
+
+ BindToPipe(registrar.release(), message_pipe.Pass());
+}
+
+ExternalApplicationListenerPosix::RegistrarImpl::RegistrarImpl(
+ const RegisterCallback& callback)
+ : register_callback_(callback) {
+}
+
+ExternalApplicationListenerPosix::RegistrarImpl::~RegistrarImpl() {
+}
+
+void ExternalApplicationListenerPosix::RegistrarImpl::OnConnectionError() {
+ channel_init.WillDestroySoon();
+}
+
+void ExternalApplicationListenerPosix::RegistrarImpl::Register(
+ const String& app_url,
+ InterfaceRequest<Shell> shell,
+ const mojo::Closure& callback) {
+ register_callback_.Run(app_url.To<GURL>(), shell.PassMessagePipe());
+ callback.Run();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/external_application_listener_posix.h b/mojo/shell/external_application_listener_posix.h
new file mode 100644
index 0000000..788a4ff
--- /dev/null
+++ b/mojo/shell/external_application_listener_posix.h
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium 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_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
+#define MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
+
+#include "mojo/shell/external_application_listener.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/edk/embedder/channel_init.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/shell/external_application_registrar.mojom.h"
+#include "mojo/shell/incoming_connection_listener_posix.h"
+#include "net/socket/socket_descriptor.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+// In order to support Mojo apps whose lifetime is managed by
+// something other than mojo_shell, mojo_shell needs to support a
+// mechanism by which such an application can discover a running shell
+// instance, connect to it, and ask to be "registered" at a given
+// URL. Registration implies that the app can be connected to at that
+// URL from then on out, and that the app has received a usable ShellPtr.
+//
+// This class implements most of the mojo_shell side of external application
+// registration. It handles:
+// 1) discoverability - sets up a unix domain socket at a well-known location,
+// 2) incoming connections - listens for and accepts incoming connections on
+// that socket, and
+// 3) registration requests - forwarded to a RegisterCallback that implements
+// the actual registration logic.
+//
+// External applications can connect to the shell using the
+// ExternalApplicationRegistrarConnection class.
+class ExternalApplicationListenerPosix
+ : public ExternalApplicationListener,
+ public IncomingConnectionListenerPosix::Delegate {
+ public:
+ // This class uses two threads, an IO thread for listening and accepting
+ // incoming sockets, and a "main" thread where all Mojo traffic is processed
+ // and provided callbacks are run.
+ ExternalApplicationListenerPosix(
+ const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& io_runner);
+
+ // Some of this class' internal state needs to be destroyed on io_runner_,
+ // so the destructor will post a task to that thread to call StopListening()
+ // and then wait for it to complete.
+ virtual ~ExternalApplicationListenerPosix();
+
+ // Begin listening (on io_runner) to a socket at listen_socket_path.
+ // Incoming registration requests will be forwarded to register_callback.
+ // Errors are ignored.
+ virtual void ListenInBackground(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback) override;
+
+ // Begin listening (on io_runner) to a socket at listen_socket_path.
+ // Incoming registration requests will be forwarded to register_callback.
+ // Errors are reported via error_callback.
+ virtual void ListenInBackgroundWithErrorCallback(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback,
+ const ErrorCallback& error_callback) override;
+
+ // Block the current thread until listening has started on io_runner.
+ // If listening has already started, returns immediately.
+ virtual void WaitForListening() override;
+
+ private:
+ class RegistrarImpl;
+
+ // MUST be called on io_runner.
+ // Creates listener_ and tells it to StartListening() on a socket it creates
+ // at listen_socket_path.
+ void StartListening(const base::FilePath& listen_socket_path);
+
+ // MUST be called on io_runner.
+ // Destroys listener_ and signals event when done.
+ void StopListening(base::WaitableEvent* event);
+
+ // Implementation of IncomingConnectionListener::Delegate
+ virtual void OnListening(int rv) override;
+ virtual void OnConnection(net::SocketDescriptor incoming) override;
+
+ // If listener_ fails to start listening, this method is run on shell_runner_
+ // to report the error.
+ void RunErrorCallbackIfListeningFailed(int rv);
+
+ // When a connection succeeds, it is passed to this method running
+ // on shell_runner_, where it is "promoted" to a Mojo MessagePipe and
+ // bound to a RegistrarImpl.
+ void CreatePipeAndBindToRegistrarImpl(net::SocketDescriptor incoming_socket);
+
+ scoped_refptr<base::SequencedTaskRunner> shell_runner_;
+ scoped_refptr<base::SequencedTaskRunner> io_runner_;
+
+ // MUST be created, used, and destroyed on io_runner_.
+ scoped_ptr<IncomingConnectionListenerPosix> listener_;
+
+ // Callers can wait on this event, which will be signalled once listening
+ // has either successfully begun or definitively failed.
+ base::WaitableEvent signal_on_listening_;
+
+ // Locked to thread on which StartListening() is run (should be io_runner_).
+ // All methods that touch listener_ check that they're on that same thread.
+ base::ThreadChecker listener_thread_checker_;
+
+ ErrorCallback error_callback_;
+ RegisterCallback register_callback_;
+ base::ThreadChecker register_thread_checker_;
+
+ // Used on shell_runner_.
+ base::WeakPtrFactory<ExternalApplicationListenerPosix> weak_ptr_factory_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
diff --git a/mojo/shell/external_application_listener_unittest.cc b/mojo/shell/external_application_listener_unittest.cc
new file mode 100644
index 0000000..5676b68
--- /dev/null
+++ b/mojo/shell/external_application_listener_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/shell/external_application_listener_posix.h"
+#include "mojo/shell/external_application_registrar.mojom.h"
+#include "mojo/shell/external_application_registrar_connection.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/unix_domain_client_socket_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+class ExternalApplicationListenerTest : public testing::Test {
+ public:
+ ExternalApplicationListenerTest() : io_thread_("io thread") {}
+ virtual ~ExternalApplicationListenerTest() {}
+
+ virtual void SetUp() override {
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ io_thread_.StartWithOptions(options);
+
+ listener_.reset(new ExternalApplicationListenerPosix(
+ loop_.task_runner(), io_thread_.task_runner()));
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ socket_path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("socket"));
+ }
+
+ protected:
+ base::MessageLoop loop_;
+ base::RunLoop run_loop_;
+ base::Thread io_thread_;
+
+ base::ScopedTempDir temp_dir_;
+ ApplicationManager application_manager_;
+ base::FilePath socket_path_;
+ scoped_ptr<ExternalApplicationListener> listener_;
+};
+
+namespace {
+
+class StubShellImpl : public InterfaceImpl<Shell> {
+ private:
+ virtual void ConnectToApplication(
+ const String& requestor_url,
+ InterfaceRequest<ServiceProvider> in_service_provider) override {
+ ServiceProviderPtr out_service_provider;
+ out_service_provider.Bind(in_service_provider.PassMessagePipe());
+ client()->AcceptConnection(requestor_url, out_service_provider.Pass());
+ }
+};
+
+void DoLocalRegister(const GURL& app_url, ScopedMessagePipeHandle shell) {
+ BindToPipe(new StubShellImpl, shell.Pass());
+}
+
+void QuitLoopOnConnect(scoped_refptr<base::TaskRunner> loop,
+ base::Closure quit_callback,
+ int rv) {
+ EXPECT_EQ(net::OK, rv);
+ loop->PostTask(FROM_HERE, quit_callback);
+}
+
+void ConnectOnIOThread(const base::FilePath& socket_path,
+ scoped_refptr<base::TaskRunner> to_quit,
+ base::Closure quit_callback) {
+ ExternalApplicationRegistrarConnection connection(socket_path);
+ connection.Connect(base::Bind(&QuitLoopOnConnect, to_quit, quit_callback));
+}
+
+} // namespace
+
+TEST_F(ExternalApplicationListenerTest, ConnectConnection) {
+ listener_->ListenInBackground(socket_path_, base::Bind(&DoLocalRegister));
+ listener_->WaitForListening();
+ io_thread_.task_runner()->PostTask(FROM_HERE,
+ base::Bind(&ConnectOnIOThread,
+ socket_path_,
+ loop_.task_runner(),
+ run_loop_.QuitClosure()));
+ run_loop_.Run();
+}
+
+namespace {
+
+class QuitLoopOnConnectApplicationImpl : public InterfaceImpl<Application> {
+ public:
+ QuitLoopOnConnectApplicationImpl(const std::string& url,
+ scoped_refptr<base::TaskRunner> loop,
+ base::Closure quit_callback)
+ : url_(url), to_quit_(loop), quit_callback_(quit_callback) {}
+
+ private:
+ virtual void Initialize(Array<String> args) override {}
+
+ virtual void AcceptConnection(const String& requestor_url,
+ ServiceProviderPtr p) override {
+ DVLOG(1) << url_ << " accepting connection from " << requestor_url;
+ to_quit_->PostTask(FROM_HERE, quit_callback_);
+ }
+
+ const std::string url_;
+ scoped_refptr<base::TaskRunner> to_quit_;
+ base::Closure quit_callback_;
+};
+
+class FakeExternalApplication {
+ public:
+ FakeExternalApplication(const std::string& url) : url_(url) {}
+
+ void ConnectSynchronously(const base::FilePath& socket_path) {
+ connection_.reset(new ExternalApplicationRegistrarConnection(socket_path));
+ net::TestCompletionCallback connect_callback;
+ connection_->Connect(connect_callback.callback());
+ connect_callback.WaitForResult();
+ }
+
+ // application_impl is the the actual implementation to be registered.
+ void Register(scoped_ptr<InterfaceImpl<Application>> application_impl,
+ base::Closure register_complete_callback) {
+ connection_->Register(GURL(url_), &ptr_, register_complete_callback);
+ application_impl_ = application_impl.Pass();
+ ptr_.set_client(application_impl_.get());
+ }
+
+ void ConnectToAppByUrl(std::string app_url) {
+ ServiceProviderPtr sp;
+ ptr_->ConnectToApplication(app_url, Get(&sp));
+ }
+
+ const std::string& url() { return url_; }
+
+ private:
+ const std::string url_;
+ scoped_ptr<InterfaceImpl<Application>> application_impl_;
+ ShellPtr ptr_;
+
+ scoped_ptr<ExternalApplicationRegistrarConnection> connection_;
+};
+
+void ConnectToApp(FakeExternalApplication* connector,
+ FakeExternalApplication* connectee) {
+ connector->ConnectToAppByUrl(connectee->url());
+}
+
+void NoOp() {
+}
+
+void ConnectAndRegisterOnIOThread(const base::FilePath& socket_path,
+ scoped_refptr<base::TaskRunner> loop,
+ base::Closure quit_callback,
+ FakeExternalApplication* connector,
+ FakeExternalApplication* connectee) {
+ // Connect the first app to the registrar.
+ connector->ConnectSynchronously(socket_path);
+ // connector will use this implementation of the Mojo Application interface
+ // once registration complete.
+ scoped_ptr<QuitLoopOnConnectApplicationImpl> connector_app_impl(
+ new QuitLoopOnConnectApplicationImpl(
+ connector->url(), loop, quit_callback));
+ // Since connectee won't be ready when connector is done registering, pass
+ // in a do-nothing callback.
+ connector->Register(connector_app_impl.PassAs<InterfaceImpl<Application>>(),
+ base::Bind(&NoOp));
+
+ // Connect the second app to the registrar.
+ connectee->ConnectSynchronously(socket_path);
+ scoped_ptr<QuitLoopOnConnectApplicationImpl> connectee_app_impl(
+ new QuitLoopOnConnectApplicationImpl(
+ connectee->url(), loop, quit_callback));
+ // After connectee is successfully registered, connector should be
+ // able to connect to is by URL. Pass in a callback to attempt the
+ // app -> app connection.
+ connectee->Register(connectee_app_impl.PassAs<InterfaceImpl<Application>>(),
+ base::Bind(&ConnectToApp, connector, connectee));
+}
+
+void DestroyOnIOThread(scoped_ptr<FakeExternalApplication> doomed1,
+ scoped_ptr<FakeExternalApplication> doomed2) {
+}
+
+} // namespace
+
+// Create two external applications, have them discover and connect to
+// the registrar, and then have one app connect to the other by URL.
+TEST_F(ExternalApplicationListenerTest, ConnectTwoExternalApplications) {
+ listener_->ListenInBackground(
+ socket_path_,
+ base::Bind(&ApplicationManager::RegisterExternalApplication,
+ base::Unretained(&application_manager_)));
+ listener_->WaitForListening();
+
+ // Create two external apps.
+ scoped_ptr<FakeExternalApplication> supersweet_app(
+ new FakeExternalApplication("http://my.supersweet.app"));
+ scoped_ptr<FakeExternalApplication> awesome_app(
+ new FakeExternalApplication("http://my.awesome.app"));
+
+ // Connecting and talking to the registrar has to happen on the IO thread.
+ io_thread_.task_runner()->PostTask(FROM_HERE,
+ base::Bind(&ConnectAndRegisterOnIOThread,
+ socket_path_,
+ loop_.task_runner(),
+ run_loop_.QuitClosure(),
+ supersweet_app.get(),
+ awesome_app.get()));
+ run_loop_.Run();
+
+ // The apps need to be destroyed on the thread where they did socket stuff.
+ io_thread_.task_runner()->PostTask(FROM_HERE,
+ base::Bind(&DestroyOnIOThread,
+ base::Passed(&supersweet_app),
+ base::Passed(&awesome_app)));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/external_application_listener_win.cc b/mojo/shell/external_application_listener_win.cc
new file mode 100644
index 0000000..f2c3151
--- /dev/null
+++ b/mojo/shell/external_application_listener_win.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/shell/external_application_listener_win.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner.h"
+
+namespace mojo {
+namespace shell {
+
+// static
+base::FilePath ExternalApplicationListener::ConstructDefaultSocketPath() {
+ return base::FilePath(FILE_PATH_LITERAL(""));
+}
+
+// static
+scoped_ptr<ExternalApplicationListener> ExternalApplicationListener::Create(
+ const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& io_runner) {
+ return make_scoped_ptr(new ExternalApplicationListenerStub);
+}
+
+ExternalApplicationListenerStub::ExternalApplicationListenerStub() {
+}
+
+ExternalApplicationListenerStub::~ExternalApplicationListenerStub() {
+}
+
+void ExternalApplicationListenerStub::ListenInBackground(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback) {
+}
+
+void ExternalApplicationListenerStub::ListenInBackgroundWithErrorCallback(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback,
+ const ErrorCallback& error_callback) {
+}
+
+void ExternalApplicationListenerStub::WaitForListening() {
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/external_application_listener_win.h b/mojo/shell/external_application_listener_win.h
new file mode 100644
index 0000000..8280e75
--- /dev/null
+++ b/mojo/shell/external_application_listener_win.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_SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
+#define MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
+
+#include "mojo/shell/external_application_listener.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+
+namespace mojo {
+namespace shell {
+
+// External application registration is not supported on Windows, hence this
+// stub.
+class ExternalApplicationListenerStub : public ExternalApplicationListener {
+ public:
+ ExternalApplicationListenerStub();
+ virtual ~ExternalApplicationListenerStub() override;
+
+ void ListenInBackground(const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback) override;
+ void ListenInBackgroundWithErrorCallback(
+ const base::FilePath& listen_socket_path,
+ const RegisterCallback& register_callback,
+ const ErrorCallback& error_callback) override;
+ void WaitForListening() override;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
diff --git a/mojo/shell/external_application_registrar.mojom b/mojo/shell/external_application_registrar.mojom
new file mode 100644
index 0000000..db76aec
--- /dev/null
+++ b/mojo/shell/external_application_registrar.mojom
@@ -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 "mojo/public/interfaces/application/shell.mojom"
+
+module mojo {
+
+// This interface allows applications running outside the auspices of a Shell
+// to request registration at the given application_url.
+// The shell implementing this interface will make the calling application
+// available to other apps at that URL and bind the provided Shell& to
+// an impl capable of servicing the external application's connection requests.
+interface ExternalApplicationRegistrar {
+ Register(string application_url, Shell& shell) => ();
+};
+
+}
diff --git a/mojo/shell/external_application_registrar_connection.cc b/mojo/shell/external_application_registrar_connection.cc
new file mode 100644
index 0000000..57498a9
--- /dev/null
+++ b/mojo/shell/external_application_registrar_connection.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/external_application_registrar_connection.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/channel_init.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/shell/external_application_registrar.mojom.h"
+#include "net/base/net_errors.h"
+#include "net/socket/socket_descriptor.h"
+#include "net/socket/unix_domain_client_socket_posix.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+ExternalApplicationRegistrarConnection::ExternalApplicationRegistrarConnection(
+ const base::FilePath& socket_path)
+ : client_socket_(
+ new net::UnixDomainClientSocket(socket_path.value(), false)) {
+}
+
+ExternalApplicationRegistrarConnection::
+ ~ExternalApplicationRegistrarConnection() {
+ channel_init_.WillDestroySoon();
+}
+
+void ExternalApplicationRegistrarConnection::OnConnectionError() {
+ channel_init_.WillDestroySoon();
+}
+
+void ExternalApplicationRegistrarConnection::Connect(
+ const net::CompletionCallback& callback) {
+ DCHECK(client_socket_) << "Single use only.";
+ int rv = client_socket_->Connect(
+ base::Bind(&ExternalApplicationRegistrarConnection::OnConnect,
+ base::Unretained(this),
+ callback));
+ if (rv != net::ERR_IO_PENDING) {
+ DVLOG(1) << "Connect returning immediately: " << net::ErrorToString(rv);
+ OnConnect(callback, rv);
+ return;
+ }
+ DVLOG(1) << "Waiting for connection.";
+}
+
+void ExternalApplicationRegistrarConnection::Register(
+ const GURL& app_url,
+ ShellPtr* shell,
+ base::Closure register_complete_callback) {
+ DCHECK(!client_socket_);
+ registrar_->Register(
+ String::From(app_url), Get(shell), register_complete_callback);
+}
+
+void ExternalApplicationRegistrarConnection::OnConnect(
+ net::CompletionCallback callback,
+ int rv) {
+ DVLOG(1) << "OnConnect called: " << net::ErrorToString(rv);
+ if (rv != net::OK) {
+ callback.Run(rv);
+ return;
+ }
+
+ mojo::ScopedMessagePipeHandle ptr_message_pipe_handle =
+ channel_init_.Init(client_socket_->ReleaseConnectedSocket(),
+ base::MessageLoopProxy::current());
+ CHECK(ptr_message_pipe_handle.is_valid());
+ client_socket_.reset(); // This is dead now, ensure it can't be reused.
+
+ registrar_.Bind(ptr_message_pipe_handle.Pass());
+ callback.Run(rv);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/external_application_registrar_connection.h b/mojo/shell/external_application_registrar_connection.h
new file mode 100644
index 0000000..327d19b
--- /dev/null
+++ b/mojo/shell/external_application_registrar_connection.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium 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_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
+#define MOJO_SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/edk/embedder/channel_init.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/shell/external_application_registrar.mojom.h"
+#include "net/socket/socket_descriptor.h"
+#include "net/socket/unix_domain_client_socket_posix.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+// Externally-running applications can use this class to discover and register
+// with a running mojo_shell instance.
+// MUST be run on an IO thread
+class ExternalApplicationRegistrarConnection : public ErrorHandler {
+ public:
+ // Configures client_socket_ to point at socket_path.
+ explicit ExternalApplicationRegistrarConnection(
+ const base::FilePath& socket_path);
+ virtual ~ExternalApplicationRegistrarConnection();
+
+ // Implementation of ErrorHandler
+ virtual void OnConnectionError() override;
+
+ // Connects client_socket_ and binds it to registrar_.
+ // Status code is passed to callback upon success or failure.
+ // May return either synchronously or asynchronously, depending on the
+ // status of the underlying socket.
+ void Connect(const net::CompletionCallback& callback);
+
+ // Registers this app with the shell at the provided URL.
+ // shell is not ready for use until register_complete_callback fires.
+ // TODO(cmasone): Once the pipe for shell can be placed in a FIFO relationship
+ // with the one underlying registrar_, the callback becomes unneeded.
+ void Register(const GURL& app_url,
+ ShellPtr* shell,
+ base::Closure register_complete_callback);
+
+ private:
+ // Handles the result of Connect(). If it was successful, promotes the socket
+ // to a MessagePipe and binds it to registrar_.
+ // Hands rv to callback regardless.
+ void OnConnect(net::CompletionCallback callback, int rv);
+
+ scoped_ptr<net::UnixDomainClientSocket> client_socket_;
+ mojo::embedder::ChannelInit channel_init_;
+ ExternalApplicationRegistrarPtr registrar_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
diff --git a/mojo/shell/external_application_test_main.cc b/mojo/shell/external_application_test_main.cc
new file mode 100644
index 0000000..859f52b
--- /dev/null
+++ b/mojo/shell/external_application_test_main.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 "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ mojo::embedder::Init(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+
+ base::TestSuite test_suite(argc, argv);
+ return base::LaunchUnitTests(
+ argc,
+ argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/shell/external_service.mojom b/mojo/shell/external_service.mojom
new file mode 100644
index 0000000..8cc9e0c
--- /dev/null
+++ b/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/mojo/shell/in_process_dynamic_service_runner.cc b/mojo/shell/in_process_dynamic_service_runner.cc
new file mode 100644
index 0000000..c98ef0f
--- /dev/null
+++ b/mojo/shell/in_process_dynamic_service_runner.cc
@@ -0,0 +1,153 @@
+// Copyright 2014 The Chromium 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 "mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.h"
+#include "mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.h"
+#include "mojo/public/platform/native/gles2_impl_thunks.h"
+#include "mojo/public/platform/native/gles2_thunks.h"
+#include "mojo/public/platform/native/system_thunks.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+template <typename Thunks>
+bool SetThunks(Thunks (*make_thunks)(),
+ const char* function_name,
+ base::ScopedNativeLibrary* library) {
+ typedef size_t (*SetThunksFn)(const Thunks* thunks);
+ SetThunksFn set_thunks =
+ reinterpret_cast<SetThunksFn>(library->GetFunctionPointer(function_name));
+ if (!set_thunks)
+ return false;
+ Thunks thunks = make_thunks();
+ size_t expected_size = set_thunks(&thunks);
+ if (expected_size > sizeof(Thunks)) {
+ LOG(ERROR) << "Invalid app library: expected " << function_name
+ << " to return thunks of size: " << expected_size;
+ return false;
+ }
+ return true;
+}
+}
+
+InProcessDynamicServiceRunner::InProcessDynamicServiceRunner(
+ Context* context) {
+}
+
+InProcessDynamicServiceRunner::~InProcessDynamicServiceRunner() {
+ if (thread_) {
+ DCHECK(thread_->HasBeenStarted());
+ DCHECK(!thread_->HasBeenJoined());
+ thread_->Join();
+ }
+
+ // It is important to let the thread exit before unloading the DSO because
+ // the library may have registered thread-local data and destructors to run
+ // on thread termination.
+ app_library_.Reset(base::NativeLibrary());
+}
+
+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_);
+ thread_.reset(new base::DelegateSimpleThread(this, "app_thread"));
+ thread_->Start();
+}
+
+void InProcessDynamicServiceRunner::Run() {
+ DVLOG(2) << "Loading/running Mojo app in process from library: "
+ << app_path_.value();
+
+ do {
+ base::NativeLibraryLoadError error;
+ app_library_.Reset(base::LoadNativeLibrary(app_path_, &error));
+ if (!app_library_.is_valid()) {
+ LOG(ERROR) << "Failed to load app library (error: " << error.ToString()
+ << ")";
+ break;
+ }
+
+ if (!SetThunks(
+ &MojoMakeSystemThunks, "MojoSetSystemThunks", &app_library_)) {
+ // In the component build, Mojo Apps link against mojo_system_impl.
+#if !defined(COMPONENT_BUILD)
+ // 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";
+#endif
+ }
+
+ if (SetThunks(&MojoMakeGLES2ControlThunks,
+ "MojoSetGLES2ControlThunks",
+ &app_library_)) {
+ // If we have the control thunks, we probably also have the
+ // GLES2 implementation thunks.
+ if (!SetThunks(&MojoMakeGLES2ImplThunks,
+ "MojoSetGLES2ImplThunks",
+ &app_library_)) {
+ // In the component build, Mojo Apps link against mojo_gles2_impl.
+#if !defined(COMPONENT_BUILD)
+ // Warn on this really weird case: The library requires the GLES2
+ // control functions, but doesn't require the GLES2 implementation.
+ LOG(WARNING) << app_path_.value()
+ << " has MojoSetGLES2ControlThunks, "
+ "but doesn't have MojoSetGLES2ImplThunks.";
+#endif
+ }
+
+ // If the application is using GLES2 extension points, register those
+ // thunks. Applications may use or not use any of these, so don't warn if
+ // they are missing.
+ SetThunks(MojoMakeGLES2ImplChromiumTextureMailboxThunks,
+ "MojoSetGLES2ImplChromiumTextureMailboxThunks",
+ &app_library_);
+ SetThunks(MojoMakeGLES2ImplChromiumSyncPointThunks,
+ "MojoSetGLES2ImplChromiumSyncPointThunks",
+ &app_library_);
+ }
+ // Unlike system thunks, we don't warn on a lack of GLES2 thunks because
+ // not everything is a visual app.
+
+ typedef MojoResult (*MojoMainFunction)(MojoHandle);
+ MojoMainFunction main_function = reinterpret_cast<MojoMainFunction>(
+ app_library_.GetFunctionPointer("MojoMain"));
+ if (!main_function) {
+ LOG(ERROR) << "Entrypoint MojoMain not found: " << app_path_.value();
+ 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);
+
+ app_completed_callback_runner_.Run();
+ app_completed_callback_runner_.Reset();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/in_process_dynamic_service_runner.h b/mojo/shell/in_process_dynamic_service_runner.h
new file mode 100644
index 0000000..0e0cade
--- /dev/null
+++ b/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/scoped_native_library.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/shell/dynamic_service_runner.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;
+
+ base::FilePath app_path_;
+ ScopedMessagePipeHandle service_handle_;
+ base::Callback<bool(void)> app_completed_callback_runner_;
+
+ base::ScopedNativeLibrary app_library_;
+ scoped_ptr<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/mojo/shell/in_process_dynamic_service_runner_unittest.cc b/mojo/shell/in_process_dynamic_service_runner_unittest.cc
new file mode 100644
index 0000000..2ade6ec
--- /dev/null
+++ b/mojo/shell/in_process_dynamic_service_runner_unittest.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/context.h"
+#include "mojo/shell/in_process_dynamic_service_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+
+TEST(InProcessDynamicServiceRunnerTest, NotStarted) {
+ Context context;
+ base::MessageLoop loop;
+ context.Init();
+ InProcessDynamicServiceRunner runner(&context);
+ // Shouldn't crash or DCHECK on destruction.
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/incoming_connection_listener_posix.cc b/mojo/shell/incoming_connection_listener_posix.cc
new file mode 100644
index 0000000..8684d15
--- /dev/null
+++ b/mojo/shell/incoming_connection_listener_posix.cc
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium 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/incoming_connection_listener_posix.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/tracked_objects.h"
+#include "net/base/net_errors.h"
+#include "net/socket/socket_descriptor.h"
+#include "net/socket/unix_domain_server_socket_posix.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+// TODO(cmasone): Figure out what we should be doing about "authenticating" the
+// process trying to connect.
+bool Yes(const net::UnixDomainServerSocket::Credentials& ignored) {
+ return true;
+}
+} // anonymous namespace
+
+IncomingConnectionListenerPosix::IncomingConnectionListenerPosix(
+ const base::FilePath& socket_path,
+ Delegate* delegate)
+ : delegate_(delegate),
+ socket_path_(socket_path),
+ listen_socket_(base::Bind(&Yes), false),
+ incoming_socket_(net::kInvalidSocket),
+ weak_ptr_factory_(this) {
+ DCHECK(delegate_);
+}
+
+IncomingConnectionListenerPosix::~IncomingConnectionListenerPosix() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ if (!base::DeleteFile(socket_path_, false))
+ PLOG(ERROR) << "Listening Unix domain socket can't be destroyed.";
+}
+
+void IncomingConnectionListenerPosix::StartListening() {
+ DCHECK(listen_thread_checker_.CalledOnValidThread());
+
+ int rv = net::OK;
+ if (!base::DirectoryExists(socket_path_.DirName())) {
+ LOG(ERROR) << "Directorty for listening socket does not exist.";
+ rv = net::ERR_FILE_NOT_FOUND;
+ } else if (!base::PathIsWritable(socket_path_.DirName())) {
+ LOG(ERROR) << "Listening socket file path is not writable.";
+ rv = net::ERR_ACCESS_DENIED;
+ } else if (!base::DeleteFile(socket_path_, false)) {
+ PLOG(ERROR) << "Listening socket file exists and can't be deleted";
+ rv = net::ERR_FILE_EXISTS;
+ } else {
+ const std::string& socket_address = socket_path_.value();
+ rv = listen_socket_.ListenWithAddressAndPort(socket_address, 0, 100);
+ }
+
+ // Call OnListening() before Accept(), so that the delegate is certain to
+ // hear about listening before a connection might be accepted below.
+ delegate_->OnListening(rv);
+ if (rv == net::OK)
+ Accept();
+}
+
+void IncomingConnectionListenerPosix::Accept() {
+ DCHECK(listen_thread_checker_.CalledOnValidThread());
+ int rv = listen_socket_.AcceptSocketDescriptor(
+ &incoming_socket_,
+ base::Bind(&IncomingConnectionListenerPosix::OnAccept,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ // If rv == net::ERR_IO_PENDING), listen_socket_ will call
+ // OnAccept() later, when a connection attempt comes in.
+ if (rv != net::ERR_IO_PENDING) {
+ DVLOG_IF(1, rv == net::OK) << "Accept succeeded immediately";
+ OnAccept(rv);
+ }
+}
+
+void IncomingConnectionListenerPosix::OnAccept(int rv) {
+ DCHECK(listen_thread_checker_.CalledOnValidThread());
+
+ if (rv != net::OK || incoming_socket_ == net::kInvalidSocket) {
+ LOG_IF(ERROR, rv != net::OK) << "Accept failed " << net::ErrorToString(rv);
+ PLOG_IF(ERROR, rv == net::OK) << "Socket invalid";
+ } else {
+ // Passes ownership of incoming_socket_ to delegate_.
+ delegate_->OnConnection(incoming_socket_);
+ incoming_socket_ = net::kInvalidSocket;
+ }
+
+ // Continue waiting to accept incoming connections...
+ Accept();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/incoming_connection_listener_posix.h b/mojo/shell/incoming_connection_listener_posix.h
new file mode 100644
index 0000000..3981702
--- /dev/null
+++ b/mojo/shell/incoming_connection_listener_posix.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_SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
+#define MOJO_SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "net/socket/socket_descriptor.h"
+#include "net/socket/unix_domain_server_socket_posix.h"
+
+namespace mojo {
+namespace shell {
+
+// Asynchronously listens for incoming connections on a unix domain
+// socket at the provided path. Expects the parent directory in the
+// path to exist. Must be run on an IO thread.
+class IncomingConnectionListenerPosix {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {} // Abstract base class, so this is safe.
+
+ // Called when listening has started. rv is from net/base/net_error_list.h
+ virtual void OnListening(int rv) = 0;
+
+ // Called every time an incoming connection is accepted. The delegate
+ // takes ownership of incoming.
+ virtual void OnConnection(net::SocketDescriptor incoming) = 0;
+ };
+
+ IncomingConnectionListenerPosix(const base::FilePath& socket_path,
+ Delegate* delegate);
+ virtual ~IncomingConnectionListenerPosix();
+
+ // Attempts to bind a unix domain socket, set up for listening, at
+ // socket_path_.
+ // Regardless of success or failure, calls delegate->OnListening() with a
+ // status code. If the socket was successfully created, begins asynchronously
+ // waiting to accept incoming connections.
+ void StartListening();
+
+ private:
+ // Tells listen_socket_ to perform a non-blocking accept(). It may succeed
+ // or fail immediately, or asynchronously wait for a later connection attempt.
+ // Regardless, when it returns a definitive result (OK or a failing error),
+ // calls OnAccept().
+ void Accept();
+
+ // If rv indicates success, incoming_socket_ should be populated with a
+ // connected FD. Hands this off to delegate->OnConnection() and goes
+ // back to non-blocking accept().
+ // Upon error, logs the error and goes back to non-blocking accept().
+ void OnAccept(int rv);
+
+ Delegate* const delegate_;
+
+ const base::FilePath socket_path_;
+ net::UnixDomainServerSocket listen_socket_;
+ base::ThreadChecker listen_thread_checker_;
+
+ net::SocketDescriptor incoming_socket_;
+
+ base::WeakPtrFactory<IncomingConnectionListenerPosix> weak_ptr_factory_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
diff --git a/mojo/shell/incoming_connection_listener_unittest.cc b/mojo/shell/incoming_connection_listener_unittest.cc
new file mode 100644
index 0000000..5488c98
--- /dev/null
+++ b/mojo/shell/incoming_connection_listener_unittest.cc
@@ -0,0 +1,149 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/edk/embedder/channel_init.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/shell/external_application_registrar_connection.h"
+#include "mojo/shell/incoming_connection_listener_posix.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/socket_descriptor.h"
+#include "net/socket/unix_domain_client_socket_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+// Delegate implementation that expects success.
+class TestDelegate : public IncomingConnectionListenerPosix::Delegate {
+ public:
+ TestDelegate() {}
+ virtual ~TestDelegate() {}
+
+ virtual void OnListening(int rv) override { EXPECT_EQ(net::OK, rv); }
+ virtual void OnConnection(net::SocketDescriptor incoming) override {
+ EXPECT_NE(net::kInvalidSocket, incoming);
+ }
+};
+
+// Delegate implementation that expects a (configurable) failure to listen.
+class ListeningFailsDelegate
+ : public IncomingConnectionListenerPosix::Delegate {
+ public:
+ explicit ListeningFailsDelegate(int expected) : expected_error_(expected) {}
+ virtual ~ListeningFailsDelegate() {}
+
+ virtual void OnListening(int rv) override { EXPECT_EQ(expected_error_, rv); }
+ virtual void OnConnection(net::SocketDescriptor incoming) override {
+ FAIL() << "No connection should be attempted.";
+ }
+
+ private:
+ const int expected_error_;
+};
+
+// For ExternalApplicationRegistrarConnection::Connect() callbacks.
+void OnConnect(base::Closure quit_callback, int rv) {
+ EXPECT_EQ(net::OK, rv);
+ base::MessageLoop::current()->PostTask(FROM_HERE, quit_callback);
+}
+
+} // namespace
+
+class IncomingConnectionListenerTest : public testing::Test {
+ public:
+ IncomingConnectionListenerTest() {}
+ virtual ~IncomingConnectionListenerTest() {}
+
+ virtual void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ socket_path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("socket"));
+ }
+
+ protected:
+ base::MessageLoopForIO loop_;
+ base::RunLoop run_loop_;
+
+ base::ScopedTempDir temp_dir_;
+ base::FilePath socket_path_;
+};
+
+TEST_F(IncomingConnectionListenerTest, CleanupCheck) {
+ TestDelegate delegate;
+ {
+ IncomingConnectionListenerPosix cleanup_check(socket_path_, &delegate);
+ cleanup_check.StartListening();
+ ASSERT_TRUE(base::PathExists(socket_path_));
+ }
+ ASSERT_FALSE(base::PathExists(socket_path_));
+}
+
+TEST_F(IncomingConnectionListenerTest, ConnectSuccess) {
+ TestDelegate delegate;
+ IncomingConnectionListenerPosix listener(socket_path_, &delegate);
+
+ ASSERT_FALSE(base::PathExists(socket_path_));
+ listener.StartListening();
+ ASSERT_TRUE(base::PathExists(socket_path_));
+
+ ExternalApplicationRegistrarConnection connection(socket_path_);
+ connection.Connect(base::Bind(&OnConnect, run_loop_.QuitClosure()));
+
+ run_loop_.Run();
+}
+
+TEST_F(IncomingConnectionListenerTest, ConnectSuccess_SocketFileExists) {
+ TestDelegate delegate;
+ IncomingConnectionListenerPosix listener(socket_path_, &delegate);
+
+ ASSERT_EQ(1, base::WriteFile(socket_path_, "1", 1));
+ ASSERT_TRUE(base::PathExists(socket_path_));
+ listener.StartListening();
+
+ ExternalApplicationRegistrarConnection connection(socket_path_);
+ connection.Connect(base::Bind(&OnConnect, run_loop_.QuitClosure()));
+
+ run_loop_.Run();
+}
+
+TEST_F(IncomingConnectionListenerTest, ConnectFails_SocketFileUndeletable) {
+ ListeningFailsDelegate fail_delegate(net::ERR_FILE_EXISTS);
+ IncomingConnectionListenerPosix listener(socket_path_, &fail_delegate);
+
+ // Create the socket file.
+ ASSERT_EQ(1, base::WriteFile(socket_path_, "1", 1));
+ ASSERT_TRUE(base::PathExists(socket_path_));
+
+ // Render it undeletable, but in a way that the test harness can recover
+ // later.
+ int temp_dir_perms = 0;
+ ASSERT_TRUE(base::GetPosixFilePermissions(temp_dir_.path(), &temp_dir_perms));
+ ASSERT_TRUE(
+ base::SetPosixFilePermissions(temp_dir_.path(),
+ base::FILE_PERMISSION_READ_BY_USER |
+ base::FILE_PERMISSION_WRITE_BY_USER));
+ // The listener should fail to start up.
+ listener.StartListening();
+
+ ASSERT_TRUE(base::SetPosixFilePermissions(temp_dir_.path(), temp_dir_perms));
+}
+
+TEST_F(IncomingConnectionListenerTest, ConnectFails_SocketDirNonexistent) {
+ base::FilePath nonexistent_dir(temp_dir_.path().Append("dir").Append("file"));
+
+ ListeningFailsDelegate fail_delegate(net::ERR_FILE_NOT_FOUND);
+ IncomingConnectionListenerPosix listener(nonexistent_dir, &fail_delegate);
+
+ // The listener should fail to start up.
+ listener.StartListening();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/init.cc b/mojo/shell/init.cc
new file mode 100644
index 0000000..481e5ee
--- /dev/null
+++ b/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/mojo/shell/init.h b/mojo/shell/init.h
new file mode 100644
index 0000000..c83134a
--- /dev/null
+++ b/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/mojo/shell/mojo_url_resolver.cc b/mojo/shell/mojo_url_resolver.cc
new file mode 100644
index 0000000..c399854
--- /dev/null
+++ b/mojo/shell/mojo_url_resolver.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/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 host_name + ".so";
+#else
+ NOTREACHED() << "dynamic loading of services not supported";
+ return std::string();
+#endif
+}
+
+GURL AddTrailingSlashIfNeeded(const GURL& url) {
+ if (!url.has_path() || *url.path().rbegin() == '/')
+ return url;
+
+ std::string path(url.path() + '/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(path);
+ return url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+MojoURLResolver::MojoURLResolver() {
+ // Needed to treat first component of mojo URLs as host, not path.
+ url::AddStandardScheme("mojo");
+
+ // By default, resolve mojo URLs to files living alongside the shell.
+ base::FilePath path;
+ PathService::Get(base::DIR_MODULE, &path);
+ default_base_url_ = AddTrailingSlashIfNeeded(net::FilePathToFileURL(path));
+}
+
+MojoURLResolver::~MojoURLResolver() {
+}
+
+void MojoURLResolver::SetBaseURL(const GURL& base_url) {
+ DCHECK(base_url.is_valid());
+ // Force a trailing slash on the base_url to simplify resolving
+ // relative files and URLs below.
+ base_url_ = AddTrailingSlashIfNeeded(base_url);
+}
+
+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 (!base_url_.is_valid() ||
+ local_file_set_.find(mojo_url) != local_file_set_.end()) {
+ // Resolve to a local file URL.
+ return default_base_url_.Resolve(lib);
+ }
+
+ // Otherwise, resolve to an URL relative to base_url_.
+ return base_url_.Resolve(lib);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/mojo_url_resolver.h b/mojo/shell/mojo_url_resolver.h
new file mode 100644
index 0000000..ccd9bf4
--- /dev/null
+++ b/mojo/shell/mojo_url_resolver.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_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
+ // base URL. That is, the portion after the colon will be appeneded to
+ // |base_url| with platform-specific shared library prefix and suffix
+ // inserted.
+ void SetBaseURL(const GURL& base_url);
+
+ // 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 a base::DIR_MODULE-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_;
+ GURL default_base_url_;
+ GURL base_url_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_MOJO_URL_RESOLVER_H_
diff --git a/mojo/shell/network_application_loader.cc b/mojo/shell/network_application_loader.cc
new file mode 100644
index 0000000..dbe70b7
--- /dev/null
+++ b/mojo/shell/network_application_loader.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/shell/network_application_loader.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/network/network_service_impl.h"
+
+namespace {
+base::FilePath GetBasePath() {
+ base::FilePath path;
+ CHECK(PathService::Get(base::DIR_TEMP, &path));
+ return path.Append(FILE_PATH_LITERAL("network_service"));
+}
+}
+
+namespace mojo {
+namespace shell {
+
+NetworkApplicationLoader::NetworkApplicationLoader() {
+}
+
+NetworkApplicationLoader::~NetworkApplicationLoader() {
+}
+
+void NetworkApplicationLoader::Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+
+ uintptr_t key = reinterpret_cast<uintptr_t>(manager);
+ if (apps_.find(key) == apps_.end()) {
+ scoped_ptr<ApplicationImpl> app(
+ new ApplicationImpl(this, shell_handle.Pass()));
+ apps_.add(key, app.Pass());
+ }
+}
+
+void NetworkApplicationLoader::OnApplicationError(ApplicationManager* manager,
+ const GURL& url) {
+ apps_.erase(reinterpret_cast<uintptr_t>(manager));
+}
+
+void NetworkApplicationLoader::Initialize(ApplicationImpl* app) {
+ // The context must be created on the same thread as the network service.
+ context_.reset(new NetworkContext(GetBasePath()));
+}
+
+bool NetworkApplicationLoader::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService(this);
+ return true;
+}
+
+void NetworkApplicationLoader::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<NetworkService> request) {
+ BindToRequest(new NetworkServiceImpl(connection, context_.get()), &request);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/network_application_loader.h b/mojo/shell/network_application_loader.h
new file mode 100644
index 0000000..0f426b0
--- /dev/null
+++ b/mojo/shell/network_application_loader.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_NETWORK_APPLICATION_LOADER_H_
+#define MOJO_SHELL_NETWORK_APPLICATION_LOADER_H_
+
+#include "base/containers/scoped_ptr_hash_map.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/network/network_context.h"
+
+namespace mojo {
+
+class ApplicationImpl;
+class NetworkService;
+
+namespace shell {
+
+// ApplicationLoader responsible for creating connections to the NetworkService.
+class NetworkApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<NetworkService> {
+ public:
+ NetworkApplicationLoader();
+ virtual ~NetworkApplicationLoader();
+
+ private:
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override;
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ // ApplicationDelegate overrides.
+ virtual void Initialize(ApplicationImpl* app) override;
+ virtual bool ConfigureIncomingConnection(
+ ApplicationConnection* connection) override;
+
+ // InterfaceFactory<NetworkService> overrides.
+ virtual void Create(ApplicationConnection* connection,
+ InterfaceRequest<NetworkService> request) override;
+
+ base::ScopedPtrHashMap<uintptr_t, ApplicationImpl> apps_;
+ scoped_ptr<NetworkContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkApplicationLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_NETWORK_APPLICATION_LOADER_H_
diff --git a/mojo/shell/out_of_process_dynamic_service_runner.cc b/mojo/shell/out_of_process_dynamic_service_runner.cc
new file mode 100644
index 0000000..c1243a3
--- /dev/null
+++ b/mojo/shell/out_of_process_dynamic_service_runner.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/out_of_process_dynamic_service_runner.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/scoped_native_library.h"
+
+namespace mojo {
+namespace shell {
+
+OutOfProcessDynamicServiceRunner::OutOfProcessDynamicServiceRunner(
+ Context* context)
+ : context_(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/mojo/shell/out_of_process_dynamic_service_runner.h b/mojo/shell/out_of_process_dynamic_service_runner.h
new file mode 100644
index 0000000..71272e7
--- /dev/null
+++ b/mojo/shell/out_of_process_dynamic_service_runner.h
@@ -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.
+
+#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"
+
+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_;
+
+ 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/mojo/shell/shell_test_base.cc b/mojo/shell/shell_test_base.cc
new file mode 100644
index 0000000..296db5e
--- /dev/null
+++ b/mojo/shell/shell_test_base.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/shell_test_base.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "build/build_config.h"
+#include "net/base/filename_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+
+ShellTestBase::ShellTestBase() {
+}
+
+ShellTestBase::~ShellTestBase() {
+}
+
+void ShellTestBase::SetUp() {
+ shell_context_.Init();
+ test_server_.reset(new net::test_server::EmbeddedTestServer());
+ ASSERT_TRUE(test_server_->InitializeAndWaitUntilReady());
+ base::FilePath service_dir;
+ CHECK(PathService::Get(base::DIR_MODULE, &service_dir));
+ test_server_->ServeFilesFromDirectory(service_dir);
+}
+
+ScopedMessagePipeHandle ShellTestBase::ConnectToService(
+ const GURL& application_url,
+ const std::string& service_name) {
+ // Set the MojoURLResolver origin to be the same as the base file path for
+ // local files. This is primarily for test convenience, so that references
+ // to unknown mojo: urls that do not have specific local file or custom
+ // mappings registered on the URL resolver are treated as shared libraries.
+ base::FilePath service_dir;
+ CHECK(PathService::Get(base::DIR_MODULE, &service_dir));
+ shell_context_.mojo_url_resolver()->SetBaseURL(
+ net::FilePathToFileURL(service_dir));
+
+ return shell_context_.ConnectToServiceByName(
+ application_url, service_name).Pass();
+}
+
+ScopedMessagePipeHandle ShellTestBase::ConnectToServiceViaNetwork(
+ const GURL& application_url,
+ const std::string& service_name) {
+ shell_context_.mojo_url_resolver()->SetBaseURL(
+ test_server_->base_url());
+
+ return shell_context_.ConnectToServiceByName(
+ application_url, service_name).Pass();
+}
+
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_base.h b/mojo/shell/shell_test_base.h
new file mode 100644
index 0000000..4017315
--- /dev/null
+++ b/mojo/shell/shell_test_base.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_SHELL_SHELL_TEST_BASE_H_
+#define MOJO_SHELL_SHELL_TEST_BASE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/shell/context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class GURL;
+
+namespace net {
+namespace test_server {
+class EmbeddedTestServer;
+}
+} // namespace net
+
+namespace mojo {
+namespace shell {
+namespace test {
+
+class ShellTestBase : public testing::Test {
+ public:
+ ShellTestBase();
+ virtual ~ShellTestBase();
+
+ virtual void SetUp() override;
+
+ // |application_url| should typically be a mojo: URL (the origin will be set
+ // to an "appropriate" file: URL).
+ // TODO(tim): Should the test base be a ServiceProvider?
+ ScopedMessagePipeHandle ConnectToService(
+ const GURL& application_url,
+ const std::string& service_name);
+
+ ScopedMessagePipeHandle ConnectToServiceViaNetwork(
+ const GURL& application_url,
+ const std::string& service_name);
+
+ template <typename Interface>
+ void ConnectToService(const GURL& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ptr->Bind(ConnectToService(application_url, Interface::Name_).Pass());
+ }
+
+ template <typename Interface>
+ void ConnectToServiceViaNetwork(const GURL& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ptr->Bind(
+ ConnectToServiceViaNetwork(application_url, Interface::Name_).Pass());
+ }
+
+ base::MessageLoop* message_loop() { return &message_loop_; }
+ Context* shell_context() { return &shell_context_; }
+
+ private:
+ scoped_ptr<net::test_server::EmbeddedTestServer> test_server_;
+ Context shell_context_;
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellTestBase);
+};
+
+} // namespace test
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_SHELL_TEST_BASE_H_
diff --git a/mojo/shell/shell_test_base_unittest.cc b/mojo/shell/shell_test_base_unittest.cc
new file mode 100644
index 0000000..8a0bdd4
--- /dev/null
+++ b/mojo/shell/shell_test_base_unittest.cc
@@ -0,0 +1,311 @@
+// Copyright 2014 The Chromium 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/i18n/time_formatting.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.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_request_tracker.mojom.h"
+#include "mojo/services/test_service/test_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using mojo::test::ServiceReport;
+using mojo::test::ServiceReportPtr;
+using mojo::test::TestService;
+using mojo::test::TestTimeService;
+using mojo::test::TestServicePtr;
+using mojo::test::TestTimeServicePtr;
+using mojo::test::TestTrackedRequestService;
+using mojo::test::TestTrackedRequestServicePtr;
+
+namespace mojo {
+namespace shell {
+namespace test {
+namespace {
+
+void GetReportCallback(base::MessageLoop* loop,
+ std::vector<ServiceReport>* reports_out,
+ mojo::Array<ServiceReportPtr> report) {
+ for (size_t i = 0; i < report.size(); i++)
+ reports_out->push_back(*report[i]);
+ loop->QuitWhenIdle();
+}
+
+class ShellTestBaseTest : public ShellTestBase {
+ public:
+ // Convenience helpers for use as callbacks in tests.
+ template <typename T>
+ base::Callback<void()> SetAndQuit(T* val, T result) {
+ return base::Bind(&ShellTestBaseTest::SetAndQuitImpl<T>,
+ base::Unretained(this), val, result);
+ }
+ template <typename T>
+ base::Callback<void(T result)> SetAndQuit(T* val) {
+ return base::Bind(&ShellTestBaseTest::SetAndQuitImpl<T>,
+ base::Unretained(this), val);
+ }
+ static GURL test_app_url() {
+ return GURL("mojo:mojo_test_app");
+ }
+
+ void GetReport(std::vector<ServiceReport>* report) {
+ ConnectToService(GURL("mojo:mojo_test_request_tracker_app"),
+ &request_tracking_);
+ request_tracking_->GetReport(base::Bind(&GetReportCallback,
+ base::Unretained(message_loop()),
+ base::Unretained(report)));
+ message_loop()->Run();
+ }
+
+ private:
+ template<typename T>
+ void SetAndQuitImpl(T* val, T result) {
+ *val = result;
+ message_loop()->QuitWhenIdle();
+ }
+ TestTrackedRequestServicePtr request_tracking_;
+};
+
+class QuitMessageLoopErrorHandler : public ErrorHandler {
+ public:
+ QuitMessageLoopErrorHandler() {}
+ virtual ~QuitMessageLoopErrorHandler() {}
+
+ // |ErrorHandler| implementation:
+ virtual void OnConnectionError() override {
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler);
+};
+
+// Tests that we can connect to a single service within a single app.
+TEST_F(ShellTestBaseTest, ConnectBasic) {
+ InterfacePtr<TestService> service;
+ ConnectToService(test_app_url(), &service);
+
+ bool was_run = false;
+ service->Ping(SetAndQuit<bool>(&was_run, true));
+ message_loop()->Run();
+ EXPECT_TRUE(was_run);
+ EXPECT_FALSE(service.encountered_error());
+
+ service.reset();
+
+ // This will run until the test app has actually quit (which it will,
+ // since we killed the only connection to it).
+ message_loop()->Run();
+}
+
+// Tests that trying to connect to a service fails properly if the service
+// doesn't exist. Implicit in this test is verification that the shell
+// terminates if no services are running.
+TEST_F(ShellTestBaseTest, ConnectInvalidService) {
+ InterfacePtr<TestService> test_service;
+ ConnectToService(GURL("mojo:non_existent_service"), &test_service);
+
+ bool was_run = false;
+ test_service->Ping(SetAndQuit<bool>(&was_run, true));
+
+ // 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();
+}
+
+// Tests that we can connect to a single service within a single app using
+// a network based loader instead of local files.
+// TODO(tim): Disabled because network service leaks NSS at exit, meaning
+// subsequent tests can't init properly.
+TEST_F(ShellTestBaseTest, DISABLED_ConnectBasicNetwork) {
+ InterfacePtr<TestService> service;
+ ConnectToService(test_app_url(), &service);
+
+ bool was_run = false;
+ service->Ping(SetAndQuit<bool>(&was_run, true));
+ message_loop()->Run();
+ EXPECT_TRUE(was_run);
+ EXPECT_FALSE(service.encountered_error());
+
+ // Note that use of the network service is implicit in this test.
+ // Since TestService is not the only service in use, the shell won't auto
+ // magically exit when TestService is destroyed (unlike ConnectBasic).
+ // Tearing down the shell context will kill connections. The shell loop will
+ // exit as soon as no more apps are connected.
+ // TODO(tim): crbug.com/392685. Calling this explicitly shouldn't be
+ // necessary once the shell terminates if the primordial app exits, which
+ // we could enforce here by resetting |service|.
+ shell_context()->application_manager()->TerminateShellConnections();
+ message_loop()->Run(); // Waits for all connections to die.
+}
+
+// Tests that trying to connect to a service over network fails preoprly
+// if the service doesn't exist.
+// TODO(tim): Disabled because network service leaks NSS at exit, meaning
+// subsequent tests can't init properly.
+TEST_F(ShellTestBaseTest, DISABLED_ConnectInvalidServiceNetwork) {
+ InterfacePtr<TestService> test_service;
+ ConnectToServiceViaNetwork(GURL("mojo:non_existent_service"), &test_service);
+ QuitMessageLoopErrorHandler quitter;
+ test_service.set_error_handler(&quitter);
+ bool was_run = false;
+ test_service->Ping(SetAndQuit<bool>(&was_run, true));
+ message_loop()->Run();
+ EXPECT_TRUE(test_service.encountered_error());
+
+ // TODO(tim): crbug.com/392685. Calling this explicitly shouldn't be
+ // necessary once the shell terminates if the primordial app exits, which
+ // we could enforce here by resetting |service|.
+ shell_context()->application_manager()->TerminateShellConnections();
+ message_loop()->Run(); // Waits for all connections to die.
+}
+
+// Similar to ConnectBasic, but causes the app to instantiate multiple
+// service implementation objects and verifies the shell can reach both.
+TEST_F(ShellTestBaseTest, ConnectMultipleInstancesPerApp) {
+ {
+ TestServicePtr service1, service2;
+ ConnectToService(test_app_url(), &service1);
+ ConnectToService(test_app_url(), &service2);
+
+ bool was_run1 = false;
+ bool was_run2 = false;
+ service1->Ping(SetAndQuit<bool>(&was_run1, true));
+ message_loop()->Run();
+ service2->Ping(SetAndQuit<bool>(&was_run2, true));
+ message_loop()->Run();
+ EXPECT_TRUE(was_run1);
+ EXPECT_TRUE(was_run2);
+ EXPECT_FALSE(service1.encountered_error());
+ EXPECT_FALSE(service2.encountered_error());
+ }
+ message_loop()->Run();
+}
+
+// Tests that service A and service B, both in App 1, can talk to each other
+// and parameters are passed around properly.
+TEST_F(ShellTestBaseTest, ConnectDifferentServicesInSingleApp) {
+ // Have a TestService GetPartyTime on a TestTimeService in the same app.
+ int64 time_message;
+ TestServicePtr service;
+ ConnectToService(test_app_url(), &service);
+ service->ConnectToAppAndGetTime(test_app_url().spec(),
+ SetAndQuit<int64>(&time_message));
+ message_loop()->Run();
+
+ // Verify by hitting the TimeService directly.
+ TestTimeServicePtr time_service;
+ ConnectToService(test_app_url(), &time_service);
+ int64 party_time;
+ time_service->GetPartyTime(SetAndQuit<int64>(&party_time));
+ message_loop()->Run();
+
+ EXPECT_EQ(time_message, party_time);
+}
+
+// Tests that a service A in App 1 can talk to service B in App 2 and
+// parameters are passed around properly.
+TEST_F(ShellTestBaseTest, ConnectDifferentServicesInDifferentApps) {
+ int64 time_message;
+ TestServicePtr service;
+ ConnectToService(test_app_url(), &service);
+ service->ConnectToAppAndGetTime("mojo:mojo_test_request_tracker_app",
+ SetAndQuit<int64>(&time_message));
+ message_loop()->Run();
+
+ // Verify by hitting the TimeService in the request tracker app directly.
+ TestTimeServicePtr time_service;
+ ConnectToService(GURL("mojo:mojo_test_request_tracker_app"), &time_service);
+ int64 party_time;
+ time_service->GetPartyTime(SetAndQuit<int64>(&party_time));
+ message_loop()->Run();
+
+ EXPECT_EQ(time_message, party_time);
+}
+
+// Tests that service A in App 1 can be a client of service B in App 2.
+TEST_F(ShellTestBaseTest, ConnectServiceAsClientOfSeparateApp) {
+ TestServicePtr service;
+ ConnectToService(test_app_url(), &service);
+ service->StartTrackingRequests(message_loop()->QuitWhenIdleClosure());
+ service->Ping(mojo::Callback<void()>());
+ message_loop()->Run();
+
+ for (int i = 0; i < 8; i++)
+ service->Ping(mojo::Callback<void()>());
+ service->Ping(message_loop()->QuitWhenIdleClosure());
+ message_loop()->Run();
+
+ // If everything worked properly, the tracking service should report
+ // 10 pings to TestService.
+ std::vector<ServiceReport> reports;
+ GetReport(&reports);
+ ASSERT_EQ(1U, reports.size());
+ EXPECT_EQ(TestService::Name_, reports[0].service_name);
+ EXPECT_EQ(10U, reports[0].total_requests);
+}
+
+// Connect several services together and use the tracking service to verify
+// communication.
+TEST_F(ShellTestBaseTest, ConnectManyClientsAndServices) {
+ TestServicePtr service;
+ TestTimeServicePtr time_service;
+
+ // Make a request to the TestService and have it contact TimeService in the
+ // tracking app. Do all this with tracking enabled, meaning both services
+ // are connected as clients of the TrackedRequestService.
+ ConnectToService(test_app_url(), &service);
+ service->StartTrackingRequests(message_loop()->QuitWhenIdleClosure());
+ message_loop()->Run();
+ for (int i = 0; i < 5; i++)
+ service->Ping(mojo::Callback<void()>());
+ int64 time_result;
+ service->ConnectToAppAndGetTime("mojo:mojo_test_request_tracker_app",
+ SetAndQuit<int64>(&time_result));
+ message_loop()->Run();
+
+ // Also make a few requests to the TimeService in the test_app.
+ ConnectToService(test_app_url(), &time_service);
+ time_service->StartTrackingRequests(message_loop()->QuitWhenIdleClosure());
+ time_service->GetPartyTime(mojo::Callback<void(uint64_t)>());
+ message_loop()->Run();
+ for (int i = 0; i < 18; i++)
+ time_service->GetPartyTime(mojo::Callback<void(uint64_t)>());
+ // Flush the tasks with one more to quit.
+ int64 party_time = 0;
+ time_service->GetPartyTime(SetAndQuit<int64>(&party_time));
+ message_loop()->Run();
+
+ std::vector<ServiceReport> reports;
+ GetReport(&reports);
+ ASSERT_EQ(3U, reports.size());
+ EXPECT_EQ(TestService::Name_, reports[0].service_name);
+ EXPECT_EQ(6U, reports[0].total_requests);
+ EXPECT_EQ(TestTimeService::Name_, reports[1].service_name);
+ EXPECT_EQ(1U, reports[1].total_requests);
+ EXPECT_EQ(TestTimeService::Name_, reports[2].service_name);
+ EXPECT_EQ(20U, reports[2].total_requests);
+}
+
+} // namespace
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_helper.cc b/mojo/shell/shell_test_helper.cc
new file mode 100644
index 0000000..d0f0f7a
--- /dev/null
+++ b/mojo/shell/shell_test_helper.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/shell/shell_test_helper.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "mojo/shell/init.h"
+#include "net/base/filename_util.h"
+
+namespace mojo {
+namespace shell {
+
+ShellTestHelper::ShellTestHelper() {
+ base::CommandLine::Init(0, NULL);
+ mojo::shell::InitializeLogging();
+}
+
+ShellTestHelper::~ShellTestHelper() {
+}
+
+void ShellTestHelper::Init() {
+ context_.Init();
+ test_api_.reset(
+ new ApplicationManager::TestAPI(context_.application_manager()));
+ base::FilePath service_dir;
+ CHECK(PathService::Get(base::DIR_MODULE, &service_dir));
+ context_.mojo_url_resolver()->SetBaseURL(
+ net::FilePathToFileURL(service_dir));
+}
+
+void ShellTestHelper::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader,
+ const GURL& url) {
+ context_.application_manager()->SetLoaderForURL(loader.Pass(), url);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_helper.h b/mojo/shell/shell_test_helper.h
new file mode 100644
index 0000000..38fb008
--- /dev/null
+++ b/mojo/shell/shell_test_helper.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_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/application_manager/application_loader.h"
+#include "mojo/shell/context.h"
+
+class GURL;
+
+namespace mojo {
+
+class ApplicationLoader;
+
+namespace shell {
+
+// ShellTestHelper is useful for tests to establish a connection to the
+// ApplicationManager. Invoke Init() to establish the connection. Once done,
+// application_manager() returns the ApplicationManager.
+class ShellTestHelper {
+ public:
+ ShellTestHelper();
+ ~ShellTestHelper();
+
+ void Init();
+
+ ApplicationManager* application_manager() {
+ return context_.application_manager();
+ }
+
+ // Sets a ApplicationLoader for the specified URL. |loader| is ultimately used
+ // on
+ // the thread this class spawns.
+ void SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, const GURL& url);
+
+ private:
+ Context context_;
+ base::MessageLoop shell_loop_;
+ scoped_ptr<ApplicationManager::TestAPI> test_api_;
+ DISALLOW_COPY_AND_ASSIGN(ShellTestHelper);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_SHELL_TEST_HELPER_
diff --git a/mojo/shell/shell_test_main.cc b/mojo/shell/shell_test_main.cc
new file mode 100644
index 0000000..7f057f7
--- /dev/null
+++ b/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/mojo/shell/switches.cc b/mojo/shell/switches.cc
new file mode 100644
index 0000000..61a93e2
--- /dev/null
+++ b/mojo/shell/switches.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 "mojo/shell/switches.h"
+
+namespace switches {
+
+// Specify configuration arguments for a Mojo application URL. For example:
+// --args-for='mojo://mojo_wget http://www.google.com'
+const char kArgsFor[] = "args-for";
+
+// Used to specify the type of child process (switch values from
+// |ChildProcess::Type|).
+const char kChildProcessType[] = "child-process-type";
+
+// Comma separated list like:
+// text/html,mojo://mojo_html_viewer,application/bravo,https://abarth.com/bravo
+const char kContentHandlers[] = "content-handlers";
+
+// Force dynamically loaded apps / services to be loaded irrespective of cache
+// instructions.
+const char kDisableCache[] = "disable-cache";
+
+// Allow externally-running applications to discover, connect to, and register
+// themselves with the shell.
+// TODO(cmasone): Work in progress. Once we're sure this works, remove.
+const char kEnableExternalApplications[] = "enable-external-applications";
+
+// 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/mojo/shell/switches.h b/mojo/shell/switches.h
new file mode 100644
index 0000000..33f5946
--- /dev/null
+++ b/mojo/shell/switches.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_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 kArgsFor[];
+extern const char kChildProcessType[];
+extern const char kContentHandlers[];
+extern const char kDisableCache[];
+extern const char kEnableExternalApplications[];
+extern const char kEnableMultiprocess[];
+extern const char kOrigin[];
+extern const char kSpy[];
+} // namespace switches
+
+#endif // MOJO_SHELL_SWITCHES_H_
diff --git a/mojo/shell/task_runners.cc b/mojo/shell/task_runners.cc
new file mode 100644
index 0000000..cb157ee
--- /dev/null
+++ b/mojo/shell/task_runners.cc
@@ -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.
+
+#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(
+ const scoped_refptr<base::SingleThreadTaskRunner>& shell_runner)
+ : shell_runner_(shell_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/mojo/shell/task_runners.h b/mojo/shell/task_runners.h
new file mode 100644
index 0000000..d5a0cec
--- /dev/null
+++ b/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(
+ const scoped_refptr<base::SingleThreadTaskRunner>& shell_runner);
+ ~TaskRunners();
+
+ base::SingleThreadTaskRunner* shell_runner() const {
+ return shell_runner_.get();
+ }
+
+ base::SingleThreadTaskRunner* io_runner() const {
+ return io_thread_->message_loop_proxy().get();
+ }
+
+ base::SequencedWorkerPool* blocking_pool() const {
+ return blocking_pool_.get();
+ }
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> shell_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/mojo/shell/test_child_process.cc b/mojo/shell/test_child_process.cc
new file mode 100644
index 0000000..fe187a5
--- /dev/null
+++ b/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/mojo/shell/test_child_process.h b/mojo/shell/test_child_process.h
new file mode 100644
index 0000000..c3acb9b
--- /dev/null
+++ b/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/mojo/shell/ui_application_loader_android.cc b/mojo/shell/ui_application_loader_android.cc
new file mode 100644
index 0000000..1e4e5aa
--- /dev/null
+++ b/mojo/shell/ui_application_loader_android.cc
@@ -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.
+
+#include "mojo/shell/ui_application_loader_android.h"
+
+#include "base/bind.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/shell/context.h"
+
+namespace mojo {
+
+class UIApplicationLoader::UILoader {
+ public:
+ explicit UILoader(ApplicationLoader* loader) : loader_(loader) {}
+ ~UILoader() {}
+
+ void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle) {
+ scoped_refptr<LoadCallbacks> callbacks(
+ new ApplicationLoader::SimpleLoadCallbacks(shell_handle.Pass()));
+ loader_->Load(manager, url, callbacks);
+ }
+
+ void OnApplicationError(ApplicationManager* manager, const GURL& url) {
+ loader_->OnApplicationError(manager, url);
+ }
+
+ private:
+ ApplicationLoader* loader_; // Owned by UIApplicationLoader
+
+ DISALLOW_COPY_AND_ASSIGN(UILoader);
+};
+
+UIApplicationLoader::UIApplicationLoader(
+ scoped_ptr<ApplicationLoader> real_loader,
+ shell::Context* context)
+ : loader_(real_loader.Pass()), context_(context) {
+}
+
+UIApplicationLoader::~UIApplicationLoader() {
+ context_->ui_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&UIApplicationLoader::ShutdownOnUIThread,
+ base::Unretained(this)));
+}
+
+void UIApplicationLoader::Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+ context_->ui_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &UIApplicationLoader::LoadOnUIThread,
+ base::Unretained(this),
+ manager,
+ url,
+ base::Owned(new ScopedMessagePipeHandle(shell_handle.Pass()))));
+}
+
+void UIApplicationLoader::OnApplicationError(ApplicationManager* manager,
+ const GURL& url) {
+ context_->ui_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&UIApplicationLoader::OnApplicationErrorOnUIThread,
+ base::Unretained(this),
+ manager,
+ url));
+}
+
+void UIApplicationLoader::LoadOnUIThread(
+ ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle* shell_handle) {
+ if (!ui_loader_)
+ ui_loader_ = new UILoader(loader_.get());
+ ui_loader_->Load(manager, url, shell_handle->Pass());
+}
+
+void UIApplicationLoader::OnApplicationErrorOnUIThread(
+ ApplicationManager* manager,
+ const GURL& url) {
+ if (!ui_loader_)
+ ui_loader_ = new UILoader(loader_.get());
+ ui_loader_->OnApplicationError(manager, url);
+}
+
+void UIApplicationLoader::ShutdownOnUIThread() {
+ delete ui_loader_;
+ // Destroy |loader_| on the thread it's actually used on.
+ loader_.reset();
+}
+
+} // namespace mojo
diff --git a/mojo/shell/ui_application_loader_android.h b/mojo/shell/ui_application_loader_android.h
new file mode 100644
index 0000000..bed36ca
--- /dev/null
+++ b/mojo/shell/ui_application_loader_android.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_UI_APPLICATION_LOADER_ANDROID_H_
+#define MOJO_SHELL_UI_APPLICATION_LOADER_ANDROID_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/application_manager/application_loader.h"
+
+namespace mojo {
+
+class ApplicationManager;
+
+namespace shell {
+class Context;
+}
+
+// ApplicationLoader implementation that creates a background thread and issues
+// load
+// requests there.
+class UIApplicationLoader : public ApplicationLoader {
+ public:
+ UIApplicationLoader(scoped_ptr<ApplicationLoader> real_loader,
+ shell::Context* context);
+ virtual ~UIApplicationLoader();
+
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override;
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ private:
+ class UILoader;
+
+ // 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 ApplicationManager
+ // isn't thread safe).
+ void LoadOnUIThread(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle* shell_handle);
+ void OnApplicationErrorOnUIThread(ApplicationManager* manager,
+ const GURL& url);
+ void ShutdownOnUIThread();
+
+ scoped_ptr<ApplicationLoader> loader_;
+ shell::Context* context_;
+
+ // Lives on the UI thread. Trivial interface that calls through to |loader_|.
+ UILoader* ui_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIApplicationLoader);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SHELL_UI_APPLICATION_LOADER_ANDROID_H_
diff --git a/mojo/shell/view_manager_loader.cc b/mojo/shell/view_manager_loader.cc
new file mode 100644
index 0000000..9128084
--- /dev/null
+++ b/mojo/shell/view_manager_loader.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.
+
+#include "mojo/shell/view_manager_loader.h"
+
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/view_manager/view_manager_init_service_impl.h"
+
+namespace mojo {
+
+using service::ViewManagerInitServiceImpl;
+
+namespace shell {
+
+ViewManagerLoader::ViewManagerLoader() {
+}
+
+ViewManagerLoader::~ViewManagerLoader() {
+}
+
+void ViewManagerLoader::Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) {
+ ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
+ if (!shell_handle.is_valid())
+ return;
+
+ // TODO(sky): this needs some sort of authentication as well as making sure
+ // we only ever have one active at a time.
+ scoped_ptr<ApplicationImpl> app(
+ new ApplicationImpl(this, shell_handle.Pass()));
+ apps_.push_back(app.release());
+}
+
+void ViewManagerLoader::OnApplicationError(ApplicationManager* manager,
+ const GURL& url) {
+}
+
+bool ViewManagerLoader::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ context_.ConfigureIncomingConnection(connection);
+ connection->AddService(this);
+ return true;
+}
+
+void ViewManagerLoader::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerInitService> request) {
+ BindToRequest(new ViewManagerInitServiceImpl(connection, &context_),
+ &request);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/view_manager_loader.h b/mojo/shell/view_manager_loader.h
new file mode 100644
index 0000000..83c5f31
--- /dev/null
+++ b/mojo/shell/view_manager_loader.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_VIEW_MANAGER_LOADER_H_
+#define MOJO_SHELL_VIEW_MANAGER_LOADER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/application_manager/application_loader.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
+#include "mojo/services/view_manager/view_manager_init_service_context.h"
+
+namespace mojo {
+
+class Application;
+
+namespace shell {
+
+// ApplicationLoader responsible for creating connections to the ViewManager.
+class ViewManagerLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<ViewManagerInitService> {
+ public:
+ ViewManagerLoader();
+ virtual ~ViewManagerLoader();
+
+ private:
+ // ApplicationLoader overrides:
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ scoped_refptr<LoadCallbacks> callbacks) override;
+ virtual void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ // ApplicationDelegate overrides.
+ virtual bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ // InterfaceFactory<ViewManagerInitService> overrides.
+ virtual void Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerInitService> request) override;
+
+ ScopedVector<Application> apps_;
+ service::ViewManagerInitServiceContext context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_VIEW_MANAGER_LOADER_H_
diff --git a/mojo/spy/BUILD.gn b/mojo/spy/BUILD.gn
new file mode 100644
index 0000000..c89b5a1
--- /dev/null
+++ b/mojo/spy/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+source_set("spy") {
+ deps = [
+ ":spy_bindings",
+ "//base",
+ "//net:http_server",
+ "//url",
+ "//mojo/application_manager",
+ ]
+
+ sources = [
+ "common.h",
+ "spy.cc",
+ "spy.h",
+ "spy_server_impl.h",
+ "spy_server_impl.cc",
+ "websocket_server.cc",
+ "websocket_server.h",
+ ]
+}
+
+mojom("spy_bindings") {
+ sources = [
+ "public/spy.mojom",
+ ]
+}
diff --git a/mojo/spy/DEPS b/mojo/spy/DEPS
new file mode 100644
index 0000000..8fa9d48
--- /dev/null
+++ b/mojo/spy/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+net",
+]
diff --git a/mojo/spy/PRESUBMIT.py b/mojo/spy/PRESUBMIT.py
new file mode 100644
index 0000000..5090fb8
--- /dev/null
+++ b/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/mojo/spy/common.h b/mojo/spy/common.h
new file mode 100644
index 0000000..f39f283
--- /dev/null
+++ b/mojo/spy/common.h
@@ -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.
+
+#ifndef MOJO_SPY_COMMON_H_
+#define MOJO_SPY_COMMON_H_
+
+#include <stdint.h>
+
+namespace mojo {
+
+#pragma pack(push, 1)
+
+// Mojo message header structures. These are based off the Mojo spec.
+
+enum {
+ kMessageExpectsResponse = 1 << 0,
+ kMessageIsResponse = 1 << 1
+};
+
+struct MojoCommonHeader {
+ uint32_t num_bytes;
+ uint32_t num_fields;
+};
+
+struct MojoMessageHeader : public MojoCommonHeader {
+ uint32_t name;
+ uint32_t flags;
+};
+
+struct MojoRequestHeader : public MojoMessageHeader {
+ uint64_t request_id;
+};
+
+struct MojoMessageData {
+ MojoRequestHeader header;
+};
+
+#pragma pack(pop)
+
+} // namespace mojo
+
+#endif // MOJO_SPY_COMMON_H_
diff --git a/mojo/spy/public/spy.mojom b/mojo/spy/public/spy.mojom
new file mode 100644
index 0000000..fd5fffe
--- /dev/null
+++ b/mojo/spy/public/spy.mojom
@@ -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.
+
+module mojo.spy_api {
+
+enum Result {
+ ALL_OK,
+ INTERNAL_ERROR,
+ INVALID_ID,
+ NO_MORE_IDS,
+ INVALID_CALL,
+ INVALID_PARAMS,
+ BAD_STATE,
+ RESOURCE_LIMIT
+};
+
+struct Version {
+ uint32 v_major;
+ uint32 v_minor;
+};
+
+enum ConnectionOptions {
+ SKIP,
+ PAUSE,
+ RESUME,
+ PEEK_MESSAGES
+};
+
+struct Message {
+ uint32 id;
+ uint32 time;
+ array<uint8>? data;
+};
+
+[Client=SpyClient]
+interface SpyServer {
+ StartSession(Version? version) => (Result result, string? name);
+ StopSession() => (Result result);
+ TrackConnection(uint32 id, ConnectionOptions options) => (Result result);
+};
+
+interface SpyClient {
+ OnFatalError(Result result);
+ OnSessionEnd(Result result);
+ OnClientConnection(string? name, uint32 id, ConnectionOptions options);
+ OnMessage(Message? message);
+};
+
+}
diff --git a/mojo/spy/run_ui_dev_server b/mojo/spy/run_ui_dev_server
new file mode 100755
index 0000000..5dd294f
--- /dev/null
+++ b/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/mojo/spy/run_ui_tests b/mojo/spy/run_ui_tests
new file mode 100755
index 0000000..00f8ba1
--- /dev/null
+++ b/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/mojo/spy/spy.cc b/mojo/spy/spy.cc
new file mode 100644
index 0000000..67ae56a
--- /dev/null
+++ b/mojo/spy/spy.cc
@@ -0,0 +1,338 @@
+// Copyright 2014 The Chromium 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 <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.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 "base/time/time.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/spy/common.h"
+#include "mojo/spy/public/spy.mojom.h"
+#include "mojo/spy/spy_server_impl.h"
+#include "mojo/spy/websocket_server.h"
+#include "url/gurl.h"
+
+namespace {
+
+mojo::WebSocketServer* ws_server = NULL;
+
+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(base::MessageLoopProxy* control_loop_proxy)
+ : last_result_(MOJO_RESULT_OK),
+ bytes_transfered_(0),
+ control_loop_proxy_(control_loop_proxy) {
+ message_count_[0] = 0;
+ message_count_[1] = 0;
+ handle_count_[0] = 0;
+ handle_count_[1] = 0;
+ }
+
+ void Start(mojo::ScopedMessagePipeHandle client,
+ mojo::ScopedMessagePipeHandle interceptor,
+ const GURL& url) {
+ 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;
+
+ // Intercept message pipes which are returned via the ReadMessageRaw
+ // call
+ for (uint32_t i = 0; i < handles_read; i++) {
+ // Hack to determine if a handle is a message pipe.
+ // TODO(ananta)
+ // We should have an API which given a handle returns additional
+ // information about the handle which includes its type, etc.
+ if (MojoReadMessage(hbuf[i], NULL, NULL, NULL, NULL,
+ MOJO_READ_MESSAGE_FLAG_NONE) !=
+ MOJO_RESULT_INVALID_ARGUMENT) {
+ mojo::ScopedMessagePipeHandle message_pipe_handle;
+ message_pipe_handle.reset(mojo::MessagePipeHandle(hbuf[i]));
+
+ mojo::ScopedMessagePipeHandle faux_client;
+ mojo::ScopedMessagePipeHandle interceptor;
+ CreateMessagePipe(NULL, &faux_client, &interceptor);
+
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&MessageProcessor::Start,
+ this,
+ base::Passed(&message_pipe_handle),
+ base::Passed(&interceptor),
+ url),
+ true);
+ hbuf.get()[i] = faux_client.release().value();
+ }
+ }
+ }
+ ++message_count_[r];
+ bytes_transfered_ += bytes_read;
+
+ LogMessageInfo(mbuf.get(), url);
+
+ 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;
+ }
+
+ void LogInvalidMessage(const mojo::MojoMessageHeader& header) {
+ LOG(ERROR) << "Invalid message: Number of Fields: "
+ << header.num_fields
+ << " Number of bytes: "
+ << header.num_bytes
+ << " Flags: "
+ << header.flags;
+ }
+
+ // Validates the message as per the mojo spec.
+ bool IsValidMessage(const mojo::MojoMessageHeader& header) {
+ if (header.num_fields == 2) {
+ if (header.num_bytes != sizeof(mojo::MojoMessageHeader)) {
+ LogInvalidMessage(header);
+ return false;
+ }
+ } else if (header.num_fields == 3) {
+ if (header.num_bytes != sizeof(mojo::MojoRequestHeader)) {
+ LogInvalidMessage(header);
+ }
+ } else if (header.num_fields > 3) {
+ if (header.num_bytes < sizeof(mojo::MojoRequestHeader)) {
+ LogInvalidMessage(header);
+ return false;
+ }
+ }
+ // These flags should be specified in request or response messages.
+ if (header.num_fields < 3 &&
+ ((header.flags & mojo::kMessageExpectsResponse) ||
+ (header.flags & mojo::kMessageIsResponse))) {
+ LOG(ERROR) << "Invalid request message.";
+ LogInvalidMessage(header);
+ return false;
+ }
+ // These flags are mutually exclusive.
+ if ((header.flags & mojo::kMessageExpectsResponse) &&
+ (header.flags & mojo::kMessageIsResponse)) {
+ LOG(ERROR) << "Invalid flags combination in request message.";
+ LogInvalidMessage(header);
+ return false;
+ }
+ return true;
+ }
+
+ void LogMessageInfo(void* data, const GURL& url) {
+ mojo::MojoMessageData* message_data =
+ reinterpret_cast<mojo::MojoMessageData*>(data);
+ if (IsValidMessage(message_data->header)) {
+ control_loop_proxy_->PostTask(
+ FROM_HERE,
+ base::Bind(&mojo::WebSocketServer::LogMessageInfo,
+ base::Unretained(ws_server),
+ message_data->header, url, base::Time::Now()));
+ }
+ }
+
+ MojoResult last_result_;
+ uint32_t bytes_transfered_;
+ uint32_t message_count_[2];
+ uint32_t handle_count_[2];
+ scoped_refptr<base::MessageLoopProxy> control_loop_proxy_;
+};
+
+// In charge of intercepting access to the service manager.
+class SpyInterceptor : public mojo::ApplicationManager::Interceptor {
+ public:
+ explicit SpyInterceptor(
+ scoped_refptr<mojo::SpyServerImpl> spy_server,
+ const scoped_refptr<base::MessageLoopProxy>& control_loop_proxy)
+ : spy_server_(spy_server),
+ proxy_(base::MessageLoopProxy::current()),
+ control_loop_proxy_(control_loop_proxy) {}
+
+ private:
+ virtual mojo::ServiceProviderPtr OnConnectToClient(
+ const GURL& url, mojo::ServiceProviderPtr 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)
+ return real_client.Pass();
+
+ mojo::ScopedMessagePipeHandle faux_client;
+ mojo::ScopedMessagePipeHandle interceptor;
+ CreateMessagePipe(NULL, &faux_client, &interceptor);
+
+ scoped_refptr<MessageProcessor> processor =
+ new MessageProcessor(control_loop_proxy_.get());
+ mojo::ScopedMessagePipeHandle real_handle = real_client.PassMessagePipe();
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&MessageProcessor::Start,
+ processor,
+ base::Passed(&real_handle), base::Passed(&interceptor),
+ url),
+ true);
+
+ mojo::ServiceProviderPtr faux_provider;
+ faux_provider.Bind(faux_client.Pass());
+ return faux_provider.Pass();
+ }
+
+ bool MustIntercept(const GURL& url) {
+ // TODO(cpu): manage who and when to intercept.
+ proxy_->PostTask(
+ FROM_HERE,
+ base::Bind(&mojo::SpyServerImpl::OnIntercept, spy_server_, url));
+ return true;
+ }
+
+ scoped_refptr<mojo::SpyServerImpl> spy_server_;
+ scoped_refptr<base::MessageLoopProxy> proxy_;
+ scoped_refptr<base::MessageLoopProxy> control_loop_proxy_;
+};
+
+void StartWebServer(int port, mojo::ScopedMessagePipeHandle pipe) {
+ // TODO(cpu) figure out lifetime of the server. See Spy() dtor.
+ ws_server = new mojo::WebSocketServer(port, pipe.Pass());
+ 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::ApplicationManager* application_manager,
+ const std::string& options) {
+ SpyOptions spy_options = ProcessOptions(options);
+
+ spy_server_ = new SpyServerImpl();
+
+ // 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(&StartWebServer,
+ spy_options.websocket_port,
+ base::Passed(spy_server_->ServerPipe())));
+
+ // Start intercepting mojo services.
+ application_manager->SetInterceptor(
+ new SpyInterceptor(spy_server_, control_thread_->message_loop_proxy()));
+}
+
+Spy::~Spy() {
+ // TODO(cpu): Do not leak the interceptor. Lifetime between the
+ // application_manager and the spy is still unclear hence the leak.
+}
+
+} // namespace mojo
diff --git a/mojo/spy/spy.h b/mojo/spy/spy.h
new file mode 100644
index 0000000..f2b6be9
--- /dev/null
+++ b/mojo/spy/spy.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_SPY_H_
+#define MOJO_SPY_SPY_H_
+
+#include <string>
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class Thread;
+}
+
+namespace mojo {
+
+class ApplicationManager;
+class SpyServerImpl;
+
+// 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::ApplicationManager* application_manager,
+ const std::string& options);
+ ~Spy();
+
+ private:
+ scoped_refptr<SpyServerImpl> spy_server_;
+ // 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/mojo/spy/spy_server_impl.cc b/mojo/spy/spy_server_impl.cc
new file mode 100644
index 0000000..25e640e
--- /dev/null
+++ b/mojo/spy/spy_server_impl.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/spy/spy_server_impl.h"
+
+#include "mojo/public/cpp/system/core.h"
+
+namespace {
+
+bool NextId(uint32_t* out_id) {
+ static uint32_t id = 1;
+ if (!++id)
+ return false;
+ *out_id = id;
+ return true;
+}
+
+} // namespace
+
+namespace mojo {
+
+struct SpyServerImpl::Item {
+ enum Type {
+ kServiceIntercept,
+ kMessage
+ };
+
+ uint32_t id;
+ Type type;
+
+ Item(uint32_t id, Type type) : id(id), type(type) {}
+};
+
+SpyServerImpl::SpyServerImpl() : has_session_(false) {
+ BindToPipe(this, pipe_.handle0.Pass());
+}
+
+SpyServerImpl::~SpyServerImpl() {
+}
+
+void SpyServerImpl::StartSession(
+ spy_api::VersionPtr version,
+ const mojo::Callback<void(spy_api::Result, mojo::String)>& callback) {
+ if (has_session_) {
+ callback.Run(spy_api::RESULT_RESOURCE_LIMIT, "");
+ return;
+ }
+ callback.Run(spy_api::RESULT_ALL_OK, "session 0");
+ has_session_ = true;
+}
+
+void SpyServerImpl::StopSession(
+ const mojo::Callback<void(spy_api::Result)>& callback) {
+ if (!has_session_) {
+ callback.Run(spy_api::RESULT_INVALID_CALL);
+ return;
+ }
+ callback.Run(spy_api::RESULT_ALL_OK);
+ has_session_ = false;
+}
+
+void SpyServerImpl::TrackConnection(
+ uint32_t id,
+ spy_api::ConnectionOptions options,
+ const mojo::Callback<void(spy_api::Result)>& callback) {
+}
+
+void SpyServerImpl::OnConnectionError() {
+ // Pipe got disconnected.
+}
+
+void SpyServerImpl::OnIntercept(const GURL& url) {
+ if (!has_session_)
+ return;
+ uint32_t id;
+ if (!NextId(&id)) {
+ client()->OnFatalError(spy_api::RESULT_NO_MORE_IDS);
+ return;
+ }
+
+ items_[id] = new Item(id, Item::kServiceIntercept);
+ client()->OnClientConnection(url.possibly_invalid_spec(),
+ id,
+ spy_api::CONNECTION_OPTIONS_PEEK_MESSAGES);
+}
+
+ScopedMessagePipeHandle SpyServerImpl::ServerPipe() {
+ return ScopedMessagePipeHandle(pipe_.handle1.Pass()).Pass();
+}
+
+} // namespace mojo
diff --git a/mojo/spy/spy_server_impl.h b/mojo/spy/spy_server_impl.h
new file mode 100644
index 0000000..20d355e
--- /dev/null
+++ b/mojo/spy/spy_server_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_SPY_SPY_SERVER_IMPL_H_
+#define MOJO_SPY_SPY_SERVER_IMPL_H_
+
+#include <map>
+
+#include "base/memory/ref_counted.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/spy/public/spy.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class SpyServerImpl :
+ public base::RefCounted<SpyServerImpl>,
+ public InterfaceImpl<spy_api::SpyServer> {
+ public:
+ SpyServerImpl();
+
+ // spy_api::SpyServer implementation.
+ virtual void StartSession(
+ spy_api::VersionPtr version,
+ const mojo::Callback<void(spy_api::Result,
+ mojo::String)>& callback) override;
+
+ virtual void StopSession(
+ const mojo::Callback<void(spy_api::Result)>& callback) override;
+
+ virtual void TrackConnection(
+ uint32_t id,
+ spy_api::ConnectionOptions options,
+ const mojo::Callback<void(spy_api::Result)>& callback) override;
+
+ virtual void OnConnectionError() override;
+
+ // SpyServerImpl own methods.
+ void OnIntercept(const GURL& url);
+
+ ScopedMessagePipeHandle ServerPipe();
+
+ private:
+ friend class base::RefCounted<SpyServerImpl>;
+ virtual ~SpyServerImpl();
+
+ // Item models the entities that we track by IDs.
+ struct Item;
+
+ MessagePipe pipe_;
+ bool has_session_;
+ std::map<uint32_t, Item*> items_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_SPY_SPY_SERVER_IMPL_H_
diff --git a/mojo/spy/test/spy_repl_test.html b/mojo/spy/test/spy_repl_test.html
new file mode 100644
index 0000000..a709a54
--- /dev/null
+++ b/mojo/spy/test/spy_repl_test.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+
+<html lang="en">
+<head>
+ <meta charset="utf-8" />
+ <title>Spy WS test</title>
+ <style>
+ hr {color:sienna;}
+ body {
+ background-color:#b0c4de;
+ font:normal 16px/20px "Helvetica Neue", Helvetica, sans-serif;
+ }
+ #command {
+ width:70%;
+ }
+ #status {
+ background-color:#0094ff;
+ width:50%;
+ padding:4px;
+ }
+ </style>
+</head>
+<body>
+<header><h1>mojo spy</h1></header>
+ <form>
+ <input type="text" id="command" placeholder="enter spy command + enter" />
+ </form>
+ <p id="status">status: no connection</p>
+ <p id="log">...</p>
+ <script>
+ function openConnection() {
+ if (conn.readyState === undefined || conn.readyState > 1) {
+ conn = new WebSocket('ws://127.0.0.1:42424');
+ conn.onopen = function () {
+ state.innerHTML = 'connected @port 42424';
+ };
+ conn.onmessage = function (event) {
+ var message = event.data;
+ log.innerHTML += "<br/>" + message;
+ };
+ conn.onclose = function (event) {
+ state.innerHTML = 'connection closed';
+ };
+ conn.onerror = function (event) {
+ state.innerHTML = 'got error';
+ };
+ }
+ }
+
+ var addEvent = (function () {
+ if (document.addEventListener) {
+ return function (el, type, fn) {
+ if (el && el.nodeName || el === window) {
+ el.addEventListener(type, fn, false);
+ } else if (el && el.length) {
+ for (var i = 0; i < el.length; i++) {
+ addEvent(el[i], type, fn);
+ }
+ }
+ };
+ } else {
+ return function (el, type, fn) {
+ if (el && el.nodeName || el === window) {
+ el.attachEvent('on' + type, function () { return fn.call(el, window.event); });
+ } else if (el && el.length) {
+ for (var i = 0; i < el.length; i++) {
+ addEvent(el[i], type, fn);
+ }
+ }
+ };
+ }
+ })();
+
+ var log = document.getElementById('log');
+ var cmd = document.getElementById('command');
+ var form = cmd.form;
+ var conn = {};
+ var state = document.getElementById('status');
+
+ if (window.WebSocket === undefined) {
+ state.innerHTML = 'websockets not supported';
+ } else {
+ addEvent(form, 'submit', function (event) {
+ event.preventDefault();
+ if (conn.readyState === 1) {
+ conn.send(JSON.stringify(cmd.value));
+ log.innerHTML = 'data sent';
+ cmd.value = '';
+ }
+ });
+
+ openConnection();
+ }
+ </script>
+</body>
+</html>
diff --git a/mojo/spy/ui/__init__.py b/mojo/spy/ui/__init__.py
new file mode 100644
index 0000000..bfecdb2
--- /dev/null
+++ b/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/mojo/spy/ui/dev_server.py b/mojo/spy/ui/dev_server.py
new file mode 100644
index 0000000..2a83032
--- /dev/null
+++ b/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/mojo/spy/ui/spy.html b/mojo/spy/ui/spy.html
new file mode 100644
index 0000000..79aeb7f
--- /dev/null
+++ b/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/mojo/spy/ui/spy.js b/mojo/spy/ui/spy.js
new file mode 100644
index 0000000..e3f47e1
--- /dev/null
+++ b/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/mojo/spy/ui/spy_project.py b/mojo/spy/ui/spy_project.py
new file mode 100644
index 0000000..409ecdf
--- /dev/null
+++ b/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/mojo/spy/ui/spy_shell.html b/mojo/spy/ui/spy_shell.html
new file mode 100644
index 0000000..c46a24f
--- /dev/null
+++ b/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/mojo/spy/ui/spy_shell.js b/mojo/spy/ui/spy_shell.js
new file mode 100644
index 0000000..bcf0430
--- /dev/null
+++ b/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/mojo/spy/ui/spy_shell_to_html b/mojo/spy/ui/spy_shell_to_html
new file mode 100755
index 0000000..68ba089
--- /dev/null
+++ b/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/mojo/spy/ui/spy_shell_to_html.py b/mojo/spy/ui/spy_shell_to_html.py
new file mode 100644
index 0000000..293b9fb
--- /dev/null
+++ b/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/mojo/spy/ui/spy_test.js b/mojo/spy/ui/spy_test.js
new file mode 100644
index 0000000..b70ecd9
--- /dev/null
+++ b/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/mojo/spy/ui/tvcm_stub.py b/mojo/spy/ui/tvcm_stub.py
new file mode 100644
index 0000000..8c7c084
--- /dev/null
+++ b/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/mojo/spy/ui/ui_unittest.py b/mojo/spy/ui/ui_unittest.py
new file mode 100644
index 0000000..ffaffad
--- /dev/null
+++ b/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/mojo/spy/websocket_server.cc b/mojo/spy/websocket_server.cc
new file mode 100644
index 0000000..20e2da6
--- /dev/null
+++ b/mojo/spy/websocket_server.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/spy/websocket_server.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/public/cpp/bindings/message.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_server_socket.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+const int kNotConnected = -1;
+
+#define MOJO_DEBUGGER_MESSAGE_FORMAT "\"url: %s\n," \
+ "time: %02d:%02d:%02d\n,"\
+ "bytes: %u\n,"\
+ "fields: %u\n,"\
+ "name: %u\n,"\
+ "requestId: %llu\n,"\
+ "type: %s\n,"\
+ "end:\n\""
+
+WebSocketServer::WebSocketServer(int port,
+ mojo::ScopedMessagePipeHandle server_pipe)
+ : port_(port),
+ connection_id_(kNotConnected),
+ spy_server_(MakeProxy<spy_api::SpyServer>(server_pipe.Pass())) {
+ spy_server_.set_client(this);
+}
+
+WebSocketServer::~WebSocketServer() {
+}
+
+bool WebSocketServer::Start() {
+ scoped_ptr<net::ServerSocket> server_socket(
+ new net::TCPServerSocket(NULL, net::NetLog::Source()));
+ server_socket->ListenWithAddressAndPort("0.0.0.0", port_, 1);
+ web_server_.reset(new net::HttpServer(server_socket.Pass(), this));
+ net::IPEndPoint address;
+ int error = web_server_->GetLocalAddress(&address);
+ port_ = address.port();
+ return (error == net::OK);
+}
+
+void WebSocketServer::LogMessageInfo(
+ const mojo::MojoRequestHeader& message_header,
+ const GURL& url,
+ const base::Time& message_time) {
+ base::Time::Exploded exploded;
+ message_time.LocalExplode(&exploded);
+
+ std::string output_message = base::StringPrintf(
+ MOJO_DEBUGGER_MESSAGE_FORMAT,
+ url.spec().c_str(),
+ exploded.hour,
+ exploded.minute,
+ exploded.second,
+ static_cast<unsigned>(message_header.num_bytes),
+ static_cast<unsigned>(message_header.num_fields),
+ static_cast<unsigned>(message_header.name),
+ static_cast<unsigned long long>(
+ message_header.num_fields == 3 ? message_header.request_id
+ : 0),
+ message_header.flags != 0 ?
+ (message_header.flags & mojo::kMessageExpectsResponse ?
+ "Expects response" : "Response message")
+ : "Not a request or response message");
+ if (Connected()) {
+ web_server_->SendOverWebSocket(connection_id_, output_message.c_str());
+ } else {
+ DVLOG(1) << output_message;
+ }
+}
+
+void WebSocketServer::OnHttpRequest(
+ int connection_id,
+ const net::HttpServerRequestInfo& info) {
+ web_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.
+ web_server_->Close(connection_id);
+ return;
+ }
+ // Accept the connection.
+ web_server_->AcceptWebSocket(connection_id, info);
+ connection_id_ = connection_id;
+}
+
+void WebSocketServer::OnWebSocketMessage(
+ int connection_id,
+ const std::string& data) {
+
+ if (data == "\"start\"") {
+ spy_api::VersionPtr ver = spy_api::Version::New();
+ ver->v_major = 0;
+ ver->v_minor = 1;
+ spy_server_->StartSession(
+ ver.Pass(),
+ base::Bind(&WebSocketServer::OnStartSession, base::Unretained(this)));
+ } else if (data == "\"stop\"") {
+ spy_server_->StopSession(
+ base::Bind(&WebSocketServer::OnSessionEnd, base::Unretained(this)));
+ }
+}
+
+void WebSocketServer::OnFatalError(spy_api::Result result) {
+ web_server_->SendOverWebSocket(connection_id_, "\"fatal error\"");
+}
+
+void WebSocketServer::OnClose(
+ int connection_id) {
+ if (connection_id != connection_id_)
+ return;
+ connection_id_ = kNotConnected;
+
+ spy_server_->StopSession(
+ base::Bind(&WebSocketServer::OnSessionEnd, base::Unretained(this)));
+}
+
+void WebSocketServer::OnSessionEnd(spy_api::Result result) {
+ // Called when the spy session (not the websocket) ends.
+}
+
+void WebSocketServer::OnClientConnection(
+ const mojo::String& name,
+ uint32_t id,
+ spy_api::ConnectionOptions options) {
+ std::string cc("\"");
+ cc += name.To<std::string>() + "\"";
+ web_server_->SendOverWebSocket(connection_id_, cc);
+}
+
+void WebSocketServer::OnMessage(spy_api::MessagePtr message) {
+}
+
+void WebSocketServer::OnStartSession(spy_api::Result, mojo::String) {
+ web_server_->SendOverWebSocket(connection_id_, "\"ok start\"");
+}
+
+bool WebSocketServer::Connected() const {
+ return connection_id_ != kNotConnected;
+}
+
+} // namespace mojo
diff --git a/mojo/spy/websocket_server.h b/mojo/spy/websocket_server.h
new file mode 100644
index 0000000..c6dc076
--- /dev/null
+++ b/mojo/spy/websocket_server.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_SPY_WEBSOCKET_SERVER_H_
+#define MOJO_SPY_WEBSOCKET_SERVER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "mojo/spy/common.h"
+#include "mojo/spy/public/spy.mojom.h"
+#include "net/server/http_server.h"
+
+namespace base {
+class Time;
+};
+
+class GURL;
+
+namespace mojo {
+
+class WebSocketServer : public net::HttpServer::Delegate,
+ public spy_api::SpyClient {
+ public:
+ // Pass 0 in |port| to listen in one available port.
+ explicit WebSocketServer(int port, ScopedMessagePipeHandle server_pipe);
+ 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_; }
+
+ // Maintains a log of the message passed in.
+ void LogMessageInfo(
+ const mojo::MojoRequestHeader& message_header,
+ const GURL& url,
+ const base::Time& message_time);
+
+ protected:
+ // Overridden from net::HttpServer::Delegate.
+ virtual void OnConnect(int connection_id) override {}
+ 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;
+
+ // Overriden form spy_api::SpyClient.
+ virtual void OnFatalError(spy_api::Result result) override;
+ virtual void OnSessionEnd(spy_api::Result result) override;
+ virtual void OnClientConnection(
+ const mojo::String& name,
+ uint32_t id,
+ spy_api::ConnectionOptions options) override;
+ virtual void OnMessage(spy_api::MessagePtr message) override;
+
+ // Callbacks from calling spy_api::SpyServer.
+ void OnStartSession(spy_api::Result, mojo::String);
+
+ bool Connected() const;
+
+ private:
+ int port_;
+ int connection_id_;
+ scoped_ptr<net::HttpServer> web_server_;
+ spy_api::SpyServerPtr spy_server_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketServer);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SPY_WEBSOCKET_SERVER_H_
diff --git a/mojo/tools/BUILD.gn b/mojo/tools/BUILD.gn
new file mode 100644
index 0000000..de38e6c
--- /dev/null
+++ b/mojo/tools/BUILD.gn
@@ -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.
+
+# GYP version: mojo/mojo_base.gyp:mojo_message_generator
+executable("message_generator") {
+ testonly = true
+ output_name = "mojo_message_generator"
+
+ sources = [
+ "message_generator.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//mojo/public/cpp/bindings",
+ "//testing/gtest",
+ ]
+}
+
diff --git a/mojo/tools/check_mojom_golden_files.py b/mojo/tools/check_mojom_golden_files.py
new file mode 100755
index 0000000..9af7a86
--- /dev/null
+++ b/mojo/tools/check_mojom_golden_files.py
@@ -0,0 +1,103 @@
+#!/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)
+_chromium_src_dir = os.path.join(_mojo_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)
+ # TODO(vtl): This may wrong, since the path can be overridden in the .gyp
+ # file.
+ RunBindingsGenerator(out_dir, _mojo_dir, mojom_file,
+ ["-I", os.path.abspath(_chromium_src_dir)])
+
+ 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/mojo/tools/data/unittests b/mojo/tools/data/unittests
new file mode 100644
index 0000000..0bafba4
--- /dev/null
+++ b/mojo/tools/data/unittests
@@ -0,0 +1,27 @@
+# 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_application_manager_unittests
+mojo_common_unittests
+mojo_view_manager_lib_unittests
+mojo_view_manager_unittests
+mojo_surfaces_lib_unittests
+
+# JavaScript tests:
+*mojo_apps_js_unittests
+*mojo_js_unittests
+
+# Shell integration tests:
+*mojo_shell_tests
diff --git a/mojo/tools/generate_java_callback_interfaces.py b/mojo/tools/generate_java_callback_interfaces.py
new file mode 100644
index 0000000..257a540
--- /dev/null
+++ b/mojo/tools/generate_java_callback_interfaces.py
@@ -0,0 +1,69 @@
+"""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 {
+
+ /**
+ * A generic callback.
+ */
+ interface Callback0 {
+ /**
+ * Call the callback.
+ */
+ public void call();
+ }
+%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/mojo/tools/message_generator.cc b/mojo/tools/message_generator.cc
new file mode 100644
index 0000000..08b7dcc
--- /dev/null
+++ b/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/files/file_path.h"
+#include "base/files/file_util.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/mojo/tools/mojob.sh b/mojo/tools/mojob.sh
new file mode 100755
index 0000000..e0804d1
--- /dev/null
+++ b/mojo/tools/mojob.sh
@@ -0,0 +1,238 @@
+#!/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).
+ gypall - Run gyp for all of chromium (does not sync).
+ 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.
+ Use goma:
+ --use-goma - Use goma if \$GOMA_DIR is set or \$HOME/goma exists (default).
+ --no-use-goma - Do not use goma.
+
+Note: It will abort on the first failure (if any).
+EOF
+}
+
+do_build() {
+ echo "Building in out/$1 ..."
+ if [ "$GOMA" = "auto" -a -v GOMA_DIR ]; then
+ ninja -j 1000 -l 100 -C "out/$1" mojo || exit 1
+ else
+ ninja -C "out/$1" mojo || exit 1
+ fi
+}
+
+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 for mojo with GYP_DEFINES=$gyp_defines ..."
+ GYP_DEFINES="$gyp_defines" build/gyp_chromium mojo/mojo.gyp || exit 1
+}
+
+do_gypall() {
+ local gyp_defines="$(make_gyp_defines)"
+ echo "Running gyp for everything with GYP_DEFINES=$gyp_defines ..."
+ GYP_DEFINES="$gyp_defines" build/gyp_chromium || 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
+# Valid values: auto or disabled.
+GOMA=auto
+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
+ case "$GOMA" in
+ auto)
+ if [ -v GOMA_DIR ]; then
+ options+=("use_goma=1" "gomadir=\"${GOMA_DIR}\"")
+ else
+ options+=("use_goma=0")
+ fi
+ ;;
+ disabled)
+ options+=("use_goma=0")
+ ;;
+ esac
+ echo "${options[*]}"
+}
+
+set_goma_dir_if_necessary() {
+ if [ "$GOMA" = "auto" -a ! -v GOMA_DIR ]; then
+ if [ -d "${HOME}/goma" ]; then
+ GOMA_DIR="${HOME}/goma"
+ fi
+ fi
+}
+
+start_goma_if_necessary() {
+ if [ "$GOMA" = "auto" -a -v GOMA_DIR ]; then
+ "${GOMA_DIR}/goma_ctl.py" ensure_start
+ fi
+}
+
+# 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)
+ set_goma_dir_if_necessary
+ start_goma_if_necessary
+ 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)
+ set_goma_dir_if_necessary
+ do_gyp
+ ;;
+ gypall)
+ set_goma_dir_if_necessary
+ do_gypall
+ ;;
+ 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
+ ;;
+ --use-goma)
+ GOMA=auto
+ ;;
+ --no-use-goma)
+ GOMA=disabled
+ ;;
+ *)
+ echo "Unknown command \"${arg}\". Try \"$(basename "$0") help\"."
+ exit 1
+ ;;
+ esac
+done
diff --git a/mojo/tools/mojosh.sh b/mojo/tools/mojosh.sh
new file mode 100755
index 0000000..bb8a2cd
--- /dev/null
+++ b/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/mojo/tools/package_manager/BUILD.gn b/mojo/tools/package_manager/BUILD.gn
new file mode 100644
index 0000000..76b5771
--- /dev/null
+++ b/mojo/tools/package_manager/BUILD.gn
@@ -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.
+
+shared_library("package_manager") {
+ output_name = "mojo_package_manager"
+
+ sources = [
+ "manifest.cc",
+ "manifest.h",
+ "package_fetcher.cc",
+ "package_fetcher.h",
+ "package_manager.cc",
+ "package_manager_application.cc",
+ "package_manager_application.h",
+ "unpacker.cc",
+ "unpacker.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/public/c/system:for_shared_library",
+ "//mojo/public/cpp/utility",
+ "//mojo/services/public/interfaces/network",
+ "//third_party/zlib:zip",
+ "//url",
+ ]
+}
diff --git a/mojo/tools/package_manager/DEPS b/mojo/tools/package_manager/DEPS
new file mode 100644
index 0000000..784b7fb
--- /dev/null
+++ b/mojo/tools/package_manager/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ '+third_party/zlib',
+]
diff --git a/mojo/tools/package_manager/manifest.cc b/mojo/tools/package_manager/manifest.cc
new file mode 100644
index 0000000..5a0e346
--- /dev/null
+++ b/mojo/tools/package_manager/manifest.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/tools/package_manager/manifest.h"
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+Manifest::Manifest() {
+}
+
+Manifest::~Manifest() {
+}
+
+bool Manifest::Parse(const std::string& str, std::string* err_msg) {
+ int err_code = base::JSONReader::JSON_NO_ERROR;
+ scoped_ptr<base::Value> root(base::JSONReader::ReadAndReturnError(
+ str,
+ base::JSON_ALLOW_TRAILING_COMMAS,
+ &err_code, err_msg));
+ if (err_code != base::JSONReader::JSON_NO_ERROR)
+ return false;
+
+ const base::DictionaryValue* root_dict;
+ if (!root->GetAsDictionary(&root_dict)) {
+ *err_msg = "Manifest is not a dictionary.";
+ return false;
+ }
+
+ if (!PopulateDeps(root_dict, err_msg))
+ return false;
+
+ return true;
+}
+
+bool Manifest::ParseFromFile(const base::FilePath& file_name,
+ std::string* err_msg) {
+ std::string data;
+ if (!base::ReadFileToString(file_name, &data)) {
+ *err_msg = "Couldn't read manifest file " + file_name.AsUTF8Unsafe();
+ return false;
+ }
+ return Parse(data, err_msg);
+}
+
+bool Manifest::PopulateDeps(const base::DictionaryValue* root,
+ std::string* err_msg) {
+ const base::Value* deps_value;
+ if (!root->Get("deps", &deps_value))
+ return true; // No deps, that's OK.
+
+ const base::ListValue* deps;
+ if (!deps_value->GetAsList(&deps)) {
+ *err_msg = "Deps is not a list. Should be \"deps\": [ \"...\", \"...\" ]";
+ return false;
+ }
+
+ deps_.reserve(deps->GetSize());
+ for (size_t i = 0; i < deps->GetSize(); i++) {
+ std::string cur_dep;
+ if (!deps->GetString(i, &cur_dep)) {
+ *err_msg = "Dependency list item wasn't a string.";
+ return false;
+ }
+
+ GURL cur_url(cur_dep);
+ if (!cur_url.is_valid()) {
+ *err_msg = "Dependency entry isn't a valid URL: " + cur_dep;
+ return false;
+ }
+
+ deps_.push_back(cur_url);
+ }
+
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/tools/package_manager/manifest.h b/mojo/tools/package_manager/manifest.h
new file mode 100644
index 0000000..f87040d
--- /dev/null
+++ b/mojo/tools/package_manager/manifest.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_TOOLS_PACKAGE_MANAGER_MANIFEST_H_
+#define MOJO_TOOLS_PACKAGE_MANAGER_MANIFEST_H_
+
+#include <string>
+#include <vector>
+
+#include "mojo/public/cpp/system/macros.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+}
+
+namespace mojo {
+
+class Manifest {
+ public:
+ Manifest();
+ ~Manifest();
+
+ // Parses the manifest from raw data. Returns true on success. On failure,
+ // populates the "err_msg" string with an error.
+ bool Parse(const std::string& str, std::string* err_msg);
+
+ // Like Parse but reads the data from a file.
+ bool ParseFromFile(const base::FilePath& file_name, std::string* err_msg);
+
+ const std::vector<GURL>& deps() const { return deps_; }
+
+ private:
+ bool PopulateDeps(const base::DictionaryValue* root, std::string* err_msg);
+
+ std::vector<GURL> deps_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Manifest);
+};
+
+} // namespace mojo
+
+#endif // MOJO_TOOLS_PACKAGE_MANAGER_MANIFEST_H_
diff --git a/mojo/tools/package_manager/package_fetcher.cc b/mojo/tools/package_manager/package_fetcher.cc
new file mode 100644
index 0000000..b5a47e5
--- /dev/null
+++ b/mojo/tools/package_manager/package_fetcher.cc
@@ -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.
+
+#include "mojo/tools/package_manager/package_fetcher.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "mojo/services/public/interfaces/network/url_loader.mojom.h"
+
+namespace mojo {
+
+PackageFetcher::PackageFetcher(NetworkService* network_service,
+ PackageFetcherDelegate* delegate,
+ const GURL& url)
+ : delegate_(delegate),
+ url_(url) {
+ network_service->CreateURLLoader(Get(&loader_));
+
+ URLRequestPtr request(URLRequest::New());
+ request->url = url.spec();
+ request->auto_follow_redirects = true;
+
+ loader_->Start(request.Pass(),
+ base::Bind(&PackageFetcher::OnReceivedResponse,
+ base::Unretained(this)));
+}
+
+PackageFetcher::~PackageFetcher() {
+}
+
+void PackageFetcher::OnReceivedResponse(URLResponsePtr response) {
+ if (response->error) {
+ printf("Got error %d (%s) for %s\n",
+ response->error->code,
+ response->error->description.get().c_str(),
+ url_.spec().c_str());
+ delegate_->FetchFailed(url_);
+ return;
+ }
+
+ if (!base::CreateTemporaryFile(&output_file_name_)) {
+ delegate_->FetchFailed(url_);
+ return;
+ }
+ output_file_.Initialize(output_file_name_,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ if (!output_file_.IsValid()) {
+ base::DeleteFile(output_file_name_, false);
+ delegate_->FetchFailed(url_);
+ // Danger: may be deleted now.
+ return;
+ }
+
+ body_ = response->body.Pass();
+ ReadData(MOJO_RESULT_OK);
+ // Danger: may be deleted now.
+}
+
+void PackageFetcher::ReadData(MojoResult) {
+ char buf[4096];
+ uint32_t num_bytes = sizeof(buf);
+ MojoResult result = ReadDataRaw(body_.get(), buf, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ WaitToReadMore();
+ } else if (result == MOJO_RESULT_OK) {
+ if (output_file_.WriteAtCurrentPos(buf, num_bytes) !=
+ static_cast<int>(num_bytes)) {
+ // Clean up the output file.
+ output_file_.Close();
+ base::DeleteFile(output_file_name_, false);
+
+ delegate_->FetchFailed(url_);
+ // Danger: may be deleted now.
+ return;
+ }
+ WaitToReadMore();
+ } else if (result == MOJO_RESULT_FAILED_PRECONDITION) {
+ // Done.
+ output_file_.Close();
+ delegate_->FetchSucceeded(url_, output_file_name_);
+ // Danger: may be deleted now.
+ }
+}
+
+void PackageFetcher::WaitToReadMore() {
+ handle_watcher_.Start(
+ body_.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ base::Bind(&PackageFetcher::ReadData, base::Unretained(this)));
+}
+
+} // namespace mojo
diff --git a/mojo/tools/package_manager/package_fetcher.h b/mojo/tools/package_manager/package_fetcher.h
new file mode 100644
index 0000000..3cbd7a7
--- /dev/null
+++ b/mojo/tools/package_manager/package_fetcher.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_TOOLS_PACKAGE_MANAGER_FETCHER_H_
+#define MOJO_TOOLS_PACKAGE_MANAGER_FETCHER_H_
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "mojo/common/handle_watcher.h"
+#include "mojo/public/cpp/system/macros.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 base {
+class FilePath;
+} // namespace base
+
+namespace mojo {
+
+class PackageFetcherDelegate;
+
+class PackageFetcher {
+ public:
+ PackageFetcher(NetworkService* network_service,
+ PackageFetcherDelegate* delegate,
+ const GURL& url);
+ virtual ~PackageFetcher();
+
+ private:
+ void OnReceivedResponse(URLResponsePtr response);
+
+ void ReadData(MojoResult);
+ void WaitToReadMore();
+
+ PackageFetcherDelegate* delegate_;
+
+ // URL of the package.
+ GURL url_;
+
+ URLLoaderPtr loader_;
+
+ // Valid once file has started streaming.
+ ScopedDataPipeConsumerHandle body_;
+ common::HandleWatcher handle_watcher_;
+
+ base::FilePath output_file_name_;
+ base::File output_file_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PackageFetcher);
+};
+
+class PackageFetcherDelegate {
+ public:
+ virtual void FetchSucceeded(const GURL& url, const base::FilePath& name) = 0;
+
+ virtual void FetchFailed(const GURL& url) = 0;
+};
+
+} // namespace mojo
+
+#endif // MOJO_TOOLS_PACKAGE_MANAGER_FETCHER_H_
diff --git a/mojo/tools/package_manager/package_manager.cc b/mojo/tools/package_manager/package_manager.cc
new file mode 100644
index 0000000..b091f6d
--- /dev/null
+++ b/mojo/tools/package_manager/package_manager.cc
@@ -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.
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/tools/package_manager/package_manager_application.h"
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new mojo::PackageManagerApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/tools/package_manager/package_manager_application.cc b/mojo/tools/package_manager/package_manager_application.cc
new file mode 100644
index 0000000..ae4d4a4
--- /dev/null
+++ b/mojo/tools/package_manager/package_manager_application.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium 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/tools/package_manager/package_manager_application.h"
+
+#include "base/files/file_util.h"
+#include "mojo/tools/package_manager/manifest.h"
+#include "mojo/tools/package_manager/unpacker.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "mojo/public/cpp/application/application_impl.h"
+
+namespace mojo {
+
+namespace {
+
+const base::FilePath::CharType kManifestFileName[] =
+ FILE_PATH_LITERAL("manifest.json");
+
+} // namespace
+
+PackageManagerApplication::PendingLoad::PendingLoad() {
+}
+
+PackageManagerApplication::PendingLoad::~PendingLoad() {
+}
+
+PackageManagerApplication::PackageManagerApplication() {
+}
+
+PackageManagerApplication::~PackageManagerApplication() {
+ STLDeleteContainerPairSecondPointers(pending_.begin(), pending_.end());
+}
+
+void PackageManagerApplication::Initialize(ApplicationImpl* app) {
+ app->ConnectToService("mojo:mojo_network_service", &network_service_);
+
+ printf("Enter URL> ");
+ char buf[1024];
+ if (scanf("%1023s", buf) != 1) {
+ printf("No input, exiting\n");
+ base::MessageLoop::current()->Quit();
+ return;
+ }
+ GURL url(buf);
+ if (!url.is_valid()) {
+ printf("Invalid URL\n");
+ base::MessageLoop::current()->Quit();
+ return;
+ }
+
+ StartLoad(url);
+}
+
+void PackageManagerApplication::StartLoad(const GURL& url) {
+ if (completed_.find(url) != completed_.end() ||
+ pending_.find(url) != pending_.end())
+ return; // Already loaded or are loading this one.
+
+ PendingLoad* load = new PendingLoad;
+ load->fetcher.reset(new mojo::PackageFetcher(
+ network_service_.get(), this, url));
+ pending_[url] = load;
+}
+
+void PackageManagerApplication::LoadDeps(const Manifest& manifest) {
+ for (size_t i = 0; i < manifest.deps().size(); i++)
+ StartLoad(manifest.deps()[i]);
+}
+
+void PackageManagerApplication::PendingLoadComplete(const GURL& url) {
+ pending_.erase(pending_.find(url));
+ completed_.insert(url);
+ if (pending_.empty())
+ base::MessageLoop::current()->Quit();
+}
+
+void PackageManagerApplication::FetchSucceeded(
+ const GURL& url,
+ const base::FilePath& name) {
+ Unpacker unpacker;
+ if (!unpacker.Unpack(name)) {
+ base::DeleteFile(name, false);
+ printf("Failed to unpack\n");
+ PendingLoadComplete(url);
+ return;
+ }
+ // The downloaded .zip file is no longer necessary.
+ base::DeleteFile(name, false);
+
+ // Load the manifest.
+ base::FilePath manifest_path = unpacker.dir().Append(kManifestFileName);
+ Manifest manifest;
+ std::string err_msg;
+ if (!manifest.ParseFromFile(manifest_path, &err_msg)) {
+ printf("%s\n", err_msg.c_str());
+ PendingLoadComplete(url);
+ return;
+ }
+
+ // Enqueue loads for any deps.
+ LoadDeps(manifest);
+
+ printf("Loaded %s\n", url.spec().c_str());
+ PendingLoadComplete(url);
+}
+
+void PackageManagerApplication::FetchFailed(const GURL& url) {
+ PendingLoadComplete(url);
+}
+
+} // namespace mojo
diff --git a/mojo/tools/package_manager/package_manager_application.h b/mojo/tools/package_manager/package_manager_application.h
new file mode 100644
index 0000000..87d4fcd
--- /dev/null
+++ b/mojo/tools/package_manager/package_manager_application.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_PACKAGE_MANAGER_PACKAGE_MANAGER_APPLICATION_H_
+#define MOJO_PACKAGE_MANAGER_PACKAGE_MANAGER_APPLICATION_H_
+
+#include <map>
+#include <set>
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/public/interfaces/network/network_service.mojom.h"
+#include "mojo/tools/package_manager/package_fetcher.h"
+
+namespace mojo {
+
+class Manifest;
+
+class PackageManagerApplication
+ : public ApplicationDelegate,
+ public PackageFetcherDelegate {
+ public:
+ PackageManagerApplication();
+ virtual ~PackageManagerApplication();
+
+ private:
+ struct PendingLoad {
+ PendingLoad();
+ ~PendingLoad();
+
+ scoped_ptr<PackageFetcher> fetcher;
+ };
+ typedef std::map<GURL, PendingLoad*> PendingLoadMap;
+
+ void StartLoad(const GURL& url);
+
+ void LoadDeps(const Manifest& manifest);
+
+ // Deletes the pending load entry for the given URL and possibly exits the
+ // message loop if all loads are done.
+ void PendingLoadComplete(const GURL& url);
+
+ // ApplicationDelegate implementation.
+ virtual void Initialize(ApplicationImpl* app) override;
+
+ // PackageFetcher.
+ virtual void FetchSucceeded(const GURL& url,
+ const base::FilePath& name) override;
+ virtual void FetchFailed(const GURL& url) override;
+
+ mojo::NetworkServicePtr network_service_;
+
+ PendingLoadMap pending_; // Owning pointers.
+ std::set<GURL> completed_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PackageManagerApplication);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PACKAGE_MANAGER_PACKAGE_MANAGER_APPLICATION_H
diff --git a/mojo/tools/package_manager/unpacker.cc b/mojo/tools/package_manager/unpacker.cc
new file mode 100644
index 0000000..8ed34d5
--- /dev/null
+++ b/mojo/tools/package_manager/unpacker.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/tools/package_manager/unpacker.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "third_party/zlib/google/zip.h"
+
+namespace mojo {
+
+Unpacker::Unpacker() {
+}
+
+Unpacker::~Unpacker() {
+ if (!dir_.empty())
+ DeleteFile(dir_, true);
+}
+
+bool Unpacker::Unpack(const base::FilePath& zip_file) {
+ DCHECK(zip_file_.empty());
+ zip_file_ = zip_file;
+
+ DCHECK(dir_.empty());
+ if (!CreateNewTempDirectory(base::FilePath::StringType(), &dir_))
+ return false;
+ if (!zip::Unzip(zip_file, dir_)) {
+ dir_ = base::FilePath();
+ return false;
+ }
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/tools/package_manager/unpacker.h b/mojo/tools/package_manager/unpacker.h
new file mode 100644
index 0000000..2f7acc5
--- /dev/null
+++ b/mojo/tools/package_manager/unpacker.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_TOOLS_PACKAGE_MANAGER_UNPACKER_H_
+#define MOJO_TOOLS_PACKAGE_MANAGER_UNPACKER_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+// Unzips a package into a temporary folder. The temporary folder will be
+// deleted when the object is destroyed.
+//
+// In the future, this class would probably manage doing the unzip operation on
+// a background thread.
+class Unpacker {
+ public:
+ Unpacker();
+ ~Unpacker();
+
+ // Actually does the unpacking, returns true on success.
+ bool Unpack(const base::FilePath& zip_file);
+
+ // The root directory where the package has been unpacked.
+ const base::FilePath& dir() const { return dir_; }
+
+ private:
+ base::FilePath zip_file_;
+
+ base::FilePath dir_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Unpacker);
+};
+
+} // namespace mojo
+
+#endif // MOJO_TOOLS_PACKAGE_MANAGER_UNPACKER_H_
diff --git a/mojo/tools/pylib/mojo_python_tests_runner.py b/mojo/tools/pylib/mojo_python_tests_runner.py
new file mode 100644
index 0000000..e63a2a4
--- /dev/null
+++ b/mojo/tools/pylib/mojo_python_tests_runner.py
@@ -0,0 +1,147 @@
+# Copyright 2014 The Chromium 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 json
+import os
+import sys
+import time
+import unittest
+
+
+class MojoPythonTestRunner(object):
+ """Helper class to run python tests on the bots."""
+
+ def __init__(self, test_dir):
+ self._test_dir = test_dir
+
+ def run(self):
+ parser = argparse.ArgumentParser()
+ parser.usage = 'run_mojo_python_tests.py [options] [tests...]'
+ parser.add_argument('-v', '--verbose', action='count', default=0)
+ parser.add_argument('--metadata', action='append', default=[],
+ help=('optional key=value metadata that will be stored '
+ 'in the results files (can be used for revision '
+ 'numbers, etc.)'))
+ parser.add_argument('--write-full-results-to', metavar='FILENAME',
+ action='store',
+ help='path to write the list of full results to.')
+ parser.add_argument('tests', nargs='*')
+
+ self.add_custom_commandline_options(parser)
+ args = parser.parse_args()
+ self.apply_customization(args)
+
+ bad_metadata = False
+ for val in args.metadata:
+ if '=' not in val:
+ print >> sys.stderr, ('Error: malformed metadata "%s"' % val)
+ bad_metadata = True
+ if bad_metadata:
+ print >> sys.stderr
+ parser.print_help()
+ return 2
+
+ chromium_src_dir = os.path.join(os.path.dirname(__file__),
+ os.pardir,
+ os.pardir,
+ os.pardir)
+
+ loader = unittest.loader.TestLoader()
+ print "Running Python unit tests under %s..." % self._test_dir
+
+ pylib_dir = os.path.abspath(os.path.join(chromium_src_dir, self._test_dir))
+ if args.tests:
+ if pylib_dir not in sys.path:
+ sys.path.append(pylib_dir)
+ suite = unittest.TestSuite()
+ for test_name in args.tests:
+ suite.addTests(loader.loadTestsFromName(test_name))
+ else:
+ suite = loader.discover(pylib_dir, pattern='*_unittest.py')
+
+ runner = unittest.runner.TextTestRunner(verbosity=(args.verbose + 1))
+ result = runner.run(suite)
+
+ full_results = _FullResults(suite, result, args.metadata)
+ if args.write_full_results_to:
+ with open(args.write_full_results_to, 'w') as fp:
+ json.dump(full_results, fp, indent=2)
+ fp.write("\n")
+
+ return 0 if result.wasSuccessful() else 1
+
+ def add_custom_commandline_options(self, parser):
+ """Allow to add custom option to the runner script."""
+ pass
+
+ def apply_customization(self, args):
+ """Allow to apply any customization to the runner."""
+ pass
+
+
+TEST_SEPARATOR = '.'
+
+
+def _FullResults(suite, result, metadata):
+ """Convert the unittest results to the Chromium JSON test result format.
+
+ This matches run-webkit-tests (the layout tests) and the flakiness dashboard.
+ """
+
+ full_results = {}
+ full_results['interrupted'] = False
+ full_results['path_delimiter'] = TEST_SEPARATOR
+ full_results['version'] = 3
+ full_results['seconds_since_epoch'] = time.time()
+ for md in metadata:
+ key, val = md.split('=', 1)
+ full_results[key] = val
+
+ all_test_names = _AllTestNames(suite)
+ failed_test_names = _FailedTestNames(result)
+
+ full_results['num_failures_by_type'] = {
+ 'FAIL': len(failed_test_names),
+ 'PASS': len(all_test_names) - len(failed_test_names),
+ }
+
+ full_results['tests'] = {}
+
+ for test_name in all_test_names:
+ value = {}
+ value['expected'] = 'PASS'
+ if test_name in failed_test_names:
+ value['actual'] = 'FAIL'
+ value['is_unexpected'] = True
+ else:
+ value['actual'] = 'PASS'
+ _AddPathToTrie(full_results['tests'], test_name, value)
+
+ return full_results
+
+
+def _AllTestNames(suite):
+ test_names = []
+ # _tests is protected pylint: disable=W0212
+ for test in suite._tests:
+ if isinstance(test, unittest.suite.TestSuite):
+ test_names.extend(_AllTestNames(test))
+ else:
+ test_names.append(test.id())
+ return test_names
+
+
+def _FailedTestNames(result):
+ return set(test.id() for test, _ in result.failures + result.errors)
+
+
+def _AddPathToTrie(trie, path, value):
+ if TEST_SEPARATOR not in path:
+ trie[path] = value
+ return
+ directory, rest = path.split(TEST_SEPARATOR, 1)
+ if directory not in trie:
+ trie[directory] = {}
+ _AddPathToTrie(trie[directory], rest, value)
diff --git a/mojo/tools/pylib/transitive_hash.py b/mojo/tools/pylib/transitive_hash.py
new file mode 100644
index 0000000..93e8dc4
--- /dev/null
+++ b/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/mojo/tools/run_mojo_python_bindings_tests.py b/mojo/tools/run_mojo_python_bindings_tests.py
new file mode 100755
index 0000000..916c68d
--- /dev/null
+++ b/mojo/tools/run_mojo_python_bindings_tests.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.
+
+import os
+import sys
+
+_script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, os.path.join(_script_dir, "pylib"))
+
+from mojo_python_tests_runner import MojoPythonTestRunner
+
+
+class PythonBindingsTestRunner(MojoPythonTestRunner):
+
+ def add_custom_commandline_options(self, parser):
+ parser.add_argument('--build-dir', action='store',
+ help='path to the build output directory')
+
+ def apply_customization(self, args):
+ if args.build_dir:
+ python_build_dir = os.path.join(args.build_dir, 'python')
+ if python_build_dir not in sys.path:
+ sys.path.append(python_build_dir)
+ python_gen_dir = os.path.join(
+ args.build_dir,
+ 'gen', 'mojo', 'public', 'interfaces', 'bindings', 'tests')
+ if python_gen_dir not in sys.path:
+ sys.path.append(python_gen_dir)
+
+
+def main():
+ runner = PythonBindingsTestRunner(os.path.join('mojo', 'python', 'tests'))
+ sys.exit(runner.run())
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/mojo/tools/run_mojo_python_tests.py b/mojo/tools/run_mojo_python_tests.py
new file mode 100755
index 0000000..d83ca54
--- /dev/null
+++ b/mojo/tools/run_mojo_python_tests.py
@@ -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.
+
+import os
+import sys
+
+_script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, os.path.join(_script_dir, "pylib"))
+
+from mojo_python_tests_runner import MojoPythonTestRunner
+
+
+def main():
+ runner = MojoPythonTestRunner(os.path.join('mojo', 'public', 'tools',
+ 'bindings', 'pylib'))
+ sys.exit(runner.run())
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/mojo/tools/test_runner.py b/mojo/tools/test_runner.py
new file mode 100755
index 0000000..b0bb4d9
--- /dev/null
+++ b/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/mojo/views/BUILD.gn b/mojo/views/BUILD.gn
new file mode 100644
index 0000000..005654a
--- /dev/null
+++ b/mojo/views/BUILD.gn
@@ -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("//build/config/ui.gni")
+
+assert(use_aura)
+
+# GYP version: mojo/mojo.gyp:mojo_views_support
+source_set("views") {
+ sources = [
+ "input_method_mojo_linux.cc",
+ "input_method_mojo_linux.h",
+ "native_widget_view_manager.cc",
+ "native_widget_view_manager.h",
+ "views_init.cc",
+ "views_init.h",
+ ]
+
+ deps = [
+ ":views_internal",
+ "//base",
+ "//base:i18n",
+ "//skia",
+ "//third_party/icu",
+ "//ui/aura",
+ "//ui/base",
+ "//ui/views",
+ "//ui/wm",
+ "//mojo/aura",
+ ]
+}
+
+# GYP version: mojo/mojo.gyp:mojo_views_support_internal
+component("views_internal") {
+ output_name = "mojo_views_support_internal"
+
+ visibility = [ ":views" ]
+
+ sources = [
+ "mojo_views_export.h",
+ "views_init_internal.cc",
+ "views_init_internal.h",
+ ]
+
+ defines = [ "MOJO_VIEWS_IMPLEMENTATION" ]
+
+ deps = [
+ "//base",
+ "//base:i18n",
+ "//base/third_party/dynamic_annotations",
+ "//skia",
+ "//third_party/icu",
+ "//ui/base",
+ "//ui/gfx",
+ ]
+}
diff --git a/mojo/views/DEPS b/mojo/views/DEPS
new file mode 100644
index 0000000..a182521
--- /dev/null
+++ b/mojo/views/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+ "+base",
+ "+skia",
+ "+ui/aura",
+ "+ui/base",
+ "+ui/events",
+ "+ui/views",
+ "+ui/wm",
+]
diff --git a/mojo/views/input_method_mojo_linux.cc b/mojo/views/input_method_mojo_linux.cc
new file mode 100644
index 0000000..e54b644
--- /dev/null
+++ b/mojo/views/input_method_mojo_linux.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 "mojo/views/input_method_mojo_linux.h"
+
+#include "ui/base/ime/text_input_client.h"
+#include "ui/events/event.h"
+
+namespace mojo {
+
+InputMethodMojoLinux::InputMethodMojoLinux(
+ ui::internal::InputMethodDelegate* delegate)
+ : ui::InputMethodAuraLinux(delegate) {
+}
+
+InputMethodMojoLinux::~InputMethodMojoLinux() {}
+
+bool InputMethodMojoLinux::DispatchKeyEvent(const ui::KeyEvent& event) {
+ DCHECK(event.type() == ui::ET_KEY_PRESSED ||
+ event.type() == ui::ET_KEY_RELEASED);
+ DCHECK(system_toplevel_window_focused());
+
+ // If no text input client, do nothing.
+ if (!GetTextInputClient())
+ return DispatchKeyEventPostIME(event);
+
+ // Here is where we change the differ from our base class's logic. Instead of
+ // always dispatching a key down event, and then sending a synthesized
+ // character event, we instead check to see if this is a character event and
+ // send out the key if it is. (We fallback to normal dispatch if it isn't.)
+ if (event.is_char()) {
+ const uint16 ch = event.GetCharacter();
+ if (GetTextInputClient())
+ GetTextInputClient()->InsertChar(ch, event.flags());
+
+ return false;
+ }
+
+ return DispatchKeyEventPostIME(event);
+}
+
+} // namespace mojo
diff --git a/mojo/views/input_method_mojo_linux.h b/mojo/views/input_method_mojo_linux.h
new file mode 100644
index 0000000..0072a29
--- /dev/null
+++ b/mojo/views/input_method_mojo_linux.h
@@ -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.
+
+#ifndef MOJO_VIEWS_INPUT_METHOD_MOJO_LINUX_H_
+#define MOJO_VIEWS_INPUT_METHOD_MOJO_LINUX_H_
+
+#include "ui/base/ime/input_method_auralinux.h"
+#include "ui/base/ime/input_method_delegate.h"
+
+namespace mojo {
+
+// An input method for linux that does absolutely no translation.
+//
+// The current InputMethodMinimal makes assumptions that a system will only
+// input/output keydown/keyup events; it assumes that things don't work like
+// Windows does. When it gets a keydown event, it then tries to insert a
+// character at the same time.
+//
+// However, we're standardizing on Windows' WM_CHAR style events. This tries to
+// follow InputMethodWin::DispatchKeyEvent() instead, because PlatformViewX11
+// now synthesizes a character events so that we have one behaviour across our
+// platforms.
+class InputMethodMojoLinux : public ui::InputMethodAuraLinux {
+ public:
+ explicit InputMethodMojoLinux(ui::internal::InputMethodDelegate* delegate);
+ virtual ~InputMethodMojoLinux();
+
+ // Overriden from ui::InputMethodAuraLinux:
+ virtual bool DispatchKeyEvent(const ui::KeyEvent& event) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InputMethodMojoLinux);
+};
+
+} // namespace mojo
+
+#endif // MOJO_VIEWS_INPUT_METHOD_MOJO_LINUX_H_
diff --git a/mojo/views/mojo_views_export.h b/mojo/views/mojo_views_export.h
new file mode 100644
index 0000000..0ca8985
--- /dev/null
+++ b/mojo/views/mojo_views_export.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_VIEWS_MOJO_VIEWS_EXPORT_H_
+#define MOJO_VIEWS_MOJO_VIEWS_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(MOJO_VIEWS_IMPLEMENTATION)
+#define MOJO_VIEWS_EXPORT __declspec(dllexport)
+#else
+#define MOJO_VIEWS_EXPORT __declspec(dllimport)
+#endif // defined(MOJO_VIEWS_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(MOJO_VIEWS_IMPLEMENTATION)
+#define MOJO_VIEWS_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_VIEWS_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define MOJO_VIEWS_EXPORT
+#endif
+
+#endif // MOJO_VIEWS_MOJO_VIEWS_EXPORT_H_
diff --git a/mojo/views/native_widget_view_manager.cc b/mojo/views/native_widget_view_manager.cc
new file mode 100644
index 0000000..6907ea6
--- /dev/null
+++ b/mojo/views/native_widget_view_manager.cc
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium 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 "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/default_capture_client.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_base.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/capture_controller.h"
+#include "ui/wm/core/focus_controller.h"
+
+#if defined(OS_LINUX)
+#include "mojo/views/input_method_mojo_linux.h"
+#endif
+
+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) {
+ ui::InitializeInputMethodForTesting();
+#if defined(OS_LINUX)
+ input_method_.reset(new InputMethodMojoLinux(this));
+#else
+ input_method_ = ui::CreateInputMethod(this, gfx::kNullAcceleratedWidget);
+#endif
+ 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* view)
+ : NativeWidgetAura(delegate),
+ view_(view) {
+ view_->AddObserver(this);
+ window_tree_host_.reset(new WindowTreeHostMojo(view_, 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());
+
+ capture_client_.reset(
+ new aura::client::DefaultCaptureClient(window_tree_host_->window()));
+}
+
+NativeWidgetViewManager::~NativeWidgetViewManager() {
+ if (view_)
+ 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) {
+ if (view_)
+ view_->SetContents(bitmap);
+}
+
+void NativeWidgetViewManager::OnViewDestroyed(View* view) {
+ DCHECK_EQ(view, view_);
+ view->RemoveObserver(this);
+ view_ = NULL;
+}
+
+void NativeWidgetViewManager::OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ GetWidget()->SetBounds(gfx::Rect(view->bounds().size()));
+}
+
+void NativeWidgetViewManager::OnViewInputEvent(View* view,
+ const EventPtr& event) {
+ scoped_ptr<ui::Event> ui_event(event.To<scoped_ptr<ui::Event> >());
+ if (ui_event)
+ window_tree_host_->SendEventToProcessor(ui_event.get());
+}
+
+} // namespace mojo
diff --git a/mojo/views/native_widget_view_manager.h b/mojo/views/native_widget_view_manager.h
new file mode 100644
index 0000000..a9e8220
--- /dev/null
+++ b/mojo/views/native_widget_view_manager.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_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/view_observer.h"
+#include "ui/views/widget/native_widget_aura.h"
+
+namespace aura {
+namespace client {
+class DefaultCaptureClient;
+}
+}
+
+namespace ui {
+namespace internal {
+class InputMethodDelegate;
+}
+}
+
+namespace wm {
+class FocusController;
+}
+
+namespace mojo {
+
+class WindowTreeHostMojo;
+
+class NativeWidgetViewManager : public views::NativeWidgetAura,
+ public WindowTreeHostMojoDelegate,
+ public ViewObserver {
+ public:
+ NativeWidgetViewManager(views::internal::NativeWidgetDelegate* delegate,
+ View* view);
+ virtual ~NativeWidgetViewManager();
+
+ private:
+ // Overridden from internal::NativeWidgetAura:
+ virtual void InitNativeWidget(
+ const views::Widget::InitParams& in_params) OVERRIDE;
+
+ // WindowTreeHostMojoDelegate:
+ virtual void CompositorContentsChanged(const SkBitmap& bitmap) OVERRIDE;
+
+ // ViewObserver:
+ virtual void OnViewDestroyed(View* view) OVERRIDE;
+ virtual void OnViewBoundsChanged(View* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void OnViewInputEvent(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* view_;
+
+ scoped_ptr<aura::client::DefaultCaptureClient> capture_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeWidgetViewManager);
+};
+
+} // namespace mojo
+
+#endif // MOJO_VIEWS_NATIVE_WIDGET_VIEW_MANAGER_H_
diff --git a/mojo/views/views_init.cc b/mojo/views/views_init.cc
new file mode 100644
index 0000000..edd8af3
--- /dev/null
+++ b/mojo/views/views_init.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/views/views_init.h"
+
+#include "mojo/views/views_init_internal.h"
+
+namespace mojo {
+
+ViewsInit::ViewsInit() {
+ InitViews();
+}
+
+ViewsInit::~ViewsInit() {
+}
+
+} // namespace mojo
diff --git a/mojo/views/views_init.h b/mojo/views/views_init.h
new file mode 100644
index 0000000..ad3b95e
--- /dev/null
+++ b/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_
diff --git a/mojo/views/views_init_internal.cc b/mojo/views/views_init_internal.cc
new file mode 100644
index 0000000..6d92c7c
--- /dev/null
+++ b/mojo/views/views_init_internal.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/views/views_init_internal.h"
+
+#include "base/i18n/icu_util.h"
+#include "base/lazy_instance.h"
+#include "base/path_service.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/ui_base_paths.h"
+
+namespace mojo {
+
+namespace {
+
+// Used to ensure we only init once.
+class Setup {
+ public:
+ Setup() {
+ 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);
+
+ // There is a bunch of static state in gfx::Font, by running this now,
+ // before any other apps load, we ensure all the state is set up.
+ gfx::Font();
+ }
+
+ ~Setup() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Setup);
+};
+
+static base::LazyInstance<Setup>::Leaky setup = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+void InitViews() {
+ setup.Get();
+}
+
+} // namespace mojo
diff --git a/mojo/views/views_init_internal.h b/mojo/views/views_init_internal.h
new file mode 100644
index 0000000..5ec66e7
--- /dev/null
+++ b/mojo/views/views_init_internal.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_VIEWS_VIEWS_INIT_INTERNAL_H_
+#define MOJO_VIEWS_VIEWS_INIT_INTERNAL_H_
+
+#include "mojo/views/mojo_views_export.h"
+
+namespace mojo {
+
+// This is in a separate target so that we only initialize some common state
+// once.
+// Do not use this, it is called internally by ViewsInit.
+MOJO_VIEWS_EXPORT void InitViews();
+
+} // namespace mojo
+
+#endif // MOJO_VIEWS_VIEWS_INIT_INTERNAL_H_