Move //mojo/shell to //shell
//shell is unambiguous in this repository and shorter.
R=ben@chromium.org
Review URL: https://codereview.chromium.org/775343004
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
new file mode 100644
index 0000000..405b423
--- /dev/null
+++ b/shell/BUILD.gn
@@ -0,0 +1,443 @@
+# Copyright 2014 The Chromium 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/mojo.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+# We don't support building in the component build since mojo apps are
+# inherently components.
+assert(!is_component_build)
+
+group("shell") {
+ testonly = true
+
+ deps = [
+ ":mojo_shell",
+ ":tests",
+ ]
+
+ if (!is_win) {
+ deps += [ ":mojo_launcher" ]
+ }
+}
+
+group("tests") {
+ testonly = true
+ deps = [
+ ":external_application_unittests",
+ ":mojo_shell_tests",
+ ]
+}
+
+if (is_android) {
+ import("//build/config/android/config.gni")
+ import("//build/config/android/rules.gni")
+}
+
+if (!use_prebuilt_mojo_shell) {
+ executable("mojo_shell") {
+ sources = [
+ "desktop/mojo_main.cc",
+ ]
+
+ deps = [
+ ":init",
+ ":lib",
+ "//base",
+ "//build/config/sanitizers:deps",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ ]
+ }
+} # !use_prebuilt_mojo_shell
+
+executable("mojo_launcher") {
+ sources = [
+ "launcher_main.cc",
+ ]
+
+ deps = [
+ ":external_application_registrar_bindings",
+ ":external_application_registrar_connection",
+ ":init",
+ ":in_process_dynamic_service_runner",
+ "//base",
+ "//build/config/sanitizers:deps",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//url",
+ ]
+}
+
+source_set("init") {
+ sources = [
+ "init.cc",
+ "init.h",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+source_set("in_process_dynamic_service_runner") {
+ sources = [
+ "dynamic_service_runner.cc",
+ "dynamic_service_runner.h",
+ "in_process_dynamic_service_runner.cc",
+ "in_process_dynamic_service_runner.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/gles2",
+ "//mojo/public/cpp/system",
+ ]
+
+ # This target has to include the public thunk headers, which generally
+ # shouldn't be included without picking an implementation. We are providing
+ # the implementation but the thunk header target cannot declare that we are
+ # permitted to include it since it's in the public SDK and we are not.
+ # Suppress include checking so we can still check the rest of the targets in
+ # this file.
+ check_includes = false
+}
+
+source_set("lib") {
+ 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",
+ "data_pipe_peek.cc",
+ "data_pipe_peek.h",
+ "dynamic_application_loader.cc",
+ "dynamic_application_loader.h",
+ "external_application_listener.h",
+ "external_application_listener_posix.cc",
+ "external_application_listener_win.cc",
+ "filename_util.cc",
+ "filename_util.h",
+ "incoming_connection_listener_posix.cc",
+ "incoming_connection_listener_posix.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",
+ ]
+
+ deps = [
+ ":app_child_process_bindings",
+ ":external_application_registrar_bindings",
+ ":init",
+ ":in_process_dynamic_service_runner",
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//base:base_static",
+ "//mojo/application",
+ "//mojo/application_manager",
+ "//mojo/common",
+ "//mojo/common:tracing_impl",
+ "//mojo/edk/system",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/interfaces/application",
+ "//mojo/services/public/interfaces/network",
+ "//shell/domain_socket",
+ "//mojo/spy",
+ "//services/tracing:bindings",
+ "//url",
+ ]
+
+ if (is_win) {
+ deps -= [ "//shell/domain_socket" ]
+ }
+
+ if (is_android) {
+ sources += [
+ "android/android_handler.h",
+ "android/android_handler.cc",
+ "android/android_handler_loader.h",
+ "android/android_handler_loader.cc",
+ "network_application_loader.cc",
+ "network_application_loader.h",
+ ]
+
+ deps += [
+ ":jni_headers",
+ ":run_android_application_function",
+ "//mojo/application:content_handler",
+ "//mojo/services/network:lib",
+ "//services/gles2",
+ "//services/native_viewport:lib",
+ ]
+ }
+
+ # This target includes some files behind #ifdef OS... guards. Since gn is not
+ # smart enough to understand preprocess includes, it does complains about
+ # these includes when not using the build files for that OS. Suppress checking
+ # so we can enable checking for the rest of the targets in this file.
+ # TODO: Might be better to split the files with OS-specific includes out to a
+ # separate source_set so we can leave checking on for the rest of the target.
+ check_includes = false
+}
+
+if (is_android) {
+ generate_jni("jni_headers") {
+ sources = [
+ "android/apk/src/org/chromium/mojo_shell_apk/AndroidHandler.java",
+ "android/apk/src/org/chromium/mojo_shell_apk/Bootstrap.java",
+ "android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java",
+ ]
+ jni_package = "mojo"
+ }
+
+ android_library("bootstrap_java") {
+ java_files =
+ [ "android/apk/src/org/chromium/mojo_shell_apk/Bootstrap.java" ]
+
+ deps = [
+ "//base:base_java",
+ ]
+
+ dex_path = "$target_out_dir/bootstrap_java.dex.jar"
+ }
+
+ shared_library("bootstrap") {
+ sources = [
+ "android/bootstrap.cc",
+ ]
+ deps = [
+ ":jni_headers",
+ ":lib",
+ ":run_android_application_function",
+ "//base",
+ ]
+ }
+
+ # Shared header between the bootstrap and the main shell .so.
+ source_set("run_android_application_function") {
+ sources = [
+ "android/run_android_application_function.h",
+ ]
+ }
+
+ android_library("java") {
+ java_files = [
+ "android/apk/src/org/chromium/mojo_shell_apk/AndroidHandler.java",
+ "android/apk/src/org/chromium/mojo_shell_apk/FileHelper.java",
+ "android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java",
+ "android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java",
+ "android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java",
+ ]
+
+ deps = [
+ "//base:base_java",
+ "//net/android:net_java",
+ ]
+ }
+
+ android_resources("resources") {
+ resource_dirs = [ "android/apk/res" ]
+ custom_package = "org.chromium.mojo_shell_apk"
+ }
+
+ shared_library("libmojo_shell") {
+ sources = [
+ "android/library_loader.cc",
+ "android/mojo_main.cc",
+ "android/mojo_main.h",
+ ]
+ deps = [
+ ":jni_headers",
+ ":lib",
+ "//mojo/application_manager",
+ "//net",
+ "//services/native_viewport:lib",
+ "//ui/gl",
+ ]
+ }
+
+ mojo_shell_assets_dir = "$root_build_dir/mojo_shell_assets"
+
+ copy_ex("copy_mojo_shell_assets") {
+ clear_dir = true
+ dest = mojo_shell_assets_dir
+ sources = [
+ "$root_out_dir/lib.stripped/libbootstrap.so",
+ "$root_out_dir/obj/mojo/shell/bootstrap_java.dex.jar",
+ ]
+ }
+
+ android_apk("mojo_shell_apk") {
+ apk_name = "MojoShell"
+
+ android_manifest = "android/apk/AndroidManifest.xml"
+
+ native_libs = [ "libmojo_shell.so" ]
+
+ asset_location = mojo_shell_assets_dir
+
+ deps = [
+ ":copy_mojo_shell_assets",
+ ":java",
+ ":libmojo_shell",
+ ":resources",
+ "//services/native_viewport:native_viewport_java",
+ ]
+ }
+}
+
+mojom("app_child_process_bindings") {
+ sources = [
+ "app_child_process.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",
+ "//base",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/interfaces/application",
+ "//shell/domain_socket",
+ "//url",
+ ]
+
+ if (is_win) {
+ deps -= [ "//shell/domain_socket" ]
+ }
+}
+
+# GYP version: mojo/mojo.gyp:mojo_shell_tests
+test("mojo_shell_tests") {
+ sources = [
+ "child_process_host_unittest.cc",
+ "data_pipe_peek_unittest.cc",
+ "dynamic_application_loader_unittest.cc",
+ "in_process_dynamic_service_runner_unittest.cc",
+ "mojo_url_resolver_unittest.cc",
+ "shell_test_base.cc",
+ "shell_test_base.h",
+ "shell_test_base_unittest.cc",
+ "shell_test_main.cc",
+ ]
+
+ deps = [
+ ":in_process_dynamic_service_runner",
+ ":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/public/cpp/bindings",
+ "//services/test_service:bindings",
+ ]
+
+ datadeps = [
+ "//services/test_service:test_app",
+ "//services/test_service:test_request_tracker_app",
+ ]
+
+ 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 = [
+ ":init",
+ ":lib",
+ "//base",
+ "//mojo/application_manager",
+ "//mojo/edk/system",
+ ]
+}
+
+test("external_application_unittests") {
+ sources = [
+ "incoming_connection_listener_unittest.cc",
+ "external_application_listener_unittest.cc",
+ "external_application_test_main.cc",
+ ]
+
+ deps = [
+ ":lib",
+ ":external_application_registrar_connection",
+ ":external_application_registrar_bindings",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ "//url",
+ "//mojo/application",
+ "//mojo/application_manager",
+ "//mojo/common",
+ "//mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//shell/domain_socket",
+ "//shell/domain_socket:tests",
+ ]
+
+ if (is_win) {
+ sources -= [
+ "incoming_connection_listener_unittest.cc",
+ "external_application_listener_unittest.cc",
+ ]
+
+ deps -= [
+ ":lib",
+ ":external_application_registrar_connection",
+ ":external_application_registrar_bindings",
+ "//shell/domain_socket",
+ "//shell/domain_socket:tests",
+ ]
+ }
+}
diff --git a/shell/android/android_handler.cc b/shell/android/android_handler.cc
new file mode 100644
index 0000000..073aa01
--- /dev/null
+++ b/shell/android/android_handler.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 "shell/android/android_handler.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/scoped_native_library.h"
+#include "jni/AndroidHandler_jni.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "shell/android/run_android_application_function.h"
+#include "shell/dynamic_service_runner.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetApplicationContext;
+
+namespace mojo {
+
+namespace {
+// This function loads the application library, sets the application context and
+// thunks and calls into the application MojoMain. To ensure that the thunks are
+// set correctly we keep it in the Mojo shell .so and pass the function pointer
+// to the helper libbootstrap.so.
+void RunAndroidApplication(JNIEnv* env,
+ jobject j_context,
+ const base::FilePath& app_path,
+ jint j_handle) {
+ ScopedMessagePipeHandle handle((mojo::MessagePipeHandle(j_handle)));
+
+ // Load the library, so that we can set the application context there if
+ // needed.
+ base::NativeLibraryLoadError error;
+ base::ScopedNativeLibrary app_library(
+ base::LoadNativeLibrary(app_path, &error));
+ if (!app_library.is_valid()) {
+ LOG(ERROR) << "Failed to load app library (error: " << error.ToString()
+ << ")";
+ return;
+ }
+
+ // Set the application context if needed. Most applications will need to
+ // access the Android ApplicationContext in which they are run. If the
+ // application library exports the InitApplicationContext function, we will
+ // set it there.
+ const char* init_application_context_name = "InitApplicationContext";
+ typedef void (*InitApplicationContextFn)(
+ const base::android::JavaRef<jobject>&);
+ InitApplicationContextFn init_application_context =
+ reinterpret_cast<InitApplicationContextFn>(
+ app_library.GetFunctionPointer(init_application_context_name));
+ if (init_application_context) {
+ base::android::ScopedJavaLocalRef<jobject> scoped_context(env, j_context);
+ init_application_context(scoped_context);
+ }
+
+ // Run the application.
+ base::ScopedNativeLibrary app_library_from_runner(
+ shell::DynamicServiceRunner::LoadAndRunService(app_path, handle.Pass()));
+}
+} // namespace
+
+void AndroidHandler::RunApplication(ShellPtr shell, URLResponsePtr response) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_archive_path =
+ Java_AndroidHandler_getNewTempArchivePath(env, GetApplicationContext());
+ base::FilePath archive_path(
+ ConvertJavaStringToUTF8(env, j_archive_path.obj()));
+
+ mojo::common::BlockingCopyToFile(response->body.Pass(), archive_path);
+ RunAndroidApplicationFn run_android_application_fn = &RunAndroidApplication;
+ Java_AndroidHandler_bootstrap(
+ env, GetApplicationContext(), j_archive_path.obj(),
+ shell.PassMessagePipe().release().value(),
+ reinterpret_cast<jlong>(run_android_application_fn));
+}
+
+void AndroidHandler::Initialize(ApplicationImpl* app) {
+}
+
+bool AndroidHandler::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService(&content_handler_factory_);
+ return true;
+}
+
+bool RegisterAndroidHandlerJni(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace mojo
diff --git a/shell/android/android_handler.h b/shell/android/android_handler.h
new file mode 100644
index 0000000..17107af
--- /dev/null
+++ b/shell/android/android_handler.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_SHELL_ANDROID_CONTENT_HANDLER_H_
+#define MOJO_SHELL_ANDROID_CONTENT_HANDLER_H_
+
+#include <jni.h>
+
+#include "mojo/application/content_handler_factory.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace mojo {
+
+class AndroidHandler : public ApplicationDelegate,
+ public ContentHandlerFactory::Delegate {
+ public:
+ AndroidHandler() : content_handler_factory_(this) {}
+ virtual ~AndroidHandler() {}
+
+ private:
+ // ApplicationDelegate:
+ void Initialize(ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+
+ // ContentHandlerFactory::Delegate:
+ void RunApplication(ShellPtr shell, URLResponsePtr response) override;
+
+ ContentHandlerFactory content_handler_factory_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AndroidHandler);
+};
+
+bool RegisterAndroidHandlerJni(JNIEnv* env);
+
+} // namespace mojo
+
+#endif // MOJO_SHELL_ANDROID_CONTENT_HANDLER_H_
diff --git a/shell/android/android_handler_loader.cc b/shell/android/android_handler_loader.cc
new file mode 100644
index 0000000..a6eab42
--- /dev/null
+++ b/shell/android/android_handler_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 "shell/android/android_handler_loader.h"
+
+namespace mojo {
+namespace shell {
+
+AndroidHandlerLoader::AndroidHandlerLoader() {
+}
+
+AndroidHandlerLoader::~AndroidHandlerLoader() {
+}
+
+void AndroidHandlerLoader::Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) {
+ DCHECK(shell_handle.is_valid());
+ application_.reset(
+ new ApplicationImpl(&android_handler_, shell_handle.Pass()));
+}
+
+void AndroidHandlerLoader::OnApplicationError(ApplicationManager* manager,
+ const GURL& url) {
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/android/android_handler_loader.h b/shell/android/android_handler_loader.h
new file mode 100644
index 0000000..19e7fa6
--- /dev/null
+++ b/shell/android/android_handler_loader.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_ANDROID_ANDROID_HANDLER_LOADER_H_
+#define SHELL_ANDROID_ANDROID_HANDLER_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_impl.h"
+#include "shell/android/android_handler.h"
+
+namespace mojo {
+namespace shell {
+
+class AndroidHandlerLoader : public ApplicationLoader {
+ public:
+ AndroidHandlerLoader();
+ virtual ~AndroidHandlerLoader();
+
+ private:
+ // ApplicationLoader overrides:
+ void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) override;
+ void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override;
+
+ AndroidHandler android_handler_;
+ scoped_ptr<ApplicationImpl> application_;
+
+ DISALLOW_COPY_AND_ASSIGN(AndroidHandlerLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_ANDROID_ANDROID_HANDLER_LOADER_H_
diff --git a/shell/android/apk/AndroidManifest.xml b/shell/android/apk/AndroidManifest.xml
new file mode 100644
index 0000000..3c4c3ae
--- /dev/null
+++ b/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="21" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/shell/android/apk/res/layout/mojo_shell_activity.xml b/shell/android/apk/res/layout/mojo_shell_activity.xml
new file mode 100644
index 0000000..d319941
--- /dev/null
+++ b/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/shell/android/apk/res/values/strings.xml b/shell/android/apk/res/values/strings.xml
new file mode 100644
index 0000000..ff3f8bb
--- /dev/null
+++ b/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/shell/android/apk/src/org/chromium/mojo_shell_apk/AndroidHandler.java b/shell/android/apk/src/org/chromium/mojo_shell_apk/AndroidHandler.java
new file mode 100644
index 0000000..9b7838d
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo_shell_apk/AndroidHandler.java
@@ -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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.content.Context;
+import android.util.Log;
+
+import dalvik.system.DexClassLoader;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+
+/**
+ * Content handler for archives containing native libraries bundled with Java code.
+ * <p>
+ * TODO(ppi): create a seperate instance for each application being bootstrapped to keep track of
+ * the temporary files and clean them up once the execution finishes.
+ */
+@JNINamespace("mojo")
+public class AndroidHandler {
+ private static final String TAG = "AndroidHandler";
+
+ // Bootstrap native and java libraries are packaged with the MojoShell APK as assets.
+ private static final String BOOTSTRAP_JAVA_LIBRARY = "bootstrap_java.dex.jar";
+ private static final String BOOTSTRAP_NATIVE_LIBRARY = "libbootstrap.so";
+ // Name of the bootstrapping runnable shipped in the packaged Java library.
+ private static final String BOOTSTRAP_CLASS = "org.chromium.mojo_shell_apk.Bootstrap";
+
+ // File extensions used to identify application libraries in the provided archive.
+ private static final String JAVA_LIBRARY_SUFFIX = ".dex.jar";
+ private static final String NATIVE_LIBRARY_SUFFIX = ".so";
+ // Filename sections used for naming temporary files holding application files.
+ private static final String ARCHIVE_PREFIX = "archive";
+ private static final String ARCHIVE_SUFFIX = ".zip";
+
+ // Directories used to hold temporary files. These are cleared when clearTemporaryFiles() is
+ // called.
+ private static final String DEX_OUTPUT_DIRECTORY = "dex_output";
+ private static final String APP_DIRECTORY = "applications";
+ private static final String ASSET_DIRECTORY = "assets";
+
+ /**
+ * Deletes directories holding the temporary files. This should be called early on shell startup
+ * to clean up after the previous run.
+ */
+ static void clearTemporaryFiles(Context context) {
+ FileHelper.deleteRecursively(getDexOutputDir(context));
+ FileHelper.deleteRecursively(getAppDir(context));
+ FileHelper.deleteRecursively(getAssetDir(context));
+ }
+
+ /**
+ * Returns the path at which the native part should save the application archive.
+ */
+ @CalledByNative
+ private static String getNewTempArchivePath(Context context) throws IOException {
+ return File.createTempFile(ARCHIVE_PREFIX, ARCHIVE_SUFFIX,
+ getAppDir(context)).getAbsolutePath();
+ }
+
+ /**
+ * Extracts and runs the application libraries contained by the indicated archive.
+ * @param context the application context
+ * @param archivePath the path of the archive containing the application to be run
+ * @param handle handle to the shell to be passed to the native application. On the Java side
+ * this is opaque payload.
+ * @param runApplicationPtr pointer to the function that will set the native thunks and call
+ * into the application MojoMain. On the Java side this is opaque
+ * payload.
+ */
+ @CalledByNative
+ private static boolean bootstrap(Context context, String archivePath, int handle,
+ long runApplicationPtr) {
+ File bootstrap_java_library;
+ File bootstrap_native_library;
+ try {
+ bootstrap_java_library = FileHelper.extractFromAssets(context, BOOTSTRAP_JAVA_LIBRARY,
+ getAssetDir(context));
+ bootstrap_native_library = FileHelper.extractFromAssets(context,
+ BOOTSTRAP_NATIVE_LIBRARY, getAssetDir(context));
+ } catch (Exception e) {
+ Log.e(TAG, "Extraction of bootstrap files from assets failed.", e);
+ return false;
+ }
+
+ File application_java_library;
+ File application_native_library;
+ try {
+ File archive = new File(archivePath);
+ application_java_library = FileHelper.extractFromArchive(archive, JAVA_LIBRARY_SUFFIX,
+ getAppDir(context));
+ application_native_library = FileHelper.extractFromArchive(archive,
+ NATIVE_LIBRARY_SUFFIX, getAppDir(context));
+ } catch (Exception e) {
+ Log.e(TAG, "Extraction of application files from the archive failed.", e);
+ return false;
+ }
+
+ String dexPath = bootstrap_java_library.getAbsolutePath() + File.pathSeparator
+ + application_java_library.getAbsolutePath();
+ DexClassLoader bootstrapLoader = new DexClassLoader(dexPath,
+ getDexOutputDir(context).getAbsolutePath(), null,
+ ClassLoader.getSystemClassLoader());
+
+ try {
+ Class<?> loadedClass = bootstrapLoader.loadClass(BOOTSTRAP_CLASS);
+ Class<? extends Runnable> bootstrapClass = loadedClass.asSubclass(Runnable.class);
+ Constructor<? extends Runnable> constructor = bootstrapClass.getConstructor(
+ Context.class, File.class, File.class, Integer.class, Long.class);
+ Runnable bootstrapRunnable = constructor.newInstance(context, bootstrap_native_library,
+ application_native_library, Integer.valueOf(handle),
+ Long.valueOf(runApplicationPtr));
+ bootstrapRunnable.run();
+ } catch (Throwable t) {
+ Log.e(TAG, "Running Bootstrap failed.", t);
+ return false;
+ }
+ return true;
+ }
+
+ private static File getDexOutputDir(Context context) {
+ return context.getDir(DEX_OUTPUT_DIRECTORY, Context.MODE_PRIVATE);
+ }
+
+ private static File getAppDir(Context context) {
+ return context.getDir(APP_DIRECTORY, Context.MODE_PRIVATE);
+ }
+
+ private static File getAssetDir(Context context) {
+ return context.getDir(ASSET_DIRECTORY, Context.MODE_PRIVATE);
+ }
+}
diff --git a/shell/android/apk/src/org/chromium/mojo_shell_apk/Bootstrap.java b/shell/android/apk/src/org/chromium/mojo_shell_apk/Bootstrap.java
new file mode 100644
index 0000000..b74f03a
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo_shell_apk/Bootstrap.java
@@ -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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.content.Context;
+
+import org.chromium.base.JNINamespace;
+
+import java.io.File;
+
+/**
+ * Runnable used to bootstrap execution of Android Mojo application. For the JNI to work, we need a
+ * Java class with the application classloader in the call stack. We load this class in the
+ * application classloader and call into native from it to achieve that.
+ */
+@JNINamespace("mojo")
+public class Bootstrap implements Runnable {
+
+ private final Context mContext;
+ private final File mBootstrapNativeLibrary;
+ private final File mApplicationNativeLibrary;
+ private final int mHandle;
+ private final long mRunApplicationPtr;
+
+ public Bootstrap(Context context, File bootstrapNativeLibrary, File applicationNativeLibrary,
+ Integer handle, Long runApplicationPtr) {
+ mContext = context;
+ mBootstrapNativeLibrary = bootstrapNativeLibrary;
+ mApplicationNativeLibrary = applicationNativeLibrary;
+ mHandle = handle;
+ mRunApplicationPtr = runApplicationPtr;
+ }
+
+ @Override
+ public void run() {
+ System.load(mBootstrapNativeLibrary.getAbsolutePath());
+ System.load(mApplicationNativeLibrary.getAbsolutePath());
+ nativeBootstrap(mContext, mApplicationNativeLibrary.getAbsolutePath(), mHandle,
+ mRunApplicationPtr);
+ }
+
+ native void nativeBootstrap(Context context, String libraryPath, int handle,
+ long runApplicationPtr);
+}
diff --git a/shell/android/apk/src/org/chromium/mojo_shell_apk/FileHelper.java b/shell/android/apk/src/org/chromium/mojo_shell_apk/FileHelper.java
new file mode 100644
index 0000000..70f95e1
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo_shell_apk/FileHelper.java
@@ -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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.content.Context;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Helper methods for file extraction from APK assets and zip archives.
+ */
+class FileHelper {
+ // Size of the buffer used in streaming file operations.
+ private static final int BUFFER_SIZE = 1024 * 1024;
+ // Prefix used when naming temporary files.
+ private static final String TEMP_FILE_PREFIX = "temp-";
+
+ static File extractFromAssets(Context context, String assetName, File outputDirectory)
+ throws IOException, FileNotFoundException {
+ // Make the original filename part of the temp file name.
+ // TODO(ppi): do we need to sanitize the suffix?
+ String suffix = "-" + assetName;
+ File outputFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, outputDirectory);
+ BufferedInputStream inputStream = new BufferedInputStream(
+ context.getAssets().open(assetName));
+ writeStreamToFile(inputStream, outputFile);
+ inputStream.close();
+ return outputFile;
+ }
+
+ /**
+ * Extracts the file of the given extension from the archive. Throws FileNotFoundException if no
+ * matching file is found.
+ */
+ static File extractFromArchive(File archive, String suffixToMatch,
+ File outputDirectory) throws IOException, FileNotFoundException {
+ ZipInputStream zip = new ZipInputStream(new BufferedInputStream(new FileInputStream(
+ archive)));
+ ZipEntry entry;
+ while ((entry = zip.getNextEntry()) != null) {
+ if (entry.getName().endsWith(suffixToMatch)) {
+ // Make the original filename part of the temp file name.
+ // TODO(ppi): do we need to sanitize the suffix?
+ String suffix = "-" + new File(entry.getName()).getName();
+ File extractedFile = File.createTempFile(TEMP_FILE_PREFIX, suffix,
+ outputDirectory);
+ writeStreamToFile(zip, extractedFile);
+ zip.close();
+ return extractedFile;
+ }
+ }
+ zip.close();
+ throw new FileNotFoundException();
+ }
+
+ /**
+ * Deletes a file or directory. Directory will be deleted even if not empty.
+ */
+ static void deleteRecursively(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ deleteRecursively(child);
+ }
+ }
+ file.delete();
+ }
+
+ private static void writeStreamToFile(InputStream inputStream, File outputFile)
+ throws IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
+ int read;
+ while ((read = inputStream.read(buffer, 0, BUFFER_SIZE)) > 0) {
+ outputStream.write(buffer, 0, read);
+ }
+ outputStream.close();
+ }
+}
diff --git a/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java b/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java
new file mode 100644
index 0000000..2503eb0
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoMain.java
@@ -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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.content.Context;
+
+import org.chromium.base.JNINamespace;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 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.
+ **/
+ static void ensureInitialized(Context applicationContext, String[] parameters) {
+ if (sInitialized)
+ return;
+ List<String> parametersList = new ArrayList<String>();
+ // Program name.
+ parametersList.add("mojo_shell");
+ if (parameters != null) {
+ parametersList.addAll(Arrays.asList(parameters));
+ }
+ nativeInit(applicationContext, parametersList.toArray(new String[parametersList.size()]));
+ sInitialized = true;
+ }
+
+ /**
+ * Starts the specified application in the specified context.
+ **/
+ 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, String[] parameters);
+
+ private static native void nativeStart(String appUrl);
+}
diff --git a/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java b/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java
new file mode 100644
index 0000000..66e8565
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellActivity.java
@@ -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.
+
+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;
+
+/**
+ * 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);
+
+ 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 static String[] getParametersFromIntent(Intent intent) {
+ return intent != null ? intent.getStringArrayExtra("parameters") : null;
+ }
+
+ private void startWithURL(String url) {
+ // TODO(ppi): Gotcha - the call below will work only once per process lifetime, but the OS
+ // has no obligation to kill the application process between destroying and restarting the
+ // activity. If the application process is kept alive, initialization parameters sent with
+ // the intent will be stale.
+ // TODO(qsr): We should be passing application context here as required by
+ // InitApplicationContext on the native side. Currently we can't, as PlatformViewportAndroid
+ // relies on this being the activity context.
+ MojoMain.ensureInitialized(this, getParametersFromIntent(getIntent()));
+ MojoMain.start(url);
+ Log.i(TAG, "Mojo started: " + url);
+ }
+}
diff --git a/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java b/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java
new file mode 100644
index 0000000..c90fd5e
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo_shell_apk/MojoShellApplication.java
@@ -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.
+
+package org.chromium.mojo_shell_apk;
+
+import android.util.Log;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.base.PathUtils;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.ProcessInitException;
+
+/**
+ * MojoShell implementation of {@link android.app.Application}, managing application-level global
+ * state and initializations.
+ */
+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();
+ clearTemporaryFiles();
+ initializeJavaUtils();
+ initializeNative();
+ }
+
+ /**
+ * Deletes the temporary files and directories created in the previous run of the application.
+ * This is important regardless of cleanups on exit, as the previous run could have crashed.
+ */
+ private void clearTemporaryFiles() {
+ AndroidHandler.clearTemporaryFiles(this);
+ }
+
+ /**
+ * Initializes Java-side utils.
+ */
+ private void initializeJavaUtils() {
+ PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+ }
+
+ /**
+ * Loads the native library.
+ */
+ private void initializeNative() {
+ try {
+ LibraryLoader.ensureInitialized();
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "libmojo_shell initialization failed.", e);
+ System.exit(-1);
+ return;
+ }
+ }
+}
diff --git a/shell/android/bootstrap.cc b/shell/android/bootstrap.cc
new file mode 100644
index 0000000..823258c
--- /dev/null
+++ b/shell/android/bootstrap.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/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "jni/Bootstrap_jni.h"
+#include "shell/android/run_android_application_function.h"
+
+namespace mojo {
+
+void Bootstrap(JNIEnv* env,
+ jobject,
+ jobject j_context,
+ jstring j_native_library_path,
+ jint j_handle,
+ jlong j_run_application_ptr) {
+ base::FilePath app_path(
+ base::android::ConvertJavaStringToUTF8(env, j_native_library_path));
+ RunAndroidApplicationFn run_android_application_fn =
+ reinterpret_cast<RunAndroidApplicationFn>(j_run_application_ptr);
+ run_android_application_fn(env, j_context, app_path, j_handle);
+}
+
+bool RegisterBootstrapJni(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace mojo
+
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ base::android::InitVM(vm);
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ if (!mojo::RegisterBootstrapJni(env))
+ return -1;
+
+ return JNI_VERSION_1_4;
+}
diff --git a/shell/android/library_loader.cc b/shell/android/library_loader.cc
new file mode 100644
index 0000000..8db9250
--- /dev/null
+++ b/shell/android/library_loader.cc
@@ -0,0 +1,51 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#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 "net/android/net_jni_registrar.h"
+#include "services/native_viewport/platform_viewport_android.h"
+#include "shell/android/android_handler.h"
+#include "shell/android/mojo_main.h"
+
+namespace {
+
+base::android::RegistrationMethod kMojoRegisteredMethods[] = {
+ {"AndroidHandler", mojo::RegisterAndroidHandlerJni},
+ {"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;
+
+ if (!mojo::RegisterAndroidHandlerJni(env))
+ return -1;
+
+ return JNI_VERSION_1_4;
+}
diff --git a/shell/android/mojo_main.cc b/shell/android/mojo_main.cc
new file mode 100644
index 0000000..491b59e
--- /dev/null
+++ b/shell/android/mojo_main.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 "shell/android/mojo_main.h"
+
+#include "base/android/command_line_android.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 "shell/context.h"
+#include "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,
+ jobjectArray jparameters) {
+ base::android::ScopedJavaLocalRef<jobject> scoped_context(env, context);
+
+ base::android::InitApplicationContext(env, scoped_context);
+
+ base::android::InitNativeCommandLineFromJavaArray(env, jparameters);
+ 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/shell/android/mojo_main.h b/shell/android/mojo_main.h
new file mode 100644
index 0000000..65fec20
--- /dev/null
+++ b/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 SHELL_ANDROID_MOJO_MAIN_H_
+#define SHELL_ANDROID_MOJO_MAIN_H_
+
+#include <jni.h>
+
+namespace mojo {
+
+bool RegisterMojoMain(JNIEnv* env);
+
+} // namespace mojo
+
+#endif // SHELL_ANDROID_MOJO_MAIN_H_
diff --git a/shell/android/run_android_application_function.h b/shell/android/run_android_application_function.h
new file mode 100644
index 0000000..490d724
--- /dev/null
+++ b/shell/android/run_android_application_function.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 SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_
+#define SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_
+
+#include "base/android/jni_android.h"
+#include "base/files/file_path.h"
+
+// Type of the function that we inject from the main .so of the Mojo shell to
+// the helper libbootstrap.so. This function will set the thunks in the
+// application .so and call into application MojoMain. Injecting the function
+// from the main .so ensures that the thunks are set correctly.
+
+namespace mojo {
+typedef void (*RunAndroidApplicationFn)(JNIEnv* env,
+ jobject j_context,
+ const base::FilePath& app_path,
+ jint j_handle);
+}
+
+#endif // SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_
diff --git a/shell/app_child_process.cc b/shell/app_child_process.cc
new file mode 100644
index 0000000..3a00843
--- /dev/null
+++ b/shell/app_child_process.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 "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/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 "shell/app_child_process.mojom.h"
+#include "shell/dynamic_service_runner.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:
+ ~AppChildControllerImpl() override {
+ 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());
+ }
+
+ void OnConnectionError() override {
+ // TODO(darin): How should we handle a connection error here?
+ }
+
+ // |AppChildController| methods:
+ 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";
+
+ // We intentionally don't unload the native library as its lifetime is the
+ // same as that of the process.
+ DynamicServiceRunner::LoadAndRunService(app_path, service.Pass());
+ }
+
+ 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/shell/app_child_process.h b/shell/app_child_process.h
new file mode 100644
index 0000000..9f0a071
--- /dev/null
+++ b/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 SHELL_APP_CHILD_PROCESS_H_
+#define SHELL_APP_CHILD_PROCESS_H_
+
+#include "base/macros.h"
+#include "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();
+ ~AppChildProcess() override;
+
+ void Main() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AppChildProcess);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APP_CHILD_PROCESS_H_
diff --git a/shell/app_child_process.mojom b/shell/app_child_process.mojom
new file mode 100644
index 0000000..a0d72ca
--- /dev/null
+++ b/shell/app_child_process.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.
+
+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);
+};
diff --git a/shell/app_child_process_host.cc b/shell/app_child_process_host.cc
new file mode 100644
index 0000000..d6cf7f0
--- /dev/null
+++ b/shell/app_child_process_host.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 "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 "shell/context.h"
+#include "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/shell/app_child_process_host.h b/shell/app_child_process_host.h
new file mode 100644
index 0000000..4a00d3f
--- /dev/null
+++ b/shell/app_child_process_host.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 SHELL_APP_CHILD_PROCESS_HOST_H_
+#define SHELL_APP_CHILD_PROCESS_HOST_H_
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/channel_info_forward.h"
+#include "shell/app_child_process.mojom.h"
+#include "shell/child_process_host.h"
+
+namespace mojo {
+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);
+ ~AppChildProcessHost() override;
+
+ AppChildController* controller() { return controller_.get(); }
+
+ private:
+ // |ChildProcessHost::Delegate| methods:
+ void WillStart() override;
+ 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 // SHELL_APP_CHILD_PROCESS_HOST_H_
diff --git a/shell/child_process.cc b/shell/child_process.cc
new file mode 100644
index 0000000..e53ac0a
--- /dev/null
+++ b/shell/child_process.cc
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "shell/app_child_process.h"
+#include "shell/switches.h"
+#include "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/shell/child_process.h b/shell/child_process.h
new file mode 100644
index 0000000..2394d5c
--- /dev/null
+++ b/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 SHELL_CHILD_PROCESS_H_
+#define 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 // SHELL_CHILD_PROCESS_H_
diff --git a/shell/child_process_host.cc b/shell/child_process_host.cc
new file mode 100644
index 0000000..9a68ec4
--- /dev/null
+++ b/shell/child_process_host.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "shell/context.h"
+#include "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/shell/child_process_host.h b/shell/child_process_host.h
new file mode 100644
index 0000000..8ed37e6
--- /dev/null
+++ b/shell/child_process_host.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 SHELL_CHILD_PROCESS_HOST_H_
+#define 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 "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 // SHELL_CHILD_PROCESS_HOST_H_
diff --git a/shell/child_process_host_unittest.cc b/shell/child_process_host_unittest.cc
new file mode 100644
index 0000000..88aaba7
--- /dev/null
+++ b/shell/child_process_host_unittest.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.
+
+// Note: This file also tests child_process.*.
+
+#include "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 "shell/context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+namespace {
+
+class TestChildProcessHostDelegate : public ChildProcessHost::Delegate {
+ public:
+ TestChildProcessHostDelegate() {}
+ ~TestChildProcessHostDelegate() {}
+ void WillStart() override {
+ VLOG(2) << "TestChildProcessHostDelegate::WillStart()";
+ }
+ 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/shell/context.cc b/shell/context.cc
new file mode 100644
index 0000000..7cb4449
--- /dev/null
+++ b/shell/context.cc
@@ -0,0 +1,316 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "mojo/application_manager/application_loader.h"
+#include "mojo/application_manager/application_manager.h"
+#include "mojo/application_manager/background_shell_application_loader.h"
+#include "mojo/common/tracing_impl.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/spy/spy.h"
+#include "services/tracing/tracing.mojom.h"
+#include "shell/dynamic_application_loader.h"
+#include "shell/external_application_listener.h"
+#include "shell/in_process_dynamic_service_runner.h"
+#include "shell/out_of_process_dynamic_service_runner.h"
+#include "shell/switches.h"
+#include "shell/ui_application_loader_android.h"
+#include "url/gurl.h"
+
+#if defined(OS_ANDROID)
+#include "services/gles2/gpu_impl.h"
+#include "services/native_viewport/native_viewport_impl.h"
+#include "shell/android/android_handler_loader.h"
+#include "shell/network_application_loader.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: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("application/pdf", GURL("mojo:pdf_viewer"));
+ loader->RegisterContentHandler("image/png", GURL("mojo:png_viewer"));
+ loader->RegisterContentHandler("text/html", GURL("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:
+ void ConnectToService(const mojo::String& service_name,
+ ScopedMessagePipeHandle client_handle) override {}
+};
+
+bool ConfigureURLMappings(const std::string& mappings,
+ mojo::shell::MojoURLResolver* resolver) {
+ base::StringPairs pairs;
+ if (!base::SplitStringIntoKeyValuePairs(mappings, '=', ',', &pairs))
+ return false;
+ using StringPair = std::pair<std::string, std::string>;
+ for (const StringPair& pair : pairs) {
+ const GURL from(pair.first);
+ const GURL to(pair.second);
+ if (!from.is_valid() || !to.is_valid())
+ return false;
+ resolver->AddCustomMapping(from, to);
+ }
+ return true;
+}
+
+} // namespace
+
+#if defined(OS_ANDROID)
+class Context::NativeViewportApplicationLoader
+ : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<NativeViewport>,
+ public InterfaceFactory<Gpu> {
+ public:
+ NativeViewportApplicationLoader() : gpu_state_(new gles2::GpuImpl::State) {}
+ virtual ~NativeViewportApplicationLoader() {}
+
+ private:
+ // ApplicationLoader implementation.
+ virtual void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) override {
+ DCHECK(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 {
+ new gles2::GpuImpl(request.Pass(), gpu_state_);
+ }
+
+ scoped_refptr<gles2::GpuImpl::State> gpu_state_;
+ scoped_ptr<ApplicationImpl> app_;
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportApplicationLoader);
+};
+#endif
+
+Context::Context() : application_manager_(this) {
+ DCHECK(!base::MessageLoop::current());
+}
+
+Context::~Context() {
+ DCHECK(!base::MessageLoop::current());
+}
+
+void Context::EnsureEmbedderIsInitialized() {
+ setup.Get();
+}
+
+bool Context::Init() {
+ EnsureEmbedderIsInitialized();
+ 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_)));
+ }
+ if (command_line->HasSwitch(switches::kOrigin)) {
+ mojo_url_resolver()->SetBaseURL(
+ GURL(command_line->GetSwitchValueASCII(switches::kOrigin)));
+ }
+ if (command_line->HasSwitch(switches::kURLMappings) &&
+ !ConfigureURLMappings(
+ command_line->GetSwitchValueASCII(switches::kURLMappings),
+ mojo_url_resolver())) {
+ return false;
+ }
+
+ 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:native_viewport_service"));
+#endif
+
+ if (command_line->HasSwitch(switches::kSpy)) {
+ spy_.reset(
+ new mojo::Spy(&application_manager_,
+ command_line->GetSwitchValueASCII(switches::kSpy)));
+ // TODO(cpu): the spy can snoop, but can't tell anybody until
+ // the Spy::WebSocketDelegate is implemented. In the original repo this
+ // was implemented by src\mojo\spy\websocket_server.h and .cc.
+ }
+
+#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.Pass(),
+ GURL("mojo:network_service"));
+ }
+ {
+ scoped_ptr<BackgroundShellApplicationLoader> loader(
+ new BackgroundShellApplicationLoader(
+ scoped_ptr<ApplicationLoader>(new AndroidHandlerLoader()),
+ "android_handler", base::MessageLoop::TYPE_DEFAULT));
+ application_manager_.SetLoaderForURL(loader.Pass(),
+ GURL("mojo:android_handler"));
+ }
+#endif
+
+ tracing::TraceDataCollectorPtr trace_data_collector_ptr;
+ application_manager_.ConnectToService(GURL("mojo:tracing"),
+ &trace_data_collector_ptr);
+ TracingImpl::Create(trace_data_collector_ptr.Pass());
+
+ if (listener_)
+ listener_->WaitForListening();
+
+ return true;
+}
+
+void Context::OnApplicationError(const GURL& url) {
+ if (app_urls_.find(url) != app_urls_.end()) {
+ app_urls_.erase(url);
+ if (app_urls_.empty() && base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+ }
+}
+
+GURL Context::ResolveURL(const GURL& url) {
+ return mojo_url_resolver_.Resolve(url);
+}
+
+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/shell/context.h b/shell/context.h
new file mode 100644
index 0000000..71ef68b
--- /dev/null
+++ b/shell/context.h
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_CONTEXT_H_
+#define SHELL_CONTEXT_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "mojo/application_manager/application_manager.h"
+#include "shell/mojo_url_resolver.h"
+#include "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();
+ ~Context() override;
+
+ static void EnsureEmbedderIsInitialized();
+ bool Init();
+
+ 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;
+
+ // ApplicationManager::Delegate override.
+ void OnApplicationError(const GURL& url) override;
+ GURL ResolveURL(const GURL& url) override;
+
+ 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 // SHELL_CONTEXT_H_
diff --git a/shell/data_pipe_peek.cc b/shell/data_pipe_peek.cc
new file mode 100644
index 0000000..82e72a9
--- /dev/null
+++ b/shell/data_pipe_peek.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 "shell/data_pipe_peek.h"
+
+#include "base/bind.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+// Sleep for as long as max_sleep_micros if the deadline hasn't been reached
+// and the number of bytes read is still increasing. Returns true if sleep
+// was actually called.
+//
+// This class is a substitute for being able to wait until N bytes are available
+// from a data pipe. The MaybeSleep method is called when num_bytes_read are
+// available but more are needed by the Peek operation. If a second
+// Peek operation finds the same number of bytes after sleeping we assume
+// that there's no point in trying again.
+// TODO(hansmuller): this heuristic is weak. crbug.com/429377
+class PeekSleeper {
+ public:
+ explicit PeekSleeper(MojoTimeTicks deadline)
+ : deadline_(deadline),
+ kMaxSleepMicros_(1000 * 10), // 10ms
+ last_number_bytes_read_(0) {}
+
+ bool MaybeSleep(uint32 num_bytes_read) {
+ if (num_bytes_read > 0 && last_number_bytes_read_ >= num_bytes_read)
+ return false;
+ last_number_bytes_read_ = num_bytes_read;
+
+ MojoTimeTicks now(GetTimeTicksNow());
+ if (now > deadline_)
+ return false;
+
+ MojoTimeTicks sleep_time =
+ (deadline_ == 0)
+ ? kMaxSleepMicros_
+ : std::min<int64>(deadline_ - now, PeekSleeper::kMaxSleepMicros_);
+ base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(sleep_time));
+ return true;
+ }
+
+ private:
+ const MojoTimeTicks deadline_; // 0 => MOJO_DEADLINE_INDEFINITE
+ const MojoTimeTicks kMaxSleepMicros_;
+ uint32 last_number_bytes_read_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PeekSleeper);
+};
+
+enum PeekStatus { kSuccess, kFail, kKeepReading };
+typedef const base::Callback<PeekStatus(const void*, uint32_t, std::string*)>&
+ PeekFunc;
+
+// When data is available on source, call peek_func and then either return true
+// and value, continue waiting for enough data to satisfy peek_func, or fail
+// and return false. Fail if the timeout is exceeded.
+// This function is not guaranteed to work correctly if applied to a data pipe
+// that's already been read from.
+bool BlockingPeekHelper(DataPipeConsumerHandle source,
+ std::string* value,
+ MojoDeadline timeout,
+ PeekFunc peek_func) {
+ DCHECK(value);
+ value->clear();
+
+ MojoTimeTicks deadline =
+ (timeout == MOJO_DEADLINE_INDEFINITE)
+ ? 0
+ : 1 + GetTimeTicksNow() + static_cast<MojoTimeTicks>(timeout);
+ PeekSleeper sleeper(deadline);
+
+ MojoResult result;
+ do {
+ const void* buffer;
+ uint32_t num_bytes;
+ result =
+ BeginReadDataRaw(source, &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+
+ if (result == MOJO_RESULT_OK) {
+ PeekStatus status = peek_func.Run(buffer, num_bytes, value);
+ CHECK_EQ(EndReadDataRaw(source, 0), MOJO_RESULT_OK);
+ switch (status) {
+ case PeekStatus::kSuccess:
+ return true;
+ case PeekStatus::kFail:
+ return false;
+ case PeekStatus::kKeepReading:
+ break;
+ }
+ if (!sleeper.MaybeSleep(num_bytes))
+ return false;
+ } else if (result == MOJO_RESULT_SHOULD_WAIT) {
+ MojoTimeTicks now(GetTimeTicksNow());
+ if (timeout == MOJO_DEADLINE_INDEFINITE || now < deadline)
+ result = Wait(source, MOJO_HANDLE_SIGNAL_READABLE, deadline - now);
+ }
+ } while (result == MOJO_RESULT_OK);
+
+ return false;
+}
+
+PeekStatus PeekLine(size_t max_line_length,
+ const void* buffer,
+ uint32 buffer_num_bytes,
+ std::string* line) {
+ const char* p = static_cast<const char*>(buffer);
+ size_t max_p_index = std::min<size_t>(buffer_num_bytes, max_line_length);
+ for (size_t i = 0; i < max_p_index; i++) {
+ if (p[i] == '\n') {
+ *line = std::string(p, i + 1); // Include the trailing newline.
+ return PeekStatus::kSuccess;
+ }
+ }
+ return (buffer_num_bytes >= max_line_length) ? PeekStatus::kFail
+ : PeekStatus::kKeepReading;
+}
+
+PeekStatus PeekNBytes(size_t bytes_length,
+ const void* buffer,
+ uint32 buffer_num_bytes,
+ std::string* bytes) {
+ if (buffer_num_bytes >= bytes_length) {
+ const char* p = static_cast<const char*>(buffer);
+ *bytes = std::string(p, bytes_length);
+ return PeekStatus::kSuccess;
+ }
+ return PeekStatus::kKeepReading;
+}
+
+} // namespace
+
+bool BlockingPeekNBytes(DataPipeConsumerHandle source,
+ std::string* bytes,
+ size_t bytes_length,
+ MojoDeadline timeout) {
+ PeekFunc peek_nbytes = base::Bind(PeekNBytes, bytes_length);
+ return BlockingPeekHelper(source, bytes, timeout, peek_nbytes);
+}
+
+bool BlockingPeekLine(DataPipeConsumerHandle source,
+ std::string* line,
+ size_t max_line_length,
+ MojoDeadline timeout) {
+ PeekFunc peek_line = base::Bind(PeekLine, max_line_length);
+ return BlockingPeekHelper(source, line, timeout, peek_line);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/data_pipe_peek.h b/shell/data_pipe_peek.h
new file mode 100644
index 0000000..adaabce
--- /dev/null
+++ b/shell/data_pipe_peek.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 SHELL_DATA_PIPE_SEEK_H_
+#define SHELL_DATA_PIPE_SEEK_H_
+
+#include <string>
+
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace shell {
+
+// The Peek functions are only intended to be used by the
+// DyanmicApplicationLoader class for discovering the type of a
+// URL response. They are a stopgap to be replaced by real support
+// in the DataPipe classes.
+
+// Return true and the first newline terminated line from source. Return false
+// if more than max_line_length bytes are scanned without seeing a newline, or
+// if the timeout is exceeded.
+bool BlockingPeekLine(DataPipeConsumerHandle source,
+ std::string* line,
+ size_t max_line_length,
+ MojoDeadline timeout);
+
+// Return true and the first bytes_length bytes from source. Return false
+// if the timeout is exceeded.
+bool BlockingPeekNBytes(DataPipeConsumerHandle source,
+ std::string* bytes,
+ size_t bytes_length,
+ MojoDeadline timeout);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DATA_PIPE_SEEK_H_
diff --git a/shell/data_pipe_peek_unittest.cc b/shell/data_pipe_peek_unittest.cc
new file mode 100644
index 0000000..ec96490
--- /dev/null
+++ b/shell/data_pipe_peek_unittest.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 "shell/data_pipe_peek.h"
+
+#include "shell/context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+TEST(DataPipePeek, PeekNBytes) {
+ Context::EnsureEmbedderIsInitialized();
+
+ DataPipe data_pipe;
+ DataPipeConsumerHandle consumer(data_pipe.consumer_handle.get());
+ DataPipeProducerHandle producer(data_pipe.producer_handle.get());
+
+ // Inialize the pipe with 4 bytes.
+
+ const char* s4 = "1234";
+ uint32_t num_bytes4 = 4;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteDataRaw(producer, s4, &num_bytes4, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(4u, num_bytes4);
+
+ // We're not consuming data, so peeking for 4 bytes should always succeed.
+
+ std::string bytes;
+ MojoDeadline timeout = 0;
+ EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes4, timeout));
+ EXPECT_EQ(bytes, std::string(s4));
+
+ timeout = 1000; // 1ms
+ EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes4, timeout));
+ EXPECT_EQ(bytes, std::string(s4));
+
+ timeout = MOJO_DEADLINE_INDEFINITE;
+ EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes4, timeout));
+ EXPECT_EQ(bytes, std::string(s4));
+
+ // Peeking for 5 bytes should fail, until another byte is written.
+
+ uint32_t bytes1 = 1;
+ uint32_t num_bytes5 = 5;
+ const char* s1 = "5";
+ const char* s5 = "12345";
+
+ timeout = 0;
+ EXPECT_FALSE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout));
+
+ timeout = 500; // Should cause peek to timeout after about 0.5ms.
+ EXPECT_FALSE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteDataRaw(producer, s1, &bytes1, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, bytes1);
+
+ EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout));
+ EXPECT_EQ(bytes, std::string(s5));
+
+ // If the consumer side of the pipe is closed, peek should fail.
+
+ data_pipe.consumer_handle.reset();
+ timeout = 0;
+ EXPECT_FALSE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout));
+}
+
+TEST(DataPipePeek, PeekLine) {
+ Context::EnsureEmbedderIsInitialized();
+
+ DataPipe data_pipe;
+ DataPipeConsumerHandle consumer(data_pipe.consumer_handle.get());
+ DataPipeProducerHandle producer(data_pipe.producer_handle.get());
+
+ // Inialize the pipe with 4 bytes and no newline.
+
+ const char* s4 = "1234";
+ uint32_t num_bytes4 = 4;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteDataRaw(producer, s4, &num_bytes4, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(4u, num_bytes4);
+
+ // Peeking for a line should fail.
+
+ std::string str;
+ size_t max_str_length = 5;
+ MojoDeadline timeout = 0;
+ EXPECT_FALSE(BlockingPeekLine(consumer, &str, max_str_length, timeout));
+
+ // Writing a newline should cause PeekLine to succeed.
+
+ uint32_t bytes1 = 1;
+ const char* s1 = "\n";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WriteDataRaw(producer, s1, &bytes1, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, bytes1);
+
+ EXPECT_TRUE(BlockingPeekLine(consumer, &str, max_str_length, timeout));
+ EXPECT_EQ(str, std::string(s4) + "\n");
+
+ // If the max_line_length parameter is less than the length of the
+ // newline terminated string, then peek should fail.
+
+ max_str_length = 3;
+ EXPECT_FALSE(BlockingPeekLine(consumer, &str, max_str_length, timeout));
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/shell/desktop/mojo_main.cc b/shell/desktop/mojo_main.cc
new file mode 100644
index 0000000..6d3e1ea
--- /dev/null
+++ b/shell/desktop/mojo_main.cc
@@ -0,0 +1,173 @@
+// Copyright 2013 The Chromium Authors. 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 <iostream>
+
+#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 "shell/child_process.h"
+#include "shell/context.h"
+#include "shell/init.h"
+#include "shell/mojo_url_resolver.h"
+#include "shell/switches.h"
+
+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
+
+bool IsEmpty(const std::string& s) {
+ return s.empty();
+}
+
+// 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 std::string& app_url_and_args,
+ mojo::shell::Context* context) {
+ // SplitString() returns empty strings for extra delimeter characters (' ').
+ std::vector<std::string> argv;
+ base::SplitString(app_url_and_args, ' ', &argv);
+ argv.erase(std::remove_if(argv.begin(), argv.end(), IsEmpty), argv.end());
+
+ if (argv.empty())
+ return GURL::EmptyGURL();
+ GURL app_url(argv[0]);
+ if (!app_url.is_valid()) {
+ LOG(ERROR) << "Error: invalid URL: " << argv[0];
+ return app_url;
+ }
+ 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()) {
+ std::string arg2;
+#if defined(OS_WIN)
+ arg2 = base::UTF16ToUTF8(arg);
+#else
+ arg2 = arg;
+#endif
+ GURL url = GetAppURLAndSetArgs(arg2, context);
+ if (!url.is_valid())
+ return;
+ context->Run(GetAppURLAndSetArgs(arg2, context));
+ }
+}
+
+void Usage() {
+ std::cerr << "Launch Mojo applications.\n";
+ std::cerr
+ << "Usage: mojo_shell"
+ << " [--" << switches::kArgsFor << "=<mojo-app>]"
+ << " [--" << switches::kContentHandlers << "=<handlers>]"
+ << " [--" << switches::kEnableExternalApplications << "]"
+ << " [--" << switches::kDisableCache << "]"
+ << " [--" << switches::kEnableMultiprocess << "]"
+ << " [--" << switches::kOrigin << "=<url-lib-path>]"
+ << " [--" << switches::kURLMappings << "=from1=to1,from2=to2]"
+ << " <mojo-app> ...\n\n"
+ << "A <mojo-app> is a Mojo URL or a Mojo URL and arguments within "
+ << "quotes.\n"
+ << "Example: mojo_shell \"mojo:js_standalone test.js\".\n"
+ << "<url-lib-path> is searched for shared libraries named by mojo URLs.\n"
+ << "The value of <handlers> is a comma separated list like:\n"
+ << "text/html,mojo:html_viewer,"
+ << "application/javascript,mojo:js_content_handler\n";
+}
+
+bool IsArgsFor(const std::string& arg, std::string* value) {
+ const std::string kArgsForSwitches[] = {
+ "-" + std::string(switches::kArgsFor),
+ "--" + std::string(switches::kArgsFor),
+ };
+ for (size_t i = 0; i < arraysize(kArgsForSwitches); i++) {
+ std::string argsfor_switch(kArgsForSwitches[i]);
+ if (arg.compare(0, argsfor_switch.size(), argsfor_switch) == 0) {
+ *value = arg.substr(argsfor_switch.size() + 1, std::string::npos);
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit;
+ base::CommandLine::Init(argc, argv);
+
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+
+ const std::set<std::string> all_switches = switches::GetAllSwitches();
+ const base::CommandLine::SwitchMap switches = command_line.GetSwitches();
+ bool found_unknown_switch = false;
+ for (const auto& s : switches) {
+ if (all_switches.find(s.first) == all_switches.end()) {
+ std::cerr << "unknown switch: " << s.first << std::endl;
+ found_unknown_switch = true;
+ }
+ }
+
+ if (found_unknown_switch ||
+ (!command_line.HasSwitch(switches::kEnableExternalApplications) &&
+ (command_line.HasSwitch(switches::kHelp) ||
+ command_line.GetArgs().empty()))) {
+ Usage();
+ return 0;
+ }
+
+#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 {
+ // 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;
+ if (!shell_context.Init()) {
+ Usage();
+ return 0;
+ }
+
+ // The mojo_shell --args-for command-line switch is handled specially
+ // because it can appear more than once. The base::CommandLine class
+ // collapses multiple occurrences of the same switch.
+ for (int i = 1; i < argc; i++) {
+ std::string args_for_value;
+ if (IsArgsFor(argv[i], &args_for_value))
+ GetAppURLAndSetArgs(args_for_value, &shell_context);
+ }
+
+ message_loop.PostTask(FROM_HERE, base::Bind(RunApps, &shell_context));
+ message_loop.Run();
+ }
+ }
+ return 0;
+}
diff --git a/shell/domain_socket/BUILD.gn b/shell/domain_socket/BUILD.gn
new file mode 100644
index 0000000..601e612
--- /dev/null
+++ b/shell/domain_socket/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.
+
+source_set("domain_socket") {
+ sources = [
+ "completion_callback.h",
+ "net_errors.cc",
+ "net_errors.h",
+ "net_error_list.h",
+ "socket_descriptor.h",
+ "socket_libevent.cc",
+ "socket_libevent.h",
+ "unix_domain_client_socket_posix.cc",
+ "unix_domain_client_socket_posix.h",
+ "unix_domain_server_socket_posix.cc",
+ "unix_domain_server_socket_posix.h",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+source_set("tests") {
+ testonly = true
+
+ sources = [
+ "test_completion_callback.cc",
+ "test_completion_callback.h",
+ "unix_domain_client_socket_posix_unittest.cc",
+ "unix_domain_server_socket_posix_unittest.cc",
+ ]
+
+ deps = [
+ ":domain_socket",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/shell/domain_socket/completion_callback.h b/shell/domain_socket/completion_callback.h
new file mode 100644
index 0000000..453e48d
--- /dev/null
+++ b/shell/domain_socket/completion_callback.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef SHELL_DOMAIN_SOCKET_COMPLETION_CALLBACK_H__
+#define SHELL_DOMAIN_SOCKET_COMPLETION_CALLBACK_H__
+
+#include "base/callback.h"
+#include "base/cancelable_callback.h"
+
+namespace mojo {
+namespace shell {
+
+// A callback specialization that takes a single int parameter. Usually this is
+// used to report a byte count or network error code.
+typedef base::Callback<void(int)> CompletionCallback;
+
+// 64bit version of callback specialization that takes a single int64 parameter.
+// Usually this is used to report a file offset, size or network error code.
+typedef base::Callback<void(int64)> Int64CompletionCallback;
+
+typedef base::CancelableCallback<void(int)> CancelableCompletionCallback;
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DOMAIN_SOCKET_COMPLETION_CALLBACK_H__
diff --git a/shell/domain_socket/net_error_list.h b/shell/domain_socket/net_error_list.h
new file mode 100644
index 0000000..bd6ccb3
--- /dev/null
+++ b/shell/domain_socket/net_error_list.h
@@ -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.
+
+// This file intentionally does not have header guards, it's included
+// inside a macro to generate enum values.
+
+// This file contains the list of network errors.
+
+//
+// Ranges:
+// 0- 99 System related errors
+// 100-199 Connection related errors
+
+// An asynchronous IO operation is not yet complete. This usually does not
+// indicate a fatal error. Typically this error will be generated as a
+// notification to wait for some external notification that the IO operation
+// finally completed.
+NET_ERROR(IO_PENDING, -1)
+
+// A generic failure occurred.
+NET_ERROR(FAILED, -2)
+
+// An operation was aborted (due to user action).
+NET_ERROR(ABORTED, -3)
+
+// An argument to the function is incorrect.
+NET_ERROR(INVALID_ARGUMENT, -4)
+
+// The handle or file descriptor is invalid.
+NET_ERROR(INVALID_HANDLE, -5)
+
+// The file or directory cannot be found.
+NET_ERROR(FILE_NOT_FOUND, -6)
+
+// An operation timed out.
+NET_ERROR(TIMED_OUT, -7)
+
+// The file is too large.
+NET_ERROR(FILE_TOO_BIG, -8)
+
+// An unexpected error. This may be caused by a programming mistake or an
+// invalid assumption.
+NET_ERROR(UNEXPECTED, -9)
+
+// Permission to access a resource, other than the network, was denied.
+NET_ERROR(ACCESS_DENIED, -10)
+
+// The operation failed because of unimplemented functionality.
+NET_ERROR(NOT_IMPLEMENTED, -11)
+
+// There were not enough resources to complete the operation.
+NET_ERROR(INSUFFICIENT_RESOURCES, -12)
+
+// Memory allocation failed.
+NET_ERROR(OUT_OF_MEMORY, -13)
+
+// The file upload failed because the file's modification time was different
+// from the expectation.
+NET_ERROR(UPLOAD_FILE_CHANGED, -14)
+
+// The socket is not connected.
+NET_ERROR(SOCKET_NOT_CONNECTED, -15)
+
+// The file already exists.
+NET_ERROR(FILE_EXISTS, -16)
+
+// The path or file name is too long.
+NET_ERROR(FILE_PATH_TOO_LONG, -17)
+
+// Not enough room left on the disk.
+NET_ERROR(FILE_NO_SPACE, -18)
+
+// The file has a virus.
+NET_ERROR(FILE_VIRUS_INFECTED, -19)
+
+// The client chose to block the request.
+NET_ERROR(BLOCKED_BY_CLIENT, -20)
+
+// The network changed.
+NET_ERROR(NETWORK_CHANGED, -21)
+
+// The request was blocked by the URL blacklist configured by the domain
+// administrator.
+NET_ERROR(BLOCKED_BY_ADMINISTRATOR, -22)
+
+// The socket is already connected.
+NET_ERROR(SOCKET_IS_CONNECTED, -23)
+
+// The request was blocked because the forced reenrollment check is still
+// pending. This error can only occur on ChromeOS.
+// The error can be emitted by code in chrome/browser/policy/policy_helpers.cc.
+NET_ERROR(BLOCKED_ENROLLMENT_CHECK_PENDING, -24)
+
+// The upload failed because the upload stream needed to be re-read, due to a
+// retry or a redirect, but the upload stream doesn't support that operation.
+NET_ERROR(UPLOAD_STREAM_REWIND_NOT_SUPPORTED, -25)
+
+// A connection was closed (corresponding to a TCP FIN).
+NET_ERROR(CONNECTION_CLOSED, -100)
+
+// A connection was reset (corresponding to a TCP RST).
+NET_ERROR(CONNECTION_RESET, -101)
+
+// A connection attempt was refused.
+NET_ERROR(CONNECTION_REFUSED, -102)
+
+// A connection timed out as a result of not receiving an ACK for data sent.
+// This can include a FIN packet that did not get ACK'd.
+NET_ERROR(CONNECTION_ABORTED, -103)
+
+// A connection attempt failed.
+NET_ERROR(CONNECTION_FAILED, -104)
+
+// The host name could not be resolved.
+NET_ERROR(NAME_NOT_RESOLVED, -105)
+
+// The Internet connection has been lost.
+NET_ERROR(INTERNET_DISCONNECTED, -106)
+
+// An SSL protocol error occurred.
+NET_ERROR(SSL_PROTOCOL_ERROR, -107)
+
+// The IP address or port number is invalid (e.g., cannot connect to the IP
+// address 0 or the port 0).
+NET_ERROR(ADDRESS_INVALID, -108)
+
+// The IP address is unreachable. This usually means that there is no route to
+// the specified host or network.
+NET_ERROR(ADDRESS_UNREACHABLE, -109)
+
+// The server requested a client certificate for SSL client authentication.
+NET_ERROR(SSL_CLIENT_AUTH_CERT_NEEDED, -110)
+
+// A tunnel connection through the proxy could not be established.
+NET_ERROR(TUNNEL_CONNECTION_FAILED, -111)
+
+// No SSL protocol versions are enabled.
+NET_ERROR(NO_SSL_VERSIONS_ENABLED, -112)
+
+// The client and server don't support a common SSL protocol version or
+// cipher suite.
+NET_ERROR(SSL_VERSION_OR_CIPHER_MISMATCH, -113)
+
+// The server requested a renegotiation (rehandshake).
+NET_ERROR(SSL_RENEGOTIATION_REQUESTED, -114)
+
+// The proxy requested authentication (for tunnel establishment) with an
+// unsupported method.
+NET_ERROR(PROXY_AUTH_UNSUPPORTED, -115)
+
+// During SSL renegotiation (rehandshake), the server sent a certificate with
+// an error.
+//
+// Note: this error is not in the -2xx range so that it won't be handled as a
+// certificate error.
+NET_ERROR(CERT_ERROR_IN_SSL_RENEGOTIATION, -116)
+
+// The SSL handshake failed because of a bad or missing client certificate.
+NET_ERROR(BAD_SSL_CLIENT_AUTH_CERT, -117)
+
+// A connection attempt timed out.
+NET_ERROR(CONNECTION_TIMED_OUT, -118)
+
+// There are too many pending DNS resolves, so a request in the queue was
+// aborted.
+NET_ERROR(HOST_RESOLVER_QUEUE_TOO_LARGE, -119)
+
+// Failed establishing a connection to the SOCKS proxy server for a target host.
+NET_ERROR(SOCKS_CONNECTION_FAILED, -120)
+
+// The SOCKS proxy server failed establishing connection to the target host
+// because that host is unreachable.
+NET_ERROR(SOCKS_CONNECTION_HOST_UNREACHABLE, -121)
+
+// The request to negotiate an alternate protocol failed.
+NET_ERROR(NPN_NEGOTIATION_FAILED, -122)
+
+// The peer sent an SSL no_renegotiation alert message.
+NET_ERROR(SSL_NO_RENEGOTIATION, -123)
+
+// Winsock sometimes reports more data written than passed. This is probably
+// due to a broken LSP.
+NET_ERROR(WINSOCK_UNEXPECTED_WRITTEN_BYTES, -124)
+
+// An SSL peer sent us a fatal decompression_failure alert. This typically
+// occurs when a peer selects DEFLATE compression in the mistaken belief that
+// it supports it.
+NET_ERROR(SSL_DECOMPRESSION_FAILURE_ALERT, -125)
+
+// An SSL peer sent us a fatal bad_record_mac alert. This has been observed
+// from servers with buggy DEFLATE support.
+NET_ERROR(SSL_BAD_RECORD_MAC_ALERT, -126)
+
+// The proxy requested authentication (for tunnel establishment).
+NET_ERROR(PROXY_AUTH_REQUESTED, -127)
+
+// A known TLS strict server didn't offer the renegotiation extension.
+NET_ERROR(SSL_UNSAFE_NEGOTIATION, -128)
+
+// The SSL server attempted to use a weak ephemeral Diffie-Hellman key.
+NET_ERROR(SSL_WEAK_SERVER_EPHEMERAL_DH_KEY, -129)
+
+// Could not create a connection to the proxy server. An error occurred
+// either in resolving its name, or in connecting a socket to it.
+// Note that this does NOT include failures during the actual "CONNECT" method
+// of an HTTP proxy.
+NET_ERROR(PROXY_CONNECTION_FAILED, -130)
+
+// A mandatory proxy configuration could not be used. Currently this means
+// that a mandatory PAC script could not be fetched, parsed or executed.
+NET_ERROR(MANDATORY_PROXY_CONFIGURATION_FAILED, -131)
+
+// -132 was formerly ERR_ESET_ANTI_VIRUS_SSL_INTERCEPTION
+
+// We've hit the max socket limit for the socket pool while preconnecting. We
+// don't bother trying to preconnect more sockets.
+NET_ERROR(PRECONNECT_MAX_SOCKET_LIMIT, -133)
+
+// The permission to use the SSL client certificate's private key was denied.
+NET_ERROR(SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED, -134)
+
+// The SSL client certificate has no private key.
+NET_ERROR(SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY, -135)
+
+// The certificate presented by the HTTPS Proxy was invalid.
+NET_ERROR(PROXY_CERTIFICATE_INVALID, -136)
+
+// An error occurred when trying to do a name resolution (DNS).
+NET_ERROR(NAME_RESOLUTION_FAILED, -137)
+
+// Permission to access the network was denied. This is used to distinguish
+// errors that were most likely caused by a firewall from other access denied
+// errors. See also ERR_ACCESS_DENIED.
+NET_ERROR(NETWORK_ACCESS_DENIED, -138)
+
+// The request throttler module cancelled this request to avoid DDOS.
+NET_ERROR(TEMPORARILY_THROTTLED, -139)
+
+// A request to create an SSL tunnel connection through the HTTPS proxy
+// received a non-200 (OK) and non-407 (Proxy Auth) response. The response
+// body might include a description of why the request failed.
+NET_ERROR(HTTPS_PROXY_TUNNEL_RESPONSE, -140)
+
+// We were unable to sign the CertificateVerify data of an SSL client auth
+// handshake with the client certificate's private key.
+//
+// Possible causes for this include the user implicitly or explicitly
+// denying access to the private key, the private key may not be valid for
+// signing, the key may be relying on a cached handle which is no longer
+// valid, or the CSP won't allow arbitrary data to be signed.
+NET_ERROR(SSL_CLIENT_AUTH_SIGNATURE_FAILED, -141)
+
+// The message was too large for the transport. (for example a UDP message
+// which exceeds size threshold).
+NET_ERROR(MSG_TOO_BIG, -142)
+
+// A SPDY session already exists, and should be used instead of this connection.
+NET_ERROR(SPDY_SESSION_ALREADY_EXISTS, -143)
+
+// Error -144 was removed (LIMIT_VIOLATION).
+
+// Websocket protocol error. Indicates that we are terminating the connection
+// due to a malformed frame or other protocol violation.
+NET_ERROR(WS_PROTOCOL_ERROR, -145)
+
+// Connection was aborted for switching to another ptotocol.
+// WebSocket abort SocketStream connection when alternate protocol is found.
+NET_ERROR(PROTOCOL_SWITCHED, -146)
+
+// Returned when attempting to bind an address that is already in use.
+NET_ERROR(ADDRESS_IN_USE, -147)
+
+// An operation failed because the SSL handshake has not completed.
+NET_ERROR(SSL_HANDSHAKE_NOT_COMPLETED, -148)
+
+// SSL peer's public key is invalid.
+NET_ERROR(SSL_BAD_PEER_PUBLIC_KEY, -149)
+
+// The certificate didn't match the built-in public key pins for the host name.
+// The pins are set in net/http/transport_security_state.cc and require that
+// one of a set of public keys exist on the path from the leaf to the root.
+NET_ERROR(SSL_PINNED_KEY_NOT_IN_CERT_CHAIN, -150)
+
+// Server request for client certificate did not contain any types we support.
+NET_ERROR(CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, -151)
+
+// Server requested one type of cert, then requested a different type while the
+// first was still being generated.
+NET_ERROR(ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH, -152)
+
+// An SSL peer sent us a fatal decrypt_error alert. This typically occurs when
+// a peer could not correctly verify a signature (in CertificateVerify or
+// ServerKeyExchange) or validate a Finished message.
+NET_ERROR(SSL_DECRYPT_ERROR_ALERT, -153)
+
+// There are too many pending WebSocketJob instances, so the new job was not
+// pushed to the queue.
+NET_ERROR(WS_THROTTLE_QUEUE_TOO_LARGE, -154)
+
+// There are too many active SocketStream instances, so the new connect request
+// was rejected.
+NET_ERROR(TOO_MANY_SOCKET_STREAMS, -155)
+
+// The SSL server certificate changed in a renegotiation.
+NET_ERROR(SSL_SERVER_CERT_CHANGED, -156)
+
+// The SSL server indicated that an unnecessary TLS version fallback was
+// performed.
+NET_ERROR(SSL_INAPPROPRIATE_FALLBACK, -157)
+
+// Certificate Transparency: All Signed Certificate Timestamps failed to verify.
+NET_ERROR(CT_NO_SCTS_VERIFIED_OK, -158)
+
+// The SSL server sent us a fatal unrecognized_name alert.
+NET_ERROR(SSL_UNRECOGNIZED_NAME_ALERT, -159)
+
+// Failed to set the socket's receive buffer size as requested.
+NET_ERROR(SOCKET_SET_RECEIVE_BUFFER_SIZE_ERROR, -160)
+
+// Failed to set the socket's send buffer size as requested.
+NET_ERROR(SOCKET_SET_SEND_BUFFER_SIZE_ERROR, -161)
+
+// Failed to set the socket's receive buffer size as requested, despite success
+// return code from setsockopt.
+NET_ERROR(SOCKET_RECEIVE_BUFFER_SIZE_UNCHANGEABLE, -162)
+
+// Failed to set the socket's send buffer size as requested, despite success
+// return code from setsockopt.
+NET_ERROR(SOCKET_SEND_BUFFER_SIZE_UNCHANGEABLE, -163)
+
+// Failed to import a client certificate from the platform store into the SSL
+// library.
+NET_ERROR(SSL_CLIENT_AUTH_CERT_BAD_FORMAT, -164)
+
+// The SSL server requires falling back to a version older than the configured
+// minimum fallback version, and thus fallback failed.
+NET_ERROR(SSL_FALLBACK_BEYOND_MINIMUM_VERSION, -165)
diff --git a/shell/domain_socket/net_errors.cc b/shell/domain_socket/net_errors.cc
new file mode 100644
index 0000000..2216579
--- /dev/null
+++ b/shell/domain_socket/net_errors.cc
@@ -0,0 +1,168 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shell/domain_socket/net_errors.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#if defined(OS_POSIX)
+#include <unistd.h>
+#endif
+#if defined(OS_WIN)
+#include <winsock2.h>
+#define EDQUOT WSAEDQUOT
+#define EHOSTDOWN WSAEHOSTDOWN
+#define EUSERS WSAEUSERS
+#endif
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file.h"
+#include "base/logging.h"
+
+namespace mojo {
+namespace shell {
+namespace net {
+
+std::string ErrorToString(int error) {
+ if (error == 0)
+ return "OK";
+
+ const char* error_string;
+ switch (error) {
+#define NET_ERROR(label, value) \
+ case ERR_##label: \
+ error_string = #label; \
+ break;
+#include "shell/domain_socket/net_error_list.h"
+#undef NET_ERROR
+ default:
+ NOTREACHED();
+ error_string = "<unknown>";
+ }
+ return std::string("ERR_") + error_string;
+}
+
+Error FileErrorToNetError(base::File::Error file_error) {
+ switch (file_error) {
+ case base::File::FILE_OK:
+ return net::OK;
+ case base::File::FILE_ERROR_ACCESS_DENIED:
+ return net::ERR_ACCESS_DENIED;
+ case base::File::FILE_ERROR_NOT_FOUND:
+ return net::ERR_FILE_NOT_FOUND;
+ default:
+ return net::ERR_FAILED;
+ }
+}
+
+Error MapSystemError(int os_error) {
+ if (os_error != 0)
+ DVLOG(2) << "Error " << os_error;
+
+ // There are numerous posix error codes, but these are the ones we thus far
+ // find interesting.
+ switch (os_error) {
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return ERR_IO_PENDING;
+ case EACCES:
+ return ERR_ACCESS_DENIED;
+ case ENETDOWN:
+ return ERR_INTERNET_DISCONNECTED;
+ case ETIMEDOUT:
+ return ERR_TIMED_OUT;
+ case ECONNRESET:
+ case ENETRESET: // Related to keep-alive.
+ case EPIPE:
+ return ERR_CONNECTION_RESET;
+ case ECONNABORTED:
+ return ERR_CONNECTION_ABORTED;
+ case ECONNREFUSED:
+ return ERR_CONNECTION_REFUSED;
+ case EHOSTUNREACH:
+ case EHOSTDOWN:
+ case ENETUNREACH:
+ case EAFNOSUPPORT:
+ return ERR_ADDRESS_UNREACHABLE;
+ case EADDRNOTAVAIL:
+ return ERR_ADDRESS_INVALID;
+ case EMSGSIZE:
+ return ERR_MSG_TOO_BIG;
+ case ENOTCONN:
+ return ERR_SOCKET_NOT_CONNECTED;
+ case EISCONN:
+ return ERR_SOCKET_IS_CONNECTED;
+ case EINVAL:
+ return ERR_INVALID_ARGUMENT;
+ case EADDRINUSE:
+ return ERR_ADDRESS_IN_USE;
+ case E2BIG: // Argument list too long.
+ return ERR_INVALID_ARGUMENT;
+ case EBADF: // Bad file descriptor.
+ return ERR_INVALID_HANDLE;
+ case EBUSY: // Device or resource busy.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ECANCELED: // Operation canceled.
+ return ERR_ABORTED;
+ case EDEADLK: // Resource deadlock avoided.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case EDQUOT: // Disk quota exceeded.
+ return ERR_FILE_NO_SPACE;
+ case EEXIST: // File exists.
+ return ERR_FILE_EXISTS;
+ case EFAULT: // Bad address.
+ return ERR_INVALID_ARGUMENT;
+ case EFBIG: // File too large.
+ return ERR_FILE_TOO_BIG;
+ case EISDIR: // Operation not allowed for a directory.
+ return ERR_ACCESS_DENIED;
+ case ENAMETOOLONG: // Filename too long.
+ return ERR_FILE_PATH_TOO_LONG;
+ case ENFILE: // Too many open files in system.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ENOBUFS: // No buffer space available.
+ return ERR_OUT_OF_MEMORY;
+ case ENODEV: // No such device.
+ return ERR_INVALID_ARGUMENT;
+ case ENOENT: // No such file or directory.
+ return ERR_FILE_NOT_FOUND;
+ case ENOLCK: // No locks available.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ENOMEM: // Not enough space.
+ return ERR_OUT_OF_MEMORY;
+ case ENOSPC: // No space left on device.
+ return ERR_FILE_NO_SPACE;
+ case ENOSYS: // Function not implemented.
+ return ERR_NOT_IMPLEMENTED;
+ case ENOTDIR: // Not a directory.
+ return ERR_FILE_NOT_FOUND;
+ case ENOTSUP: // Operation not supported.
+ return ERR_NOT_IMPLEMENTED;
+ case EPERM: // Operation not permitted.
+ return ERR_ACCESS_DENIED;
+ case EROFS: // Read-only file system.
+ return ERR_ACCESS_DENIED;
+ case ETXTBSY: // Text file busy.
+ return ERR_ACCESS_DENIED;
+ case EUSERS: // Too many users.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case EMFILE: // Too many open files.
+ return ERR_INSUFFICIENT_RESOURCES;
+
+ case 0:
+ return OK;
+ default:
+ LOG(WARNING) << "Unknown error " << os_error
+ << " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+}
+
+} // namespace net
+} // namespace shell
+} // namespace mojo
diff --git a/shell/domain_socket/net_errors.h b/shell/domain_socket/net_errors.h
new file mode 100644
index 0000000..997705d
--- /dev/null
+++ b/shell/domain_socket/net_errors.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 SHELL_DOMAIN_SOCKET_NET_ERRORS_H__
+#define SHELL_DOMAIN_SOCKET_NET_ERRORS_H__
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file.h"
+
+namespace mojo {
+namespace shell {
+namespace net {
+
+// Error values are negative.
+enum Error {
+ // No error.
+ OK = 0,
+
+#define NET_ERROR(label, value) ERR_##label = value,
+#include "shell/domain_socket/net_error_list.h"
+#undef NET_ERROR
+
+};
+
+// Returns a textual representation of the error code for logging purposes.
+std::string ErrorToString(int error);
+
+// A convenient function to translate file error to net error code.
+Error FileErrorToNetError(base::File::Error file_error);
+
+// Map system error code to Error.
+Error MapSystemError(int os_error);
+
+} // namespace net
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DOMAIN_SOCKET_NET_ERRORS_H__
diff --git a/shell/domain_socket/socket_descriptor.h b/shell/domain_socket/socket_descriptor.h
new file mode 100644
index 0000000..b636bdf
--- /dev/null
+++ b/shell/domain_socket/socket_descriptor.h
@@ -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.
+
+#ifndef SHELL_DOMAIN_SOCKET_SOCKET_DESCRIPTOR_H_
+#define SHELL_DOMAIN_SOCKET_SOCKET_DESCRIPTOR_H_
+
+namespace mojo {
+namespace shell {
+
+typedef int SocketDescriptor;
+const SocketDescriptor kInvalidSocket = -1;
+
+} // namespace mojo
+} // namespace shell
+
+#endif // SHELL_DOMAIN_SOCKET_SOCKET_DESCRIPTOR_H_
diff --git a/shell/domain_socket/socket_libevent.cc b/shell/domain_socket/socket_libevent.cc
new file mode 100644
index 0000000..21fc924
--- /dev/null
+++ b/shell/domain_socket/socket_libevent.cc
@@ -0,0 +1,380 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shell/domain_socket/socket_libevent.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "shell/domain_socket/net_errors.h"
+
+namespace mojo {
+namespace shell {
+
+SockaddrStorage::SockaddrStorage(const SockaddrStorage& other)
+ : addr_len(other.addr_len),
+ addr(reinterpret_cast<struct sockaddr*>(&addr_storage)) {
+ memcpy(addr, other.addr, addr_len);
+}
+
+void SockaddrStorage::operator=(const SockaddrStorage& other) {
+ addr_len = other.addr_len;
+ // addr is already set to &this->addr_storage by default ctor.
+ memcpy(addr, other.addr, addr_len);
+}
+
+namespace {
+
+int MapAcceptError(int os_error) {
+ switch (os_error) {
+ // If the client aborts the connection before the server calls accept,
+ // POSIX specifies accept should fail with ECONNABORTED. The server can
+ // ignore the error and just call accept again, so we map the error to
+ // ERR_IO_PENDING. See UNIX Network Programming, Vol. 1, 3rd Ed., Sec.
+ // 5.11, "Connection Abort before accept Returns".
+ case ECONNABORTED:
+ return net::ERR_IO_PENDING;
+ default:
+ return net::MapSystemError(os_error);
+ }
+}
+
+int MapConnectError(int os_error) {
+ switch (os_error) {
+ case EINPROGRESS:
+ return net::ERR_IO_PENDING;
+ case EACCES:
+ return net::ERR_NETWORK_ACCESS_DENIED;
+ case ETIMEDOUT:
+ return net::ERR_CONNECTION_TIMED_OUT;
+ default: {
+ int net_error = net::MapSystemError(os_error);
+ if (net_error == net::ERR_FAILED)
+ return net::ERR_CONNECTION_FAILED; // More specific than ERR_FAILED.
+ return net_error;
+ }
+ }
+}
+
+int SetNonBlocking(int fd) {
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (-1 == flags)
+ return flags;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+}
+
+} // namespace
+
+SocketLibevent::SocketLibevent()
+ : socket_fd_(kInvalidSocket), waiting_connect_(false) {
+}
+
+SocketLibevent::~SocketLibevent() {
+ Close();
+}
+
+int SocketLibevent::Open(int address_family) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(kInvalidSocket, socket_fd_);
+ DCHECK(address_family == AF_INET || address_family == AF_INET6 ||
+ address_family == AF_UNIX);
+
+ int socket_type = SOCK_STREAM;
+#ifdef SOCK_NONBLOCK
+ socket_type |= SOCK_NONBLOCK;
+#endif
+ socket_fd_ = ::socket(address_family, socket_type,
+ address_family == AF_UNIX ? 0 : IPPROTO_TCP);
+#ifndef SOCK_NONBLOCK
+ if (SetNonBlocking(socket_fd_) != 0) {
+ PLOG(ERROR) << "SetNonBlocking() returned an error, errno=" << errno;
+ return net::MapSystemError(errno);
+ }
+#endif
+ if (socket_fd_ < 0) {
+ PLOG(ERROR) << "CreatePlatformSocket() returned an error, errno=" << errno;
+ return net::MapSystemError(errno);
+ }
+
+ return net::OK;
+}
+
+int SocketLibevent::AdoptConnectedSocket(SocketDescriptor socket,
+ const SockaddrStorage& address) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(kInvalidSocket, socket_fd_);
+
+ socket_fd_ = socket;
+
+ if (SetNonBlocking(socket_fd_)) {
+ int rv = net::MapSystemError(errno);
+ Close();
+ return rv;
+ }
+
+ SetPeerAddress(address);
+ return net::OK;
+}
+
+SocketDescriptor SocketLibevent::ReleaseConnectedSocket() {
+ StopWatchingAndCleanUp();
+ SocketDescriptor socket_fd = socket_fd_;
+ socket_fd_ = kInvalidSocket;
+ return socket_fd;
+}
+
+int SocketLibevent::Bind(const SockaddrStorage& address) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_fd_);
+
+ int rv = bind(socket_fd_, address.addr, address.addr_len);
+ if (rv < 0) {
+ PLOG(ERROR) << "bind() returned an error, errno=" << errno;
+ return net::MapSystemError(errno);
+ }
+
+ return net::OK;
+}
+
+int SocketLibevent::Listen(int backlog) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_fd_);
+ DCHECK_LT(0, backlog);
+
+ int rv = listen(socket_fd_, backlog);
+ if (rv < 0) {
+ PLOG(ERROR) << "listen() returned an error, errno=" << errno;
+ return net::MapSystemError(errno);
+ }
+
+ return net::OK;
+}
+
+int SocketLibevent::Accept(scoped_ptr<SocketLibevent>* socket,
+ const CompletionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_fd_);
+ DCHECK(accept_callback_.is_null());
+ DCHECK(socket);
+ DCHECK(!callback.is_null());
+
+ int rv = DoAccept(socket);
+ if (rv != net::ERR_IO_PENDING)
+ return rv;
+
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_fd_, true, base::MessageLoopForIO::WATCH_READ,
+ &accept_socket_watcher_, this)) {
+ PLOG(ERROR) << "WatchFileDescriptor failed on accept, errno " << errno;
+ return net::MapSystemError(errno);
+ }
+
+ accept_socket_ = socket;
+ accept_callback_ = callback;
+ return net::ERR_IO_PENDING;
+}
+
+int SocketLibevent::Connect(const SockaddrStorage& address,
+ const CompletionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_fd_);
+ DCHECK(!waiting_connect_);
+ DCHECK(!callback.is_null());
+
+ SetPeerAddress(address);
+
+ int rv = DoConnect();
+ if (rv != net::ERR_IO_PENDING)
+ return rv;
+
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_fd_, true, base::MessageLoopForIO::WATCH_WRITE,
+ &write_socket_watcher_, this)) {
+ PLOG(ERROR) << "WatchFileDescriptor failed on connect, errno " << errno;
+ return net::MapSystemError(errno);
+ }
+
+ write_callback_ = callback;
+ waiting_connect_ = true;
+ return net::ERR_IO_PENDING;
+}
+
+bool SocketLibevent::IsConnected() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (socket_fd_ == kInvalidSocket || waiting_connect_)
+ return false;
+
+ // Checks if connection is alive.
+ char c;
+ int rv = HANDLE_EINTR(recv(socket_fd_, &c, 1, MSG_PEEK));
+ if (rv == 0)
+ return false;
+ if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
+ return false;
+
+ return true;
+}
+
+bool SocketLibevent::IsConnectedAndIdle() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (socket_fd_ == kInvalidSocket || waiting_connect_)
+ return false;
+
+ // Check if connection is alive and we haven't received any data
+ // unexpectedly.
+ char c;
+ int rv = HANDLE_EINTR(recv(socket_fd_, &c, 1, MSG_PEEK));
+ if (rv >= 0)
+ return false;
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ return false;
+
+ return true;
+}
+
+int SocketLibevent::GetLocalAddress(SockaddrStorage* address) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(address);
+
+ if (getsockname(socket_fd_, address->addr, &address->addr_len) < 0)
+ return net::MapSystemError(errno);
+ return net::OK;
+}
+
+int SocketLibevent::GetPeerAddress(SockaddrStorage* address) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(address);
+
+ if (!HasPeerAddress())
+ return net::ERR_SOCKET_NOT_CONNECTED;
+
+ *address = *peer_address_;
+ return net::OK;
+}
+
+void SocketLibevent::SetPeerAddress(const SockaddrStorage& address) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // |peer_address_| will be non-NULL if Connect() has been called. Unless
+ // Close() is called to reset the internal state, a second call to Connect()
+ // is not allowed.
+ // Please note that we don't allow a second Connect() even if the previous
+ // Connect() has failed. Connecting the same |socket_| again after a
+ // connection attempt failed results in unspecified behavior according to
+ // POSIX.
+ DCHECK(!peer_address_);
+ peer_address_.reset(new SockaddrStorage(address));
+}
+
+bool SocketLibevent::HasPeerAddress() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return peer_address_ != NULL;
+}
+
+void SocketLibevent::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ StopWatchingAndCleanUp();
+
+ if (socket_fd_ != kInvalidSocket) {
+ if (IGNORE_EINTR(close(socket_fd_)) < 0)
+ PLOG(ERROR) << "close() returned an error, errno=" << errno;
+ socket_fd_ = kInvalidSocket;
+ }
+}
+
+void SocketLibevent::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK(!accept_callback_.is_null() || !read_callback_.is_null());
+ if (!accept_callback_.is_null()) {
+ AcceptCompleted();
+ } else { // !read_callback_.is_null()
+ NOTREACHED();
+ }
+}
+
+void SocketLibevent::OnFileCanWriteWithoutBlocking(int fd) {
+ DCHECK(!write_callback_.is_null());
+ if (waiting_connect_) {
+ ConnectCompleted();
+ } else {
+ NOTREACHED();
+ }
+}
+
+int SocketLibevent::DoAccept(scoped_ptr<SocketLibevent>* socket) {
+ SockaddrStorage new_peer_address;
+ int new_socket = HANDLE_EINTR(
+ accept(socket_fd_, new_peer_address.addr, &new_peer_address.addr_len));
+ if (new_socket < 0)
+ return MapAcceptError(errno);
+
+ scoped_ptr<SocketLibevent> accepted_socket(new SocketLibevent);
+ int rv = accepted_socket->AdoptConnectedSocket(new_socket, new_peer_address);
+ if (rv != net::OK)
+ return rv;
+
+ *socket = accepted_socket.Pass();
+ return net::OK;
+}
+
+void SocketLibevent::AcceptCompleted() {
+ DCHECK(accept_socket_);
+ int rv = DoAccept(accept_socket_);
+ if (rv == net::ERR_IO_PENDING)
+ return;
+
+ bool ok = accept_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ accept_socket_ = NULL;
+ base::ResetAndReturn(&accept_callback_).Run(rv);
+}
+
+int SocketLibevent::DoConnect() {
+ int rv = HANDLE_EINTR(
+ connect(socket_fd_, peer_address_->addr, peer_address_->addr_len));
+ DCHECK_GE(0, rv);
+ return rv == 0 ? net::OK : MapConnectError(errno);
+}
+
+void SocketLibevent::ConnectCompleted() {
+ // Get the error that connect() completed with.
+ int os_error = 0;
+ socklen_t len = sizeof(os_error);
+ if (getsockopt(socket_fd_, SOL_SOCKET, SO_ERROR, &os_error, &len) == 0) {
+ // TCPSocketLibevent expects errno to be set.
+ errno = os_error;
+ }
+
+ int rv = MapConnectError(errno);
+ if (rv == net::ERR_IO_PENDING)
+ return;
+
+ bool ok = write_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ waiting_connect_ = false;
+ base::ResetAndReturn(&write_callback_).Run(rv);
+}
+
+void SocketLibevent::StopWatchingAndCleanUp() {
+ bool ok = accept_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+
+ if (!accept_callback_.is_null()) {
+ accept_socket_ = NULL;
+ accept_callback_.Reset();
+ }
+
+ waiting_connect_ = false;
+ peer_address_.reset();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/domain_socket/socket_libevent.h b/shell/domain_socket/socket_libevent.h
new file mode 100644
index 0000000..4a84f40
--- /dev/null
+++ b/shell/domain_socket/socket_libevent.h
@@ -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.
+
+#ifndef SHELL_DOMAIN_SOCKET_SOCKET_LIBEVENT_H_
+#define SHELL_DOMAIN_SOCKET_SOCKET_LIBEVENT_H_
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.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/threading/thread_checker.h"
+#include "shell/domain_socket/completion_callback.h"
+#include "shell/domain_socket/socket_descriptor.h"
+
+namespace mojo {
+namespace shell {
+
+// Convenience struct for when you need a |struct sockaddr|.
+struct SockaddrStorage {
+ SockaddrStorage()
+ : addr_len(sizeof(addr_storage)),
+ addr(reinterpret_cast<struct sockaddr*>(&addr_storage)) {}
+ SockaddrStorage(const SockaddrStorage& other);
+ void operator=(const SockaddrStorage& other);
+
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_len;
+ struct sockaddr* const addr;
+};
+
+// Socket class to provide asynchronous read/write operations on top of the
+// posix socket api. It supports AF_INET, AF_INET6, and AF_UNIX addresses.
+class SocketLibevent : public base::MessageLoopForIO::Watcher {
+ public:
+ SocketLibevent();
+ ~SocketLibevent() override;
+
+ // Opens a socket and returns net::OK if |address_family| is AF_INET, AF_INET6
+ // or AF_UNIX. Otherwise, it does DCHECK() and returns a net error.
+ int Open(int address_family);
+ // Takes ownership of |socket|.
+ int AdoptConnectedSocket(SocketDescriptor socket,
+ const SockaddrStorage& peer_address);
+ // Releases ownership of |socket_fd_| to caller.
+ SocketDescriptor ReleaseConnectedSocket();
+
+ int Bind(const SockaddrStorage& address);
+
+ int Listen(int backlog);
+ int Accept(scoped_ptr<SocketLibevent>* socket,
+ const CompletionCallback& callback);
+
+ // Connects socket. On non-ERR_IO_PENDING error, sets errno and returns a net
+ // error code. On ERR_IO_PENDING, |callback| is called with a net error code,
+ // not errno, though errno is set if connect event happens with error.
+ // TODO(byungchul): Need more robust way to pass system errno.
+ int Connect(const SockaddrStorage& address,
+ const CompletionCallback& callback);
+ bool IsConnected() const;
+ bool IsConnectedAndIdle() const;
+
+ int GetLocalAddress(SockaddrStorage* address) const;
+ int GetPeerAddress(SockaddrStorage* address) const;
+ void SetPeerAddress(const SockaddrStorage& address);
+ // Returns true if peer address has been set regardless of socket state.
+ bool HasPeerAddress() const;
+
+ void Close();
+
+ SocketDescriptor socket_fd() const { return socket_fd_; }
+
+ private:
+ // base::MessageLoopForIO::Watcher methods.
+ void OnFileCanReadWithoutBlocking(int fd) override;
+ void OnFileCanWriteWithoutBlocking(int fd) override;
+
+ int DoAccept(scoped_ptr<SocketLibevent>* socket);
+ void AcceptCompleted();
+
+ int DoConnect();
+ void ConnectCompleted();
+
+ void StopWatchingAndCleanUp();
+
+ SocketDescriptor socket_fd_;
+
+ base::MessageLoopForIO::FileDescriptorWatcher accept_socket_watcher_;
+ scoped_ptr<SocketLibevent>* accept_socket_;
+ CompletionCallback accept_callback_;
+
+ base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_;
+ // External callback; called when read is complete.
+ CompletionCallback read_callback_;
+
+ base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_;
+ // External callback; called when write or connect is complete.
+ CompletionCallback write_callback_;
+
+ // A connect operation is pending. In this case, |write_callback_| needs to be
+ // called when connect is complete.
+ bool waiting_connect_;
+
+ scoped_ptr<SockaddrStorage> peer_address_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketLibevent);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DOMAIN_SOCKET_SOCKET_LIBEVENT_H_
diff --git a/shell/domain_socket/test_completion_callback.cc b/shell/domain_socket/test_completion_callback.cc
new file mode 100644
index 0000000..566f806
--- /dev/null
+++ b/shell/domain_socket/test_completion_callback.cc
@@ -0,0 +1,56 @@
+// 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.
+
+#include "shell/domain_socket/test_completion_callback.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+
+namespace mojo {
+namespace shell {
+
+namespace internal {
+
+void TestCompletionCallbackBaseInternal::DidSetResult() {
+ have_result_ = true;
+ if (waiting_for_result_)
+ base::MessageLoop::current()->Quit();
+}
+
+void TestCompletionCallbackBaseInternal::WaitForResult() {
+ DCHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ have_result_ = false; // Auto-reset for next callback.
+}
+
+TestCompletionCallbackBaseInternal::TestCompletionCallbackBaseInternal()
+ : have_result_(false), waiting_for_result_(false) {
+}
+
+} // namespace internal
+
+TestCompletionCallback::TestCompletionCallback()
+ : callback_(base::Bind(&TestCompletionCallback::SetResult,
+ base::Unretained(this))) {
+}
+
+TestCompletionCallback::~TestCompletionCallback() {
+}
+
+TestInt64CompletionCallback::TestInt64CompletionCallback()
+ : callback_(base::Bind(&TestInt64CompletionCallback::SetResult,
+ base::Unretained(this))) {
+}
+
+TestInt64CompletionCallback::~TestInt64CompletionCallback() {
+}
+
+} // namespace net
+} // namespace net
diff --git a/shell/domain_socket/test_completion_callback.h b/shell/domain_socket/test_completion_callback.h
new file mode 100644
index 0000000..5c3a5f1
--- /dev/null
+++ b/shell/domain_socket/test_completion_callback.h
@@ -0,0 +1,115 @@
+// 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.
+
+#ifndef SHELL_DOMAIN_SOCKET_TEST_COMPLETION_CALLBACK_H_
+#define SHELL_DOMAIN_SOCKET_TEST_COMPLETION_CALLBACK_H_
+
+#include "base/compiler_specific.h"
+#include "base/tuple.h"
+#include "shell/domain_socket/completion_callback.h"
+#include "shell/domain_socket/net_errors.h"
+
+//-----------------------------------------------------------------------------
+// completion callback helper
+
+// A helper class for completion callbacks, designed to make it easy to run
+// tests involving asynchronous operations. Just call WaitForResult to wait
+// for the asynchronous operation to complete.
+//
+// NOTE: Since this runs a message loop to wait for the completion callback,
+// there could be other side-effects resulting from WaitForResult. For this
+// reason, this class is probably not ideal for a general application.
+//
+
+namespace mojo {
+namespace shell {
+
+namespace internal {
+
+class TestCompletionCallbackBaseInternal {
+ public:
+ bool have_result() const { return have_result_; }
+
+ protected:
+ TestCompletionCallbackBaseInternal();
+ void DidSetResult();
+ void WaitForResult();
+
+ bool have_result_;
+ bool waiting_for_result_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestCompletionCallbackBaseInternal);
+};
+
+template <typename R>
+class TestCompletionCallbackTemplate
+ : public TestCompletionCallbackBaseInternal {
+ public:
+ virtual ~TestCompletionCallbackTemplate() {}
+
+ R WaitForResult() {
+ TestCompletionCallbackBaseInternal::WaitForResult();
+ return result_;
+ }
+
+ R GetResult(R result) {
+ if (net::ERR_IO_PENDING != result)
+ return result;
+ return WaitForResult();
+ }
+
+ protected:
+ // Override this method to gain control as the callback is running.
+ virtual void SetResult(R result) {
+ result_ = result;
+ DidSetResult();
+ }
+
+ TestCompletionCallbackTemplate() : result_(R()) {}
+ R result_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestCompletionCallbackTemplate);
+};
+
+} // namespace internal
+
+// Base class overridden by custom implementations of TestCompletionCallback.
+typedef internal::TestCompletionCallbackTemplate<int>
+ TestCompletionCallbackBase;
+
+typedef internal::TestCompletionCallbackTemplate<int64>
+ TestInt64CompletionCallbackBase;
+
+class TestCompletionCallback : public TestCompletionCallbackBase {
+ public:
+ TestCompletionCallback();
+ ~TestCompletionCallback() override;
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ const CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCompletionCallback);
+};
+
+class TestInt64CompletionCallback : public TestInt64CompletionCallbackBase {
+ public:
+ TestInt64CompletionCallback();
+ ~TestInt64CompletionCallback() override;
+
+ const Int64CompletionCallback& callback() const { return callback_; }
+
+ private:
+ const Int64CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestInt64CompletionCallback);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DOMAIN_SOCKET_TEST_COMPLETION_CALLBACK_H_
diff --git a/shell/domain_socket/unix_domain_client_socket_posix.cc b/shell/domain_socket/unix_domain_client_socket_posix.cc
new file mode 100644
index 0000000..1733950
--- /dev/null
+++ b/shell/domain_socket/unix_domain_client_socket_posix.cc
@@ -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.
+
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_libevent.h"
+
+namespace mojo {
+namespace shell {
+
+UnixDomainClientSocket::UnixDomainClientSocket(const std::string& socket_path,
+ bool use_abstract_namespace)
+ : socket_path_(socket_path),
+ use_abstract_namespace_(use_abstract_namespace) {
+}
+
+UnixDomainClientSocket::UnixDomainClientSocket(
+ scoped_ptr<SocketLibevent> socket)
+ : use_abstract_namespace_(false), socket_(socket.Pass()) {
+}
+
+UnixDomainClientSocket::~UnixDomainClientSocket() {
+ Disconnect();
+}
+
+// static
+bool UnixDomainClientSocket::FillAddress(const std::string& socket_path,
+ bool use_abstract_namespace,
+ SockaddrStorage* address) {
+ struct sockaddr_un* socket_addr =
+ reinterpret_cast<struct sockaddr_un*>(address->addr);
+ size_t path_max = address->addr_len - offsetof(struct sockaddr_un, sun_path);
+ // Non abstract namespace pathname should be null-terminated. Abstract
+ // namespace pathname must start with '\0'. So, the size is always greater
+ // than socket_path size by 1.
+ size_t path_size = socket_path.size() + 1;
+ if (path_size > path_max)
+ return false;
+
+ memset(socket_addr, 0, address->addr_len);
+ socket_addr->sun_family = AF_UNIX;
+ address->addr_len = path_size + offsetof(struct sockaddr_un, sun_path);
+ if (!use_abstract_namespace) {
+ memcpy(socket_addr->sun_path, socket_path.c_str(), socket_path.size());
+ return true;
+ }
+
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+ // Convert the path given into abstract socket name. It must start with
+ // the '\0' character, so we are adding it. |addr_len| must specify the
+ // length of the structure exactly, as potentially the socket name may
+ // have '\0' characters embedded (although we don't support this).
+ // Note that addr.sun_path is already zero initialized.
+ memcpy(socket_addr->sun_path + 1, socket_path.c_str(), socket_path.size());
+ return true;
+#else
+ return false;
+#endif
+}
+
+int UnixDomainClientSocket::Connect(const CompletionCallback& callback) {
+ DCHECK(!socket_);
+
+ if (socket_path_.empty())
+ return net::ERR_ADDRESS_INVALID;
+
+ SockaddrStorage address;
+ if (!FillAddress(socket_path_, use_abstract_namespace_, &address))
+ return net::ERR_ADDRESS_INVALID;
+
+ socket_.reset(new SocketLibevent);
+ int rv = socket_->Open(AF_UNIX);
+ DCHECK_NE(net::ERR_IO_PENDING, rv);
+ if (rv != net::OK)
+ return rv;
+
+ return socket_->Connect(address, callback);
+}
+
+void UnixDomainClientSocket::Disconnect() {
+ socket_.reset();
+}
+
+bool UnixDomainClientSocket::IsConnected() const {
+ return socket_ && socket_->IsConnected();
+}
+
+bool UnixDomainClientSocket::IsConnectedAndIdle() const {
+ return socket_ && socket_->IsConnectedAndIdle();
+}
+
+SocketDescriptor UnixDomainClientSocket::ReleaseConnectedSocket() {
+ DCHECK(socket_);
+ DCHECK(socket_->IsConnected());
+
+ SocketDescriptor socket_fd = socket_->ReleaseConnectedSocket();
+ socket_.reset();
+ return socket_fd;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/domain_socket/unix_domain_client_socket_posix.h b/shell/domain_socket/unix_domain_client_socket_posix.h
new file mode 100644
index 0000000..719f9e4
--- /dev/null
+++ b/shell/domain_socket/unix_domain_client_socket_posix.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 SHELL_DOMAIN_SOCKET_UNIX_DOMAIN_CLIENT_SOCKET_POSIX_H_
+#define SHELL_DOMAIN_SOCKET_UNIX_DOMAIN_CLIENT_SOCKET_POSIX_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "shell/domain_socket/completion_callback.h"
+#include "shell/domain_socket/socket_descriptor.h"
+
+namespace mojo {
+namespace shell {
+
+class SocketLibevent;
+struct SockaddrStorage;
+
+// A client socket that uses unix domain socket as the transport layer.
+class UnixDomainClientSocket {
+ public:
+ // Builds a client socket with |socket_path|. The caller should call Connect()
+ // to connect to a server socket.
+ UnixDomainClientSocket(const std::string& socket_path,
+ bool use_abstract_namespace);
+ // Builds a client socket with socket libevent which is already connected.
+ // UnixDomainServerSocket uses this after it accepts a connection.
+ explicit UnixDomainClientSocket(scoped_ptr<SocketLibevent> socket);
+
+ ~UnixDomainClientSocket();
+
+ // Fills |address| with |socket_path| and its length. For Android or Linux
+ // platform, this supports abstract namespaces.
+ static bool FillAddress(const std::string& socket_path,
+ bool use_abstract_namespace,
+ SockaddrStorage* address);
+
+ int Connect(const CompletionCallback& callback);
+ void Disconnect();
+ bool IsConnected() const;
+ bool IsConnectedAndIdle() const;
+
+ // Releases ownership of underlying SocketDescriptor to caller.
+ // Internal state is reset so that this object can be used again.
+ // Socket must be connected in order to release it.
+ SocketDescriptor ReleaseConnectedSocket();
+
+ private:
+ const std::string socket_path_;
+ const bool use_abstract_namespace_;
+ scoped_ptr<SocketLibevent> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnixDomainClientSocket);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DOMAIN_SOCKET_UNIX_DOMAIN_CLIENT_SOCKET_POSIX_H_
diff --git a/shell/domain_socket/unix_domain_client_socket_posix_unittest.cc b/shell/domain_socket/unix_domain_client_socket_posix_unittest.cc
new file mode 100644
index 0000000..69e8bf7
--- /dev/null
+++ b/shell/domain_socket/unix_domain_client_socket_posix_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 "shell/domain_socket/unix_domain_client_socket_posix.h"
+
+#include <unistd.h>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/posix/eintr_wrapper.h"
+#include "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_libevent.h"
+#include "shell/domain_socket/test_completion_callback.h"
+#include "shell/domain_socket/unix_domain_server_socket_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+const char kSocketFilename[] = "socket_for_testing";
+
+bool UserCanConnectCallback(
+ bool allow_user,
+ const UnixDomainServerSocket::Credentials& credentials) {
+ // Here peers are running in same process.
+ EXPECT_EQ(getpid(), credentials.process_id);
+ EXPECT_EQ(getuid(), credentials.user_id);
+ EXPECT_EQ(getgid(), credentials.group_id);
+ return allow_user;
+}
+
+UnixDomainServerSocket::AuthCallback CreateAuthCallback(bool allow_user) {
+ return base::Bind(&UserCanConnectCallback, allow_user);
+}
+
+// Connects socket synchronously.
+int ConnectSynchronously(UnixDomainClientSocket* socket) {
+ TestCompletionCallback connect_callback;
+ int rv = socket->Connect(connect_callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = connect_callback.WaitForResult();
+ return rv;
+}
+} // namespace
+
+class UnixDomainClientSocketTest : public testing::Test {
+ protected:
+ UnixDomainClientSocketTest() {
+ EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+ socket_path_ = temp_dir_.path().Append(kSocketFilename).value();
+ }
+
+ base::ScopedTempDir temp_dir_;
+ std::string socket_path_;
+ base::MessageLoopForIO loop_;
+};
+
+TEST_F(UnixDomainClientSocketTest, ConnectWithSocketDescriptor) {
+ const bool kUseAbstractNamespace = false;
+
+ UnixDomainServerSocket server_socket(CreateAuthCallback(true),
+ kUseAbstractNamespace);
+ EXPECT_EQ(net::OK, server_socket.ListenWithPath(socket_path_, 1));
+
+ SocketDescriptor accepted_socket_fd = kInvalidSocket;
+ TestCompletionCallback accept_callback;
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ server_socket.Accept(&accepted_socket_fd, accept_callback.callback()));
+ EXPECT_EQ(kInvalidSocket, accepted_socket_fd);
+
+ UnixDomainClientSocket client_socket(socket_path_, kUseAbstractNamespace);
+ EXPECT_FALSE(client_socket.IsConnected());
+
+ EXPECT_EQ(net::OK, ConnectSynchronously(&client_socket));
+ EXPECT_TRUE(client_socket.IsConnected());
+ // Server has not yet been notified of the connection.
+ EXPECT_EQ(kInvalidSocket, accepted_socket_fd);
+
+ EXPECT_EQ(net::OK, accept_callback.WaitForResult());
+ EXPECT_NE(kInvalidSocket, accepted_socket_fd);
+
+ SocketDescriptor client_socket_fd = client_socket.ReleaseConnectedSocket();
+ EXPECT_NE(kInvalidSocket, client_socket_fd);
+
+ // Now, re-wrap client_socket_fd in a UnixDomainClientSocket and check that
+ // it hasn't gotten accidentally closed.
+ SockaddrStorage addr;
+ ASSERT_TRUE(UnixDomainClientSocket::FillAddress(socket_path_, false, &addr));
+ scoped_ptr<SocketLibevent> adopter(new SocketLibevent);
+ adopter->AdoptConnectedSocket(client_socket_fd, addr);
+ UnixDomainClientSocket rewrapped_socket(adopter.Pass());
+ EXPECT_TRUE(rewrapped_socket.IsConnected());
+
+ EXPECT_EQ(0, IGNORE_EINTR(close(accepted_socket_fd)));
+}
+
+TEST_F(UnixDomainClientSocketTest, ConnectWithAbstractNamespace) {
+ const bool kUseAbstractNamespace = true;
+
+ UnixDomainClientSocket client_socket(socket_path_, kUseAbstractNamespace);
+ EXPECT_FALSE(client_socket.IsConnected());
+
+ UnixDomainServerSocket server_socket(CreateAuthCallback(true),
+ kUseAbstractNamespace);
+ EXPECT_EQ(net::OK, server_socket.ListenWithPath(socket_path_, 1));
+
+ SocketDescriptor accepted_socket_fd = kInvalidSocket;
+ TestCompletionCallback accept_callback;
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ server_socket.Accept(&accepted_socket_fd, accept_callback.callback()));
+ EXPECT_EQ(kInvalidSocket, accepted_socket_fd);
+
+ EXPECT_EQ(net::OK, ConnectSynchronously(&client_socket));
+ EXPECT_TRUE(client_socket.IsConnected());
+ // Server has not yet been notified of the connection.
+ EXPECT_EQ(kInvalidSocket, accepted_socket_fd);
+
+ EXPECT_EQ(net::OK, accept_callback.WaitForResult());
+ EXPECT_NE(kInvalidSocket, accepted_socket_fd);
+
+ EXPECT_EQ(0, IGNORE_EINTR(close(accepted_socket_fd)));
+}
+
+TEST_F(UnixDomainClientSocketTest, ConnectToNonExistentSocket) {
+ const bool kUseAbstractNamespace = false;
+
+ UnixDomainClientSocket client_socket(socket_path_, kUseAbstractNamespace);
+ EXPECT_FALSE(client_socket.IsConnected());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, ConnectSynchronously(&client_socket));
+}
+
+TEST_F(UnixDomainClientSocketTest,
+ ConnectToNonExistentSocketWithAbstractNamespace) {
+ const bool kUseAbstractNamespace = true;
+
+ UnixDomainClientSocket client_socket(socket_path_, kUseAbstractNamespace);
+ EXPECT_FALSE(client_socket.IsConnected());
+
+ TestCompletionCallback connect_callback;
+ EXPECT_EQ(net::ERR_CONNECTION_REFUSED, ConnectSynchronously(&client_socket));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/domain_socket/unix_domain_server_socket_posix.cc b/shell/domain_socket/unix_domain_server_socket_posix.cc
new file mode 100644
index 0000000..778e41f
--- /dev/null
+++ b/shell/domain_socket/unix_domain_server_socket_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 "shell/domain_socket/unix_domain_server_socket_posix.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "shell/domain_socket/completion_callback.h"
+#include "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_descriptor.h"
+#include "shell/domain_socket/socket_libevent.h"
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+// Intended for use as SetterCallbacks in Accept() helper methods.
+void SetSocketDescriptor(SocketDescriptor* socket,
+ scoped_ptr<SocketLibevent> accepted_socket) {
+ *socket = accepted_socket->ReleaseConnectedSocket();
+}
+
+} // anonymous namespace
+
+UnixDomainServerSocket::UnixDomainServerSocket(
+ const AuthCallback& auth_callback,
+ bool use_abstract_namespace)
+ : auth_callback_(auth_callback),
+ use_abstract_namespace_(use_abstract_namespace) {
+ DCHECK(!auth_callback_.is_null());
+}
+
+UnixDomainServerSocket::~UnixDomainServerSocket() {
+}
+
+// static
+bool UnixDomainServerSocket::GetPeerCredentials(SocketDescriptor socket,
+ Credentials* credentials) {
+ struct ucred user_cred;
+ socklen_t len = sizeof(user_cred);
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &user_cred, &len) < 0)
+ return false;
+ credentials->process_id = user_cred.pid;
+ credentials->user_id = user_cred.uid;
+ credentials->group_id = user_cred.gid;
+ return true;
+}
+
+int UnixDomainServerSocket::ListenWithPath(const std::string& unix_domain_path,
+ int backlog) {
+ DCHECK(!listen_socket_);
+
+ SockaddrStorage address;
+ if (!UnixDomainClientSocket::FillAddress(unix_domain_path,
+ use_abstract_namespace_, &address)) {
+ return net::ERR_ADDRESS_INVALID;
+ }
+
+ scoped_ptr<SocketLibevent> socket(new SocketLibevent);
+ int rv = socket->Open(AF_UNIX);
+ DCHECK_NE(net::ERR_IO_PENDING, rv);
+ if (rv != net::OK)
+ return rv;
+
+ rv = socket->Bind(address);
+ DCHECK_NE(net::ERR_IO_PENDING, rv);
+ if (rv != net::OK) {
+ PLOG(ERROR) << "Could not bind unix domain socket to " << unix_domain_path
+ << (use_abstract_namespace_ ? " (with abstract namespace)"
+ : "");
+ return rv;
+ }
+
+ rv = socket->Listen(backlog);
+ DCHECK_NE(net::ERR_IO_PENDING, rv);
+ if (rv != net::OK)
+ return rv;
+
+ listen_socket_.swap(socket);
+ return rv;
+}
+
+int UnixDomainServerSocket::Accept(SocketDescriptor* socket,
+ const CompletionCallback& callback) {
+ DCHECK(socket);
+
+ SetterCallback setter_callback = base::Bind(&SetSocketDescriptor, socket);
+ return DoAccept(setter_callback, callback);
+}
+
+int UnixDomainServerSocket::DoAccept(const SetterCallback& setter_callback,
+ const CompletionCallback& callback) {
+ DCHECK(!setter_callback.is_null());
+ DCHECK(!callback.is_null());
+ DCHECK(listen_socket_);
+ DCHECK(!accept_socket_);
+
+ while (true) {
+ int rv = listen_socket_->Accept(
+ &accept_socket_,
+ base::Bind(&UnixDomainServerSocket::AcceptCompleted,
+ base::Unretained(this), setter_callback, callback));
+ if (rv != net::OK)
+ return rv;
+ if (AuthenticateAndGetStreamSocket(setter_callback))
+ return net::OK;
+ // Accept another socket because authentication error should be transparent
+ // to the caller.
+ }
+}
+
+void UnixDomainServerSocket::AcceptCompleted(
+ const SetterCallback& setter_callback,
+ const CompletionCallback& callback,
+ int rv) {
+ if (rv != net::OK) {
+ callback.Run(rv);
+ return;
+ }
+
+ if (AuthenticateAndGetStreamSocket(setter_callback)) {
+ callback.Run(net::OK);
+ return;
+ }
+
+ // Accept another socket because authentication error should be transparent
+ // to the caller.
+ rv = DoAccept(setter_callback, callback);
+ if (rv != net::ERR_IO_PENDING)
+ callback.Run(rv);
+}
+
+bool UnixDomainServerSocket::AuthenticateAndGetStreamSocket(
+ const SetterCallback& setter_callback) {
+ DCHECK(accept_socket_);
+
+ Credentials credentials;
+ if (!GetPeerCredentials(accept_socket_->socket_fd(), &credentials) ||
+ !auth_callback_.Run(credentials)) {
+ accept_socket_.reset();
+ return false;
+ }
+
+ setter_callback.Run(accept_socket_.Pass());
+ return true;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/domain_socket/unix_domain_server_socket_posix.h b/shell/domain_socket/unix_domain_server_socket_posix.h
new file mode 100644
index 0000000..ebb1f9f
--- /dev/null
+++ b/shell/domain_socket/unix_domain_server_socket_posix.h
@@ -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.
+
+#ifndef SHELL_DOMAIN_SOCKET_UNIX_DOMAIN_SERVER_SOCKET_POSIX_H_
+#define SHELL_DOMAIN_SOCKET_UNIX_DOMAIN_SERVER_SOCKET_POSIX_H_
+
+#include <sys/types.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "shell/domain_socket/completion_callback.h"
+#include "shell/domain_socket/socket_descriptor.h"
+
+namespace mojo {
+namespace shell {
+
+class SocketLibevent;
+
+// Unix Domain Server Socket Implementation. Supports abstract namespaces on
+// Linux and Android.
+class UnixDomainServerSocket {
+ public:
+ // Credentials of a peer process connected to the socket.
+ struct Credentials {
+ pid_t process_id;
+ uid_t user_id;
+ gid_t group_id;
+ };
+
+ // Callback that returns whether the already connected client, identified by
+ // its credentials, is allowed to keep the connection open. Note that
+ // the socket is closed immediately in case the callback returns false.
+ typedef base::Callback<bool(const Credentials&)> AuthCallback;
+
+ UnixDomainServerSocket(const AuthCallback& auth_callack,
+ bool use_abstract_namespace);
+ ~UnixDomainServerSocket();
+
+ // Gets credentials of peer to check permissions.
+ static bool GetPeerCredentials(SocketDescriptor socket_fd,
+ Credentials* credentials);
+
+ int ListenWithPath(const std::string& unix_domain_path, int backlog);
+
+ // Accepts an incoming connection on |listen_socket_|, but passes back
+ // a raw SocketDescriptor instead of a StreamSocket.
+ int Accept(SocketDescriptor* socket_descriptor,
+ const CompletionCallback& callback);
+
+ private:
+ // A callback to wrap the setting of the out-parameter to Accept().
+ // This allows the internal machinery of that call to be implemented in
+ // a manner that's agnostic to the caller's desired output.
+ typedef base::Callback<void(scoped_ptr<SocketLibevent>)> SetterCallback;
+
+ int DoAccept(const SetterCallback& setter_callback,
+ const CompletionCallback& callback);
+ void AcceptCompleted(const SetterCallback& setter_callback,
+ const CompletionCallback& callback,
+ int rv);
+ bool AuthenticateAndGetStreamSocket(const SetterCallback& setter_callback);
+
+ scoped_ptr<SocketLibevent> listen_socket_;
+ const AuthCallback auth_callback_;
+ const bool use_abstract_namespace_;
+
+ scoped_ptr<SocketLibevent> accept_socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocket);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_DOMAIN_SOCKET_UNIX_DOMAIN_SERVER_SOCKET_POSIX_H_
diff --git a/shell/domain_socket/unix_domain_server_socket_posix_unittest.cc b/shell/domain_socket/unix_domain_server_socket_posix_unittest.cc
new file mode 100644
index 0000000..96760f2
--- /dev/null
+++ b/shell/domain_socket/unix_domain_server_socket_posix_unittest.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 "shell/domain_socket/unix_domain_server_socket_posix.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/test_completion_callback.h"
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+const char kSocketFilename[] = "socket_for_testing";
+const char kInvalidSocketPath[] = "/invalid/path";
+
+bool UserCanConnectCallback(
+ bool allow_user,
+ const UnixDomainServerSocket::Credentials& credentials) {
+ // Here peers are running in same process.
+ EXPECT_EQ(getpid(), credentials.process_id);
+ EXPECT_EQ(getuid(), credentials.user_id);
+ EXPECT_EQ(getgid(), credentials.group_id);
+ return allow_user;
+}
+
+UnixDomainServerSocket::AuthCallback CreateAuthCallback(bool allow_user) {
+ return base::Bind(&UserCanConnectCallback, allow_user);
+}
+} // namespace
+
+class UnixDomainServerSocketTest : public testing::Test {
+ protected:
+ UnixDomainServerSocketTest() {
+ EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+ socket_path_ = temp_dir_.path().Append(kSocketFilename).value();
+ }
+
+ base::ScopedTempDir temp_dir_;
+ std::string socket_path_;
+ base::MessageLoopForIO loop_;
+};
+
+TEST_F(UnixDomainServerSocketTest, ListenWithInvalidPath) {
+ const bool kUseAbstractNamespace = false;
+ UnixDomainServerSocket server_socket(CreateAuthCallback(true),
+ kUseAbstractNamespace);
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
+ server_socket.ListenWithPath(kInvalidSocketPath, 1));
+}
+
+TEST_F(UnixDomainServerSocketTest, ListenWithInvalidPathWithAbstractNamespace) {
+ const bool kUseAbstractNamespace = true;
+ UnixDomainServerSocket server_socket(CreateAuthCallback(true),
+ kUseAbstractNamespace);
+ EXPECT_EQ(net::OK, server_socket.ListenWithPath(kInvalidSocketPath, 1));
+}
+
+TEST_F(UnixDomainServerSocketTest, ListenAgainAfterFailureWithInvalidPath) {
+ const bool kUseAbstractNamespace = false;
+ UnixDomainServerSocket server_socket(CreateAuthCallback(true),
+ kUseAbstractNamespace);
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
+ server_socket.ListenWithPath(kInvalidSocketPath, 1));
+ EXPECT_EQ(net::OK, server_socket.ListenWithPath(socket_path_, 1));
+}
+
+TEST_F(UnixDomainServerSocketTest, AcceptWithForbiddenUser) {
+ const bool kUseAbstractNamespace = false;
+
+ UnixDomainServerSocket server_socket(CreateAuthCallback(false),
+ kUseAbstractNamespace);
+ EXPECT_EQ(net::OK, server_socket.ListenWithPath(socket_path_, 1));
+
+ SocketDescriptor accepted_socket = kInvalidSocket;
+ TestCompletionCallback accept_callback;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ server_socket.Accept(&accepted_socket, accept_callback.callback()));
+ EXPECT_EQ(accepted_socket, kInvalidSocket);
+
+ UnixDomainClientSocket client_socket(socket_path_, kUseAbstractNamespace);
+ EXPECT_FALSE(client_socket.IsConnected());
+
+ // Connect() will return net::OK before the server rejects the connection.
+ TestCompletionCallback connect_callback;
+ int rv = connect_callback.GetResult(
+ client_socket.Connect(connect_callback.callback()));
+ ASSERT_EQ(net::OK, rv);
+
+ // Run message loop so server can process incoming connection attempt.
+ {
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+ EXPECT_FALSE(client_socket.IsConnected());
+
+ // The server socket should not have called |accept_callback| or modified
+ // |accepted_socket|.
+ EXPECT_FALSE(accept_callback.have_result());
+ EXPECT_EQ(accepted_socket, kInvalidSocket);
+}
+
+// Normal cases including read/write are tested by UnixDomainClientSocketTest.
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/dynamic_application_loader.cc b/shell/dynamic_application_loader.cc
new file mode 100644
index 0000000..e8a4fb2
--- /dev/null
+++ b/shell/dynamic_application_loader.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 "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/format_macros.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/services/public/interfaces/network/url_loader.mojom.h"
+#include "shell/context.h"
+#include "shell/data_pipe_peek.h"
+#include "shell/filename_util.h"
+#include "shell/switches.h"
+#include "url/url_util.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+static const char kMojoMagic[] = "#!mojo:";
+static const size_t kMaxShebangLength = 2048;
+
+void IgnoreResult(bool result) {
+}
+
+} // namespace
+
+// 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(MimeTypeToURLMap* mime_type_to_url,
+ Context* context,
+ DynamicServiceRunnerFactory* runner_factory,
+ ScopedMessagePipeHandle shell_handle,
+ ApplicationLoader::LoadCallback load_callback,
+ const LoaderCompleteCallback& loader_complete_callback)
+ : shell_handle_(shell_handle.Pass()),
+ load_callback_(load_callback),
+ loader_complete_callback_(loader_complete_callback),
+ context_(context),
+ mime_type_to_url_(mime_type_to_url),
+ runner_factory_(runner_factory),
+ weak_ptr_factory_(this) {}
+
+ virtual ~Loader() {}
+
+ protected:
+ virtual URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) = 0;
+
+ virtual void AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) = 0;
+
+ virtual std::string MimeType() = 0;
+
+ virtual bool HasMojoMagic() = 0;
+
+ virtual bool PeekFirstLine(std::string* line) = 0;
+
+ void Load() {
+ // If the response begins with a #!mojo:<content-handler-url>, use it.
+ GURL url;
+ std::string shebang;
+ if (PeekContentHandler(&shebang, &url)) {
+ load_callback_.Run(
+ url, shell_handle_.Pass(),
+ AsURLResponse(context_->task_runners()->blocking_pool(),
+ static_cast<int>(shebang.size())));
+ return;
+ }
+
+ MimeTypeToURLMap::iterator iter = mime_type_to_url_->find(MimeType());
+ if (iter != mime_type_to_url_->end()) {
+ load_callback_.Run(
+ iter->second, shell_handle_.Pass(),
+ AsURLResponse(context_->task_runners()->blocking_pool(), 0));
+ return;
+ }
+
+ // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo
+ // application. That could either mean looking for the platform-specific dll
+ // header, or looking for some specific mojo signature prepended to the
+ // library.
+
+ AsPath(context_->task_runners()->blocking_pool(),
+ base::Bind(&Loader::RunLibrary, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ void ReportComplete() { loader_complete_callback_.Run(this); }
+
+ private:
+ bool PeekContentHandler(std::string* mojo_shebang,
+ GURL* mojo_content_handler_url) {
+ std::string shebang;
+ if (HasMojoMagic() && PeekFirstLine(&shebang)) {
+ GURL url(shebang.substr(2, std::string::npos));
+ if (url.is_valid()) {
+ *mojo_shebang = shebang;
+ *mojo_content_handler_url = url;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void RunLibrary(const base::FilePath& path, bool path_exists) {
+ DCHECK(shell_handle_.is_valid());
+
+ if (!path_exists) {
+ LOG(ERROR) << "Library not started because library path '" << path.value()
+ << "' does not exist.";
+ ReportComplete();
+ return;
+ }
+
+ runner_ = runner_factory_->Create(context_);
+ runner_->Start(
+ path, shell_handle_.Pass(),
+ base::Bind(&Loader::ReportComplete, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ ScopedMessagePipeHandle shell_handle_;
+ ApplicationLoader::LoadCallback load_callback_;
+ LoaderCompleteCallback loader_complete_callback_;
+ Context* context_;
+ MimeTypeToURLMap* mime_type_to_url_;
+ 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,
+ MimeTypeToURLMap* mime_type_to_url,
+ Context* context,
+ DynamicServiceRunnerFactory* runner_factory,
+ ScopedMessagePipeHandle shell_handle,
+ ApplicationLoader::LoadCallback load_callback,
+ const LoaderCompleteCallback& loader_complete_callback)
+ : Loader(mime_type_to_url,
+ context,
+ runner_factory,
+ shell_handle.Pass(),
+ load_callback,
+ loader_complete_callback),
+ url_(url),
+ path_(UrlToFile(url)) {
+ Load();
+ }
+
+ private:
+ static base::FilePath UrlToFile(const GURL& url) {
+ DCHECK(url.SchemeIsFile());
+ url::RawCanonOutputW<1024> output;
+ url::DecodeURLEscapeSequences(
+ url.path().data(), static_cast<int>(url.path().length()), &output);
+ base::string16 decoded_path =
+ base::string16(output.data(), output.length());
+#if defined(OS_WIN)
+ base::TrimString(decoded_path, L"/", &decoded_path);
+ base::FilePath path(decoded_path);
+#else
+ base::FilePath path(base::UTF16ToUTF8(decoded_path));
+#endif
+ return path;
+ }
+
+ URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) override {
+ URLResponsePtr response(URLResponse::New());
+ response->url = String::From(url_);
+ DataPipe data_pipe;
+ response->body = data_pipe.consumer_handle.Pass();
+ int64 file_size;
+ if (base::GetFileSize(path_, &file_size)) {
+ response->headers = Array<String>(1);
+ response->headers[0] =
+ base::StringPrintf("Content-Length: %" PRId64, file_size);
+ }
+ common::CopyFromFile(path_, data_pipe.producer_handle.Pass(), skip,
+ task_runner, base::Bind(&IgnoreResult));
+ return response.Pass();
+ }
+
+ void AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) override {
+ // Async for consistency with network case.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
+ }
+
+ std::string MimeType() override { return ""; }
+
+ bool HasMojoMagic() override {
+ std::string magic;
+ ReadFileToString(path_, &magic, strlen(kMojoMagic));
+ return magic == kMojoMagic;
+ }
+
+ bool PeekFirstLine(std::string* line) override {
+ std::string start_of_file;
+ ReadFileToString(path_, &start_of_file, kMaxShebangLength);
+ size_t return_position = start_of_file.find('\n');
+ if (return_position == std::string::npos)
+ return false;
+ *line = start_of_file.substr(0, return_position + 1);
+ return true;
+ }
+
+ GURL url_;
+ base::FilePath path_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalLoader);
+};
+
+// A loader for network files.
+class DynamicApplicationLoader::NetworkLoader : public Loader {
+ public:
+ NetworkLoader(const GURL& url,
+ NetworkService* network_service,
+ MimeTypeToURLMap* mime_type_to_url,
+ Context* context,
+ DynamicServiceRunnerFactory* runner_factory,
+ ScopedMessagePipeHandle shell_handle,
+ ApplicationLoader::LoadCallback load_callback,
+ const LoaderCompleteCallback& loader_complete_callback)
+ : Loader(mime_type_to_url,
+ context,
+ runner_factory,
+ shell_handle.Pass(),
+ load_callback,
+ loader_complete_callback),
+ weak_ptr_factory_(this) {
+ StartNetworkRequest(url, network_service);
+ }
+
+ ~NetworkLoader() override {
+ if (!path_.empty())
+ base::DeleteFile(path_, false);
+ }
+
+ private:
+ // TODO(hansmuller): Revisit this when a real peek operation is available.
+ static const MojoDeadline kPeekTimeout = MOJO_DEADLINE_INDEFINITE;
+
+ URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) override {
+ if (skip != 0) {
+ MojoResult result = ReadDataRaw(
+ response_->body.get(), nullptr, &skip,
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE | MOJO_READ_DATA_FLAG_DISCARD);
+ DCHECK_EQ(result, MOJO_RESULT_OK);
+ }
+ return response_.Pass();
+ }
+
+ void AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) override {
+ if (!path_.empty() || !response_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
+ return;
+ }
+ base::CreateTemporaryFile(&path_);
+ common::CopyToFile(response_->body.Pass(), path_, task_runner,
+ base::Bind(callback, path_));
+ }
+
+ std::string MimeType() override {
+ DCHECK(response_);
+ return response_->mime_type;
+ }
+
+ bool HasMojoMagic() override {
+ std::string magic;
+ return BlockingPeekNBytes(response_->body.get(), &magic, strlen(kMojoMagic),
+ kPeekTimeout) &&
+ magic == kMojoMagic;
+ }
+
+ bool PeekFirstLine(std::string* line) override {
+ return BlockingPeekLine(response_->body.get(), line, kMaxShebangLength,
+ kPeekTimeout);
+ }
+
+ void StartNetworkRequest(const GURL& url, NetworkService* network_service) {
+ 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(GetProxy(&url_loader_));
+ url_loader_->Start(request.Pass(),
+ base::Bind(&NetworkLoader::OnLoadComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ void OnLoadComplete(URLResponsePtr response) {
+ if (response->error) {
+ LOG(ERROR) << "Error (" << response->error->code << ": "
+ << response->error->description << ") while fetching "
+ << response->url;
+ ReportComplete();
+ return;
+ }
+ response_ = response.Pass();
+ Load();
+ }
+
+ URLLoaderPtr url_loader_;
+ URLResponsePtr response_;
+ base::FilePath path_;
+ base::WeakPtrFactory<NetworkLoader> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkLoader);
+};
+
+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) {
+ DCHECK(content_handler_url.is_valid())
+ << "Content handler URL is invalid for mime type " << mime_type;
+ mime_type_to_url_[mime_type] = content_handler_url;
+}
+
+void DynamicApplicationLoader::Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback load_callback) {
+ if (url.SchemeIsFile()) {
+ loaders_.push_back(new LocalLoader(
+ url, &mime_type_to_url_, context_, runner_factory_.get(),
+ shell_handle.Pass(), load_callback, loader_complete_callback_));
+ return;
+ }
+
+ if (!network_service_) {
+ context_->application_manager()->ConnectToService(
+ GURL("mojo:network_service"), &network_service_);
+ }
+
+ loaders_.push_back(
+ new NetworkLoader(url, network_service_.get(), &mime_type_to_url_,
+ context_, runner_factory_.get(), shell_handle.Pass(),
+ load_callback, 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/shell/dynamic_application_loader.h b/shell/dynamic_application_loader.h
new file mode 100644
index 0000000..1dc9a8a
--- /dev/null
+++ b/shell/dynamic_application_loader.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 SHELL_DYNAMIC_APPLICATION_LOADER_H_
+#define 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 "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);
+ ~DynamicApplicationLoader() override;
+
+ void RegisterContentHandler(const std::string& mime_type,
+ const GURL& content_handler_url);
+
+ // ApplicationLoader methods:
+ void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) override;
+ 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 // SHELL_DYNAMIC_APPLICATION_LOADER_H_
diff --git a/shell/dynamic_application_loader_unittest.cc b/shell/dynamic_application_loader_unittest.cc
new file mode 100644
index 0000000..ad4726d
--- /dev/null
+++ b/shell/dynamic_application_loader_unittest.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 "base/files/scoped_temp_dir.h"
+#include "shell/context.h"
+#include "shell/dynamic_application_loader.h"
+#include "shell/dynamic_service_runner.h"
+#include "shell/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;
+ }
+ ~TestDynamicServiceRunner() override {
+ state_->runner_was_destroyed = true;
+ base::MessageLoop::current()->Quit();
+ }
+ 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) {}
+ ~TestDynamicServiceRunnerFactory() override {}
+ 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() {}
+ ~DynamicApplicationLoaderTest() override {}
+ 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(FilePathToFileURL(temp_dir.path().Append(nonexistent_file)));
+ MessagePipe pipe;
+ loader_->Load(context_.application_manager(), url, pipe.handle0.Pass(),
+ ApplicationLoader::SimpleLoadCallback());
+ 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/shell/dynamic_service_runner.cc b/shell/dynamic_service_runner.cc
new file mode 100644
index 0000000..fc0a0ab
--- /dev/null
+++ b/shell/dynamic_service_runner.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 "shell/dynamic_service_runner.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.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::NativeLibrary library) {
+ typedef size_t (*SetThunksFn)(const Thunks* thunks);
+ SetThunksFn set_thunks = reinterpret_cast<SetThunksFn>(
+ base::GetFunctionPointerFromNativeLibrary(library, 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;
+}
+
+} // namespace
+
+base::NativeLibrary DynamicServiceRunner::LoadAndRunService(
+ const base::FilePath& app_path,
+ ScopedMessagePipeHandle service_handle) {
+ DVLOG(2) << "Loading/running Mojo app in process from library: "
+ << app_path.value();
+ base::NativeLibraryLoadError error;
+ base::NativeLibrary app_library = base::LoadNativeLibrary(app_path, &error);
+ do {
+ if (!app_library) {
+ LOG(ERROR) << "Failed to load app library (error: " << error.ToString()
+ << ")";
+ break;
+ }
+ // Go shared library support requires us to initialize the runtime before we
+ // start running any go code. This is a temporary patch.
+ typedef void (*InitGoRuntimeFn)();
+ InitGoRuntimeFn init_go_runtime = reinterpret_cast<InitGoRuntimeFn>(
+ base::GetFunctionPointerFromNativeLibrary(app_library,
+ "InitGoRuntime"));
+ if (init_go_runtime) {
+ DVLOG(2) << "InitGoRuntime: Initializing Go Runtime found in app";
+ init_go_runtime();
+ }
+
+ if (!SetThunks(&MojoMakeSystemThunks, "MojoSetSystemThunks", app_library)) {
+ LOG(ERROR) << app_path.value() << " MojoSetSystemThunks not found";
+ break;
+ }
+
+ if (SetThunks(&MojoMakeGLES2ControlThunks, "MojoSetGLES2ControlThunks",
+ app_library)) {
+ // If we have the control thunks, we should also have the GLES2
+ // implementation thunks.
+ if (!SetThunks(&MojoMakeGLES2ImplThunks, "MojoSetGLES2ImplThunks",
+ app_library)) {
+ LOG(ERROR) << app_path.value()
+ << " has MojoSetGLES2ControlThunks, "
+ "but doesn't have MojoSetGLES2ImplThunks.";
+ break;
+ }
+
+ // 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>(
+ base::GetFunctionPointerFromNativeLibrary(app_library, "MojoMain"));
+ if (!main_function) {
+ LOG(ERROR) << app_path.value() << " MojoMain not found";
+ break;
+ }
+ // |MojoMain()| takes ownership of the service handle.
+ MojoResult result = main_function(service_handle.release().value());
+ if (result < MOJO_RESULT_OK) {
+ LOG(ERROR) << app_path.value() << " MojoMain returned error(" << result
+ << ")";
+ }
+ } while (false);
+
+ return app_library;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/dynamic_service_runner.h b/shell/dynamic_service_runner.h
new file mode 100644
index 0000000..7408789
--- /dev/null
+++ b/shell/dynamic_service_runner.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 SHELL_DYNAMIC_SERVICE_RUNNER_H_
+#define SHELL_DYNAMIC_SERVICE_RUNNER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/native_library.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;
+
+ // Loads the service in the DSO specificed by |app_path| and prepares it for
+ // execution. Runs the DSO's exported function MojoMain().
+ // The NativeLibrary is returned and ownership transferred to the caller.
+ // This is so if it is unloaded at all, this can be done safely after this
+ // thread is destroyed and any thread-local destructors have been executed.
+ static base::NativeLibrary LoadAndRunService(
+ const base::FilePath& app_path,
+ ScopedMessagePipeHandle service_handle);
+};
+
+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 // SHELL_DYNAMIC_SERVICE_RUNNER_H_
diff --git a/shell/external_application_listener.h b/shell/external_application_listener.h
new file mode 100644
index 0000000..15b930e
--- /dev/null
+++ b/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 SHELL_EXTERNAL_APPLICATION_LISTENER_H_
+#define 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 "shell/domain_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 // SHELL_EXTERNAL_APPLICATION_LISTENER_H_
diff --git a/shell/external_application_listener_posix.cc b/shell/external_application_listener_posix.cc
new file mode 100644
index 0000000..adc6725
--- /dev/null
+++ b/shell/external_application_listener_posix.cc
@@ -0,0 +1,194 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_descriptor.h"
+#include "shell/external_application_registrar.mojom.h"
+#include "shell/incoming_connection_listener_posix.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);
+ ~RegistrarImpl() override;
+
+ void OnConnectionError() override;
+
+ embedder::ChannelInit channel_init;
+
+ private:
+ virtual void Register(
+ const String& app_url,
+ const mojo::Callback<void(ShellPtr)>& 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(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(
+ 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,
+ const mojo::Callback<void(ShellPtr)>& callback) {
+ MessagePipe pipe;
+ register_callback_.Run(app_url.To<GURL>(), pipe.handle0.Pass());
+ callback.Run(MakeProxy<Shell>(pipe.handle1.Pass()));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/external_application_listener_posix.h b/shell/external_application_listener_posix.h
new file mode 100644
index 0000000..13e671a
--- /dev/null
+++ b/shell/external_application_listener_posix.h
@@ -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.
+
+#ifndef SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
+#define SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
+
+#include "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 "shell/domain_socket/socket_descriptor.h"
+#include "shell/external_application_registrar.mojom.h"
+#include "shell/incoming_connection_listener_posix.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.
+ ~ExternalApplicationListenerPosix() override;
+
+ // Begin listening (on io_runner) to a socket at listen_socket_path.
+ // Incoming registration requests will be forwarded to register_callback.
+ // Errors are ignored.
+ 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.
+ 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.
+ 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
+ void OnListening(int rv) override;
+ void OnConnection(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(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 // SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
diff --git a/shell/external_application_listener_unittest.cc b/shell/external_application_listener_unittest.cc
new file mode 100644
index 0000000..5a76f60
--- /dev/null
+++ b/shell/external_application_listener_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 "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_loader.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 "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/test_completion_callback.h"
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+#include "shell/external_application_listener_posix.h"
+#include "shell/external_application_registrar.mojom.h"
+#include "shell/external_application_registrar_connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+class NotAnApplicationLoader : public ApplicationLoader {
+ public:
+ NotAnApplicationLoader() {}
+ ~NotAnApplicationLoader() override {}
+
+ void Load(ApplicationManager* application_manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) override {
+ NOTREACHED();
+ }
+
+ void OnApplicationError(ApplicationManager* manager,
+ const GURL& url) override {
+ NOTREACHED();
+ }
+};
+
+class ExternalApplicationListenerTest : public testing::Test {
+ public:
+ ExternalApplicationListenerTest() : io_thread_("io thread") {}
+ ~ExternalApplicationListenerTest() override {}
+
+ 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_;
+ base::FilePath socket_path_;
+ scoped_ptr<ExternalApplicationListener> listener_;
+};
+
+namespace {
+
+class StubShellImpl : public InterfaceImpl<Shell> {
+ private:
+ 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:
+ void Initialize(Array<String> args) override {}
+
+ 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));
+ 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_),
+ base::Bind(&FakeExternalApplication::OnRegister, base::Unretained(this),
+ register_complete_callback));
+ application_impl_ = application_impl.Pass();
+ }
+
+ void ConnectToAppByUrl(std::string app_url) {
+ ServiceProviderPtr sp;
+ ptr_->ConnectToApplication(app_url, GetProxy(&sp));
+ }
+
+ const std::string& url() { return url_; }
+
+ private:
+ void OnRegister(base::Closure complete_callback, ShellPtr shell) {
+ ptr_ = shell.Pass();
+ ptr_.set_client(application_impl_.get());
+ complete_callback.Run();
+ }
+
+ 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.Pass(), 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.Pass(),
+ 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) {
+ ApplicationManager::Delegate delegate;
+ ApplicationManager application_manager(&delegate);
+ application_manager.set_default_loader(
+ scoped_ptr<ApplicationLoader>(new NotAnApplicationLoader));
+
+ 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/shell/external_application_listener_win.cc b/shell/external_application_listener_win.cc
new file mode 100644
index 0000000..af34598
--- /dev/null
+++ b/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 "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/shell/external_application_listener_win.h b/shell/external_application_listener_win.h
new file mode 100644
index 0000000..522d3f2
--- /dev/null
+++ b/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 SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
+#define SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
+
+#include "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 // SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
diff --git a/shell/external_application_registrar.mojom b/shell/external_application_registrar.mojom
new file mode 100644
index 0000000..a523f33
--- /dev/null
+++ b/shell/external_application_registrar.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;
+
+import "mojo/public/interfaces/application/shell.mojom";
+
+// 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 pass back a Shell bound to
+// an impl capable of servicing the external application's connection requests.
+interface ExternalApplicationRegistrar {
+ Register(string application_url) => (Shell shell);
+};
diff --git a/shell/external_application_registrar_connection.cc b/shell/external_application_registrar_connection.cc
new file mode 100644
index 0000000..2abcccc
--- /dev/null
+++ b/shell/external_application_registrar_connection.cc
@@ -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.
+
+#include "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 "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_descriptor.h"
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+#include "shell/external_application_registrar.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+ExternalApplicationRegistrarConnection::ExternalApplicationRegistrarConnection(
+ const base::FilePath& socket_path)
+ : client_socket_(new UnixDomainClientSocket(socket_path.value(), false)) {
+}
+
+ExternalApplicationRegistrarConnection::
+ ~ExternalApplicationRegistrarConnection() {
+ channel_init_.WillDestroySoon();
+}
+
+void ExternalApplicationRegistrarConnection::OnConnectionError() {
+ channel_init_.WillDestroySoon();
+}
+
+void ExternalApplicationRegistrarConnection::Connect(
+ const 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,
+ base::Callback<void(ShellPtr)> register_complete_callback) {
+ DCHECK(!client_socket_);
+ registrar_->Register(String::From(app_url), register_complete_callback);
+}
+
+void ExternalApplicationRegistrarConnection::OnConnect(
+ 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/shell/external_application_registrar_connection.h b/shell/external_application_registrar_connection.h
new file mode 100644
index 0000000..28780e4
--- /dev/null
+++ b/shell/external_application_registrar_connection.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 SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
+#define 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 "shell/domain_socket/socket_descriptor.h"
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+#include "shell/external_application_registrar.mojom.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);
+ ~ExternalApplicationRegistrarConnection() override;
+
+ // Implementation of ErrorHandler
+ 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 CompletionCallback& callback);
+
+ // Registers this app with the shell at the provided URL.
+ void Register(const GURL& app_url,
+ base::Callback<void(ShellPtr)> 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(CompletionCallback callback, int rv);
+
+ scoped_ptr<UnixDomainClientSocket> client_socket_;
+ mojo::embedder::ChannelInit channel_init_;
+ ExternalApplicationRegistrarPtr registrar_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
diff --git a/shell/external_application_test_main.cc b/shell/external_application_test_main.cc
new file mode 100644
index 0000000..0373185
--- /dev/null
+++ b/shell/external_application_test_main.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 "base/bind.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/shell/filename_util.cc b/shell/filename_util.cc
new file mode 100644
index 0000000..9ef0358
--- /dev/null
+++ b/shell/filename_util.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 "shell/filename_util.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "url/gurl.h"
+#include "url/url_canon_internal.h"
+#include "url/url_util.h"
+
+namespace mojo {
+
+// Prefix to prepend to get a file URL.
+static const base::FilePath::CharType kFileURLPrefix[] =
+ FILE_PATH_LITERAL("file://");
+
+GURL FilePathToFileURL(const base::FilePath& path) {
+ // Produce a URL like "file:///C:/foo" for a regular file, or
+ // "file://///server/path" for UNC. The URL canonicalizer will fix up the
+ // latter case to be the canonical UNC form: "file://server/path"
+ base::FilePath::StringType url_string(kFileURLPrefix);
+ if (!path.IsAbsolute()) {
+ base::FilePath current_dir;
+ PathService::Get(base::DIR_CURRENT, ¤t_dir);
+ url_string.append(current_dir.value());
+ url_string.push_back(base::FilePath::kSeparators[0]);
+ }
+ url_string.append(path.value());
+
+ // Now do replacement of some characters. Since we assume the input is a
+ // literal filename, anything the URL parser might consider special should
+ // be escaped here.
+
+ // This must be the first substitution since others will introduce percents as
+ // the escape character
+ ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("%"),
+ FILE_PATH_LITERAL("%25"));
+
+ // A semicolon is supposed to be some kind of separator according to RFC 2396.
+ ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL(";"),
+ FILE_PATH_LITERAL("%3B"));
+
+ ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("#"),
+ FILE_PATH_LITERAL("%23"));
+
+ ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("?"),
+ FILE_PATH_LITERAL("%3F"));
+
+#if defined(OS_POSIX)
+ ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("\\"),
+ FILE_PATH_LITERAL("%5C"));
+#endif
+
+ return GURL(url_string);
+}
+
+} // namespace mojo
diff --git a/shell/filename_util.h b/shell/filename_util.h
new file mode 100644
index 0000000..10fa8fe
--- /dev/null
+++ b/shell/filename_util.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 SHELL_FILENAME_UTIL_H_
+#define SHELL_FILENAME_UTIL_H_
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace mojo {
+
+// Given the full path to a file name, creates a file: URL. The returned URL
+// may not be valid if the input is malformed.
+GURL FilePathToFileURL(const base::FilePath& path);
+
+} // namespace mojo
+
+#endif // SHELL_FILENAME_UTIL_H_
diff --git a/shell/in_process_dynamic_service_runner.cc b/shell/in_process_dynamic_service_runner.cc
new file mode 100644
index 0000000..0c0a20f
--- /dev/null
+++ b/shell/in_process_dynamic_service_runner.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shell/in_process_dynamic_service_runner.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/platform_thread.h"
+
+namespace mojo {
+namespace shell {
+
+InProcessDynamicServiceRunner::InProcessDynamicServiceRunner(Context* context)
+ : app_library_(nullptr) {
+}
+
+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.
+ if (app_library_)
+ base::UnloadNativeLibrary(app_library_);
+}
+
+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()
+ << " thread id=" << base::PlatformThread::CurrentId();
+
+ app_library_ = LoadAndRunService(app_path_, service_handle_.Pass());
+ app_completed_callback_runner_.Run();
+ app_completed_callback_runner_.Reset();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/in_process_dynamic_service_runner.h b/shell/in_process_dynamic_service_runner.h
new file mode 100644
index 0000000..c4310df
--- /dev/null
+++ b/shell/in_process_dynamic_service_runner.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 SHELL_IN_PROCESS_DYNAMIC_SERVICE_RUNNER_H_
+#define SHELL_IN_PROCESS_DYNAMIC_SERVICE_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/threading/simple_thread.h"
+#include "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);
+ ~InProcessDynamicServiceRunner() override;
+
+ // |DynamicServiceRunner| method:
+ void Start(const base::FilePath& app_path,
+ ScopedMessagePipeHandle service_handle,
+ const base::Closure& app_completed_callback) override;
+
+ private:
+ // |base::DelegateSimpleThread::Delegate| method:
+ void Run() override;
+
+ base::FilePath app_path_;
+ ScopedMessagePipeHandle service_handle_;
+ base::Callback<bool(void)> app_completed_callback_runner_;
+
+ base::NativeLibrary app_library_;
+ scoped_ptr<base::DelegateSimpleThread> thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(InProcessDynamicServiceRunner);
+};
+
+typedef DynamicServiceRunnerFactoryImpl<InProcessDynamicServiceRunner>
+ InProcessDynamicServiceRunnerFactory;
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_IN_PROCESS_DYNAMIC_SERVICE_RUNNER_H_
diff --git a/shell/in_process_dynamic_service_runner_unittest.cc b/shell/in_process_dynamic_service_runner_unittest.cc
new file mode 100644
index 0000000..a2ff900
--- /dev/null
+++ b/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 "shell/context.h"
+#include "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/shell/incoming_connection_listener_posix.cc b/shell/incoming_connection_listener_posix.cc
new file mode 100644
index 0000000..cf9dfb3
--- /dev/null
+++ b/shell/incoming_connection_listener_posix.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_descriptor.h"
+#include "shell/domain_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 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_(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) << "Directory 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_.ListenWithPath(socket_address, 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_.Accept(
+ &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_ == 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_ = kInvalidSocket;
+ }
+
+ // Continue waiting to accept incoming connections...
+ Accept();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/incoming_connection_listener_posix.h b/shell/incoming_connection_listener_posix.h
new file mode 100644
index 0000000..ffd1396
--- /dev/null
+++ b/shell/incoming_connection_listener_posix.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 SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
+#define 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 "shell/domain_socket/socket_descriptor.h"
+#include "shell/domain_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
+ // shell/domain_socket/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(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_;
+ UnixDomainServerSocket listen_socket_;
+ base::ThreadChecker listen_thread_checker_;
+
+ SocketDescriptor incoming_socket_;
+
+ base::WeakPtrFactory<IncomingConnectionListenerPosix> weak_ptr_factory_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
diff --git a/shell/incoming_connection_listener_unittest.cc b/shell/incoming_connection_listener_unittest.cc
new file mode 100644
index 0000000..1c03889
--- /dev/null
+++ b/shell/incoming_connection_listener_unittest.cc
@@ -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.
+
+#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 "shell/domain_socket/net_errors.h"
+#include "shell/domain_socket/socket_descriptor.h"
+#include "shell/domain_socket/test_completion_callback.h"
+#include "shell/domain_socket/unix_domain_client_socket_posix.h"
+#include "shell/external_application_registrar_connection.h"
+#include "shell/incoming_connection_listener_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() {}
+ ~TestDelegate() override {}
+
+ void OnListening(int rv) override { EXPECT_EQ(net::OK, rv); }
+ void OnConnection(SocketDescriptor incoming) override {
+ EXPECT_NE(kInvalidSocket, incoming);
+ }
+};
+
+// Delegate implementation that expects a (configurable) failure to listen.
+class ListeningFailsDelegate
+ : public IncomingConnectionListenerPosix::Delegate {
+ public:
+ explicit ListeningFailsDelegate(int expected) : expected_error_(expected) {}
+ ~ListeningFailsDelegate() override {}
+
+ void OnListening(int rv) override { EXPECT_EQ(expected_error_, rv); }
+ void OnConnection(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() {}
+ ~IncomingConnectionListenerTest() override {}
+
+ 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(FILE_PATH_LITERAL("dir"))
+ .Append(FILE_PATH_LITERAL("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/shell/init.cc b/shell/init.cc
new file mode 100644
index 0000000..3b93080
--- /dev/null
+++ b/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 "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/shell/init.h b/shell/init.h
new file mode 100644
index 0000000..23a5140
--- /dev/null
+++ b/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 SHELL_INIT_H_
+#define SHELL_INIT_H_
+
+namespace mojo {
+namespace shell {
+
+// Initialization routines shared by desktop and Android main functions.
+
+void InitializeLogging();
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_INIT_H_
diff --git a/shell/launcher_main.cc b/shell/launcher_main.cc
new file mode 100644
index 0000000..a4d0b8a
--- /dev/null
+++ b/shell/launcher_main.cc
@@ -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.
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "shell/external_application_registrar_connection.h"
+#include "shell/in_process_dynamic_service_runner.h"
+#include "shell/init.h"
+#include "url/gurl.h"
+
+namespace {
+const char kAppPath[] = "app-path";
+const char kAppURL[] = "app-url";
+const char kShellPath[] = "shell-path";
+}
+
+class Launcher {
+ public:
+ explicit Launcher(base::CommandLine* command_line)
+ : app_path_(command_line->GetSwitchValuePath(kAppPath)),
+ app_url_(command_line->GetSwitchValueASCII(kAppURL)),
+ loop_(base::MessageLoop::TYPE_IO),
+ connection_(
+ base::FilePath(command_line->GetSwitchValuePath(kShellPath))),
+ connect_result_(0) {}
+ ~Launcher() {}
+
+ int Connect() {
+ DCHECK(!run_loop_.get());
+ run_loop_.reset(new base::RunLoop);
+ connection_.Connect(
+ base::Bind(&Launcher::OnConnected, base::Unretained(this)));
+ run_loop_->Run();
+ run_loop_.reset();
+ return connect_result_;
+ }
+
+ bool Register() {
+ DCHECK(!run_loop_.get());
+ DCHECK(connect_result_ == 0);
+ run_loop_.reset(new base::RunLoop);
+ connection_.Register(
+ app_url_, base::Bind(&Launcher::OnRegistered, base::Unretained(this)));
+ run_loop_->Run();
+ run_loop_.reset();
+ return shell_handle_.is_valid();
+ }
+
+ void Run() {
+ DCHECK(!run_loop_.get());
+ DCHECK(shell_handle_.is_valid());
+ mojo::shell::InProcessDynamicServiceRunner service_runner(nullptr);
+ run_loop_.reset(new base::RunLoop);
+ service_runner.Start(
+ app_path_, shell_handle_.Pass(),
+ base::Bind(&Launcher::OnAppCompleted, base::Unretained(this)));
+ run_loop_->Run();
+ run_loop_.reset();
+ }
+
+ private:
+ void OnConnected(int result) {
+ connect_result_ = result;
+ run_loop_->Quit();
+ }
+
+ void OnRegistered(mojo::ShellPtr shell) {
+ shell_handle_ = shell.PassMessagePipe();
+ run_loop_->Quit();
+ }
+
+ void OnAppCompleted() { run_loop_->Quit(); }
+
+ const base::FilePath app_path_;
+ const GURL app_url_;
+ base::MessageLoop loop_;
+ mojo::shell::ExternalApplicationRegistrarConnection connection_;
+ int connect_result_;
+ mojo::ScopedMessagePipeHandle shell_handle_;
+ scoped_ptr<base::RunLoop> run_loop_;
+};
+
+#if defined(OS_WIN)
+int main(int argc, wchar_t** argv) {
+#else
+int main(int argc, char** argv) {
+#endif
+ base::AtExitManager at_exit;
+ mojo::embedder::Init(scoped_ptr<mojo::embedder::PlatformSupport>(
+ new mojo::embedder::SimplePlatformSupport()));
+
+ base::CommandLine::Init(argc, argv);
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ mojo::shell::InitializeLogging();
+
+ Launcher launcher(command_line);
+ int result = launcher.Connect();
+ if (result < 0) {
+ LOG(ERROR) << "Error(" << result << ") connecting on socket "
+ << command_line->GetSwitchValueASCII(kShellPath);
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ if (!launcher.Register()) {
+ LOG(ERROR) << "Error registering "
+ << command_line->GetSwitchValueASCII(kAppURL);
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ launcher.Run();
+ return MOJO_RESULT_OK;
+}
diff --git a/shell/mojo_url_resolver.cc b/shell/mojo_url_resolver.cc
new file mode 100644
index 0000000..499c6bf
--- /dev/null
+++ b/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 "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 "shell/filename_util.h"
+#include "url/url_util.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+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(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 {
+ const GURL mapped_url(ApplyCustomMappings(mojo_url));
+
+ // Continue resolving if the mapped url is a mojo: url.
+ if (mapped_url.scheme() != "mojo")
+ return mapped_url;
+
+ std::string lib = mapped_url.host() + ".mojo";
+
+ if (!base_url_.is_valid() ||
+ local_file_set_.find(mapped_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);
+}
+
+GURL MojoURLResolver::ApplyCustomMappings(const GURL& url) const {
+ GURL mapped_url(url);
+ for (;;) {
+ std::map<GURL, GURL>::const_iterator it = url_map_.find(mapped_url);
+ if (it == url_map_.end())
+ break;
+ mapped_url = it->second;
+ }
+ return mapped_url;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/mojo_url_resolver.h b/shell/mojo_url_resolver.h
new file mode 100644
index 0000000..a18dac4
--- /dev/null
+++ b/shell/mojo_url_resolver.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 SHELL_MOJO_URL_RESOLVER_H_
+#define SHELL_MOJO_URL_RESOLVER_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#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. If |resolved_url| is
+ // itself a mojo url normal resolution rules apply.
+ 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:
+ // Applies all custom mappings for |url|, returning the last non-mapped url.
+ // For example, if 'a' maps to 'b' and 'b' maps to 'c' calling this with 'a'
+ // returns 'c'.
+ GURL ApplyCustomMappings(const GURL& url) const;
+
+ std::map<GURL, GURL> url_map_;
+ std::set<GURL> local_file_set_;
+ GURL default_base_url_;
+ GURL base_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoURLResolver);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_MOJO_URL_RESOLVER_H_
diff --git a/shell/mojo_url_resolver_unittest.cc b/shell/mojo_url_resolver_unittest.cc
new file mode 100644
index 0000000..77d97db
--- /dev/null
+++ b/shell/mojo_url_resolver_unittest.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 "shell/mojo_url_resolver.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+namespace {
+
+typedef testing::Test MojoURLResolverTest;
+
+TEST_F(MojoURLResolverTest, MojoURLsFallThrough) {
+ MojoURLResolver resolver;
+ resolver.AddCustomMapping(GURL("mojo:test"), GURL("mojo:foo"));
+ const GURL base_url("file:/base");
+ resolver.SetBaseURL(base_url);
+ std::string resolved(resolver.Resolve(GURL("mojo:test")).spec());
+ // Resolved must start with |base_url|.
+ EXPECT_EQ(base_url.spec(), resolved.substr(0, base_url.spec().size()));
+ // And must contain foo.
+ EXPECT_NE(std::string::npos, resolved.find("foo"));
+}
+
+} // namespace
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/shell/network_application_loader.cc b/shell/network_application_loader.cc
new file mode 100644
index 0000000..320e2bc
--- /dev/null
+++ b/shell/network_application_loader.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) {
+ DCHECK(shell_handle.is_valid());
+ 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/shell/network_application_loader.h b/shell/network_application_loader.h
new file mode 100644
index 0000000..5544e19
--- /dev/null
+++ b/shell/network_application_loader.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 SHELL_NETWORK_APPLICATION_LOADER_H_
+#define 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,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) 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 // SHELL_NETWORK_APPLICATION_LOADER_H_
diff --git a/shell/out_of_process_dynamic_service_runner.cc b/shell/out_of_process_dynamic_service_runner.cc
new file mode 100644
index 0000000..f0d32a2
--- /dev/null
+++ b/shell/out_of_process_dynamic_service_runner.cc
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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(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/shell/out_of_process_dynamic_service_runner.h b/shell/out_of_process_dynamic_service_runner.h
new file mode 100644
index 0000000..df2c967
--- /dev/null
+++ b/shell/out_of_process_dynamic_service_runner.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 SHELL_OUT_OF_PROCESS_DYNAMIC_SERVICE_RUNNER_H_
+#define SHELL_OUT_OF_PROCESS_DYNAMIC_SERVICE_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "shell/app_child_process.mojom.h"
+#include "shell/app_child_process_host.h"
+#include "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);
+ ~OutOfProcessDynamicServiceRunner() override;
+
+ // |DynamicServiceRunner| method:
+ void Start(const base::FilePath& app_path,
+ ScopedMessagePipeHandle service_handle,
+ const base::Closure& app_completed_callback) override;
+
+ private:
+ // |AppChildControllerClient| method:
+ void AppCompleted(int32_t result) override;
+
+ Context* const context_;
+
+ base::FilePath app_path_;
+ 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 // SHELL_OUT_OF_PROCESS_DYNAMIC_SERVICE_RUNNER_H_
diff --git a/shell/shell_test_base.cc b/shell/shell_test_base.cc
new file mode 100644
index 0000000..daeef19
--- /dev/null
+++ b/shell/shell_test_base.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "shell/filename_util.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+
+ShellTestBase::ShellTestBase() {
+}
+
+ShellTestBase::~ShellTestBase() {
+}
+
+void ShellTestBase::SetUp() {
+ shell_context_.Init();
+ base::FilePath service_dir;
+ CHECK(PathService::Get(base::DIR_MODULE, &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(
+ FilePathToFileURL(service_dir));
+
+ return shell_context_.ConnectToServiceByName(application_url, service_name)
+ .Pass();
+}
+
+ScopedMessagePipeHandle ShellTestBase::ConnectToServiceViaNetwork(
+ const GURL& application_url,
+ const std::string& service_name) {
+ return shell_context_.ConnectToServiceByName(application_url, service_name)
+ .Pass();
+}
+
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/shell/shell_test_base.h b/shell/shell_test_base.h
new file mode 100644
index 0000000..7b0b5bb
--- /dev/null
+++ b/shell/shell_test_base.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 SHELL_SHELL_TEST_BASE_H_
+#define 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 "shell/context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class GURL;
+
+namespace mojo {
+namespace shell {
+namespace test {
+
+class ShellTestBase : public testing::Test {
+ public:
+ ShellTestBase();
+ ~ShellTestBase() override;
+
+ 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:
+ Context shell_context_;
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellTestBase);
+};
+
+} // namespace test
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_SHELL_TEST_BASE_H_
diff --git a/shell/shell_test_base_unittest.cc b/shell/shell_test_base_unittest.cc
new file mode 100644
index 0000000..d6dbb45
--- /dev/null
+++ b/shell/shell_test_base_unittest.cc
@@ -0,0 +1,308 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 "services/test_service/test_request_tracker.mojom.h"
+#include "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:test_app"); }
+
+ void GetReport(std::vector<ServiceReport>* report) {
+ ConnectToService(GURL("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() {}
+ ~QuitMessageLoopErrorHandler() override {}
+
+ // |ErrorHandler| implementation:
+ 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: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: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: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/shell/shell_test_helper.cc b/shell/shell_test_helper.cc
new file mode 100644
index 0000000..dc1364d
--- /dev/null
+++ b/shell/shell_test_helper.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 "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 "shell/filename_util.h"
+#include "shell/init.h"
+#include "shell/mojo_url_resolver.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(FilePathToFileURL(service_dir));
+}
+
+void ShellTestHelper::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader,
+ const GURL& url) {
+ context_.application_manager()->SetLoaderForURL(loader.Pass(), url);
+}
+
+void ShellTestHelper::AddCustomMapping(const GURL& mojo_url,
+ const GURL& resolved_url) {
+ context_.mojo_url_resolver()->AddCustomMapping(mojo_url, resolved_url);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/shell/shell_test_helper.h b/shell/shell_test_helper.h
new file mode 100644
index 0000000..2d57bd3
--- /dev/null
+++ b/shell/shell_test_helper.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 SHELL_SHELL_TEST_HELPER_H_
+#define SHELL_SHELL_TEST_HELPER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "mojo/application_manager/application_loader.h"
+#include "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);
+
+ // Adds a mapping that is used when resolving mojo urls. See MojoURLResolver
+ // for details.
+ void AddCustomMapping(const GURL& mojo_url, const GURL& resolved_url);
+
+ private:
+ Context context_;
+ base::MessageLoop shell_loop_;
+ scoped_ptr<ApplicationManager::TestAPI> test_api_;
+ DISALLOW_COPY_AND_ASSIGN(ShellTestHelper);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_SHELL_TEST_HELPER_H_
diff --git a/shell/shell_test_main.cc b/shell/shell_test_main.cc
new file mode 100644
index 0000000..d13238c
--- /dev/null
+++ b/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 "shell/child_process.h"
+#include "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/shell/switches.cc b/shell/switches.cc
new file mode 100644
index 0000000..986cd54
--- /dev/null
+++ b/shell/switches.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 "shell/switches.h"
+
+#include "base/basictypes.h"
+
+namespace switches {
+
+namespace {
+// This controls logging verbosity. It's not strictly a switch for mojo_shell,
+// and isn't included in the public switches, but is included here so that it
+// doesn't trigger an error at startup.
+const char kV[] = "v";
+
+} // namespace
+
+// Specify configuration arguments for a Mojo application URL. For example:
+// --args-for='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: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";
+
+// Print the usage message and exit.
+const char kHelp[] = "help";
+
+// 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";
+
+// Specifies a set of mappings to apply when resolving urls. The value is set of
+// ',' separated mappings, where each mapping consists of a pair of urls giving
+// the to/from url to map. For example, 'a=b,c=d' contains two mappings, the
+// first maps 'a' to 'b' and the second 'c' to 'd'.
+const char kURLMappings[] = "url-mappings";
+
+const char* kSwitchArray[] = {kV,
+ kArgsFor,
+ kChildProcessType,
+ kContentHandlers,
+ kDisableCache,
+ kEnableExternalApplications,
+ kEnableMultiprocess,
+ kHelp,
+ kOrigin,
+ kSpy,
+ kURLMappings};
+
+const std::set<std::string> GetAllSwitches() {
+ std::set<std::string> switch_set;
+
+ for (size_t i = 0; i < arraysize(kSwitchArray); ++i)
+ switch_set.insert(kSwitchArray[i]);
+ return switch_set;
+}
+
+} // namespace switches
diff --git a/shell/switches.h b/shell/switches.h
new file mode 100644
index 0000000..c8096fd
--- /dev/null
+++ b/shell/switches.h
@@ -0,0 +1,31 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_SWITCHES_H_
+#define SHELL_SWITCHES_H_
+
+#include <set>
+#include <string>
+
+namespace switches {
+
+// All switches in alphabetical order. The switches should be documented
+// alongside the definition of their values in the .cc file and, as needed,
+// in mojo_main's Usage() function.
+extern const char kArgsFor[];
+extern const char kHelp[];
+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[];
+extern const char kURLMappings[];
+
+extern const std::set<std::string> GetAllSwitches();
+
+} // namespace switches
+
+#endif // SHELL_SWITCHES_H_
diff --git a/shell/task_runners.cc b/shell/task_runners.cc
new file mode 100644
index 0000000..dfadfb8
--- /dev/null
+++ b/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 "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/shell/task_runners.h b/shell/task_runners.h
new file mode 100644
index 0000000..891d3e1
--- /dev/null
+++ b/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 SHELL_TASK_RUNNERS_H_
+#define 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 // SHELL_TASK_RUNNERS_H_
diff --git a/shell/test_child_process.cc b/shell/test_child_process.cc
new file mode 100644
index 0000000..85ab796
--- /dev/null
+++ b/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 "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/shell/test_child_process.h b/shell/test_child_process.h
new file mode 100644
index 0000000..006774b
--- /dev/null
+++ b/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 SHELL_TEST_CHILD_PROCESS_H_
+#define SHELL_TEST_CHILD_PROCESS_H_
+
+#include "base/macros.h"
+#include "shell/child_process.h"
+
+namespace mojo {
+namespace shell {
+
+class TestChildProcess : public ChildProcess {
+ public:
+ TestChildProcess();
+ ~TestChildProcess() override;
+
+ void Main() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestChildProcess);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_TEST_CHILD_PROCESS_H_
diff --git a/shell/ui_application_loader_android.cc b/shell/ui_application_loader_android.cc
new file mode 100644
index 0000000..7c2f2e3
--- /dev/null
+++ b/shell/ui_application_loader_android.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 "shell/ui_application_loader_android.h"
+
+#include "base/bind.h"
+#include "mojo/application_manager/application_manager.h"
+#include "shell/context.h"
+
+namespace mojo {
+
+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,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) {
+ DCHECK(shell_handle.is_valid());
+ context_->ui_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&UIApplicationLoader::LoadOnUIThread, base::Unretained(this),
+ manager, url, base::Passed(&shell_handle)));
+}
+
+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) {
+ loader_->Load(manager, url, shell_handle.Pass(), SimpleLoadCallback());
+}
+
+void UIApplicationLoader::OnApplicationErrorOnUIThread(
+ ApplicationManager* manager,
+ const GURL& url) {
+ loader_->OnApplicationError(manager, url);
+}
+
+void UIApplicationLoader::ShutdownOnUIThread() {
+ // Destroy |loader_| on the thread it's actually used on.
+ loader_.reset();
+}
+
+} // namespace mojo
diff --git a/shell/ui_application_loader_android.h b/shell/ui_application_loader_android.h
new file mode 100644
index 0000000..64b13f0
--- /dev/null
+++ b/shell/ui_application_loader_android.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 SHELL_UI_APPLICATION_LOADER_ANDROID_H_
+#define 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);
+ ~UIApplicationLoader() override;
+
+ // ApplicationLoader overrides:
+ void Load(ApplicationManager* manager,
+ const GURL& url,
+ ScopedMessagePipeHandle shell_handle,
+ LoadCallback callback) override;
+ 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIApplicationLoader);
+};
+
+} // namespace mojo
+
+#endif // SHELL_UI_APPLICATION_LOADER_ANDROID_H_