summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mojo/BUILD.gn4
-rw-r--r--mojo/application/BUILD.gn16
-rw-r--r--mojo/application/DEPS3
-rw-r--r--mojo/application/content_handler_factory.cc130
-rw-r--r--mojo/application/content_handler_factory.h80
-rw-r--r--mojo/common/BUILD.gn21
-rw-r--r--mojo/common/DEPS9
-rw-r--r--mojo/common/data_pipe_file_utils.cc79
-rw-r--r--mojo/common/data_pipe_utils.cc31
-rw-r--r--mojo/common/data_pipe_utils.h17
-rw-r--r--mojo/common/trace_controller_impl.cc49
-rw-r--r--mojo/common/trace_controller_impl.h39
-rw-r--r--mojo/common/tracing_impl.cc30
-rw-r--r--mojo/common/tracing_impl.h35
-rw-r--r--mojo/common/weak_binding_set.h106
-rw-r--r--mojo/common/weak_interface_ptr_set.h88
-rw-r--r--mojo/converters/geometry/geometry_type_converters.cc15
-rw-r--r--mojo/converters/geometry/geometry_type_converters.h9
-rw-r--r--mojo/converters/input_events/input_event_names.h46
-rw-r--r--mojo/converters/input_events/input_events_type_converters.cc265
-rw-r--r--mojo/converters/input_events/input_events_type_converters.h12
-rw-r--r--mojo/gles2/BUILD.gn54
-rw-r--r--mojo/gles2/DEPS4
-rw-r--r--mojo/gles2/README.md5
-rw-r--r--mojo/gles2/command_buffer_client_impl.cc336
-rw-r--r--mojo/gles2/command_buffer_client_impl.h109
-rw-r--r--mojo/gles2/gles2_context.cc64
-rw-r--r--mojo/gles2/gles2_context.h56
-rw-r--r--mojo/gles2/gles2_impl.cc76
-rw-r--r--mojo/public/mojo.gni34
-rw-r--r--mojo/public/mojo_application.gni354
-rw-r--r--mojo/public/mojo_sdk.gni139
-rw-r--r--mojo/services/BUILD.gn6
-rw-r--r--mojo/services/clipboard/BUILD.gn44
-rw-r--r--mojo/services/clipboard/DEPS4
-rw-r--r--mojo/services/clipboard/clipboard_apptest.cc156
-rw-r--r--mojo/services/clipboard/clipboard_standalone_impl.cc89
-rw-r--r--mojo/services/clipboard/clipboard_standalone_impl.h62
-rw-r--r--mojo/services/clipboard/main.cc37
-rw-r--r--mojo/services/gles2/BUILD.gn59
-rw-r--r--mojo/services/gles2/DEPS7
-rw-r--r--mojo/services/gles2/command_buffer_driver.cc250
-rw-r--r--mojo/services/gles2/command_buffer_driver.h103
-rw-r--r--mojo/services/gles2/command_buffer_impl.cc160
-rw-r--r--mojo/services/gles2/command_buffer_impl.h71
-rw-r--r--mojo/services/gles2/command_buffer_type_conversions.cc170
-rw-r--r--mojo/services/gles2/command_buffer_type_conversions.h67
-rw-r--r--mojo/services/gles2/gpu_impl.cc36
-rw-r--r--mojo/services/gles2/gpu_impl.h49
-rw-r--r--mojo/services/gles2/gpu_state.cc21
-rw-r--r--mojo/services/gles2/gpu_state.h52
-rw-r--r--mojo/services/gles2/mojo_buffer_backing.cc34
-rw-r--r--mojo/services/gles2/mojo_buffer_backing.h39
-rw-r--r--mojo/services/keyboard/public/interfaces/BUILD.gn12
-rw-r--r--mojo/services/keyboard/public/interfaces/keyboard.mojom10
-rw-r--r--mojo/services/kiosk_wm/BUILD.gn34
-rw-r--r--mojo/services/kiosk_wm/DEPS6
-rw-r--r--mojo/services/kiosk_wm/README.md6
-rw-r--r--mojo/services/kiosk_wm/kiosk_wm.cc134
-rw-r--r--mojo/services/kiosk_wm/kiosk_wm.h95
-rw-r--r--mojo/services/kiosk_wm/main.cc13
-rw-r--r--mojo/services/kiosk_wm/merged_service_provider.cc35
-rw-r--r--mojo/services/kiosk_wm/merged_service_provider.h41
-rw-r--r--mojo/services/kiosk_wm/navigator_host_impl.cc54
-rw-r--r--mojo/services/kiosk_wm/navigator_host_impl.h43
-rw-r--r--mojo/services/native_viewport/BUILD.gn110
-rw-r--r--mojo/services/native_viewport/DEPS7
-rw-r--r--mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java183
-rw-r--r--mojo/services/native_viewport/main.cc99
-rw-r--r--mojo/services/native_viewport/native_viewport_impl.cc164
-rw-r--r--mojo/services/native_viewport/native_viewport_impl.h90
-rw-r--r--mojo/services/native_viewport/onscreen_context_provider.cc58
-rw-r--r--mojo/services/native_viewport/onscreen_context_provider.h46
-rw-r--r--mojo/services/native_viewport/platform_viewport.h49
-rw-r--r--mojo/services/native_viewport/platform_viewport_android.cc208
-rw-r--r--mojo/services/native_viewport/platform_viewport_android.h83
-rw-r--r--mojo/services/native_viewport/platform_viewport_headless.cc49
-rw-r--r--mojo/services/native_viewport/platform_viewport_headless.h39
-rw-r--r--mojo/services/native_viewport/platform_viewport_stub.cc14
-rw-r--r--mojo/services/native_viewport/platform_viewport_win.cc137
-rw-r--r--mojo/services/native_viewport/platform_viewport_x11.cc167
-rw-r--r--mojo/services/surfaces/BUILD.gn47
-rw-r--r--mojo/services/surfaces/DEPS7
-rw-r--r--mojo/services/surfaces/context_provider_mojo.cc80
-rw-r--r--mojo/services/surfaces/context_provider_mojo.h61
-rw-r--r--mojo/services/surfaces/display_factory_impl.cc36
-rw-r--r--mojo/services/surfaces/display_factory_impl.h44
-rw-r--r--mojo/services/surfaces/display_impl.cc137
-rw-r--r--mojo/services/surfaces/display_impl.h80
-rw-r--r--mojo/services/surfaces/surfaces_impl.cc81
-rw-r--r--mojo/services/surfaces/surfaces_impl.h68
-rw-r--r--mojo/services/surfaces/surfaces_output_surface.cc42
-rw-r--r--mojo/services/surfaces/surfaces_output_surface.h29
-rw-r--r--mojo/services/surfaces/surfaces_scheduler.cc113
-rw-r--r--mojo/services/surfaces/surfaces_scheduler.h58
-rw-r--r--mojo/services/surfaces/surfaces_service_application.cc54
-rw-r--r--mojo/services/surfaces/surfaces_service_application.h55
-rw-r--r--mojo/services/test_service/BUILD.gn61
-rw-r--r--mojo/services/test_service/test_request_tracker.mojom34
-rw-r--r--mojo/services/test_service/test_request_tracker_application.cc63
-rw-r--r--mojo/services/test_service/test_request_tracker_application.h54
-rw-r--r--mojo/services/test_service/test_request_tracker_impl.cc75
-rw-r--r--mojo/services/test_service/test_request_tracker_impl.h69
-rw-r--r--mojo/services/test_service/test_service.mojom19
-rw-r--r--mojo/services/test_service/test_service_application.cc66
-rw-r--r--mojo/services/test_service/test_service_application.h52
-rw-r--r--mojo/services/test_service/test_service_impl.cc65
-rw-r--r--mojo/services/test_service/test_service_impl.h50
-rw-r--r--mojo/services/test_service/test_time_service_impl.cc43
-rw-r--r--mojo/services/test_service/test_time_service_impl.h43
-rw-r--r--mojo/services/test_service/tracked_service.cc53
-rw-r--r--mojo/services/test_service/tracked_service.h38
-rw-r--r--mojo/services/tracing/BUILD.gn29
-rw-r--r--mojo/services/tracing/DEPS3
-rw-r--r--mojo/services/tracing/main.cc116
-rw-r--r--mojo/services/tracing/trace_data_sink.cc43
-rw-r--r--mojo/services/tracing/trace_data_sink.h32
-rw-r--r--mojo/services/tracing/tracing.mojom28
-rw-r--r--mojo/services/view_manager/BUILD.gn179
-rw-r--r--mojo/services/view_manager/DEPS7
-rw-r--r--mojo/services/view_manager/access_policy.h52
-rw-r--r--mojo/services/view_manager/access_policy_delegate.h36
-rw-r--r--mojo/services/view_manager/animation_runner.cc157
-rw-r--r--mojo/services/view_manager/animation_runner.h114
-rw-r--r--mojo/services/view_manager/animation_runner_observer.h23
-rw-r--r--mojo/services/view_manager/animation_runner_unittest.cc641
-rw-r--r--mojo/services/view_manager/client_connection.cc39
-rw-r--r--mojo/services/view_manager/client_connection.h62
-rw-r--r--mojo/services/view_manager/connection_manager.cc494
-rw-r--r--mojo/services/view_manager/connection_manager.h241
-rw-r--r--mojo/services/view_manager/connection_manager_delegate.h50
-rw-r--r--mojo/services/view_manager/default_access_policy.cc112
-rw-r--r--mojo/services/view_manager/default_access_policy.h53
-rw-r--r--mojo/services/view_manager/display_manager.cc187
-rw-r--r--mojo/services/view_manager/display_manager.h96
-rw-r--r--mojo/services/view_manager/ids.h62
-rw-r--r--mojo/services/view_manager/main.cc12
-rw-r--r--mojo/services/view_manager/scheduled_animation_group.cc352
-rw-r--r--mojo/services/view_manager/scheduled_animation_group.h110
-rw-r--r--mojo/services/view_manager/scheduled_animation_group_unittest.cc97
-rw-r--r--mojo/services/view_manager/server_view.cc206
-rw-r--r--mojo/services/view_manager/server_view.h109
-rw-r--r--mojo/services/view_manager/server_view_delegate.h65
-rw-r--r--mojo/services/view_manager/test_change_tracker.cc314
-rw-r--r--mojo/services/view_manager/test_change_tracker.h157
-rw-r--r--mojo/services/view_manager/test_server_view_delegate.cc57
-rw-r--r--mojo/services/view_manager/test_server_view_delegate.h47
-rw-r--r--mojo/services/view_manager/view_coordinate_conversions.cc42
-rw-r--r--mojo/services/view_manager/view_coordinate_conversions.h24
-rw-r--r--mojo/services/view_manager/view_coordinate_conversions_unittest.cc33
-rw-r--r--mojo/services/view_manager/view_manager_app.cc137
-rw-r--r--mojo/services/view_manager/view_manager_app.h86
-rw-r--r--mojo/services/view_manager/view_manager_client_apptest.cc613
-rw-r--r--mojo/services/view_manager/view_manager_service_apptest.cc1499
-rw-r--r--mojo/services/view_manager/view_manager_service_impl.cc654
-rw-r--r--mojo/services/view_manager/view_manager_service_impl.h260
-rw-r--r--mojo/services/view_manager/view_manager_service_unittest.cc471
-rw-r--r--mojo/services/view_manager/window_manager_access_policy.cc97
-rw-r--r--mojo/services/view_manager/window_manager_access_policy.h52
-rw-r--r--mojo/services/window_manager/BUILD.gn129
-rw-r--r--mojo/services/window_manager/DEPS6
-rw-r--r--mojo/services/window_manager/basic_focus_rules.cc171
-rw-r--r--mojo/services/window_manager/basic_focus_rules.h49
-rw-r--r--mojo/services/window_manager/capture_controller.cc103
-rw-r--r--mojo/services/window_manager/capture_controller.h49
-rw-r--r--mojo/services/window_manager/capture_controller_observer.h20
-rw-r--r--mojo/services/window_manager/focus_controller.cc317
-rw-r--r--mojo/services/window_manager/focus_controller.h102
-rw-r--r--mojo/services/window_manager/focus_controller_observer.h35
-rw-r--r--mojo/services/window_manager/focus_controller_unittest.cc1197
-rw-r--r--mojo/services/window_manager/focus_rules.h65
-rw-r--r--mojo/services/window_manager/hit_test.h45
-rw-r--r--mojo/services/window_manager/main.cc92
-rw-r--r--mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc36
-rw-r--r--mojo/services/window_manager/native_viewport_event_dispatcher_impl.h42
-rw-r--r--mojo/services/window_manager/run_all_unittests.cc43
-rw-r--r--mojo/services/window_manager/view_event_dispatcher.cc55
-rw-r--r--mojo/services/window_manager/view_event_dispatcher.h47
-rw-r--r--mojo/services/window_manager/view_target.cc192
-rw-r--r--mojo/services/window_manager/view_target.h100
-rw-r--r--mojo/services/window_manager/view_target_unittest.cc81
-rw-r--r--mojo/services/window_manager/view_targeter.cc109
-rw-r--r--mojo/services/window_manager/view_targeter.h44
-rw-r--r--mojo/services/window_manager/view_targeter_unittest.cc114
-rw-r--r--mojo/services/window_manager/window_manager_api_unittest.cc260
-rw-r--r--mojo/services/window_manager/window_manager_app.cc417
-rw-r--r--mojo/services/window_manager/window_manager_app.h213
-rw-r--r--mojo/services/window_manager/window_manager_app_android.cc20
-rw-r--r--mojo/services/window_manager/window_manager_app_linux.cc14
-rw-r--r--mojo/services/window_manager/window_manager_app_win.cc14
-rw-r--r--mojo/services/window_manager/window_manager_apptest.cc212
-rw-r--r--mojo/services/window_manager/window_manager_delegate.h26
-rw-r--r--mojo/services/window_manager/window_manager_impl.cc98
-rw-r--r--mojo/services/window_manager/window_manager_impl.h68
-rw-r--r--mojo/services/window_manager/window_manager_test_util.cc39
-rw-r--r--mojo/services/window_manager/window_manager_test_util.h43
-rw-r--r--mojo/shell/BUILD.gn473
-rw-r--r--mojo/shell/DEPS6
-rw-r--r--mojo/shell/PRESUBMIT.py16
-rw-r--r--mojo/shell/android/android_handler.cc111
-rw-r--r--mojo/shell/android/android_handler.h46
-rw-r--r--mojo/shell/android/android_handler_loader.cc25
-rw-r--r--mojo/shell/android/android_handler_loader.h37
-rw-r--r--mojo/shell/android/apk/AndroidManifest.xml31
-rw-r--r--mojo/shell/android/apk/res/values/strings.xml10
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java138
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java45
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java179
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java35
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java67
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java57
-rw-r--r--mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java105
-rw-r--r--mojo/shell/android/background_application_loader.cc73
-rw-r--r--mojo/shell/android/background_application_loader.h64
-rw-r--r--mojo/shell/android/background_application_loader_unittest.cc51
-rw-r--r--mojo/shell/android/bootstrap.cc43
-rw-r--r--mojo/shell/android/keyboard_impl.cc36
-rw-r--r--mojo/shell/android/keyboard_impl.h35
-rw-r--r--mojo/shell/android/library_loader.cc46
-rw-r--r--mojo/shell/android/main.cc222
-rw-r--r--mojo/shell/android/main.h18
-rw-r--r--mojo/shell/android/native_viewport_application_loader.cc58
-rw-r--r--mojo/shell/android/native_viewport_application_loader.h64
-rw-r--r--mojo/shell/android/run_android_application_function.h27
-rw-r--r--mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java40
-rw-r--r--mojo/shell/android/ui_application_loader_android.cc48
-rw-r--r--mojo/shell/android/ui_application_loader_android.h54
-rw-r--r--mojo/shell/app_child_process.cc302
-rw-r--r--mojo/shell/app_child_process.h30
-rw-r--r--mojo/shell/app_child_process.mojom17
-rw-r--r--mojo/shell/app_child_process_host.cc84
-rw-r--r--mojo/shell/app_child_process_host.h55
-rw-r--r--mojo/shell/app_child_process_host_unittest.cc63
-rw-r--r--mojo/shell/application_manager/BUILD.gn75
-rw-r--r--mojo/shell/application_manager/application_loader.h38
-rw-r--r--mojo/shell/application_manager/application_manager.cc523
-rw-r--r--mojo/shell/application_manager/application_manager.h239
-rw-r--r--mojo/shell/application_manager/application_manager_unittest.cc829
-rw-r--r--mojo/shell/application_manager/data_pipe_peek.cc160
-rw-r--r--mojo/shell/application_manager/data_pipe_peek.h38
-rw-r--r--mojo/shell/application_manager/fetcher.cc38
-rw-r--r--mojo/shell/application_manager/fetcher.h76
-rw-r--r--mojo/shell/application_manager/identity.cc28
-rw-r--r--mojo/shell/application_manager/identity.h31
-rw-r--r--mojo/shell/application_manager/local_fetcher.cc107
-rw-r--r--mojo/shell/application_manager/local_fetcher.h51
-rw-r--r--mojo/shell/application_manager/native_runner.h58
-rw-r--r--mojo/shell/application_manager/network_fetcher.cc250
-rw-r--r--mojo/shell/application_manager/network_fetcher.h83
-rw-r--r--mojo/shell/application_manager/query_util.cc31
-rw-r--r--mojo/shell/application_manager/query_util.h23
-rw-r--r--mojo/shell/application_manager/query_util_unittest.cc47
-rw-r--r--mojo/shell/application_manager/shell_impl.cc62
-rw-r--r--mojo/shell/application_manager/shell_impl.h62
-rw-r--r--mojo/shell/application_manager/test.mojom23
-rw-r--r--mojo/shell/child_process.cc40
-rw-r--r--mojo/shell/child_process.h51
-rw-r--r--mojo/shell/child_process_host.cc89
-rw-r--r--mojo/shell/child_process_host.h73
-rw-r--r--mojo/shell/command_line_util.cc90
-rw-r--r--mojo/shell/command_line_util.h38
-rw-r--r--mojo/shell/command_line_util_unittest.cc83
-rw-r--r--mojo/shell/context.cc328
-rw-r--r--mojo/shell/context.h87
-rw-r--r--mojo/shell/data_pipe_peek_unittest.cc113
-rw-r--r--mojo/shell/desktop/main.cc185
-rw-r--r--mojo/shell/filename_util.cc72
-rw-r--r--mojo/shell/filename_util.h28
-rw-r--r--mojo/shell/in_process_native_runner.cc72
-rw-r--r--mojo/shell/in_process_native_runner.h67
-rw-r--r--mojo/shell/in_process_native_runner_unittest.cc23
-rw-r--r--mojo/shell/init.cc24
-rw-r--r--mojo/shell/init.h18
-rw-r--r--mojo/shell/launcher_main.cc116
-rw-r--r--mojo/shell/native_application_support.cc124
-rw-r--r--mojo/shell/native_application_support.h51
-rw-r--r--mojo/shell/native_runner_unittest.cc102
-rw-r--r--mojo/shell/out_of_process_native_runner.cc72
-rw-r--r--mojo/shell/out_of_process_native_runner.h65
-rw-r--r--mojo/shell/shell_apptest.cc197
-rw-r--r--mojo/shell/shell_test_base.cc73
-rw-r--r--mojo/shell/shell_test_base.h59
-rw-r--r--mojo/shell/shell_test_base_android.cc47
-rw-r--r--mojo/shell/shell_test_base_unittest.cc309
-rw-r--r--mojo/shell/shell_test_helper.cc46
-rw-r--r--mojo/shell/shell_test_helper.h52
-rw-r--r--mojo/shell/shell_test_main.cc33
-rw-r--r--mojo/shell/switches.cc106
-rw-r--r--mojo/shell/switches.h35
-rw-r--r--mojo/shell/task_runners.cc39
-rw-r--r--mojo/shell/task_runners.h53
-rw-r--r--mojo/shell/test/BUILD.gn28
-rw-r--r--mojo/shell/test/pingable.mojom9
-rw-r--r--mojo/shell/test/pingable_app.cc69
-rw-r--r--mojo/shell/url_resolver.cc118
-rw-r--r--mojo/shell/url_resolver.h79
-rw-r--r--mojo/shell/url_resolver_unittest.cc151
-rw-r--r--third_party/mojo/src/mojo/public/mojo.gni8
-rw-r--r--third_party/mojo/src/mojo/public/mojo_application.gni8
-rw-r--r--third_party/mojo/src/mojo/public/tools/BUILD.gn23
300 files changed, 29656 insertions, 191 deletions
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
index f4432cd..66e69af 100644
--- a/mojo/BUILD.gn
+++ b/mojo/BUILD.gn
@@ -17,6 +17,10 @@ group("mojo") {
if (is_android) {
deps += [ "//mojo/android" ]
}
+
+ if (!is_component_build) {
+ deps += [ "//mojo/shell" ]
+ }
}
group("tests") {
diff --git a/mojo/application/BUILD.gn b/mojo/application/BUILD.gn
index dda877e..0087ebc 100644
--- a/mojo/application/BUILD.gn
+++ b/mojo/application/BUILD.gn
@@ -19,6 +19,22 @@ source_set("application") {
]
}
+source_set("content_handler") {
+ sources = [
+ "content_handler_factory.cc",
+ "content_handler_factory.h",
+ ]
+ deps = [
+ ":application",
+ "//base",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ "//third_party/mojo_services/src/content_handler/public/interfaces",
+ "//mojo/services/network/public/interfaces",
+ ]
+}
+
source_set("test_support") {
testonly = true
sources = [
diff --git a/mojo/application/DEPS b/mojo/application/DEPS
new file mode 100644
index 0000000..dfd226d
--- /dev/null
+++ b/mojo/application/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/mojo_services/src/content_handler",
+]
diff --git a/mojo/application/content_handler_factory.cc b/mojo/application/content_handler_factory.cc
new file mode 100644
index 0000000..8518f4c
--- /dev/null
+++ b/mojo/application/content_handler_factory.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/application/content_handler_factory.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/platform_thread.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/message_pump_mojo.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace mojo {
+
+namespace {
+
+class ApplicationThread : public base::PlatformThread::Delegate {
+ public:
+ ApplicationThread(
+ scoped_refptr<base::MessageLoopProxy> handler_thread,
+ const base::Callback<void(ApplicationThread*)>& termination_callback,
+ ContentHandlerFactory::Delegate* handler_delegate,
+ InterfaceRequest<Application> application_request,
+ URLResponsePtr response)
+ : handler_thread_(handler_thread),
+ termination_callback_(termination_callback),
+ handler_delegate_(handler_delegate),
+ application_request_(application_request.Pass()),
+ response_(response.Pass()) {}
+
+ private:
+ void ThreadMain() override {
+ handler_delegate_->RunApplication(application_request_.Pass(),
+ response_.Pass());
+ handler_thread_->PostTask(FROM_HERE,
+ base::Bind(termination_callback_, this));
+ }
+
+ scoped_refptr<base::MessageLoopProxy> handler_thread_;
+ base::Callback<void(ApplicationThread*)> termination_callback_;
+ ContentHandlerFactory::Delegate* handler_delegate_;
+ InterfaceRequest<Application> application_request_;
+ URLResponsePtr response_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApplicationThread);
+};
+
+class ContentHandlerImpl : public ContentHandler {
+ public:
+ ContentHandlerImpl(ContentHandlerFactory::Delegate* delegate,
+ InterfaceRequest<ContentHandler> request)
+ : delegate_(delegate),
+ binding_(this, request.Pass()),
+ weak_factory_(this) {}
+ ~ContentHandlerImpl() override {
+ // We're shutting down and doing cleanup. Cleanup may trigger calls back to
+ // OnThreadEnd(). As we're doing the cleanup here we don't want to do it in
+ // OnThreadEnd() as well. InvalidateWeakPtrs() ensures we don't get any
+ // calls to OnThreadEnd().
+ weak_factory_.InvalidateWeakPtrs();
+ for (auto thread : active_threads_) {
+ base::PlatformThread::Join(thread.second);
+ delete thread.first;
+ }
+ }
+
+ private:
+ // Overridden from ContentHandler:
+ void StartApplication(InterfaceRequest<Application> application_request,
+ URLResponsePtr response) override {
+ ApplicationThread* thread = new ApplicationThread(
+ base::MessageLoopProxy::current(),
+ base::Bind(&ContentHandlerImpl::OnThreadEnd,
+ weak_factory_.GetWeakPtr()),
+ delegate_, application_request.Pass(), response.Pass());
+ base::PlatformThreadHandle handle;
+ bool launched = base::PlatformThread::Create(0, thread, &handle);
+ DCHECK(launched);
+ active_threads_[thread] = handle;
+ }
+
+ void OnThreadEnd(ApplicationThread* thread) {
+ DCHECK(active_threads_.find(thread) != active_threads_.end());
+ base::PlatformThreadHandle handle = active_threads_[thread];
+ active_threads_.erase(thread);
+ base::PlatformThread::Join(handle);
+ delete thread;
+ }
+
+ ContentHandlerFactory::Delegate* delegate_;
+ std::map<ApplicationThread*, base::PlatformThreadHandle> active_threads_;
+ StrongBinding<ContentHandler> binding_;
+ base::WeakPtrFactory<ContentHandlerImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
+};
+
+} // namespace
+
+ContentHandlerFactory::ContentHandlerFactory(Delegate* delegate)
+ : delegate_(delegate) {
+}
+
+ContentHandlerFactory::~ContentHandlerFactory() {
+}
+
+void ContentHandlerFactory::ManagedDelegate::RunApplication(
+ InterfaceRequest<Application> application_request,
+ URLResponsePtr response) {
+ base::MessageLoop loop(common::MessagePumpMojo::Create());
+ auto application =
+ this->CreateApplication(application_request.Pass(), response.Pass());
+ if (application)
+ loop.Run();
+}
+
+void ContentHandlerFactory::Create(ApplicationConnection* connection,
+ InterfaceRequest<ContentHandler> request) {
+ new ContentHandlerImpl(delegate_, request.Pass());
+}
+
+} // namespace mojo
diff --git a/mojo/application/content_handler_factory.h b/mojo/application/content_handler_factory.h
new file mode 100644
index 0000000..aabba6f
--- /dev/null
+++ b/mojo/application/content_handler_factory.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 MOJO_APPLICATION_CONTENT_HANDLER_FACTORY_H_
+#define MOJO_APPLICATION_CONTENT_HANDLER_FACTORY_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h"
+
+namespace mojo {
+
+class ContentHandlerFactory : public InterfaceFactory<ContentHandler> {
+ public:
+ class HandledApplicationHolder {
+ public:
+ virtual ~HandledApplicationHolder() {}
+ };
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ // Implement this method to create the Application. This method will be
+ // called on a new thread. Leaving this method will quit the application.
+ virtual void RunApplication(
+ InterfaceRequest<Application> application_request,
+ URLResponsePtr response) = 0;
+ };
+
+ class ManagedDelegate : public Delegate {
+ public:
+ ~ManagedDelegate() override {}
+ // Implement this method to create the Application for the given content.
+ // This method will be called on a new thread. The application will be run
+ // on this new thread, and the returned value will be kept alive until the
+ // application ends.
+ virtual scoped_ptr<HandledApplicationHolder> CreateApplication(
+ InterfaceRequest<Application> application_request,
+ URLResponsePtr response) = 0;
+
+ private:
+ void RunApplication(InterfaceRequest<Application> application_request,
+ URLResponsePtr response) override;
+ };
+
+ explicit ContentHandlerFactory(Delegate* delegate);
+ ~ContentHandlerFactory() override;
+
+ private:
+ // From InterfaceFactory:
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<ContentHandler> request) override;
+
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHandlerFactory);
+};
+
+template <class A>
+class HandledApplicationHolderImpl
+ : public ContentHandlerFactory::HandledApplicationHolder {
+ public:
+ explicit HandledApplicationHolderImpl(A* value) : value_(value) {}
+
+ private:
+ scoped_ptr<A> value_;
+};
+
+template <class A>
+scoped_ptr<ContentHandlerFactory::HandledApplicationHolder>
+make_handled_factory_holder(A* value) {
+ return make_scoped_ptr(new HandledApplicationHolderImpl<A>(value));
+}
+
+} // namespace mojo
+
+#endif // MOJO_APPLICATION_CONTENT_HANDLER_FACTORY_H_
diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn
index 8044698..8f5dfaa 100644
--- a/mojo/common/BUILD.gn
+++ b/mojo/common/BUILD.gn
@@ -18,6 +18,7 @@ component("common_base") {
sources = [
"common_type_converters.cc",
"common_type_converters.h",
+ "data_pipe_file_utils.cc",
"data_pipe_utils.cc",
"data_pipe_utils.h",
"handle_watcher.cc",
@@ -27,6 +28,8 @@ component("common_base") {
"message_pump_mojo_handler.h",
"time_helper.cc",
"time_helper.h",
+ "weak_binding_set.h",
+ "weak_interface_ptr_set.h",
]
defines = [ "MOJO_COMMON_IMPLEMENTATION" ]
@@ -76,3 +79,21 @@ test("mojo_common_unittests") {
"message_pump_mojo_unittest.cc",
]
}
+
+if (!is_component_build) {
+ source_set("tracing_impl") {
+ sources = [
+ "trace_controller_impl.cc",
+ "trace_controller_impl.h",
+ "tracing_impl.cc",
+ "tracing_impl.h",
+ ]
+
+ deps = [
+ "//base",
+ "//third_party/mojo/src/mojo/public/cpp/application",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//mojo/services/tracing:bindings",
+ ]
+ }
+}
diff --git a/mojo/common/DEPS b/mojo/common/DEPS
index f3ee1c8..81bd86c 100644
--- a/mojo/common/DEPS
+++ b/mojo/common/DEPS
@@ -4,3 +4,12 @@ include_rules = [
"+mojo/common",
"+third_party/mojo/src/mojo/public",
]
+
+specific_include_rules = {
+ "trace_controller_impl\.h": [
+ "+mojo/services/tracing/tracing.mojom.h"
+ ],
+ "tracing_impl\.h": [
+ "+mojo/services/tracing/tracing.mojom.h"
+ ],
+}
diff --git a/mojo/common/data_pipe_file_utils.cc b/mojo/common/data_pipe_file_utils.cc
new file mode 100644
index 0000000..4bc2686
--- /dev/null
+++ b/mojo/common/data_pipe_file_utils.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 "mojo/common/data_pipe_utils.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/location.h"
+#include "base/task_runner_util.h"
+
+namespace mojo {
+namespace common {
+namespace {
+
+bool BlockingCopyFromFile(const base::FilePath& source,
+ ScopedDataPipeProducerHandle destination,
+ uint32_t skip) {
+ base::File file(source, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid())
+ return false;
+ if (file.Seek(base::File::FROM_BEGIN, skip) != skip) {
+ LOG(ERROR) << "Seek of " << skip << " in " << source.value() << " failed";
+ return false;
+ }
+ for (;;) {
+ void* buffer = nullptr;
+ uint32_t buffer_num_bytes = 0;
+ MojoResult result =
+ BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ int bytes_read =
+ file.ReadAtCurrentPos(static_cast<char*>(buffer), buffer_num_bytes);
+ if (bytes_read >= 0) {
+ EndWriteDataRaw(destination.get(), bytes_read);
+ if (bytes_read == 0) {
+ // eof
+ return true;
+ }
+ } else {
+ // error
+ EndWriteDataRaw(destination.get(), 0);
+ return false;
+ }
+ } else if (result == MOJO_RESULT_SHOULD_WAIT) {
+ result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE, nullptr);
+ if (result != MOJO_RESULT_OK) {
+ // If the consumer handle was closed, then treat as EOF.
+ return result == MOJO_RESULT_FAILED_PRECONDITION;
+ }
+ } else {
+ // If the consumer handle was closed, then treat as EOF.
+ return result == MOJO_RESULT_FAILED_PRECONDITION;
+ }
+ }
+#if !defined(OS_WIN)
+ NOTREACHED();
+ return false;
+#endif
+}
+
+} // namespace
+
+void CopyFromFile(const base::FilePath& source,
+ ScopedDataPipeProducerHandle destination,
+ uint32_t skip,
+ base::TaskRunner* task_runner,
+ const base::Callback<void(bool)>& callback) {
+ base::PostTaskAndReplyWithResult(task_runner, FROM_HERE,
+ base::Bind(&BlockingCopyFromFile, source,
+ base::Passed(&destination), skip),
+ callback);
+}
+
+} // namespace common
+} // namespace mojo
diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc
index c99c666..a280dad 100644
--- a/mojo/common/data_pipe_utils.cc
+++ b/mojo/common/data_pipe_utils.cc
@@ -71,6 +71,37 @@ bool BlockingCopyToString(ScopedDataPipeConsumerHandle source,
source.Pass(), base::Bind(&CopyToStringHelper, result));
}
+bool MOJO_COMMON_EXPORT BlockingCopyFromString(
+ const std::string& source,
+ const ScopedDataPipeProducerHandle& destination) {
+ auto it = source.begin();
+ for (;;) {
+ void* buffer = nullptr;
+ uint32_t buffer_num_bytes = 0;
+ MojoResult result =
+ BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ char* char_buffer = static_cast<char*>(buffer);
+ uint32_t byte_index = 0;
+ while (it != source.end() && byte_index < buffer_num_bytes) {
+ char_buffer[byte_index++] = *it++;
+ }
+ EndWriteDataRaw(destination.get(), byte_index);
+ } else if (result == MOJO_RESULT_SHOULD_WAIT) {
+ result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE, nullptr);
+ if (result != MOJO_RESULT_OK) {
+ // If the consumer handle was closed, then treat as EOF.
+ return result == MOJO_RESULT_FAILED_PRECONDITION;
+ }
+ } else {
+ // If the consumer handle was closed, then treat as EOF.
+ return result == MOJO_RESULT_FAILED_PRECONDITION;
+ }
+ }
+}
+
bool BlockingCopyToFile(ScopedDataPipeConsumerHandle source,
const base::FilePath& destination) {
base::ScopedFILE fp(base::OpenFile(destination, "wb"));
diff --git a/mojo/common/data_pipe_utils.h b/mojo/common/data_pipe_utils.h
index 65a0b8b..5b32cfd 100644
--- a/mojo/common/data_pipe_utils.h
+++ b/mojo/common/data_pipe_utils.h
@@ -28,6 +28,13 @@ void MOJO_COMMON_EXPORT CopyToFile(
base::TaskRunner* task_runner,
const base::Callback<void(bool /*success*/)>& callback);
+void MOJO_COMMON_EXPORT
+CopyFromFile(const base::FilePath& source,
+ ScopedDataPipeProducerHandle destination,
+ uint32_t skip,
+ base::TaskRunner* task_runner,
+ const base::Callback<void(bool /*success*/)>& callback);
+
// Copies the data from |source| into |contents| and returns true on success and
// false on error. In case of I/O error, |contents| holds the data that could
// be read from source before the error occurred.
@@ -35,6 +42,16 @@ bool MOJO_COMMON_EXPORT BlockingCopyToString(
ScopedDataPipeConsumerHandle source,
std::string* contents);
+bool MOJO_COMMON_EXPORT BlockingCopyFromString(
+ const std::string& source,
+ const ScopedDataPipeProducerHandle& destination);
+
+// Synchronously copies data from source to the destination file returning true
+// on success and false on error. In case of an error, |destination| holds the
+// data that could be read from the source before the error occured.
+bool MOJO_COMMON_EXPORT BlockingCopyToFile(ScopedDataPipeConsumerHandle source,
+ const base::FilePath& destination);
+
} // namespace common
} // namespace mojo
diff --git a/mojo/common/trace_controller_impl.cc b/mojo/common/trace_controller_impl.cc
new file mode 100644
index 0000000..178f4cd
--- /dev/null
+++ b/mojo/common/trace_controller_impl.cc
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/common/trace_controller_impl.h"
+
+#include "base/trace_event/trace_event.h"
+#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h"
+#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h"
+
+namespace mojo {
+
+TraceControllerImpl::TraceControllerImpl(
+ InterfaceRequest<tracing::TraceController> request)
+ : binding_(this, request.Pass()) {
+}
+
+TraceControllerImpl::~TraceControllerImpl() {
+}
+
+void TraceControllerImpl::StartTracing(
+ const String& categories,
+ tracing::TraceDataCollectorPtr collector) {
+ DCHECK(!collector_.get());
+ collector_ = collector.Pass();
+ std::string categories_str = categories.To<std::string>();
+ base::trace_event::TraceLog::GetInstance()->SetEnabled(
+ base::trace_event::CategoryFilter(categories_str),
+ base::trace_event::TraceLog::RECORDING_MODE,
+ base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL));
+}
+
+void TraceControllerImpl::StopTracing() {
+ base::trace_event::TraceLog::GetInstance()->SetDisabled();
+
+ base::trace_event::TraceLog::GetInstance()->Flush(
+ base::Bind(&TraceControllerImpl::SendChunk, base::Unretained(this)));
+}
+
+void TraceControllerImpl::SendChunk(
+ const scoped_refptr<base::RefCountedString>& events_str,
+ bool has_more_events) {
+ collector_->DataCollected(mojo::String(events_str->data()));
+ if (!has_more_events) {
+ collector_.reset();
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/common/trace_controller_impl.h b/mojo/common/trace_controller_impl.h
new file mode 100644
index 0000000..3f9225a
--- /dev/null
+++ b/mojo/common/trace_controller_impl.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_COMMON_TRACING_CONTROLLER_IMPL_H_
+#define MOJO_COMMON_TRACING_CONTROLLER_IMPL_H_
+
+#include "base/memory/ref_counted_memory.h"
+#include "mojo/services/tracing/tracing.mojom.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h"
+
+namespace mojo {
+
+class TraceControllerImpl : public tracing::TraceController {
+ public:
+ explicit TraceControllerImpl(
+ InterfaceRequest<tracing::TraceController> request);
+
+ ~TraceControllerImpl() override;
+
+ private:
+ // tracing::TraceController implementation:
+ void StartTracing(const String& categories,
+ tracing::TraceDataCollectorPtr collector) override;
+ void StopTracing() override;
+
+ void SendChunk(const scoped_refptr<base::RefCountedString>& events_str,
+ bool has_more_events);
+
+ tracing::TraceDataCollectorPtr collector_;
+ StrongBinding<tracing::TraceController> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceControllerImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_COMMON_TRACING_CONTROLLER_IMPL_H_
diff --git a/mojo/common/tracing_impl.cc b/mojo/common/tracing_impl.cc
new file mode 100644
index 0000000..420a475
--- /dev/null
+++ b/mojo/common/tracing_impl.cc
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/common/tracing_impl.h"
+
+#include "base/trace_event/trace_event.h"
+#include "mojo/common/trace_controller_impl.h"
+#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h"
+#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h"
+
+namespace mojo {
+
+TracingImpl::TracingImpl() {
+}
+
+TracingImpl::~TracingImpl() {
+}
+
+void TracingImpl::Initialize(ApplicationImpl* app) {
+ ApplicationConnection* connection = app->ConnectToApplication("mojo:tracing");
+ connection->AddService(this);
+}
+
+void TracingImpl::Create(ApplicationConnection* connection,
+ InterfaceRequest<tracing::TraceController> request) {
+ new TraceControllerImpl(request.Pass());
+}
+
+} // namespace mojo
diff --git a/mojo/common/tracing_impl.h b/mojo/common/tracing_impl.h
new file mode 100644
index 0000000..c9118e2
--- /dev/null
+++ b/mojo/common/tracing_impl.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_COMMON_TRACING_IMPL_H_
+#define MOJO_COMMON_TRACING_IMPL_H_
+
+#include "base/macros.h"
+#include "mojo/services/tracing/tracing.mojom.h"
+#include "third_party/mojo/src/mojo/public/cpp/application/interface_factory.h"
+
+namespace mojo {
+
+class ApplicationImpl;
+
+class TracingImpl : public InterfaceFactory<tracing::TraceController> {
+ public:
+ TracingImpl();
+ ~TracingImpl() override;
+
+ // This connects to the tracing service and registers ourselves to provide
+ // tracing data on demand.
+ void Initialize(ApplicationImpl* app);
+
+ private:
+ // InterfaceFactory<tracing::TraceController> implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<tracing::TraceController> request) override;
+
+ DISALLOW_COPY_AND_ASSIGN(TracingImpl);
+};
+
+} // namespace mojo
+
+#endif // MOJO_COMMON_TRACING_IMPL_H_
diff --git a/mojo/common/weak_binding_set.h b/mojo/common/weak_binding_set.h
new file mode 100644
index 0000000..8738388
--- /dev/null
+++ b/mojo/common/weak_binding_set.h
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_COMMON_WEAK_BINDING_SET_H_
+#define MOJO_COMMON_WEAK_BINDING_SET_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h"
+
+namespace mojo {
+
+template <typename Interface>
+class WeakBinding;
+
+// Use this class to manage a set of weak pointers to bindings each of which is
+// owned by the pipe they are bound to.
+template <typename Interface>
+class WeakBindingSet : public ErrorHandler {
+ public:
+ WeakBindingSet() : error_handler_(nullptr) {}
+ ~WeakBindingSet() { CloseAllBindings(); }
+
+ void set_error_handler(ErrorHandler* error_handler) {
+ error_handler_ = error_handler;
+ }
+
+ void AddBinding(Interface* impl, InterfaceRequest<Interface> request) {
+ auto binding = new WeakBinding<Interface>(impl, request.Pass());
+ binding->set_error_handler(this);
+ bindings_.push_back(binding->GetWeakPtr());
+ }
+
+ void CloseAllBindings() {
+ for (const auto& it : bindings_) {
+ if (it)
+ it->Close();
+ }
+ bindings_.clear();
+ }
+
+ private:
+ // ErrorHandler implementation.
+ void OnConnectionError() override {
+ // Clear any deleted bindings.
+ bindings_.erase(
+ std::remove_if(bindings_.begin(), bindings_.end(),
+ [](const base::WeakPtr<WeakBinding<Interface>>& p) {
+ return p.get() == nullptr;
+ }),
+ bindings_.end());
+
+ if (error_handler_)
+ error_handler_->OnConnectionError();
+ }
+
+ ErrorHandler* error_handler_;
+ std::vector<base::WeakPtr<WeakBinding<Interface>>> bindings_;
+
+ DISALLOW_COPY_AND_ASSIGN(WeakBindingSet);
+};
+
+template <typename Interface>
+class WeakBinding : public ErrorHandler {
+ public:
+ WeakBinding(Interface* impl, InterfaceRequest<Interface> request)
+ : binding_(impl, request.Pass()),
+ error_handler_(nullptr),
+ weak_ptr_factory_(this) {
+ binding_.set_error_handler(this);
+ }
+
+ ~WeakBinding() override {}
+
+ void set_error_handler(ErrorHandler* error_handler) {
+ error_handler_ = error_handler;
+ }
+
+ base::WeakPtr<WeakBinding> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ void Close() { binding_.Close(); }
+
+ // ErrorHandler implementation.
+ void OnConnectionError() override {
+ ErrorHandler* error_handler = error_handler_;
+ delete this;
+ if (error_handler)
+ error_handler->OnConnectionError();
+ }
+
+ private:
+ mojo::Binding<Interface> binding_;
+ ErrorHandler* error_handler_;
+ base::WeakPtrFactory<WeakBinding> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WeakBinding);
+};
+
+} // namespace mojo
+
+#endif // MOJO_COMMON_WEAK_BINDING_SET_H_
diff --git a/mojo/common/weak_interface_ptr_set.h b/mojo/common/weak_interface_ptr_set.h
new file mode 100644
index 0000000..e2e88a5
--- /dev/null
+++ b/mojo/common/weak_interface_ptr_set.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_COMMON_WEAK_INTERFACE_PTR_SET_H_
+#define MOJO_COMMON_WEAK_INTERFACE_PTR_SET_H_
+
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_ptr.h"
+
+namespace mojo {
+
+template <typename Interface>
+class WeakInterfacePtr;
+
+template <typename Interface>
+class WeakInterfacePtrSet {
+ public:
+ WeakInterfacePtrSet() {}
+ ~WeakInterfacePtrSet() { CloseAll(); }
+
+ void AddInterfacePtr(InterfacePtr<Interface> ptr) {
+ auto weak_interface_ptr = new WeakInterfacePtr<Interface>(ptr.Pass());
+ ptrs_.push_back(weak_interface_ptr->GetWeakPtr());
+ ClearNullInterfacePtrs();
+ }
+
+ template <typename FunctionType>
+ void ForAllPtrs(FunctionType function) {
+ for (const auto& it : ptrs_) {
+ if (it)
+ function(it->get());
+ }
+ ClearNullInterfacePtrs();
+ }
+
+ void CloseAll() {
+ for (const auto& it : ptrs_) {
+ if (it)
+ it->Close();
+ }
+ ptrs_.clear();
+ }
+
+ private:
+ using WPWIPI = base::WeakPtr<WeakInterfacePtr<Interface>>;
+
+ void ClearNullInterfacePtrs() {
+ ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(), [](const WPWIPI& p) {
+ return p.get() == nullptr;
+ }), ptrs_.end());
+ }
+
+ std::vector<WPWIPI> ptrs_;
+};
+
+template <typename Interface>
+class WeakInterfacePtr : public ErrorHandler {
+ public:
+ explicit WeakInterfacePtr(InterfacePtr<Interface> ptr)
+ : ptr_(ptr.Pass()), weak_ptr_factory_(this) {
+ ptr_.set_error_handler(this);
+ }
+ ~WeakInterfacePtr() override {}
+
+ void Close() { ptr_.reset(); }
+
+ Interface* get() { return ptr_.get(); }
+
+ base::WeakPtr<WeakInterfacePtr> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ // ErrorHandler implementation
+ void OnConnectionError() override { delete this; }
+
+ InterfacePtr<Interface> ptr_;
+ base::WeakPtrFactory<WeakInterfacePtr> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WeakInterfacePtr);
+};
+
+} // namespace mojo
+
+#endif // MOJO_COMMON_WEAK_INTERFACE_PTR_SET_H_
diff --git a/mojo/converters/geometry/geometry_type_converters.cc b/mojo/converters/geometry/geometry_type_converters.cc
index ea9166d..fb17e03 100644
--- a/mojo/converters/geometry/geometry_type_converters.cc
+++ b/mojo/converters/geometry/geometry_type_converters.cc
@@ -87,4 +87,19 @@ gfx::RectF TypeConverter<gfx::RectF, RectFPtr>::Convert(const RectFPtr& input) {
return gfx::RectF(input->x, input->y, input->width, input->height);
}
+// static
+Rect TypeConverter<Rect, gfx::Rect>::Convert(const gfx::Rect& input) {
+ Rect rect;
+ rect.x = input.x();
+ rect.y = input.y();
+ rect.width = input.width();
+ rect.height = input.height();
+ return rect;
+}
+
+// static
+gfx::Rect TypeConverter<gfx::Rect, Rect>::Convert(const Rect& input) {
+ return gfx::Rect(input.x, input.y, input.width, input.height);
+}
+
} // namespace mojo
diff --git a/mojo/converters/geometry/geometry_type_converters.h b/mojo/converters/geometry/geometry_type_converters.h
index 2d906c1..cb2a6b8 100644
--- a/mojo/converters/geometry/geometry_type_converters.h
+++ b/mojo/converters/geometry/geometry_type_converters.h
@@ -59,6 +59,15 @@ struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::RectF, RectFPtr> {
static gfx::RectF Convert(const RectFPtr& input);
};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<Rect, gfx::Rect> {
+ static Rect Convert(const gfx::Rect& input);
+};
+template <>
+struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::Rect, Rect> {
+ static gfx::Rect Convert(const Rect& input);
+};
+
} // namespace mojo
#endif // MOJO_CONVERTERS_GEOMETRY_GEOMETRY_TYPE_CONVERTERS_H_
diff --git a/mojo/converters/input_events/input_event_names.h b/mojo/converters/input_events/input_event_names.h
deleted file mode 100644
index 3fd6ef6..0000000
--- a/mojo/converters/input_events/input_event_names.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-MOJO_INPUT_EVENT_NAME(UNKNOWN);
-MOJO_INPUT_EVENT_NAME(MOUSE_PRESSED);
-MOJO_INPUT_EVENT_NAME(MOUSE_DRAGGED);
-MOJO_INPUT_EVENT_NAME(MOUSE_RELEASED);
-MOJO_INPUT_EVENT_NAME(MOUSE_MOVED);
-MOJO_INPUT_EVENT_NAME(MOUSE_ENTERED);
-MOJO_INPUT_EVENT_NAME(MOUSE_EXITED);
-MOJO_INPUT_EVENT_NAME(KEY_PRESSED);
-MOJO_INPUT_EVENT_NAME(KEY_RELEASED);
-MOJO_INPUT_EVENT_NAME(MOUSEWHEEL);
-MOJO_INPUT_EVENT_NAME(MOUSE_CAPTURE_CHANGED);
-MOJO_INPUT_EVENT_NAME(TOUCH_RELEASED);
-MOJO_INPUT_EVENT_NAME(TOUCH_PRESSED);
-MOJO_INPUT_EVENT_NAME(TOUCH_MOVED);
-MOJO_INPUT_EVENT_NAME(TOUCH_CANCELLED);
-MOJO_INPUT_EVENT_NAME(DROP_TARGET_EVENT);
-MOJO_INPUT_EVENT_NAME(TRANSLATED_KEY_PRESS);
-MOJO_INPUT_EVENT_NAME(TRANSLATED_KEY_RELEASE);
-MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_BEGIN);
-MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_END);
-MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_UPDATE);
-MOJO_INPUT_EVENT_NAME(GESTURE_TAP);
-MOJO_INPUT_EVENT_NAME(GESTURE_TAP_DOWN);
-MOJO_INPUT_EVENT_NAME(GESTURE_TAP_CANCEL);
-MOJO_INPUT_EVENT_NAME(GESTURE_TAP_UNCONFIRMED);
-MOJO_INPUT_EVENT_NAME(GESTURE_DOUBLE_TAP);
-MOJO_INPUT_EVENT_NAME(GESTURE_BEGIN);
-MOJO_INPUT_EVENT_NAME(GESTURE_END);
-MOJO_INPUT_EVENT_NAME(GESTURE_TWO_FINGER_TAP);
-MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_BEGIN);
-MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_END);
-MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_UPDATE);
-MOJO_INPUT_EVENT_NAME(GESTURE_LONG_PRESS);
-MOJO_INPUT_EVENT_NAME(GESTURE_LONG_TAP);
-MOJO_INPUT_EVENT_NAME(GESTURE_SWIPE);
-MOJO_INPUT_EVENT_NAME(GESTURE_SHOW_PRESS);
-MOJO_INPUT_EVENT_NAME(GESTURE_WIN8_EDGE_SWIPE);
-MOJO_INPUT_EVENT_NAME(SCROLL);
-MOJO_INPUT_EVENT_NAME(SCROLL_FLING_START);
-MOJO_INPUT_EVENT_NAME(SCROLL_FLING_CANCEL);
-MOJO_INPUT_EVENT_NAME(CANCEL_MODE);
-MOJO_INPUT_EVENT_NAME(UMA_DATA);
diff --git a/mojo/converters/input_events/input_events_type_converters.cc b/mojo/converters/input_events/input_events_type_converters.cc
index f6be2f1..300ec18 100644
--- a/mojo/converters/input_events/input_events_type_converters.cc
+++ b/mojo/converters/input_events/input_events_type_converters.cc
@@ -16,6 +16,70 @@
#include "ui/events/keycodes/keyboard_codes.h"
namespace mojo {
+namespace {
+
+ui::EventType MojoMouseEventTypeToUIEvent(const EventPtr& event) {
+ DCHECK(!event->pointer_data.is_null());
+ DCHECK_EQ(POINTER_KIND_MOUSE, event->pointer_data->kind);
+ switch (event->action) {
+ case EVENT_TYPE_POINTER_DOWN:
+ return ui::ET_MOUSE_PRESSED;
+
+ case EVENT_TYPE_POINTER_UP:
+ return ui::ET_MOUSE_RELEASED;
+
+ case EVENT_TYPE_POINTER_MOVE:
+ DCHECK(event->pointer_data);
+ if (event->pointer_data->horizontal_wheel != 0 ||
+ event->pointer_data->vertical_wheel != 0) {
+ return ui::ET_MOUSEWHEEL;
+ }
+ if (event->flags &
+ (EVENT_FLAGS_LEFT_MOUSE_BUTTON | EVENT_FLAGS_MIDDLE_MOUSE_BUTTON |
+ EVENT_FLAGS_RIGHT_MOUSE_BUTTON)) {
+ return ui::ET_MOUSE_DRAGGED;
+ }
+ return ui::ET_MOUSE_MOVED;
+
+ default:
+ NOTREACHED();
+ }
+
+ return ui::ET_MOUSE_RELEASED;
+}
+
+ui::EventType MojoTouchEventTypeToUIEvent(const EventPtr& event) {
+ DCHECK(!event->pointer_data.is_null());
+ DCHECK_EQ(POINTER_KIND_TOUCH, event->pointer_data->kind);
+ switch (event->action) {
+ case EVENT_TYPE_POINTER_DOWN:
+ return ui::ET_TOUCH_PRESSED;
+
+ case EVENT_TYPE_POINTER_UP:
+ return ui::ET_TOUCH_RELEASED;
+
+ case EVENT_TYPE_POINTER_MOVE:
+ return ui::ET_TOUCH_MOVED;
+
+ case EVENT_TYPE_POINTER_CANCEL:
+ return ui::ET_TOUCH_CANCELLED;
+
+ default:
+ NOTREACHED();
+ }
+
+ return ui::ET_TOUCH_CANCELLED;
+}
+
+void SetPointerDataLocationFromEvent(const ui::LocatedEvent& located_event,
+ PointerData* pointer_data) {
+ pointer_data->x = located_event.location_f().x();
+ pointer_data->y = located_event.location_f().y();
+ pointer_data->screen_x = located_event.root_location_f().x();
+ pointer_data->screen_y = located_event.root_location_f().y();
+}
+
+} // namespace
COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_NONE) ==
static_cast<int32>(ui::EF_NONE),
@@ -60,62 +124,80 @@ COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_MOD3_DOWN) ==
// static
EventType TypeConverter<EventType, ui::EventType>::Convert(ui::EventType type) {
-#define MOJO_INPUT_EVENT_NAME(name) case ui::ET_##name: return EVENT_TYPE_##name
-
switch (type) {
-#include "mojo/converters/input_events/input_event_names.h"
- case ui::ET_LAST:
- NOTREACHED();
- break;
- }
+ case ui::ET_MOUSE_PRESSED:
+ case ui::ET_TOUCH_PRESSED:
+ return EVENT_TYPE_POINTER_DOWN;
-#undef MOJO_INPUT_EVENT_NAME
+ case ui::ET_MOUSE_DRAGGED:
+ case ui::ET_MOUSE_MOVED:
+ case ui::ET_MOUSE_ENTERED:
+ case ui::ET_MOUSE_EXITED:
+ case ui::ET_TOUCH_MOVED:
+ case ui::ET_MOUSEWHEEL:
+ return EVENT_TYPE_POINTER_MOVE;
- NOTREACHED();
- return EVENT_TYPE_UNKNOWN;
-}
+ case ui::ET_MOUSE_RELEASED:
+ case ui::ET_TOUCH_RELEASED:
+ return EVENT_TYPE_POINTER_UP;
-// static
-ui::EventType TypeConverter<ui::EventType, EventType>::Convert(EventType type) {
-#define MOJO_INPUT_EVENT_NAME(name) case EVENT_TYPE_##name: return ui::ET_##name
+ case ui::ET_TOUCH_CANCELLED:
+ return EVENT_TYPE_POINTER_CANCEL;
- switch (type) {
-#include "mojo/converters/input_events/input_event_names.h"
- }
+ case ui::ET_KEY_PRESSED:
+ return EVENT_TYPE_KEY_PRESSED;
-#undef MOJO_INPUT_EVENT_NAME
+ case ui::ET_KEY_RELEASED:
+ return EVENT_TYPE_KEY_RELEASED;
- NOTREACHED();
- return ui::ET_UNKNOWN;
+ default:
+ break;
+ }
+ return EVENT_TYPE_UNKNOWN;
}
-// static
EventPtr TypeConverter<EventPtr, ui::Event>::Convert(const ui::Event& input) {
- EventPtr event(Event::New());
- event->action = ConvertTo<EventType>(input.type());
+ const EventType type = ConvertTo<EventType>(input.type());
+ if (type == EVENT_TYPE_UNKNOWN)
+ return nullptr;
+
+ EventPtr event = Event::New();
+ event->action = type;
event->flags = EventFlags(input.flags());
event->time_stamp = input.time_stamp().ToInternalValue();
- if (input.IsMouseEvent() || input.IsTouchEvent()) {
+ PointerData pointer_data;
+ if (input.IsMouseEvent()) {
const ui::LocatedEvent* located_event =
static_cast<const ui::LocatedEvent*>(&input);
-
- LocationDataPtr location_data(LocationData::New());
- location_data->in_view_location = Point::From(located_event->location());
- if (input.HasNativeEvent()) {
- location_data->screen_location =
- Point::From(ui::EventSystemLocationFromNative(input.native_event()));
+ PointerDataPtr pointer_data(PointerData::New());
+ // TODO(sky): come up with a better way to handle this.
+ pointer_data->pointer_id = std::numeric_limits<int32>::max();
+ pointer_data->kind = POINTER_KIND_MOUSE;
+ SetPointerDataLocationFromEvent(*located_event, pointer_data.get());
+ if (input.IsMouseWheelEvent()) {
+ const ui::MouseWheelEvent* wheel_event =
+ static_cast<const ui::MouseWheelEvent*>(&input);
+ // This conversion assumes we're using the mojo meaning of these values:
+ // [-1 1].
+ pointer_data->horizontal_wheel =
+ static_cast<float>(wheel_event->x_offset()) / 100.0f;
+ pointer_data->vertical_wheel =
+ static_cast<float>(wheel_event->y_offset()) / 100.0f;
}
-
- event->location_data = location_data.Pass();
- }
-
- if (input.IsTouchEvent()) {
+ event->pointer_data = pointer_data.Pass();
+ } else if (input.IsTouchEvent()) {
const ui::TouchEvent* touch_event =
static_cast<const ui::TouchEvent*>(&input);
- TouchDataPtr touch_data(TouchData::New());
- touch_data->pointer_id = touch_event->touch_id();
- event->touch_data = touch_data.Pass();
+ PointerDataPtr pointer_data(PointerData::New());
+ pointer_data->pointer_id = touch_event->touch_id();
+ pointer_data->kind = POINTER_KIND_TOUCH;
+ SetPointerDataLocationFromEvent(*touch_event, pointer_data.get());
+ pointer_data->radius_major = touch_event->radius_x();
+ pointer_data->radius_minor = touch_event->radius_y();
+ pointer_data->pressure = touch_event->force();
+ pointer_data->orientation = touch_event->rotation_angle();
+ event->pointer_data = pointer_data.Pass();
} else if (input.IsKeyEvent()) {
const ui::KeyEvent* key_event = static_cast<const ui::KeyEvent*>(&input);
KeyDataPtr key_data(KeyData::New());
@@ -138,15 +220,7 @@ EventPtr TypeConverter<EventPtr, ui::Event>::Convert(const ui::Event& input) {
key_data->text = key_event->GetText();
key_data->unmodified_text = key_event->GetUnmodifiedText();
}
-
event->key_data = key_data.Pass();
- } else if (input.IsMouseWheelEvent()) {
- const ui::MouseWheelEvent* wheel_event =
- static_cast<const ui::MouseWheelEvent*>(&input);
- MouseWheelDataPtr wheel_data(MouseWheelData::New());
- wheel_data->x_offset = wheel_event->x_offset();
- wheel_data->y_offset = wheel_event->y_offset();
- event->wheel_data = wheel_data.Pass();
}
return event.Pass();
}
@@ -160,18 +234,17 @@ EventPtr TypeConverter<EventPtr, ui::KeyEvent>::Convert(
// static
scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert(
const EventPtr& input) {
- scoped_ptr<ui::Event> ui_event;
- ui::EventType ui_event_type = ConvertTo<ui::EventType>(input->action);
-
- gfx::Point location;
- if (!input->location_data.is_null() &&
- !input->location_data->in_view_location.is_null()) {
- location = input->location_data->in_view_location.To<gfx::Point>();
+ gfx::PointF location;
+ gfx::PointF screen_location;
+ if (!input->pointer_data.is_null()) {
+ location.SetPoint(input->pointer_data->x, input->pointer_data->y);
+ screen_location.SetPoint(input->pointer_data->screen_x,
+ input->pointer_data->screen_y);
}
switch (input->action) {
- case ui::ET_KEY_PRESSED:
- case ui::ET_KEY_RELEASED: {
+ case EVENT_TYPE_KEY_PRESSED:
+ case EVENT_TYPE_KEY_RELEASED: {
scoped_ptr<ui::KeyEvent> key_event;
if (input->key_data->is_char) {
key_event.reset(new ui::KeyEvent(
@@ -181,9 +254,10 @@ scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert(
input->flags));
} else {
key_event.reset(new ui::KeyEvent(
- ui_event_type,
- static_cast<ui::KeyboardCode>(
- input->key_data->key_code),
+ input->action == EVENT_TYPE_KEY_PRESSED ? ui::ET_KEY_PRESSED
+ : ui::ET_KEY_RELEASED,
+
+ static_cast<ui::KeyboardCode>(input->key_data->key_code),
input->flags));
}
key_event->SetExtendedKeyEventData(scoped_ptr<ui::ExtendedKeyEventData>(
@@ -192,54 +266,43 @@ scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert(
input->key_data->text,
input->key_data->unmodified_text)));
key_event->set_platform_keycode(input->key_data->native_key_code);
- ui_event = key_event.Pass();
- break;
- }
- case EVENT_TYPE_MOUSE_PRESSED:
- case EVENT_TYPE_MOUSE_DRAGGED:
- case EVENT_TYPE_MOUSE_RELEASED:
- case EVENT_TYPE_MOUSE_MOVED:
- case EVENT_TYPE_MOUSE_ENTERED:
- case EVENT_TYPE_MOUSE_EXITED: {
- // TODO: last flags isn't right. Need to send changed_flags.
- ui_event.reset(new ui::MouseEvent(
- ui_event_type,
- location,
- location,
- ui::EventFlags(input->flags),
- ui::EventFlags(input->flags)));
- break;
+ return key_event.Pass();
}
- case EVENT_TYPE_MOUSEWHEEL: {
- const gfx::Vector2d offset(input->wheel_data->x_offset,
- input->wheel_data->y_offset);
- ui_event.reset(new ui::MouseWheelEvent(offset,
- location,
- location,
- ui::EventFlags(input->flags),
- ui::EventFlags(input->flags)));
- break;
- }
- case EVENT_TYPE_TOUCH_MOVED:
- case EVENT_TYPE_TOUCH_PRESSED:
- case EVENT_TYPE_TOUCH_CANCELLED:
- case EVENT_TYPE_TOUCH_RELEASED: {
- ui_event.reset(new ui::TouchEvent(
- ui_event_type,
- location,
- ui::EventFlags(input->flags),
- input->touch_data->pointer_id,
- base::TimeDelta::FromInternalValue(input->time_stamp),
- 0.f, 0.f, 0.f, 0.f));
- break;
+ case EVENT_TYPE_POINTER_DOWN:
+ case EVENT_TYPE_POINTER_UP:
+ case EVENT_TYPE_POINTER_MOVE:
+ case EVENT_TYPE_POINTER_CANCEL: {
+ if (input->pointer_data->kind == POINTER_KIND_MOUSE) {
+ // TODO: last flags isn't right. Need to send changed_flags.
+ scoped_ptr<ui::MouseEvent> event(new ui::MouseEvent(
+ MojoMouseEventTypeToUIEvent(input), location, screen_location,
+ ui::EventTimeForNow(), ui::EventFlags(input->flags),
+ ui::EventFlags(input->flags)));
+ if (event->IsMouseWheelEvent()) {
+ // This conversion assumes we're using the mojo meaning of these
+ // values: [-1 1].
+ scoped_ptr<ui::MouseEvent> wheel_event(new ui::MouseWheelEvent(
+ *event,
+ static_cast<int>(input->pointer_data->horizontal_wheel * 100),
+ static_cast<int>(input->pointer_data->vertical_wheel * 100)));
+ event = wheel_event.Pass();
+ }
+ return event.Pass();
+ }
+ scoped_ptr<ui::TouchEvent> touch_event(new ui::TouchEvent(
+ MojoTouchEventTypeToUIEvent(input), location,
+ ui::EventFlags(input->flags), input->pointer_data->pointer_id,
+ base::TimeDelta::FromInternalValue(input->time_stamp),
+ input->pointer_data->radius_major, input->pointer_data->radius_minor,
+ input->pointer_data->orientation, input->pointer_data->pressure));
+ touch_event->set_root_location(screen_location);
+ return touch_event.Pass();
}
default:
- // TODO: support other types.
- // NOTIMPLEMENTED();
- ;
+ NOTIMPLEMENTED();
}
// TODO: need to support time_stamp.
- return ui_event.Pass();
+ return nullptr;
}
} // namespace mojo
diff --git a/mojo/converters/input_events/input_events_type_converters.h b/mojo/converters/input_events/input_events_type_converters.h
index d520006..b9e152f 100644
--- a/mojo/converters/input_events/input_events_type_converters.h
+++ b/mojo/converters/input_events/input_events_type_converters.h
@@ -12,17 +12,14 @@
namespace mojo {
+// NOTE: the mojo input events do not necessarily provide a 1-1 mapping with
+// ui::Event types. Be careful in using them!
template <>
struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventType, ui::EventType> {
static EventType Convert(ui::EventType type);
};
template <>
-struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<ui::EventType, EventType> {
- static ui::EventType Convert(EventType type);
-};
-
-template <>
struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::Event> {
static EventPtr Convert(const ui::Event& input);
};
@@ -33,6 +30,11 @@ struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::KeyEvent> {
};
template <>
+struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::GestureEvent> {
+ static EventPtr Convert(const ui::GestureEvent& input);
+};
+
+template <>
struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<scoped_ptr<ui::Event>, EventPtr> {
static scoped_ptr<ui::Event> Convert(const EventPtr& input);
};
diff --git a/mojo/gles2/BUILD.gn b/mojo/gles2/BUILD.gn
new file mode 100644
index 0000000..3c27743
--- /dev/null
+++ b/mojo/gles2/BUILD.gn
@@ -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.
+
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+
+config("mojo_use_gles2") {
+ defines = [ "MOJO_USE_GLES2_IMPL" ]
+}
+
+config("gles2_use_mojo") {
+ defines = [ "GLES2_USE_MOJO" ]
+}
+
+source_set("gles2") {
+ sources = [
+ "command_buffer_client_impl.cc",
+ "command_buffer_client_impl.h",
+ "gles2_impl.cc",
+ "gles2_context.cc",
+ "gles2_context.h",
+ ]
+
+ defines = [
+ "GL_GLEXT_PROTOTYPES",
+ "MOJO_GLES2_IMPLEMENTATION",
+ ]
+
+ configs += [
+ ":gles2_use_mojo",
+ ":mojo_use_gles2",
+ ]
+ public_configs = [ ":gles2_use_mojo" ]
+ all_dependent_configs = [ ":mojo_use_gles2" ]
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//gpu/command_buffer/client",
+ "//gpu/command_buffer/client:gles2_cmd_helper",
+ "//gpu/command_buffer/client:gles2_implementation",
+ "//gpu/command_buffer/client:gles2_interface",
+ "//gpu/command_buffer/common",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/c/gles2:headers",
+ "//third_party/mojo/src/mojo/public/c/system",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo_services/src/gpu/public/interfaces",
+ "//mojo/services/gles2:lib",
+ ]
+
+ include_dirs = [ ".." ]
+}
diff --git a/mojo/gles2/DEPS b/mojo/gles2/DEPS
new file mode 100644
index 0000000..aa72943
--- /dev/null
+++ b/mojo/gles2/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+gpu",
+ "+third_party/mojo_services/src/gpu",
+] \ No newline at end of file
diff --git a/mojo/gles2/README.md b/mojo/gles2/README.md
new file mode 100644
index 0000000..94b3997a5
--- /dev/null
+++ b/mojo/gles2/README.md
@@ -0,0 +1,5 @@
+Mojo GLES2
+==========
+
+We export this dynamically linked library via mojo/public/gles2 in order to
+hide the gpu/command_buffer/client dependency from clients of the Mojo API.
diff --git a/mojo/gles2/command_buffer_client_impl.cc b/mojo/gles2/command_buffer_client_impl.cc
new file mode 100644
index 0000000..37dc30c
--- /dev/null
+++ b/mojo/gles2/command_buffer_client_impl.cc
@@ -0,0 +1,336 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/gles2/command_buffer_client_impl.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/process/process_handle.h"
+#include "mojo/services/gles2/command_buffer_type_conversions.h"
+#include "mojo/services/gles2/mojo_buffer_backing.h"
+
+namespace gles2 {
+
+namespace {
+
+bool CreateMapAndDupSharedBuffer(size_t size,
+ void** memory,
+ mojo::ScopedSharedBufferHandle* handle,
+ mojo::ScopedSharedBufferHandle* duped) {
+ MojoResult result = mojo::CreateSharedBuffer(NULL, size, handle);
+ if (result != MOJO_RESULT_OK)
+ return false;
+ DCHECK(handle->is_valid());
+
+ result = mojo::DuplicateBuffer(handle->get(), NULL, duped);
+ if (result != MOJO_RESULT_OK)
+ return false;
+ DCHECK(duped->is_valid());
+
+ result = mojo::MapBuffer(
+ handle->get(), 0, size, memory, MOJO_MAP_BUFFER_FLAG_NONE);
+ if (result != MOJO_RESULT_OK)
+ return false;
+ DCHECK(*memory);
+
+ return true;
+}
+
+} // namespace
+
+CommandBufferDelegate::~CommandBufferDelegate() {}
+
+void CommandBufferDelegate::ContextLost() {}
+
+class CommandBufferClientImpl::SyncClientImpl
+ : public mojo::CommandBufferSyncClient {
+ public:
+ SyncClientImpl(mojo::CommandBufferSyncClientPtr* ptr,
+ const MojoAsyncWaiter* async_waiter)
+ : initialized_successfully_(false), binding_(this, ptr, async_waiter) {}
+
+ bool WaitForInitialization() {
+ if (!binding_.WaitForIncomingMethodCall())
+ return false;
+ return initialized_successfully_;
+ }
+
+ mojo::CommandBufferStatePtr WaitForProgress() {
+ if (!binding_.WaitForIncomingMethodCall())
+ return mojo::CommandBufferStatePtr();
+ return command_buffer_state_.Pass();
+ }
+
+ gpu::Capabilities GetCapabilities() {
+ return capabilities_.To<gpu::Capabilities>();
+ }
+
+ private:
+ // CommandBufferSyncClient methods:
+ void DidInitialize(bool success,
+ mojo::GpuCapabilitiesPtr capabilities) override {
+ initialized_successfully_ = success;
+ capabilities_ = capabilities.Pass();
+ }
+ void DidMakeProgress(mojo::CommandBufferStatePtr state) override {
+ command_buffer_state_ = state.Pass();
+ }
+
+ bool initialized_successfully_;
+ mojo::GpuCapabilitiesPtr capabilities_;
+ mojo::CommandBufferStatePtr command_buffer_state_;
+ mojo::Binding<mojo::CommandBufferSyncClient> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncClientImpl);
+};
+
+class CommandBufferClientImpl::SyncPointClientImpl
+ : public mojo::CommandBufferSyncPointClient {
+ public:
+ SyncPointClientImpl(mojo::CommandBufferSyncPointClientPtr* ptr,
+ const MojoAsyncWaiter* async_waiter)
+ : sync_point_(0u), binding_(this, ptr, async_waiter) {}
+
+ uint32_t WaitForInsertSyncPoint() {
+ if (!binding_.WaitForIncomingMethodCall())
+ return 0u;
+ uint32_t result = sync_point_;
+ sync_point_ = 0u;
+ return result;
+ }
+
+ private:
+ void DidInsertSyncPoint(uint32_t sync_point) override {
+ sync_point_ = sync_point;
+ }
+
+ uint32_t sync_point_;
+
+ mojo::Binding<mojo::CommandBufferSyncPointClient> binding_;
+};
+
+CommandBufferClientImpl::CommandBufferClientImpl(
+ CommandBufferDelegate* delegate,
+ const MojoAsyncWaiter* async_waiter,
+ mojo::ScopedMessagePipeHandle command_buffer_handle)
+ : delegate_(delegate),
+ observer_binding_(this),
+ shared_state_(NULL),
+ last_put_offset_(-1),
+ next_transfer_buffer_id_(0),
+ async_waiter_(async_waiter) {
+ command_buffer_.Bind(command_buffer_handle.Pass(), async_waiter);
+ command_buffer_.set_error_handler(this);
+}
+
+CommandBufferClientImpl::~CommandBufferClientImpl() {}
+
+bool CommandBufferClientImpl::Initialize() {
+ const size_t kSharedStateSize = sizeof(gpu::CommandBufferSharedState);
+ void* memory = NULL;
+ mojo::ScopedSharedBufferHandle duped;
+ bool result = CreateMapAndDupSharedBuffer(
+ kSharedStateSize, &memory, &shared_state_handle_, &duped);
+ if (!result)
+ return false;
+
+ shared_state_ = static_cast<gpu::CommandBufferSharedState*>(memory);
+
+ shared_state()->Initialize();
+
+ mojo::CommandBufferSyncClientPtr sync_client;
+ sync_client_impl_.reset(new SyncClientImpl(&sync_client, async_waiter_));
+
+ mojo::CommandBufferSyncPointClientPtr sync_point_client;
+ sync_point_client_impl_.reset(
+ new SyncPointClientImpl(&sync_point_client, async_waiter_));
+
+ mojo::CommandBufferLostContextObserverPtr observer_ptr;
+ observer_binding_.Bind(GetProxy(&observer_ptr), async_waiter_);
+ command_buffer_->Initialize(sync_client.Pass(),
+ sync_point_client.Pass(),
+ observer_ptr.Pass(),
+ duped.Pass());
+
+ // Wait for DidInitialize to come on the sync client pipe.
+ if (!sync_client_impl_->WaitForInitialization()) {
+ VLOG(1) << "Channel encountered error while creating command buffer";
+ return false;
+ }
+ capabilities_ = sync_client_impl_->GetCapabilities();
+ return true;
+}
+
+gpu::CommandBuffer::State CommandBufferClientImpl::GetLastState() {
+ return last_state_;
+}
+
+int32 CommandBufferClientImpl::GetLastToken() {
+ TryUpdateState();
+ return last_state_.token;
+}
+
+void CommandBufferClientImpl::Flush(int32 put_offset) {
+ if (last_put_offset_ == put_offset)
+ return;
+
+ last_put_offset_ = put_offset;
+ command_buffer_->Flush(put_offset);
+}
+
+void CommandBufferClientImpl::OrderingBarrier(int32_t put_offset) {
+ // TODO(jamesr): Implement this more efficiently.
+ Flush(put_offset);
+}
+
+void CommandBufferClientImpl::WaitForTokenInRange(int32 start, int32 end) {
+ TryUpdateState();
+ while (!InRange(start, end, last_state_.token) &&
+ last_state_.error == gpu::error::kNoError) {
+ MakeProgressAndUpdateState();
+ TryUpdateState();
+ }
+}
+
+void CommandBufferClientImpl::WaitForGetOffsetInRange(int32 start, int32 end) {
+ TryUpdateState();
+ while (!InRange(start, end, last_state_.get_offset) &&
+ last_state_.error == gpu::error::kNoError) {
+ MakeProgressAndUpdateState();
+ TryUpdateState();
+ }
+}
+
+void CommandBufferClientImpl::SetGetBuffer(int32 shm_id) {
+ command_buffer_->SetGetBuffer(shm_id);
+ last_put_offset_ = -1;
+}
+
+scoped_refptr<gpu::Buffer> CommandBufferClientImpl::CreateTransferBuffer(
+ size_t size,
+ int32* id) {
+ if (size >= std::numeric_limits<uint32_t>::max())
+ return NULL;
+
+ void* memory = NULL;
+ mojo::ScopedSharedBufferHandle handle;
+ mojo::ScopedSharedBufferHandle duped;
+ if (!CreateMapAndDupSharedBuffer(size, &memory, &handle, &duped))
+ return NULL;
+
+ *id = ++next_transfer_buffer_id_;
+
+ command_buffer_->RegisterTransferBuffer(
+ *id, duped.Pass(), static_cast<uint32_t>(size));
+
+ scoped_ptr<gpu::BufferBacking> backing(
+ new MojoBufferBacking(handle.Pass(), memory, size));
+ scoped_refptr<gpu::Buffer> buffer(new gpu::Buffer(backing.Pass()));
+ return buffer;
+}
+
+void CommandBufferClientImpl::DestroyTransferBuffer(int32 id) {
+ command_buffer_->DestroyTransferBuffer(id);
+}
+
+gpu::Capabilities CommandBufferClientImpl::GetCapabilities() {
+ return capabilities_;
+}
+
+int32_t CommandBufferClientImpl::CreateImage(ClientBuffer buffer,
+ size_t width,
+ size_t height,
+ unsigned internalformat) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+ return -1;
+}
+
+void CommandBufferClientImpl::DestroyImage(int32 id) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+}
+
+int32_t CommandBufferClientImpl::CreateGpuMemoryBufferImage(
+ size_t width,
+ size_t height,
+ unsigned internalformat,
+ unsigned usage) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+ return -1;
+}
+
+uint32_t CommandBufferClientImpl::InsertSyncPoint() {
+ command_buffer_->InsertSyncPoint(true);
+ return sync_point_client_impl_->WaitForInsertSyncPoint();
+}
+
+uint32_t CommandBufferClientImpl::InsertFutureSyncPoint() {
+ command_buffer_->InsertSyncPoint(false);
+ return sync_point_client_impl_->WaitForInsertSyncPoint();
+}
+
+void CommandBufferClientImpl::RetireSyncPoint(uint32_t sync_point) {
+ command_buffer_->RetireSyncPoint(sync_point);
+}
+
+void CommandBufferClientImpl::SignalSyncPoint(uint32_t sync_point,
+ const base::Closure& callback) {
+ // TODO(piman)
+}
+
+void CommandBufferClientImpl::SignalQuery(uint32_t query,
+ const base::Closure& callback) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+}
+
+void CommandBufferClientImpl::SetSurfaceVisible(bool visible) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+}
+
+uint32_t CommandBufferClientImpl::CreateStreamTexture(uint32_t texture_id) {
+ // TODO(piman)
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+void CommandBufferClientImpl::DidLoseContext(int32_t lost_reason) {
+ last_state_.error = gpu::error::kLostContext;
+ last_state_.context_lost_reason =
+ static_cast<gpu::error::ContextLostReason>(lost_reason);
+ delegate_->ContextLost();
+}
+
+void CommandBufferClientImpl::OnConnectionError() {
+ DidLoseContext(gpu::error::kUnknown);
+}
+
+void CommandBufferClientImpl::TryUpdateState() {
+ if (last_state_.error == gpu::error::kNoError)
+ shared_state()->Read(&last_state_);
+}
+
+void CommandBufferClientImpl::MakeProgressAndUpdateState() {
+ command_buffer_->MakeProgress(last_state_.get_offset);
+
+ mojo::CommandBufferStatePtr state = sync_client_impl_->WaitForProgress();
+ if (!state) {
+ VLOG(1) << "Channel encountered error while waiting for command buffer";
+ // TODO(piman): is it ok for this to re-enter?
+ DidLoseContext(gpu::error::kUnknown);
+ return;
+ }
+
+ if (state->generation - last_state_.generation < 0x80000000U)
+ last_state_ = state.To<State>();
+}
+
+void CommandBufferClientImpl::SetLock(base::Lock* lock) {
+}
+
+} // namespace gles2
diff --git a/mojo/gles2/command_buffer_client_impl.h b/mojo/gles2/command_buffer_client_impl.h
new file mode 100644
index 0000000..811bb07
--- /dev/null
+++ b/mojo/gles2/command_buffer_client_impl.h
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_
+#define MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "gpu/command_buffer/client/gpu_control.h"
+#include "gpu/command_buffer/common/command_buffer.h"
+#include "gpu/command_buffer/common/command_buffer_shared.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+
+namespace base {
+class RunLoop;
+}
+
+namespace gles2 {
+class CommandBufferClientImpl;
+
+class CommandBufferDelegate {
+ public:
+ virtual ~CommandBufferDelegate();
+ virtual void ContextLost();
+};
+
+class CommandBufferClientImpl : public mojo::CommandBufferLostContextObserver,
+ public mojo::ErrorHandler,
+ public gpu::CommandBuffer,
+ public gpu::GpuControl {
+ public:
+ explicit CommandBufferClientImpl(
+ CommandBufferDelegate* delegate,
+ const MojoAsyncWaiter* async_waiter,
+ mojo::ScopedMessagePipeHandle command_buffer_handle);
+ ~CommandBufferClientImpl() override;
+
+ // CommandBuffer implementation:
+ bool Initialize() override;
+ State GetLastState() override;
+ int32_t GetLastToken() override;
+ void Flush(int32_t put_offset) override;
+ void OrderingBarrier(int32_t put_offset) override;
+ void WaitForTokenInRange(int32_t start, int32_t end) override;
+ void WaitForGetOffsetInRange(int32_t start, int32_t end) override;
+ void SetGetBuffer(int32_t shm_id) override;
+ scoped_refptr<gpu::Buffer> CreateTransferBuffer(size_t size,
+ int32_t* id) override;
+ void DestroyTransferBuffer(int32_t id) override;
+
+ // gpu::GpuControl implementation:
+ gpu::Capabilities GetCapabilities() override;
+ int32_t CreateImage(ClientBuffer buffer,
+ size_t width,
+ size_t height,
+ unsigned internalformat) override;
+ void DestroyImage(int32_t id) override;
+ int32_t CreateGpuMemoryBufferImage(size_t width,
+ size_t height,
+ unsigned internalformat,
+ unsigned usage) override;
+ uint32 InsertSyncPoint() override;
+ uint32 InsertFutureSyncPoint() override;
+ void RetireSyncPoint(uint32 sync_point) override;
+ void SignalSyncPoint(uint32 sync_point,
+ const base::Closure& callback) override;
+ void SignalQuery(uint32 query, const base::Closure& callback) override;
+ void SetSurfaceVisible(bool visible) override;
+ uint32 CreateStreamTexture(uint32 texture_id) override;
+ void SetLock(base::Lock*) override;
+
+ private:
+ class SyncClientImpl;
+ class SyncPointClientImpl;
+
+ // mojo::CommandBufferLostContextObserver implementation:
+ void DidLoseContext(int32_t lost_reason) override;
+
+ // mojo::ErrorHandler implementation:
+ void OnConnectionError() override;
+
+ void TryUpdateState();
+ void MakeProgressAndUpdateState();
+
+ gpu::CommandBufferSharedState* shared_state() const { return shared_state_; }
+
+ CommandBufferDelegate* delegate_;
+ mojo::Binding<mojo::CommandBufferLostContextObserver> observer_binding_;
+ mojo::CommandBufferPtr command_buffer_;
+ scoped_ptr<SyncClientImpl> sync_client_impl_;
+ scoped_ptr<SyncPointClientImpl> sync_point_client_impl_;
+
+ gpu::Capabilities capabilities_;
+ State last_state_;
+ mojo::ScopedSharedBufferHandle shared_state_handle_;
+ gpu::CommandBufferSharedState* shared_state_;
+ int32_t last_put_offset_;
+ int32_t next_transfer_buffer_id_;
+
+ const MojoAsyncWaiter* async_waiter_;
+};
+
+} // gles2
+
+#endif // MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_
diff --git a/mojo/gles2/gles2_context.cc b/mojo/gles2/gles2_context.cc
new file mode 100644
index 0000000..7334c64
--- /dev/null
+++ b/mojo/gles2/gles2_context.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/gles2/gles2_context.h"
+
+#include "gpu/command_buffer/client/gles2_cmd_helper.h"
+#include "gpu/command_buffer/client/gles2_implementation.h"
+#include "gpu/command_buffer/client/transfer_buffer.h"
+#include "mojo/public/c/gles2/gles2.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace gles2 {
+
+namespace {
+const size_t kDefaultCommandBufferSize = 1024 * 1024;
+const size_t kDefaultStartTransferBufferSize = 1 * 1024 * 1024;
+const size_t kDefaultMinTransferBufferSize = 1 * 256 * 1024;
+const size_t kDefaultMaxTransferBufferSize = 16 * 1024 * 1024;
+}
+
+GLES2Context::GLES2Context(const MojoAsyncWaiter* async_waiter,
+ mojo::ScopedMessagePipeHandle command_buffer_handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure)
+ : command_buffer_(this, async_waiter, command_buffer_handle.Pass()),
+ lost_callback_(lost_callback),
+ closure_(closure) {
+}
+
+GLES2Context::~GLES2Context() {}
+
+bool GLES2Context::Initialize() {
+ if (!command_buffer_.Initialize())
+ return false;
+ gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(&command_buffer_));
+ if (!gles2_helper_->Initialize(kDefaultCommandBufferSize))
+ return false;
+ gles2_helper_->SetAutomaticFlushes(false);
+ transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get()));
+ gpu::Capabilities capabilities = command_buffer_.GetCapabilities();
+ bool bind_generates_resource =
+ !!capabilities.bind_generates_resource_chromium;
+ // TODO(piman): Some contexts (such as compositor) want this to be true, so
+ // this needs to be a public parameter.
+ bool lose_context_when_out_of_memory = false;
+ bool support_client_side_arrays = false;
+ implementation_.reset(
+ new gpu::gles2::GLES2Implementation(gles2_helper_.get(),
+ NULL,
+ transfer_buffer_.get(),
+ bind_generates_resource,
+ lose_context_when_out_of_memory,
+ support_client_side_arrays,
+ &command_buffer_));
+ return implementation_->Initialize(kDefaultStartTransferBufferSize,
+ kDefaultMinTransferBufferSize,
+ kDefaultMaxTransferBufferSize,
+ gpu::gles2::GLES2Implementation::kNoLimit);
+}
+
+void GLES2Context::ContextLost() { lost_callback_(closure_); }
+
+} // namespace gles2
diff --git a/mojo/gles2/gles2_context.h b/mojo/gles2/gles2_context.h
new file mode 100644
index 0000000..092ed74
--- /dev/null
+++ b/mojo/gles2/gles2_context.h
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_GLES2_GLES2_CONTEXT_H_
+#define MOJO_GLES2_GLES2_CONTEXT_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "gpu/command_buffer/client/gles2_implementation.h"
+#include "mojo/gles2/command_buffer_client_impl.h"
+#include "mojo/public/c/gles2/gles2.h"
+
+struct MojoGLES2ContextPrivate {};
+
+namespace gpu {
+class TransferBuffer;
+namespace gles2 {
+class GLES2CmdHelper;
+class GLES2Implementation;
+}
+}
+
+namespace gles2 {
+
+class GLES2Context : public CommandBufferDelegate,
+ public MojoGLES2ContextPrivate {
+ public:
+ explicit GLES2Context(const MojoAsyncWaiter* async_waiter,
+ mojo::ScopedMessagePipeHandle command_buffer_handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure);
+ ~GLES2Context() override;
+ bool Initialize();
+
+ gpu::gles2::GLES2Interface* interface() const {
+ return implementation_.get();
+ }
+ gpu::ContextSupport* context_support() const { return implementation_.get(); }
+
+ private:
+ void ContextLost() override;
+
+ CommandBufferClientImpl command_buffer_;
+ scoped_ptr<gpu::gles2::GLES2CmdHelper> gles2_helper_;
+ scoped_ptr<gpu::TransferBuffer> transfer_buffer_;
+ scoped_ptr<gpu::gles2::GLES2Implementation> implementation_;
+ MojoGLES2ContextLost lost_callback_;
+ void* closure_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(GLES2Context);
+};
+
+} // namespace gles2
+
+#endif // MOJO_GLES2_GLES2_CONTEXT_H_
diff --git a/mojo/gles2/gles2_impl.cc b/mojo/gles2/gles2_impl.cc
new file mode 100644
index 0000000..9a65a6d
--- /dev/null
+++ b/mojo/gles2/gles2_impl.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/c/gles2/gles2.h"
+
+#include "base/lazy_instance.h"
+#include "base/threading/thread_local.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "mojo/gles2/gles2_context.h"
+
+using gles2::GLES2Context;
+
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<gpu::gles2::GLES2Interface> >::Leaky
+ g_gpu_interface;
+
+} // namespace
+
+extern "C" {
+MojoGLES2Context MojoGLES2CreateContext(MojoHandle handle,
+ MojoGLES2ContextLost lost_callback,
+ void* closure,
+ const MojoAsyncWaiter* async_waiter) {
+ mojo::MessagePipeHandle mph(handle);
+ mojo::ScopedMessagePipeHandle scoped_handle(mph);
+ scoped_ptr<GLES2Context> client(new GLES2Context(
+ async_waiter, scoped_handle.Pass(), lost_callback, closure));
+ if (!client->Initialize())
+ client.reset();
+ return client.release();
+}
+
+void MojoGLES2DestroyContext(MojoGLES2Context context) {
+ delete static_cast<GLES2Context*>(context);
+}
+
+void MojoGLES2MakeCurrent(MojoGLES2Context context) {
+ gpu::gles2::GLES2Interface* interface = NULL;
+ if (context) {
+ GLES2Context* client = static_cast<GLES2Context*>(context);
+ interface = client->interface();
+ DCHECK(interface);
+ }
+ g_gpu_interface.Get().Set(interface);
+}
+
+void MojoGLES2SwapBuffers() {
+ DCHECK(g_gpu_interface.Get().Get());
+ g_gpu_interface.Get().Get()->SwapBuffers();
+}
+
+void* MojoGLES2GetGLES2Interface(MojoGLES2Context context) {
+ return static_cast<GLES2Context*>(context)->interface();
+}
+
+void* MojoGLES2GetContextSupport(MojoGLES2Context context) {
+ return static_cast<GLES2Context*>(context)->context_support();
+}
+
+#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \
+ ReturnType gl##Function PARAMETERS { \
+ DCHECK(g_gpu_interface.Get().Get()); \
+ return g_gpu_interface.Get().Get()->Function ARGUMENTS; \
+ }
+#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_miscellaneous_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sub_image_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h"
+#include "mojo/public/c/gles2/gles2_call_visitor_occlusion_query_ext_autogen.h"
+#undef VISIT_GL_CALL
+
+} // extern "C"
diff --git a/mojo/public/mojo.gni b/mojo/public/mojo.gni
new file mode 100644
index 0000000..f2631a0
--- /dev/null
+++ b/mojo/public/mojo.gni
@@ -0,0 +1,34 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/module_args/mojo.gni")
+
+# If using the prebuilt shell, gate its usage by the platforms for which it is
+# published.
+mojo_use_prebuilt_mojo_shell = false
+if (!defined(mojo_build_mojo_shell_from_source) ||
+ !mojo_build_mojo_shell_from_source) {
+ mojo_use_prebuilt_mojo_shell = is_linux || is_android
+}
+
+# If using the prebuilt network service, gate its usage by the platforms for
+# which it is published.
+mojo_use_prebuilt_network_service = false
+if (!defined(mojo_build_network_service_from_source) ||
+ !mojo_build_network_service_from_source) {
+ mojo_use_prebuilt_network_service = is_linux || is_android
+}
+
+# Enable Dart apptest framework by default.
+mojo_use_dart_apptest_framework = true
+if (defined(mojo_disable_dart_apptest_framework) &&
+ mojo_disable_dart_apptest_framework) {
+ mojo_use_dart_apptest_framework = false
+}
+
+# The absolute path to the directory containing the mojo public SDK (i.e., the
+# directory containing mojo/public). The build files within the Mojo public
+# SDK use this variable to allow themselves to be parameterized by the location
+# of the public SDK within a client repo.
+mojo_root = get_path_info("../..", "abspath")
diff --git a/mojo/public/mojo_application.gni b/mojo/public/mojo_application.gni
new file mode 100644
index 0000000..2c7ee4a
--- /dev/null
+++ b/mojo/public/mojo_application.gni
@@ -0,0 +1,354 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/module_args/mojo.gni")
+import("mojo.gni")
+import("mojo_sdk.gni")
+
+# Generate a binary mojo application.The parameters of this template are those
+# of a shared library.
+template("mojo_native_application") {
+ if (defined(invoker.output_name)) {
+ base_target_name = invoker.output_name
+ } else {
+ base_target_name = target_name
+ }
+
+ final_target_name = target_name
+
+ if (!is_nacl) {
+ output = base_target_name + ".mojo"
+ library_target_name = base_target_name + "_library"
+
+ if (is_linux || is_android) {
+ library_name = "lib${library_target_name}.so"
+ } else if (is_win) {
+ library_name = "${library_target_name}.dll"
+ } else if (is_mac) {
+ library_name = "lib${library_target_name}.dylib"
+ } else {
+ assert(false, "Platform not supported.")
+ }
+
+ if (is_android) {
+ # On android, use the stripped version of the library, because applications
+ # are always fetched over the network.
+ library_dir = "${root_out_dir}/lib.stripped"
+ } else {
+ library_dir = root_out_dir
+ }
+
+ shared_library(library_target_name) {
+ if (defined(invoker.cflags)) {
+ cflags = invoker.cflags
+ }
+ if (defined(invoker.cflags_c)) {
+ cflags_c = invoker.cflags_c
+ }
+ if (defined(invoker.cflags_cc)) {
+ cflags_cc = invoker.cflags_cc
+ }
+ if (defined(invoker.cflags_objc)) {
+ cflags_objc = invoker.cflags_objc
+ }
+ if (defined(invoker.cflags_objcc)) {
+ cflags_objcc = invoker.cflags_objcc
+ }
+ if (defined(invoker.defines)) {
+ defines = invoker.defines
+ }
+ if (defined(invoker.include_dirs)) {
+ include_dirs = invoker.include_dirs
+ }
+ if (defined(invoker.ldflags)) {
+ ldflags = invoker.ldflags
+ }
+ if (defined(invoker.lib_dirs)) {
+ lib_dirs = invoker.lib_dirs
+ }
+ if (defined(invoker.libs)) {
+ libs = invoker.libs
+ }
+
+ data_deps = []
+ if (defined(invoker.data_deps)) {
+ data_deps = invoker.data_deps
+ }
+
+ # Copy any necessary prebuilt artifacts.
+ if (mojo_use_prebuilt_mojo_shell) {
+ data_deps +=
+ [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ]
+ }
+ if (mojo_use_prebuilt_network_service) {
+ data_deps += [ rebase_path("mojo/public/tools:copy_network_service",
+ ".",
+ mojo_root) ]
+ }
+
+ deps = rebase_path([
+ "mojo/public/c/system",
+ "mojo/public/platform/native:system",
+ ],
+ ".",
+ mojo_root)
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ if (defined(invoker.forward_dependent_configs_from)) {
+ forward_dependent_configs_from = invoker.forward_dependent_configs_from
+ }
+ if (defined(invoker.public_deps)) {
+ public_deps = invoker.public_deps
+ }
+ if (defined(invoker.all_dependent_configs)) {
+ all_dependent_configs = invoker.all_dependent_configs
+ }
+ if (defined(invoker.public_configs)) {
+ public_configs = invoker.public_configs
+ }
+ if (defined(invoker.check_includes)) {
+ check_includes = invoker.check_includes
+ }
+ if (defined(invoker.configs)) {
+ configs += invoker.configs
+ }
+ if (defined(invoker.data)) {
+ data = invoker.data
+ }
+ if (defined(invoker.inputs)) {
+ inputs = invoker.inputs
+ }
+ if (defined(invoker.public)) {
+ public = invoker.public
+ }
+ if (defined(invoker.sources)) {
+ sources = invoker.sources
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ visibility = [ ":${final_target_name}" ]
+ }
+
+ copy(final_target_name) {
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ deps = [
+ ":${library_target_name}",
+ ]
+
+ sources = [
+ "${library_dir}/${library_name}",
+ ]
+ outputs = [
+ "${root_out_dir}/${output}",
+ ]
+ }
+ } else {
+ nexe_target_name = base_target_name + "_nexe"
+ nexe_name = base_target_name + ".nexe"
+
+ output = "${base_target_name}_${target_cpu}.nexe.mojo"
+
+ executable(nexe_target_name) {
+ output_name = base_target_name
+
+ if (defined(invoker.cflags)) {
+ cflags = invoker.cflags
+ }
+ if (defined(invoker.cflags_c)) {
+ cflags_c = invoker.cflags_c
+ }
+ if (defined(invoker.cflags_cc)) {
+ cflags_cc = invoker.cflags_cc
+ }
+ if (defined(invoker.cflags_objc)) {
+ cflags_objc = invoker.cflags_objc
+ }
+ if (defined(invoker.cflags_objcc)) {
+ cflags_objcc = invoker.cflags_objcc
+ }
+ if (defined(invoker.defines)) {
+ defines = invoker.defines
+ }
+ if (defined(invoker.include_dirs)) {
+ include_dirs = invoker.include_dirs
+ }
+ if (defined(invoker.ldflags)) {
+ ldflags = invoker.ldflags
+ }
+ if (defined(invoker.lib_dirs)) {
+ lib_dirs = invoker.lib_dirs
+ }
+ if (defined(invoker.libs)) {
+ libs = invoker.libs
+ }
+
+ data_deps = []
+ if (defined(invoker.data_deps)) {
+ data_deps = invoker.data_deps
+ }
+
+ # Copy any necessary prebuilt artifacts.
+ if (mojo_use_prebuilt_mojo_shell) {
+ data_deps +=
+ [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ]
+ }
+ if (mojo_use_prebuilt_network_service) {
+ data_deps += [ rebase_path("mojo/public/tools:copy_network_service",
+ ".",
+ mojo_root) ]
+ }
+
+ deps = rebase_path([
+ "mojo/public/c/system",
+ "mojo/public/platform/nacl:system",
+ ],
+ ".",
+ mojo_root)
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ }
+ if (defined(invoker.forward_dependent_configs_from)) {
+ forward_dependent_configs_from = invoker.forward_dependent_configs_from
+ }
+ if (defined(invoker.public_deps)) {
+ public_deps = invoker.public_deps
+ }
+ if (defined(invoker.all_dependent_configs)) {
+ all_dependent_configs = invoker.all_dependent_configs
+ }
+ if (defined(invoker.public_configs)) {
+ public_configs = invoker.public_configs
+ }
+ if (defined(invoker.check_includes)) {
+ check_includes = invoker.check_includes
+ }
+ if (defined(invoker.configs)) {
+ configs += invoker.configs
+ }
+ if (defined(invoker.data)) {
+ data = invoker.data
+ }
+ if (defined(invoker.inputs)) {
+ inputs = invoker.inputs
+ }
+ if (defined(invoker.public)) {
+ public = invoker.public
+ }
+ if (defined(invoker.sources)) {
+ sources = invoker.sources
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ visibility = [ ":${final_target_name}" ]
+ }
+
+ action(target_name) {
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+
+ script = rebase_path("mojo/public/tools/prepend.py", ".", mojo_root)
+
+ input_path = "${root_out_dir}/${nexe_name}"
+ inputs = [
+ input_path,
+ ]
+
+ output_path = "${root_build_dir}/${output}"
+ outputs = [
+ output_path,
+ ]
+
+ deps = [
+ ":${nexe_target_name}",
+ ]
+
+ rebase_input = rebase_path(input_path, root_build_dir)
+ rebase_output = rebase_path(output_path, root_build_dir)
+ args = [
+ "--input=$rebase_input",
+ "--output=$rebase_output",
+ "--line=#!mojo mojo:nacl_content_handler",
+ ]
+ }
+ }
+}
+
+if (is_android) {
+ # Declares an Android Mojo application consisting of an .so file and a
+ # corresponding .dex.jar file.
+ #
+ # Variables:
+ # input_so: the .so file to bundle
+ # input_dex_jar: the .dex.jar file to bundle
+ # output_name (optional): override for the output file name
+ template("mojo_android_application") {
+ assert(defined(invoker.input_so))
+ assert(defined(invoker.input_dex_jar))
+
+ zip_action_name = "${target_name}_zip"
+ zip_action_output = "$target_gen_dir/${target_name}.zip"
+ action(zip_action_name) {
+ script = "//build/android/gn/zip.py"
+
+ inputs = [
+ invoker.input_so,
+ invoker.input_dex_jar,
+ ]
+
+ output = zip_action_output
+ outputs = [
+ output,
+ ]
+
+ rebase_inputs = rebase_path(inputs, root_build_dir)
+ rebase_output = rebase_path(output, root_build_dir)
+ args = [
+ "--inputs=$rebase_inputs",
+ "--output=$rebase_output",
+ ]
+ }
+
+ if (defined(invoker.output_name)) {
+ mojo_output = "$root_out_dir/" + invoker.output_name + ".mojo"
+ } else {
+ mojo_output = "$root_out_dir/" + target_name + ".mojo"
+ }
+
+ action(target_name) {
+ script = rebase_path("mojo/public/tools/prepend.py", ".", mojo_root)
+
+ input = zip_action_output
+ inputs = [
+ input,
+ ]
+
+ output = mojo_output
+ outputs = [
+ output,
+ ]
+
+ rebase_input = rebase_path(input, root_build_dir)
+ rebase_output = rebase_path(output, root_build_dir)
+ args = [
+ "--input=$rebase_input",
+ "--output=$rebase_output",
+ "--line=#!mojo mojo:android_handler",
+ ]
+ }
+ }
+}
diff --git a/mojo/public/mojo_sdk.gni b/mojo/public/mojo_sdk.gni
new file mode 100644
index 0000000..dbed84c
--- /dev/null
+++ b/mojo/public/mojo_sdk.gni
@@ -0,0 +1,139 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# The absolute path to the directory containing the mojo public SDK (i.e., the
+# directory containing mojo/public). The build files within the Mojo public
+# SDK use this variable to allow themselves to be parameterized by the location
+# of the public SDK within a client repo.
+mojo_root = get_path_info("../..", "abspath")
+
+# Takes as input a "source_set" that includes dependencies that are relative to
+# the parent directory of the Mojo public SDK (given in |mojo_sdk_deps|).
+# Generates a source_set that has the mojo_sdk_deps added as ordinary deps
+# rebased to the current directory.
+# By default, restricts the entries that are given in invoker.deps and
+# invoker.public_deps to be only within the same file and on a small set of
+# whitelisted external dependencies. This check can be elided by setting
+# restrict_external_deps to false in the invoker. DO NOT DO THIS in
+# //mojo/public.
+#
+# Example of a mojo_sdk_source_set:
+#
+# mojo_sdk_source_set("foo") {
+# sources = [
+# "foo.h",
+# "foo.cc",
+# ]
+#
+# # Same-file deps are specified in the ordinary way. Any external
+# dependencies are specified the same way (although in general there should
+# be very few of these).
+# deps = [
+# ":bar",
+# ]
+#
+# # Mojo SDK deps are specified relative to the containing directory of the
+# SDK via mojo_sdk_deps.
+# mojo_sdk_deps = [
+# "mojo/public/cpp/bindings",
+# "mojo/public/cpp/environment",
+# "mojo/public/cpp/system",
+# ]
+# }
+#
+template("mojo_sdk_source_set") {
+ source_set(target_name) {
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ } else {
+ visibility = [ "*" ]
+ }
+ if (defined(invoker.mojo_sdk_visibility)) {
+ foreach(sdk_target, invoker.mojo_sdk_visibility) {
+ # Check that the SDK target was not mistakenly given as an absolute
+ # path.
+ assert(get_path_info(sdk_target, "abspath") != sdk_target)
+ visibility += [ rebase_path(sdk_target, ".", mojo_root) ]
+ }
+ }
+
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+
+ if (defined(invoker.sources)) {
+ sources = invoker.sources
+ }
+
+ if (defined(invoker.defines)) {
+ defines = invoker.defines
+ }
+
+ if (defined(invoker.libs)) {
+ libs = invoker.libs
+ }
+
+ public_configs =
+ [ rebase_path("mojo/public/build/config:mojo_sdk", ".", mojo_root) ]
+ if (defined(invoker.public_configs)) {
+ public_configs += invoker.public_configs
+ }
+
+ if (defined(invoker.configs)) {
+ configs += invoker.configs
+ }
+
+ if (defined(invoker.allow_circular_includes_from)) {
+ allow_circular_includes_from = invoker.allow_circular_includes_from
+ }
+
+ if (defined(invoker.public_deps) || defined(invoker.deps)) {
+ restrict_external_deps = true
+ if (defined(invoker.restrict_external_deps)) {
+ restrict_external_deps = invoker.restrict_external_deps
+ }
+ }
+
+ public_deps = []
+ if (defined(invoker.public_deps)) {
+ foreach(dep, invoker.public_deps) {
+ if (restrict_external_deps) {
+ # The only deps that are not specified relative to the location of
+ # the Mojo SDK should be on targets within the same file or on a
+ # whitelisted set of external dependencies.
+ assert(get_path_info(dep, "dir") == ".")
+ }
+ public_deps += [ dep ]
+ }
+ }
+ if (defined(invoker.mojo_sdk_public_deps)) {
+ foreach(sdk_dep, invoker.mojo_sdk_public_deps) {
+ # Check that the SDK dep was not mistakenly given as an absolute path.
+ assert(get_path_info(sdk_dep, "abspath") != sdk_dep)
+ public_deps += [ rebase_path(sdk_dep, ".", mojo_root) ]
+ }
+ }
+
+ deps = []
+ if (defined(invoker.deps)) {
+ foreach(dep, invoker.deps) {
+ if (restrict_external_deps) {
+ # The only deps that are not specified relative to the location of
+ # the Mojo SDK should be on targets within the same file or on a
+ # whitelisted set of external dependencies.
+ dep_dir = get_path_info(dep, "dir")
+ assert(dep_dir == "." || dep == "//testing/gtest")
+ }
+ deps += [ dep ]
+ }
+ }
+ if (defined(invoker.mojo_sdk_deps)) {
+ foreach(sdk_dep, invoker.mojo_sdk_deps) {
+ # Check that the SDK dep was not mistakenly given as an absolute path.
+ assert(get_path_info(sdk_dep, "abspath") != sdk_dep)
+ deps += [ rebase_path(sdk_dep, ".", mojo_root) ]
+ }
+ }
+ }
+}
diff --git a/mojo/services/BUILD.gn b/mojo/services/BUILD.gn
index eea5522..459288f 100644
--- a/mojo/services/BUILD.gn
+++ b/mojo/services/BUILD.gn
@@ -19,8 +19,14 @@ group("services") {
if (!is_component_build) {
deps += [
+ "//mojo/services/clipboard",
"//mojo/services/html_viewer",
+ "//mojo/services/kiosk_wm",
+ "//mojo/services/native_viewport",
"//mojo/services/network",
+ "//mojo/services/surfaces",
+ "//mojo/services/tracing",
+ "//mojo/services/view_manager",
]
# TODO(GYP): Make this work.
diff --git a/mojo/services/clipboard/BUILD.gn b/mojo/services/clipboard/BUILD.gn
new file mode 100644
index 0000000..cfdcb25
--- /dev/null
+++ b/mojo/services/clipboard/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+
+mojo_native_application("clipboard") {
+ sources = [
+ "clipboard_standalone_impl.cc",
+ "clipboard_standalone_impl.h",
+ "main.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:callback",
+ "//third_party/mojo_services/src/clipboard/public/interfaces",
+ ]
+}
+
+mojo_native_application("apptests") {
+ output_name = "clipboard_apptests"
+
+ testonly = true
+
+ sources = [
+ "clipboard_apptest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/application:test_support",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo_services/src/clipboard/public/interfaces",
+ ]
+
+ data_deps = [ ":clipboard($default_toolchain)" ]
+}
diff --git a/mojo/services/clipboard/DEPS b/mojo/services/clipboard/DEPS
new file mode 100644
index 0000000..e6505c5
--- /dev/null
+++ b/mojo/services/clipboard/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+mojo/application",
+ "+third_party/mojo_services/src/clipboard",
+]
diff --git a/mojo/services/clipboard/clipboard_apptest.cc b/mojo/services/clipboard/clipboard_apptest.cc
new file mode 100644
index 0000000..b6a2fad
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_apptest.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 "base/bind.h"
+#include "base/run_loop.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "third_party/mojo_services/src/clipboard/public/interfaces/clipboard.mojom.h"
+
+using mojo::Array;
+using mojo::Clipboard;
+using mojo::Map;
+using mojo::String;
+
+namespace {
+
+void CopyUint64AndEndRunloop(uint64_t* output,
+ base::RunLoop* run_loop,
+ uint64_t input) {
+ *output = input;
+ run_loop->Quit();
+}
+
+void CopyStringAndEndRunloop(std::string* output,
+ bool* string_is_null,
+ base::RunLoop* run_loop,
+ const Array<uint8_t>& input) {
+ *string_is_null = input.is_null();
+ *output = input.is_null() ? "" : input.To<std::string>();
+ run_loop->Quit();
+}
+
+void CopyVectorStringAndEndRunloop(std::vector<std::string>* output,
+ base::RunLoop* run_loop,
+ const Array<String>& input) {
+ *output = input.To<std::vector<std::string> >();
+ run_loop->Quit();
+}
+
+const char* kUninitialized = "Uninitialized data";
+const char* kPlainTextData = "Some plain data";
+const char* kHtmlData = "<html>data</html>";
+
+} // namespace
+
+namespace clipboard {
+
+class ClipboardAppTest : public mojo::test::ApplicationTestBase {
+ public:
+ ClipboardAppTest() : ApplicationTestBase() {}
+ ~ClipboardAppTest() override {}
+
+ void SetUp() override {
+ mojo::test::ApplicationTestBase::SetUp();
+ application_impl()->ConnectToService("mojo:clipboard", &clipboard_);
+ }
+
+ uint64_t GetSequenceNumber() {
+ base::RunLoop run_loop;
+ uint64_t sequence_num = 999999;
+ clipboard_->GetSequenceNumber(
+ Clipboard::TYPE_COPY_PASTE,
+ base::Bind(&CopyUint64AndEndRunloop, &sequence_num, &run_loop));
+ run_loop.Run();
+ return sequence_num;
+ }
+
+ std::vector<std::string> GetAvailableFormatMimeTypes() {
+ base::RunLoop run_loop;
+ std::vector<std::string> types;
+ types.push_back(kUninitialized);
+ clipboard_->GetAvailableMimeTypes(
+ Clipboard::TYPE_COPY_PASTE,
+ base::Bind(&CopyVectorStringAndEndRunloop, &types, &run_loop));
+ run_loop.Run();
+ return types;
+ }
+
+ bool GetDataOfType(const std::string& mime_type, std::string* data) {
+ base::RunLoop run_loop;
+ bool is_null = false;
+ clipboard_->ReadMimeType(
+ Clipboard::TYPE_COPY_PASTE, mime_type,
+ base::Bind(&CopyStringAndEndRunloop, data, &is_null, &run_loop));
+ run_loop.Run();
+ return !is_null;
+ }
+
+ void SetStringText(const std::string& data) {
+ Map<String, Array<uint8_t>> mime_data;
+ mime_data[Clipboard::MIME_TYPE_TEXT] = Array<uint8_t>::From(data);
+ clipboard_->WriteClipboardData(Clipboard::TYPE_COPY_PASTE,
+ mime_data.Pass());
+ }
+
+ protected:
+ mojo::ClipboardPtr clipboard_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardAppTest);
+};
+
+TEST_F(ClipboardAppTest, EmptyClipboardOK) {
+ EXPECT_EQ(0ul, GetSequenceNumber());
+ EXPECT_TRUE(GetAvailableFormatMimeTypes().empty());
+ std::string data;
+ EXPECT_FALSE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+TEST_F(ClipboardAppTest, CanReadBackText) {
+ std::string data;
+ EXPECT_FALSE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(0ul, GetSequenceNumber());
+
+ SetStringText(kPlainTextData);
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+}
+
+TEST_F(ClipboardAppTest, CanSetMultipleDataTypesAtOnce) {
+ Map<String, Array<uint8_t>> mime_data;
+ mime_data[Clipboard::MIME_TYPE_TEXT] =
+ Array<uint8_t>::From(std::string(kPlainTextData));
+ mime_data[Clipboard::MIME_TYPE_HTML] =
+ Array<uint8_t>::From(std::string(kHtmlData));
+
+ clipboard_->WriteClipboardData(Clipboard::TYPE_COPY_PASTE, mime_data.Pass());
+
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ std::string data;
+ EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+ EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_HTML, &data));
+ EXPECT_EQ(kHtmlData, data);
+}
+
+TEST_F(ClipboardAppTest, CanClearClipboardWithZeroArray) {
+ std::string data;
+ SetStringText(kPlainTextData);
+ EXPECT_EQ(1ul, GetSequenceNumber());
+
+ EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data));
+ EXPECT_EQ(kPlainTextData, data);
+
+ Map<String, Array<uint8_t>> mime_data;
+ clipboard_->WriteClipboardData(Clipboard::TYPE_COPY_PASTE, mime_data.Pass());
+
+ EXPECT_EQ(2ul, GetSequenceNumber());
+ EXPECT_FALSE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+} // namespace clipboard
diff --git a/mojo/services/clipboard/clipboard_standalone_impl.cc b/mojo/services/clipboard/clipboard_standalone_impl.cc
new file mode 100644
index 0000000..e783af7
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_impl.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/clipboard/clipboard_standalone_impl.h"
+
+#include <string.h>
+
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/public/cpp/bindings/string.h"
+
+using mojo::Array;
+using mojo::Map;
+using mojo::String;
+
+namespace clipboard {
+
+// ClipboardData contains data copied to the Clipboard for a variety of formats.
+// It mostly just provides APIs to cleanly access and manipulate this data.
+class ClipboardStandaloneImpl::ClipboardData {
+ public:
+ ClipboardData() {}
+ ~ClipboardData() {}
+
+ Array<String> GetMimeTypes() const {
+ Array<String> types(data_types_.size());
+ int i = 0;
+ for (auto it = data_types_.begin(); it != data_types_.end(); ++it, ++i)
+ types[i] = it.GetKey();
+
+ return types.Pass();
+ }
+
+ void SetData(Map<String, Array<uint8_t>> data) { data_types_ = data.Pass(); }
+
+ void GetData(const String& mime_type, Array<uint8_t>* data) const {
+ auto it = data_types_.find(mime_type);
+ if (it != data_types_.end())
+ *data = it.GetValue().Clone();
+ }
+
+ private:
+ Map<String, Array<uint8_t>> data_types_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardData);
+};
+
+ClipboardStandaloneImpl::ClipboardStandaloneImpl(
+ mojo::InterfaceRequest<mojo::Clipboard> request)
+ : binding_(this, request.Pass()) {
+ for (int i = 0; i < kNumClipboards; ++i) {
+ sequence_number_[i] = 0;
+ clipboard_state_[i].reset(new ClipboardData);
+ }
+}
+
+ClipboardStandaloneImpl::~ClipboardStandaloneImpl() {
+}
+
+void ClipboardStandaloneImpl::GetSequenceNumber(
+ Clipboard::Type clipboard_type,
+ const mojo::Callback<void(uint64_t)>& callback) {
+ callback.Run(sequence_number_[clipboard_type]);
+}
+
+void ClipboardStandaloneImpl::GetAvailableMimeTypes(
+ Clipboard::Type clipboard_type,
+ const mojo::Callback<void(Array<String>)>& callback) {
+ callback.Run(clipboard_state_[clipboard_type]->GetMimeTypes().Pass());
+}
+
+void ClipboardStandaloneImpl::ReadMimeType(
+ Clipboard::Type clipboard_type,
+ const String& mime_type,
+ const mojo::Callback<void(Array<uint8_t>)>& callback) {
+ Array<uint8_t> mime_data;
+ clipboard_state_[clipboard_type]->GetData(mime_type, &mime_data);
+ callback.Run(mime_data.Pass());
+}
+
+void ClipboardStandaloneImpl::WriteClipboardData(
+ Clipboard::Type clipboard_type,
+ Map<String, Array<uint8_t>> data) {
+ sequence_number_[clipboard_type]++;
+ clipboard_state_[clipboard_type]->SetData(data.Pass());
+}
+
+} // namespace clipboard
diff --git a/mojo/services/clipboard/clipboard_standalone_impl.h b/mojo/services/clipboard/clipboard_standalone_impl.h
new file mode 100644
index 0000000..6c5d828
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_impl.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 SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
+#define SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
+
+#include <base/memory/scoped_ptr.h>
+#include <string>
+
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/mojo_services/src/clipboard/public/interfaces/clipboard.mojom.h"
+
+namespace clipboard {
+
+// Stub clipboard implementation.
+//
+// Eventually, we'll actually want to interact with the system clipboard, but
+// that's hard today because the system clipboard is asynchronous (on X11), the
+// ui::Clipboard interface is synchronous (which is what we'd use), mojo is
+// asynchronous across processes, and the WebClipboard interface is synchronous
+// (which is at least tractable).
+class ClipboardStandaloneImpl : public mojo::Clipboard {
+ public:
+ // mojo::Clipboard exposes three possible clipboards.
+ static const int kNumClipboards = 3;
+
+ explicit ClipboardStandaloneImpl(
+ mojo::InterfaceRequest<mojo::Clipboard> request);
+ ~ClipboardStandaloneImpl() override;
+
+ // mojo::Clipboard implementation.
+ void GetSequenceNumber(
+ mojo::Clipboard::Type clipboard_type,
+ const mojo::Callback<void(uint64_t)>& callback) override;
+ void GetAvailableMimeTypes(
+ mojo::Clipboard::Type clipboard_types,
+ const mojo::Callback<void(mojo::Array<mojo::String>)>& callback) override;
+ void ReadMimeType(
+ mojo::Clipboard::Type clipboard_type,
+ const mojo::String& mime_type,
+ const mojo::Callback<void(mojo::Array<uint8_t>)>& callback) override;
+ void WriteClipboardData(
+ mojo::Clipboard::Type clipboard_type,
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> data) override;
+
+ private:
+ uint64_t sequence_number_[kNumClipboards];
+
+ // Internal struct which stores the current state of the clipboard.
+ class ClipboardData;
+
+ // The current clipboard state. This is what is read from.
+ scoped_ptr<ClipboardData> clipboard_state_[kNumClipboards];
+ mojo::StrongBinding<mojo::Clipboard> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClipboardStandaloneImpl);
+};
+
+} // namespace clipboard
+
+#endif // SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
diff --git a/mojo/services/clipboard/main.cc b/mojo/services/clipboard/main.cc
new file mode 100644
index 0000000..0c5e17a
--- /dev/null
+++ b/mojo/services/clipboard/main.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/clipboard/clipboard_standalone_impl.h"
+
+class Delegate : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::Clipboard> {
+ public:
+ Delegate() {}
+ ~Delegate() override {}
+
+ // mojo::ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // mojo::InterfaceFactory<mojo::Clipboard> implementation.
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::Clipboard> request) override {
+ // TODO(erg): Write native implementations of the clipboard. For now, we
+ // just build a clipboard which doesn't interact with the system.
+ new clipboard::ClipboardStandaloneImpl(request.Pass());
+ }
+};
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new Delegate);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/gles2/BUILD.gn b/mojo/services/gles2/BUILD.gn
new file mode 100644
index 0000000..a34b9b3
--- /dev/null
+++ b/mojo/services/gles2/BUILD.gn
@@ -0,0 +1,59 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+
+source_set("gles2") {
+ visibility = [
+ "//mojo/shell:lib", # For android
+ "//mojo/services/native_viewport:*",
+ ]
+
+ sources = [
+ "command_buffer_driver.cc",
+ "command_buffer_driver.h",
+ "command_buffer_impl.cc",
+ "command_buffer_impl.h",
+ "gpu_state.cc",
+ "gpu_state.h",
+ "gpu_impl.cc",
+ "gpu_impl.h",
+ ]
+
+ public_deps = [
+ ":lib",
+ ]
+ deps = [
+ "//base",
+ "//gpu/command_buffer/service",
+ "//mojo/converters/geometry",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/gpu/public/interfaces",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ ]
+
+ include_dirs = [ "../.." ]
+}
+
+source_set("lib") {
+ sources = [
+ "command_buffer_type_conversions.cc",
+ "command_buffer_type_conversions.h",
+ "mojo_buffer_backing.cc",
+ "mojo_buffer_backing.h",
+ ]
+
+ deps = [
+ "//base",
+ "//gpu/command_buffer/common",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo_services/src/gpu/public/interfaces",
+ ]
+
+ include_dirs = [ "../.." ]
+}
diff --git a/mojo/services/gles2/DEPS b/mojo/services/gles2/DEPS
new file mode 100644
index 0000000..f34d3eb
--- /dev/null
+++ b/mojo/services/gles2/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+gpu",
+ "+mojo/converters",
+ "+third_party/mojo_services/src/geometry",
+ "+third_party/mojo_services/src/gpu",
+ "+ui",
+]
diff --git a/mojo/services/gles2/command_buffer_driver.cc b/mojo/services/gles2/command_buffer_driver.cc
new file mode 100644
index 0000000..3ae617b
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_driver.cc
@@ -0,0 +1,250 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/gles2/command_buffer_driver.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/shared_memory.h"
+#include "gpu/command_buffer/common/constants.h"
+#include "gpu/command_buffer/common/value_state.h"
+#include "gpu/command_buffer/service/command_buffer_service.h"
+#include "gpu/command_buffer/service/context_group.h"
+#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
+#include "gpu/command_buffer/service/gpu_scheduler.h"
+#include "gpu/command_buffer/service/image_manager.h"
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "gpu/command_buffer/service/memory_tracking.h"
+#include "gpu/command_buffer/service/sync_point_manager.h"
+#include "gpu/command_buffer/service/valuebuffer_manager.h"
+#include "mojo/services/gles2/command_buffer_type_conversions.h"
+#include "mojo/services/gles2/mojo_buffer_backing.h"
+#include "ui/gfx/vsync_provider.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_surface.h"
+
+namespace gles2 {
+
+namespace {
+
+class MemoryTrackerStub : public gpu::gles2::MemoryTracker {
+ public:
+ MemoryTrackerStub() {}
+
+ void TrackMemoryAllocatedChange(
+ size_t old_size,
+ size_t new_size,
+ gpu::gles2::MemoryTracker::Pool pool) override {}
+
+ bool EnsureGPUMemoryAvailable(size_t size_needed) override { return true; };
+
+ private:
+ ~MemoryTrackerStub() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryTrackerStub);
+};
+
+} // anonymous namespace
+
+CommandBufferDriver::Client::~Client() {
+}
+
+CommandBufferDriver::CommandBufferDriver(
+ gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager,
+ gpu::SyncPointManager* sync_point_manager)
+ : CommandBufferDriver(gfx::kNullAcceleratedWidget,
+ share_group,
+ mailbox_manager,
+ sync_point_manager) {
+}
+
+CommandBufferDriver::CommandBufferDriver(
+ gfx::AcceleratedWidget widget,
+ gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager,
+ gpu::SyncPointManager* sync_point_manager)
+ : client_(nullptr),
+ widget_(widget),
+ share_group_(share_group),
+ mailbox_manager_(mailbox_manager),
+ sync_point_manager_(sync_point_manager),
+ weak_factory_(this) {
+}
+
+CommandBufferDriver::~CommandBufferDriver() {
+ if (decoder_) {
+ bool have_context = decoder_->MakeCurrent();
+ decoder_->Destroy(have_context);
+ }
+}
+
+void CommandBufferDriver::Initialize(
+ mojo::CommandBufferSyncClientPtr sync_client,
+ mojo::CommandBufferLostContextObserverPtr loss_observer,
+ mojo::ScopedSharedBufferHandle shared_state) {
+ sync_client_ = sync_client.Pass();
+ loss_observer_ = loss_observer.Pass();
+ bool success = DoInitialize(shared_state.Pass());
+ mojo::GpuCapabilitiesPtr capabilities =
+ success ? mojo::GpuCapabilities::From(decoder_->GetCapabilities())
+ : mojo::GpuCapabilities::New();
+ sync_client_->DidInitialize(success, capabilities.Pass());
+}
+
+bool CommandBufferDriver::DoInitialize(
+ mojo::ScopedSharedBufferHandle shared_state) {
+ if (widget_ == gfx::kNullAcceleratedWidget)
+ surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1));
+ else {
+ surface_ = gfx::GLSurface::CreateViewGLSurface(widget_);
+ if (auto vsync_provider = surface_->GetVSyncProvider()) {
+ vsync_provider->GetVSyncParameters(
+ base::Bind(&CommandBufferDriver::OnUpdateVSyncParameters,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+
+ if (!surface_.get())
+ return false;
+
+ // TODO(piman): virtual contexts, gpu preference.
+ context_ = gfx::GLContext::CreateGLContext(share_group_.get(), surface_.get(),
+ gfx::PreferIntegratedGpu);
+ if (!context_.get())
+ return false;
+
+ if (!context_->MakeCurrent(surface_.get()))
+ return false;
+
+ // TODO(piman): ShaderTranslatorCache is currently per-ContextGroup but
+ // only needs to be per-thread.
+ bool bind_generates_resource = false;
+ scoped_refptr<gpu::gles2::ContextGroup> context_group =
+ new gpu::gles2::ContextGroup(
+ mailbox_manager_.get(), new MemoryTrackerStub,
+ new gpu::gles2::ShaderTranslatorCache, nullptr, nullptr, nullptr,
+ bind_generates_resource);
+
+ command_buffer_.reset(
+ new gpu::CommandBufferService(context_group->transfer_buffer_manager()));
+ bool result = command_buffer_->Initialize();
+ DCHECK(result);
+
+ decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group.get()));
+ scheduler_.reset(new gpu::GpuScheduler(command_buffer_.get(), decoder_.get(),
+ decoder_.get()));
+ decoder_->set_engine(scheduler_.get());
+ decoder_->SetResizeCallback(
+ base::Bind(&CommandBufferDriver::OnResize, base::Unretained(this)));
+ decoder_->SetWaitSyncPointCallback(base::Bind(
+ &CommandBufferDriver::OnWaitSyncPoint, base::Unretained(this)));
+
+ gpu::gles2::DisallowedFeatures disallowed_features;
+
+ // TODO(piman): attributes.
+ std::vector<int32> attrib_vector;
+ if (!decoder_->Initialize(surface_, context_, false /* offscreen */,
+ gfx::Size(1, 1), disallowed_features,
+ attrib_vector))
+ return false;
+
+ command_buffer_->SetPutOffsetChangeCallback(base::Bind(
+ &gpu::GpuScheduler::PutChanged, base::Unretained(scheduler_.get())));
+ command_buffer_->SetGetBufferChangeCallback(base::Bind(
+ &gpu::GpuScheduler::SetGetBuffer, base::Unretained(scheduler_.get())));
+ command_buffer_->SetParseErrorCallback(
+ base::Bind(&CommandBufferDriver::OnParseError, base::Unretained(this)));
+
+ // TODO(piman): other callbacks
+
+ const size_t kSize = sizeof(gpu::CommandBufferSharedState);
+ scoped_ptr<gpu::BufferBacking> backing(
+ gles2::MojoBufferBacking::Create(shared_state.Pass(), kSize));
+ if (!backing)
+ return false;
+
+ command_buffer_->SetSharedStateBuffer(backing.Pass());
+ return true;
+}
+
+void CommandBufferDriver::SetGetBuffer(int32_t buffer) {
+ command_buffer_->SetGetBuffer(buffer);
+}
+
+void CommandBufferDriver::Flush(int32_t put_offset) {
+ if (!context_->MakeCurrent(surface_.get())) {
+ DLOG(WARNING) << "Context lost";
+ OnContextLost(gpu::error::kUnknown);
+ return;
+ }
+ command_buffer_->Flush(put_offset);
+}
+
+void CommandBufferDriver::MakeProgress(int32_t last_get_offset) {
+ // TODO(piman): handle out-of-order.
+ sync_client_->DidMakeProgress(
+ mojo::CommandBufferState::From(command_buffer_->GetLastState()));
+}
+
+void CommandBufferDriver::RegisterTransferBuffer(
+ int32_t id,
+ mojo::ScopedSharedBufferHandle transfer_buffer,
+ uint32_t size) {
+ // Take ownership of the memory and map it into this process.
+ // This validates the size.
+ scoped_ptr<gpu::BufferBacking> backing(
+ gles2::MojoBufferBacking::Create(transfer_buffer.Pass(), size));
+ if (!backing) {
+ DVLOG(0) << "Failed to map shared memory.";
+ return;
+ }
+ command_buffer_->RegisterTransferBuffer(id, backing.Pass());
+}
+
+void CommandBufferDriver::DestroyTransferBuffer(int32_t id) {
+ command_buffer_->DestroyTransferBuffer(id);
+}
+
+void CommandBufferDriver::Echo(const mojo::Callback<void()>& callback) {
+ callback.Run();
+}
+
+void CommandBufferDriver::OnParseError() {
+ gpu::CommandBuffer::State state = command_buffer_->GetLastState();
+ OnContextLost(state.context_lost_reason);
+}
+
+void CommandBufferDriver::OnResize(gfx::Size size, float scale_factor) {
+ surface_->Resize(size);
+}
+
+bool CommandBufferDriver::OnWaitSyncPoint(uint32_t sync_point) {
+ if (!sync_point)
+ return true;
+ if (sync_point_manager_->IsSyncPointRetired(sync_point))
+ return true;
+ scheduler_->SetScheduled(false);
+ sync_point_manager_->AddSyncPointCallback(
+ sync_point, base::Bind(&CommandBufferDriver::OnSyncPointRetired,
+ weak_factory_.GetWeakPtr()));
+ return scheduler_->IsScheduled();
+}
+
+void CommandBufferDriver::OnSyncPointRetired() {
+ scheduler_->SetScheduled(true);
+}
+
+void CommandBufferDriver::OnContextLost(uint32_t reason) {
+ loss_observer_->DidLoseContext(reason);
+ client_->DidLoseContext();
+}
+
+void CommandBufferDriver::OnUpdateVSyncParameters(
+ const base::TimeTicks timebase,
+ const base::TimeDelta interval) {
+ client_->UpdateVSyncParameters(timebase, interval);
+}
+
+} // namespace gles2
diff --git a/mojo/services/gles2/command_buffer_driver.h b/mojo/services/gles2/command_buffer_driver.h
new file mode 100644
index 0000000..5006567
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_driver.h
@@ -0,0 +1,103 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GLES2_COMMAND_BUFFER_DRIVER_H_
+#define SERVICES_GLES2_COMMAND_BUFFER_DRIVER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/timer/timer.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gpu {
+class CommandBufferService;
+class GpuScheduler;
+class GpuControlService;
+class SyncPointManager;
+namespace gles2 {
+class GLES2Decoder;
+class MailboxManager;
+}
+}
+
+namespace gfx {
+class GLContext;
+class GLShareGroup;
+class GLSurface;
+}
+
+namespace gles2 {
+
+class CommandBufferDriver {
+ public:
+ class Client {
+ public:
+ virtual ~Client();
+ virtual void UpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) = 0;
+ virtual void DidLoseContext() = 0;
+ };
+ // Offscreen.
+ CommandBufferDriver(gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager,
+ gpu::SyncPointManager* sync_point_manager);
+ // Onscreen.
+ CommandBufferDriver(gfx::AcceleratedWidget widget,
+ gfx::GLShareGroup* share_group,
+ gpu::gles2::MailboxManager* mailbox_manager,
+ gpu::SyncPointManager* sync_point_manager);
+ ~CommandBufferDriver();
+
+ void set_client(scoped_ptr<Client> client) { client_ = client.Pass(); }
+
+ void Initialize(mojo::CommandBufferSyncClientPtr sync_client,
+ mojo::CommandBufferLostContextObserverPtr loss_observer,
+ mojo::ScopedSharedBufferHandle shared_state);
+ void SetGetBuffer(int32_t buffer);
+ void Flush(int32_t put_offset);
+ void MakeProgress(int32_t last_get_offset);
+ void RegisterTransferBuffer(int32_t id,
+ mojo::ScopedSharedBufferHandle transfer_buffer,
+ uint32_t size);
+ void DestroyTransferBuffer(int32_t id);
+ void Echo(const mojo::Callback<void()>& callback);
+
+ private:
+ bool DoInitialize(mojo::ScopedSharedBufferHandle shared_state);
+ void OnResize(gfx::Size size, float scale_factor);
+ bool OnWaitSyncPoint(uint32_t sync_point);
+ void OnSyncPointRetired();
+ void OnParseError();
+ void OnContextLost(uint32_t reason);
+ void OnUpdateVSyncParameters(const base::TimeTicks timebase,
+ const base::TimeDelta interval);
+
+ scoped_ptr<Client> client_;
+ mojo::CommandBufferSyncClientPtr sync_client_;
+ mojo::CommandBufferLostContextObserverPtr loss_observer_;
+ gfx::AcceleratedWidget widget_;
+ scoped_ptr<gpu::CommandBufferService> command_buffer_;
+ scoped_ptr<gpu::gles2::GLES2Decoder> decoder_;
+ scoped_ptr<gpu::GpuScheduler> scheduler_;
+ scoped_refptr<gfx::GLContext> context_;
+ scoped_refptr<gfx::GLSurface> surface_;
+ scoped_refptr<gfx::GLShareGroup> share_group_;
+ scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_;
+ scoped_refptr<gpu::SyncPointManager> sync_point_manager_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> context_lost_task_runner_;
+ base::Callback<void(int32_t)> context_lost_callback_;
+
+ base::WeakPtrFactory<CommandBufferDriver> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CommandBufferDriver);
+};
+
+} // namespace gles2
+
+#endif // SERVICES_GLES2_COMMAND_BUFFER_DRIVER_H_
diff --git a/mojo/services/gles2/command_buffer_impl.cc b/mojo/services/gles2/command_buffer_impl.cc
new file mode 100644
index 0000000..b89279f
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_impl.cc
@@ -0,0 +1,160 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/gles2/command_buffer_impl.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "gpu/command_buffer/service/sync_point_manager.h"
+#include "mojo/services/gles2/command_buffer_driver.h"
+
+namespace gles2 {
+namespace {
+void DestroyDriver(scoped_ptr<CommandBufferDriver> driver) {
+ // Just let ~scoped_ptr run.
+}
+
+void RunCallback(const mojo::Callback<void()>& callback) {
+ callback.Run();
+}
+
+class CommandBufferDriverClientImpl : public CommandBufferDriver::Client {
+ public:
+ CommandBufferDriverClientImpl(
+ base::WeakPtr<CommandBufferImpl> command_buffer,
+ scoped_refptr<base::SingleThreadTaskRunner> control_task_runner)
+ : command_buffer_(command_buffer),
+ control_task_runner_(control_task_runner) {}
+
+ private:
+ void UpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) override {
+ control_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferImpl::UpdateVSyncParameters,
+ command_buffer_, timebase, interval));
+ }
+
+ void DidLoseContext() override {
+ control_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferImpl::DidLoseContext,
+ command_buffer_));
+ }
+
+ base::WeakPtr<CommandBufferImpl> command_buffer_;
+ scoped_refptr<base::SingleThreadTaskRunner> control_task_runner_;
+};
+}
+
+CommandBufferImpl::CommandBufferImpl(
+ mojo::InterfaceRequest<mojo::CommandBuffer> request,
+ mojo::ViewportParameterListenerPtr listener,
+ scoped_refptr<base::SingleThreadTaskRunner> control_task_runner,
+ gpu::SyncPointManager* sync_point_manager,
+ scoped_ptr<CommandBufferDriver> driver)
+ : sync_point_manager_(sync_point_manager),
+ driver_task_runner_(base::MessageLoop::current()->task_runner()),
+ driver_(driver.Pass()),
+ viewport_parameter_listener_(listener.Pass()),
+ binding_(this),
+ weak_factory_(this) {
+ driver_->set_client(make_scoped_ptr(new CommandBufferDriverClientImpl(
+ weak_factory_.GetWeakPtr(), control_task_runner)));
+
+ control_task_runner->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferImpl::BindToRequest,
+ base::Unretained(this), base::Passed(&request)));
+}
+
+CommandBufferImpl::~CommandBufferImpl() {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&DestroyDriver, base::Passed(&driver_)));
+}
+
+void CommandBufferImpl::Initialize(
+ mojo::CommandBufferSyncClientPtr sync_client,
+ mojo::CommandBufferSyncPointClientPtr sync_point_client,
+ mojo::CommandBufferLostContextObserverPtr loss_observer,
+ mojo::ScopedSharedBufferHandle shared_state) {
+ sync_point_client_ = sync_point_client.Pass();
+ driver_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&CommandBufferDriver::Initialize,
+ base::Unretained(driver_.get()), base::Passed(&sync_client),
+ base::Passed(&loss_observer),
+ base::Passed(&shared_state)));
+}
+
+void CommandBufferImpl::SetGetBuffer(int32_t buffer) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferDriver::SetGetBuffer,
+ base::Unretained(driver_.get()), buffer));
+}
+
+void CommandBufferImpl::Flush(int32_t put_offset) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferDriver::Flush,
+ base::Unretained(driver_.get()), put_offset));
+}
+
+void CommandBufferImpl::MakeProgress(int32_t last_get_offset) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferDriver::MakeProgress,
+ base::Unretained(driver_.get()), last_get_offset));
+}
+
+void CommandBufferImpl::RegisterTransferBuffer(
+ int32_t id,
+ mojo::ScopedSharedBufferHandle transfer_buffer,
+ uint32_t size) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferDriver::RegisterTransferBuffer,
+ base::Unretained(driver_.get()), id,
+ base::Passed(&transfer_buffer), size));
+}
+
+void CommandBufferImpl::DestroyTransferBuffer(int32_t id) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&CommandBufferDriver::DestroyTransferBuffer,
+ base::Unretained(driver_.get()), id));
+}
+
+void CommandBufferImpl::InsertSyncPoint(bool retire) {
+ uint32_t sync_point = sync_point_manager_->GenerateSyncPoint();
+ sync_point_client_->DidInsertSyncPoint(sync_point);
+ if (retire) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&gpu::SyncPointManager::RetireSyncPoint,
+ sync_point_manager_, sync_point));
+ }
+}
+
+void CommandBufferImpl::RetireSyncPoint(uint32_t sync_point) {
+ driver_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&gpu::SyncPointManager::RetireSyncPoint,
+ sync_point_manager_, sync_point));
+}
+
+void CommandBufferImpl::Echo(const mojo::Callback<void()>& callback) {
+ driver_task_runner_->PostTaskAndReply(FROM_HERE, base::Bind(&base::DoNothing),
+ base::Bind(&RunCallback, callback));
+}
+
+void CommandBufferImpl::BindToRequest(
+ mojo::InterfaceRequest<mojo::CommandBuffer> request) {
+ binding_.Bind(request.Pass());
+}
+
+void CommandBufferImpl::DidLoseContext() {
+ binding_.OnConnectionError();
+}
+
+void CommandBufferImpl::UpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ if (!viewport_parameter_listener_)
+ return;
+ viewport_parameter_listener_->OnVSyncParametersUpdated(
+ timebase.ToInternalValue(), interval.ToInternalValue());
+}
+
+} // namespace gles2
diff --git a/mojo/services/gles2/command_buffer_impl.h b/mojo/services/gles2/command_buffer_impl.h
new file mode 100644
index 0000000..61b33a2
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_impl.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 SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_
+#define SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/viewport_parameter_listener.mojom.h"
+
+namespace gpu {
+class SyncPointManager;
+}
+
+namespace gles2 {
+class CommandBufferDriver;
+
+// This class listens to the CommandBuffer message pipe on a low-latency thread
+// so that we can insert sync points without blocking on the GL driver. It
+// forwards most method calls to the CommandBufferDriver, which runs on the
+// same thread as the native viewport.
+class CommandBufferImpl : public mojo::CommandBuffer {
+ public:
+ CommandBufferImpl(
+ mojo::InterfaceRequest<CommandBuffer> request,
+ mojo::ViewportParameterListenerPtr listener,
+ scoped_refptr<base::SingleThreadTaskRunner> control_task_runner,
+ gpu::SyncPointManager* sync_point_manager,
+ scoped_ptr<CommandBufferDriver> driver);
+ ~CommandBufferImpl() override;
+
+ void Initialize(mojo::CommandBufferSyncClientPtr sync_client,
+ mojo::CommandBufferSyncPointClientPtr sync_point_client,
+ mojo::CommandBufferLostContextObserverPtr loss_observer,
+ mojo::ScopedSharedBufferHandle shared_state) override;
+ void SetGetBuffer(int32_t buffer) override;
+ void Flush(int32_t put_offset) override;
+ void MakeProgress(int32_t last_get_offset) override;
+ void RegisterTransferBuffer(int32_t id,
+ mojo::ScopedSharedBufferHandle transfer_buffer,
+ uint32_t size) override;
+ void DestroyTransferBuffer(int32_t id) override;
+ void InsertSyncPoint(bool retire) override;
+ void RetireSyncPoint(uint32_t sync_point) override;
+ void Echo(const mojo::Callback<void()>& callback) override;
+
+ void DidLoseContext();
+ void UpdateVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ private:
+ void BindToRequest(mojo::InterfaceRequest<CommandBuffer> request);
+
+ scoped_refptr<gpu::SyncPointManager> sync_point_manager_;
+ scoped_refptr<base::SingleThreadTaskRunner> driver_task_runner_;
+ scoped_ptr<CommandBufferDriver> driver_;
+ mojo::CommandBufferSyncPointClientPtr sync_point_client_;
+ mojo::ViewportParameterListenerPtr viewport_parameter_listener_;
+ mojo::StrongBinding<CommandBuffer> binding_;
+
+ base::WeakPtrFactory<CommandBufferImpl> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(CommandBufferImpl);
+};
+
+} // namespace gles2
+
+#endif // SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_
diff --git a/mojo/services/gles2/command_buffer_type_conversions.cc b/mojo/services/gles2/command_buffer_type_conversions.cc
new file mode 100644
index 0000000..9c068be
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_type_conversions.cc
@@ -0,0 +1,170 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/gles2/command_buffer_type_conversions.h"
+
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+
+namespace mojo {
+
+CommandBufferStatePtr
+TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State>::Convert(
+ const gpu::CommandBuffer::State& input) {
+ CommandBufferStatePtr result(CommandBufferState::New());
+ result->get_offset = input.get_offset;
+ result->token = input.token;
+ result->error = input.error;
+ result->context_lost_reason = input.context_lost_reason;
+ result->generation = input.generation;
+ return result.Pass();
+}
+
+gpu::CommandBuffer::State
+TypeConverter<gpu::CommandBuffer::State, CommandBufferStatePtr>::Convert(
+ const CommandBufferStatePtr& input) {
+ gpu::CommandBuffer::State state;
+ state.get_offset = input->get_offset;
+ state.token = input->token;
+ state.error = static_cast<gpu::error::Error>(input->error);
+ state.context_lost_reason =
+ static_cast<gpu::error::ContextLostReason>(input->context_lost_reason);
+ state.generation = input->generation;
+ return state;
+}
+
+GpuShaderPrecisionPtr
+TypeConverter<GpuShaderPrecisionPtr, gpu::Capabilities::ShaderPrecision>::
+ Convert(const gpu::Capabilities::ShaderPrecision& input) {
+ GpuShaderPrecisionPtr result(GpuShaderPrecision::New());
+ result->min_range = input.min_range;
+ result->max_range = input.max_range;
+ result->precision = input.precision;
+ return result.Pass();
+}
+
+gpu::Capabilities::ShaderPrecision TypeConverter<
+ gpu::Capabilities::ShaderPrecision,
+ GpuShaderPrecisionPtr>::Convert(const GpuShaderPrecisionPtr& input) {
+ gpu::Capabilities::ShaderPrecision result;
+ result.min_range = input->min_range;
+ result.max_range = input->max_range;
+ result.precision = input->precision;
+ return result;
+}
+
+GpuPerStagePrecisionsPtr
+TypeConverter<GpuPerStagePrecisionsPtr, gpu::Capabilities::PerStagePrecisions>::
+ Convert(const gpu::Capabilities::PerStagePrecisions& input) {
+ GpuPerStagePrecisionsPtr result(GpuPerStagePrecisions::New());
+ result->low_int = GpuShaderPrecision::From(input.low_int);
+ result->medium_int = GpuShaderPrecision::From(input.medium_int);
+ result->high_int = GpuShaderPrecision::From(input.high_int);
+ result->low_float = GpuShaderPrecision::From(input.low_float);
+ result->medium_float = GpuShaderPrecision::From(input.medium_float);
+ result->high_float = GpuShaderPrecision::From(input.high_float);
+ return result.Pass();
+}
+
+gpu::Capabilities::PerStagePrecisions TypeConverter<
+ gpu::Capabilities::PerStagePrecisions,
+ GpuPerStagePrecisionsPtr>::Convert(const GpuPerStagePrecisionsPtr& input) {
+ gpu::Capabilities::PerStagePrecisions result;
+ result.low_int = input->low_int.To<gpu::Capabilities::ShaderPrecision>();
+ result.medium_int =
+ input->medium_int.To<gpu::Capabilities::ShaderPrecision>();
+ result.high_int = input->high_int.To<gpu::Capabilities::ShaderPrecision>();
+ result.low_float = input->low_float.To<gpu::Capabilities::ShaderPrecision>();
+ result.medium_float =
+ input->medium_float.To<gpu::Capabilities::ShaderPrecision>();
+ result.high_float =
+ input->high_float.To<gpu::Capabilities::ShaderPrecision>();
+ return result;
+}
+
+GpuCapabilitiesPtr
+TypeConverter<GpuCapabilitiesPtr, gpu::Capabilities>::Convert(
+ const gpu::Capabilities& input) {
+ GpuCapabilitiesPtr result(GpuCapabilities::New());
+ result->vertex_shader_precisions =
+ GpuPerStagePrecisions::From(input.vertex_shader_precisions);
+ result->fragment_shader_precisions =
+ GpuPerStagePrecisions::From(input.fragment_shader_precisions);
+ result->max_combined_texture_image_units =
+ input.max_combined_texture_image_units;
+ result->max_cube_map_texture_size = input.max_cube_map_texture_size;
+ result->max_fragment_uniform_vectors = input.max_fragment_uniform_vectors;
+ result->max_renderbuffer_size = input.max_renderbuffer_size;
+ result->max_texture_image_units = input.max_texture_image_units;
+ result->max_texture_size = input.max_texture_size;
+ result->max_varying_vectors = input.max_varying_vectors;
+ result->max_vertex_attribs = input.max_vertex_attribs;
+ result->max_vertex_texture_image_units = input.max_vertex_texture_image_units;
+ result->max_vertex_uniform_vectors = input.max_vertex_uniform_vectors;
+ result->num_compressed_texture_formats = input.num_compressed_texture_formats;
+ result->num_shader_binary_formats = input.num_shader_binary_formats;
+ result->bind_generates_resource_chromium =
+ input.bind_generates_resource_chromium;
+ result->post_sub_buffer = input.post_sub_buffer;
+ result->egl_image_external = input.egl_image_external;
+ result->texture_format_bgra8888 = input.texture_format_bgra8888;
+ result->texture_format_etc1 = input.texture_format_etc1;
+ result->texture_format_etc1_npot = input.texture_format_etc1_npot;
+ result->texture_rectangle = input.texture_rectangle;
+ result->iosurface = input.iosurface;
+ result->texture_usage = input.texture_usage;
+ result->texture_storage = input.texture_storage;
+ result->discard_framebuffer = input.discard_framebuffer;
+ result->sync_query = input.sync_query;
+ result->image = input.image;
+ result->future_sync_points = input.future_sync_points;
+ result->blend_equation_advanced = input.blend_equation_advanced;
+ result->blend_equation_advanced_coherent =
+ input.blend_equation_advanced_coherent;
+ return result.Pass();
+}
+
+gpu::Capabilities TypeConverter<gpu::Capabilities, GpuCapabilitiesPtr>::Convert(
+ const GpuCapabilitiesPtr& input) {
+ gpu::Capabilities result;
+ result.vertex_shader_precisions =
+ input->vertex_shader_precisions
+ .To<gpu::Capabilities::PerStagePrecisions>();
+ result.fragment_shader_precisions =
+ input->fragment_shader_precisions
+ .To<gpu::Capabilities::PerStagePrecisions>();
+ result.max_combined_texture_image_units =
+ input->max_combined_texture_image_units;
+ result.max_cube_map_texture_size = input->max_cube_map_texture_size;
+ result.max_fragment_uniform_vectors = input->max_fragment_uniform_vectors;
+ result.max_renderbuffer_size = input->max_renderbuffer_size;
+ result.max_texture_image_units = input->max_texture_image_units;
+ result.max_texture_size = input->max_texture_size;
+ result.max_varying_vectors = input->max_varying_vectors;
+ result.max_vertex_attribs = input->max_vertex_attribs;
+ result.max_vertex_texture_image_units = input->max_vertex_texture_image_units;
+ result.max_vertex_uniform_vectors = input->max_vertex_uniform_vectors;
+ result.num_compressed_texture_formats = input->num_compressed_texture_formats;
+ result.num_shader_binary_formats = input->num_shader_binary_formats;
+ result.bind_generates_resource_chromium =
+ input->bind_generates_resource_chromium;
+ result.post_sub_buffer = input->post_sub_buffer;
+ result.egl_image_external = input->egl_image_external;
+ result.texture_format_bgra8888 = input->texture_format_bgra8888;
+ result.texture_format_etc1 = input->texture_format_etc1;
+ result.texture_format_etc1_npot = input->texture_format_etc1_npot;
+ result.texture_rectangle = input->texture_rectangle;
+ result.iosurface = input->iosurface;
+ result.texture_usage = input->texture_usage;
+ result.texture_storage = input->texture_storage;
+ result.discard_framebuffer = input->discard_framebuffer;
+ result.sync_query = input->sync_query;
+ result.image = input->image;
+ result.future_sync_points = input->future_sync_points;
+ result.blend_equation_advanced = input->blend_equation_advanced;
+ result.blend_equation_advanced_coherent =
+ input->blend_equation_advanced_coherent;
+ return result;
+}
+
+} // namespace gles2
diff --git a/mojo/services/gles2/command_buffer_type_conversions.h b/mojo/services/gles2/command_buffer_type_conversions.h
new file mode 100644
index 0000000..9243a56
--- /dev/null
+++ b/mojo/services/gles2/command_buffer_type_conversions.h
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_
+#define SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_
+
+#include "gpu/command_buffer/common/capabilities.h"
+#include "gpu/command_buffer/common/command_buffer.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/type_converter.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+
+namespace mojo {
+
+class CommandBufferState;
+
+template <>
+struct TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State> {
+ static CommandBufferStatePtr Convert(const gpu::CommandBuffer::State& input);
+};
+
+template <>
+struct TypeConverter<gpu::CommandBuffer::State, CommandBufferStatePtr> {
+ static gpu::CommandBuffer::State Convert(const CommandBufferStatePtr& input);
+};
+
+template <>
+struct TypeConverter<GpuShaderPrecisionPtr,
+ gpu::Capabilities::ShaderPrecision> {
+ static GpuShaderPrecisionPtr Convert(
+ const gpu::Capabilities::ShaderPrecision& input);
+};
+
+template <>
+struct TypeConverter<gpu::Capabilities::ShaderPrecision,
+ GpuShaderPrecisionPtr> {
+ static gpu::Capabilities::ShaderPrecision Convert(
+ const GpuShaderPrecisionPtr& input);
+};
+
+template <>
+struct TypeConverter<GpuPerStagePrecisionsPtr,
+ gpu::Capabilities::PerStagePrecisions> {
+ static GpuPerStagePrecisionsPtr Convert(
+ const gpu::Capabilities::PerStagePrecisions& input);
+};
+
+template <>
+struct TypeConverter<gpu::Capabilities::PerStagePrecisions,
+ GpuPerStagePrecisionsPtr> {
+ static gpu::Capabilities::PerStagePrecisions Convert(
+ const GpuPerStagePrecisionsPtr& input);
+};
+
+template <>
+struct TypeConverter<GpuCapabilitiesPtr, gpu::Capabilities> {
+ static GpuCapabilitiesPtr Convert(const gpu::Capabilities& input);
+};
+
+template <>
+struct TypeConverter<gpu::Capabilities, GpuCapabilitiesPtr> {
+ static gpu::Capabilities Convert(const GpuCapabilitiesPtr& input);
+};
+
+} // namespace gles2
+
+#endif // SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_
diff --git a/mojo/services/gles2/gpu_impl.cc b/mojo/services/gles2/gpu_impl.cc
new file mode 100644
index 0000000..304da8d
--- /dev/null
+++ b/mojo/services/gles2/gpu_impl.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/gles2/gpu_impl.h"
+
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "gpu/command_buffer/service/mailbox_manager_impl.h"
+#include "gpu/command_buffer/service/sync_point_manager.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/services/gles2/command_buffer_driver.h"
+#include "mojo/services/gles2/command_buffer_impl.h"
+#include "ui/gl/gl_share_group.h"
+#include "ui/gl/gl_surface.h"
+
+namespace gles2 {
+
+GpuImpl::GpuImpl(mojo::InterfaceRequest<Gpu> request,
+ const scoped_refptr<GpuState>& state)
+ : binding_(this, request.Pass()), state_(state) {
+}
+
+GpuImpl::~GpuImpl() {
+}
+
+void GpuImpl::CreateOffscreenGLES2Context(
+ mojo::InterfaceRequest<mojo::CommandBuffer> request) {
+ new CommandBufferImpl(request.Pass(), mojo::ViewportParameterListenerPtr(),
+ state_->control_task_runner(),
+ state_->sync_point_manager(),
+ make_scoped_ptr(new CommandBufferDriver(
+ state_->share_group(), state_->mailbox_manager(),
+ state_->sync_point_manager())));
+}
+
+} // namespace gles2
diff --git a/mojo/services/gles2/gpu_impl.h b/mojo/services/gles2/gpu_impl.h
new file mode 100644
index 0000000..3e50a9a
--- /dev/null
+++ b/mojo/services/gles2/gpu_impl.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GLES2_GPU_IMPL_H_
+#define SERVICES_GLES2_GPU_IMPL_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+#include "mojo/services/gles2/gpu_state.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/mojo_services/src/geometry/public/interfaces/geometry.mojom.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h"
+
+namespace gfx {
+class GLShareGroup;
+}
+
+namespace gpu {
+class SyncPointManager;
+namespace gles2 {
+class MailboxManager;
+}
+}
+
+namespace gles2 {
+
+class GpuImpl : public mojo::Gpu {
+ public:
+ GpuImpl(mojo::InterfaceRequest<mojo::Gpu> request,
+ const scoped_refptr<GpuState>& state);
+ ~GpuImpl() override;
+
+ private:
+ void CreateOffscreenGLES2Context(mojo::InterfaceRequest<mojo::CommandBuffer>
+ command_buffer_request) override;
+
+ mojo::StrongBinding<Gpu> binding_;
+ scoped_refptr<GpuState> state_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuImpl);
+};
+
+} // namespace gles2
+
+#endif // SERVICES_GLES2_GPU_IMPL_H_
diff --git a/mojo/services/gles2/gpu_state.cc b/mojo/services/gles2/gpu_state.cc
new file mode 100644
index 0000000..63d6e29
--- /dev/null
+++ b/mojo/services/gles2/gpu_state.cc
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "mojo/services/gles2/gpu_state.h"
+
+namespace gles2 {
+
+GpuState::GpuState()
+ : control_thread_("gpu_command_buffer_control"),
+ sync_point_manager_(gpu::SyncPointManager::Create(true)),
+ share_group_(new gfx::GLShareGroup),
+ mailbox_manager_(new gpu::gles2::MailboxManagerImpl) {
+ control_thread_.Start();
+}
+
+GpuState::~GpuState() {
+}
+
+} // namespace gles2
diff --git a/mojo/services/gles2/gpu_state.h b/mojo/services/gles2/gpu_state.h
new file mode 100644
index 0000000..1d75318
--- /dev/null
+++ b/mojo/services/gles2/gpu_state.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GLES2_GPU_STATE_H_
+#define SERVICES_GLES2_GPU_STATE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "gpu/command_buffer/service/mailbox_manager_impl.h"
+#include "gpu/command_buffer/service/sync_point_manager.h"
+#include "ui/gl/gl_share_group.h"
+
+namespace gles2 {
+
+// We need to share these across all CommandBuffer instances so that contexts
+// they create can share resources with each other via mailboxes.
+class GpuState : public base::RefCounted<GpuState> {
+ public:
+ GpuState();
+
+ // We run the CommandBufferImpl on the control_task_runner, which forwards
+ // most method class to the CommandBufferDriver, which runs on the "driver",
+ // thread (i.e., the thread on which GpuImpl instances are created).
+ scoped_refptr<base::SingleThreadTaskRunner> control_task_runner() {
+ return control_thread_.task_runner();
+ }
+
+ // These objects are intended to be used on the "driver" thread (i.e., the
+ // thread on which GpuImpl instances are created).
+ gfx::GLShareGroup* share_group() const { return share_group_.get(); }
+ gpu::gles2::MailboxManager* mailbox_manager() const {
+ return mailbox_manager_.get();
+ }
+ gpu::SyncPointManager* sync_point_manager() const {
+ return sync_point_manager_.get();
+ }
+
+ private:
+ friend class base::RefCounted<GpuState>;
+ ~GpuState();
+
+ base::Thread control_thread_;
+ scoped_refptr<gpu::SyncPointManager> sync_point_manager_;
+ scoped_refptr<gfx::GLShareGroup> share_group_;
+ scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_;
+};
+
+} // namespace gles2
+
+#endif // SERVICES_GLES2_GPU_STATE_H_
diff --git a/mojo/services/gles2/mojo_buffer_backing.cc b/mojo/services/gles2/mojo_buffer_backing.cc
new file mode 100644
index 0000000..3e6c38e
--- /dev/null
+++ b/mojo/services/gles2/mojo_buffer_backing.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/gles2/mojo_buffer_backing.h"
+
+#include "base/logging.h"
+
+namespace gles2 {
+
+MojoBufferBacking::MojoBufferBacking(mojo::ScopedSharedBufferHandle handle,
+ void* memory,
+ size_t size)
+ : handle_(handle.Pass()), memory_(memory), size_(size) {}
+
+MojoBufferBacking::~MojoBufferBacking() { mojo::UnmapBuffer(memory_); }
+
+// static
+scoped_ptr<gpu::BufferBacking> MojoBufferBacking::Create(
+ mojo::ScopedSharedBufferHandle handle,
+ size_t size) {
+ void* memory = NULL;
+ MojoResult result = mojo::MapBuffer(
+ handle.get(), 0, size, &memory, MOJO_MAP_BUFFER_FLAG_NONE);
+ if (result != MOJO_RESULT_OK)
+ return scoped_ptr<BufferBacking>();
+ DCHECK(memory);
+ return scoped_ptr<BufferBacking>(
+ new MojoBufferBacking(handle.Pass(), memory, size));
+}
+void* MojoBufferBacking::GetMemory() const { return memory_; }
+size_t MojoBufferBacking::GetSize() const { return size_; }
+
+} // namespace gles2
diff --git a/mojo/services/gles2/mojo_buffer_backing.h b/mojo/services/gles2/mojo_buffer_backing.h
new file mode 100644
index 0000000..6dfd509
--- /dev/null
+++ b/mojo/services/gles2/mojo_buffer_backing.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_GLES2_MOJO_BUFFER_BACKING_H_
+#define SERVICES_GLES2_MOJO_BUFFER_BACKING_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "gpu/command_buffer/common/buffer.h"
+#include "third_party/mojo/src/mojo/public/cpp/system/core.h"
+
+namespace gles2 {
+
+class MojoBufferBacking : public gpu::BufferBacking {
+ public:
+ MojoBufferBacking(mojo::ScopedSharedBufferHandle handle,
+ void* memory,
+ size_t size);
+ ~MojoBufferBacking() override;
+
+ static scoped_ptr<gpu::BufferBacking> Create(
+ mojo::ScopedSharedBufferHandle handle,
+ size_t size);
+
+ void* GetMemory() const override;
+ size_t GetSize() const override;
+
+ private:
+ mojo::ScopedSharedBufferHandle handle_;
+ void* memory_;
+ size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoBufferBacking);
+};
+
+} // namespace gles2
+
+#endif // SERVICES_GLES2_MOJO_BUFFER_BACKING_H_
diff --git a/mojo/services/keyboard/public/interfaces/BUILD.gn b/mojo/services/keyboard/public/interfaces/BUILD.gn
new file mode 100644
index 0000000..6aa9389
--- /dev/null
+++ b/mojo/services/keyboard/public/interfaces/BUILD.gn
@@ -0,0 +1,12 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+ sources = [
+ "keyboard.mojom",
+ ]
+}
diff --git a/mojo/services/keyboard/public/interfaces/keyboard.mojom b/mojo/services/keyboard/public/interfaces/keyboard.mojom
new file mode 100644
index 0000000..d11253e
--- /dev/null
+++ b/mojo/services/keyboard/public/interfaces/keyboard.mojom
@@ -0,0 +1,10 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo;
+
+interface Keyboard {
+ Show();
+ Hide();
+};
diff --git a/mojo/services/kiosk_wm/BUILD.gn b/mojo/services/kiosk_wm/BUILD.gn
new file mode 100644
index 0000000..99c25f0
--- /dev/null
+++ b/mojo/services/kiosk_wm/BUILD.gn
@@ -0,0 +1,34 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni")
+
+mojo_native_application("kiosk_wm") {
+ sources = [
+ "kiosk_wm.cc",
+ "kiosk_wm.h",
+ "main.cc",
+ "merged_service_provider.cc",
+ "merged_service_provider.h",
+ "navigator_host_impl.cc",
+ "navigator_host_impl.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common:common",
+ "//mojo/converters/geometry",
+ "//mojo/converters/input_events",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/utility",
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ "//third_party/mojo_services/src/input_events/public/interfaces",
+ "//third_party/mojo_services/src/navigation/public/interfaces",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//mojo/services/window_manager:lib",
+ "//ui/base",
+ ]
+}
diff --git a/mojo/services/kiosk_wm/DEPS b/mojo/services/kiosk_wm/DEPS
new file mode 100644
index 0000000..191abbe
--- /dev/null
+++ b/mojo/services/kiosk_wm/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+mojo/application",
+ "+mojo/services/window_manager",
+ "+third_party/mojo_services",
+ "+ui",
+]
diff --git a/mojo/services/kiosk_wm/README.md b/mojo/services/kiosk_wm/README.md
new file mode 100644
index 0000000..4c38010
--- /dev/null
+++ b/mojo/services/kiosk_wm/README.md
@@ -0,0 +1,6 @@
+A very simple window kiosk-like window manager.
+
+This can handle a single application at a time, any further calls to
+WindowManager::Embed will replace the existing application.
+
+Renders full-screen. On linux defaults to a Nexus-5 aspect ratio. \ No newline at end of file
diff --git a/mojo/services/kiosk_wm/kiosk_wm.cc b/mojo/services/kiosk_wm/kiosk_wm.cc
new file mode 100644
index 0000000..9aa2aab
--- /dev/null
+++ b/mojo/services/kiosk_wm/kiosk_wm.cc
@@ -0,0 +1,134 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/kiosk_wm/kiosk_wm.h"
+
+#include "mojo/services/kiosk_wm/merged_service_provider.h"
+#include "mojo/services/window_manager/basic_focus_rules.h"
+
+namespace kiosk_wm {
+
+KioskWM::KioskWM()
+ : window_manager_app_(new window_manager::WindowManagerApp(this, this)),
+ root_(nullptr),
+ content_(nullptr),
+ navigator_host_(this),
+ weak_factory_(this) {
+ exposed_services_impl_.AddService(this);
+}
+
+KioskWM::~KioskWM() {
+}
+
+base::WeakPtr<KioskWM> KioskWM::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+void KioskWM::Initialize(mojo::ApplicationImpl* app) {
+ window_manager_app_->Initialize(app);
+
+ // Format: --args-for="app_url default_url"
+ if (app->args().size() > 1)
+ default_url_ = app->args()[1];
+}
+
+bool KioskWM::ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) {
+ window_manager_app_->ConfigureIncomingConnection(connection);
+ return true;
+}
+
+bool KioskWM::ConfigureOutgoingConnection(
+ mojo::ApplicationConnection* connection) {
+ window_manager_app_->ConfigureOutgoingConnection(connection);
+ return true;
+}
+
+void KioskWM::OnEmbed(
+ mojo::View* root,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) {
+ // KioskWM does not support being embedded more than once.
+ CHECK(!root_);
+
+ root_ = root;
+ root_->AddObserver(this);
+
+ // Resize to match the Nexus 5 aspect ratio:
+ window_manager_app_->SetViewportSize(gfx::Size(320, 640));
+
+ content_ = root->view_manager()->CreateView();
+ content_->SetBounds(root_->bounds());
+ root_->AddChild(content_);
+ content_->SetVisible(true);
+
+ window_manager_app_->InitFocus(
+ make_scoped_ptr(new window_manager::BasicFocusRules(root_)));
+ window_manager_app_->accelerator_manager()->Register(
+ ui::Accelerator(ui::VKEY_BROWSER_BACK, 0),
+ ui::AcceleratorManager::kNormalPriority, this);
+
+ // Now that we're ready, either load a pending url or the default url.
+ if (!pending_url_.empty())
+ Embed(pending_url_, services.Pass(), exposed_services.Pass());
+ else if (!default_url_.empty())
+ Embed(default_url_, services.Pass(), exposed_services.Pass());
+}
+
+void KioskWM::Embed(const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) {
+ // We can get Embed calls before we've actually been
+ // embedded into the root view and content_ is created.
+ // Just save the last url, we'll embed it when we're ready.
+ if (!content_) {
+ pending_url_ = url;
+ return;
+ }
+
+ merged_service_provider_.reset(
+ new MergedServiceProvider(exposed_services.Pass(), this));
+ content_->Embed(url, services.Pass(),
+ merged_service_provider_->GetServiceProviderPtr().Pass());
+
+ navigator_host_.RecordNavigation(url);
+}
+
+void KioskWM::Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::NavigatorHost> request) {
+ navigator_host_.Bind(request.Pass());
+}
+
+void KioskWM::OnViewManagerDisconnected(
+ mojo::ViewManager* view_manager) {
+ root_ = nullptr;
+}
+
+void KioskWM::OnViewDestroyed(mojo::View* view) {
+ view->RemoveObserver(this);
+}
+
+void KioskWM::OnViewBoundsChanged(mojo::View* view,
+ const mojo::Rect& old_bounds,
+ const mojo::Rect& new_bounds) {
+ content_->SetBounds(new_bounds);
+}
+
+// Convenience method:
+void KioskWM::ReplaceContentWithURL(const mojo::String& url) {
+ Embed(url, nullptr, nullptr);
+}
+
+bool KioskWM::AcceleratorPressed(const ui::Accelerator& accelerator) {
+ if (accelerator.key_code() != ui::VKEY_BROWSER_BACK)
+ return false;
+ navigator_host_.RequestNavigateHistory(-1);
+ return true;
+}
+
+bool KioskWM::CanHandleAccelerators() const {
+ return true;
+}
+
+} // namespace kiosk_wm
diff --git a/mojo/services/kiosk_wm/kiosk_wm.h b/mojo/services/kiosk_wm/kiosk_wm.h
new file mode 100644
index 0000000..5ae0ded
--- /dev/null
+++ b/mojo/services/kiosk_wm/kiosk_wm.h
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_KIOSK_WM_KIOSK_WM_H_
+#define SERVICES_KIOSK_WM_KIOSK_WM_H_
+
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/services/kiosk_wm/navigator_host_impl.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "mojo/services/window_manager/window_manager_delegate.h"
+#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h"
+#include "third_party/mojo_services/src/navigation/public/interfaces/navigation.mojom.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace kiosk_wm {
+
+class MergedServiceProvider;
+
+class KioskWM : public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate,
+ public mojo::ViewObserver,
+ public window_manager::WindowManagerDelegate,
+ public mojo::InterfaceFactory<mojo::NavigatorHost>,
+ public ui::AcceleratorTarget {
+ public:
+ KioskWM();
+ ~KioskWM() override;
+
+ base::WeakPtr<KioskWM> GetWeakPtr();
+
+ void ReplaceContentWithURL(const mojo::String& url);
+
+ private:
+ // Overridden from mojo::ApplicationDelegate:
+ void Initialize(mojo::ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+ bool ConfigureOutgoingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ // Overridden from mojo::ViewManagerDelegate:
+ void OnEmbed(mojo::View* root,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override;
+ void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override;
+
+ // Overriden from mojo::ViewObserver:
+ void OnViewDestroyed(mojo::View* view) override;
+ void OnViewBoundsChanged(mojo::View* view,
+ const mojo::Rect& old_bounds,
+ const mojo::Rect& new_bounds) override;
+
+ // Overridden from WindowManagerDelegate:
+ void Embed(const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override;
+
+ // Overridden from mojo::InterfaceFactory<mojo::NavigatorHost>:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::NavigatorHost> request) override;
+
+ // Overriden from ui::AcceleratorTarget:
+ bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
+ bool CanHandleAccelerators() const override;
+
+ scoped_ptr<window_manager::WindowManagerApp> window_manager_app_;
+
+ // Only support being embedded once, so both application-level
+ // and embedding-level state are shared on the same object.
+ mojo::View* root_;
+ mojo::View* content_;
+ std::string default_url_;
+ std::string pending_url_;
+
+ mojo::ServiceProviderImpl exposed_services_impl_;
+ scoped_ptr<MergedServiceProvider> merged_service_provider_;
+
+ NavigatorHostImpl navigator_host_;
+
+ base::WeakPtrFactory<KioskWM> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(KioskWM);
+};
+
+} // namespace kiosk_wm
+
+#endif // SERVICES_KIOSK_WM_KIOSK_WM_H_
diff --git a/mojo/services/kiosk_wm/main.cc b/mojo/services/kiosk_wm/main.cc
new file mode 100644
index 0000000..fa60bf5
--- /dev/null
+++ b/mojo/services/kiosk_wm/main.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+
+#include "mojo/services/kiosk_wm/kiosk_wm.h"
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new kiosk_wm::KioskWM);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/kiosk_wm/merged_service_provider.cc b/mojo/services/kiosk_wm/merged_service_provider.cc
new file mode 100644
index 0000000..8987096
--- /dev/null
+++ b/mojo/services/kiosk_wm/merged_service_provider.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/kiosk_wm/merged_service_provider.h"
+
+namespace kiosk_wm {
+
+MergedServiceProvider::MergedServiceProvider(
+ mojo::ServiceProviderPtr exposed_services,
+ mojo::InterfaceFactory<mojo::NavigatorHost>* factory)
+ : exposed_services_(exposed_services.Pass()), factory_(factory) {
+}
+
+MergedServiceProvider::~MergedServiceProvider() {
+}
+
+mojo::ServiceProviderPtr MergedServiceProvider::GetServiceProviderPtr() {
+ mojo::ServiceProviderPtr sp;
+ binding_.reset(new mojo::Binding<mojo::ServiceProvider>(this, GetProxy(&sp)));
+ return sp.Pass();
+}
+
+void MergedServiceProvider::ConnectToService(
+ const mojo::String& interface_name,
+ mojo::ScopedMessagePipeHandle pipe) {
+ if (interface_name == mojo::NavigatorHost::Name_) {
+ factory_->Create(nullptr,
+ mojo::MakeRequest<mojo::NavigatorHost>(pipe.Pass()));
+ } else if (exposed_services_.get()) {
+ exposed_services_->ConnectToService(interface_name, pipe.Pass());
+ }
+}
+
+} // namespace kiosk_wm
diff --git a/mojo/services/kiosk_wm/merged_service_provider.h b/mojo/services/kiosk_wm/merged_service_provider.h
new file mode 100644
index 0000000..3b76138
--- /dev/null
+++ b/mojo/services/kiosk_wm/merged_service_provider.h
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_KIOSK_WM_MERGED_SERVICE_PROVIDER_H_
+#define SERVICES_KIOSK_WM_MERGED_SERVICE_PROVIDER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "third_party/mojo_services/src/navigation/public/interfaces/navigation.mojom.h"
+
+namespace kiosk_wm {
+
+// Used to wrap the second incoming WindowManager Embed() "exposed_services"
+// parameter with a new ServiceProvider that adds the KioskWM's
+// NavigatorHost service.
+class MergedServiceProvider : public mojo::ServiceProvider {
+ public:
+ MergedServiceProvider(mojo::ServiceProviderPtr exposed_services,
+ mojo::InterfaceFactory<mojo::NavigatorHost>* factory);
+ ~MergedServiceProvider() override;
+
+ mojo::ServiceProviderPtr GetServiceProviderPtr();
+
+ private:
+ // mojo::ServiceProvider:
+ void ConnectToService(const mojo::String& interface_name,
+ mojo::ScopedMessagePipeHandle pipe) override;
+
+ mojo::ServiceProviderPtr exposed_services_;
+ mojo::InterfaceFactory<mojo::NavigatorHost>* factory_;
+ scoped_ptr<mojo::Binding<ServiceProvider>> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(MergedServiceProvider);
+};
+
+} // namespace kiosk_wm
+
+#endif // SERVICES_KIOSK_WM_MERGED_SERVICE_PROVIDER_H_
diff --git a/mojo/services/kiosk_wm/navigator_host_impl.cc b/mojo/services/kiosk_wm/navigator_host_impl.cc
new file mode 100644
index 0000000..3b28339
--- /dev/null
+++ b/mojo/services/kiosk_wm/navigator_host_impl.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/kiosk_wm/navigator_host_impl.h"
+
+#include "mojo/services/kiosk_wm/kiosk_wm.h"
+
+namespace kiosk_wm {
+
+NavigatorHostImpl::NavigatorHostImpl(KioskWM* window_manager)
+ : current_index_(-1), kiosk_wm_(window_manager) {
+}
+
+NavigatorHostImpl::~NavigatorHostImpl() {
+}
+
+void NavigatorHostImpl::Bind(
+ mojo::InterfaceRequest<mojo::NavigatorHost> request) {
+ bindings_.AddBinding(this, request.Pass());
+}
+
+void NavigatorHostImpl::DidNavigateLocally(const mojo::String& url) {
+ RecordNavigation(url);
+ // TODO(abarth): Do something interesting.
+}
+
+void NavigatorHostImpl::RequestNavigate(mojo::Target target,
+ mojo::URLRequestPtr request) {
+ // kiosk_wm sets up default services including navigation.
+ kiosk_wm_->ReplaceContentWithURL(request->url);
+}
+
+void NavigatorHostImpl::RequestNavigateHistory(int32_t delta) {
+ if (history_.empty())
+ return;
+ current_index_ =
+ std::max(0, std::min(current_index_ + delta,
+ static_cast<int32_t>(history_.size()) - 1));
+ kiosk_wm_->ReplaceContentWithURL(history_[current_index_]);
+}
+
+void NavigatorHostImpl::RecordNavigation(const std::string& url) {
+ if (current_index_ >= 0 && history_[current_index_] == url) {
+ // This is a navigation to the current entry, ignore.
+ return;
+ }
+
+ history_.erase(history_.begin() + (current_index_ + 1), history_.end());
+ history_.push_back(url);
+ ++current_index_;
+}
+
+} // namespace kiosk_wm
diff --git a/mojo/services/kiosk_wm/navigator_host_impl.h b/mojo/services/kiosk_wm/navigator_host_impl.h
new file mode 100644
index 0000000..bf47a97
--- /dev/null
+++ b/mojo/services/kiosk_wm/navigator_host_impl.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 SERVICES_KIOSK_WM_NAVIGATOR_HOST_IMPL_H_
+#define SERVICES_KIOSK_WM_NAVIGATOR_HOST_IMPL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "mojo/common/weak_binding_set.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "third_party/mojo_services/src/navigation/public/interfaces/navigation.mojom.h"
+
+namespace kiosk_wm {
+class KioskWM;
+
+class NavigatorHostImpl : public mojo::NavigatorHost {
+ public:
+ NavigatorHostImpl(KioskWM* kiosk_wm);
+ ~NavigatorHostImpl() override;
+
+ void Bind(mojo::InterfaceRequest<mojo::NavigatorHost> request);
+
+ void RecordNavigation(const std::string& url);
+
+ // mojo::NavigatorHost implementation:
+ void DidNavigateLocally(const mojo::String& url) override;
+ void RequestNavigate(mojo::Target target,
+ mojo::URLRequestPtr request) override;
+ void RequestNavigateHistory(int32_t delta) override;
+
+ private:
+ std::vector<std::string> history_;
+ int32_t current_index_;
+
+ KioskWM* kiosk_wm_;
+ mojo::WeakBindingSet<NavigatorHost> bindings_;
+
+ DISALLOW_COPY_AND_ASSIGN(NavigatorHostImpl);
+};
+
+} // namespace kiosk_wm
+
+#endif // SERVICES_KIOSK_WM_NAVIGATOR_HOST_IMPL_H_
diff --git a/mojo/services/native_viewport/BUILD.gn b/mojo/services/native_viewport/BUILD.gn
new file mode 100644
index 0000000..3e77370
--- /dev/null
+++ b/mojo/services/native_viewport/BUILD.gn
@@ -0,0 +1,110 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ui.gni")
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+
+if (is_android) {
+ import("//build/config/android/config.gni")
+ import("//build/config/android/rules.gni")
+
+ group("native_viewport") {
+ deps = [
+ ":lib",
+ ":native_viewport_java",
+ ":jni_headers",
+ ]
+ }
+
+ android_library("native_viewport_java") {
+ java_files =
+ [ "android/src/org/chromium/mojo/PlatformViewportAndroid.java" ]
+
+ deps = [
+ "//base:base_java",
+ ]
+ }
+
+ generate_jni("jni_headers") {
+ sources = [
+ "android/src/org/chromium/mojo/PlatformViewportAndroid.java",
+ ]
+
+ jni_package = "mojo"
+ }
+} else {
+ mojo_native_application("native_viewport") {
+ output_name = "native_viewport_service"
+ sources = [
+ "main.cc",
+ ]
+ deps = [
+ ":lib",
+ "//base",
+ "//mojo/application",
+ "//mojo/common:tracing_impl",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo_services/src/native_viewport/public/interfaces",
+ "//third_party/mojo_services/src/native_viewport/public/cpp:args",
+ "//mojo/services/gles2",
+ "//ui/events",
+ "//ui/events/platform",
+ "//ui/gl",
+ ]
+ }
+}
+
+source_set("lib") {
+ sources = [
+ "native_viewport_impl.cc",
+ "native_viewport_impl.h",
+ "onscreen_context_provider.cc",
+ "onscreen_context_provider.h",
+ "platform_viewport.h",
+ "platform_viewport_android.cc",
+ "platform_viewport_android.h",
+ "platform_viewport_headless.cc",
+ "platform_viewport_headless.h",
+ "platform_viewport_stub.cc",
+ "platform_viewport_win.cc",
+ "platform_viewport_x11.cc",
+ ]
+
+ if (!is_ios) {
+ sources -= [ "platform_viewport_stub.cc" ]
+ }
+
+ deps = [
+ "//base",
+ "//gpu/command_buffer/service",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/converters/geometry",
+ "//mojo/converters/input_events",
+ "//mojo/environment:chromium",
+ "//mojo/services/gles2",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/native_viewport/public/interfaces",
+ "//ui/events",
+ "//ui/events/platform",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl",
+ "//ui/platform_window",
+ ]
+
+ if (is_android) {
+ deps += [ ":jni_headers" ]
+ }
+
+ if (use_x11) {
+ deps += [ "//ui/platform_window/x11" ]
+ } else {
+ sources -= [ "platform_viewport_x11.cc" ]
+ }
+
+ if (is_win) {
+ deps += [ "//ui/platform_window/win:win_window" ]
+ }
+}
diff --git a/mojo/services/native_viewport/DEPS b/mojo/services/native_viewport/DEPS
new file mode 100644
index 0000000..9c70113
--- /dev/null
+++ b/mojo/services/native_viewport/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+mojo/application",
+ "+mojo/converters",
+ "+mojo/services/gles2",
+ "+third_party/mojo_services",
+ "+ui",
+]
diff --git a/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java b/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java
new file mode 100644
index 0000000..15eb96c
--- /dev/null
+++ b/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java
@@ -0,0 +1,183 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Exposes SurfaceView to native code.
+ */
+@JNINamespace("native_viewport")
+public class PlatformViewportAndroid extends SurfaceView {
+
+ private long mNativeMojoViewport;
+ private final SurfaceHolder.Callback mSurfaceCallback;
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static void createForActivity(Activity activity, long nativeViewport) {
+ activity.setContentView(new PlatformViewportAndroid(activity, nativeViewport));
+ }
+
+ public PlatformViewportAndroid(Context context, long nativeViewport) {
+ super(context);
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+
+ mNativeMojoViewport = nativeViewport;
+ assert mNativeMojoViewport != 0;
+
+ final float density = context.getResources().getDisplayMetrics().density;
+
+ mSurfaceCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ assert mNativeMojoViewport != 0;
+ nativeSurfaceSetSize(mNativeMojoViewport, width, height, density);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ assert mNativeMojoViewport != 0;
+ nativeSurfaceCreated(mNativeMojoViewport, holder.getSurface());
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ assert mNativeMojoViewport != 0;
+ nativeSurfaceDestroyed(mNativeMojoViewport);
+ }
+ };
+ getHolder().addCallback(mSurfaceCallback);
+
+ }
+
+ // TODO(abarth): Someone needs to call destroy at some point.
+ public void destroy() {
+ getHolder().removeCallback(mSurfaceCallback);
+ nativeDestroy(mNativeMojoViewport);
+ mNativeMojoViewport = 0;
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility == View.VISIBLE) {
+ requestFocusFromTouch();
+ requestFocus();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final int actionMasked = event.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_POINTER_DOWN
+ || actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ // Up/down events identify a single point.
+ return notifyTouchEventAtIndex(event, event.getActionIndex());
+ }
+ assert event.getPointerCount() != 0;
+ // All other types can have more than one point.
+ boolean result = false;
+ for (int i = 0, count = event.getPointerCount(); i < count; i++) {
+ final boolean sub_result = notifyTouchEventAtIndex(event, i);
+ result |= sub_result;
+ }
+ return result;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (privateDispatchKeyEvent(event)) {
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ if (privateDispatchKeyEvent(event)) {
+ return true;
+ }
+ return super.dispatchKeyEventPreIme(event);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ if (privateDispatchKeyEvent(event)) {
+ return true;
+ }
+ return super.dispatchKeyShortcutEvent(event);
+ }
+
+ private boolean notifyTouchEventAtIndex(MotionEvent event, int index) {
+ return nativeTouchEvent(mNativeMojoViewport, event.getEventTime(), event.getActionMasked(),
+ event.getPointerId(index), event.getX(index), event.getY(index),
+ event.getPressure(index), event.getTouchMajor(index), event.getTouchMinor(index),
+ event.getOrientation(index), event.getAxisValue(MotionEvent.AXIS_HSCROLL, index),
+ event.getAxisValue(MotionEvent.AXIS_VSCROLL, index));
+ }
+
+ private boolean privateDispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ boolean result = false;
+ if (event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
+ String characters = event.getCharacters();
+ for (int i = 0; i < characters.length(); ++i) {
+ char c = characters.charAt(i);
+ int codepoint = c;
+ if (codepoint >= Character.MIN_SURROGATE
+ && codepoint < (Character.MAX_SURROGATE + 1)) {
+ i++;
+ char c2 = characters.charAt(i);
+ codepoint = Character.toCodePoint(c, c2);
+ }
+ result |= nativeKeyEvent(mNativeMojoViewport, true, 0, codepoint);
+ result |= nativeKeyEvent(mNativeMojoViewport, false, 0, codepoint);
+ }
+ } else {
+ for (int i = 0; i < event.getRepeatCount(); ++i) {
+ result |= nativeKeyEvent(
+ mNativeMojoViewport, true, event.getKeyCode(), event.getUnicodeChar());
+ result |= nativeKeyEvent(
+ mNativeMojoViewport, false, event.getKeyCode(), event.getUnicodeChar());
+ }
+ }
+ return result;
+ } else {
+ return nativeKeyEvent(mNativeMojoViewport, event.getAction() == KeyEvent.ACTION_DOWN,
+ event.getKeyCode(), event.getUnicodeChar());
+ }
+ }
+
+ private static native void nativeDestroy(long nativePlatformViewportAndroid);
+
+ private static native void nativeSurfaceCreated(
+ long nativePlatformViewportAndroid, Surface surface);
+
+ private static native void nativeSurfaceDestroyed(
+ long nativePlatformViewportAndroid);
+
+ private static native void nativeSurfaceSetSize(
+ long nativePlatformViewportAndroid, int width, int height, float density);
+
+ private static native boolean nativeTouchEvent(long nativePlatformViewportAndroid, long timeMs,
+ int maskedAction, int pointerId, float x, float y, float pressure, float touchMajor,
+ float touchMinor, float orientation, float hWheel, float vWheel);
+
+ private static native boolean nativeKeyEvent(
+ long nativePlatformViewportAndroid, boolean pressed, int keyCode, int unicodeCharacter);
+}
diff --git a/mojo/services/native_viewport/main.cc b/mojo/services/native_viewport/main.cc
new file mode 100644
index 0000000..504e555
--- /dev/null
+++ b/mojo/services/native_viewport/main.cc
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/services/gles2/gpu_impl.h"
+#include "mojo/services/native_viewport/native_viewport_impl.h"
+#include "third_party/mojo_services/src/native_viewport/public/cpp/args.h"
+#include "ui/events/event_switches.h"
+#include "ui/gl/gl_surface.h"
+
+using mojo::ApplicationConnection;
+using mojo::Gpu;
+using mojo::NativeViewport;
+
+namespace native_viewport {
+
+class NativeViewportAppDelegate : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<NativeViewport>,
+ public mojo::InterfaceFactory<Gpu> {
+ public:
+ NativeViewportAppDelegate() : is_headless_(false) {}
+ ~NativeViewportAppDelegate() override {}
+
+ private:
+ // mojo::ApplicationDelegate implementation.
+ void Initialize(mojo::ApplicationImpl* application) override {
+ tracing_.Initialize(application);
+
+#if defined(OS_LINUX)
+ // Apply the switch for kTouchEvents to CommandLine (if set). This allows
+ // redirecting the mouse to a touch device on X for testing.
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ const std::string touch_event_string("--" +
+ std::string(switches::kTouchDevices));
+ auto touch_iter = std::find(application->args().begin(),
+ application->args().end(),
+ touch_event_string);
+ if (touch_iter != application->args().end() &&
+ ++touch_iter != application->args().end()) {
+ command_line->AppendSwitchASCII(touch_event_string, *touch_iter);
+ }
+#endif
+
+ if (application->HasArg(mojo::kUseTestConfig))
+ gfx::GLSurface::InitializeOneOffForTests();
+ else
+ gfx::GLSurface::InitializeOneOff();
+
+ is_headless_ = application->HasArg(mojo::kUseHeadlessConfig);
+ }
+
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ connection->AddService<NativeViewport>(this);
+ connection->AddService<Gpu>(this);
+ return true;
+ }
+
+ // mojo::InterfaceFactory<NativeViewport> implementation.
+ void Create(ApplicationConnection* connection,
+ mojo::InterfaceRequest<NativeViewport> request) override {
+ if (!gpu_state_.get())
+ gpu_state_ = new gles2::GpuState;
+ new NativeViewportImpl(is_headless_, gpu_state_, request.Pass());
+ }
+
+ // mojo::InterfaceFactory<Gpu> implementation.
+ void Create(ApplicationConnection* connection,
+ mojo::InterfaceRequest<Gpu> request) override {
+ if (!gpu_state_.get())
+ gpu_state_ = new gles2::GpuState;
+ new gles2::GpuImpl(request.Pass(), gpu_state_);
+ }
+
+ scoped_refptr<gles2::GpuState> gpu_state_;
+ bool is_headless_;
+ mojo::TracingImpl tracing_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportAppDelegate);
+};
+}
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(
+ new native_viewport::NativeViewportAppDelegate);
+ runner.set_message_loop_type(base::MessageLoop::TYPE_UI);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/native_viewport/native_viewport_impl.cc b/mojo/services/native_viewport/native_viewport_impl.cc
new file mode 100644
index 0000000..f5bcc27
--- /dev/null
+++ b/mojo/services/native_viewport/native_viewport_impl.cc
@@ -0,0 +1,164 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/native_viewport_impl.h"
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/gles2/gpu_state.h"
+#include "mojo/services/native_viewport/platform_viewport_headless.h"
+#include "ui/events/event.h"
+
+namespace native_viewport {
+
+NativeViewportImpl::NativeViewportImpl(
+ bool is_headless,
+ const scoped_refptr<gles2::GpuState>& gpu_state,
+ mojo::InterfaceRequest<mojo::NativeViewport> request)
+ : is_headless_(is_headless),
+ context_provider_(gpu_state),
+ sent_metrics_(false),
+ metrics_(mojo::ViewportMetrics::New()),
+ binding_(this, request.Pass()),
+ weak_factory_(this) {
+ binding_.set_error_handler(this);
+}
+
+NativeViewportImpl::~NativeViewportImpl() {
+ // Destroy the NativeViewport early on as it may call us back during
+ // destruction and we want to be in a known state.
+ platform_viewport_.reset();
+}
+
+void NativeViewportImpl::Create(mojo::SizePtr size,
+ const CreateCallback& callback) {
+ create_callback_ = callback;
+ metrics_->size = size.Clone();
+ if (is_headless_)
+ platform_viewport_ = PlatformViewportHeadless::Create(this);
+ else
+ platform_viewport_ = PlatformViewport::Create(this);
+ platform_viewport_->Init(gfx::Rect(size.To<gfx::Size>()));
+}
+
+void NativeViewportImpl::RequestMetrics(
+ const RequestMetricsCallback& callback) {
+ if (!sent_metrics_) {
+ callback.Run(metrics_.Clone());
+ sent_metrics_ = true;
+ return;
+ }
+ metrics_callback_ = callback;
+}
+
+void NativeViewportImpl::Show() {
+ platform_viewport_->Show();
+}
+
+void NativeViewportImpl::Hide() {
+ platform_viewport_->Hide();
+}
+
+void NativeViewportImpl::Close() {
+ DCHECK(platform_viewport_);
+ platform_viewport_->Close();
+}
+
+void NativeViewportImpl::SetSize(mojo::SizePtr size) {
+ platform_viewport_->SetBounds(gfx::Rect(size.To<gfx::Size>()));
+}
+
+void NativeViewportImpl::GetContextProvider(
+ mojo::InterfaceRequest<mojo::ContextProvider> request) {
+ context_provider_.Bind(request.Pass());
+}
+
+void NativeViewportImpl::SetEventDispatcher(
+ mojo::NativeViewportEventDispatcherPtr dispatcher) {
+ event_dispatcher_ = dispatcher.Pass();
+}
+
+void NativeViewportImpl::OnMetricsChanged(mojo::ViewportMetricsPtr metrics) {
+ if (metrics_->Equals(*metrics))
+ return;
+
+ metrics_ = metrics.Pass();
+ sent_metrics_ = false;
+
+ if (!metrics_callback_.is_null()) {
+ metrics_callback_.Run(metrics_.Clone());
+ metrics_callback_.reset();
+ sent_metrics_ = true;
+ }
+}
+
+void NativeViewportImpl::OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) {
+ context_provider_.SetAcceleratedWidget(widget);
+ // TODO: The metrics here might not match the actual window size on android
+ // where we don't know the actual size until the first OnMetricsChanged call.
+ create_callback_.Run(metrics_.Clone());
+ sent_metrics_ = true;
+ create_callback_.reset();
+}
+
+void NativeViewportImpl::OnAcceleratedWidgetDestroyed() {
+ context_provider_.SetAcceleratedWidget(gfx::kNullAcceleratedWidget);
+}
+
+bool NativeViewportImpl::OnEvent(mojo::EventPtr event) {
+ if (event.is_null() || !event_dispatcher_.get())
+ return false;
+
+ mojo::NativeViewportEventDispatcher::OnEventCallback callback;
+ switch (event->action) {
+ case mojo::EVENT_TYPE_POINTER_MOVE: {
+ // TODO(sky): add logic to remember last event location and not send if
+ // the same.
+ if (pointers_waiting_on_ack_.count(event->pointer_data->pointer_id))
+ return false;
+
+ pointers_waiting_on_ack_.insert(event->pointer_data->pointer_id);
+ callback =
+ base::Bind(&NativeViewportImpl::AckEvent, weak_factory_.GetWeakPtr(),
+ event->pointer_data->pointer_id);
+ break;
+ }
+
+ case mojo::EVENT_TYPE_POINTER_CANCEL:
+ pointers_waiting_on_ack_.clear();
+ break;
+
+ case mojo::EVENT_TYPE_POINTER_UP:
+ pointers_waiting_on_ack_.erase(event->pointer_data->pointer_id);
+ break;
+
+ default:
+ break;
+ }
+
+ event_dispatcher_->OnEvent(event.Pass(), callback);
+ return false;
+}
+
+void NativeViewportImpl::OnDestroyed() {
+ // This will signal a connection error and cause us to delete |this|.
+ binding_.Close();
+}
+
+void NativeViewportImpl::OnConnectionError() {
+ binding_.set_error_handler(nullptr);
+ delete this;
+}
+
+void NativeViewportImpl::AckEvent(int32 pointer_id) {
+ pointers_waiting_on_ack_.erase(pointer_id);
+}
+
+} // namespace native_viewport
diff --git a/mojo/services/native_viewport/native_viewport_impl.h b/mojo/services/native_viewport/native_viewport_impl.h
new file mode 100644
index 0000000..3c2344a
--- /dev/null
+++ b/mojo/services/native_viewport/native_viewport_impl.h
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_IMPL_H_
+#define SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_IMPL_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/services/native_viewport/onscreen_context_provider.h"
+#include "mojo/services/native_viewport/platform_viewport.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h"
+#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace gles2 {
+class GpuState;
+}
+
+namespace ui {
+class Event;
+}
+
+namespace native_viewport {
+
+// A NativeViewportImpl is bound to a message pipe and to a PlatformViewport.
+// The NativeViewportImpl's lifetime ends when either the message pipe is closed
+// or the PlatformViewport informs the NativeViewportImpl that it has been
+// destroyed.
+class NativeViewportImpl : public mojo::NativeViewport,
+ public PlatformViewport::Delegate,
+ public mojo::ErrorHandler {
+ public:
+ NativeViewportImpl(bool is_headless,
+ const scoped_refptr<gles2::GpuState>& gpu_state,
+ mojo::InterfaceRequest<mojo::NativeViewport> request);
+ ~NativeViewportImpl() override;
+
+ // NativeViewport implementation.
+ void Create(mojo::SizePtr size, const CreateCallback& callback) override;
+ void RequestMetrics(const RequestMetricsCallback& callback) override;
+ void Show() override;
+ void Hide() override;
+ void Close() override;
+ void SetSize(mojo::SizePtr size) override;
+ void GetContextProvider(
+ mojo::InterfaceRequest<mojo::ContextProvider> request) override;
+ void SetEventDispatcher(
+ mojo::NativeViewportEventDispatcherPtr dispatcher) override;
+
+ // PlatformViewport::Delegate implementation.
+ void OnMetricsChanged(mojo::ViewportMetricsPtr metrics) override;
+ void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override;
+ void OnAcceleratedWidgetDestroyed() override;
+ bool OnEvent(mojo::EventPtr event) override;
+ void OnDestroyed() override;
+
+ // mojo::ErrorHandler implementation.
+ void OnConnectionError() override;
+
+ private:
+ // Callback when the dispatcher has processed a message we're waiting on
+ // an ack from. |pointer_id| identifies the pointer the message was associated
+ // with.
+ void AckEvent(int32 pointer_id);
+
+ bool is_headless_;
+ scoped_ptr<PlatformViewport> platform_viewport_;
+ OnscreenContextProvider context_provider_;
+ bool sent_metrics_;
+ mojo::ViewportMetricsPtr metrics_;
+ CreateCallback create_callback_;
+ RequestMetricsCallback metrics_callback_;
+ mojo::NativeViewportEventDispatcherPtr event_dispatcher_;
+ mojo::Binding<mojo::NativeViewport> binding_;
+
+ // Set of pointer_ids we've sent a move to and are waiting on an ack.
+ std::set<int32> pointers_waiting_on_ack_;
+
+ base::WeakPtrFactory<NativeViewportImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportImpl);
+};
+
+} // namespace native_viewport
+
+#endif // SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_IMPL_H_
diff --git a/mojo/services/native_viewport/onscreen_context_provider.cc b/mojo/services/native_viewport/onscreen_context_provider.cc
new file mode 100644
index 0000000..af72736
--- /dev/null
+++ b/mojo/services/native_viewport/onscreen_context_provider.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/onscreen_context_provider.h"
+
+#include "mojo/services/gles2/command_buffer_driver.h"
+#include "mojo/services/gles2/command_buffer_impl.h"
+#include "mojo/services/gles2/gpu_state.h"
+
+namespace native_viewport {
+
+OnscreenContextProvider::OnscreenContextProvider(
+ const scoped_refptr<gles2::GpuState>& state)
+ : state_(state), widget_(gfx::kNullAcceleratedWidget), binding_(this) {
+}
+
+OnscreenContextProvider::~OnscreenContextProvider() {
+}
+
+void OnscreenContextProvider::Bind(
+ mojo::InterfaceRequest<mojo::ContextProvider> request) {
+ binding_.Bind(request.Pass());
+}
+
+void OnscreenContextProvider::SetAcceleratedWidget(
+ gfx::AcceleratedWidget widget) {
+ widget_ = widget;
+ if (widget_ != gfx::kNullAcceleratedWidget &&
+ !pending_create_callback_.is_null())
+ CreateAndReturnCommandBuffer();
+}
+
+void OnscreenContextProvider::Create(
+ mojo::ViewportParameterListenerPtr viewport_parameter_listener,
+ const CreateCallback& callback) {
+ if (!pending_create_callback_.is_null())
+ pending_create_callback_.Run(nullptr);
+ pending_listener_ = viewport_parameter_listener.Pass();
+ pending_create_callback_ = callback;
+
+ if (widget_ != gfx::kNullAcceleratedWidget)
+ CreateAndReturnCommandBuffer();
+}
+
+void OnscreenContextProvider::CreateAndReturnCommandBuffer() {
+ mojo::CommandBufferPtr cb;
+ new gles2::CommandBufferImpl(
+ GetProxy(&cb), pending_listener_.Pass(), state_->control_task_runner(),
+ state_->sync_point_manager(),
+ make_scoped_ptr(new gles2::CommandBufferDriver(
+ widget_, state_->share_group(), state_->mailbox_manager(),
+ state_->sync_point_manager())));
+ pending_create_callback_.Run(cb.Pass());
+ pending_create_callback_.reset();
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/onscreen_context_provider.h b/mojo/services/native_viewport/onscreen_context_provider.h
new file mode 100644
index 0000000..e8a05d5
--- /dev/null
+++ b/mojo/services/native_viewport/onscreen_context_provider.h
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NATIVE_VIEWPORT_ONSCREEN_CONTEXT_PROVIDER_H_
+#define SERVICES_NATIVE_VIEWPORT_ONSCREEN_CONTEXT_PROVIDER_H_
+
+#include "base/memory/ref_counted.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/context_provider.mojom.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/viewport_parameter_listener.mojom.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gles2 {
+class GpuState;
+}
+
+namespace native_viewport {
+
+class OnscreenContextProvider : public mojo::ContextProvider {
+ public:
+ explicit OnscreenContextProvider(const scoped_refptr<gles2::GpuState>& state);
+ ~OnscreenContextProvider() override;
+
+ void Bind(mojo::InterfaceRequest<mojo::ContextProvider> request);
+
+ void SetAcceleratedWidget(gfx::AcceleratedWidget widget);
+
+ private:
+ // mojo::ContextProvider implementation:
+ void Create(mojo::ViewportParameterListenerPtr viewport_parameter_listener,
+ const CreateCallback& callback) override;
+
+ void CreateAndReturnCommandBuffer();
+
+ scoped_refptr<gles2::GpuState> state_;
+ gfx::AcceleratedWidget widget_;
+ mojo::ViewportParameterListenerPtr pending_listener_;
+ CreateCallback pending_create_callback_;
+ mojo::Binding<mojo::ContextProvider> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(OnscreenContextProvider);
+};
+
+} // namespace mojo
+
+#endif // SERVICES_NATIVE_VIEWPORT_ONSCREEN_CONTEXT_PROVIDER_H_
diff --git a/mojo/services/native_viewport/platform_viewport.h b/mojo/services/native_viewport/platform_viewport.h
new file mode 100644
index 0000000..62594b0
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport.h
@@ -0,0 +1,49 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_
+#define SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h"
+#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace native_viewport {
+
+// Encapsulation of platform-specific Viewport.
+class PlatformViewport {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual void OnMetricsChanged(mojo::ViewportMetricsPtr metrics) = 0;
+ virtual void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) = 0;
+ virtual void OnAcceleratedWidgetDestroyed() = 0;
+ virtual bool OnEvent(mojo::EventPtr event) = 0;
+ virtual void OnDestroyed() = 0;
+ };
+
+ virtual ~PlatformViewport() {}
+
+ virtual void Init(const gfx::Rect& bounds) = 0;
+ virtual void Show() = 0;
+ virtual void Hide() = 0;
+ virtual void Close() = 0;
+ virtual gfx::Size GetSize() = 0;
+ virtual void SetBounds(const gfx::Rect& bounds) = 0;
+
+ static scoped_ptr<PlatformViewport> Create(Delegate* delegate);
+};
+
+} // namespace native_viewport
+
+#endif // SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_
diff --git a/mojo/services/native_viewport/platform_viewport_android.cc b/mojo/services/native_viewport/platform_viewport_android.cc
new file mode 100644
index 0000000..90bfc76
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_android.cc
@@ -0,0 +1,208 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/platform_viewport_android.h"
+
+#include <android/input.h>
+#include <android/native_window_jni.h>
+
+#include "base/android/jni_android.h"
+#include "jni/PlatformViewportAndroid_jni.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "ui/events/event.h"
+#include "ui/events/keycodes/keyboard_code_conversion_android.h"
+#include "ui/gfx/geometry/point.h"
+
+namespace native_viewport {
+namespace {
+
+mojo::EventType MotionEventActionToEventType(jint action) {
+ switch (action) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ return mojo::EVENT_TYPE_POINTER_DOWN;
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ return mojo::EVENT_TYPE_POINTER_UP;
+ case AMOTION_EVENT_ACTION_MOVE:
+ return mojo::EVENT_TYPE_POINTER_MOVE;
+ case AMOTION_EVENT_ACTION_CANCEL:
+ return mojo::EVENT_TYPE_POINTER_CANCEL;
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ case AMOTION_EVENT_ACTION_SCROLL:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ default:
+ NOTIMPLEMENTED() << "Unimplemented motion action: " << action;
+ }
+ return mojo::EVENT_TYPE_UNKNOWN;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewportAndroid, public:
+
+// static
+bool PlatformViewportAndroid::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+PlatformViewportAndroid::PlatformViewportAndroid(Delegate* delegate)
+ : delegate_(delegate),
+ window_(NULL),
+ id_generator_(0),
+ weak_factory_(this) {
+}
+
+PlatformViewportAndroid::~PlatformViewportAndroid() {
+ if (window_)
+ ReleaseWindow();
+}
+
+void PlatformViewportAndroid::Destroy(JNIEnv* env, jobject obj) {
+ delegate_->OnDestroyed();
+}
+
+void PlatformViewportAndroid::SurfaceCreated(JNIEnv* env,
+ jobject obj,
+ jobject jsurface) {
+ base::android::ScopedJavaLocalRef<jobject> protector(env, jsurface);
+ // Note: This ensures that any local references used by
+ // ANativeWindow_fromSurface are released immediately. This is needed as a
+ // workaround for https://code.google.com/p/android/issues/detail?id=68174
+ {
+ base::android::ScopedJavaLocalFrame scoped_local_reference_frame(env);
+ window_ = ANativeWindow_fromSurface(env, jsurface);
+ }
+ delegate_->OnAcceleratedWidgetAvailable(window_);
+}
+
+void PlatformViewportAndroid::SurfaceDestroyed(JNIEnv* env, jobject obj) {
+ DCHECK(window_);
+ delegate_->OnAcceleratedWidgetDestroyed();
+ ReleaseWindow();
+}
+
+void PlatformViewportAndroid::SurfaceSetSize(JNIEnv* env,
+ jobject obj,
+ jint width,
+ jint height,
+ jfloat density) {
+ metrics_ = mojo::ViewportMetrics::New();
+ metrics_->size = mojo::Size::New();
+ metrics_->size->width = static_cast<int>(width);
+ metrics_->size->height = static_cast<int>(height);
+ metrics_->device_pixel_ratio = density;
+ delegate_->OnMetricsChanged(metrics_.Clone());
+}
+
+bool PlatformViewportAndroid::TouchEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jint masked_action,
+ jint pointer_id,
+ jfloat x,
+ jfloat y,
+ jfloat pressure,
+ jfloat touch_major,
+ jfloat touch_minor,
+ jfloat orientation,
+ jfloat h_wheel,
+ jfloat v_wheel) {
+ mojo::EventPtr event(mojo::Event::New());
+ event->time_stamp = time_ms;
+ event->action = MotionEventActionToEventType(masked_action);
+ if (event->action == mojo::EVENT_TYPE_UNKNOWN)
+ return false;
+
+ event->pointer_data = mojo::PointerData::New();
+ event->pointer_data->pointer_id = pointer_id;
+ event->pointer_data->x = x;
+ event->pointer_data->y = y;
+ event->pointer_data->screen_x = x;
+ event->pointer_data->screen_y = y;
+ event->pointer_data->pressure = pressure;
+ event->pointer_data->radius_major = touch_major;
+ event->pointer_data->radius_minor = touch_minor;
+ event->pointer_data->orientation = orientation;
+ event->pointer_data->horizontal_wheel = h_wheel;
+ event->pointer_data->vertical_wheel = v_wheel;
+ delegate_->OnEvent(event.Pass());
+
+ return true;
+}
+
+bool PlatformViewportAndroid::KeyEvent(JNIEnv* env,
+ jobject obj,
+ bool pressed,
+ jint key_code,
+ jint unicode_character) {
+ ui::KeyEvent event(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+ ui::KeyboardCodeFromAndroidKeyCode(key_code), 0);
+ event.set_platform_keycode(key_code);
+ delegate_->OnEvent(mojo::Event::From(event));
+ if (pressed && unicode_character) {
+ ui::KeyEvent char_event(unicode_character,
+ ui::KeyboardCodeFromAndroidKeyCode(key_code), 0);
+ char_event.set_platform_keycode(key_code);
+ delegate_->OnEvent(mojo::Event::From(char_event));
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewportAndroid, PlatformViewport implementation:
+
+void PlatformViewportAndroid::Init(const gfx::Rect& bounds) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_PlatformViewportAndroid_createForActivity(
+ env,
+ base::android::GetApplicationContext(),
+ reinterpret_cast<jlong>(this));
+}
+
+void PlatformViewportAndroid::Show() {
+ // Nothing to do. View is created visible.
+}
+
+void PlatformViewportAndroid::Hide() {
+ // Nothing to do. View is always visible.
+}
+
+void PlatformViewportAndroid::Close() {
+ // TODO(beng): close activity containing MojoView?
+
+ // TODO(beng): perform this in response to view destruction.
+ delegate_->OnDestroyed();
+}
+
+gfx::Size PlatformViewportAndroid::GetSize() {
+ return metrics_->size.To<gfx::Size>();
+}
+
+void PlatformViewportAndroid::SetBounds(const gfx::Rect& bounds) {
+ NOTIMPLEMENTED();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewportAndroid, private:
+
+void PlatformViewportAndroid::ReleaseWindow() {
+ ANativeWindow_release(window_);
+ window_ = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformViewport, public:
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(
+ new PlatformViewportAndroid(delegate)).Pass();
+}
+
+} // namespace native_viewport
diff --git a/mojo/services/native_viewport/platform_viewport_android.h b/mojo/services/native_viewport/platform_viewport_android.h
new file mode 100644
index 0000000..ebfe9495
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_android.h
@@ -0,0 +1,83 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_
+#define SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_
+
+#include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/native_viewport/platform_viewport.h"
+#include "ui/events/event_constants.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/sequential_id_generator.h"
+
+namespace gpu {
+class GLInProcessContext;
+}
+
+struct ANativeWindow;
+
+namespace native_viewport {
+
+class PlatformViewportAndroid : public PlatformViewport {
+ public:
+ static bool Register(JNIEnv* env);
+
+ explicit PlatformViewportAndroid(Delegate* delegate);
+ virtual ~PlatformViewportAndroid();
+
+ void Destroy(JNIEnv* env, jobject obj);
+ void SurfaceCreated(JNIEnv* env, jobject obj, jobject jsurface);
+ void SurfaceDestroyed(JNIEnv* env, jobject obj);
+ void SurfaceSetSize(JNIEnv* env,
+ jobject obj,
+ jint width,
+ jint height,
+ jfloat density);
+ bool TouchEvent(JNIEnv* env,
+ jobject obj,
+ jlong time_ms,
+ jint masked_action,
+ jint pointer_id,
+ jfloat x,
+ jfloat y,
+ jfloat pressure,
+ jfloat touch_major,
+ jfloat touch_minor,
+ jfloat orientation,
+ jfloat h_wheel,
+ jfloat v_wheel);
+ bool KeyEvent(JNIEnv* env,
+ jobject obj,
+ bool pressed,
+ jint key_code,
+ jint unicode_character);
+
+ private:
+ // Overridden from PlatformViewport:
+ virtual void Init(const gfx::Rect& bounds) override;
+ virtual void Show() override;
+ virtual void Hide() override;
+ virtual void Close() override;
+ virtual gfx::Size GetSize() override;
+ virtual void SetBounds(const gfx::Rect& bounds) override;
+
+ void ReleaseWindow();
+
+ Delegate* delegate_;
+ ANativeWindow* window_;
+ mojo::ViewportMetricsPtr metrics_;
+ ui::SequentialIDGenerator id_generator_;
+
+ base::WeakPtrFactory<PlatformViewportAndroid> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportAndroid);
+};
+
+} // namespace native_viewport
+
+#endif // SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_
diff --git a/mojo/services/native_viewport/platform_viewport_headless.cc b/mojo/services/native_viewport/platform_viewport_headless.cc
new file mode 100644
index 0000000..af3cec7
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_headless.cc
@@ -0,0 +1,49 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/platform_viewport_headless.h"
+
+#include "mojo/converters/geometry/geometry_type_converters.h"
+
+namespace native_viewport {
+
+PlatformViewportHeadless::PlatformViewportHeadless(Delegate* delegate)
+ : delegate_(delegate) {
+}
+
+PlatformViewportHeadless::~PlatformViewportHeadless() {
+}
+
+void PlatformViewportHeadless::Init(const gfx::Rect& bounds) {
+ metrics_ = mojo::ViewportMetrics::New();
+ metrics_->size = mojo::Size::From(bounds.size());
+}
+
+void PlatformViewportHeadless::Show() {
+}
+
+void PlatformViewportHeadless::Hide() {
+}
+
+void PlatformViewportHeadless::Close() {
+ delegate_->OnDestroyed();
+}
+
+gfx::Size PlatformViewportHeadless::GetSize() {
+ return metrics_->size.To<gfx::Size>();
+}
+
+void PlatformViewportHeadless::SetBounds(const gfx::Rect& bounds) {
+ metrics_->size = mojo::Size::From(bounds.size());
+ delegate_->OnMetricsChanged(metrics_->Clone());
+}
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewportHeadless::Create(
+ Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(
+ new PlatformViewportHeadless(delegate)).Pass();
+}
+
+} // namespace native_viewport
diff --git a/mojo/services/native_viewport/platform_viewport_headless.h b/mojo/services/native_viewport/platform_viewport_headless.h
new file mode 100644
index 0000000..fe7d148
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_headless.h
@@ -0,0 +1,39 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_HEADLESS_H_
+#define SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_HEADLESS_H_
+
+#include "base/macros.h"
+#include "mojo/services/native_viewport/platform_viewport.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace native_viewport {
+
+class PlatformViewportHeadless : public PlatformViewport {
+ public:
+ ~PlatformViewportHeadless() override;
+
+ static scoped_ptr<PlatformViewport> Create(Delegate* delegate);
+
+ private:
+ explicit PlatformViewportHeadless(Delegate* delegate);
+
+ // Overridden from PlatformViewport:
+ void Init(const gfx::Rect& bounds) override;
+ void Show() override;
+ void Hide() override;
+ void Close() override;
+ gfx::Size GetSize() override;
+ void SetBounds(const gfx::Rect& bounds) override;
+
+ Delegate* delegate_;
+ mojo::ViewportMetricsPtr metrics_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportHeadless);
+};
+
+} // namespace native_viewport
+
+#endif // SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_HEADLESS_H_
diff --git a/mojo/services/native_viewport/platform_viewport_stub.cc b/mojo/services/native_viewport/platform_viewport_stub.cc
new file mode 100644
index 0000000..80a7e94
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_stub.cc
@@ -0,0 +1,14 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/platform_viewport_headless.h"
+
+namespace mojo {
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return PlatformViewportHeadless::Create(delegate);
+}
+
+} // namespace mojo
diff --git a/mojo/services/native_viewport/platform_viewport_win.cc b/mojo/services/native_viewport/platform_viewport_win.cc
new file mode 100644
index 0000000..8dcd589
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_win.cc
@@ -0,0 +1,137 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/platform_viewport.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "ui/events/event.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/platform_window/platform_window_delegate.h"
+#include "ui/platform_window/win/win_window.h"
+
+namespace native_viewport {
+namespace {
+float ConvertUIWheelValueToMojoValue(int offset) {
+ // Mojo's event type takes a value between -1 and 1. Normalize by allowing
+ // up to 20 of ui's offset. This is a bit arbitrary.
+ return std::max(
+ -1.0f, std::min(1.0f, static_cast<float>(offset) /
+ (20 * static_cast<float>(
+ ui::MouseWheelEvent::kWheelDelta))));
+}
+} // namespace
+
+class PlatformViewportWin : public PlatformViewport,
+ public ui::PlatformWindowDelegate {
+ public:
+ explicit PlatformViewportWin(Delegate* delegate)
+ : delegate_(delegate) {
+ }
+
+ ~PlatformViewportWin() {
+ // Destroy the platform-window while |this| is still alive.
+ platform_window_.reset();
+ }
+
+ private:
+ // Overridden from PlatformViewport:
+ void Init(const gfx::Rect& bounds) override {
+ metrics_ = mojo::ViewportMetrics::New();
+ metrics_->size = mojo::Size::From(bounds.size());
+ platform_window_.reset(new ui::WinWindow(this, bounds));
+ }
+
+ void Show() override {
+ platform_window_->Show();
+ }
+
+ void Hide() override {
+ platform_window_->Hide();
+ }
+
+ void Close() override {
+ platform_window_->Close();
+ }
+
+ gfx::Size GetSize() override { return metrics_->size.To<gfx::Size>(); }
+
+ void SetBounds(const gfx::Rect& bounds) override {
+ platform_window_->SetBounds(bounds);
+ }
+
+ // ui::PlatformWindowDelegate:
+ void OnBoundsChanged(const gfx::Rect& new_bounds) override {
+ metrics_->size = mojo::Size::From(new_bounds.size());
+ delegate_->OnMetricsChanged(metrics_.Clone());
+ }
+
+ void OnDamageRect(const gfx::Rect& damaged_region) override {
+ }
+
+ void DispatchEvent(ui::Event* event) override {
+ // TODO(jam): this code is copied from the X11 version.
+ mojo::EventPtr mojo_event(mojo::Event::From(*event));
+ if (event->IsMouseWheelEvent()) {
+ // Mojo's event type has a different meaning for wheel events. Convert
+ // between the two.
+ ui::MouseWheelEvent* wheel_event =
+ static_cast<ui::MouseWheelEvent*>(event);
+ DCHECK(mojo_event->pointer_data);
+ mojo_event->pointer_data->horizontal_wheel =
+ ConvertUIWheelValueToMojoValue(wheel_event->x_offset());
+ mojo_event->pointer_data->horizontal_wheel =
+ ConvertUIWheelValueToMojoValue(wheel_event->y_offset());
+ }
+ delegate_->OnEvent(mojo_event.Pass());
+
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ case ui::ET_TOUCH_PRESSED:
+ platform_window_->SetCapture();
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ case ui::ET_TOUCH_RELEASED:
+ platform_window_->ReleaseCapture();
+ break;
+ default:
+ break;
+ }
+ }
+
+ void OnCloseRequest() override {
+ platform_window_->Close();
+ }
+
+ void OnClosed() override {
+ delegate_->OnDestroyed();
+ }
+
+ void OnWindowStateChanged(ui::PlatformWindowState state) override {
+ }
+
+ void OnLostCapture() override {
+ }
+
+ void OnAcceleratedWidgetAvailable(
+ gfx::AcceleratedWidget widget) override {
+ delegate_->OnAcceleratedWidgetAvailable(widget);
+ }
+
+ void OnActivationChanged(bool active) override {}
+
+ scoped_ptr<ui::PlatformWindow> platform_window_;
+ Delegate* delegate_;
+ mojo::ViewportMetricsPtr metrics_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportWin);
+};
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return scoped_ptr<PlatformViewport>(new PlatformViewportWin(delegate)).Pass();
+}
+
+} // namespace native_viewport
diff --git a/mojo/services/native_viewport/platform_viewport_x11.cc b/mojo/services/native_viewport/platform_viewport_x11.cc
new file mode 100644
index 0000000..a3cff26
--- /dev/null
+++ b/mojo/services/native_viewport/platform_viewport_x11.cc
@@ -0,0 +1,167 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/native_viewport/platform_viewport.h"
+
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "mojo/converters/input_events/mojo_extended_key_event_data.h"
+#include "ui/events/event.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/platform_window/platform_window.h"
+#include "ui/platform_window/platform_window_delegate.h"
+#include "ui/platform_window/x11/x11_window.h"
+
+namespace native_viewport {
+namespace {
+
+float ConvertUIWheelValueToMojoValue(int offset) {
+ // Mojo's event type takes a value between -1 and 1. Normalize by allowing
+ // up to 20 of ui's offset. This is a bit arbitrary.
+ return std::max(
+ -1.0f, std::min(1.0f, static_cast<float>(offset) /
+ (20 * static_cast<float>(
+ ui::MouseWheelEvent::kWheelDelta))));
+}
+} // namespace
+
+class PlatformViewportX11 : public PlatformViewport,
+ public ui::PlatformWindowDelegate {
+ public:
+ explicit PlatformViewportX11(Delegate* delegate) : delegate_(delegate) {
+ }
+
+ ~PlatformViewportX11() override {
+ // Destroy the platform-window while |this| is still alive.
+ platform_window_.reset();
+ }
+
+ private:
+ // Overridden from PlatformViewport:
+ void Init(const gfx::Rect& bounds) override {
+ CHECK(!event_source_);
+ CHECK(!platform_window_);
+
+ event_source_ = ui::PlatformEventSource::CreateDefault();
+
+ metrics_ = mojo::ViewportMetrics::New();
+ metrics_->size = mojo::Size::From(bounds.size());
+
+ platform_window_.reset(new ui::X11Window(this));
+ platform_window_->SetBounds(bounds);
+ }
+
+ void Show() override { platform_window_->Show(); }
+
+ void Hide() override { platform_window_->Hide(); }
+
+ void Close() override { platform_window_->Close(); }
+
+ gfx::Size GetSize() override { return metrics_->size.To<gfx::Size>(); }
+
+ void SetBounds(const gfx::Rect& bounds) override {
+ platform_window_->SetBounds(bounds);
+ }
+
+ // ui::PlatformWindowDelegate:
+ void OnBoundsChanged(const gfx::Rect& new_bounds) override {
+ metrics_->size = mojo::Size::From(new_bounds.size());
+ delegate_->OnMetricsChanged(metrics_.Clone());
+ }
+
+ void OnDamageRect(const gfx::Rect& damaged_region) override {}
+
+ void DispatchEvent(ui::Event* event) override {
+ mojo::EventPtr mojo_event(mojo::Event::From(*event));
+ if (event->IsMouseWheelEvent()) {
+ // Mojo's event type has a different meaning for wheel events. Convert
+ // between the two.
+ ui::MouseWheelEvent* wheel_event =
+ static_cast<ui::MouseWheelEvent*>(event);
+ DCHECK(mojo_event->pointer_data);
+ mojo_event->pointer_data->horizontal_wheel =
+ ConvertUIWheelValueToMojoValue(wheel_event->x_offset());
+ mojo_event->pointer_data->horizontal_wheel =
+ ConvertUIWheelValueToMojoValue(wheel_event->y_offset());
+ }
+ delegate_->OnEvent(mojo_event.Pass());
+
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ case ui::ET_TOUCH_PRESSED:
+ platform_window_->SetCapture();
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ case ui::ET_TOUCH_RELEASED:
+ platform_window_->ReleaseCapture();
+ break;
+ default:
+ break;
+ }
+
+ // We want to emulate the WM_CHAR generation behaviour of Windows.
+ //
+ // On Linux, we've previously inserted characters by having
+ // InputMethodAuraLinux take all key down events and send a character event
+ // to the TextInputClient. This causes a mismatch in code that has to be
+ // shared between Windows and Linux, including blink code. Now that we're
+ // trying to have one way of doing things, we need to standardize on and
+ // emulate Windows character events.
+ //
+ // This is equivalent to what we're doing in the current Linux port, but
+ // done once instead of done multiple times in different places.
+ if (event->type() == ui::ET_KEY_PRESSED) {
+ ui::KeyEvent* key_press_event = static_cast<ui::KeyEvent*>(event);
+ ui::KeyEvent char_event(key_press_event->GetCharacter(),
+ key_press_event->key_code(),
+ key_press_event->flags());
+
+ DCHECK_EQ(key_press_event->GetCharacter(), char_event.GetCharacter());
+ DCHECK_EQ(key_press_event->key_code(), char_event.key_code());
+ DCHECK_EQ(key_press_event->flags(), char_event.flags());
+
+ char_event.SetExtendedKeyEventData(
+ make_scoped_ptr(new mojo::MojoExtendedKeyEventData(
+ key_press_event->GetLocatedWindowsKeyboardCode(),
+ key_press_event->GetText(),
+ key_press_event->GetUnmodifiedText())));
+ char_event.set_platform_keycode(key_press_event->platform_keycode());
+
+ delegate_->OnEvent(mojo::Event::From(char_event));
+ }
+ }
+
+ void OnCloseRequest() override { platform_window_->Close(); }
+
+ void OnClosed() override { delegate_->OnDestroyed(); }
+
+ void OnWindowStateChanged(ui::PlatformWindowState state) override {}
+
+ void OnLostCapture() override {}
+
+ void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override {
+ delegate_->OnAcceleratedWidgetAvailable(widget);
+ }
+
+ void OnActivationChanged(bool active) override {}
+
+ scoped_ptr<ui::PlatformEventSource> event_source_;
+ scoped_ptr<ui::PlatformWindow> platform_window_;
+ Delegate* delegate_;
+ mojo::ViewportMetricsPtr metrics_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformViewportX11);
+};
+
+// static
+scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) {
+ return make_scoped_ptr(new PlatformViewportX11(delegate));
+}
+
+} // namespace native_viewport
diff --git a/mojo/services/surfaces/BUILD.gn b/mojo/services/surfaces/BUILD.gn
new file mode 100644
index 0000000..c1f8d2e
--- /dev/null
+++ b/mojo/services/surfaces/BUILD.gn
@@ -0,0 +1,47 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+
+mojo_native_application("surfaces") {
+ output_name = "surfaces_service"
+ sources = [
+ "context_provider_mojo.cc",
+ "context_provider_mojo.h",
+ "display_factory_impl.cc",
+ "display_factory_impl.h",
+ "display_impl.cc",
+ "display_impl.h",
+ "surfaces_impl.cc",
+ "surfaces_impl.h",
+ "surfaces_output_surface.cc",
+ "surfaces_output_surface.h",
+ "surfaces_scheduler.cc",
+ "surfaces_scheduler.h",
+ "surfaces_service_application.cc",
+ "surfaces_service_application.h",
+ ]
+
+ deps = [
+ "//base",
+ "//cc",
+ "//cc/surfaces",
+ "//cc/surfaces:surface_id",
+ "//gpu/command_buffer/client:gles2_interface",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/common:tracing_impl",
+ "//mojo/converters/geometry",
+ "//mojo/converters/surfaces",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/c/gles2",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/environment",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/gpu/public/interfaces",
+ "//third_party/mojo_services/src/surfaces/public/interfaces",
+ "//ui/gfx/geometry",
+ ]
+}
diff --git a/mojo/services/surfaces/DEPS b/mojo/services/surfaces/DEPS
new file mode 100644
index 0000000..3b182bf
--- /dev/null
+++ b/mojo/services/surfaces/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+cc",
+ "+gpu",
+ "+mojo/application",
+ "+mojo/converters",
+ "+third_party/mojo_services",
+]
diff --git a/mojo/services/surfaces/context_provider_mojo.cc b/mojo/services/surfaces/context_provider_mojo.cc
new file mode 100644
index 0000000..2874794
--- /dev/null
+++ b/mojo/services/surfaces/context_provider_mojo.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/context_provider_mojo.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/environment/environment.h"
+
+namespace mojo {
+
+ContextProviderMojo::ContextProviderMojo(
+ ScopedMessagePipeHandle command_buffer_handle)
+ : command_buffer_handle_(command_buffer_handle.Pass()),
+ context_(nullptr),
+ context_lost_(false) {
+}
+
+bool ContextProviderMojo::BindToCurrentThread() {
+ DCHECK(command_buffer_handle_.is_valid());
+ context_ = MojoGLES2CreateContext(command_buffer_handle_.release().value(),
+ &ContextLostThunk, this,
+ Environment::GetDefaultAsyncWaiter());
+ DCHECK(context_);
+ return !!context_;
+}
+
+gpu::gles2::GLES2Interface* ContextProviderMojo::ContextGL() {
+ if (!context_)
+ return nullptr;
+ return static_cast<gpu::gles2::GLES2Interface*>(
+ MojoGLES2GetGLES2Interface(context_));
+}
+
+gpu::ContextSupport* ContextProviderMojo::ContextSupport() {
+ if (!context_)
+ return nullptr;
+ return static_cast<gpu::ContextSupport*>(
+ MojoGLES2GetContextSupport(context_));
+}
+
+class GrContext* ContextProviderMojo::GrContext() {
+ return NULL;
+}
+
+cc::ContextProvider::Capabilities ContextProviderMojo::ContextCapabilities() {
+ return capabilities_;
+}
+
+void ContextProviderMojo::SetupLock() {
+}
+
+base::Lock* ContextProviderMojo::GetLock() {
+ return &context_lock_;
+}
+
+bool ContextProviderMojo::IsContextLost() {
+ return context_lost_;
+}
+bool ContextProviderMojo::DestroyedOnMainThread() {
+ return !context_;
+}
+
+void ContextProviderMojo::SetLostContextCallback(
+ const LostContextCallback& lost_context_callback) {
+ lost_context_callback_ = lost_context_callback;
+}
+
+ContextProviderMojo::~ContextProviderMojo() {
+ if (context_)
+ MojoGLES2DestroyContext(context_);
+}
+
+void ContextProviderMojo::ContextLost() {
+ context_lost_ = true;
+ if (!lost_context_callback_.is_null())
+ lost_context_callback_.Run();
+}
+
+} // namespace mojo
diff --git a/mojo/services/surfaces/context_provider_mojo.h b/mojo/services/surfaces/context_provider_mojo.h
new file mode 100644
index 0000000..48d12f2
--- /dev/null
+++ b/mojo/services/surfaces/context_provider_mojo.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 SERVICES_SURFACES_CONTEXT_PROVIDER_MOJO_H_
+#define SERVICES_SURFACES_CONTEXT_PROVIDER_MOJO_H_
+
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "cc/output/context_provider.h"
+#include "mojo/public/c/gles2/gles2.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+class ContextProviderMojo : public cc::ContextProvider {
+ public:
+ explicit ContextProviderMojo(ScopedMessagePipeHandle command_buffer_handle);
+
+ // cc::ContextProvider implementation.
+ bool BindToCurrentThread() override;
+ gpu::gles2::GLES2Interface* ContextGL() override;
+ gpu::ContextSupport* ContextSupport() override;
+ class GrContext* GrContext() override;
+ Capabilities ContextCapabilities() override;
+ bool IsContextLost() override;
+ void VerifyContexts() override {}
+ void DeleteCachedResources() override {}
+ bool DestroyedOnMainThread() override;
+ void SetLostContextCallback(
+ const LostContextCallback& lost_context_callback) override;
+ void SetMemoryPolicyChangedCallback(
+ const MemoryPolicyChangedCallback& memory_policy_changed_callback)
+ override {}
+ void SetupLock() override;
+ base::Lock* GetLock() override;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ContextProviderMojo>;
+ ~ContextProviderMojo() override;
+
+ private:
+ static void ContextLostThunk(void* closure) {
+ static_cast<ContextProviderMojo*>(closure)->ContextLost();
+ }
+ void ContextLost();
+
+ cc::ContextProvider::Capabilities capabilities_;
+ ScopedMessagePipeHandle command_buffer_handle_;
+ MojoGLES2Context context_;
+ bool context_lost_;
+ LostContextCallback lost_context_callback_;
+
+ base::Lock context_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextProviderMojo);
+};
+
+} // namespace mojo
+
+#endif // SERVICES_SURFACES_CONTEXT_PROVIDER_MOJO_H_
diff --git a/mojo/services/surfaces/display_factory_impl.cc b/mojo/services/surfaces/display_factory_impl.cc
new file mode 100644
index 0000000..11e9bf2
--- /dev/null
+++ b/mojo/services/surfaces/display_factory_impl.cc
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/display_factory_impl.h"
+
+#include "cc/surfaces/surface_id.h"
+
+namespace surfaces {
+
+DisplayFactoryImpl::DisplayFactoryImpl(
+ cc::SurfaceManager* manager,
+ uint32_t id_namespace,
+ SurfacesScheduler* scheduler,
+ mojo::InterfaceRequest<mojo::DisplayFactory> request)
+ : id_namespace_(id_namespace),
+ next_local_id_(1u),
+ scheduler_(scheduler),
+ manager_(manager),
+ binding_(this, request.Pass()) {
+}
+
+DisplayFactoryImpl::~DisplayFactoryImpl() {
+}
+
+void DisplayFactoryImpl::Create(
+ mojo::ContextProviderPtr context_provider,
+ mojo::ResourceReturnerPtr returner,
+ mojo::InterfaceRequest<mojo::Display> display_request) {
+ cc::SurfaceId cc_id(static_cast<uint64_t>(id_namespace_) << 32 |
+ next_local_id_++);
+ new DisplayImpl(manager_, cc_id, scheduler_, context_provider.Pass(),
+ returner.Pass(), display_request.Pass());
+}
+
+} // namespace surfaces
diff --git a/mojo/services/surfaces/display_factory_impl.h b/mojo/services/surfaces/display_factory_impl.h
new file mode 100644
index 0000000..7a21c43
--- /dev/null
+++ b/mojo/services/surfaces/display_factory_impl.h
@@ -0,0 +1,44 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_SURFACES_DISPLAY_FACTORY_IMPL_H_
+#define SERVICES_SURFACES_DISPLAY_FACTORY_IMPL_H_
+
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/services/surfaces/display_impl.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h"
+
+namespace cc {
+class SurfaceManager;
+}
+
+namespace surfaces {
+class SurfacesScheduler;
+
+class DisplayFactoryImpl : public mojo::DisplayFactory {
+ public:
+ DisplayFactoryImpl(cc::SurfaceManager* manager,
+ uint32_t id_namespace,
+ SurfacesScheduler* scheduler,
+ mojo::InterfaceRequest<mojo::DisplayFactory> request);
+ ~DisplayFactoryImpl() override;
+
+ private:
+ // mojo::DisplayFactory implementation.
+ void Create(mojo::ContextProviderPtr context_provider,
+ mojo::ResourceReturnerPtr returner,
+ mojo::InterfaceRequest<mojo::Display> display_request) override;
+
+ // We use one ID namespace for all DisplayImpls since the ID is used only by
+ // cc and not exposed through mojom.
+ uint32_t id_namespace_;
+ uint32_t next_local_id_;
+ SurfacesScheduler* scheduler_;
+ cc::SurfaceManager* manager_;
+ mojo::StrongBinding<mojo::DisplayFactory> binding_;
+};
+
+} // namespace surfaces
+
+#endif // SERVICES_SURFACES_DISPLAY_FACTORY_IMPL_H_
diff --git a/mojo/services/surfaces/display_impl.cc b/mojo/services/surfaces/display_impl.cc
new file mode 100644
index 0000000..3fff760
--- /dev/null
+++ b/mojo/services/surfaces/display_impl.cc
@@ -0,0 +1,137 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/display_impl.h"
+
+#include "cc/output/compositor_frame.h"
+#include "cc/surfaces/display.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/surfaces/surfaces_type_converters.h"
+#include "mojo/services/surfaces/context_provider_mojo.h"
+#include "mojo/services/surfaces/surfaces_output_surface.h"
+#include "mojo/services/surfaces/surfaces_scheduler.h"
+
+namespace surfaces {
+namespace {
+void CallCallback(const mojo::Closure& callback, cc::SurfaceDrawStatus status) {
+ callback.Run();
+}
+}
+
+DisplayImpl::DisplayImpl(cc::SurfaceManager* manager,
+ cc::SurfaceId cc_id,
+ SurfacesScheduler* scheduler,
+ mojo::ContextProviderPtr context_provider,
+ mojo::ResourceReturnerPtr returner,
+ mojo::InterfaceRequest<mojo::Display> display_request)
+ : manager_(manager),
+ factory_(manager, this),
+ cc_id_(cc_id),
+ scheduler_(scheduler),
+ context_provider_(context_provider.Pass()),
+ returner_(returner.Pass()),
+ viewport_param_binding_(this),
+ display_binding_(this, display_request.Pass()) {
+ mojo::ViewportParameterListenerPtr viewport_parameter_listener;
+ viewport_param_binding_.Bind(GetProxy(&viewport_parameter_listener));
+ context_provider_->Create(
+ viewport_parameter_listener.Pass(),
+ base::Bind(&DisplayImpl::OnContextCreated, base::Unretained(this)));
+}
+
+void DisplayImpl::OnContextCreated(mojo::CommandBufferPtr gles2_client) {
+ DCHECK(!display_);
+
+ cc::RendererSettings settings;
+ display_.reset(new cc::Display(this, manager_, nullptr, nullptr, settings));
+ scheduler_->AddDisplay(display_.get());
+ display_->Initialize(make_scoped_ptr(new mojo::DirectOutputSurface(
+ new mojo::ContextProviderMojo(gles2_client.PassMessagePipe()))));
+
+ factory_.Create(cc_id_);
+ display_->SetSurfaceId(cc_id_, 1.f);
+ if (pending_frame_)
+ Draw();
+}
+
+DisplayImpl::~DisplayImpl() {
+ if (display_) {
+ factory_.Destroy(cc_id_);
+ scheduler_->RemoveDisplay(display_.get());
+ }
+}
+
+void DisplayImpl::SubmitFrame(mojo::FramePtr frame,
+ const SubmitFrameCallback& callback) {
+ DCHECK(pending_callback_.is_null());
+ pending_frame_ = frame.Pass();
+ pending_callback_ = callback;
+ if (display_)
+ Draw();
+}
+
+void DisplayImpl::Draw() {
+ gfx::Size frame_size =
+ pending_frame_->passes[0]->output_rect.To<gfx::Rect>().size();
+ display_->Resize(frame_size);
+ factory_.SubmitFrame(cc_id_,
+ pending_frame_.To<scoped_ptr<cc::CompositorFrame>>(),
+ base::Bind(&CallCallback, pending_callback_));
+ scheduler_->SetNeedsDraw();
+ pending_callback_.reset();
+}
+
+void DisplayImpl::DisplayDamaged() {
+}
+
+void DisplayImpl::DidSwapBuffers() {
+}
+
+void DisplayImpl::DidSwapBuffersComplete() {
+}
+
+void DisplayImpl::CommitVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+}
+
+void DisplayImpl::OutputSurfaceLost() {
+ // If our OutputSurface is lost we can't draw until we get a new one. For now,
+ // destroy the display and create a new one when our ContextProvider provides
+ // a new one.
+ // TODO: This is more violent than necessary - we could simply remove this
+ // display from the scheduler's set and pass a new context in to the
+ // OutputSurface. It should be able to reinitialize properly.
+ scheduler_->RemoveDisplay(display_.get());
+ display_.reset();
+ factory_.Destroy(cc_id_);
+ viewport_param_binding_.Close();
+ mojo::ViewportParameterListenerPtr viewport_parameter_listener;
+ viewport_param_binding_.Bind(GetProxy(&viewport_parameter_listener));
+ context_provider_->Create(
+ viewport_parameter_listener.Pass(),
+ base::Bind(&DisplayImpl::OnContextCreated, base::Unretained(this)));
+}
+
+void DisplayImpl::SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) {
+}
+
+void DisplayImpl::OnVSyncParametersUpdated(int64_t timebase, int64_t interval) {
+ scheduler_->OnVSyncParametersUpdated(
+ base::TimeTicks::FromInternalValue(timebase),
+ base::TimeDelta::FromInternalValue(interval));
+}
+
+void DisplayImpl::ReturnResources(const cc::ReturnedResourceArray& resources) {
+ if (resources.empty())
+ return;
+ DCHECK(returner_);
+
+ mojo::Array<mojo::ReturnedResourcePtr> ret(resources.size());
+ for (size_t i = 0; i < resources.size(); ++i) {
+ ret[i] = mojo::ReturnedResource::From(resources[i]);
+ }
+ returner_->ReturnResources(ret.Pass());
+}
+
+} // namespace surfaces
diff --git a/mojo/services/surfaces/display_impl.h b/mojo/services/surfaces/display_impl.h
new file mode 100644
index 0000000..19d11e3
--- /dev/null
+++ b/mojo/services/surfaces/display_impl.h
@@ -0,0 +1,80 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_SURFACES_DISPLAY_IMPL_H_
+#define SERVICES_SURFACES_DISPLAY_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/surfaces/display_client.h"
+#include "cc/surfaces/surface_factory.h"
+#include "cc/surfaces/surface_factory_client.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h"
+
+namespace cc {
+class Display;
+class SurfaceFactory;
+}
+
+namespace surfaces {
+class SurfacesScheduler;
+
+class DisplayImpl : public mojo::Display,
+ public mojo::ViewportParameterListener,
+ public cc::DisplayClient,
+ public cc::SurfaceFactoryClient {
+ public:
+ DisplayImpl(cc::SurfaceManager* manager,
+ cc::SurfaceId cc_id,
+ SurfacesScheduler* scheduler,
+ mojo::ContextProviderPtr context_provider,
+ mojo::ResourceReturnerPtr returner,
+ mojo::InterfaceRequest<mojo::Display> display_request);
+ ~DisplayImpl() override;
+
+ private:
+ void OnContextCreated(mojo::CommandBufferPtr gles2_client);
+
+ // mojo::Display implementation:
+ void SubmitFrame(mojo::FramePtr frame,
+ const SubmitFrameCallback& callback) override;
+
+ // DisplayClient implementation.
+ void DisplayDamaged() override;
+ void DidSwapBuffers() override;
+ void DidSwapBuffersComplete() override;
+ void CommitVSyncParameters(base::TimeTicks timebase,
+ base::TimeDelta interval) override;
+ void OutputSurfaceLost() override;
+ void SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) override;
+
+ // ViewportParameterListener
+ void OnVSyncParametersUpdated(int64_t timebase, int64_t interval) override;
+
+ // SurfaceFactoryClient implementation.
+ void ReturnResources(const cc::ReturnedResourceArray& resources) override;
+
+ void Draw();
+
+ cc::SurfaceManager* manager_;
+ cc::SurfaceFactory factory_;
+ cc::SurfaceId cc_id_;
+ SurfacesScheduler* scheduler_;
+ mojo::ContextProviderPtr context_provider_;
+ mojo::ResourceReturnerPtr returner_;
+
+ mojo::FramePtr pending_frame_;
+ SubmitFrameCallback pending_callback_;
+
+ scoped_ptr<cc::Display> display_;
+
+ mojo::Binding<mojo::ViewportParameterListener> viewport_param_binding_;
+ mojo::StrongBinding<mojo::Display> display_binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayImpl);
+};
+
+} // namespace surfaces
+
+#endif // SERVICES_SURFACES_DISPLAY_IMPL_H_
diff --git a/mojo/services/surfaces/surfaces_impl.cc b/mojo/services/surfaces/surfaces_impl.cc
new file mode 100644
index 0000000..577b6c1
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_impl.cc
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/surfaces_impl.h"
+
+#include "base/trace_event/trace_event.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/resources/returned_resource.h"
+#include "cc/surfaces/surface_id_allocator.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/surfaces/surfaces_type_converters.h"
+#include "mojo/services/surfaces/surfaces_scheduler.h"
+
+using mojo::SurfaceIdPtr;
+
+namespace surfaces {
+
+namespace {
+void CallCallback(const mojo::Closure& callback, cc::SurfaceDrawStatus status) {
+ callback.Run();
+}
+}
+
+SurfacesImpl::SurfacesImpl(cc::SurfaceManager* manager,
+ uint32_t id_namespace,
+ SurfacesScheduler* scheduler,
+ mojo::InterfaceRequest<mojo::Surface> request)
+ : manager_(manager),
+ factory_(manager, this),
+ id_namespace_(id_namespace),
+ scheduler_(scheduler),
+ binding_(this, request.Pass()) {
+}
+
+SurfacesImpl::~SurfacesImpl() {
+ factory_.DestroyAll();
+}
+
+void SurfacesImpl::GetIdNamespace(
+ const Surface::GetIdNamespaceCallback& callback) {
+ callback.Run(id_namespace_);
+}
+
+void SurfacesImpl::SetResourceReturner(mojo::ResourceReturnerPtr returner) {
+ returner_ = returner.Pass();
+}
+
+void SurfacesImpl::CreateSurface(uint32_t local_id) {
+ factory_.Create(QualifyIdentifier(local_id));
+}
+
+void SurfacesImpl::SubmitFrame(uint32_t local_id,
+ mojo::FramePtr frame,
+ const mojo::Closure& callback) {
+ TRACE_EVENT0("mojo", "SurfacesImpl::SubmitFrame");
+ factory_.SubmitFrame(QualifyIdentifier(local_id),
+ frame.To<scoped_ptr<cc::CompositorFrame>>(),
+ base::Bind(&CallCallback, callback));
+ scheduler_->SetNeedsDraw();
+}
+
+void SurfacesImpl::DestroySurface(uint32_t local_id) {
+ factory_.Destroy(QualifyIdentifier(local_id));
+}
+
+void SurfacesImpl::ReturnResources(const cc::ReturnedResourceArray& resources) {
+ if (resources.empty() || !returner_)
+ return;
+ mojo::Array<mojo::ReturnedResourcePtr> ret(resources.size());
+ for (size_t i = 0; i < resources.size(); ++i) {
+ ret[i] = mojo::ReturnedResource::From(resources[i]);
+ }
+ returner_->ReturnResources(ret.Pass());
+}
+
+cc::SurfaceId SurfacesImpl::QualifyIdentifier(uint32_t local_id) {
+ return cc::SurfaceId(static_cast<uint64_t>(id_namespace_) << 32 | local_id);
+}
+
+} // namespace mojo
diff --git a/mojo/services/surfaces/surfaces_impl.h b/mojo/services/surfaces/surfaces_impl.h
new file mode 100644
index 0000000..10c6b7b
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_impl.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_SURFACES_SURFACES_IMPL_H_
+#define SERVICES_SURFACES_SURFACES_IMPL_H_
+
+#include "cc/surfaces/display_client.h"
+#include "cc/surfaces/surface_factory.h"
+#include "cc/surfaces/surface_factory_client.h"
+#include "mojo/common/weak_binding_set.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/viewport_parameter_listener.mojom.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h"
+
+namespace cc {
+class Display;
+}
+
+namespace mojo {
+class ApplicationManager;
+}
+
+namespace surfaces {
+class SurfacesScheduler;
+
+class SurfacesImpl : public mojo::Surface, public cc::SurfaceFactoryClient {
+ public:
+ SurfacesImpl(cc::SurfaceManager* manager,
+ uint32_t id_namespace,
+ SurfacesScheduler* scheduler,
+ mojo::InterfaceRequest<mojo::Surface> request);
+
+ ~SurfacesImpl() override;
+
+ // Surface implementation.
+ void GetIdNamespace(const Surface::GetIdNamespaceCallback& callback) override;
+ void SetResourceReturner(mojo::ResourceReturnerPtr returner) override;
+ void CreateSurface(uint32_t local_id) override;
+ void SubmitFrame(uint32_t local_id,
+ mojo::FramePtr frame,
+ const mojo::Closure& callback) override;
+ void DestroySurface(uint32_t local_id) override;
+
+ // SurfaceFactoryClient implementation.
+ void ReturnResources(const cc::ReturnedResourceArray& resources) override;
+
+ cc::SurfaceFactory* factory() { return &factory_; }
+
+ private:
+ cc::SurfaceId QualifyIdentifier(uint32_t local_id);
+
+ cc::SurfaceManager* manager_;
+ cc::SurfaceFactory factory_;
+ const uint32_t id_namespace_;
+ SurfacesScheduler* scheduler_;
+ mojo::ScopedMessagePipeHandle command_buffer_handle_;
+ mojo::ResourceReturnerPtr returner_;
+ mojo::StrongBinding<Surface> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesImpl);
+};
+
+} // namespace surfaces
+
+#endif // SERVICES_SURFACES_SURFACES_IMPL_H_
diff --git a/mojo/services/surfaces/surfaces_output_surface.cc b/mojo/services/surfaces/surfaces_output_surface.cc
new file mode 100644
index 0000000..891ef13
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_output_surface.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/surfaces_output_surface.h"
+
+#include "base/bind.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/output_surface_client.h"
+#include "gpu/command_buffer/client/context_support.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+
+namespace mojo {
+
+DirectOutputSurface::DirectOutputSurface(
+ const scoped_refptr<cc::ContextProvider>& context_provider)
+ : cc::OutputSurface(context_provider), weak_ptr_factory_(this) {
+}
+
+DirectOutputSurface::~DirectOutputSurface() {
+}
+
+void DirectOutputSurface::SwapBuffers(cc::CompositorFrame* frame) {
+ DCHECK(context_provider_.get());
+ DCHECK(frame->gl_frame_data);
+ if (frame->gl_frame_data->sub_buffer_rect ==
+ gfx::Rect(frame->gl_frame_data->size)) {
+ context_provider_->ContextSupport()->Swap();
+ } else {
+ context_provider_->ContextSupport()->PartialSwapBuffers(
+ frame->gl_frame_data->sub_buffer_rect);
+ }
+ uint32_t sync_point =
+ context_provider_->ContextGL()->InsertSyncPointCHROMIUM();
+ context_provider_->ContextSupport()->SignalSyncPoint(
+ sync_point, base::Bind(&OutputSurface::OnSwapBuffersComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+ client_->DidSwapBuffers();
+}
+
+} // namespace mojo
diff --git a/mojo/services/surfaces/surfaces_output_surface.h b/mojo/services/surfaces/surfaces_output_surface.h
new file mode 100644
index 0000000..a2e61d0
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_output_surface.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_SURFACES_SURFACES_OUTPUT_SURFACE_H_
+#define SERVICES_SURFACES_SURFACES_OUTPUT_SURFACE_H_
+
+#include "cc/output/output_surface.h"
+
+namespace mojo {
+
+// An OutputSurface implementation that directly draws and
+// swaps to an actual GL surface.
+class DirectOutputSurface : public cc::OutputSurface {
+ public:
+ explicit DirectOutputSurface(
+ const scoped_refptr<cc::ContextProvider>& context_provider);
+ ~DirectOutputSurface() override;
+
+ // cc::OutputSurface implementation
+ void SwapBuffers(cc::CompositorFrame* frame) override;
+
+ private:
+ base::WeakPtrFactory<DirectOutputSurface> weak_ptr_factory_;
+};
+
+} // namespace mojo
+
+#endif // SERVICES_SURFACES_SURFACES_OUTPUT_SURFACE_H_
diff --git a/mojo/services/surfaces/surfaces_scheduler.cc b/mojo/services/surfaces/surfaces_scheduler.cc
new file mode 100644
index 0000000..a86dd90
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_scheduler.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/surfaces_scheduler.h"
+
+#include "cc/surfaces/display.h"
+
+namespace surfaces {
+
+SurfacesScheduler::SurfacesScheduler() {
+ cc::SchedulerSettings settings;
+ scheduler_ = cc::Scheduler::Create(
+ this, settings, 0, base::MessageLoop::current()->task_runner(), nullptr);
+ scheduler_->SetCanStart();
+ scheduler_->SetVisible(true);
+ scheduler_->SetCanDraw(true);
+ scheduler_->SetNeedsCommit();
+}
+
+SurfacesScheduler::~SurfacesScheduler() {
+}
+
+void SurfacesScheduler::SetNeedsDraw() {
+ // Don't tell the scheduler we need to draw if we have no active displays
+ // which can happen if we haven't initialized displays yet or if all active
+ // displays have lost their context.
+ if (!displays_.empty())
+ scheduler_->SetNeedsRedraw();
+}
+
+void SurfacesScheduler::OnVSyncParametersUpdated(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ scheduler_->CommitVSyncParameters(timebase, interval);
+}
+
+void SurfacesScheduler::AddDisplay(cc::Display* display) {
+ DCHECK(displays_.find(display) == displays_.end());
+ displays_.insert(display);
+}
+
+void SurfacesScheduler::RemoveDisplay(cc::Display* display) {
+ auto it = displays_.find(display);
+ DCHECK(it != displays_.end());
+ displays_.erase(it);
+}
+
+void SurfacesScheduler::WillBeginImplFrame(const cc::BeginFrameArgs& args) {
+}
+
+void SurfacesScheduler::ScheduledActionSendBeginMainFrame() {
+ scheduler_->NotifyBeginMainFrameStarted();
+ scheduler_->NotifyReadyToCommit();
+}
+
+cc::DrawResult SurfacesScheduler::ScheduledActionDrawAndSwapIfPossible() {
+ base::TimeTicks start = base::TimeTicks::Now();
+ for (const auto& it : displays_) {
+ it->Draw();
+ }
+ base::TimeDelta duration = base::TimeTicks::Now() - start;
+
+ draw_estimate_ = (duration + draw_estimate_) / 2;
+ return cc::DRAW_SUCCESS;
+}
+
+cc::DrawResult SurfacesScheduler::ScheduledActionDrawAndSwapForced() {
+ NOTREACHED() << "ScheduledActionDrawAndSwapIfPossible always succeeds.";
+ return cc::DRAW_SUCCESS;
+}
+
+void SurfacesScheduler::ScheduledActionAnimate() {
+}
+
+void SurfacesScheduler::ScheduledActionCommit() {
+}
+
+void SurfacesScheduler::ScheduledActionActivateSyncTree() {
+}
+
+void SurfacesScheduler::ScheduledActionBeginOutputSurfaceCreation() {
+ scheduler_->DidCreateAndInitializeOutputSurface();
+}
+
+void SurfacesScheduler::ScheduledActionPrepareTiles() {
+}
+
+void SurfacesScheduler::DidAnticipatedDrawTimeChange(base::TimeTicks time) {
+}
+
+base::TimeDelta SurfacesScheduler::DrawDurationEstimate() {
+ return draw_estimate_;
+}
+
+base::TimeDelta SurfacesScheduler::BeginMainFrameToCommitDurationEstimate() {
+ return base::TimeDelta();
+}
+
+base::TimeDelta SurfacesScheduler::CommitToActivateDurationEstimate() {
+ return base::TimeDelta();
+}
+
+void SurfacesScheduler::DidBeginImplFrameDeadline() {
+}
+
+void SurfacesScheduler::SendBeginFramesToChildren(
+ const cc::BeginFrameArgs& args) {
+}
+
+void SurfacesScheduler::SendBeginMainFrameNotExpectedSoon() {
+}
+
+} // namespace mojo
diff --git a/mojo/services/surfaces/surfaces_scheduler.h b/mojo/services/surfaces/surfaces_scheduler.h
new file mode 100644
index 0000000..1c98af1
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_scheduler.h
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_SERVICES_SURFACES_SURFACES_SCHEDULER_H_
+#define MOJO_SERVICES_SURFACES_SURFACES_SCHEDULER_H_
+
+#include <set>
+
+#include "cc/scheduler/scheduler.h"
+
+namespace cc {
+class Display;
+}
+
+namespace surfaces {
+
+class SurfacesScheduler : public cc::SchedulerClient {
+ public:
+ SurfacesScheduler();
+ ~SurfacesScheduler() override;
+
+ void SetNeedsDraw();
+
+ void OnVSyncParametersUpdated(base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ void AddDisplay(cc::Display* display);
+ void RemoveDisplay(cc::Display* display);
+
+ private:
+ void WillBeginImplFrame(const cc::BeginFrameArgs& args) override;
+ void ScheduledActionSendBeginMainFrame() override;
+ cc::DrawResult ScheduledActionDrawAndSwapIfPossible() override;
+ cc::DrawResult ScheduledActionDrawAndSwapForced() override;
+ void ScheduledActionAnimate() override;
+ void ScheduledActionCommit() override;
+ void ScheduledActionActivateSyncTree() override;
+ void ScheduledActionBeginOutputSurfaceCreation() override;
+ void ScheduledActionPrepareTiles() override;
+ void DidAnticipatedDrawTimeChange(base::TimeTicks time) override;
+ base::TimeDelta DrawDurationEstimate() override;
+ base::TimeDelta BeginMainFrameToCommitDurationEstimate() override;
+ base::TimeDelta CommitToActivateDurationEstimate() override;
+ void DidBeginImplFrameDeadline() override;
+ void SendBeginFramesToChildren(const cc::BeginFrameArgs& args) override;
+ void SendBeginMainFrameNotExpectedSoon() override;
+
+ std::set<cc::Display*> displays_;
+ scoped_ptr<cc::Scheduler> scheduler_;
+ base::TimeDelta draw_estimate_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesScheduler);
+};
+
+} // namespace mojo
+
+#endif // MOJO_SERVICES_SURFACES_SURFACES_SCHEDULER_H_
diff --git a/mojo/services/surfaces/surfaces_service_application.cc b/mojo/services/surfaces/surfaces_service_application.cc
new file mode 100644
index 0000000..7d3e370
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_service_application.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/surfaces/surfaces_service_application.h"
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/services/surfaces/display_factory_impl.h"
+#include "mojo/services/surfaces/surfaces_impl.h"
+#include "mojo/services/surfaces/surfaces_scheduler.h"
+
+namespace surfaces {
+
+SurfacesServiceApplication::SurfacesServiceApplication()
+ : next_id_namespace_(1u) {
+}
+
+SurfacesServiceApplication::~SurfacesServiceApplication() {
+}
+
+void SurfacesServiceApplication::Initialize(mojo::ApplicationImpl* app) {
+ tracing_.Initialize(app);
+ scheduler_.reset(new SurfacesScheduler);
+}
+
+bool SurfacesServiceApplication::ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) {
+ connection->AddService<mojo::DisplayFactory>(this);
+ connection->AddService<mojo::Surface>(this);
+ return true;
+}
+
+void SurfacesServiceApplication::Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::DisplayFactory> request) {
+ new DisplayFactoryImpl(&manager_, next_id_namespace_++, scheduler_.get(),
+ request.Pass());
+}
+
+void SurfacesServiceApplication::Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::Surface> request) {
+ new SurfacesImpl(&manager_, next_id_namespace_++, scheduler_.get(),
+ request.Pass());
+}
+
+} // namespace surfaces
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(
+ new surfaces::SurfacesServiceApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/surfaces/surfaces_service_application.h b/mojo/services/surfaces/surfaces_service_application.h
new file mode 100644
index 0000000..cae7b35
--- /dev/null
+++ b/mojo/services/surfaces/surfaces_service_application.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 SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_
+#define SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_
+
+#include "base/macros.h"
+#include "cc/surfaces/surface_manager.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h"
+
+namespace mojo {
+class ApplicationConnection;
+}
+
+namespace surfaces {
+class SurfacesScheduler;
+
+class SurfacesServiceApplication
+ : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::DisplayFactory>,
+ public mojo::InterfaceFactory<mojo::Surface> {
+ public:
+ SurfacesServiceApplication();
+ ~SurfacesServiceApplication() override;
+
+ // ApplicationDelegate implementation.
+ void Initialize(mojo::ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ // InterfaceFactory<DisplayFactory> implementation.
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::DisplayFactory> request) override;
+
+ // InterfaceFactory<Surface> implementation.
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::Surface> request) override;
+
+ private:
+ cc::SurfaceManager manager_;
+ uint32_t next_id_namespace_;
+ scoped_ptr<SurfacesScheduler> scheduler_;
+ mojo::TracingImpl tracing_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfacesServiceApplication);
+};
+
+} // namespace surfaces
+
+#endif // SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_
diff --git a/mojo/services/test_service/BUILD.gn b/mojo/services/test_service/BUILD.gn
new file mode 100644
index 0000000..f602afc
--- /dev/null
+++ b/mojo/services/test_service/BUILD.gn
@@ -0,0 +1,61 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+
+mojom("bindings") {
+ sources = [
+ "test_request_tracker.mojom",
+ "test_service.mojom",
+ ]
+}
+
+mojo_native_application("test_app") {
+ sources = [
+ "test_service_application.cc",
+ "test_service_application.h",
+ "test_service_impl.cc",
+ "test_service_impl.h",
+ "test_time_service_impl.cc",
+ "test_time_service_impl.h",
+ "tracked_service.cc",
+ "tracked_service.h",
+ ]
+
+ deps = [
+ ":bindings",
+ "//base",
+ "//base:i18n",
+ "//third_party/mojo/src/mojo/public/cpp/application",
+ "//third_party/mojo/src/mojo/public/cpp/application:standalone",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo/src/mojo/public/cpp/utility",
+ ]
+}
+
+mojo_native_application("test_request_tracker_app") {
+ sources = [
+ "test_request_tracker_application.cc",
+ "test_request_tracker_application.h",
+ "test_request_tracker_impl.cc",
+ "test_request_tracker_impl.h",
+ "test_time_service_impl.cc",
+ "test_time_service_impl.h",
+ "tracked_service.cc",
+ "tracked_service.h",
+ ]
+
+ deps = [
+ ":bindings",
+ "//base",
+ "//base:i18n",
+ "//third_party/mojo/src/mojo/public/cpp/application",
+ "//third_party/mojo/src/mojo/public/cpp/application:standalone",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo/src/mojo/public/cpp/utility",
+ ]
+}
diff --git a/mojo/services/test_service/test_request_tracker.mojom b/mojo/services/test_service/test_request_tracker.mojom
new file mode 100644
index 0000000..91f8a2d
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker.mojom
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo.test;
+
+// Various counters that services can periodically send to a
+// TestTrackedRequestService for recording.
+struct ServiceStats {
+ uint64 num_new_requests;
+ double health;
+};
+
+// A per-service summary of all the ServiceStats the
+// TestTrackedRequestService has observed.
+struct ServiceReport {
+ string? service_name;
+ uint64 total_requests;
+ double mean_health;
+};
+
+// A simple interface to obtain a "report" from all services that have
+// opted to connect themselves to for request tracking.
+interface TestTrackedRequestService {
+ GetReport() => (array<ServiceReport?>? report);
+};
+
+// TestRequestTracker records ServiceStats for an individual service
+// connection for aggregation in a TestTrackedRequestService.
+interface TestRequestTracker {
+ SetNameAndReturnId(string service_name) => (uint64 id);
+ // Upload a ServiceStats for tracking.
+ RecordStats(uint64 client_id, ServiceStats? stats);
+};
diff --git a/mojo/services/test_service/test_request_tracker_application.cc b/mojo/services/test_service/test_request_tracker_application.cc
new file mode 100644
index 0000000..2fcf1f3
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_application.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/test_service/test_request_tracker_application.h"
+
+#include <assert.h>
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestRequestTrackerApplication::TestRequestTrackerApplication()
+ : app_impl_(nullptr) {
+}
+
+TestRequestTrackerApplication::~TestRequestTrackerApplication() {
+}
+
+void TestRequestTrackerApplication::Initialize(ApplicationImpl* app) {
+ app_impl_ = app;
+}
+
+bool TestRequestTrackerApplication::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ // Every instance of the service and recorder shares the context.
+ // Note, this app is single-threaded, so this is thread safe.
+ connection->AddService<TestTimeService>(this);
+ connection->AddService<TestRequestTracker>(this);
+ connection->AddService<TestTrackedRequestService>(this);
+ return true;
+}
+
+void TestRequestTrackerApplication::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) {
+ new TestTimeServiceImpl(app_impl_, request.Pass());
+}
+
+void TestRequestTrackerApplication::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<TestRequestTracker> request) {
+ new TestRequestTrackerImpl(request.Pass(), &context_);
+}
+
+void TestRequestTrackerApplication::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<TestTrackedRequestService> request) {
+ new TestTrackedRequestServiceImpl(request.Pass(), &context_);
+}
+
+} // namespace test
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(
+ new mojo::test::TestRequestTrackerApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/test_service/test_request_tracker_application.h b/mojo/services/test_service/test_request_tracker_application.h
new file mode 100644
index 0000000..761a845
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_application.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_
+#define SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_request_tracker_impl.h"
+
+namespace mojo {
+class ApplicationImpl;
+namespace test {
+class TestTimeService;
+
+// Embeds TestRequestTracker mojo services into an application.
+class TestRequestTrackerApplication
+ : public ApplicationDelegate,
+ public InterfaceFactory<TestTimeService>,
+ public InterfaceFactory<TestRequestTracker>,
+ public InterfaceFactory<TestTrackedRequestService> {
+ public:
+ TestRequestTrackerApplication();
+ ~TestRequestTrackerApplication() override;
+
+ void Initialize(ApplicationImpl* app) override;
+
+ // ApplicationDelegate methods:
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+
+ // InterfaceFactory<TestTimeService> methods:
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) override;
+
+ // InterfaceFactory<TestRequestTracker> methods:
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestRequestTracker> request) override;
+
+ // InterfaceFactory<TestTrackedRequestService> methods:
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTrackedRequestService> request) override;
+
+ private:
+ ApplicationImpl* app_impl_;
+ TrackingContext context_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestRequestTrackerApplication);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_
diff --git a/mojo/services/test_service/test_request_tracker_impl.cc b/mojo/services/test_service/test_request_tracker_impl.cc
new file mode 100644
index 0000000..09fa4d5
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_impl.cc
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/test_service/test_request_tracker_impl.h"
+
+namespace mojo {
+namespace test {
+
+TrackingContext::TrackingContext() : next_id(1) {
+}
+
+TrackingContext::~TrackingContext() {
+}
+
+TestRequestTrackerImpl::TestRequestTrackerImpl(
+ InterfaceRequest<TestRequestTracker> request,
+ TrackingContext* context)
+ : context_(context), binding_(this, request.Pass()), weak_factory_(this) {
+}
+
+TestRequestTrackerImpl::~TestRequestTrackerImpl() {
+}
+
+void TestRequestTrackerImpl::RecordStats(
+ uint64_t client_id,
+ ServiceStatsPtr stats) {
+ assert(context_->ids_to_names.find(client_id) !=
+ context_->ids_to_names.end());
+ context_->records[client_id].push_back(*stats);
+}
+
+void TestRequestTrackerImpl::SetNameAndReturnId(
+ const String& service_name,
+ const Callback<void(uint64_t id)>& callback) {
+ uint64_t id = context_->next_id++;
+ callback.Run(id);
+ DCHECK(context_->ids_to_names.find(id) == context_->ids_to_names.end());
+ context_->ids_to_names[id] = service_name;
+}
+
+TestTrackedRequestServiceImpl::TestTrackedRequestServiceImpl(
+ InterfaceRequest<TestTrackedRequestService> request,
+ TrackingContext* context)
+ : context_(context), binding_(this, request.Pass()) {
+}
+
+TestTrackedRequestServiceImpl::~TestTrackedRequestServiceImpl() {
+}
+
+void TestTrackedRequestServiceImpl::GetReport(
+ const mojo::Callback<void(mojo::Array<ServiceReportPtr>)>& callback) {
+ mojo::Array<ServiceReportPtr> reports;
+ for (AllRecordsMap::const_iterator it1 = context_->records.begin();
+ it1 != context_->records.end(); ++it1) {
+ ServiceReportPtr report(ServiceReport::New());
+ report->service_name = context_->ids_to_names[it1->first];
+ double mean_health_numerator = 0;
+ size_t num_samples = it1->second.size();
+ if (num_samples == 0)
+ continue;
+
+ for (std::vector<ServiceStats>::const_iterator it2 = it1->second.begin();
+ it2 != it1->second.end(); ++it2) {
+ report->total_requests += it2->num_new_requests;
+ mean_health_numerator += it2->health;
+ }
+ report->mean_health = mean_health_numerator / num_samples;
+ reports.push_back(report.Pass());
+ }
+ callback.Run(reports.Pass());
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_request_tracker_impl.h b/mojo/services/test_service/test_request_tracker_impl.h
new file mode 100644
index 0000000..0ed220a
--- /dev/null
+++ b/mojo/services/test_service/test_request_tracker_impl.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_
+#define SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_
+
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+
+namespace mojo {
+class ApplicationConnection;
+namespace test {
+
+typedef std::map<uint64_t, std::vector<ServiceStats> > AllRecordsMap;
+
+// Shared state between all instances of TestRequestTrackerImpl
+// and the master TrackedRequestService.
+struct TrackingContext {
+ TrackingContext();
+ ~TrackingContext();
+ AllRecordsMap records;
+ std::map<uint64_t, std::string> ids_to_names;
+ uint64_t next_id;
+};
+
+class TestRequestTrackerImpl : public TestRequestTracker {
+ public:
+ TestRequestTrackerImpl(InterfaceRequest<TestRequestTracker> request,
+ TrackingContext* context);
+ ~TestRequestTrackerImpl() override;
+
+ // TestRequestTracker.
+ void SetNameAndReturnId(const String& service_name,
+ const Callback<void(uint64_t id)>& callback) override;
+ void RecordStats(uint64_t client_id, ServiceStatsPtr stats) override;
+
+ private:
+ void UploaderNameCallback(uint64_t id, const mojo::String& name);
+ TrackingContext* context_;
+ StrongBinding<TestRequestTracker> binding_;
+ base::WeakPtrFactory<TestRequestTrackerImpl> weak_factory_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestRequestTrackerImpl);
+};
+
+class TestTrackedRequestServiceImpl : public TestTrackedRequestService {
+ public:
+ TestTrackedRequestServiceImpl(
+ InterfaceRequest<TestTrackedRequestService> request,
+ TrackingContext* context);
+ ~TestTrackedRequestServiceImpl() override;
+
+ // |TestTrackedRequestService| implementation.
+ void GetReport(const mojo::Callback<void(mojo::Array<ServiceReportPtr>)>&
+ callback) override;
+
+ private:
+ TrackingContext* context_;
+ StrongBinding<TestTrackedRequestService> binding_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestTrackedRequestServiceImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_
diff --git a/mojo/services/test_service/test_service.mojom b/mojo/services/test_service/test_service.mojom
new file mode 100644
index 0000000..5144add
--- /dev/null
+++ b/mojo/services/test_service/test_service.mojom
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo.test;
+
+interface TestService {
+ Ping() => ();
+ // Connect to a TestTimeService at |app_url| and ferry the data back
+ // in |response|.
+ ConnectToAppAndGetTime(string? app_url) => (int64 time_usec);
+ StartTrackingRequests() => ();
+};
+
+interface TestTimeService {
+ // Provides a constant time value.
+ GetPartyTime() => (int64 time_usec);
+ StartTrackingRequests() => ();
+};
diff --git a/mojo/services/test_service/test_service_application.cc b/mojo/services/test_service/test_service_application.cc
new file mode 100644
index 0000000..70a2824
--- /dev/null
+++ b/mojo/services/test_service/test_service_application.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/test_service/test_service_application.h"
+
+#include <assert.h>
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/utility/run_loop.h"
+#include "mojo/services/test_service/test_service_impl.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+
+namespace mojo {
+namespace test {
+
+TestServiceApplication::TestServiceApplication()
+ : ref_count_(0), app_impl_(nullptr) {
+}
+
+TestServiceApplication::~TestServiceApplication() {
+}
+
+void TestServiceApplication::Initialize(ApplicationImpl* app) {
+ app_impl_ = app;
+}
+
+bool TestServiceApplication::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService<TestService>(this);
+ connection->AddService<TestTimeService>(this);
+ return true;
+}
+
+void TestServiceApplication::Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) {
+ new TestServiceImpl(app_impl_, this, request.Pass());
+ AddRef();
+}
+
+void TestServiceApplication::Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) {
+ new TestTimeServiceImpl(app_impl_, request.Pass());
+}
+
+void TestServiceApplication::AddRef() {
+ assert(ref_count_ >= 0);
+ ref_count_++;
+}
+
+void TestServiceApplication::ReleaseRef() {
+ assert(ref_count_ > 0);
+ ref_count_--;
+ if (ref_count_ <= 0)
+ RunLoop::current()->Quit();
+}
+
+} // namespace test
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::test::TestServiceApplication);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/test_service/test_service_application.h b/mojo/services/test_service/test_service_application.h
new file mode 100644
index 0000000..6b36f07
--- /dev/null
+++ b/mojo/services/test_service/test_service_application.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_
+#define SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+class ApplicationConnection;
+
+namespace test {
+class TestService;
+class TestTimeService;
+
+class TestServiceApplication : public ApplicationDelegate,
+ public InterfaceFactory<TestService>,
+ public InterfaceFactory<TestTimeService> {
+ public:
+ TestServiceApplication();
+ ~TestServiceApplication() override;
+
+ void Initialize(ApplicationImpl* app) override;
+
+ // ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+
+ // InterfaceFactory<TestService> implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) override;
+
+ // InterfaceFactory<TestTimeService> implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestTimeService> request) override;
+
+ void AddRef();
+ void ReleaseRef();
+
+ private:
+ int ref_count_;
+ ApplicationImpl* app_impl_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceApplication);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_
diff --git a/mojo/services/test_service/test_service_impl.cc b/mojo/services/test_service/test_service_impl.cc
new file mode 100644
index 0000000..de913eb
--- /dev/null
+++ b/mojo/services/test_service/test_service_impl.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/test_service/test_service_impl.h"
+
+#include "base/bind.h"
+#include "base/i18n/time_formatting.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/test_service/test_service_application.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+#include "mojo/services/test_service/tracked_service.h"
+
+namespace mojo {
+namespace test {
+
+TestServiceImpl::TestServiceImpl(ApplicationImpl* app_impl,
+ TestServiceApplication* application,
+ InterfaceRequest<TestService> request)
+ : application_(application),
+ app_impl_(app_impl),
+ binding_(this, request.Pass()) {
+ binding_.set_error_handler(this);
+}
+
+TestServiceImpl::~TestServiceImpl() {
+}
+
+void TestServiceImpl::OnConnectionError() {
+ application_->ReleaseRef();
+}
+
+void TestServiceImpl::Ping(const mojo::Callback<void()>& callback) {
+ if (tracking_)
+ tracking_->RecordNewRequest();
+ callback.Run();
+}
+
+void SendTimeResponse(
+ const mojo::Callback<void(int64_t)>& requestor_callback,
+ int64_t time_usec) {
+ requestor_callback.Run(time_usec);
+}
+
+void TestServiceImpl::ConnectToAppAndGetTime(
+ const mojo::String& app_url,
+ const mojo::Callback<void(int64_t)>& callback) {
+ app_impl_->ConnectToService(app_url, &time_service_);
+ if (tracking_) {
+ tracking_->RecordNewRequest();
+ time_service_->StartTrackingRequests(mojo::Callback<void()>());
+ }
+ time_service_->GetPartyTime(base::Bind(&SendTimeResponse, callback));
+}
+
+void TestServiceImpl::StartTrackingRequests(
+ const mojo::Callback<void()>& callback) {
+ TestRequestTrackerPtr tracker;
+ app_impl_->ConnectToService("mojo:test_request_tracker_app", &tracker);
+ tracking_.reset(new TrackedService(tracker.Pass(), Name_, callback));
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_service_impl.h b/mojo/services/test_service/test_service_impl.h
new file mode 100644
index 0000000..475a95b
--- /dev/null
+++ b/mojo/services/test_service/test_service_impl.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_
+#define SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_service.mojom.h"
+
+namespace mojo {
+class ApplicationImpl;
+namespace test {
+
+class TrackedService;
+class TestServiceApplication;
+
+class TestServiceImpl : public TestService, ErrorHandler {
+ public:
+ TestServiceImpl(ApplicationImpl* app_impl,
+ TestServiceApplication* application,
+ InterfaceRequest<TestService> request);
+ ~TestServiceImpl() override;
+
+ // |TestService| methods:
+ void Ping(const mojo::Callback<void()>& callback) override;
+ void ConnectToAppAndGetTime(
+ const mojo::String& app_url,
+ const mojo::Callback<void(int64_t)>& callback) override;
+ void StartTrackingRequests(const mojo::Callback<void()>& callback) override;
+
+ // ErrorHandler methods:
+ void OnConnectionError() override;
+
+ private:
+ TestServiceApplication* const application_;
+ ApplicationImpl* const app_impl_;
+ TestTimeServicePtr time_service_;
+ scoped_ptr<TrackedService> tracking_;
+ StrongBinding<TestService> binding_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_
diff --git a/mojo/services/test_service/test_time_service_impl.cc b/mojo/services/test_service/test_time_service_impl.cc
new file mode 100644
index 0000000..d11d346
--- /dev/null
+++ b/mojo/services/test_service/test_time_service_impl.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/time/time.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+#include "mojo/services/test_service/test_time_service_impl.h"
+#include "mojo/services/test_service/tracked_service.h"
+
+namespace mojo {
+namespace test {
+
+TestTimeServiceImpl::TestTimeServiceImpl(
+ ApplicationImpl* app_impl,
+ InterfaceRequest<TestTimeService> request)
+ : app_impl_(app_impl), binding_(this, request.Pass()) {
+}
+
+TestTimeServiceImpl::~TestTimeServiceImpl() {
+}
+
+void TestTimeServiceImpl::StartTrackingRequests(
+ const mojo::Callback<void()>& callback) {
+ TestRequestTrackerPtr tracker;
+ app_impl_->ConnectToService("mojo:test_request_tracker_app", &tracker);
+ tracking_.reset(new TrackedService(tracker.Pass(), Name_, callback));
+}
+
+void TestTimeServiceImpl::GetPartyTime(
+ const mojo::Callback<void(int64_t)>& callback) {
+ if (tracking_)
+ tracking_->RecordNewRequest();
+ base::Time frozen_time(base::Time::UnixEpoch()
+ + base::TimeDelta::FromDays(10957)
+ + base::TimeDelta::FromHours(7)
+ + base::TimeDelta::FromMinutes(59));
+ int64 time(frozen_time.ToInternalValue());
+ callback.Run(time);
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/test_time_service_impl.h b/mojo/services/test_service/test_time_service_impl.h
new file mode 100644
index 0000000..242f66c
--- /dev/null
+++ b/mojo/services/test_service/test_time_service_impl.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 SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_
+#define SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_service.mojom.h"
+
+namespace mojo {
+
+class ApplicationConnection;
+
+namespace test {
+
+class TrackedService;
+
+class TestTimeServiceImpl : public TestTimeService {
+ public:
+ TestTimeServiceImpl(ApplicationImpl* app_impl,
+ InterfaceRequest<TestTimeService> request);
+ ~TestTimeServiceImpl() override;
+
+ // |TestTimeService| methods:
+ void GetPartyTime(
+ const mojo::Callback<void(int64_t time_usec)>& callback) override;
+ void StartTrackingRequests(const mojo::Callback<void()>& callback) override;
+
+ private:
+ ApplicationImpl* app_impl_;
+ scoped_ptr<TrackedService> tracking_;
+ StrongBinding<TestTimeService> binding_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestTimeServiceImpl);
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_
diff --git a/mojo/services/test_service/tracked_service.cc b/mojo/services/test_service/tracked_service.cc
new file mode 100644
index 0000000..a2fedfc
--- /dev/null
+++ b/mojo/services/test_service/tracked_service.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/test_service/tracked_service.h"
+
+#include "base/bind.h"
+
+namespace mojo {
+namespace test {
+
+TrackedService::TrackedService(TestRequestTrackerPtr tracker,
+ const std::string& service_name,
+ const mojo::Callback<void()>& callback)
+ : id_(0u),
+ requests_since_upload_(0u),
+ service_name_(service_name),
+ tracker_(tracker.Pass()),
+ tracking_connected_callback_(callback) {
+ tracker_->SetNameAndReturnId(
+ service_name, base::Bind(&TrackedService::SetId, base::Unretained(this)));
+}
+
+TrackedService::~TrackedService() {
+}
+
+void TrackedService::RecordNewRequest() {
+ requests_since_upload_++;
+ if (id_ == 0u)
+ return;
+ SendStats();
+}
+
+void TrackedService::SendStats() {
+ ServiceStatsPtr stats(ServiceStats::New());
+ stats->num_new_requests = requests_since_upload_;
+ stats->health = 0.7;
+ tracker_->RecordStats(id_, stats.Pass());
+ requests_since_upload_ = 0u;
+}
+
+void TrackedService::SetId(uint64_t id) {
+ assert(id != 0u);
+ assert(id_ == 0u);
+ id_ = id;
+ tracking_connected_callback_.Run();
+ if (requests_since_upload_ == 0u)
+ return;
+ SendStats();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/services/test_service/tracked_service.h b/mojo/services/test_service/tracked_service.h
new file mode 100644
index 0000000..62ec14a
--- /dev/null
+++ b/mojo/services/test_service/tracked_service.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 SERVICES_TEST_SERVICE_TRACKED_SERVICE_H_
+#define SERVICES_TEST_SERVICE_TRACKED_SERVICE_H_
+
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+
+namespace mojo {
+namespace test {
+
+class TrackedService {
+ public:
+ TrackedService(TestRequestTrackerPtr tracker,
+ const std::string& service_name,
+ const mojo::Callback<void()>& tracking_connected_callback);
+ ~TrackedService();
+
+ // Call whenever an event happens that you want to be recorded.
+ void RecordNewRequest();
+
+ private:
+ void SetId(uint64_t id);
+ void SendStats();
+
+ uint64_t id_;
+ uint64_t requests_since_upload_;
+ const std::string service_name_;
+ TestRequestTrackerPtr tracker_;
+ mojo::Callback<void()> tracking_connected_callback_;
+};
+
+} // namespace test
+} // namespace mojo
+
+#endif // SERVICES_TEST_SERVICE_TRACKED_SERVICE_H_
diff --git a/mojo/services/tracing/BUILD.gn b/mojo/services/tracing/BUILD.gn
new file mode 100644
index 0000000..0555476
--- /dev/null
+++ b/mojo/services/tracing/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+
+mojo_native_application("tracing") {
+ sources = [
+ "main.cc",
+ "trace_data_sink.cc",
+ "trace_data_sink.h",
+ ]
+
+ deps = [
+ ":bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/public/cpp/application",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+}
+
+mojom("bindings") {
+ sources = [
+ "tracing.mojom",
+ ]
+}
diff --git a/mojo/services/tracing/DEPS b/mojo/services/tracing/DEPS
new file mode 100644
index 0000000..bfc9a9e
--- /dev/null
+++ b/mojo/services/tracing/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+mojo/application",
+]
diff --git a/mojo/services/tracing/main.cc b/mojo/services/tracing/main.cc
new file mode 100644
index 0000000..6379a0a
--- /dev/null
+++ b/mojo/services/tracing/main.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/weak_binding_set.h"
+#include "mojo/common/weak_interface_ptr_set.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/services/tracing/trace_data_sink.h"
+#include "mojo/services/tracing/tracing.mojom.h"
+
+namespace tracing {
+
+namespace {
+
+class CollectorImpl : public TraceDataCollector {
+ public:
+ CollectorImpl(mojo::InterfaceRequest<TraceDataCollector> request,
+ TraceDataSink* sink)
+ : sink_(sink), binding_(this, request.Pass()) {}
+
+ ~CollectorImpl() override {}
+
+ // tracing::TraceDataCollector implementation.
+ void DataCollected(const mojo::String& json) override {
+ sink_->AddChunk(json.To<std::string>());
+ }
+
+ private:
+ TraceDataSink* sink_;
+ mojo::Binding<TraceDataCollector> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(CollectorImpl);
+};
+
+} // namespace
+
+class TracingApp : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<TraceCoordinator>,
+ public TraceCoordinator {
+ public:
+ TracingApp() {}
+ ~TracingApp() override {}
+
+ private:
+ // mojo::ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService<TraceCoordinator>(this);
+
+ // If someone connects to us they may want to use the TraceCoordinator
+ // interface and/or they may want to expose themselves to be traced. Attempt
+ // to connect to the TraceController interface to see if the application
+ // connecting to us wants to be traced. They can refuse the connection or
+ // close the pipe if not.
+ TraceControllerPtr controller_ptr;
+ connection->ConnectToService(&controller_ptr);
+ controller_ptrs_.AddInterfacePtr(controller_ptr.Pass());
+ return true;
+ }
+
+ // mojo::InterfaceFactory<TraceCoordinator> implementation.
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<TraceCoordinator> request) override {
+ coordinator_bindings_.AddBinding(this, request.Pass());
+ }
+
+ // tracing::TraceCoordinator implementation.
+ void Start(mojo::ScopedDataPipeProducerHandle stream,
+ const mojo::String& categories) override {
+ sink_.reset(new TraceDataSink(stream.Pass()));
+ controller_ptrs_.ForAllPtrs(
+ [categories, this](TraceController* controller) {
+ TraceDataCollectorPtr ptr;
+ collector_impls_.push_back(
+ new CollectorImpl(GetProxy(&ptr), sink_.get()));
+ controller->StartTracing(categories, ptr.Pass());
+ });
+ }
+ void StopAndFlush() override {
+ controller_ptrs_.ForAllPtrs(
+ [](TraceController* controller) { controller->StopTracing(); });
+
+ // TODO: We really should keep track of how many connections we have here
+ // and flush + reset the sink after we receive a EndTracing or a detect a
+ // pipe closure on all pipes.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TracingApp::AllDataCollected, base::Unretained(this)),
+ base::TimeDelta::FromSeconds(1));
+ }
+
+ void AllDataCollected() {
+ collector_impls_.clear();
+ sink_->Flush();
+ }
+
+ scoped_ptr<TraceDataSink> sink_;
+ ScopedVector<CollectorImpl> collector_impls_;
+ mojo::WeakInterfacePtrSet<TraceController> controller_ptrs_;
+ mojo::WeakBindingSet<TraceCoordinator> coordinator_bindings_;
+
+ DISALLOW_COPY_AND_ASSIGN(TracingApp);
+};
+
+} // namespace tracing
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new tracing::TracingApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/tracing/trace_data_sink.cc b/mojo/services/tracing/trace_data_sink.cc
new file mode 100644
index 0000000..151a9d3
--- /dev/null
+++ b/mojo/services/tracing/trace_data_sink.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/tracing/trace_data_sink.h"
+
+#include "base/logging.h"
+#include "mojo/common/data_pipe_utils.h"
+
+using mojo::common::BlockingCopyFromString;
+
+namespace tracing {
+namespace {
+
+const char kStart[] = "{\"traceEvents\":[";
+const char kEnd[] = "]}";
+
+} // namespace
+
+TraceDataSink::TraceDataSink(mojo::ScopedDataPipeProducerHandle pipe)
+ : pipe_(pipe.Pass()), empty_(true) {
+ BlockingCopyFromString(kStart, pipe_);
+}
+
+TraceDataSink::~TraceDataSink() {
+ if (pipe_.is_valid())
+ Flush();
+ DCHECK(!pipe_.is_valid());
+}
+
+void TraceDataSink::AddChunk(const std::string& json) {
+ if (!empty_)
+ BlockingCopyFromString(",", pipe_);
+ empty_ = false;
+ BlockingCopyFromString(json, pipe_);
+}
+
+void TraceDataSink::Flush() {
+ BlockingCopyFromString(kEnd, pipe_);
+ pipe_.reset();
+}
+
+} // namespace tracing
diff --git a/mojo/services/tracing/trace_data_sink.h b/mojo/services/tracing/trace_data_sink.h
new file mode 100644
index 0000000..c5d3f14
--- /dev/null
+++ b/mojo/services/tracing/trace_data_sink.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TRACING_TRACE_DATA_SINK_H_
+#define SERVICES_TRACING_TRACE_DATA_SINK_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+
+namespace tracing {
+
+class TraceDataSink {
+ public:
+ explicit TraceDataSink(mojo::ScopedDataPipeProducerHandle pipe);
+ ~TraceDataSink();
+
+ void AddChunk(const std::string& json);
+ void Flush();
+
+ private:
+ mojo::ScopedDataPipeProducerHandle pipe_;
+ bool empty_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceDataSink);
+};
+
+} // namespace tracing
+
+#endif // SERVICES_TRACING_TRACE_DATA_SINK_H_
diff --git a/mojo/services/tracing/tracing.mojom b/mojo/services/tracing/tracing.mojom
new file mode 100644
index 0000000..fa8be1f
--- /dev/null
+++ b/mojo/services/tracing/tracing.mojom
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module tracing;
+
+// To participate in the tracing ecosystem, implement the TraceController
+// interface and connect to the tracing app. Then, when the controller's Start()
+// function is called collect tracing data and pass it back via the provided
+// TraceDataCollector interface up until Stop() is called.
+
+interface TraceController {
+ StartTracing(string categories, TraceDataCollector collector);
+ StopTracing();
+};
+
+interface TraceDataCollector {
+ DataCollected(string json);
+};
+
+interface TraceCoordinator {
+ // Request tracing data from all connected TraceControllers to stream to
+ // |stream|.
+ Start(handle<data_pipe_producer> stream, string categories);
+
+ // Stop tracing and flush results to file.
+ StopAndFlush();
+};
diff --git a/mojo/services/view_manager/BUILD.gn b/mojo/services/view_manager/BUILD.gn
new file mode 100644
index 0000000..a96bcc6
--- /dev/null
+++ b/mojo/services/view_manager/BUILD.gn
@@ -0,0 +1,179 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ui.gni")
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("//testing/test.gni")
+
+mojo_native_application("view_manager") {
+ sources = [
+ "main.cc",
+ "view_manager_app.cc",
+ "view_manager_app.h",
+ ]
+
+ deps = [
+ ":view_manager_lib",
+ "//base",
+ "//mojo/application",
+ "//mojo/common:tracing_impl",
+ "//mojo/environment:chromium",
+ "//mojo/converters/geometry",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo_services/src/view_manager/public/interfaces",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ ]
+}
+
+source_set("view_manager_lib") {
+ sources = [
+ "access_policy.h",
+ "access_policy_delegate.h",
+ "animation_runner.cc",
+ "animation_runner.h",
+ "animation_runner_observer.h",
+ "client_connection.cc",
+ "client_connection.h",
+ "connection_manager.cc",
+ "connection_manager.h",
+ "connection_manager_delegate.h",
+ "default_access_policy.cc",
+ "default_access_policy.h",
+ "display_manager.cc",
+ "display_manager.h",
+ "scheduled_animation_group.cc",
+ "scheduled_animation_group.h",
+ "server_view.cc",
+ "server_view.h",
+ "server_view_delegate.h",
+ "view_coordinate_conversions.cc",
+ "view_coordinate_conversions.h",
+ "view_manager_service_impl.cc",
+ "view_manager_service_impl.h",
+ "window_manager_access_policy.cc",
+ "window_manager_access_policy.h",
+ ]
+
+ public_deps = [
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ ]
+
+ deps = [
+ "//base",
+ "//cc/surfaces",
+ "//cc/surfaces:surface_id",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/converters/geometry",
+ "//mojo/converters/input_events",
+ "//mojo/converters/surfaces",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:callback",
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/input_events/public/interfaces",
+ "//third_party/mojo_services/src/native_viewport/public/interfaces",
+ "//third_party/mojo_services/src/surfaces/public/cpp",
+ "//third_party/mojo_services/src/surfaces/public/interfaces",
+ "//third_party/mojo_services/src/view_manager/public/interfaces",
+ "//third_party/mojo_services/src/view_manager/public/cpp:common",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+
+ sources = [
+ "test_change_tracker.cc",
+ "test_change_tracker.h",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//third_party/mojo_services/src/view_manager/public/cpp:common",
+ "//third_party/mojo_services/src/view_manager/public/interfaces",
+ ]
+}
+
+test("view_manager_service_unittests") {
+ sources = [
+ "animation_runner_unittest.cc",
+ "scheduled_animation_group_unittest.cc",
+ "test_server_view_delegate.cc",
+ "test_server_view_delegate.h",
+ "view_coordinate_conversions_unittest.cc",
+ "view_manager_service_unittest.cc",
+ ]
+
+ deps = [
+ ":test_support",
+ ":view_manager_lib",
+ "//base",
+ "//base/test:test_config",
+ "//mojo/converters/geometry",
+ "//mojo/converters/input_events",
+ "//third_party/mojo/src/mojo/edk/test:run_all_unittests",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/native_viewport/public/cpp:args",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//third_party/mojo_services/src/view_manager/public/interfaces",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ "//testing/gtest",
+ "//ui/gfx",
+ "//ui/gfx:test_support",
+ "//ui/gfx/geometry",
+ ]
+
+ if (!is_android) { # TODO(GYP) Enable on Android when osmesa links.
+ deps += [ "//third_party/mesa:osmesa" ]
+ }
+}
+
+mojo_native_application("mojo_view_manager_client_apptests") {
+ testonly = true
+
+ sources = [
+ "view_manager_client_apptest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_config",
+ "//mojo/application",
+ "//mojo/application:test_support",
+ "//third_party/mojo_services/src/geometry/public/cpp:cpp",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ ]
+}
+
+mojo_native_application("view_manager_service_apptests") {
+ testonly = true
+
+ sources = [
+ "view_manager_service_apptest.cc",
+ ]
+
+ deps = [
+ ":test_support",
+ "//base",
+ "//mojo/application",
+ "//mojo/application:test_support",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo_services/src/geometry/public/interfaces",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//third_party/mojo_services/src/view_manager/public/interfaces",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ ]
+}
diff --git a/mojo/services/view_manager/DEPS b/mojo/services/view_manager/DEPS
new file mode 100644
index 0000000..3612d0e
--- /dev/null
+++ b/mojo/services/view_manager/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+cc",
+ "+mojo/application",
+ "+mojo/converters",
+ "+third_party/mojo_services",
+ "+ui",
+]
diff --git a/mojo/services/view_manager/access_policy.h b/mojo/services/view_manager/access_policy.h
new file mode 100644
index 0000000..6f22f91
--- /dev/null
+++ b/mojo/services/view_manager/access_policy.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_
+#define SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_
+
+#include "mojo/services/view_manager/ids.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h"
+
+namespace view_manager {
+
+class ServerView;
+
+// AccessPolicy is used by ViewManagerServiceImpl to determine what a connection
+// is allowed to do.
+class AccessPolicy {
+ public:
+ virtual ~AccessPolicy() {}
+
+ // Unless otherwise mentioned all arguments have been validated. That is the
+ // |view| arguments are non-null unless otherwise stated (eg CanSetView() is
+ // allowed to take a NULL view).
+ virtual bool CanRemoveViewFromParent(const ServerView* view) const = 0;
+ virtual bool CanAddView(const ServerView* parent,
+ const ServerView* child) const = 0;
+ virtual bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction) const = 0;
+ virtual bool CanDeleteView(const ServerView* view) const = 0;
+ virtual bool CanGetViewTree(const ServerView* view) const = 0;
+ // Used when building a view tree (GetViewTree()) to decide if we should
+ // descend into |view|.
+ virtual bool CanDescendIntoViewForViewTree(const ServerView* view) const = 0;
+ virtual bool CanEmbed(const ServerView* view) const = 0;
+ virtual bool CanChangeViewVisibility(const ServerView* view) const = 0;
+ virtual bool CanSetViewSurfaceId(const ServerView* view) const = 0;
+ virtual bool CanSetViewBounds(const ServerView* view) const = 0;
+ virtual bool CanSetViewProperties(const ServerView* view) const = 0;
+
+ // Returns whether the connection should notify on a hierarchy change.
+ // |new_parent| and |old_parent| are initially set to the new and old parents
+ // but may be altered so that the client only sees a certain set of views.
+ virtual bool ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const = 0;
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_
diff --git a/mojo/services/view_manager/access_policy_delegate.h b/mojo/services/view_manager/access_policy_delegate.h
new file mode 100644
index 0000000..1922176
--- /dev/null
+++ b/mojo/services/view_manager/access_policy_delegate.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_
+#define SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_
+
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "mojo/services/view_manager/ids.h"
+
+namespace view_manager {
+
+class ServerView;
+
+// Delegate used by the AccessPolicy implementations to get state.
+class AccessPolicyDelegate {
+ public:
+ // Returns true if |id| is the root of the connection.
+ virtual bool IsRootForAccessPolicy(const ViewId& id) const = 0;
+
+ // Returns true if |view| has been exposed to the client.
+ virtual bool IsViewKnownForAccessPolicy(const ServerView* view) const = 0;
+
+ // Returns true if Embed(view) has been invoked on |view|.
+ virtual bool IsViewRootOfAnotherConnectionForAccessPolicy(
+ const ServerView* view) const = 0;
+
+ protected:
+ virtual ~AccessPolicyDelegate() {}
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_
diff --git a/mojo/services/view_manager/animation_runner.cc b/mojo/services/view_manager/animation_runner.cc
new file mode 100644
index 0000000..dc910a1
--- /dev/null
+++ b/mojo/services/view_manager/animation_runner.cc
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/animation_runner.h"
+
+#include "base/memory/scoped_vector.h"
+#include "mojo/services/view_manager/animation_runner_observer.h"
+#include "mojo/services/view_manager/scheduled_animation_group.h"
+#include "mojo/services/view_manager/server_view.h"
+
+namespace view_manager {
+namespace {
+
+bool ConvertViewAndAnimationPairToScheduledAnimationGroup(
+ const std::vector<AnimationRunner::ViewAndAnimationPair>& views,
+ AnimationRunner::AnimationId id,
+ base::TimeTicks now,
+ ScopedVector<ScheduledAnimationGroup>* groups) {
+ for (const auto& view_animation_pair : views) {
+ DCHECK(view_animation_pair.second);
+ scoped_ptr<ScheduledAnimationGroup> group(ScheduledAnimationGroup::Create(
+ view_animation_pair.first, now, id, *(view_animation_pair.second)));
+ if (!group.get())
+ return false;
+ groups->push_back(group.release());
+ }
+ return true;
+}
+
+} // namespace
+
+AnimationRunner::AnimationRunner(base::TimeTicks now)
+ : next_id_(1), last_tick_time_(now) {
+}
+
+AnimationRunner::~AnimationRunner() {
+}
+
+void AnimationRunner::AddObserver(AnimationRunnerObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void AnimationRunner::RemoveObserver(AnimationRunnerObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+AnimationRunner::AnimationId AnimationRunner::Schedule(
+ const std::vector<ViewAndAnimationPair>& views,
+ base::TimeTicks now) {
+ DCHECK_GE(now, last_tick_time_);
+
+ const AnimationId animation_id = next_id_++;
+ ScopedVector<ScheduledAnimationGroup> groups;
+ if (!ConvertViewAndAnimationPairToScheduledAnimationGroup(views, animation_id,
+ now, &groups)) {
+ return 0;
+ }
+
+ // Cancel any animations for the views.
+ for (auto* group : groups) {
+ ScheduledAnimationGroup* current_group =
+ view_to_animation_map_.get(group->view());
+ if (current_group)
+ current_group->SetValuesToTargetValuesForPropertiesNotIn(*group);
+
+ CancelAnimationForViewImpl(group->view(), CANCEL_SOURCE_SCHEDULE);
+ }
+
+ for (auto* group : groups) {
+ group->ObtainStartValues();
+ view_to_animation_map_.set(group->view(), make_scoped_ptr(group));
+ DCHECK(!id_to_views_map_[animation_id].count(group->view()));
+ id_to_views_map_[animation_id].insert(group->view());
+ }
+ // |view_to_animation_map_| owns the groups.
+ groups.weak_clear();
+
+ FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_,
+ OnAnimationScheduled(animation_id));
+ return animation_id;
+}
+
+void AnimationRunner::CancelAnimation(AnimationId id) {
+ if (id_to_views_map_.count(id) == 0)
+ return;
+
+ std::set<ServerView*> views(id_to_views_map_[id]);
+ for (ServerView* view : views)
+ CancelAnimationForView(view);
+}
+
+void AnimationRunner::CancelAnimationForView(ServerView* view) {
+ CancelAnimationForViewImpl(view, CANCEL_SOURCE_CANCEL);
+}
+
+void AnimationRunner::Tick(base::TimeTicks time) {
+ DCHECK(time >= last_tick_time_);
+ last_tick_time_ = time;
+ if (view_to_animation_map_.empty())
+ return;
+
+ // The animation ids of any views whose animation completes are added here. We
+ // notify after processing all views so that if an observer mutates us in some
+ // way we're aren't left in a weird state.
+ std::set<AnimationId> animations_completed;
+ for (ViewToAnimationMap::iterator i = view_to_animation_map_.begin();
+ i != view_to_animation_map_.end();) {
+ if (i->second->Tick(time)) {
+ const AnimationId animation_id = i->second->id();
+ ServerView* view = i->first;
+ ++i;
+ if (RemoveViewFromMaps(view))
+ animations_completed.insert(animation_id);
+ } else {
+ ++i;
+ }
+ }
+ for (const AnimationId& id : animations_completed) {
+ FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, OnAnimationDone(id));
+ }
+}
+
+void AnimationRunner::CancelAnimationForViewImpl(ServerView* view,
+ CancelSource source) {
+ if (!view_to_animation_map_.contains(view))
+ return;
+
+ const AnimationId animation_id = view_to_animation_map_.get(view)->id();
+ if (RemoveViewFromMaps(view)) {
+ // This was the last view in the group.
+ if (source == CANCEL_SOURCE_CANCEL) {
+ FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_,
+ OnAnimationCanceled(animation_id));
+ } else {
+ FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_,
+ OnAnimationInterrupted(animation_id));
+ }
+ }
+}
+
+bool AnimationRunner::RemoveViewFromMaps(ServerView* view) {
+ DCHECK(view_to_animation_map_.contains(view));
+
+ const AnimationId animation_id = view_to_animation_map_.get(view)->id();
+ view_to_animation_map_.erase(view);
+
+ DCHECK(id_to_views_map_.count(animation_id));
+ id_to_views_map_[animation_id].erase(view);
+ if (!id_to_views_map_[animation_id].empty())
+ return false;
+
+ id_to_views_map_.erase(animation_id);
+ return true;
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/animation_runner.h b/mojo/services/view_manager/animation_runner.h
new file mode 100644
index 0000000..64bf494
--- /dev/null
+++ b/mojo/services/view_manager/animation_runner.h
@@ -0,0 +1,114 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_H_
+#define SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_H_
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/containers/scoped_ptr_hash_map.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+
+namespace mojo {
+class AnimationGroup;
+}
+
+namespace view_manager {
+
+class AnimationRunnerObserver;
+class ScheduledAnimationGroup;
+class ServerView;
+
+// AnimationRunner is responsible for maintaing and running a set of animations.
+// The animations are represented as a set of AnimationGroups. New animations
+// are scheduled by way of Schedule(). A |view| may only have one animation
+// running at a time. Schedule()ing a new animation for a view already animating
+// implicitly cancels the current animation for the view. Animations progress
+// by way of the Tick() function.
+class AnimationRunner {
+ public:
+ using AnimationId = uint32_t;
+ using ViewAndAnimationPair =
+ std::pair<ServerView*, const mojo::AnimationGroup*>;
+
+ explicit AnimationRunner(base::TimeTicks now);
+ ~AnimationRunner();
+
+ void AddObserver(AnimationRunnerObserver* observer);
+ void RemoveObserver(AnimationRunnerObserver* observer);
+
+ // Schedules animations. If any of the groups are not valid no animations are
+ // scheuled and 0 is returned. If there is an existing animation in progress
+ // for any of the views it is canceled and any properties that were animating
+ // but are no longer animating are set to their target value.
+ AnimationId Schedule(const std::vector<ViewAndAnimationPair>& views,
+ base::TimeTicks now);
+
+ // Cancels an animation scheduled by an id that was previously returned from
+ // Schedule().
+ void CancelAnimation(AnimationId id);
+
+ // Cancels the animation scheduled for |view|. Does nothing if there is no
+ // animation scheduled for |view|. This does not change |view|. That is, any
+ // in progress animations are stopped.
+ void CancelAnimationForView(ServerView* view);
+
+ // Advance the animations updating values appropriately.
+ void Tick(base::TimeTicks time);
+
+ // Returns true if there are animations currently scheduled.
+ bool HasAnimations() const { return !view_to_animation_map_.empty(); }
+
+ // Returns true if the animation identified by |id| is valid and animating.
+ bool IsAnimating(AnimationId id) const {
+ return id_to_views_map_.count(id) > 0;
+ }
+
+ // Returns the views that are currently animating for |id|. Returns an empty
+ // set if |id| does not identify a valid animation.
+ std::set<ServerView*> GetViewsAnimating(AnimationId id) {
+ return IsAnimating(id) ? id_to_views_map_.find(id)->second
+ : std::set<ServerView*>();
+ }
+
+ private:
+ enum CancelSource {
+ // Cancel is the result of scheduling another animation for the view.
+ CANCEL_SOURCE_SCHEDULE,
+
+ // Cancel originates from an explicit call to cancel.
+ CANCEL_SOURCE_CANCEL,
+ };
+
+ using ViewToAnimationMap =
+ base::ScopedPtrHashMap<ServerView*, ScheduledAnimationGroup>;
+ using IdToViewsMap = std::map<AnimationId, std::set<ServerView*>>;
+
+ void CancelAnimationForViewImpl(ServerView* view, CancelSource source);
+
+ // Removes |view| from both |view_to_animation_map_| and |id_to_views_map_|.
+ // Returns true if there are no more views animating with the animation id
+ // the view is associated with.
+ bool RemoveViewFromMaps(ServerView* view);
+
+ AnimationId next_id_;
+
+ base::TimeTicks last_tick_time_;
+
+ ObserverList<AnimationRunnerObserver> observers_;
+
+ ViewToAnimationMap view_to_animation_map_;
+
+ IdToViewsMap id_to_views_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimationRunner);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_H_
diff --git a/mojo/services/view_manager/animation_runner_observer.h b/mojo/services/view_manager/animation_runner_observer.h
new file mode 100644
index 0000000..9b79983
--- /dev/null
+++ b/mojo/services/view_manager/animation_runner_observer.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 SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_
+#define SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_
+
+namespace view_manager {
+
+class AnimationRunnerObserver {
+ public:
+ virtual void OnAnimationScheduled(uint32_t id) = 0;
+ virtual void OnAnimationDone(uint32_t id) = 0;
+ virtual void OnAnimationInterrupted(uint32_t id) = 0;
+ virtual void OnAnimationCanceled(uint32_t id) = 0;
+
+ protected:
+ virtual ~AnimationRunnerObserver() {}
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_
diff --git a/mojo/services/view_manager/animation_runner_unittest.cc b/mojo/services/view_manager/animation_runner_unittest.cc
new file mode 100644
index 0000000..c43f0b1
--- /dev/null
+++ b/mojo/services/view_manager/animation_runner_unittest.cc
@@ -0,0 +1,641 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/animation_runner.h"
+
+#include "base/strings/stringprintf.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/transform/transform_type_converters.h"
+#include "mojo/services/view_manager/animation_runner_observer.h"
+#include "mojo/services/view_manager/scheduled_animation_group.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/test_server_view_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h"
+
+using base::TimeDelta;
+using mojo::ANIMATION_PROPERTY_NONE;
+using mojo::ANIMATION_PROPERTY_OPACITY;
+using mojo::ANIMATION_PROPERTY_TRANSFORM;
+using mojo::ANIMATION_TWEEN_TYPE_LINEAR;
+using mojo::AnimationElement;
+using mojo::AnimationGroup;
+using mojo::AnimationProperty;
+using mojo::AnimationSequence;
+using mojo::AnimationTweenType;
+using mojo::AnimationValue;
+using mojo::AnimationValuePtr;
+using mojo::Transform;
+
+namespace view_manager {
+namespace {
+
+class TestAnimationRunnerObserver : public AnimationRunnerObserver {
+ public:
+ TestAnimationRunnerObserver() {}
+ ~TestAnimationRunnerObserver() override {}
+
+ std::vector<std::string>* changes() { return &changes_; }
+ std::vector<uint32_t>* change_ids() { return &change_ids_; }
+
+ void clear_changes() {
+ changes_.clear();
+ change_ids_.clear();
+ }
+
+ // AnimationRunnerDelgate:
+ void OnAnimationScheduled(uint32_t id) override {
+ change_ids_.push_back(id);
+ changes_.push_back("scheduled");
+ }
+ void OnAnimationDone(uint32_t id) override {
+ change_ids_.push_back(id);
+ changes_.push_back("done");
+ }
+ void OnAnimationInterrupted(uint32_t id) override {
+ change_ids_.push_back(id);
+ changes_.push_back("interrupted");
+ }
+ void OnAnimationCanceled(uint32_t id) override {
+ change_ids_.push_back(id);
+ changes_.push_back("canceled");
+ }
+
+ private:
+ std::vector<uint32_t> change_ids_;
+ std::vector<std::string> changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestAnimationRunnerObserver);
+};
+
+// Creates an AnimationValuePtr from the specified float value.
+AnimationValuePtr FloatAnimationValue(float float_value) {
+ AnimationValuePtr value(AnimationValue::New());
+ value->float_value = float_value;
+ return value.Pass();
+}
+
+// Creates an AnimationValuePtr from the specified transform.
+AnimationValuePtr TransformAnimationValue(const gfx::Transform& transform) {
+ AnimationValuePtr value(AnimationValue::New());
+ value->transform = Transform::From(transform);
+ return value.Pass();
+}
+
+// Adds an AnimationElement to |group|s last sequence with the specified value.
+void AddElement(AnimationGroup* group,
+ TimeDelta time,
+ AnimationValuePtr start_value,
+ AnimationValuePtr target_value,
+ AnimationProperty property,
+ AnimationTweenType tween_type) {
+ AnimationSequence& sequence =
+ *(group->sequences[group->sequences.size() - 1]);
+ sequence.elements.push_back(AnimationElement::New());
+ AnimationElement& element =
+ *(sequence.elements[sequence.elements.size() - 1]);
+ element.property = property;
+ element.duration = time.InMicroseconds();
+ element.tween_type = tween_type;
+ element.start_value = start_value.Pass();
+ element.target_value = target_value.Pass();
+}
+
+void AddOpacityElement(AnimationGroup* group,
+ TimeDelta time,
+ AnimationValuePtr start_value,
+ AnimationValuePtr target_value) {
+ AddElement(group, time, start_value.Pass(), target_value.Pass(),
+ ANIMATION_PROPERTY_OPACITY, ANIMATION_TWEEN_TYPE_LINEAR);
+}
+
+void AddTransformElement(AnimationGroup* group,
+ TimeDelta time,
+ AnimationValuePtr start_value,
+ AnimationValuePtr target_value) {
+ AddElement(group, time, start_value.Pass(), target_value.Pass(),
+ ANIMATION_PROPERTY_TRANSFORM, ANIMATION_TWEEN_TYPE_LINEAR);
+}
+
+void AddPauseElement(AnimationGroup* group, TimeDelta time) {
+ AddElement(group, time, AnimationValuePtr(), AnimationValuePtr(),
+ ANIMATION_PROPERTY_NONE, ANIMATION_TWEEN_TYPE_LINEAR);
+}
+
+void InitGroupForView(AnimationGroup* group,
+ const ViewId& id,
+ int cycle_count) {
+ group->view_id = ViewIdToTransportId(id);
+ group->sequences.push_back(AnimationSequence::New());
+ group->sequences[group->sequences.size() - 1]->cycle_count = cycle_count;
+}
+
+} // namespace
+
+class AnimationRunnerTest : public testing::Test {
+ public:
+ AnimationRunnerTest()
+ : initial_time_(base::TimeTicks::Now()), runner_(initial_time_) {
+ runner_.AddObserver(&runner_observer_);
+ }
+ ~AnimationRunnerTest() override { runner_.RemoveObserver(&runner_observer_); }
+
+ protected:
+ // Convenience to schedule an animation for a single view/group pair.
+ AnimationRunner::AnimationId ScheduleForSingleView(
+ ServerView* view,
+ const AnimationGroup* group,
+ base::TimeTicks now) {
+ std::vector<AnimationRunner::ViewAndAnimationPair> pairs;
+ pairs.push_back(std::make_pair(view, group));
+ return runner_.Schedule(pairs, now);
+ }
+
+ // If |id| is valid and there is only one view schedule against the animation
+ // it is returned; otherwise returns null.
+ ServerView* GetSingleViewAnimating(AnimationRunner::AnimationId id) {
+ std::set<ServerView*> views(runner_.GetViewsAnimating(id));
+ return views.size() == 1 ? *views.begin() : nullptr;
+ }
+
+ const base::TimeTicks initial_time_;
+ TestAnimationRunnerObserver runner_observer_;
+ AnimationRunner runner_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AnimationRunnerTest);
+};
+
+// Opacity from 1 to .5 over 1000.
+TEST_F(AnimationRunnerTest, SingleProperty) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId());
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ const uint32_t animation_id =
+ ScheduleForSingleView(&view, &group, initial_time_);
+
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("scheduled", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+ runner_observer_.clear_changes();
+
+ EXPECT_TRUE(runner_.HasAnimations());
+
+ // Opacity should still be 1 (the initial value).
+ EXPECT_EQ(1.f, view.opacity());
+
+ // Animate half way.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_TRUE(runner_observer_.changes()->empty());
+
+ // Run well past the end. Value should progress to end and delegate should
+ // be notified.
+ runner_.Tick(initial_time_ + TimeDelta::FromSeconds(10));
+ EXPECT_EQ(.5f, view.opacity());
+
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("done", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+
+ EXPECT_FALSE(runner_.HasAnimations());
+}
+
+// Opacity from 1 to .5, followed by transform from identity to 2x,3x.
+TEST_F(AnimationRunnerTest, TwoPropertiesInSequence) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId());
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(.5f));
+
+ gfx::Transform done_transform;
+ done_transform.Scale(2, 4);
+ AddTransformElement(&group, TimeDelta::FromMicroseconds(2000),
+ AnimationValuePtr(),
+ TransformAnimationValue(done_transform));
+
+ const uint32_t animation_id =
+ ScheduleForSingleView(&view, &group, initial_time_);
+ runner_observer_.clear_changes();
+
+ // Nothing in the view should have changed yet.
+ EXPECT_EQ(1.f, view.opacity());
+ EXPECT_TRUE(view.transform().IsIdentity());
+
+ // Animate half way from through opacity animation.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_TRUE(view.transform().IsIdentity());
+
+ // Finish first element (opacity).
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000));
+ EXPECT_EQ(.5f, view.opacity());
+ EXPECT_TRUE(view.transform().IsIdentity());
+
+ // Half way through second (transform).
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000));
+ EXPECT_EQ(.5f, view.opacity());
+ gfx::Transform half_way_transform;
+ half_way_transform.Scale(1.5, 2.5);
+ EXPECT_EQ(half_way_transform, view.transform());
+
+ EXPECT_TRUE(runner_observer_.changes()->empty());
+
+ // To end.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500));
+ EXPECT_EQ(.5f, view.opacity());
+ EXPECT_EQ(done_transform, view.transform());
+
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("done", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+}
+
+// Opacity from .5 to 1 over 1000, transform to 2x,4x over 500.
+TEST_F(AnimationRunnerTest, TwoPropertiesInParallel) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId(1, 1));
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ FloatAnimationValue(.5f), FloatAnimationValue(1));
+
+ group.sequences.push_back(AnimationSequence::New());
+ group.sequences[1]->cycle_count = 1;
+ gfx::Transform done_transform;
+ done_transform.Scale(2, 4);
+ AddTransformElement(&group, TimeDelta::FromMicroseconds(500),
+ AnimationValuePtr(),
+ TransformAnimationValue(done_transform));
+
+ const uint32_t animation_id =
+ ScheduleForSingleView(&view, &group, initial_time_);
+
+ runner_observer_.clear_changes();
+
+ // Nothing in the view should have changed yet.
+ EXPECT_EQ(1.f, view.opacity());
+ EXPECT_TRUE(view.transform().IsIdentity());
+
+ // Animate to 250, which is 1/4 way through opacity and half way through
+ // transform.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(250));
+
+ EXPECT_EQ(.625f, view.opacity());
+ gfx::Transform half_way_transform;
+ half_way_transform.Scale(1.5, 2.5);
+ EXPECT_EQ(half_way_transform, view.transform());
+
+ // Animate to 500, which is 1/2 way through opacity and transform done.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_EQ(done_transform, view.transform());
+
+ // Animate to 750, which is 3/4 way through opacity and transform done.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750));
+ EXPECT_EQ(.875f, view.opacity());
+ EXPECT_EQ(done_transform, view.transform());
+
+ EXPECT_TRUE(runner_observer_.changes()->empty());
+
+ // To end.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500));
+ EXPECT_EQ(1.f, view.opacity());
+ EXPECT_EQ(done_transform, view.transform());
+
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("done", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+}
+
+// Opacity from .5 to 1 over 1000, pause for 500, 1 to .5 over 500, with a cycle
+// count of 3.
+TEST_F(AnimationRunnerTest, Cycles) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId(1, 2));
+
+ view.SetOpacity(.5f);
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 3);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(1));
+ AddPauseElement(&group, TimeDelta::FromMicroseconds(500));
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(500),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ ScheduleForSingleView(&view, &group, initial_time_);
+ runner_observer_.clear_changes();
+
+ // Nothing in the view should have changed yet.
+ EXPECT_EQ(.5f, view.opacity());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+ EXPECT_EQ(.75f, view.opacity());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1250));
+ EXPECT_EQ(1.f, view.opacity());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750));
+ EXPECT_EQ(.75f, view.opacity());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2500));
+ EXPECT_EQ(.75f, view.opacity());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3250));
+ EXPECT_EQ(1.f, view.opacity());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3750));
+ EXPECT_EQ(.75f, view.opacity());
+
+ // Animate to the end.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(6500));
+ EXPECT_EQ(.5f, view.opacity());
+
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("done", runner_observer_.changes()->at(0));
+}
+
+// Verifies scheduling the same view twice sends an interrupt.
+TEST_F(AnimationRunnerTest, ScheduleTwice) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId(1, 2));
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ const uint32_t animation_id =
+ ScheduleForSingleView(&view, &group, initial_time_);
+ runner_observer_.clear_changes();
+
+ // Animate half way.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_TRUE(runner_observer_.changes()->empty());
+
+ // Schedule again. We should get an interrupt, but opacity shouldn't change.
+ const uint32_t animation2_id = ScheduleForSingleView(
+ &view, &group, initial_time_ + TimeDelta::FromMicroseconds(500));
+
+ // Id should have changed.
+ EXPECT_NE(animation_id, animation2_id);
+
+ EXPECT_FALSE(runner_.IsAnimating(animation_id));
+ EXPECT_EQ(&view, GetSingleViewAnimating(animation2_id));
+
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_EQ(2u, runner_observer_.changes()->size());
+ EXPECT_EQ("interrupted", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+ EXPECT_EQ("scheduled", runner_observer_.changes()->at(1));
+ EXPECT_EQ(animation2_id, runner_observer_.change_ids()->at(1));
+ runner_observer_.clear_changes();
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000));
+ EXPECT_EQ(.625f, view.opacity());
+ EXPECT_TRUE(runner_observer_.changes()->empty());
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000));
+ EXPECT_EQ(.5f, view.opacity());
+ EXPECT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("done", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation2_id, runner_observer_.change_ids()->at(0));
+}
+
+// Verifies Remove() works.
+TEST_F(AnimationRunnerTest, CancelAnimationForView) {
+ // Create an animation and advance it part way.
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId());
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ const uint32_t animation_id =
+ ScheduleForSingleView(&view, &group, initial_time_);
+ runner_observer_.clear_changes();
+ EXPECT_EQ(&view, GetSingleViewAnimating(animation_id));
+
+ EXPECT_TRUE(runner_.HasAnimations());
+
+ // Animate half way.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_TRUE(runner_observer_.changes()->empty());
+
+ // Cancel the animation.
+ runner_.CancelAnimationForView(&view);
+
+ EXPECT_FALSE(runner_.HasAnimations());
+ EXPECT_EQ(nullptr, GetSingleViewAnimating(animation_id));
+
+ EXPECT_EQ(.75f, view.opacity());
+
+ EXPECT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("canceled", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+}
+
+// Verifies a tick with a very large delta and a sequence that repeats forever
+// doesn't take a long time.
+TEST_F(AnimationRunnerTest, InfiniteRepeatWithHugeGap) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId(1, 2));
+
+ view.SetOpacity(.5f);
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 0);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(500),
+ AnimationValuePtr(), FloatAnimationValue(1));
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(500),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ ScheduleForSingleView(&view, &group, initial_time_);
+ runner_observer_.clear_changes();
+
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000000000750));
+
+ EXPECT_EQ(.75f, view.opacity());
+
+ ASSERT_EQ(0u, runner_observer_.changes()->size());
+}
+
+// Verifies a second schedule sets any properties that are no longer animating
+// to their final value.
+TEST_F(AnimationRunnerTest, RescheduleSetsPropertiesToFinalValue) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId());
+
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ gfx::Transform done_transform;
+ done_transform.Scale(2, 4);
+ AddTransformElement(&group, TimeDelta::FromMicroseconds(500),
+ AnimationValuePtr(),
+ TransformAnimationValue(done_transform));
+
+ ScheduleForSingleView(&view, &group, initial_time_);
+
+ // Schedule() again, this time without animating opacity.
+ group.sequences[0]->elements[0]->property = ANIMATION_PROPERTY_NONE;
+ ScheduleForSingleView(&view, &group, initial_time_);
+
+ // Opacity should go to final value.
+ EXPECT_EQ(.5f, view.opacity());
+ // Transform shouldn't have changed since newly scheduled animation also has
+ // transform in it.
+ EXPECT_TRUE(view.transform().IsIdentity());
+}
+
+// Opacity from 1 to .5 over 1000 of v1 and v2 transform to 2x,4x over 500.
+TEST_F(AnimationRunnerTest, TwoViews) {
+ TestServerViewDelegate view_delegate;
+ ServerView view1(&view_delegate, ViewId());
+ ServerView view2(&view_delegate, ViewId(1, 2));
+
+ AnimationGroup group1;
+ InitGroupForView(&group1, view1.id(), 1);
+ AddOpacityElement(&group1, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(.5));
+
+ AnimationGroup group2;
+ InitGroupForView(&group2, view2.id(), 1);
+ gfx::Transform done_transform;
+ done_transform.Scale(2, 4);
+ AddTransformElement(&group2, TimeDelta::FromMicroseconds(500),
+ AnimationValuePtr(),
+ TransformAnimationValue(done_transform));
+
+ std::vector<AnimationRunner::ViewAndAnimationPair> pairs;
+ pairs.push_back(std::make_pair(&view1, &group1));
+ pairs.push_back(std::make_pair(&view2, &group2));
+
+ const uint32_t animation_id = runner_.Schedule(pairs, initial_time_);
+
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("scheduled", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0));
+ runner_observer_.clear_changes();
+
+ EXPECT_TRUE(runner_.HasAnimations());
+ EXPECT_TRUE(runner_.IsAnimating(animation_id));
+
+ // Properties should be at the initial value.
+ EXPECT_EQ(1.f, view1.opacity());
+ EXPECT_TRUE(view2.transform().IsIdentity());
+
+ // Animate 250ms in, which is quarter way for opacity and half way for
+ // transform.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(250));
+ EXPECT_EQ(.875f, view1.opacity());
+ gfx::Transform half_way_transform;
+ half_way_transform.Scale(1.5, 2.5);
+ EXPECT_EQ(half_way_transform, view2.transform());
+ std::set<ServerView*> views_animating(
+ runner_.GetViewsAnimating(animation_id));
+ EXPECT_EQ(2u, views_animating.size());
+ EXPECT_EQ(1u, views_animating.count(&view1));
+ EXPECT_EQ(1u, views_animating.count(&view2));
+
+ // Animate 750ms in, view1 should be done 3/4 done, and view2 done.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750));
+ EXPECT_EQ(.625, view1.opacity());
+ EXPECT_EQ(done_transform, view2.transform());
+ views_animating = runner_.GetViewsAnimating(animation_id);
+ EXPECT_EQ(1u, views_animating.size());
+ EXPECT_EQ(1u, views_animating.count(&view1));
+ EXPECT_TRUE(runner_.HasAnimations());
+ EXPECT_TRUE(runner_.IsAnimating(animation_id));
+
+ // Animate to end.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750));
+ EXPECT_EQ(.5, view1.opacity());
+ EXPECT_EQ(done_transform, view2.transform());
+ views_animating = runner_.GetViewsAnimating(animation_id);
+ EXPECT_TRUE(views_animating.empty());
+ EXPECT_FALSE(runner_.HasAnimations());
+ EXPECT_FALSE(runner_.IsAnimating(animation_id));
+}
+
+TEST_F(AnimationRunnerTest, Reschedule) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId());
+
+ // Animation from 1-0 over 1ms and in parallel transform to 2x,4x over 1ms.
+ AnimationGroup group;
+ InitGroupForView(&group, view.id(), 1);
+ AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(0));
+ group.sequences.push_back(AnimationSequence::New());
+ group.sequences[1]->cycle_count = 1;
+ gfx::Transform done_transform;
+ done_transform.Scale(2, 4);
+ AddTransformElement(&group, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(),
+ TransformAnimationValue(done_transform));
+ const uint32_t animation_id1 =
+ ScheduleForSingleView(&view, &group, initial_time_);
+
+ // Animate half way in.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500));
+ EXPECT_EQ(.5f, view.opacity());
+ gfx::Transform half_way_transform;
+ half_way_transform.Scale(1.5, 2.5);
+ EXPECT_EQ(half_way_transform, view.transform());
+
+ runner_observer_.clear_changes();
+
+ // Schedule the same view animating opacity to 1.
+ AnimationGroup group2;
+ InitGroupForView(&group2, view.id(), 1);
+ AddOpacityElement(&group2, TimeDelta::FromMicroseconds(1000),
+ AnimationValuePtr(), FloatAnimationValue(1));
+ const uint32_t animation_id2 = ScheduleForSingleView(
+ &view, &group2, initial_time_ + TimeDelta::FromMicroseconds(500));
+
+ // Opacity should remain at .5, but transform should go to end state.
+ EXPECT_EQ(.5f, view.opacity());
+ EXPECT_EQ(done_transform, view.transform());
+
+ ASSERT_EQ(2u, runner_observer_.changes()->size());
+ EXPECT_EQ("interrupted", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id1, runner_observer_.change_ids()->at(0));
+ EXPECT_EQ("scheduled", runner_observer_.changes()->at(1));
+ EXPECT_EQ(animation_id2, runner_observer_.change_ids()->at(1));
+ runner_observer_.clear_changes();
+
+ // Animate half way through new sequence. Opacity should be the only thing
+ // changing.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000));
+ EXPECT_EQ(.75f, view.opacity());
+ EXPECT_EQ(done_transform, view.transform());
+ ASSERT_EQ(0u, runner_observer_.changes()->size());
+
+ // Animate to end.
+ runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000));
+ ASSERT_EQ(1u, runner_observer_.changes()->size());
+ EXPECT_EQ("done", runner_observer_.changes()->at(0));
+ EXPECT_EQ(animation_id2, runner_observer_.change_ids()->at(0));
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/client_connection.cc b/mojo/services/view_manager/client_connection.cc
new file mode 100644
index 0000000..41c2c59
--- /dev/null
+++ b/mojo/services/view_manager/client_connection.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/client_connection.h"
+
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+
+namespace view_manager {
+
+ClientConnection::ClientConnection(scoped_ptr<ViewManagerServiceImpl> service,
+ mojo::ViewManagerClient* client)
+ : service_(service.Pass()), client_(client) {
+}
+
+ClientConnection::~ClientConnection() {
+}
+
+DefaultClientConnection::DefaultClientConnection(
+ scoped_ptr<ViewManagerServiceImpl> service_impl,
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ViewManagerClientPtr client)
+ : ClientConnection(service_impl.Pass(), client.get()),
+ connection_manager_(connection_manager),
+ binding_(service(), service_request.Pass()),
+ client_(client.Pass()) {
+ binding_.set_error_handler(this);
+}
+
+DefaultClientConnection::~DefaultClientConnection() {
+}
+
+void DefaultClientConnection::OnConnectionError() {
+ connection_manager_->OnConnectionError(this);
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/client_connection.h b/mojo/services/view_manager/client_connection.h
new file mode 100644
index 0000000..83fa864
--- /dev/null
+++ b/mojo/services/view_manager/client_connection.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 SERVICES_VIEW_MANAGER_CLIENT_CONNECTION_H_
+#define SERVICES_VIEW_MANAGER_CLIENT_CONNECTION_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+
+namespace view_manager {
+
+class ConnectionManager;
+class ViewManagerServiceImpl;
+
+// ClientConnection encapsulates the state needed for a single client connected
+// to the view manager.
+class ClientConnection {
+ public:
+ ClientConnection(scoped_ptr<ViewManagerServiceImpl> service,
+ mojo::ViewManagerClient* client);
+ virtual ~ClientConnection();
+
+ ViewManagerServiceImpl* service() { return service_.get(); }
+ const ViewManagerServiceImpl* service() const { return service_.get(); }
+
+ mojo::ViewManagerClient* client() { return client_; }
+
+ private:
+ scoped_ptr<ViewManagerServiceImpl> service_;
+ mojo::ViewManagerClient* client_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientConnection);
+};
+
+// Bindings implementation of ClientConnection.
+class DefaultClientConnection : public ClientConnection,
+ public mojo::ErrorHandler {
+ public:
+ DefaultClientConnection(
+ scoped_ptr<ViewManagerServiceImpl> service_impl,
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ViewManagerClientPtr client);
+ ~DefaultClientConnection() override;
+
+ private:
+ // ErrorHandler:
+ void OnConnectionError() override;
+
+ ConnectionManager* connection_manager_;
+ mojo::Binding<mojo::ViewManagerService> binding_;
+ mojo::ViewManagerClientPtr client_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultClientConnection);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_CLIENT_CONNECTION_H_
diff --git a/mojo/services/view_manager/connection_manager.cc b/mojo/services/view_manager/connection_manager.cc
new file mode 100644
index 0000000..768d0ff
--- /dev/null
+++ b/mojo/services/view_manager/connection_manager.cc
@@ -0,0 +1,494 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/connection_manager.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/view_manager/client_connection.h"
+#include "mojo/services/view_manager/connection_manager_delegate.h"
+#include "mojo/services/view_manager/display_manager.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/view_coordinate_conversions.h"
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+
+using mojo::ConnectionSpecificId;
+
+namespace view_manager {
+namespace {
+
+// Creates a copy of |view|. The copied view has |delegate| as its delegate.
+// This does not recurse.
+ServerView* CloneView(const ServerView* view, ServerViewDelegate* delegate) {
+ ServerView* clone = new ServerView(delegate, ClonedViewId());
+ clone->SetBounds(view->bounds());
+ clone->SetSurfaceId(view->surface_id());
+ clone->SetOpacity(view->opacity());
+ return clone;
+}
+
+// Creates copies of all the visible children of |parent|. Newly cloned views
+// are added to |cloned_parent| and have |delegate| as their delegate. The
+// stacking order of the cloned views is preseved.
+void CloneViewTree(const ServerView* parent,
+ ServerView* cloned_parent,
+ ServerViewDelegate* delegate) {
+ DCHECK(parent->visible());
+ for (const ServerView* to_clone : parent->GetChildren()) {
+ if (to_clone->visible()) {
+ ServerView* cloned = CloneView(to_clone, delegate);
+ cloned_parent->Add(cloned);
+ CloneViewTree(to_clone, cloned, delegate);
+ }
+ }
+}
+
+// Recurses through all the children of |view| moving any cloned views to
+// |new_parent| stacked above |stack_above|. |stack_above| is updated as views
+// are moved.
+void ReparentClonedViews(ServerView* new_parent,
+ ServerView** stack_above,
+ ServerView* view) {
+ if (view->id() == ClonedViewId()) {
+ const gfx::Rect new_bounds(ConvertRectBetweenViews(
+ view, new_parent, gfx::Rect(view->bounds().size())));
+ new_parent->Add(view);
+ new_parent->Reorder(view, *stack_above, mojo::ORDER_DIRECTION_ABOVE);
+ view->SetBounds(new_bounds);
+ *stack_above = view;
+ return;
+ }
+
+ for (ServerView* child : view->GetChildren())
+ ReparentClonedViews(new_parent, stack_above, child);
+}
+
+// Deletes |view| and all its descendants.
+void DeleteViewTree(ServerView* view) {
+ for (ServerView* child : view->GetChildren())
+ DeleteViewTree(child);
+
+ delete view;
+}
+
+// TODO(sky): nuke, proof of concept.
+bool DecrementAnimatingViewsOpacity(ServerView* view) {
+ if (view->id() == ClonedViewId()) {
+ const float new_opacity = view->opacity() - .05f;
+ if (new_opacity <= 0)
+ DeleteViewTree(view);
+ else
+ view->SetOpacity(new_opacity);
+ return true;
+ }
+ bool ret_value = false;
+ for (ServerView* child : view->GetChildren()) {
+ if (DecrementAnimatingViewsOpacity(child))
+ ret_value = true;
+ }
+ return ret_value;
+}
+
+} // namespace
+
+ConnectionManager::ScopedChange::ScopedChange(
+ ViewManagerServiceImpl* connection,
+ ConnectionManager* connection_manager,
+ bool is_delete_view)
+ : connection_manager_(connection_manager),
+ connection_id_(connection->id()),
+ is_delete_view_(is_delete_view) {
+ connection_manager_->PrepareForChange(this);
+}
+
+ConnectionManager::ScopedChange::~ScopedChange() {
+ connection_manager_->FinishChange();
+}
+
+ConnectionManager::ConnectionManager(ConnectionManagerDelegate* delegate,
+ scoped_ptr<DisplayManager> display_manager,
+ mojo::WindowManagerInternal* wm_internal)
+ : delegate_(delegate),
+ window_manager_client_connection_(nullptr),
+ next_connection_id_(1),
+ display_manager_(display_manager.Pass()),
+ root_(new ServerView(this, RootViewId())),
+ wm_internal_(wm_internal),
+ current_change_(nullptr),
+ in_destructor_(false),
+ animation_runner_(base::TimeTicks::Now()) {
+ root_->SetBounds(gfx::Rect(800, 600));
+ root_->SetVisible(true);
+ display_manager_->Init(this);
+}
+
+ConnectionManager::~ConnectionManager() {
+ in_destructor_ = true;
+
+ STLDeleteValues(&connection_map_);
+ // All the connections should have been destroyed.
+ DCHECK(connection_map_.empty());
+ root_.reset();
+}
+
+ConnectionSpecificId ConnectionManager::GetAndAdvanceNextConnectionId() {
+ const ConnectionSpecificId id = next_connection_id_++;
+ DCHECK_LT(id, next_connection_id_);
+ return id;
+}
+
+void ConnectionManager::OnConnectionError(ClientConnection* connection) {
+ if (connection == window_manager_client_connection_) {
+ window_manager_client_connection_ = nullptr;
+ delegate_->OnLostConnectionToWindowManager();
+ // Assume we've been destroyed.
+ return;
+ }
+
+ scoped_ptr<ClientConnection> connection_owner(connection);
+
+ connection_map_.erase(connection->service()->id());
+
+ // Notify remaining connections so that they can cleanup.
+ for (auto& pair : connection_map_) {
+ pair.second->service()->OnWillDestroyViewManagerServiceImpl(
+ connection->service());
+ }
+}
+
+void ConnectionManager::EmbedAtView(
+ ConnectionSpecificId creator_id,
+ const std::string& url,
+ const ViewId& view_id,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) {
+ std::string creator_url;
+ ConnectionMap::const_iterator it = connection_map_.find(creator_id);
+ if (it != connection_map_.end())
+ creator_url = it->second->service()->url();
+
+ mojo::ViewManagerServicePtr service_ptr;
+ ClientConnection* client_connection =
+ delegate_->CreateClientConnectionForEmbedAtView(
+ this, GetProxy(&service_ptr), creator_id, creator_url, url, view_id);
+ AddConnection(client_connection);
+ client_connection->service()->Init(client_connection->client(),
+ service_ptr.Pass(), services.Pass(),
+ exposed_services.Pass());
+ OnConnectionMessagedClient(client_connection->service()->id());
+}
+
+void ConnectionManager::EmbedAtView(mojo::ConnectionSpecificId creator_id,
+ const ViewId& view_id,
+ mojo::ViewManagerClientPtr client) {
+ std::string creator_url;
+ ConnectionMap::const_iterator it = connection_map_.find(creator_id);
+ if (it != connection_map_.end())
+ creator_url = it->second->service()->url();
+
+ mojo::ViewManagerServicePtr service_ptr;
+ ClientConnection* client_connection =
+ delegate_->CreateClientConnectionForEmbedAtView(
+ this, GetProxy(&service_ptr), creator_id, creator_url, view_id,
+ client.Pass());
+ AddConnection(client_connection);
+ client_connection->service()->Init(client_connection->client(),
+ service_ptr.Pass(), nullptr, nullptr);
+ OnConnectionMessagedClient(client_connection->service()->id());
+}
+
+ViewManagerServiceImpl* ConnectionManager::GetConnection(
+ ConnectionSpecificId connection_id) {
+ ConnectionMap::iterator i = connection_map_.find(connection_id);
+ return i == connection_map_.end() ? nullptr : i->second->service();
+}
+
+ServerView* ConnectionManager::GetView(const ViewId& id) {
+ if (id == root_->id())
+ return root_.get();
+ ViewManagerServiceImpl* service = GetConnection(id.connection_id);
+ return service ? service->GetView(id) : nullptr;
+}
+
+void ConnectionManager::OnConnectionMessagedClient(ConnectionSpecificId id) {
+ if (current_change_)
+ current_change_->MarkConnectionAsMessaged(id);
+}
+
+bool ConnectionManager::DidConnectionMessageClient(
+ ConnectionSpecificId id) const {
+ return current_change_ && current_change_->DidMessageConnection(id);
+}
+
+const ViewManagerServiceImpl* ConnectionManager::GetConnectionWithRoot(
+ const ViewId& id) const {
+ for (auto& pair : connection_map_) {
+ if (pair.second->service()->IsRoot(id))
+ return pair.second->service();
+ }
+ return nullptr;
+}
+
+void ConnectionManager::SetWindowManagerClientConnection(
+ scoped_ptr<ClientConnection> connection) {
+ CHECK(!window_manager_client_connection_);
+ window_manager_client_connection_ = connection.release();
+ AddConnection(window_manager_client_connection_);
+ window_manager_client_connection_->service()->Init(
+ window_manager_client_connection_->client(), nullptr, nullptr, nullptr);
+}
+
+mojo::ViewManagerClient*
+ConnectionManager::GetWindowManagerViewManagerClient() {
+ CHECK(window_manager_client_connection_);
+ return window_manager_client_connection_->client();
+}
+
+bool ConnectionManager::CloneAndAnimate(const ViewId& view_id) {
+ ServerView* view = GetView(view_id);
+ if (!view || !view->IsDrawn(root_.get()) || view == root_.get())
+ return false;
+ if (!animation_timer_.IsRunning()) {
+ animation_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(100),
+ this, &ConnectionManager::DoAnimation);
+ }
+ ServerView* clone = CloneView(view, this);
+ CloneViewTree(view, clone, this);
+ view->parent()->Add(clone);
+ view->parent()->Reorder(clone, view, mojo::ORDER_DIRECTION_ABOVE);
+ return true;
+}
+
+void ConnectionManager::ProcessViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessViewBoundsChanged(
+ view, old_bounds, new_bounds, IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::ProcessViewportMetricsChanged(
+ const mojo::ViewportMetrics& old_metrics,
+ const mojo::ViewportMetrics& new_metrics) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessViewportMetricsChanged(
+ old_metrics, new_metrics, IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::ProcessWillChangeViewHierarchy(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessWillChangeViewHierarchy(
+ view, new_parent, old_parent, IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::ProcessViewHierarchyChanged(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessViewHierarchyChanged(
+ view, new_parent, old_parent, IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::ProcessViewReorder(
+ const ServerView* view,
+ const ServerView* relative_view,
+ const mojo::OrderDirection direction) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessViewReorder(view, relative_view, direction,
+ IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::ProcessViewDeleted(const ViewId& view) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessViewDeleted(view,
+ IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::PrepareForChange(ScopedChange* change) {
+ // Should only ever have one change in flight.
+ CHECK(!current_change_);
+ current_change_ = change;
+}
+
+void ConnectionManager::FinishChange() {
+ // PrepareForChange/FinishChange should be balanced.
+ CHECK(current_change_);
+ current_change_ = NULL;
+}
+
+void ConnectionManager::DoAnimation() {
+ if (!DecrementAnimatingViewsOpacity(root()))
+ animation_timer_.Stop();
+}
+
+void ConnectionManager::AddConnection(ClientConnection* connection) {
+ DCHECK_EQ(0u, connection_map_.count(connection->service()->id()));
+ connection_map_[connection->service()->id()] = connection;
+}
+
+void ConnectionManager::OnWillDestroyView(ServerView* view) {
+ if (!in_destructor_ && root_->Contains(view) && view != root_.get() &&
+ view->id() != ClonedViewId()) {
+ // We're about to destroy a view. Any cloned views need to be reparented
+ // else the animation would no longer be visible. By moving to a visible
+ // view, view->parent(), we ensure the animation is still visible.
+ ServerView* parent_above = view;
+ ReparentClonedViews(view->parent(), &parent_above, view);
+ }
+
+ animation_runner_.CancelAnimationForView(view);
+}
+
+void ConnectionManager::OnViewDestroyed(const ServerView* view) {
+ if (!in_destructor_)
+ ProcessViewDeleted(view->id());
+}
+
+void ConnectionManager::OnWillChangeViewHierarchy(ServerView* view,
+ ServerView* new_parent,
+ ServerView* old_parent) {
+ if (view->id() == ClonedViewId() || in_destructor_)
+ return;
+
+ if (root_->Contains(view) && view != root_.get()) {
+ // We're about to reparent a view. Any cloned views need to be reparented
+ // else the animation may be effected in unusual ways. For example, the view
+ // could move to a new location such that the animation is entirely clipped.
+ // By moving to view->parent() we ensure the animation is still visible.
+ ServerView* parent_above = view;
+ ReparentClonedViews(view->parent(), &parent_above, view);
+ }
+
+ ProcessWillChangeViewHierarchy(view, new_parent, old_parent);
+
+ animation_runner_.CancelAnimationForView(view);
+}
+
+void ConnectionManager::OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+ if (in_destructor_)
+ return;
+
+ ProcessViewHierarchyChanged(view, new_parent, old_parent);
+
+ // TODO(beng): optimize.
+ if (old_parent) {
+ display_manager_->SchedulePaint(old_parent,
+ gfx::Rect(old_parent->bounds().size()));
+ }
+ if (new_parent) {
+ display_manager_->SchedulePaint(new_parent,
+ gfx::Rect(new_parent->bounds().size()));
+ }
+}
+
+void ConnectionManager::OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ if (in_destructor_)
+ return;
+
+ ProcessViewBoundsChanged(view, old_bounds, new_bounds);
+ if (!view->parent())
+ return;
+
+ // TODO(sky): optimize this.
+ display_manager_->SchedulePaint(view->parent(), old_bounds);
+ display_manager_->SchedulePaint(view->parent(), new_bounds);
+}
+
+void ConnectionManager::OnViewSurfaceIdChanged(const ServerView* view) {
+ if (!in_destructor_)
+ display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size()));
+}
+
+void ConnectionManager::OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ mojo::OrderDirection direction) {
+ if (!in_destructor_)
+ display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size()));
+}
+
+void ConnectionManager::OnWillChangeViewVisibility(ServerView* view) {
+ if (in_destructor_)
+ return;
+
+ // Need to repaint if the view was drawn (which means it'll in the process of
+ // hiding) or the view is transitioning to drawn.
+ if (view->IsDrawn(root_.get()) || (!view->visible() && view->parent() &&
+ view->parent()->IsDrawn(root_.get()))) {
+ display_manager_->SchedulePaint(view->parent(), view->bounds());
+ }
+
+ if (view != root_.get() && view->id() != ClonedViewId() &&
+ root_->Contains(view) && view->IsDrawn(root_.get())) {
+ // We're about to hide |view|, this would implicitly make any cloned views
+ // hide to. Reparent so that animations are still visible.
+ ServerView* parent_above = view;
+ ReparentClonedViews(view->parent(), &parent_above, view);
+ }
+
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessWillChangeViewVisibility(
+ view, IsChangeSource(pair.first));
+ }
+
+ const bool is_parent_drawn =
+ view->parent() && view->parent()->IsDrawn(root_.get());
+ if (!is_parent_drawn || !view->visible())
+ animation_runner_.CancelAnimationForView(view);
+}
+
+void ConnectionManager::OnViewSharedPropertyChanged(
+ const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data) {
+ for (auto& pair : connection_map_) {
+ pair.second->service()->ProcessViewPropertyChanged(
+ view, name, new_data, IsChangeSource(pair.first));
+ }
+}
+
+void ConnectionManager::OnScheduleViewPaint(const ServerView* view) {
+ if (!in_destructor_)
+ display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size()));
+}
+
+void ConnectionManager::DispatchInputEventToView(mojo::Id transport_view_id,
+ mojo::EventPtr event) {
+ const ViewId view_id(ViewIdFromTransportId(transport_view_id));
+
+ ViewManagerServiceImpl* connection = GetConnectionWithRoot(view_id);
+ if (!connection)
+ connection = GetConnection(view_id.connection_id);
+ if (connection) {
+ connection->client()->OnViewInputEvent(
+ transport_view_id, event.Pass(), base::Bind(&base::DoNothing));
+ }
+}
+
+void ConnectionManager::SetViewportSize(mojo::SizePtr size) {
+ gfx::Size new_size = size.To<gfx::Size>();
+ display_manager_->SetViewportSize(new_size);
+}
+
+void ConnectionManager::CloneAndAnimate(mojo::Id transport_view_id) {
+ CloneAndAnimate(ViewIdFromTransportId(transport_view_id));
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/connection_manager.h b/mojo/services/view_manager/connection_manager.h
new file mode 100644
index 0000000..73f74a0
--- /dev/null
+++ b/mojo/services/view_manager/connection_manager.h
@@ -0,0 +1,241 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_
+#define SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/services/view_manager/animation_runner.h"
+#include "mojo/services/view_manager/ids.h"
+#include "mojo/services/view_manager/server_view_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h"
+
+namespace view_manager {
+
+class ClientConnection;
+class ConnectionManagerDelegate;
+class DisplayManager;
+class ServerView;
+class ViewManagerServiceImpl;
+
+// ConnectionManager manages the set of connections to the ViewManager (all the
+// ViewManagerServiceImpls) as well as providing the root of the hierarchy.
+class ConnectionManager : public ServerViewDelegate,
+ public mojo::WindowManagerInternalClient {
+ public:
+ // Create when a ViewManagerServiceImpl is about to make a change. Ensures
+ // clients are notified correctly.
+ class ScopedChange {
+ public:
+ ScopedChange(ViewManagerServiceImpl* connection,
+ ConnectionManager* connection_manager,
+ bool is_delete_view);
+ ~ScopedChange();
+
+ mojo::ConnectionSpecificId connection_id() const { return connection_id_; }
+ bool is_delete_view() const { return is_delete_view_; }
+
+ // Marks the connection with the specified id as having seen a message.
+ void MarkConnectionAsMessaged(mojo::ConnectionSpecificId connection_id) {
+ message_ids_.insert(connection_id);
+ }
+
+ // Returns true if MarkConnectionAsMessaged(connection_id) was invoked.
+ bool DidMessageConnection(mojo::ConnectionSpecificId connection_id) const {
+ return message_ids_.count(connection_id) > 0;
+ }
+
+ private:
+ ConnectionManager* connection_manager_;
+ const mojo::ConnectionSpecificId connection_id_;
+ const bool is_delete_view_;
+
+ // See description of MarkConnectionAsMessaged/DidMessageConnection.
+ std::set<mojo::ConnectionSpecificId> message_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedChange);
+ };
+
+ ConnectionManager(ConnectionManagerDelegate* delegate,
+ scoped_ptr<DisplayManager> display_manager,
+ mojo::WindowManagerInternal* wm_internal);
+ ~ConnectionManager() override;
+
+ // Returns the id for the next ViewManagerServiceImpl.
+ mojo::ConnectionSpecificId GetAndAdvanceNextConnectionId();
+
+ // Invoked when a ViewManagerServiceImpl's connection encounters an error.
+ void OnConnectionError(ClientConnection* connection);
+
+ // See description of ViewManagerService::Embed() for details. This assumes
+ // |transport_view_id| is valid.
+ void EmbedAtView(mojo::ConnectionSpecificId creator_id,
+ const std::string& url,
+ const ViewId& view_id,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services);
+ void EmbedAtView(mojo::ConnectionSpecificId creator_id,
+ const ViewId& view_id,
+ mojo::ViewManagerClientPtr client);
+
+ // Returns the connection by id.
+ ViewManagerServiceImpl* GetConnection(
+ mojo::ConnectionSpecificId connection_id);
+
+ // Returns the View identified by |id|.
+ ServerView* GetView(const ViewId& id);
+
+ ServerView* root() { return root_.get(); }
+ DisplayManager* display_manager() { return display_manager_.get(); }
+
+ bool IsProcessingChange() const { return current_change_ != NULL; }
+
+ bool is_processing_delete_view() const {
+ return current_change_ && current_change_->is_delete_view();
+ }
+
+ // Invoked when a connection messages a client about the change. This is used
+ // to avoid sending ServerChangeIdAdvanced() unnecessarily.
+ void OnConnectionMessagedClient(mojo::ConnectionSpecificId id);
+
+ // Returns true if OnConnectionMessagedClient() was invoked for id.
+ bool DidConnectionMessageClient(mojo::ConnectionSpecificId id) const;
+
+ // Returns the ViewManagerServiceImpl that has |id| as a root.
+ ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) {
+ return const_cast<ViewManagerServiceImpl*>(
+ const_cast<const ConnectionManager*>(this)->GetConnectionWithRoot(id));
+ }
+ const ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) const;
+
+ mojo::WindowManagerInternal* wm_internal() { return wm_internal_; }
+
+ void SetWindowManagerClientConnection(
+ scoped_ptr<ClientConnection> connection);
+ bool has_window_manager_client_connection() const {
+ return window_manager_client_connection_ != nullptr;
+ }
+
+ mojo::ViewManagerClient* GetWindowManagerViewManagerClient();
+
+ // WindowManagerInternalClient implementation helper; see mojom for details.
+ bool CloneAndAnimate(const ViewId& view_id);
+
+ // These functions trivially delegate to all ViewManagerServiceImpls, which in
+ // term notify their clients.
+ void ProcessViewDestroyed(ServerView* view);
+ void ProcessViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds);
+ void ProcessViewportMetricsChanged(const mojo::ViewportMetrics& old_metrics,
+ const mojo::ViewportMetrics& new_metrics);
+ void ProcessWillChangeViewHierarchy(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent);
+ void ProcessViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent);
+ void ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ const mojo::OrderDirection direction);
+ void ProcessViewDeleted(const ViewId& view);
+
+ private:
+ typedef std::map<mojo::ConnectionSpecificId, ClientConnection*> ConnectionMap;
+
+ // Invoked when a connection is about to make a change. Subsequently followed
+ // by FinishChange() once the change is done.
+ //
+ // Changes should never nest, meaning each PrepareForChange() must be
+ // balanced with a call to FinishChange() with no PrepareForChange()
+ // in between.
+ void PrepareForChange(ScopedChange* change);
+
+ // Balances a call to PrepareForChange().
+ void FinishChange();
+
+ // Returns true if the specified connection originated the current change.
+ bool IsChangeSource(mojo::ConnectionSpecificId connection_id) const {
+ return current_change_ && current_change_->connection_id() == connection_id;
+ }
+
+ // Adds |connection| to internal maps.
+ void AddConnection(ClientConnection* connection);
+
+ // Callback from animation timer.
+ // TODO(sky): make this real (move to a different class).
+ void DoAnimation();
+
+ // Overridden from ServerViewDelegate:
+ void OnWillDestroyView(ServerView* view) override;
+ void OnViewDestroyed(const ServerView* view) override;
+ void OnWillChangeViewHierarchy(ServerView* view,
+ ServerView* new_parent,
+ ServerView* old_parent) override;
+ void OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) override;
+ void OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+ void OnViewSurfaceIdChanged(const ServerView* view) override;
+ void OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ mojo::OrderDirection direction) override;
+ void OnWillChangeViewVisibility(ServerView* view) override;
+ void OnViewSharedPropertyChanged(
+ const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data) override;
+ void OnScheduleViewPaint(const ServerView* view) override;
+
+ // WindowManagerInternalClient:
+ void DispatchInputEventToView(mojo::Id transport_view_id,
+ mojo::EventPtr event) override;
+ void SetViewportSize(mojo::SizePtr size) override;
+ void CloneAndAnimate(mojo::Id transport_view_id) override;
+
+ ConnectionManagerDelegate* delegate_;
+
+ // The ClientConnection containing the ViewManagerService implementation
+ // provided to the initial connection (the WindowManager).
+ // NOTE: |window_manager_client_connection_| is also in |connection_map_|.
+ ClientConnection* window_manager_client_connection_;
+
+ // ID to use for next ViewManagerServiceImpl.
+ mojo::ConnectionSpecificId next_connection_id_;
+
+ // Set of ViewManagerServiceImpls.
+ ConnectionMap connection_map_;
+
+ scoped_ptr<DisplayManager> display_manager_;
+
+ scoped_ptr<ServerView> root_;
+
+ mojo::WindowManagerInternal* wm_internal_;
+
+ // If non-null we're processing a change. The ScopedChange is not owned by us
+ // (it's created on the stack by ViewManagerServiceImpl).
+ ScopedChange* current_change_;
+
+ bool in_destructor_;
+
+ // TODO(sky): nuke! Just a proof of concept until get real animation api.
+ base::RepeatingTimer<ConnectionManager> animation_timer_;
+
+ AnimationRunner animation_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionManager);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_
diff --git a/mojo/services/view_manager/connection_manager_delegate.h b/mojo/services/view_manager/connection_manager_delegate.h
new file mode 100644
index 0000000..22504f2
--- /dev/null
+++ b/mojo/services/view_manager/connection_manager_delegate.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_
+#define SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_
+
+#include <string>
+
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+
+namespace mojo {
+class ViewManagerService;
+}
+
+namespace view_manager {
+
+class ClientConnection;
+class ConnectionManager;
+struct ViewId;
+
+class ConnectionManagerDelegate {
+ public:
+ virtual void OnLostConnectionToWindowManager() = 0;
+
+ // Creates a ClientConnection in response to Embed() calls on the
+ // ConnectionManager.
+ virtual ClientConnection* CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id) = 0;
+ virtual ClientConnection* CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const ViewId& root_id,
+ mojo::ViewManagerClientPtr view_manager_client) = 0;
+
+ protected:
+ virtual ~ConnectionManagerDelegate() {}
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_
diff --git a/mojo/services/view_manager/default_access_policy.cc b/mojo/services/view_manager/default_access_policy.cc
new file mode 100644
index 0000000..64da06d
--- /dev/null
+++ b/mojo/services/view_manager/default_access_policy.cc
@@ -0,0 +1,112 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/default_access_policy.h"
+
+#include "mojo/services/view_manager/access_policy_delegate.h"
+#include "mojo/services/view_manager/server_view.h"
+
+namespace view_manager {
+
+DefaultAccessPolicy::DefaultAccessPolicy(
+ mojo::ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate)
+ : connection_id_(connection_id), delegate_(delegate) {
+}
+
+DefaultAccessPolicy::~DefaultAccessPolicy() {
+}
+
+bool DefaultAccessPolicy::CanRemoveViewFromParent(
+ const ServerView* view) const {
+ if (!WasCreatedByThisConnection(view))
+ return false; // Can only unparent views we created.
+
+ return delegate_->IsRootForAccessPolicy(view->parent()->id()) ||
+ WasCreatedByThisConnection(view->parent());
+}
+
+bool DefaultAccessPolicy::CanAddView(const ServerView* parent,
+ const ServerView* child) const {
+ return WasCreatedByThisConnection(child) &&
+ (delegate_->IsRootForAccessPolicy(parent->id()) ||
+ (WasCreatedByThisConnection(parent) &&
+ !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(parent)));
+}
+
+bool DefaultAccessPolicy::CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction) const {
+ return WasCreatedByThisConnection(view) &&
+ WasCreatedByThisConnection(relative_view);
+}
+
+bool DefaultAccessPolicy::CanDeleteView(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::CanGetViewTree(const ServerView* view) const {
+ return WasCreatedByThisConnection(view) ||
+ delegate_->IsRootForAccessPolicy(view->id());
+}
+
+bool DefaultAccessPolicy::CanDescendIntoViewForViewTree(
+ const ServerView* view) const {
+ return (WasCreatedByThisConnection(view) &&
+ !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) ||
+ delegate_->IsRootForAccessPolicy(view->id());
+}
+
+bool DefaultAccessPolicy::CanEmbed(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::CanChangeViewVisibility(
+ const ServerView* view) const {
+ return WasCreatedByThisConnection(view) ||
+ delegate_->IsRootForAccessPolicy(view->id());
+}
+
+bool DefaultAccessPolicy::CanSetViewSurfaceId(const ServerView* view) const {
+ // Once a view embeds another app, the embedder app is no longer able to
+ // call SetViewSurfaceId() - this ability is transferred to the embedded app.
+ if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view))
+ return false;
+ return WasCreatedByThisConnection(view) ||
+ delegate_->IsRootForAccessPolicy(view->id());
+}
+
+bool DefaultAccessPolicy::CanSetViewBounds(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::CanSetViewProperties(const ServerView* view) const {
+ return WasCreatedByThisConnection(view);
+}
+
+bool DefaultAccessPolicy::ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const {
+ if (!WasCreatedByThisConnection(view))
+ return false;
+
+ if (*new_parent && !WasCreatedByThisConnection(*new_parent) &&
+ !delegate_->IsRootForAccessPolicy((*new_parent)->id())) {
+ *new_parent = NULL;
+ }
+
+ if (*old_parent && !WasCreatedByThisConnection(*old_parent) &&
+ !delegate_->IsRootForAccessPolicy((*old_parent)->id())) {
+ *old_parent = NULL;
+ }
+ return true;
+}
+
+bool DefaultAccessPolicy::WasCreatedByThisConnection(
+ const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/default_access_policy.h b/mojo/services/view_manager/default_access_policy.h
new file mode 100644
index 0000000..92ab4f8
--- /dev/null
+++ b/mojo/services/view_manager/default_access_policy.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_
+#define SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_
+
+#include "base/basictypes.h"
+#include "mojo/services/view_manager/access_policy.h"
+
+namespace view_manager {
+
+class AccessPolicyDelegate;
+
+// AccessPolicy for all connections, except the window manager.
+class DefaultAccessPolicy : public AccessPolicy {
+ public:
+ DefaultAccessPolicy(mojo::ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate);
+ ~DefaultAccessPolicy() override;
+
+ // AccessPolicy:
+ bool CanRemoveViewFromParent(const ServerView* view) const override;
+ bool CanAddView(const ServerView* parent,
+ const ServerView* child) const override;
+ bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction) const override;
+ bool CanDeleteView(const ServerView* view) const override;
+ bool CanGetViewTree(const ServerView* view) const override;
+ bool CanDescendIntoViewForViewTree(const ServerView* view) const override;
+ bool CanEmbed(const ServerView* view) const override;
+ bool CanChangeViewVisibility(const ServerView* view) const override;
+ bool CanSetViewSurfaceId(const ServerView* view) const override;
+ bool CanSetViewBounds(const ServerView* view) const override;
+ bool CanSetViewProperties(const ServerView* view) const override;
+ bool ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const override;
+
+ private:
+ bool WasCreatedByThisConnection(const ServerView* view) const;
+
+ const mojo::ConnectionSpecificId connection_id_;
+ AccessPolicyDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultAccessPolicy);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_
diff --git a/mojo/services/view_manager/display_manager.cc b/mojo/services/view_manager/display_manager.cc
new file mode 100644
index 0000000..48422cf
--- /dev/null
+++ b/mojo/services/view_manager/display_manager.cc
@@ -0,0 +1,187 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/display_manager.h"
+
+#include "base/numerics/safe_conversions.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/surfaces/surfaces_type_converters.h"
+#include "mojo/converters/transform/transform_type_converters.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/view_coordinate_conversions.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h"
+#include "third_party/mojo_services/src/surfaces/public/cpp/surfaces_utils.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/quads.mojom.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h"
+
+using mojo::Rect;
+using mojo::Size;
+
+namespace view_manager {
+namespace {
+
+void DrawViewTree(mojo::Pass* pass,
+ const ServerView* view,
+ const gfx::Vector2d& parent_to_root_origin_offset,
+ float opacity) {
+ if (!view->visible())
+ return;
+
+ const gfx::Rect absolute_bounds =
+ view->bounds() + parent_to_root_origin_offset;
+ std::vector<const ServerView*> children(view->GetChildren());
+ const float combined_opacity = opacity * view->opacity();
+ for (std::vector<const ServerView*>::reverse_iterator it = children.rbegin();
+ it != children.rend();
+ ++it) {
+ DrawViewTree(pass, *it, absolute_bounds.OffsetFromOrigin(),
+ combined_opacity);
+ }
+
+ cc::SurfaceId node_id = view->surface_id();
+
+ auto surface_quad_state = mojo::SurfaceQuadState::New();
+ surface_quad_state->surface = mojo::SurfaceId::From(node_id);
+
+ gfx::Transform node_transform;
+ node_transform.Translate(absolute_bounds.x(), absolute_bounds.y());
+
+ const gfx::Rect bounds_at_origin(view->bounds().size());
+ auto surface_quad = mojo::Quad::New();
+ surface_quad->material = mojo::Material::MATERIAL_SURFACE_CONTENT;
+ surface_quad->rect = Rect::From(bounds_at_origin);
+ surface_quad->opaque_rect = Rect::From(bounds_at_origin);
+ surface_quad->visible_rect = Rect::From(bounds_at_origin);
+ surface_quad->needs_blending = true;
+ surface_quad->shared_quad_state_index =
+ base::saturated_cast<int32_t>(pass->shared_quad_states.size());
+ surface_quad->surface_quad_state = surface_quad_state.Pass();
+
+ auto sqs = CreateDefaultSQS(*Size::From(view->bounds().size()));
+ sqs->blend_mode = mojo::SK_XFERMODE_kSrcOver_Mode;
+ sqs->opacity = combined_opacity;
+ sqs->content_to_target_transform = mojo::Transform::From(node_transform);
+
+ pass->quads.push_back(surface_quad.Pass());
+ pass->shared_quad_states.push_back(sqs.Pass());
+}
+
+} // namespace
+
+DefaultDisplayManager::DefaultDisplayManager(
+ mojo::ApplicationImpl* app_impl,
+ mojo::ApplicationConnection* app_connection,
+ const mojo::Callback<void()>& native_viewport_closed_callback)
+ : app_impl_(app_impl),
+ app_connection_(app_connection),
+ connection_manager_(nullptr),
+ draw_timer_(false, false),
+ frame_pending_(false),
+ native_viewport_closed_callback_(native_viewport_closed_callback),
+ weak_factory_(this) {
+ metrics_.size = mojo::Size::New();
+ metrics_.size->width = 800;
+ metrics_.size->height = 600;
+}
+
+void DefaultDisplayManager::Init(ConnectionManager* connection_manager) {
+ connection_manager_ = connection_manager;
+ app_impl_->ConnectToService("mojo:native_viewport_service",
+ &native_viewport_);
+ native_viewport_.set_error_handler(this);
+ native_viewport_->Create(metrics_.size->Clone(),
+ base::Bind(&DefaultDisplayManager::OnMetricsChanged,
+ weak_factory_.GetWeakPtr()));
+ native_viewport_->Show();
+
+ mojo::ContextProviderPtr context_provider;
+ native_viewport_->GetContextProvider(GetProxy(&context_provider));
+ mojo::DisplayFactoryPtr display_factory;
+ app_impl_->ConnectToService("mojo:surfaces_service", &display_factory);
+ display_factory->Create(context_provider.Pass(),
+ nullptr, // returner - we never submit resources.
+ GetProxy(&display_));
+
+ mojo::NativeViewportEventDispatcherPtr event_dispatcher;
+ app_connection_->ConnectToService(&event_dispatcher);
+ native_viewport_->SetEventDispatcher(event_dispatcher.Pass());
+}
+
+DefaultDisplayManager::~DefaultDisplayManager() {
+}
+
+void DefaultDisplayManager::SchedulePaint(const ServerView* view,
+ const gfx::Rect& bounds) {
+ if (!view->IsDrawn(connection_manager_->root()))
+ return;
+ const gfx::Rect root_relative_rect =
+ ConvertRectBetweenViews(view, connection_manager_->root(), bounds);
+ if (root_relative_rect.IsEmpty())
+ return;
+ dirty_rect_.Union(root_relative_rect);
+ WantToDraw();
+}
+
+void DefaultDisplayManager::SetViewportSize(const gfx::Size& size) {
+ native_viewport_->SetSize(Size::From(size));
+}
+
+const mojo::ViewportMetrics& DefaultDisplayManager::GetViewportMetrics() {
+ return metrics_;
+}
+
+void DefaultDisplayManager::Draw() {
+ Rect rect;
+ rect.width = metrics_.size->width;
+ rect.height = metrics_.size->height;
+ auto pass = CreateDefaultPass(1, rect);
+ pass->damage_rect = Rect::From(dirty_rect_);
+
+ DrawViewTree(pass.get(), connection_manager_->root(), gfx::Vector2d(), 1.0f);
+
+ auto frame = mojo::Frame::New();
+ frame->passes.push_back(pass.Pass());
+ frame->resources.resize(0u);
+ frame_pending_ = true;
+ display_->SubmitFrame(
+ frame.Pass(),
+ base::Bind(&DefaultDisplayManager::DidDraw, base::Unretained(this)));
+ dirty_rect_ = gfx::Rect();
+}
+
+void DefaultDisplayManager::DidDraw() {
+ frame_pending_ = false;
+ if (!dirty_rect_.IsEmpty())
+ WantToDraw();
+}
+
+void DefaultDisplayManager::WantToDraw() {
+ if (draw_timer_.IsRunning() || frame_pending_)
+ return;
+
+ draw_timer_.Start(
+ FROM_HERE, base::TimeDelta(),
+ base::Bind(&DefaultDisplayManager::Draw, base::Unretained(this)));
+}
+
+void DefaultDisplayManager::OnMetricsChanged(mojo::ViewportMetricsPtr metrics) {
+ metrics_.size = metrics->size.Clone();
+ metrics_.device_pixel_ratio = metrics->device_pixel_ratio;
+ gfx::Rect bounds(metrics_.size.To<gfx::Size>());
+ connection_manager_->root()->SetBounds(bounds);
+ connection_manager_->ProcessViewportMetricsChanged(metrics_, *metrics);
+ native_viewport_->RequestMetrics(base::Bind(
+ &DefaultDisplayManager::OnMetricsChanged, weak_factory_.GetWeakPtr()));
+}
+
+void DefaultDisplayManager::OnConnectionError() {
+ // This is called when the native_viewport is torn down before
+ // ~DefaultDisplayManager may be called.
+ native_viewport_closed_callback_.Run();
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/display_manager.h b/mojo/services/view_manager/display_manager.h
new file mode 100644
index 0000000..41f1978
--- /dev/null
+++ b/mojo/services/view_manager/display_manager.h
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_
+#define SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace cc {
+class SurfaceIdAllocator;
+}
+
+namespace mojo {
+class ApplicationConnection;
+class ApplicationImpl;
+}
+
+namespace view_manager {
+
+class ConnectionManager;
+class ServerView;
+
+// DisplayManager is used to connect the root ServerView to a display.
+class DisplayManager {
+ public:
+ virtual ~DisplayManager() {}
+
+ virtual void Init(ConnectionManager* connection_manager) = 0;
+
+ // Schedules a paint for the specified region in the coordinates of |view|.
+ virtual void SchedulePaint(const ServerView* view,
+ const gfx::Rect& bounds) = 0;
+
+ virtual void SetViewportSize(const gfx::Size& size) = 0;
+
+ virtual const mojo::ViewportMetrics& GetViewportMetrics() = 0;
+};
+
+// DisplayManager implementation that connects to the services necessary to
+// actually display.
+class DefaultDisplayManager : public DisplayManager,
+ public mojo::ErrorHandler {
+ public:
+ DefaultDisplayManager(
+ mojo::ApplicationImpl* app_impl,
+ mojo::ApplicationConnection* app_connection,
+ const mojo::Callback<void()>& native_viewport_closed_callback);
+ ~DefaultDisplayManager() override;
+
+ // DisplayManager:
+ void Init(ConnectionManager* connection_manager) override;
+ void SchedulePaint(const ServerView* view, const gfx::Rect& bounds) override;
+ void SetViewportSize(const gfx::Size& size) override;
+ const mojo::ViewportMetrics& GetViewportMetrics() override;
+
+ private:
+ void WantToDraw();
+ void Draw();
+ void DidDraw();
+
+ void OnMetricsChanged(mojo::ViewportMetricsPtr metrics);
+
+ // ErrorHandler:
+ void OnConnectionError() override;
+
+ mojo::ApplicationImpl* app_impl_;
+ mojo::ApplicationConnection* app_connection_;
+ ConnectionManager* connection_manager_;
+
+ mojo::ViewportMetrics metrics_;
+ gfx::Rect dirty_rect_;
+ base::Timer draw_timer_;
+ bool frame_pending_;
+
+ mojo::DisplayPtr display_;
+ mojo::NativeViewportPtr native_viewport_;
+ mojo::Callback<void()> native_viewport_closed_callback_;
+ base::WeakPtrFactory<DefaultDisplayManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultDisplayManager);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_
diff --git a/mojo/services/view_manager/ids.h b/mojo/services/view_manager/ids.h
new file mode 100644
index 0000000..90c3163
--- /dev/null
+++ b/mojo/services/view_manager/ids.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 SERVICES_VIEW_MANAGER_IDS_H_
+#define SERVICES_VIEW_MANAGER_IDS_H_
+
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/util.h"
+
+namespace view_manager {
+
+// Connection id is used to indicate no connection. That is, no
+// ViewManagerServiceImpl ever gets this id.
+const mojo::ConnectionSpecificId kInvalidConnectionId = 0;
+
+// Adds a bit of type safety to view ids.
+struct ViewId {
+ ViewId(mojo::ConnectionSpecificId connection_id,
+ mojo::ConnectionSpecificId view_id)
+ : connection_id(connection_id), view_id(view_id) {}
+ ViewId() : connection_id(0), view_id(0) {}
+
+ bool operator==(const ViewId& other) const {
+ return other.connection_id == connection_id &&
+ other.view_id == view_id;
+ }
+
+ bool operator!=(const ViewId& other) const {
+ return !(*this == other);
+ }
+
+ mojo::ConnectionSpecificId connection_id;
+ mojo::ConnectionSpecificId view_id;
+};
+
+inline ViewId ViewIdFromTransportId(mojo::Id id) {
+ return ViewId(mojo::HiWord(id), mojo::LoWord(id));
+}
+
+inline mojo::Id ViewIdToTransportId(const ViewId& id) {
+ return (id.connection_id << 16) | id.view_id;
+}
+
+inline ViewId RootViewId() {
+ return ViewId(kInvalidConnectionId, 1);
+}
+
+// Returns a ViewId that is reserved to indicate no view. That is, no view will
+// ever be created with this id.
+inline ViewId InvalidViewId() {
+ return ViewId(kInvalidConnectionId, 0);
+}
+
+// All cloned views use this id.
+inline ViewId ClonedViewId() {
+ return ViewId(kInvalidConnectionId, 2);
+}
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_IDS_H_
diff --git a/mojo/services/view_manager/main.cc b/mojo/services/view_manager/main.cc
new file mode 100644
index 0000000..d3ced3d
--- /dev/null
+++ b/mojo/services/view_manager/main.cc
@@ -0,0 +1,12 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/services/view_manager/view_manager_app.h"
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(new view_manager::ViewManagerApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/view_manager/scheduled_animation_group.cc b/mojo/services/view_manager/scheduled_animation_group.cc
new file mode 100644
index 0000000..0f27c42
--- /dev/null
+++ b/mojo/services/view_manager/scheduled_animation_group.cc
@@ -0,0 +1,352 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/scheduled_animation_group.h"
+
+#include <set>
+
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/transform/transform_type_converters.h"
+#include "mojo/services/view_manager/server_view.h"
+
+using mojo::ANIMATION_PROPERTY_NONE;
+using mojo::ANIMATION_PROPERTY_OPACITY;
+using mojo::ANIMATION_PROPERTY_TRANSFORM;
+using mojo::AnimationProperty;
+
+namespace view_manager {
+namespace {
+
+using Sequences = std::vector<ScheduledAnimationSequence>;
+
+// Gets the value of |property| from |view| into |value|.
+void GetValueFromView(const ServerView* view,
+ AnimationProperty property,
+ ScheduledAnimationValue* value) {
+ switch (property) {
+ case ANIMATION_PROPERTY_NONE:
+ NOTREACHED();
+ break;
+ case ANIMATION_PROPERTY_OPACITY:
+ value->float_value = view->opacity();
+ break;
+ case ANIMATION_PROPERTY_TRANSFORM:
+ value->transform = view->transform();
+ break;
+ }
+}
+
+// Sets the value of |property| from |value| into |view|.
+void SetViewPropertyFromValue(ServerView* view,
+ AnimationProperty property,
+ const ScheduledAnimationValue& value) {
+ switch (property) {
+ case ANIMATION_PROPERTY_NONE:
+ break;
+ case ANIMATION_PROPERTY_OPACITY:
+ view->SetOpacity(value.float_value);
+ break;
+ case ANIMATION_PROPERTY_TRANSFORM:
+ view->SetTransform(value.transform);
+ break;
+ }
+}
+
+// Sets the value of |property| into |view| between two points.
+void SetViewPropertyFromValueBetween(ServerView* view,
+ AnimationProperty property,
+ double value,
+ gfx::Tween::Type tween_type,
+ const ScheduledAnimationValue& start,
+ const ScheduledAnimationValue& target) {
+ const double tween_value = gfx::Tween::CalculateValue(tween_type, value);
+ switch (property) {
+ case ANIMATION_PROPERTY_NONE:
+ break;
+ case ANIMATION_PROPERTY_OPACITY:
+ view->SetOpacity(gfx::Tween::FloatValueBetween(
+ tween_value, start.float_value, target.float_value));
+ break;
+ case ANIMATION_PROPERTY_TRANSFORM:
+ view->SetTransform(gfx::Tween::TransformValueBetween(
+ tween_value, start.transform, target.transform));
+ break;
+ }
+}
+
+gfx::Tween::Type AnimationTypeToTweenType(mojo::AnimationTweenType type) {
+ switch (type) {
+ case mojo::ANIMATION_TWEEN_TYPE_LINEAR:
+ return gfx::Tween::LINEAR;
+ case mojo::ANIMATION_TWEEN_TYPE_EASE_IN:
+ return gfx::Tween::EASE_IN;
+ case mojo::ANIMATION_TWEEN_TYPE_EASE_OUT:
+ return gfx::Tween::EASE_OUT;
+ case mojo::ANIMATION_TWEEN_TYPE_EASE_IN_OUT:
+ return gfx::Tween::EASE_IN_OUT;
+ }
+ return gfx::Tween::LINEAR;
+}
+
+void ConvertToScheduledValue(const mojo::AnimationValue& transport_value,
+ ScheduledAnimationValue* value) {
+ value->float_value = transport_value.float_value;
+ value->transform = transport_value.transform.To<gfx::Transform>();
+}
+
+void ConvertToScheduledElement(const mojo::AnimationElement& transport_element,
+ ScheduledAnimationElement* element) {
+ element->property = transport_element.property;
+ element->duration =
+ base::TimeDelta::FromMicroseconds(transport_element.duration);
+ element->tween_type = AnimationTypeToTweenType(transport_element.tween_type);
+ if (transport_element.property != ANIMATION_PROPERTY_NONE) {
+ if (transport_element.start_value.get()) {
+ element->is_start_valid = true;
+ ConvertToScheduledValue(*transport_element.start_value,
+ &(element->start_value));
+ } else {
+ element->is_start_valid = false;
+ }
+ ConvertToScheduledValue(*transport_element.target_value,
+ &(element->target_value));
+ }
+}
+
+bool IsAnimationValueValid(AnimationProperty property,
+ const mojo::AnimationValue& value) {
+ switch (property) {
+ case ANIMATION_PROPERTY_NONE:
+ NOTREACHED();
+ return false;
+ case ANIMATION_PROPERTY_OPACITY:
+ return value.float_value >= 0.f && value.float_value <= 1.f;
+ case ANIMATION_PROPERTY_TRANSFORM:
+ return value.transform.get() && value.transform->matrix.size() == 16u;
+ }
+ return false;
+}
+
+bool IsAnimationElementValid(const mojo::AnimationElement& element) {
+ if (element.property == ANIMATION_PROPERTY_NONE)
+ return true; // None is a pause and doesn't need any values.
+ if (element.start_value.get() &&
+ !IsAnimationValueValid(element.property, *element.start_value))
+ return false;
+ // For all other properties we require a target.
+ return element.target_value.get() &&
+ IsAnimationValueValid(element.property, *element.target_value);
+}
+
+bool IsAnimationSequenceValid(const mojo::AnimationSequence& sequence) {
+ if (sequence.elements.size() == 0u)
+ return false;
+
+ for (size_t i = 0; i < sequence.elements.size(); ++i) {
+ if (!IsAnimationElementValid(*sequence.elements[i]))
+ return false;
+ }
+ return true;
+}
+
+bool IsAnimationGroupValid(const mojo::AnimationGroup& transport_group) {
+ if (transport_group.sequences.size() == 0u)
+ return false;
+ for (size_t i = 0; i < transport_group.sequences.size(); ++i) {
+ if (!IsAnimationSequenceValid(*transport_group.sequences[i]))
+ return false;
+ }
+ return true;
+}
+
+// If the start value for |element| isn't valid, the value for the property
+// is obtained from |view| and placed into |element|.
+void GetStartValueFromViewIfNecessary(const ServerView* view,
+ ScheduledAnimationElement* element) {
+ if (element->property != ANIMATION_PROPERTY_NONE &&
+ !element->is_start_valid) {
+ GetValueFromView(view, element->property, &(element->start_value));
+ }
+}
+
+void GetScheduledAnimationProperties(const Sequences& sequences,
+ std::set<AnimationProperty>* properties) {
+ for (const ScheduledAnimationSequence& sequence : sequences) {
+ for (const ScheduledAnimationElement& element : sequence.elements)
+ properties->insert(element.property);
+ }
+}
+
+void SetPropertyToTargetProperty(ServerView* view,
+ mojo::AnimationProperty property,
+ const Sequences& sequences) {
+ // NOTE: this doesn't deal with |cycle_count| quite right, but I'm honestly
+ // not sure we really want to support the same property in multiple sequences
+ // animating at once so I'm not dealing.
+ base::TimeDelta max_end_duration;
+ scoped_ptr<ScheduledAnimationValue> value;
+ for (const ScheduledAnimationSequence& sequence : sequences) {
+ base::TimeDelta duration;
+ for (const ScheduledAnimationElement& element : sequence.elements) {
+ if (element.property != property)
+ continue;
+
+ duration += element.duration;
+ if (duration > max_end_duration) {
+ max_end_duration = duration;
+ value.reset(new ScheduledAnimationValue(element.target_value));
+ }
+ }
+ }
+ if (value.get())
+ SetViewPropertyFromValue(view, property, *value);
+}
+
+void ConvertSequenceToScheduled(
+ const mojo::AnimationSequence& transport_sequence,
+ base::TimeTicks now,
+ ScheduledAnimationSequence* sequence) {
+ sequence->run_until_stopped = transport_sequence.cycle_count == 0u;
+ sequence->cycle_count = transport_sequence.cycle_count;
+ DCHECK_NE(0u, transport_sequence.elements.size());
+ sequence->elements.resize(transport_sequence.elements.size());
+
+ base::TimeTicks element_start_time = now;
+ for (size_t i = 0; i < transport_sequence.elements.size(); ++i) {
+ ConvertToScheduledElement(*(transport_sequence.elements[i].get()),
+ &(sequence->elements[i]));
+ sequence->elements[i].start_time = element_start_time;
+ sequence->duration += sequence->elements[i].duration;
+ element_start_time += sequence->elements[i].duration;
+ }
+}
+
+bool AdvanceSequence(ServerView* view,
+ ScheduledAnimationSequence* sequence,
+ base::TimeTicks now) {
+ ScheduledAnimationElement* element =
+ &(sequence->elements[sequence->current_index]);
+ while (element->start_time + element->duration < now) {
+ SetViewPropertyFromValue(view, element->property, element->target_value);
+ if (++sequence->current_index == sequence->elements.size()) {
+ if (!sequence->run_until_stopped && --sequence->cycle_count == 0) {
+ SetViewPropertyFromValue(view, element->property,
+ element->target_value);
+ return false;
+ }
+
+ sequence->current_index = 0;
+ }
+ sequence->elements[sequence->current_index].start_time =
+ element->start_time + element->duration;
+ element = &(sequence->elements[sequence->current_index]);
+ GetStartValueFromViewIfNecessary(view, element);
+
+ // It's possible for the delta between now and |last_tick_time_| to be very
+ // big (could happen if machine sleeps and is woken up much later). Normally
+ // the repeat count is smallish, so we don't bother optimizing it. OTOH if
+ // a sequence repeats forever we optimize it lest we get stuck in this loop
+ // for a very long time.
+ if (sequence->run_until_stopped && sequence->current_index == 0) {
+ element->start_time =
+ now - base::TimeDelta::FromMicroseconds(
+ (now - element->start_time).InMicroseconds() %
+ sequence->duration.InMicroseconds());
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+ScheduledAnimationValue::ScheduledAnimationValue() {
+}
+ScheduledAnimationValue::~ScheduledAnimationValue() {
+}
+
+ScheduledAnimationElement::ScheduledAnimationElement()
+ : property(ANIMATION_PROPERTY_OPACITY),
+ tween_type(gfx::Tween::EASE_IN),
+ is_start_valid(false) {
+}
+ScheduledAnimationElement::~ScheduledAnimationElement() {
+}
+
+ScheduledAnimationSequence::ScheduledAnimationSequence()
+ : run_until_stopped(false), cycle_count(0), current_index(0u) {
+}
+ScheduledAnimationSequence::~ScheduledAnimationSequence() {
+}
+
+ScheduledAnimationGroup::~ScheduledAnimationGroup() {
+}
+
+// static
+scoped_ptr<ScheduledAnimationGroup> ScheduledAnimationGroup::Create(
+ ServerView* view,
+ base::TimeTicks now,
+ uint32_t id,
+ const mojo::AnimationGroup& transport_group) {
+ if (!IsAnimationGroupValid(transport_group))
+ return nullptr;
+
+ scoped_ptr<ScheduledAnimationGroup> group(
+ new ScheduledAnimationGroup(view, id, now));
+ group->sequences_.resize(transport_group.sequences.size());
+ for (size_t i = 0; i < transport_group.sequences.size(); ++i) {
+ const mojo::AnimationSequence& transport_sequence(
+ *(transport_group.sequences[i]));
+ DCHECK_NE(0u, transport_sequence.elements.size());
+ ConvertSequenceToScheduled(transport_sequence, now, &group->sequences_[i]);
+ }
+ return group.Pass();
+}
+
+void ScheduledAnimationGroup::ObtainStartValues() {
+ for (ScheduledAnimationSequence& sequence : sequences_)
+ GetStartValueFromViewIfNecessary(view_, &(sequence.elements[0]));
+}
+
+void ScheduledAnimationGroup::SetValuesToTargetValuesForPropertiesNotIn(
+ const ScheduledAnimationGroup& other) {
+ std::set<AnimationProperty> our_properties;
+ GetScheduledAnimationProperties(sequences_, &our_properties);
+
+ std::set<AnimationProperty> other_properties;
+ GetScheduledAnimationProperties(other.sequences_, &other_properties);
+
+ for (AnimationProperty property : our_properties) {
+ if (other_properties.count(property) == 0 &&
+ property != ANIMATION_PROPERTY_NONE) {
+ SetPropertyToTargetProperty(view_, property, sequences_);
+ }
+ }
+}
+
+bool ScheduledAnimationGroup::Tick(base::TimeTicks time) {
+ for (Sequences::iterator i = sequences_.begin(); i != sequences_.end();) {
+ if (!AdvanceSequence(view_, &(*i), time)) {
+ i = sequences_.erase(i);
+ continue;
+ }
+ const ScheduledAnimationElement& active_element(
+ i->elements[i->current_index]);
+ const double percent =
+ (time - active_element.start_time).InMillisecondsF() /
+ active_element.duration.InMillisecondsF();
+ SetViewPropertyFromValueBetween(
+ view_, active_element.property, percent, active_element.tween_type,
+ active_element.start_value, active_element.target_value);
+ ++i;
+ }
+ return sequences_.empty();
+}
+
+ScheduledAnimationGroup::ScheduledAnimationGroup(ServerView* view,
+ uint32_t id,
+ base::TimeTicks time_scheduled)
+ : view_(view), id_(id), time_scheduled_(time_scheduled) {
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/scheduled_animation_group.h b/mojo/services/view_manager/scheduled_animation_group.h
new file mode 100644
index 0000000..63aa899
--- /dev/null
+++ b/mojo/services/view_manager/scheduled_animation_group.h
@@ -0,0 +1,110 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_
+#define SERVICES_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/animations.mojom.h"
+#include "ui/gfx/animation/tween.h"
+#include "ui/gfx/transform.h"
+
+namespace view_manager {
+
+class ServerView;
+
+struct ScheduledAnimationValue {
+ ScheduledAnimationValue();
+ ~ScheduledAnimationValue();
+
+ float float_value;
+ gfx::Transform transform;
+};
+
+struct ScheduledAnimationElement {
+ ScheduledAnimationElement();
+ ~ScheduledAnimationElement();
+
+ mojo::AnimationProperty property;
+ base::TimeDelta duration;
+ gfx::Tween::Type tween_type;
+ bool is_start_valid;
+ ScheduledAnimationValue start_value;
+ ScheduledAnimationValue target_value;
+ // Start time is based on scheduled time and relative to any other elements
+ // in the sequence.
+ base::TimeTicks start_time;
+};
+
+struct ScheduledAnimationSequence {
+ ScheduledAnimationSequence();
+ ~ScheduledAnimationSequence();
+
+ bool run_until_stopped;
+ std::vector<ScheduledAnimationElement> elements;
+
+ // Sum of the duration of all elements. This does not take into account
+ // |cycle_count|.
+ base::TimeDelta duration;
+
+ // The following values are updated as the animation progresses.
+
+ // Number of cycles remaining. This is only used if |run_until_stopped| is
+ // false.
+ uint32_t cycle_count;
+
+ // Index into |elements| of the element currently animating.
+ size_t current_index;
+};
+
+// Corresponds to a mojo::AnimationGroup and is responsible for running the
+// actual animation.
+class ScheduledAnimationGroup {
+ public:
+ ~ScheduledAnimationGroup();
+
+ // Returns a new ScheduledAnimationGroup from the supplied parameters, or
+ // null if |transport_group| isn't valid.
+ static scoped_ptr<ScheduledAnimationGroup> Create(
+ ServerView* view,
+ base::TimeTicks now,
+ uint32_t id,
+ const mojo::AnimationGroup& transport_group);
+
+ uint32_t id() const { return id_; }
+
+ // Gets the start value for any elements that don't have an explicit start.
+ // value.
+ void ObtainStartValues();
+
+ // Sets the values of any properties that are not in |other| to their final
+ // value.
+ void SetValuesToTargetValuesForPropertiesNotIn(
+ const ScheduledAnimationGroup& other);
+
+ // Advances the group. |time| is the current time. Returns true if the group
+ // is done (nothing left to animate).
+ bool Tick(base::TimeTicks time);
+
+ ServerView* view() { return view_; }
+
+ private:
+ ScheduledAnimationGroup(ServerView* view,
+ uint32_t id,
+ base::TimeTicks time_scheduled);
+
+ ServerView* view_;
+ const uint32_t id_;
+ base::TimeTicks time_scheduled_;
+ std::vector<ScheduledAnimationSequence> sequences_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScheduledAnimationGroup);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_
diff --git a/mojo/services/view_manager/scheduled_animation_group_unittest.cc b/mojo/services/view_manager/scheduled_animation_group_unittest.cc
new file mode 100644
index 0000000..bdef45a
--- /dev/null
+++ b/mojo/services/view_manager/scheduled_animation_group_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/scheduled_animation_group.h"
+
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/transform/transform_type_converters.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/test_server_view_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/animations.mojom.h"
+
+using mojo::ANIMATION_PROPERTY_NONE;
+using mojo::ANIMATION_PROPERTY_OPACITY;
+using mojo::ANIMATION_PROPERTY_TRANSFORM;
+using mojo::ANIMATION_TWEEN_TYPE_LINEAR;
+using mojo::AnimationGroup;
+using mojo::AnimationSequence;
+using mojo::AnimationElement;
+using mojo::AnimationValue;
+
+namespace view_manager {
+namespace {
+
+bool IsAnimationGroupValid(const AnimationGroup& transport_group) {
+ TestServerViewDelegate view_delegate;
+ ServerView view(&view_delegate, ViewId());
+ scoped_ptr<ScheduledAnimationGroup> group(ScheduledAnimationGroup::Create(
+ &view, base::TimeTicks::Now(), 1, transport_group));
+ return group.get() != nullptr;
+}
+
+} // namespace
+
+TEST(ScheduledAnimationGroupTest, IsAnimationGroupValid) {
+ AnimationGroup group;
+
+ // AnimationGroup with no sequences is not valid.
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ group.sequences.push_back(AnimationSequence::New());
+
+ // Sequence with no elements is not valid.
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ AnimationSequence& sequence = *(group.sequences[0]);
+ sequence.elements.push_back(AnimationElement::New());
+ AnimationElement& element = *(sequence.elements[0]);
+ element.property = ANIMATION_PROPERTY_OPACITY;
+ element.tween_type = ANIMATION_TWEEN_TYPE_LINEAR;
+
+ // Element with no target_value is not valid.
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ // Opacity must be between 0 and 1.
+ element.target_value = AnimationValue::New();
+ element.target_value->float_value = 2.5f;
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ element.target_value->float_value = .5f;
+ EXPECT_TRUE(IsAnimationGroupValid(group));
+
+ // Bogus start value.
+ element.start_value = AnimationValue::New();
+ element.start_value->float_value = 2.5f;
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ element.start_value->float_value = .5f;
+ EXPECT_TRUE(IsAnimationGroupValid(group));
+
+ // Bogus transform.
+ element.property = ANIMATION_PROPERTY_TRANSFORM;
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+ element.start_value->transform = mojo::Transform::From(gfx::Transform());
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+ element.target_value->transform = mojo::Transform::From(gfx::Transform());
+ EXPECT_TRUE(IsAnimationGroupValid(group));
+
+ // Add another empty sequence, should be invalid again.
+ group.sequences.push_back(AnimationSequence::New());
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ AnimationSequence& sequence2 = *(group.sequences[1]);
+ sequence2.elements.push_back(AnimationElement::New());
+ AnimationElement& element2 = *(sequence2.elements[0]);
+ element2.property = ANIMATION_PROPERTY_OPACITY;
+ element2.tween_type = ANIMATION_TWEEN_TYPE_LINEAR;
+
+ // Element with no target_value is not valid.
+ EXPECT_FALSE(IsAnimationGroupValid(group));
+
+ element2.property = ANIMATION_PROPERTY_NONE;
+ EXPECT_TRUE(IsAnimationGroupValid(group));
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/server_view.cc b/mojo/services/view_manager/server_view.cc
new file mode 100644
index 0000000..6459f9a
--- /dev/null
+++ b/mojo/services/view_manager/server_view.cc
@@ -0,0 +1,206 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/server_view.h"
+
+#include <inttypes.h>
+
+#include "base/strings/stringprintf.h"
+#include "mojo/services/view_manager/server_view_delegate.h"
+
+namespace view_manager {
+
+ServerView::ServerView(ServerViewDelegate* delegate, const ViewId& id)
+ : delegate_(delegate),
+ id_(id),
+ parent_(nullptr),
+ visible_(false),
+ opacity_(1) {
+ DCHECK(delegate); // Must provide a delegate.
+}
+
+ServerView::~ServerView() {
+ delegate_->OnWillDestroyView(this);
+
+ while (!children_.empty())
+ children_.front()->parent()->Remove(children_.front());
+
+ if (parent_)
+ parent_->Remove(this);
+
+ delegate_->OnViewDestroyed(this);
+}
+
+void ServerView::Add(ServerView* child) {
+ // We assume validation checks happened already.
+ DCHECK(child);
+ DCHECK(child != this);
+ DCHECK(!child->Contains(this));
+ if (child->parent() == this) {
+ if (children_.size() == 1)
+ return; // Already in the right position.
+ Reorder(child, children_.back(), mojo::ORDER_DIRECTION_ABOVE);
+ return;
+ }
+
+ ServerView* old_parent = child->parent();
+ child->delegate_->OnWillChangeViewHierarchy(child, this, old_parent);
+ if (child->parent())
+ child->parent()->RemoveImpl(child);
+
+ child->parent_ = this;
+ children_.push_back(child);
+ child->delegate_->OnViewHierarchyChanged(child, this, old_parent);
+}
+
+void ServerView::Remove(ServerView* child) {
+ // We assume validation checks happened else where.
+ DCHECK(child);
+ DCHECK(child != this);
+ DCHECK(child->parent() == this);
+
+ child->delegate_->OnWillChangeViewHierarchy(child, NULL, this);
+ RemoveImpl(child);
+ child->delegate_->OnViewHierarchyChanged(child, NULL, this);
+}
+
+void ServerView::Reorder(ServerView* child,
+ ServerView* relative,
+ mojo::OrderDirection direction) {
+ // We assume validation checks happened else where.
+ DCHECK(child);
+ DCHECK(child->parent() == this);
+ DCHECK_GT(children_.size(), 1u);
+ children_.erase(std::find(children_.begin(), children_.end(), child));
+ Views::iterator i = std::find(children_.begin(), children_.end(), relative);
+ if (direction == mojo::ORDER_DIRECTION_ABOVE) {
+ DCHECK(i != children_.end());
+ children_.insert(++i, child);
+ } else if (direction == mojo::ORDER_DIRECTION_BELOW) {
+ DCHECK(i != children_.end());
+ children_.insert(i, child);
+ }
+ delegate_->OnViewReordered(this, relative, direction);
+}
+
+void ServerView::SetBounds(const gfx::Rect& bounds) {
+ if (bounds_ == bounds)
+ return;
+
+ const gfx::Rect old_bounds = bounds_;
+ bounds_ = bounds;
+ delegate_->OnViewBoundsChanged(this, old_bounds, bounds);
+}
+
+const ServerView* ServerView::GetRoot() const {
+ const ServerView* view = this;
+ while (view && view->parent())
+ view = view->parent();
+ return view;
+}
+
+std::vector<const ServerView*> ServerView::GetChildren() const {
+ std::vector<const ServerView*> children;
+ children.reserve(children_.size());
+ for (size_t i = 0; i < children_.size(); ++i)
+ children.push_back(children_[i]);
+ return children;
+}
+
+std::vector<ServerView*> ServerView::GetChildren() {
+ // TODO(sky): rename to children() and fix return type.
+ return children_;
+}
+
+bool ServerView::Contains(const ServerView* view) const {
+ for (const ServerView* parent = view; parent; parent = parent->parent_) {
+ if (parent == this)
+ return true;
+ }
+ return false;
+}
+
+void ServerView::SetVisible(bool value) {
+ if (visible_ == value)
+ return;
+
+ delegate_->OnWillChangeViewVisibility(this);
+ visible_ = value;
+}
+
+void ServerView::SetOpacity(float value) {
+ if (value == opacity_)
+ return;
+ opacity_ = value;
+ delegate_->OnScheduleViewPaint(this);
+}
+
+void ServerView::SetTransform(const gfx::Transform& transform) {
+ if (transform_ == transform)
+ return;
+
+ transform_ = transform;
+ delegate_->OnScheduleViewPaint(this);
+}
+
+void ServerView::SetProperty(const std::string& name,
+ const std::vector<uint8_t>* value) {
+ auto it = properties_.find(name);
+ if (it != properties_.end()) {
+ if (value && it->second == *value)
+ return;
+ } else if (!value) {
+ // This property isn't set in |properties_| and |value| is NULL, so there's
+ // no change.
+ return;
+ }
+
+ if (value) {
+ properties_[name] = *value;
+ } else if (it != properties_.end()) {
+ properties_.erase(it);
+ }
+
+ delegate_->OnViewSharedPropertyChanged(this, name, value);
+}
+
+bool ServerView::IsDrawn(const ServerView* root) const {
+ if (!root->visible_)
+ return false;
+ const ServerView* view = this;
+ while (view && view != root && view->visible_)
+ view = view->parent_;
+ return view == root;
+}
+
+void ServerView::SetSurfaceId(cc::SurfaceId surface_id) {
+ surface_id_ = surface_id;
+ delegate_->OnViewSurfaceIdChanged(this);
+}
+
+#if !defined(NDEBUG)
+std::string ServerView::GetDebugWindowHierarchy() const {
+ std::string result;
+ BuildDebugInfo(std::string(), &result);
+ return result;
+}
+
+void ServerView::BuildDebugInfo(const std::string& depth,
+ std::string* result) const {
+ *result += base::StringPrintf(
+ "%sid=%d,%d visible=%s bounds=%d,%d %dx%d surface_id=%" PRIu64 "\n",
+ depth.c_str(), static_cast<int>(id_.connection_id),
+ static_cast<int>(id_.view_id), visible_ ? "true" : "false", bounds_.x(),
+ bounds_.y(), bounds_.width(), bounds_.height(), surface_id_.id);
+ for (const ServerView* child : children_)
+ child->BuildDebugInfo(depth + " ", result);
+}
+#endif
+
+void ServerView::RemoveImpl(ServerView* view) {
+ view->parent_ = NULL;
+ children_.erase(std::find(children_.begin(), children_.end(), view));
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/server_view.h b/mojo/services/view_manager/server_view.h
new file mode 100644
index 0000000..05d000c
--- /dev/null
+++ b/mojo/services/view_manager/server_view.h
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_SERVER_VIEW_H_
+#define SERVICES_VIEW_MANAGER_SERVER_VIEW_H_
+
+#include <vector>
+
+#include "base/logging.h"
+#include "cc/surfaces/surface_id.h"
+#include "mojo/services/view_manager/ids.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace view_manager {
+
+class ServerViewDelegate;
+
+// Server side representation of a view. Delegate is informed of interesting
+// events.
+//
+// It is assumed that all functions that mutate the tree have validated the
+// mutation is possible before hand. For example, Reorder() assumes the supplied
+// view is a child and not already in position.
+class ServerView {
+ public:
+ ServerView(ServerViewDelegate* delegate, const ViewId& id);
+ virtual ~ServerView();
+
+ const ViewId& id() const { return id_; }
+
+ void Add(ServerView* child);
+ void Remove(ServerView* child);
+ void Reorder(ServerView* child,
+ ServerView* relative,
+ mojo::OrderDirection direction);
+
+ const gfx::Rect& bounds() const { return bounds_; }
+ void SetBounds(const gfx::Rect& bounds);
+
+ const ServerView* parent() const { return parent_; }
+ ServerView* parent() { return parent_; }
+
+ const ServerView* GetRoot() const;
+ ServerView* GetRoot() {
+ return const_cast<ServerView*>(
+ const_cast<const ServerView*>(this)->GetRoot());
+ }
+
+ std::vector<const ServerView*> GetChildren() const;
+ std::vector<ServerView*> GetChildren();
+
+ // Returns true if this contains |view| or is |view|.
+ bool Contains(const ServerView* view) const;
+
+ // Returns true if the window is visible. This does not consider visibility
+ // of any ancestors.
+ bool visible() const { return visible_; }
+ void SetVisible(bool value);
+
+ float opacity() const { return opacity_; }
+ void SetOpacity(float value);
+
+ const gfx::Transform& transform() const { return transform_; }
+ void SetTransform(const gfx::Transform& transform);
+
+ const std::map<std::string, std::vector<uint8_t>>& properties() const {
+ return properties_;
+ }
+ void SetProperty(const std::string& name, const std::vector<uint8_t>* value);
+
+ // Returns true if this view is attached to |root| and all ancestors are
+ // visible.
+ bool IsDrawn(const ServerView* root) const;
+
+ void SetSurfaceId(cc::SurfaceId surface_id);
+ const cc::SurfaceId& surface_id() const { return surface_id_; }
+
+#if !defined(NDEBUG)
+ std::string GetDebugWindowHierarchy() const;
+ void BuildDebugInfo(const std::string& depth, std::string* result) const;
+#endif
+
+ private:
+ typedef std::vector<ServerView*> Views;
+
+ // Implementation of removing a view. Doesn't send any notification.
+ void RemoveImpl(ServerView* view);
+
+ ServerViewDelegate* delegate_;
+ const ViewId id_;
+ ServerView* parent_;
+ Views children_;
+ bool visible_;
+ gfx::Rect bounds_;
+ cc::SurfaceId surface_id_;
+ float opacity_;
+ gfx::Transform transform_;
+
+ std::map<std::string, std::vector<uint8_t>> properties_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerView);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_SERVER_VIEW_H_
diff --git a/mojo/services/view_manager/server_view_delegate.h b/mojo/services/view_manager/server_view_delegate.h
new file mode 100644
index 0000000..beca8d2
--- /dev/null
+++ b/mojo/services/view_manager/server_view_delegate.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_
+#define SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_
+
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace mojo {
+class ViewportMetrics;
+}
+
+namespace view_manager {
+
+class ServerView;
+
+class ServerViewDelegate {
+ public:
+ // Invoked when a view is about to be destroyed; before any of the children
+ // have been removed and before the view has been removed from its parent.
+ virtual void OnWillDestroyView(ServerView* view) = 0;
+
+ // Invoked at the end of the View's destructor (after it has been removed from
+ // the hierarchy).
+ virtual void OnViewDestroyed(const ServerView* view) = 0;
+
+ virtual void OnWillChangeViewHierarchy(ServerView* view,
+ ServerView* new_parent,
+ ServerView* old_parent) = 0;
+
+ virtual void OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) = 0;
+
+ virtual void OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) = 0;
+
+ virtual void OnViewSurfaceIdChanged(const ServerView* view) = 0;
+
+ virtual void OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ mojo::OrderDirection direction) = 0;
+
+ virtual void OnWillChangeViewVisibility(ServerView* view) = 0;
+
+ virtual void OnViewSharedPropertyChanged(
+ const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data) = 0;
+
+ virtual void OnScheduleViewPaint(const ServerView* view) = 0;
+
+ protected:
+ virtual ~ServerViewDelegate() {}
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_
diff --git a/mojo/services/view_manager/test_change_tracker.cc b/mojo/services/view_manager/test_change_tracker.cc
new file mode 100644
index 0000000..3e31aa4
--- /dev/null
+++ b/mojo/services/view_manager/test_change_tracker.cc
@@ -0,0 +1,314 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/test_change_tracker.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/common/common_type_converters.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/util.h"
+
+using mojo::Array;
+using mojo::Id;
+using mojo::ViewDataPtr;
+using mojo::String;
+
+namespace view_manager {
+
+std::string ViewIdToString(Id id) {
+ return (id == 0) ? "null" : base::StringPrintf("%d,%d", mojo::HiWord(id),
+ mojo::LoWord(id));
+}
+
+namespace {
+
+std::string RectToString(const mojo::Rect& rect) {
+ return base::StringPrintf("%d,%d %dx%d", rect.x, rect.y, rect.width,
+ rect.height);
+}
+
+std::string DirectionToString(mojo::OrderDirection direction) {
+ return direction == mojo::ORDER_DIRECTION_ABOVE ? "above" : "below";
+}
+
+std::string ChangeToDescription1(const Change& change) {
+ switch (change.type) {
+ case CHANGE_TYPE_EMBED:
+ return base::StringPrintf("OnEmbed creator=%s",
+ change.creator_url.data());
+
+ case CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED:
+ return base::StringPrintf("OnEmbeddedAppDisconnected view=%s",
+ ViewIdToString(change.view_id).c_str());
+
+ case CHANGE_TYPE_NODE_BOUNDS_CHANGED:
+ return base::StringPrintf(
+ "BoundsChanged view=%s old_bounds=%s new_bounds=%s",
+ ViewIdToString(change.view_id).c_str(),
+ RectToString(change.bounds).c_str(),
+ RectToString(change.bounds2).c_str());
+
+ case CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED:
+ // TODO(sky): Not implemented.
+ return "ViewportMetricsChanged";
+
+ case CHANGE_TYPE_NODE_HIERARCHY_CHANGED:
+ return base::StringPrintf(
+ "HierarchyChanged view=%s new_parent=%s old_parent=%s",
+ ViewIdToString(change.view_id).c_str(),
+ ViewIdToString(change.view_id2).c_str(),
+ ViewIdToString(change.view_id3).c_str());
+
+ case CHANGE_TYPE_NODE_REORDERED:
+ return base::StringPrintf("Reordered view=%s relative=%s direction=%s",
+ ViewIdToString(change.view_id).c_str(),
+ ViewIdToString(change.view_id2).c_str(),
+ DirectionToString(change.direction).c_str());
+
+ case CHANGE_TYPE_NODE_DELETED:
+ return base::StringPrintf("ViewDeleted view=%s",
+ ViewIdToString(change.view_id).c_str());
+
+ case CHANGE_TYPE_NODE_VISIBILITY_CHANGED:
+ return base::StringPrintf("VisibilityChanged view=%s visible=%s",
+ ViewIdToString(change.view_id).c_str(),
+ change.bool_value ? "true" : "false");
+
+ case CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED:
+ return base::StringPrintf("DrawnStateChanged view=%s drawn=%s",
+ ViewIdToString(change.view_id).c_str(),
+ change.bool_value ? "true" : "false");
+
+ case CHANGE_TYPE_INPUT_EVENT:
+ return base::StringPrintf("InputEvent view=%s event_action=%d",
+ ViewIdToString(change.view_id).c_str(),
+ change.event_action);
+
+ case CHANGE_TYPE_PROPERTY_CHANGED:
+ return base::StringPrintf("PropertyChanged view=%s key=%s value=%s",
+ ViewIdToString(change.view_id).c_str(),
+ change.property_key.c_str(),
+ change.property_value.c_str());
+
+ case CHANGE_TYPE_DELEGATE_EMBED:
+ return base::StringPrintf("DelegateEmbed url=%s",
+ change.embed_url.data());
+ }
+ return std::string();
+}
+
+} // namespace
+
+std::vector<std::string> ChangesToDescription1(
+ const std::vector<Change>& changes) {
+ std::vector<std::string> strings(changes.size());
+ for (size_t i = 0; i < changes.size(); ++i)
+ strings[i] = ChangeToDescription1(changes[i]);
+ return strings;
+}
+
+std::string SingleChangeToDescription(const std::vector<Change>& changes) {
+ if (changes.size() != 1u)
+ return std::string();
+ return ChangeToDescription1(changes[0]);
+}
+
+std::string SingleViewDescription(const std::vector<TestView>& views) {
+ if (views.size() != 1u)
+ return std::string();
+ return views[0].ToString();
+}
+
+std::string ChangeViewDescription(const std::vector<Change>& changes) {
+ if (changes.size() != 1)
+ return std::string();
+ std::vector<std::string> view_strings(changes[0].views.size());
+ for (size_t i = 0; i < changes[0].views.size(); ++i)
+ view_strings[i] = "[" + changes[0].views[i].ToString() + "]";
+ return JoinString(view_strings, ',');
+}
+
+TestView ViewDataToTestView(const ViewDataPtr& data) {
+ TestView view;
+ view.parent_id = data->parent_id;
+ view.view_id = data->view_id;
+ view.visible = data->visible;
+ view.drawn = data->drawn;
+ view.properties =
+ data->properties.To<std::map<std::string, std::vector<uint8_t>>>();
+ return view;
+}
+
+void ViewDatasToTestViews(const Array<ViewDataPtr>& data,
+ std::vector<TestView>* test_views) {
+ for (size_t i = 0; i < data.size(); ++i)
+ test_views->push_back(ViewDataToTestView(data[i]));
+}
+
+Change::Change()
+ : type(CHANGE_TYPE_EMBED),
+ connection_id(0),
+ view_id(0),
+ view_id2(0),
+ view_id3(0),
+ event_action(0),
+ direction(mojo::ORDER_DIRECTION_ABOVE),
+ bool_value(false) {
+}
+
+Change::~Change() {
+}
+
+TestChangeTracker::TestChangeTracker()
+ : delegate_(NULL) {
+}
+
+TestChangeTracker::~TestChangeTracker() {
+}
+
+void TestChangeTracker::OnEmbed(mojo::ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root) {
+ Change change;
+ change.type = CHANGE_TYPE_EMBED;
+ change.connection_id = connection_id;
+ change.creator_url = creator_url;
+ change.views.push_back(ViewDataToTestView(root));
+ AddChange(change);
+}
+
+void TestChangeTracker::OnEmbeddedAppDisconnected(Id view_id) {
+ Change change;
+ change.type = CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED;
+ change.view_id = view_id;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewBoundsChanged(Id view_id,
+ mojo::RectPtr old_bounds,
+ mojo::RectPtr new_bounds) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED;
+ change.view_id = view_id;
+ change.bounds.x = old_bounds->x;
+ change.bounds.y = old_bounds->y;
+ change.bounds.width = old_bounds->width;
+ change.bounds.height = old_bounds->height;
+ change.bounds2.x = new_bounds->x;
+ change.bounds2.y = new_bounds->y;
+ change.bounds2.width = new_bounds->width;
+ change.bounds2.height = new_bounds->height;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewViewportMetricsChanged(
+ mojo::ViewportMetricsPtr old_metrics,
+ mojo::ViewportMetricsPtr new_metrics) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED;
+ // NOT IMPLEMENTED
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewHierarchyChanged(Id view_id,
+ Id new_parent_id,
+ Id old_parent_id,
+ Array<ViewDataPtr> views) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED;
+ change.view_id = view_id;
+ change.view_id2 = new_parent_id;
+ change.view_id3 = old_parent_id;
+ ViewDatasToTestViews(views, &change.views);
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewReordered(Id view_id,
+ Id relative_view_id,
+ mojo::OrderDirection direction) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_REORDERED;
+ change.view_id = view_id;
+ change.view_id2 = relative_view_id;
+ change.direction = direction;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewDeleted(Id view_id) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_DELETED;
+ change.view_id = view_id;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewVisibilityChanged(Id view_id, bool visible) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_VISIBILITY_CHANGED;
+ change.view_id = view_id;
+ change.bool_value = visible;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewDrawnStateChanged(Id view_id, bool drawn) {
+ Change change;
+ change.type = CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED;
+ change.view_id = view_id;
+ change.bool_value = drawn;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewInputEvent(Id view_id, mojo::EventPtr event) {
+ Change change;
+ change.type = CHANGE_TYPE_INPUT_EVENT;
+ change.view_id = view_id;
+ change.event_action = event->action;
+ AddChange(change);
+}
+
+void TestChangeTracker::OnViewSharedPropertyChanged(Id view_id,
+ String name,
+ Array<uint8_t> data) {
+ Change change;
+ change.type = CHANGE_TYPE_PROPERTY_CHANGED;
+ change.view_id = view_id;
+ change.property_key = name;
+ if (data.is_null())
+ change.property_value = "NULL";
+ else
+ change.property_value = data.To<std::string>();
+ AddChange(change);
+}
+
+void TestChangeTracker::DelegateEmbed(const String& url) {
+ Change change;
+ change.type = CHANGE_TYPE_DELEGATE_EMBED;
+ change.embed_url = url;
+ AddChange(change);
+}
+
+void TestChangeTracker::AddChange(const Change& change) {
+ changes_.push_back(change);
+ if (delegate_)
+ delegate_->OnChangeAdded();
+}
+
+TestView::TestView() {}
+
+TestView::~TestView() {}
+
+std::string TestView::ToString() const {
+ return base::StringPrintf("view=%s parent=%s",
+ ViewIdToString(view_id).c_str(),
+ ViewIdToString(parent_id).c_str());
+}
+
+std::string TestView::ToString2() const {
+ return base::StringPrintf("view=%s parent=%s visible=%s drawn=%s",
+ ViewIdToString(view_id).c_str(),
+ ViewIdToString(parent_id).c_str(),
+ visible ? "true" : "false",
+ drawn ? "true" : "false");
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/test_change_tracker.h b/mojo/services/view_manager/test_change_tracker.h
new file mode 100644
index 0000000..822efad
--- /dev/null
+++ b/mojo/services/view_manager/test_change_tracker.h
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
+#define SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "mojo/public/cpp/bindings/array.h"
+#include "third_party/mojo_services/src/geometry/public/interfaces/geometry.mojom.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+
+namespace view_manager {
+
+enum ChangeType {
+ CHANGE_TYPE_EMBED,
+ CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED,
+ // TODO(sky): NODE->VIEW.
+ CHANGE_TYPE_NODE_BOUNDS_CHANGED,
+ CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED,
+ CHANGE_TYPE_NODE_HIERARCHY_CHANGED,
+ CHANGE_TYPE_NODE_REORDERED,
+ CHANGE_TYPE_NODE_VISIBILITY_CHANGED,
+ CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED,
+ CHANGE_TYPE_NODE_DELETED,
+ CHANGE_TYPE_INPUT_EVENT,
+ CHANGE_TYPE_PROPERTY_CHANGED,
+ CHANGE_TYPE_DELEGATE_EMBED,
+};
+
+// TODO(sky): consider nuking and converting directly to ViewData.
+struct TestView {
+ TestView();
+ ~TestView();
+
+ // Returns a string description of this.
+ std::string ToString() const;
+
+ // Returns a string description that includes visible and drawn.
+ std::string ToString2() const;
+
+ mojo::Id parent_id;
+ mojo::Id view_id;
+ bool visible;
+ bool drawn;
+ std::map<std::string, std::vector<uint8_t>> properties;
+};
+
+// Tracks a call to ViewManagerClient. See the individual functions for the
+// fields that are used.
+struct Change {
+ Change();
+ ~Change();
+
+ ChangeType type;
+ mojo::ConnectionSpecificId connection_id;
+ std::vector<TestView> views;
+ mojo::Id view_id;
+ mojo::Id view_id2;
+ mojo::Id view_id3;
+ mojo::Rect bounds;
+ mojo::Rect bounds2;
+ int32_t event_action;
+ mojo::String creator_url;
+ mojo::String embed_url;
+ mojo::OrderDirection direction;
+ bool bool_value;
+ std::string property_key;
+ std::string property_value;
+};
+
+// Converts Changes to string descriptions.
+std::vector<std::string> ChangesToDescription1(
+ const std::vector<Change>& changes);
+
+// Convenience for returning the description of the first item in |changes|.
+// Returns an empty string if |changes| has something other than one entry.
+std::string SingleChangeToDescription(const std::vector<Change>& changes);
+
+// Convenience for returning the description of the first item in |views|.
+// Returns an empty string if |views| has something other than one entry.
+std::string SingleViewDescription(const std::vector<TestView>& views);
+
+// Returns a string description of |changes[0].views|. Returns an empty string
+// if change.size() != 1.
+std::string ChangeViewDescription(const std::vector<Change>& changes);
+
+// Converts ViewDatas to TestViews.
+void ViewDatasToTestViews(const mojo::Array<mojo::ViewDataPtr>& data,
+ std::vector<TestView>* test_views);
+
+// TestChangeTracker is used to record ViewManagerClient functions. It notifies
+// a delegate any time a change is added.
+class TestChangeTracker {
+ public:
+ // Used to notify the delegate when a change is added. A change corresponds to
+ // a single ViewManagerClient function.
+ class Delegate {
+ public:
+ virtual void OnChangeAdded() = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ TestChangeTracker();
+ ~TestChangeTracker();
+
+ void set_delegate(Delegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ std::vector<Change>* changes() { return &changes_; }
+
+ // Each of these functions generate a Change. There is one per
+ // ViewManagerClient function.
+ void OnEmbed(mojo::ConnectionSpecificId connection_id,
+ const mojo::String& creator_url,
+ mojo::ViewDataPtr root);
+ void OnEmbeddedAppDisconnected(mojo::Id view_id);
+ void OnViewBoundsChanged(mojo::Id view_id,
+ mojo::RectPtr old_bounds,
+ mojo::RectPtr new_bounds);
+ void OnViewViewportMetricsChanged(mojo::ViewportMetricsPtr old_bounds,
+ mojo::ViewportMetricsPtr new_bounds);
+ void OnViewHierarchyChanged(mojo::Id view_id,
+ mojo::Id new_parent_id,
+ mojo::Id old_parent_id,
+ mojo::Array<mojo::ViewDataPtr> views);
+ void OnViewReordered(mojo::Id view_id,
+ mojo::Id relative_view_id,
+ mojo::OrderDirection direction);
+ void OnViewDeleted(mojo::Id view_id);
+ void OnViewVisibilityChanged(mojo::Id view_id, bool visible);
+ void OnViewDrawnStateChanged(mojo::Id view_id, bool drawn);
+ void OnViewInputEvent(mojo::Id view_id, mojo::EventPtr event);
+ void OnViewSharedPropertyChanged(mojo::Id view_id,
+ mojo::String name,
+ mojo::Array<uint8_t> data);
+ void DelegateEmbed(const mojo::String& url);
+
+ private:
+ void AddChange(const Change& change);
+
+ Delegate* delegate_;
+ std::vector<Change> changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestChangeTracker);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_
diff --git a/mojo/services/view_manager/test_server_view_delegate.cc b/mojo/services/view_manager/test_server_view_delegate.cc
new file mode 100644
index 0000000..c7b8a97
--- /dev/null
+++ b/mojo/services/view_manager/test_server_view_delegate.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 "mojo/services/view_manager/test_server_view_delegate.h"
+
+namespace view_manager {
+
+TestServerViewDelegate::TestServerViewDelegate() {
+}
+
+TestServerViewDelegate::~TestServerViewDelegate() {
+}
+
+void TestServerViewDelegate::OnWillDestroyView(ServerView* view) {
+}
+
+void TestServerViewDelegate::OnViewDestroyed(const ServerView* view) {
+}
+
+void TestServerViewDelegate::OnWillChangeViewHierarchy(ServerView* view,
+ ServerView* new_parent,
+ ServerView* old_parent) {
+}
+
+void TestServerViewDelegate::OnViewHierarchyChanged(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) {
+}
+
+void TestServerViewDelegate::OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+}
+
+void TestServerViewDelegate::OnViewSurfaceIdChanged(const ServerView* view) {
+}
+
+void TestServerViewDelegate::OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ mojo::OrderDirection direction) {
+}
+
+void TestServerViewDelegate::OnWillChangeViewVisibility(ServerView* view) {
+}
+
+void TestServerViewDelegate::OnViewSharedPropertyChanged(
+ const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data) {
+}
+
+void TestServerViewDelegate::OnScheduleViewPaint(const ServerView* view) {
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/test_server_view_delegate.h b/mojo/services/view_manager/test_server_view_delegate.h
new file mode 100644
index 0000000..3f6725e
--- /dev/null
+++ b/mojo/services/view_manager/test_server_view_delegate.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_
+#define SERVICES_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_
+
+#include "base/basictypes.h"
+#include "mojo/services/view_manager/server_view_delegate.h"
+
+namespace view_manager {
+
+class TestServerViewDelegate : public ServerViewDelegate {
+ public:
+ TestServerViewDelegate();
+ ~TestServerViewDelegate() override;
+
+ private:
+ // ServerViewDelegate:
+ void OnWillDestroyView(ServerView* view) override;
+ void OnViewDestroyed(const ServerView* view) override;
+ void OnWillChangeViewHierarchy(ServerView* view,
+ ServerView* new_parent,
+ ServerView* old_parent) override;
+ void OnViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent) override;
+ void OnViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) override;
+ void OnViewSurfaceIdChanged(const ServerView* view) override;
+ void OnViewReordered(const ServerView* view,
+ const ServerView* relative,
+ mojo::OrderDirection direction) override;
+ void OnWillChangeViewVisibility(ServerView* view) override;
+ void OnViewSharedPropertyChanged(
+ const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data) override;
+ void OnScheduleViewPaint(const ServerView* view) override;
+
+ DISALLOW_COPY_AND_ASSIGN(TestServerViewDelegate);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_
diff --git a/mojo/services/view_manager/view_coordinate_conversions.cc b/mojo/services/view_manager/view_coordinate_conversions.cc
new file mode 100644
index 0000000..2532ee1
--- /dev/null
+++ b/mojo/services/view_manager/view_coordinate_conversions.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/view_coordinate_conversions.h"
+
+#include "mojo/services/view_manager/server_view.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+
+namespace view_manager {
+
+namespace {
+
+gfx::Vector2d CalculateOffsetToAncestor(const ServerView* view,
+ const ServerView* ancestor) {
+ DCHECK(ancestor->Contains(view));
+ gfx::Vector2d result;
+ for (const ServerView* v = view; v != ancestor; v = v->parent())
+ result += v->bounds().OffsetFromOrigin();
+ return result;
+}
+
+} // namespace
+
+gfx::Rect ConvertRectBetweenViews(const ServerView* from,
+ const ServerView* to,
+ const gfx::Rect& rect) {
+ DCHECK(from);
+ if (from == to)
+ return rect;
+
+ if (from->Contains(to)) {
+ const gfx::Vector2d offset(CalculateOffsetToAncestor(to, from));
+ return gfx::Rect(rect.origin() - offset, rect.size());
+ }
+ DCHECK(to->Contains(from));
+ const gfx::Vector2d offset(CalculateOffsetToAncestor(from, to));
+ return gfx::Rect(rect.origin() + offset, rect.size());
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/view_coordinate_conversions.h b/mojo/services/view_manager/view_coordinate_conversions.h
new file mode 100644
index 0000000..5c34183
--- /dev/null
+++ b/mojo/services/view_manager/view_coordinate_conversions.h
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_
+#define SERVICES_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_
+
+namespace gfx {
+class Rect;
+}
+
+namespace view_manager {
+
+class ServerView;
+
+// Converts |rect| from the coordinates of |from| to the coordinates of |to|.
+// |from| and |to| must be an ancestors or descendants of each other.
+gfx::Rect ConvertRectBetweenViews(const ServerView* from,
+ const ServerView* to,
+ const gfx::Rect& rect);
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_
diff --git a/mojo/services/view_manager/view_coordinate_conversions_unittest.cc b/mojo/services/view_manager/view_coordinate_conversions_unittest.cc
new file mode 100644
index 0000000..2a9a018
--- /dev/null
+++ b/mojo/services/view_manager/view_coordinate_conversions_unittest.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/server_view_delegate.h"
+#include "mojo/services/view_manager/test_server_view_delegate.h"
+#include "mojo/services/view_manager/view_coordinate_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+
+namespace view_manager {
+
+using ViewCoordinateConversionsTest = testing::Test;
+
+TEST_F(ViewCoordinateConversionsTest, ConvertRectBetweenViews) {
+ TestServerViewDelegate d1, d2, d3;
+ ServerView v1(&d1, ViewId()), v2(&d2, ViewId()), v3(&d3, ViewId());
+ v1.SetBounds(gfx::Rect(1, 2, 100, 100));
+ v2.SetBounds(gfx::Rect(3, 4, 100, 100));
+ v3.SetBounds(gfx::Rect(5, 6, 100, 100));
+ v1.Add(&v2);
+ v2.Add(&v3);
+
+ EXPECT_EQ(gfx::Rect(2, 1, 8, 9),
+ ConvertRectBetweenViews(&v1, &v3, gfx::Rect(10, 11, 8, 9)));
+
+ EXPECT_EQ(gfx::Rect(18, 21, 8, 9),
+ ConvertRectBetweenViews(&v3, &v1, gfx::Rect(10, 11, 8, 9)));
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/view_manager_app.cc b/mojo/services/view_manager/view_manager_app.cc
new file mode 100644
index 0000000..22292a3
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_app.cc
@@ -0,0 +1,137 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/view_manager_app.h"
+
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/view_manager/client_connection.h"
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/display_manager.h"
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+
+using mojo::ApplicationConnection;
+using mojo::ApplicationImpl;
+using mojo::InterfaceRequest;
+using mojo::ViewManagerService;
+using mojo::WindowManagerInternalClient;
+
+namespace view_manager {
+
+ViewManagerApp::ViewManagerApp()
+ : app_impl_(nullptr), wm_app_connection_(nullptr) {
+}
+
+ViewManagerApp::~ViewManagerApp() {}
+
+void ViewManagerApp::Initialize(ApplicationImpl* app) {
+ app_impl_ = app;
+ tracing_.Initialize(app);
+}
+
+bool ViewManagerApp::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ if (connection_manager_.get()) {
+ VLOG(1) << "ViewManager allows only one window manager connection.";
+ return false;
+ }
+ wm_app_connection_ = connection;
+ // |connection| originates from the WindowManager. Let it connect directly
+ // to the ViewManager and WindowManagerInternalClient.
+ connection->AddService<ViewManagerService>(this);
+ connection->AddService<WindowManagerInternalClient>(this);
+ connection->ConnectToService(&wm_internal_);
+ // If no ServiceProvider has been sent, refuse the connection.
+ if (!wm_internal_)
+ return false;
+ wm_internal_.set_error_handler(this);
+
+ scoped_ptr<DefaultDisplayManager> display_manager(new DefaultDisplayManager(
+ app_impl_, connection,
+ base::Bind(&ViewManagerApp::OnLostConnectionToWindowManager,
+ base::Unretained(this))));
+ connection_manager_.reset(
+ new ConnectionManager(this, display_manager.Pass(), wm_internal_.get()));
+ return true;
+}
+
+void ViewManagerApp::OnLostConnectionToWindowManager() {
+ ApplicationImpl::Terminate();
+}
+
+ClientConnection* ViewManagerApp::CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id) {
+ mojo::ViewManagerClientPtr client;
+ app_impl_->ConnectToService(url, &client);
+
+ scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl(
+ connection_manager, creator_id, creator_url, url, root_id));
+ return new DefaultClientConnection(service.Pass(), connection_manager,
+ service_request.Pass(), client.Pass());
+}
+
+ClientConnection* ViewManagerApp::CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const ViewId& root_id,
+ mojo::ViewManagerClientPtr view_manager_client) {
+ scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl(
+ connection_manager, creator_id, creator_url, std::string(), root_id));
+ return new DefaultClientConnection(service.Pass(), connection_manager,
+ service_request.Pass(),
+ view_manager_client.Pass());
+}
+
+void ViewManagerApp::Create(ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerService> request) {
+ if (connection_manager_->has_window_manager_client_connection()) {
+ VLOG(1) << "ViewManager interface requested more than once.";
+ return;
+ }
+
+ scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl(
+ connection_manager_.get(), kInvalidConnectionId, std::string(),
+ std::string("mojo:window_manager"), RootViewId()));
+ mojo::ViewManagerClientPtr client;
+ wm_internal_client_request_ = GetProxy(&client);
+ scoped_ptr<ClientConnection> client_connection(
+ new DefaultClientConnection(service.Pass(), connection_manager_.get(),
+ request.Pass(), client.Pass()));
+ connection_manager_->SetWindowManagerClientConnection(
+ client_connection.Pass());
+}
+
+void ViewManagerApp::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<WindowManagerInternalClient> request) {
+ if (wm_internal_client_binding_.get()) {
+ VLOG(1) << "WindowManagerInternalClient requested more than once.";
+ return;
+ }
+
+ // ConfigureIncomingConnection() must have been called before getting here.
+ DCHECK(connection_manager_.get());
+ wm_internal_client_binding_.reset(
+ new mojo::Binding<WindowManagerInternalClient>(connection_manager_.get(),
+ request.Pass()));
+ wm_internal_client_binding_->set_error_handler(this);
+ wm_internal_->SetViewManagerClient(
+ wm_internal_client_request_.PassMessagePipe());
+}
+
+void ViewManagerApp::OnConnectionError() {
+ ApplicationImpl::Terminate();
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/view_manager_app.h b/mojo/services/view_manager/view_manager_app.h
new file mode 100644
index 0000000..83f7961
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_app.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_VIEW_MANAGER_APP_H_
+#define SERVICES_VIEW_MANAGER_VIEW_MANAGER_APP_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/services/view_manager/connection_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h"
+
+namespace mojo {
+class ApplicationImpl;
+}
+
+namespace view_manager {
+
+class ConnectionManager;
+
+class ViewManagerApp
+ : public mojo::ApplicationDelegate,
+ public ConnectionManagerDelegate,
+ public mojo::ErrorHandler,
+ public mojo::InterfaceFactory<mojo::ViewManagerService>,
+ public mojo::InterfaceFactory<mojo::WindowManagerInternalClient> {
+ public:
+ ViewManagerApp();
+ ~ViewManagerApp() override;
+
+ private:
+ // ApplicationDelegate:
+ void Initialize(mojo::ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ // ConnectionManagerDelegate:
+ void OnLostConnectionToWindowManager() override;
+ ClientConnection* CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id) override;
+ ClientConnection* CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const ViewId& root_id,
+ mojo::ViewManagerClientPtr view_manager_client) override;
+
+ // mojo::InterfaceFactory<mojo::ViewManagerService>:
+ void Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::ViewManagerService> request) override;
+
+ // mojo::InterfaceFactory<mojo::WindowManagerInternalClient>:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::WindowManagerInternalClient> request)
+ override;
+
+ // ErrorHandler (for |wm_internal_| and |wm_internal_client_binding_|).
+ void OnConnectionError() override;
+
+ mojo::ApplicationImpl* app_impl_;
+ mojo::ApplicationConnection* wm_app_connection_;
+ scoped_ptr<mojo::Binding<mojo::WindowManagerInternalClient>>
+ wm_internal_client_binding_;
+ mojo::InterfaceRequest<mojo::ViewManagerClient> wm_internal_client_request_;
+ mojo::WindowManagerInternalPtr wm_internal_;
+ scoped_ptr<ConnectionManager> connection_manager_;
+ mojo::TracingImpl tracing_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerApp);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_VIEW_MANAGER_APP_H_
diff --git a/mojo/services/view_manager/view_manager_client_apptest.cc b/mojo/services/view_manager/view_manager_client_apptest.cc
new file mode 100644
index 0000000..9d441d7
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_client_apptest.cc
@@ -0,0 +1,613 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/test_timeouts.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "third_party/mojo_services/src/geometry/public/cpp/geometry_util.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_manager_client_impl.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_context.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h"
+
+namespace mojo {
+
+namespace {
+
+base::RunLoop* current_run_loop = nullptr;
+
+void TimeoutRunLoop(const base::Closure& timeout_task, bool* timeout) {
+ CHECK(current_run_loop);
+ *timeout = true;
+ timeout_task.Run();
+}
+
+bool DoRunLoopWithTimeout() {
+ if (current_run_loop != nullptr)
+ return false;
+
+ bool timeout = false;
+ base::RunLoop run_loop;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&TimeoutRunLoop, run_loop.QuitClosure(), &timeout),
+ TestTimeouts::action_timeout());
+
+ current_run_loop = &run_loop;
+ current_run_loop->Run();
+ current_run_loop = nullptr;
+ return !timeout;
+}
+
+void QuitRunLoop() {
+ current_run_loop->Quit();
+ current_run_loop = nullptr;
+}
+
+class BoundsChangeObserver : public ViewObserver {
+ public:
+ explicit BoundsChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ ~BoundsChangeObserver() override { view_->RemoveObserver(this); }
+
+ private:
+ // Overridden from ViewObserver:
+ void OnViewBoundsChanged(View* view,
+ const Rect& old_bounds,
+ const Rect& new_bounds) override {
+ DCHECK_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver);
+};
+
+// Wait until the bounds of the supplied view change; returns false on timeout.
+bool WaitForBoundsToChange(View* view) {
+ BoundsChangeObserver observer(view);
+ return DoRunLoopWithTimeout();
+}
+
+// Spins a run loop until the tree beginning at |root| has |tree_size| views
+// (including |root|).
+class TreeSizeMatchesObserver : public ViewObserver {
+ public:
+ TreeSizeMatchesObserver(View* tree, size_t tree_size)
+ : tree_(tree), tree_size_(tree_size) {
+ tree_->AddObserver(this);
+ }
+ ~TreeSizeMatchesObserver() override { tree_->RemoveObserver(this); }
+
+ bool IsTreeCorrectSize() { return CountViews(tree_) == tree_size_; }
+
+ private:
+ // Overridden from ViewObserver:
+ void OnTreeChanged(const TreeChangeParams& params) override {
+ if (IsTreeCorrectSize())
+ QuitRunLoop();
+ }
+
+ size_t CountViews(const View* view) const {
+ size_t count = 1;
+ View::Children::const_iterator it = view->children().begin();
+ for (; it != view->children().end(); ++it)
+ count += CountViews(*it);
+ return count;
+ }
+
+ View* tree_;
+ size_t tree_size_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver);
+};
+
+// Wait until |view|'s tree size matches |tree_size|; returns false on timeout.
+bool WaitForTreeSizeToMatch(View* view, size_t tree_size) {
+ TreeSizeMatchesObserver observer(view, tree_size);
+ return observer.IsTreeCorrectSize() || DoRunLoopWithTimeout();
+}
+
+class OrderChangeObserver : public ViewObserver {
+ public:
+ OrderChangeObserver(View* view) : view_(view) { view_->AddObserver(this); }
+ ~OrderChangeObserver() override { view_->RemoveObserver(this); }
+
+ private:
+ // Overridden from ViewObserver:
+ void OnViewReordered(View* view,
+ View* relative_view,
+ OrderDirection direction) override {
+ DCHECK_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver);
+};
+
+// Wait until |view|'s tree size matches |tree_size|; returns false on timeout.
+bool WaitForOrderChange(ViewManager* view_manager, View* view) {
+ OrderChangeObserver observer(view);
+ return DoRunLoopWithTimeout();
+}
+
+// Tracks a view's destruction. Query is_valid() for current state.
+class ViewTracker : public ViewObserver {
+ public:
+ explicit ViewTracker(View* view) : view_(view) { view_->AddObserver(this); }
+ ~ViewTracker() override {
+ if (view_)
+ view_->RemoveObserver(this);
+ }
+
+ bool is_valid() const { return !!view_; }
+
+ private:
+ // Overridden from ViewObserver:
+ void OnViewDestroyed(View* view) override {
+ DCHECK_EQ(view, view_);
+ view_ = nullptr;
+ }
+
+ int id_;
+ View* view_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ViewTracker);
+};
+
+} // namespace
+
+// ViewManager -----------------------------------------------------------------
+
+// These tests model synchronization of two peer connections to the view manager
+// service, that are given access to some root view.
+
+class ViewManagerTest : public test::ApplicationTestBase,
+ public ApplicationDelegate,
+ public ViewManagerDelegate {
+ public:
+ ViewManagerTest()
+ : most_recent_view_manager_(nullptr), window_manager_(nullptr) {}
+
+ // Overridden from ApplicationDelegate:
+ void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ // ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ ViewManager* window_manager() { return window_manager_; }
+
+ // Embeds another version of the test app @ view; returns nullptr on timeout.
+ ViewManager* Embed(ViewManager* view_manager, View* view) {
+ DCHECK_EQ(view_manager, view->view_manager());
+ most_recent_view_manager_ = nullptr;
+ view->Embed(application_impl()->url());
+ if (!DoRunLoopWithTimeout())
+ return nullptr;
+ ViewManager* vm = nullptr;
+ std::swap(vm, most_recent_view_manager_);
+ return vm;
+ }
+
+ ApplicationDelegate* GetApplicationDelegate() override { return this; }
+
+ // Overridden from ViewManagerDelegate:
+ void OnEmbed(View* root,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) override {
+ most_recent_view_manager_ = root->view_manager();
+ QuitRunLoop();
+ }
+ void OnViewManagerDisconnected(ViewManager* view_manager) override {}
+
+ private:
+ // Overridden from testing::Test:
+ void SetUp() override {
+ ApplicationTestBase::SetUp();
+
+ view_manager_context_.reset(new ViewManagerContext(application_impl()));
+ view_manager_context_->Embed(application_impl()->url());
+ ASSERT_TRUE(DoRunLoopWithTimeout());
+ std::swap(window_manager_, most_recent_view_manager_);
+ }
+
+ // Overridden from testing::Test:
+ void TearDown() override { ApplicationTestBase::TearDown(); }
+
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ scoped_ptr<ViewManagerContext> view_manager_context_;
+
+ // Used to receive the most recent view manager loaded by an embed action.
+ ViewManager* most_recent_view_manager_;
+ // The View Manager connection held by the window manager (app running at the
+ // root view).
+ ViewManager* window_manager_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerTest);
+};
+
+TEST_F(ViewManagerTest, RootView) {
+ ASSERT_NE(nullptr, window_manager());
+ EXPECT_NE(nullptr, window_manager()->GetRoot());
+ EXPECT_EQ("mojo:window_manager", window_manager()->GetEmbedderURL());
+}
+
+TEST_F(ViewManagerTest, Embed) {
+ View* view = window_manager()->CreateView();
+ ASSERT_NE(nullptr, view);
+ view->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ ASSERT_NE(nullptr, embedded);
+
+ View* view_in_embedded = embedded->GetRoot();
+ ASSERT_NE(nullptr, view_in_embedded);
+ EXPECT_EQ(view->id(), view_in_embedded->id());
+ EXPECT_EQ(nullptr, view_in_embedded->parent());
+ EXPECT_TRUE(view_in_embedded->children().empty());
+}
+
+// Window manager has two views, N1 and N11. Embeds A at N1. A should not see
+// N11.
+TEST_F(ViewManagerTest, EmbeddedDoesntSeeChild) {
+ View* view = window_manager()->CreateView();
+ ASSERT_NE(nullptr, view);
+ view->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view);
+ View* nested = window_manager()->CreateView();
+ ASSERT_NE(nullptr, nested);
+ nested->SetVisible(true);
+ view->AddChild(nested);
+
+ ViewManager* embedded = Embed(window_manager(), view);
+ ASSERT_NE(nullptr, embedded);
+ View* view_in_embedded = embedded->GetRoot();
+ EXPECT_EQ(view->id(), view_in_embedded->id());
+ EXPECT_EQ(nullptr, view_in_embedded->parent());
+ EXPECT_TRUE(view_in_embedded->children().empty());
+}
+
+// TODO(beng): write a replacement test for the one that once existed here:
+// This test validates the following scenario:
+// - a view originating from one connection
+// - a view originating from a second connection
+// + the connection originating the view is destroyed
+// -> the view should still exist (since the second connection is live) but
+// should be disconnected from any views.
+// http://crbug.com/396300
+//
+// TODO(beng): The new test should validate the scenario as described above
+// except that the second connection still has a valid tree.
+
+// Verifies that bounds changes applied to a view hierarchy in one connection
+// are reflected to another.
+TEST_F(ViewManagerTest, SetBounds) {
+ View* view = window_manager()->CreateView();
+ view->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ ASSERT_NE(nullptr, embedded);
+
+ View* view_in_embedded = embedded->GetViewById(view->id());
+ EXPECT_EQ(view->bounds(), view_in_embedded->bounds());
+
+ Rect rect;
+ rect.width = rect.height = 100;
+ view->SetBounds(rect);
+ ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded));
+ EXPECT_EQ(view->bounds(), view_in_embedded->bounds());
+}
+
+// Verifies that bounds changes applied to a view owned by a different
+// connection are refused.
+TEST_F(ViewManagerTest, SetBoundsSecurity) {
+ View* view = window_manager()->CreateView();
+ view->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ ASSERT_NE(nullptr, embedded);
+
+ View* view_in_embedded = embedded->GetViewById(view->id());
+ Rect rect;
+ rect.width = 800;
+ rect.height = 600;
+ view->SetBounds(rect);
+ ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded));
+
+ rect.width = 1024;
+ rect.height = 768;
+ view_in_embedded->SetBounds(rect);
+ // Bounds change should have been rejected.
+ EXPECT_EQ(view->bounds(), view_in_embedded->bounds());
+}
+
+// Verifies that a view can only be destroyed by the connection that created it.
+TEST_F(ViewManagerTest, DestroySecurity) {
+ View* view = window_manager()->CreateView();
+ view->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ ASSERT_NE(nullptr, embedded);
+
+ View* view_in_embedded = embedded->GetViewById(view->id());
+
+ ViewTracker tracker2(view_in_embedded);
+ view_in_embedded->Destroy();
+ // View should not have been destroyed.
+ EXPECT_TRUE(tracker2.is_valid());
+
+ ViewTracker tracker1(view);
+ view->Destroy();
+ EXPECT_FALSE(tracker1.is_valid());
+}
+
+TEST_F(ViewManagerTest, MultiRoots) {
+ View* view1 = window_manager()->CreateView();
+ view1->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view1);
+ View* view2 = window_manager()->CreateView();
+ view2->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view2);
+ ViewManager* embedded1 = Embed(window_manager(), view1);
+ ASSERT_NE(nullptr, embedded1);
+ ViewManager* embedded2 = Embed(window_manager(), view2);
+ ASSERT_NE(nullptr, embedded2);
+ EXPECT_NE(embedded1, embedded2);
+}
+
+TEST_F(ViewManagerTest, EmbeddingIdentity) {
+ View* view = window_manager()->CreateView();
+ view->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view);
+ ViewManager* embedded = Embed(window_manager(), view);
+ ASSERT_NE(nullptr, embedded);
+ EXPECT_EQ(application_impl()->url(), embedded->GetEmbedderURL());
+}
+
+// TODO(alhaad): Currently, the RunLoop gets stuck waiting for order change.
+// Debug and re-enable this.
+TEST_F(ViewManagerTest, DISABLED_Reorder) {
+ View* view1 = window_manager()->CreateView();
+ view1->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view1);
+
+ ViewManager* embedded = Embed(window_manager(), view1);
+ ASSERT_NE(nullptr, embedded);
+
+ View* view11 = embedded->CreateView();
+ view11->SetVisible(true);
+ embedded->GetRoot()->AddChild(view11);
+ View* view12 = embedded->CreateView();
+ view12->SetVisible(true);
+ embedded->GetRoot()->AddChild(view12);
+
+ View* root_in_embedded = embedded->GetRoot();
+
+ {
+ ASSERT_TRUE(WaitForTreeSizeToMatch(root_in_embedded, 3u));
+ view11->MoveToFront();
+ ASSERT_TRUE(WaitForOrderChange(embedded, root_in_embedded));
+
+ EXPECT_EQ(root_in_embedded->children().front(),
+ embedded->GetViewById(view12->id()));
+ EXPECT_EQ(root_in_embedded->children().back(),
+ embedded->GetViewById(view11->id()));
+ }
+
+ {
+ view11->MoveToBack();
+ ASSERT_TRUE(WaitForOrderChange(embedded,
+ embedded->GetViewById(view11->id())));
+
+ EXPECT_EQ(root_in_embedded->children().front(),
+ embedded->GetViewById(view11->id()));
+ EXPECT_EQ(root_in_embedded->children().back(),
+ embedded->GetViewById(view12->id()));
+ }
+}
+
+namespace {
+
+class VisibilityChangeObserver : public ViewObserver {
+ public:
+ explicit VisibilityChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ ~VisibilityChangeObserver() override { view_->RemoveObserver(this); }
+
+ private:
+ // Overridden from ViewObserver:
+ void OnViewVisibilityChanged(View* view) override {
+ EXPECT_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewManagerTest, Visible) {
+ View* view1 = window_manager()->CreateView();
+ view1->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view1);
+
+ // Embed another app and verify initial state.
+ ViewManager* embedded = Embed(window_manager(), view1);
+ ASSERT_NE(nullptr, embedded);
+ ASSERT_NE(nullptr, embedded->GetRoot());
+ View* embedded_root = embedded->GetRoot();
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_TRUE(embedded_root->IsDrawn());
+
+ // Change the visible state from the first connection and verify its mirrored
+ // correctly to the embedded app.
+ {
+ VisibilityChangeObserver observer(embedded_root);
+ view1->SetVisible(false);
+ ASSERT_TRUE(DoRunLoopWithTimeout());
+ }
+
+ EXPECT_FALSE(view1->visible());
+ EXPECT_FALSE(view1->IsDrawn());
+
+ EXPECT_FALSE(embedded_root->visible());
+ EXPECT_FALSE(embedded_root->IsDrawn());
+
+ // Make the node visible again.
+ {
+ VisibilityChangeObserver observer(embedded_root);
+ view1->SetVisible(true);
+ ASSERT_TRUE(DoRunLoopWithTimeout());
+ }
+
+ EXPECT_TRUE(view1->visible());
+ EXPECT_TRUE(view1->IsDrawn());
+
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_TRUE(embedded_root->IsDrawn());
+}
+
+namespace {
+
+class DrawnChangeObserver : public ViewObserver {
+ public:
+ explicit DrawnChangeObserver(View* view) : view_(view) {
+ view_->AddObserver(this);
+ }
+ ~DrawnChangeObserver() override { view_->RemoveObserver(this); }
+
+ private:
+ // Overridden from ViewObserver:
+ void OnViewDrawnChanged(View* view) override {
+ EXPECT_EQ(view, view_);
+ QuitRunLoop();
+ }
+
+ View* view_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewManagerTest, Drawn) {
+ View* view1 = window_manager()->CreateView();
+ view1->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view1);
+
+ // Embed another app and verify initial state.
+ ViewManager* embedded = Embed(window_manager(), view1);
+ ASSERT_NE(nullptr, embedded);
+ ASSERT_NE(nullptr, embedded->GetRoot());
+ View* embedded_root = embedded->GetRoot();
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_TRUE(embedded_root->IsDrawn());
+
+ // Change the visibility of the root, this should propagate a drawn state
+ // change to |embedded|.
+ {
+ DrawnChangeObserver observer(embedded_root);
+ window_manager()->GetRoot()->SetVisible(false);
+ ASSERT_TRUE(DoRunLoopWithTimeout());
+ }
+
+ EXPECT_TRUE(view1->visible());
+ EXPECT_FALSE(view1->IsDrawn());
+
+ EXPECT_TRUE(embedded_root->visible());
+ EXPECT_FALSE(embedded_root->IsDrawn());
+}
+
+// TODO(beng): tests for view event dispatcher.
+// - verify that we see events for all views.
+
+namespace {
+
+class FocusChangeObserver : public ViewObserver {
+ public:
+ explicit FocusChangeObserver(View* view)
+ : view_(view), last_gained_focus_(nullptr), last_lost_focus_(nullptr) {
+ view_->AddObserver(this);
+ }
+ ~FocusChangeObserver() override { view_->RemoveObserver(this); }
+
+ View* last_gained_focus() { return last_gained_focus_; }
+
+ View* last_lost_focus() { return last_lost_focus_; }
+
+ private:
+ // Overridden from ViewObserver.
+ void OnViewFocusChanged(View* gained_focus, View* lost_focus) override {
+ last_gained_focus_ = gained_focus;
+ last_lost_focus_ = lost_focus;
+ QuitRunLoop();
+ }
+
+ View* view_;
+ View* last_gained_focus_;
+ View* last_lost_focus_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver);
+};
+
+} // namespace
+
+TEST_F(ViewManagerTest, Focus) {
+ View* view1 = window_manager()->CreateView();
+ view1->SetVisible(true);
+ window_manager()->GetRoot()->AddChild(view1);
+
+ ViewManager* embedded = Embed(window_manager(), view1);
+ ASSERT_NE(nullptr, embedded);
+ View* view11 = embedded->CreateView();
+ view11->SetVisible(true);
+ embedded->GetRoot()->AddChild(view11);
+
+ // TODO(alhaad): Figure out why switching focus between views from different
+ // connections is causing the tests to crash and add tests for that.
+ {
+ View* embedded_root = embedded->GetRoot();
+ FocusChangeObserver observer(embedded_root);
+ embedded_root->SetFocus();
+ ASSERT_TRUE(DoRunLoopWithTimeout());
+ ASSERT_NE(nullptr, observer.last_gained_focus());
+ EXPECT_EQ(embedded_root->id(), observer.last_gained_focus()->id());
+ }
+ {
+ FocusChangeObserver observer(view11);
+ view11->SetFocus();
+ ASSERT_TRUE(DoRunLoopWithTimeout());
+ ASSERT_NE(nullptr, observer.last_gained_focus());
+ ASSERT_NE(nullptr, observer.last_lost_focus());
+ EXPECT_EQ(view11->id(), observer.last_gained_focus()->id());
+ EXPECT_EQ(embedded->GetRoot()->id(), observer.last_lost_focus()->id());
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/services/view_manager/view_manager_service_apptest.cc b/mojo/services/view_manager/view_manager_service_apptest.cc
new file mode 100644
index 0000000..1237cc8
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_service_apptest.cc
@@ -0,0 +1,1499 @@
+// Copyright 2014 The Chromium Authors. 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/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/services/view_manager/ids.h"
+#include "mojo/services/view_manager/test_change_tracker.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h"
+
+using mojo::ApplicationConnection;
+using mojo::ApplicationDelegate;
+using mojo::Array;
+using mojo::Callback;
+using mojo::ConnectionSpecificId;
+using mojo::ERROR_CODE_NONE;
+using mojo::ErrorCode;
+using mojo::EventPtr;
+using mojo::Id;
+using mojo::InterfaceRequest;
+using mojo::ORDER_DIRECTION_ABOVE;
+using mojo::ORDER_DIRECTION_BELOW;
+using mojo::OrderDirection;
+using mojo::RectPtr;
+using mojo::ServiceProvider;
+using mojo::ServiceProviderPtr;
+using mojo::String;
+using mojo::ViewDataPtr;
+using mojo::ViewManagerClient;
+using mojo::ViewManagerService;
+using mojo::ViewportMetricsPtr;
+
+namespace view_manager {
+
+// Creates an id used for transport from the specified parameters.
+Id BuildViewId(ConnectionSpecificId connection_id,
+ ConnectionSpecificId view_id) {
+ return (connection_id << 16) | view_id;
+}
+
+// Callback function from ViewManagerService functions. ------------------------
+
+void BoolResultCallback(base::RunLoop* run_loop,
+ bool* result_cache,
+ bool result) {
+ *result_cache = result;
+ run_loop->Quit();
+}
+
+void ErrorCodeResultCallback(base::RunLoop* run_loop,
+ ErrorCode* result_cache,
+ ErrorCode result) {
+ *result_cache = result;
+ run_loop->Quit();
+}
+
+void ViewTreeResultCallback(base::RunLoop* run_loop,
+ std::vector<TestView>* views,
+ Array<ViewDataPtr> results) {
+ ViewDatasToTestViews(results, views);
+ run_loop->Quit();
+}
+
+// -----------------------------------------------------------------------------
+
+// The following functions call through to the supplied ViewManagerService. They
+// block until call completes and return the result.
+bool CreateView(ViewManagerService* vm, Id view_id) {
+ ErrorCode result = ERROR_CODE_NONE;
+ base::RunLoop run_loop;
+ vm->CreateView(view_id,
+ base::Bind(&ErrorCodeResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result == ERROR_CODE_NONE;
+}
+
+bool EmbedUrl(ViewManagerService* vm, const String& url, Id root_id) {
+ bool result = false;
+ base::RunLoop run_loop;
+ {
+ vm->EmbedUrl(url, root_id, nullptr, nullptr,
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ }
+ run_loop.Run();
+ return result;
+}
+
+bool Embed(ViewManagerService* vm,
+ Id root_id,
+ mojo::ViewManagerClientPtr client) {
+ bool result = false;
+ base::RunLoop run_loop;
+ {
+ vm->Embed(root_id, client.Pass(),
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ }
+ run_loop.Run();
+ return result;
+}
+
+ErrorCode CreateViewWithErrorCode(ViewManagerService* vm, Id view_id) {
+ ErrorCode result = ERROR_CODE_NONE;
+ base::RunLoop run_loop;
+ vm->CreateView(view_id,
+ base::Bind(&ErrorCodeResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+bool AddView(ViewManagerService* vm, Id parent, Id child) {
+ bool result = false;
+ base::RunLoop run_loop;
+ vm->AddView(parent, child,
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+bool RemoveViewFromParent(ViewManagerService* vm, Id view_id) {
+ bool result = false;
+ base::RunLoop run_loop;
+ vm->RemoveViewFromParent(view_id,
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+bool ReorderView(ViewManagerService* vm,
+ Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) {
+ bool result = false;
+ base::RunLoop run_loop;
+ vm->ReorderView(view_id, relative_view_id, direction,
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+void GetViewTree(ViewManagerService* vm,
+ Id view_id,
+ std::vector<TestView>* views) {
+ base::RunLoop run_loop;
+ vm->GetViewTree(view_id,
+ base::Bind(&ViewTreeResultCallback, &run_loop, views));
+ run_loop.Run();
+}
+
+bool DeleteView(ViewManagerService* vm, Id view_id) {
+ base::RunLoop run_loop;
+ bool result = false;
+ vm->DeleteView(view_id, base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+bool SetViewBounds(ViewManagerService* vm,
+ Id view_id,
+ int x,
+ int y,
+ int w,
+ int h) {
+ base::RunLoop run_loop;
+ bool result = false;
+ RectPtr rect(mojo::Rect::New());
+ rect->x = x;
+ rect->y = y;
+ rect->width = w;
+ rect->height = h;
+ vm->SetViewBounds(view_id, rect.Pass(),
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+bool SetViewVisibility(ViewManagerService* vm, Id view_id, bool visible) {
+ base::RunLoop run_loop;
+ bool result = false;
+ vm->SetViewVisibility(view_id, visible,
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+bool SetViewProperty(ViewManagerService* vm,
+ Id view_id,
+ const std::string& name,
+ const std::vector<uint8_t>* data) {
+ base::RunLoop run_loop;
+ bool result = false;
+ Array<uint8_t> mojo_data;
+ if (data)
+ mojo_data = Array<uint8_t>::From(*data);
+ vm->SetViewProperty(view_id, name, mojo_data.Pass(),
+ base::Bind(&BoolResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+// Utility functions -----------------------------------------------------------
+
+// Waits for all messages to be received by |vm|. This is done by attempting to
+// create a bogus view. When we get the response we know all messages have been
+// processed.
+bool WaitForAllMessages(ViewManagerService* vm) {
+ ErrorCode result = ERROR_CODE_NONE;
+ base::RunLoop run_loop;
+ vm->CreateView(ViewIdToTransportId(InvalidViewId()),
+ base::Bind(&ErrorCodeResultCallback, &run_loop, &result));
+ run_loop.Run();
+ return result != ERROR_CODE_NONE;
+}
+
+bool HasClonedView(const std::vector<TestView>& views) {
+ for (size_t i = 0; i < views.size(); ++i)
+ if (views[i].view_id == ViewIdToTransportId(ClonedViewId()))
+ return true;
+ return false;
+}
+
+// -----------------------------------------------------------------------------
+
+// A ViewManagerClient implementation that logs all changes to a tracker.
+class ViewManagerClientImpl : public mojo::ViewManagerClient,
+ public TestChangeTracker::Delegate {
+ public:
+ ViewManagerClientImpl() : binding_(this) { tracker_.set_delegate(this); }
+
+ void Bind(mojo::InterfaceRequest<mojo::ViewManagerClient> request) {
+ binding_.Bind(request.Pass());
+ }
+
+ mojo::ViewManagerService* service() { return service_.get(); }
+ TestChangeTracker* tracker() { return &tracker_; }
+
+ // Runs a nested MessageLoop until |count| changes (calls to
+ // ViewManagerClient functions) have been received.
+ void WaitForChangeCount(size_t count) {
+ if (count == tracker_.changes()->size())
+ return;
+
+ ASSERT_TRUE(wait_state_.get() == nullptr);
+ wait_state_.reset(new WaitState);
+ wait_state_->change_count = count;
+ wait_state_->run_loop.Run();
+ wait_state_.reset();
+ }
+
+ // Runs a nested MessageLoop until OnEmbed() has been encountered.
+ void WaitForOnEmbed() {
+ if (service_)
+ return;
+ embed_run_loop_.reset(new base::RunLoop);
+ embed_run_loop_->Run();
+ embed_run_loop_.reset();
+ }
+
+ bool WaitForIncomingMethodCall() {
+ return binding_.WaitForIncomingMethodCall();
+ }
+
+ private:
+ // Used when running a nested MessageLoop.
+ struct WaitState {
+ WaitState() : change_count(0) {}
+
+ // Number of changes waiting for.
+ size_t change_count;
+ base::RunLoop run_loop;
+ };
+
+ // TestChangeTracker::Delegate:
+ void OnChangeAdded() override {
+ if (wait_state_.get() &&
+ wait_state_->change_count == tracker_.changes()->size()) {
+ wait_state_->run_loop.Quit();
+ }
+ }
+
+ // ViewManagerClient:
+ void OnEmbed(ConnectionSpecificId connection_id,
+ const String& creator_url,
+ ViewDataPtr root,
+ mojo::ViewManagerServicePtr view_manager_service,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ mojo::ScopedMessagePipeHandle window_manager_pipe) override {
+ service_ = view_manager_service.Pass();
+ tracker()->OnEmbed(connection_id, creator_url, root.Pass());
+ if (embed_run_loop_)
+ embed_run_loop_->Quit();
+ }
+ void OnEmbeddedAppDisconnected(Id view_id) override {
+ tracker()->OnEmbeddedAppDisconnected(view_id);
+ }
+ void OnViewBoundsChanged(Id view_id,
+ RectPtr old_bounds,
+ RectPtr new_bounds) override {
+ tracker()->OnViewBoundsChanged(view_id, old_bounds.Pass(),
+ new_bounds.Pass());
+ }
+ void OnViewViewportMetricsChanged(ViewportMetricsPtr old_metrics,
+ ViewportMetricsPtr new_metrics) override {
+ tracker()->OnViewViewportMetricsChanged(old_metrics.Pass(),
+ new_metrics.Pass());
+ }
+ void OnViewHierarchyChanged(Id view,
+ Id new_parent,
+ Id old_parent,
+ Array<ViewDataPtr> views) override {
+ tracker()->OnViewHierarchyChanged(view, new_parent, old_parent,
+ views.Pass());
+ }
+ void OnViewReordered(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction) override {
+ tracker()->OnViewReordered(view_id, relative_view_id, direction);
+ }
+ void OnViewDeleted(Id view) override { tracker()->OnViewDeleted(view); }
+ void OnViewVisibilityChanged(uint32_t view, bool visible) override {
+ tracker()->OnViewVisibilityChanged(view, visible);
+ }
+ void OnViewDrawnStateChanged(uint32_t view, bool drawn) override {
+ tracker()->OnViewDrawnStateChanged(view, drawn);
+ }
+ void OnViewInputEvent(Id view_id,
+ EventPtr event,
+ const Callback<void()>& callback) override {
+ tracker()->OnViewInputEvent(view_id, event.Pass());
+ callback.Run();
+ }
+ void OnViewSharedPropertyChanged(uint32_t view,
+ const String& name,
+ Array<uint8_t> new_data) override {
+ tracker_.OnViewSharedPropertyChanged(view, name, new_data.Pass());
+ }
+ void OnPerformAction(uint32_t view,
+ const String& name,
+ const Callback<void(bool)>& callback) override {}
+
+ TestChangeTracker tracker_;
+
+ mojo::ViewManagerServicePtr service_;
+
+ // If non-null we're waiting for OnEmbed() using this RunLoop.
+ scoped_ptr<base::RunLoop> embed_run_loop_;
+
+ // If non-null we're waiting for a certain number of change notifications to
+ // be encountered.
+ scoped_ptr<WaitState> wait_state_;
+
+ mojo::Binding<ViewManagerClient> binding_;
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl);
+};
+
+// -----------------------------------------------------------------------------
+
+// InterfaceFactory for vending ViewManagerClientImpls.
+class ViewManagerClientFactory
+ : public mojo::InterfaceFactory<ViewManagerClient> {
+ public:
+ ViewManagerClientFactory() {}
+ ~ViewManagerClientFactory() override {}
+
+ // Runs a nested MessageLoop until a new instance has been created.
+ scoped_ptr<ViewManagerClientImpl> WaitForInstance() {
+ if (!client_impl_.get()) {
+ DCHECK(!run_loop_.get());
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ run_loop_.reset();
+ }
+ return client_impl_.Pass();
+ }
+
+ private:
+ // InterfaceFactory<ViewManagerClient>:
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<ViewManagerClient> request) override {
+ client_impl_.reset(new ViewManagerClientImpl);
+ client_impl_->Bind(request.Pass());
+ if (run_loop_.get())
+ run_loop_->Quit();
+ }
+
+ scoped_ptr<ViewManagerClientImpl> client_impl_;
+ scoped_ptr<base::RunLoop> run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerClientFactory);
+};
+
+class ViewManagerServiceAppTest
+ : public mojo::test::ApplicationTestBase,
+ public ApplicationDelegate,
+ public mojo::InterfaceFactory<mojo::WindowManagerInternal>,
+ public mojo::WindowManagerInternal {
+ public:
+ ViewManagerServiceAppTest() : wm_internal_binding_(this) {}
+ ~ViewManagerServiceAppTest() override {}
+
+ protected:
+ // Returns the changes from the various connections.
+ std::vector<Change>* changes1() { return vm_client1_.tracker()->changes(); }
+ std::vector<Change>* changes2() { return vm_client2_->tracker()->changes(); }
+ std::vector<Change>* changes3() { return vm_client3_->tracker()->changes(); }
+
+ // Various connections. |vm1()|, being the first connection, has special
+ // permissions (it's treated as the window manager).
+ ViewManagerService* vm1() { return vm1_.get(); }
+ ViewManagerService* vm2() { return vm_client2_->service(); }
+ ViewManagerService* vm3() { return vm_client3_->service(); }
+
+ void EstablishSecondConnectionWithRoot(Id root_id) {
+ ASSERT_TRUE(vm_client2_.get() == nullptr);
+ vm_client2_ = EstablishConnectionViaEmbed(vm1(), root_id);
+ ASSERT_TRUE(vm_client2_.get() != nullptr);
+ }
+
+ void EstablishSecondConnection(bool create_initial_view) {
+ if (create_initial_view)
+ ASSERT_TRUE(CreateView(vm1_.get(), BuildViewId(1, 1)));
+ ASSERT_NO_FATAL_FAILURE(
+ EstablishSecondConnectionWithRoot(BuildViewId(1, 1)));
+
+ if (create_initial_view)
+ EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(*changes2()));
+ }
+
+ void EstablishThirdConnection(ViewManagerService* owner, Id root_id) {
+ ASSERT_TRUE(vm_client3_.get() == nullptr);
+ vm_client3_ = EstablishConnectionViaEmbed(owner, root_id);
+ ASSERT_TRUE(vm_client3_.get() != nullptr);
+ }
+
+ // Establishes a new connection by way of Embed() on the specified
+ // ViewManagerService.
+ scoped_ptr<ViewManagerClientImpl> EstablishConnectionViaEmbed(
+ ViewManagerService* owner,
+ Id root_id) {
+ if (!EmbedUrl(owner, application_impl()->url(), root_id)) {
+ ADD_FAILURE() << "Embed() failed";
+ return nullptr;
+ }
+ scoped_ptr<ViewManagerClientImpl> client =
+ client_factory_.WaitForInstance();
+ if (!client.get()) {
+ ADD_FAILURE() << "WaitForInstance failed";
+ return nullptr;
+ }
+ client->WaitForOnEmbed();
+
+ const std::string expected_creator =
+ owner == vm1() ? "mojo:window_manager" : application_impl()->url();
+ EXPECT_EQ("OnEmbed creator=" + expected_creator,
+ SingleChangeToDescription(*client->tracker()->changes()));
+ return client.Pass();
+ }
+
+ // ApplicationTestBase:
+ ApplicationDelegate* GetApplicationDelegate() override { return this; }
+ void SetUp() override {
+ ApplicationTestBase::SetUp();
+ ApplicationConnection* vm_connection =
+ application_impl()->ConnectToApplication("mojo:view_manager");
+ vm_connection->AddService(this);
+ vm_connection->ConnectToService(&vm1_);
+ vm_connection->ConnectToService(&wm_internal_client_);
+ // Spin a run loop until the view manager service sends us the
+ // ViewManagerClient pipe to use for the "window manager" connection.
+ view_manager_setup_run_loop_.reset(new base::RunLoop);
+ view_manager_setup_run_loop_->Run();
+ view_manager_setup_run_loop_ = nullptr;
+ // Next we should get an embed call on the "window manager" client.
+ vm_client1_.WaitForIncomingMethodCall();
+ ASSERT_EQ(1u, changes1()->size());
+ EXPECT_EQ(CHANGE_TYPE_EMBED, (*changes1())[0].type);
+ // All these tests assume 1 for the client id. The only real assertion here
+ // is the client id is not zero, but adding this as rest of code here
+ // assumes 1.
+ ASSERT_EQ(1, (*changes1())[0].connection_id);
+ changes1()->clear();
+ }
+
+ // ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ connection->AddService(&client_factory_);
+ return true;
+ }
+
+ // mojo::InterfaceFactory<mojo::WindowManagerInternal> implementation.
+ void Create(
+ ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::WindowManagerInternal> request) override {
+ DCHECK(!wm_internal_binding_.is_bound());
+ wm_internal_binding_.Bind(request.Pass());
+ }
+
+ // mojo::WindowManagerInternal implementation.
+ void CreateWindowManagerForViewManagerClient(
+ uint16_t connection_id,
+ mojo::ScopedMessagePipeHandle window_manager_pipe) override {}
+ void SetViewManagerClient(
+ mojo::ScopedMessagePipeHandle view_manager_client_request) override {
+ auto typed_request = mojo::MakeRequest<mojo::ViewManagerClient>(
+ view_manager_client_request.Pass());
+ vm_client1_.Bind(typed_request.Pass());
+ view_manager_setup_run_loop_->Quit();
+ }
+
+ mojo::Binding<mojo::WindowManagerInternal> wm_internal_binding_;
+ mojo::WindowManagerInternalClientPtr wm_internal_client_;
+ ViewManagerClientImpl vm_client1_;
+ scoped_ptr<ViewManagerClientImpl> vm_client2_;
+ scoped_ptr<ViewManagerClientImpl> vm_client3_;
+
+ private:
+ mojo::ViewManagerServicePtr vm1_;
+ ViewManagerClientFactory client_factory_;
+ scoped_ptr<base::RunLoop> view_manager_setup_run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceAppTest);
+};
+
+// Verifies two clients/connections get different ids.
+TEST_F(ViewManagerServiceAppTest, TwoClientsGetDifferentConnectionIds) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // It isn't strictly necessary that the second connection gets 2, but these
+ // tests are written assuming that is the case. The key thing is the
+ // connection ids of |connection_| and |connection2_| differ.
+ ASSERT_EQ(1u, changes2()->size());
+ ASSERT_EQ(2, (*changes2())[0].connection_id);
+}
+
+// Verifies when Embed() is invoked any child views are removed.
+TEST_F(ViewManagerServiceAppTest, ViewsRemovedWhenEmbedding) {
+ // Two views 1 and 2. 2 is parented to 1.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+ EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(*changes2()));
+
+ // Embed() removed view 2.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 2), &views);
+ EXPECT_EQ("view=1,2 parent=null", SingleViewDescription(views));
+ }
+
+ // vm2 should not see view 2.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(1, 1), &views);
+ EXPECT_EQ("view=1,1 parent=null", SingleViewDescription(views));
+ }
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(1, 2), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Views 3 and 4 in connection 2.
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 4)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 3), BuildViewId(2, 4)));
+
+ // Connection 3 rooted at 2.
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 3)));
+
+ // View 4 should no longer have a parent.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(2, 3), &views);
+ EXPECT_EQ("view=2,3 parent=null", SingleViewDescription(views));
+
+ views.clear();
+ GetViewTree(vm2(), BuildViewId(2, 4), &views);
+ EXPECT_EQ("view=2,4 parent=null", SingleViewDescription(views));
+ }
+
+ // And view 4 should not be visible to connection 3.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm3(), BuildViewId(2, 3), &views);
+ EXPECT_EQ("view=2,3 parent=null", SingleViewDescription(views));
+ }
+}
+
+// Verifies once Embed() has been invoked the parent connection can't see any
+// children.
+TEST_F(ViewManagerServiceAppTest, CantAccessChildrenOfEmbeddedView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2)));
+
+ ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 3)));
+ ASSERT_TRUE(AddView(vm3(), BuildViewId(2, 2), BuildViewId(3, 3)));
+
+ // Even though 3 is a child of 2 connection 2 can't see 3 as it's from a
+ // different connection.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(2, 2), &views);
+ EXPECT_EQ("view=2,2 parent=1,1", SingleViewDescription(views));
+ }
+
+ // Connection 2 shouldn't be able to get view 3 at all.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(3, 3), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Connection 1 should be able to see it all (its the root).
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(3u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString());
+ EXPECT_EQ("view=3,3 parent=2,2", views[2].ToString());
+ }
+}
+
+// Verifies once Embed() has been invoked the parent can't mutate the children.
+TEST_F(ViewManagerServiceAppTest, CantModifyChildrenOfEmbeddedView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2)));
+
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3)));
+ // Connection 2 shouldn't be able to add anything to the view anymore.
+ ASSERT_FALSE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3)));
+
+ // Create view 3 in connection 3 and add it to view 3.
+ ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 3)));
+ ASSERT_TRUE(AddView(vm3(), BuildViewId(2, 2), BuildViewId(3, 3)));
+
+ // Connection 2 shouldn't be able to remove view 3.
+ ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(3, 3)));
+}
+
+// Verifies client gets a valid id.
+TEST_F(ViewManagerServiceAppTest, CreateView) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ EXPECT_TRUE(changes1()->empty());
+
+ // Can't create a view with the same id.
+ ASSERT_EQ(mojo::ERROR_CODE_VALUE_IN_USE,
+ CreateViewWithErrorCode(vm1(), BuildViewId(1, 1)));
+ EXPECT_TRUE(changes1()->empty());
+
+ // Can't create a view with a bogus connection id.
+ EXPECT_EQ(mojo::ERROR_CODE_ILLEGAL_ARGUMENT,
+ CreateViewWithErrorCode(vm1(), BuildViewId(2, 1)));
+ EXPECT_TRUE(changes1()->empty());
+}
+
+// Verifies AddView fails when view is already in position.
+TEST_F(ViewManagerServiceAppTest, AddViewWithNoChange) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Make 3 a child of 2.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3)));
+
+ // Try again, this should fail.
+ EXPECT_FALSE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3)));
+}
+
+// Verifies AddView fails when view is already in position.
+TEST_F(ViewManagerServiceAppTest, AddAncestorFails) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Make 3 a child of 2.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3)));
+
+ // Try to make 2 a child of 3, this should fail since 2 is an ancestor of 3.
+ EXPECT_FALSE(AddView(vm1(), BuildViewId(1, 3), BuildViewId(1, 2)));
+}
+
+// Verifies adding to root sends right notifications.
+TEST_F(ViewManagerServiceAppTest, AddToRoot) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 21)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ changes2()->clear();
+
+ // Make 3 a child of 21.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 21), BuildViewId(1, 3)));
+
+ // Make 21 a child of 1.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 21)));
+
+ // Connection 2 should not be told anything (because the view is from a
+ // different connection). Create a view to ensure we got a response from
+ // the server.
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 100)));
+ EXPECT_TRUE(changes2()->empty());
+}
+
+// Verifies HierarchyChanged is correctly sent for various adds/removes.
+TEST_F(ViewManagerServiceAppTest, ViewHierarchyChangedViews) {
+ // 1,2->1,11.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 11)));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 11), true));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 11)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true));
+
+ ASSERT_TRUE(WaitForAllMessages(vm2()));
+ changes2()->clear();
+
+ // 1,1->1,2->1,11
+ {
+ // Client 2 should not get anything (1,2 is from another connection).
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2)));
+ ASSERT_TRUE(WaitForAllMessages(vm2()));
+ EXPECT_TRUE(changes2()->empty());
+ }
+
+ // 0,1->1,1->1,2->1,11.
+ {
+ // Client 2 is now connected to the root, so it should have gotten a drawn
+ // notification.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ vm_client2_->WaitForChangeCount(1u);
+ EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ // 1,1->1,2->1,11.
+ {
+ // Client 2 is no longer connected to the root, should get drawn state
+ // changed.
+ changes2()->clear();
+ ASSERT_TRUE(RemoveViewFromParent(vm1(), BuildViewId(1, 1)));
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("DrawnStateChanged view=1,1 drawn=false",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ // 1,1->1,2->1,11->1,111.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 111)));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 111), true));
+ {
+ changes2()->clear();
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 11), BuildViewId(1, 111)));
+ ASSERT_TRUE(WaitForAllMessages(vm2()));
+ EXPECT_TRUE(changes2()->empty());
+ }
+
+ // 0,1->1,1->1,2->1,11->1,111
+ {
+ changes2()->clear();
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true",
+ SingleChangeToDescription(*changes2()));
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, ViewHierarchyChangedAddingKnownToUnknown) {
+ // Create the following structure: root -> 1 -> 11 and 2->21 (2 has no
+ // parent).
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 11)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 21)));
+
+ // Set up the hierarchy.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 11)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 21)));
+
+ // Remove 11, should result in a hierarchy change for the root.
+ {
+ changes1()->clear();
+ ASSERT_TRUE(RemoveViewFromParent(vm2(), BuildViewId(2, 11)));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("HierarchyChanged view=2,11 new_parent=null old_parent=1,1",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ // Add 2 to 1.
+ {
+ changes1()->clear();
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ SingleChangeToDescription(*changes1()));
+ EXPECT_EQ(
+ "[view=2,2 parent=1,1],"
+ "[view=2,21 parent=2,2]",
+ ChangeViewDescription(*changes1()));
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, ReorderView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ Id view1_id = BuildViewId(2, 1);
+ Id view2_id = BuildViewId(2, 2);
+ Id view3_id = BuildViewId(2, 3);
+ Id view4_id = BuildViewId(1, 4); // Peer to 1,1
+ Id view5_id = BuildViewId(1, 5); // Peer to 1,1
+ Id view6_id = BuildViewId(2, 6); // Child of 1,2.
+ Id view7_id = BuildViewId(2, 7); // Unparented.
+ Id view8_id = BuildViewId(2, 8); // Unparented.
+ ASSERT_TRUE(CreateView(vm2(), view1_id));
+ ASSERT_TRUE(CreateView(vm2(), view2_id));
+ ASSERT_TRUE(CreateView(vm2(), view3_id));
+ ASSERT_TRUE(CreateView(vm1(), view4_id));
+ ASSERT_TRUE(CreateView(vm1(), view5_id));
+ ASSERT_TRUE(CreateView(vm2(), view6_id));
+ ASSERT_TRUE(CreateView(vm2(), view7_id));
+ ASSERT_TRUE(CreateView(vm2(), view8_id));
+ ASSERT_TRUE(AddView(vm2(), view1_id, view2_id));
+ ASSERT_TRUE(AddView(vm2(), view2_id, view6_id));
+ ASSERT_TRUE(AddView(vm2(), view1_id, view3_id));
+ ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view4_id));
+ ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view5_id));
+ ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view1_id));
+
+ {
+ changes1()->clear();
+ ASSERT_TRUE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_ABOVE));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=above",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ {
+ changes1()->clear();
+ ASSERT_TRUE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_BELOW));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=below",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ // view2 is already below view3.
+ EXPECT_FALSE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_BELOW));
+
+ // view4 & 5 are unknown to connection2_.
+ EXPECT_FALSE(ReorderView(vm2(), view4_id, view5_id, ORDER_DIRECTION_ABOVE));
+
+ // view6 & view3 have different parents.
+ EXPECT_FALSE(ReorderView(vm1(), view3_id, view6_id, ORDER_DIRECTION_ABOVE));
+
+ // Non-existent view-ids
+ EXPECT_FALSE(ReorderView(vm1(), BuildViewId(1, 27), BuildViewId(1, 28),
+ ORDER_DIRECTION_ABOVE));
+
+ // view7 & view8 are un-parented.
+ EXPECT_FALSE(ReorderView(vm1(), view7_id, view8_id, ORDER_DIRECTION_ABOVE));
+}
+
+// Verifies DeleteView works.
+TEST_F(ViewManagerServiceAppTest, DeleteView) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+
+ // Make 2 a child of 1.
+ {
+ changes1()->clear();
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ // Delete 2.
+ {
+ changes1()->clear();
+ changes2()->clear();
+ ASSERT_TRUE(DeleteView(vm2(), BuildViewId(2, 2)));
+ EXPECT_TRUE(changes2()->empty());
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("ViewDeleted view=2,2", SingleChangeToDescription(*changes1()));
+ }
+}
+
+// Verifies DeleteView isn't allowed from a separate connection.
+TEST_F(ViewManagerServiceAppTest, DeleteViewFromAnotherConnectionDisallowed) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ EXPECT_FALSE(DeleteView(vm2(), BuildViewId(1, 1)));
+}
+
+// Verifies if a view was deleted and then reused that other clients are
+// properly notified.
+TEST_F(ViewManagerServiceAppTest, ReuseDeletedViewId) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+
+ // Add 2 to 1.
+ {
+ changes1()->clear();
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ SingleChangeToDescription(*changes1()));
+ EXPECT_EQ("[view=2,2 parent=1,1]", ChangeViewDescription(*changes1()));
+ }
+
+ // Delete 2.
+ {
+ changes1()->clear();
+ ASSERT_TRUE(DeleteView(vm2(), BuildViewId(2, 2)));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("ViewDeleted view=2,2", SingleChangeToDescription(*changes1()));
+ }
+
+ // Create 2 again, and add it back to 1. Should get the same notification.
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ {
+ changes1()->clear();
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null",
+ SingleChangeToDescription(*changes1()));
+ EXPECT_EQ("[view=2,2 parent=1,1]", ChangeViewDescription(*changes1()));
+ }
+}
+
+// Assertions for GetViewTree.
+TEST_F(ViewManagerServiceAppTest, GetViewTree) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+
+ // Create 11 in first connection and make it a child of 1.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 11)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 11)));
+
+ // Create two views in second connection, 2 and 3, both children of 1.
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 3)));
+
+ // Verifies GetViewTree() on the root. The root connection sees all.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(0, 1), &views);
+ ASSERT_EQ(5u, views.size());
+ EXPECT_EQ("view=0,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString());
+ EXPECT_EQ("view=1,11 parent=1,1", views[2].ToString());
+ EXPECT_EQ("view=2,2 parent=1,1", views[3].ToString());
+ EXPECT_EQ("view=2,3 parent=1,1", views[4].ToString());
+ }
+
+ // Verifies GetViewTree() on the view 1,1 from vm2(). vm2() sees 1,1 as 1,1
+ // is vm2()'s root and vm2() sees all the views it created.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(3u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString());
+ EXPECT_EQ("view=2,3 parent=1,1", views[2].ToString());
+ }
+
+ // Connection 2 shouldn't be able to get the root tree.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(0, 1), &views);
+ ASSERT_EQ(0u, views.size());
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, SetViewBounds) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ changes2()->clear();
+ ASSERT_TRUE(SetViewBounds(vm1(), BuildViewId(1, 1), 0, 0, 100, 100));
+
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("BoundsChanged view=1,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100",
+ SingleChangeToDescription(*changes2()));
+
+ // Should not be possible to change the bounds of a view created by another
+ // connection.
+ ASSERT_FALSE(SetViewBounds(vm2(), BuildViewId(1, 1), 0, 0, 0, 0));
+}
+
+// Verify AddView fails when trying to manipulate views in other roots.
+TEST_F(ViewManagerServiceAppTest, CantMoveViewsFromOtherRoot) {
+ // Create 1 and 2 in the first connection.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ // Try to move 2 to be a child of 1 from connection 2. This should fail as 2
+ // should not be able to access 1.
+ ASSERT_FALSE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(1, 2)));
+
+ // Try to reparent 1 to the root. A connection is not allowed to reparent its
+ // roots.
+ ASSERT_FALSE(AddView(vm2(), BuildViewId(0, 1), BuildViewId(1, 1)));
+}
+
+// Verify RemoveViewFromParent fails for views that are descendants of the
+// roots.
+TEST_F(ViewManagerServiceAppTest, CantRemoveViewsInOtherRoots) {
+ // Create 1 and 2 in the first connection and parent both to the root.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 2)));
+
+ // Establish the second connection and give it the root 1.
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ // Connection 2 should not be able to remove view 2 or 1 from its parent.
+ ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(1, 2)));
+ ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(1, 1)));
+
+ // Create views 10 and 11 in 2.
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 10)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 11)));
+
+ // Parent 11 to 10.
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 10), BuildViewId(2, 11)));
+ // Remove 11 from 10.
+ ASSERT_TRUE(RemoveViewFromParent(vm2(), BuildViewId(2, 11)));
+
+ // Verify nothing was actually removed.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(0, 1), &views);
+ ASSERT_EQ(3u, views.size());
+ EXPECT_EQ("view=0,1 parent=null", views[0].ToString());
+ EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString());
+ EXPECT_EQ("view=1,2 parent=0,1", views[2].ToString());
+ }
+}
+
+// Verify GetViewTree fails for views that are not descendants of the roots.
+TEST_F(ViewManagerServiceAppTest, CantGetViewTreeOfOtherRoots) {
+ // Create 1 and 2 in the first connection and parent both to the root.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 2)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+
+ std::vector<TestView> views;
+
+ // Should get nothing for the root.
+ GetViewTree(vm2(), BuildViewId(0, 1), &views);
+ ASSERT_TRUE(views.empty());
+
+ // Should get nothing for view 2.
+ GetViewTree(vm2(), BuildViewId(1, 2), &views);
+ ASSERT_TRUE(views.empty());
+
+ // Should get view 1 if asked for.
+ GetViewTree(vm2(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+}
+
+TEST_F(ViewManagerServiceAppTest, OnViewInputEvent) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ changes2()->clear();
+
+ // Dispatch an event to the view and verify it's received.
+ {
+ EventPtr event(mojo::Event::New());
+ event->action = static_cast<mojo::EventType>(1);
+ wm_internal_client_->DispatchInputEventToView(BuildViewId(1, 1),
+ event.Pass());
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("InputEvent view=1,1 event_action=1",
+ SingleChangeToDescription(*changes2()));
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, EmbedWithSameViewId) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ changes2()->clear();
+
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm1(), BuildViewId(1, 1)));
+
+ // Connection2 should have been told the view was deleted.
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("ViewDeleted view=1,1", SingleChangeToDescription(*changes2()));
+ }
+
+ // Connection2 has no root. Verify it can't see view 1,1 anymore.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm2(), BuildViewId(1, 1), &views);
+ EXPECT_TRUE(views.empty());
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, EmbedWithSameViewId2) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ changes2()->clear();
+
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm1(), BuildViewId(1, 1)));
+
+ // Connection2 should have been told the view was deleted.
+ vm_client2_->WaitForChangeCount(1);
+ changes2()->clear();
+
+ // Create a view in the third connection and parent it to the root.
+ ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 1)));
+ ASSERT_TRUE(AddView(vm3(), BuildViewId(1, 1), BuildViewId(3, 1)));
+
+ // Connection 1 should have been told about the add (it owns the view).
+ {
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("HierarchyChanged view=3,1 new_parent=1,1 old_parent=null",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ // Embed 1,1 again.
+ {
+ changes3()->clear();
+
+ // We should get a new connection for the new embedding.
+ scoped_ptr<ViewManagerClientImpl> connection4(
+ EstablishConnectionViaEmbed(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(connection4.get());
+ EXPECT_EQ("[view=1,1 parent=null]",
+ ChangeViewDescription(*connection4->tracker()->changes()));
+
+ // And 3 should get a delete.
+ vm_client3_->WaitForChangeCount(1);
+ EXPECT_EQ("ViewDeleted view=1,1", SingleChangeToDescription(*changes3()));
+ }
+
+ // vm3() has no root. Verify it can't see view 1,1 anymore.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm3(), BuildViewId(1, 1), &views);
+ EXPECT_TRUE(views.empty());
+ }
+
+ // Verify 3,1 is no longer parented to 1,1. We have to do this from 1,1 as
+ // vm3() can no longer see 1,1.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=null", views[0].ToString());
+ }
+
+ // Verify vm3() can still see the view it created 3,1.
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm3(), BuildViewId(3, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=3,1 parent=null", views[0].ToString());
+ }
+}
+
+// Assertions for SetViewVisibility.
+TEST_F(ViewManagerServiceAppTest, SetViewVisibility) {
+ // Create 1 and 2 in the first connection and parent both to the root.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(0, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=0,1 parent=null visible=true drawn=true",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false",
+ views[1].ToString2());
+ }
+
+ // Show all the views.
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true));
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(0, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=0,1 parent=null visible=true drawn=true",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true",
+ views[1].ToString2());
+ }
+
+ // Hide 1.
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), false));
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false",
+ views[0].ToString2());
+ }
+
+ // Attach 2 to 1.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2)));
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=false",
+ views[1].ToString2());
+ }
+
+ // Show 1.
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true));
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true",
+ views[0].ToString2());
+ EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=true",
+ views[1].ToString2());
+ }
+}
+
+// Assertions for SetViewVisibility sending notifications.
+TEST_F(ViewManagerServiceAppTest, SetViewVisibilityNotifications) {
+ // Create 1,1 and 1,2. 1,2 is made a child of 1,1 and 1,1 a child of the root.
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true));
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2)));
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2)));
+
+ // Establish the second connection at 1,2.
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnectionWithRoot(BuildViewId(1, 2)));
+
+ // Add 2,3 as a child of 1,2.
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3)));
+ ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(2, 3), true));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 2), BuildViewId(2, 3)));
+ WaitForAllMessages(vm1());
+
+ changes2()->clear();
+ // Hide 1,2 from connection 1. Connection 2 should see this.
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), false));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("VisibilityChanged view=1,2 visible=false",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ changes1()->clear();
+ // Show 1,2 from connection 2, connection 1 should be notified.
+ ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(1, 2), true));
+ {
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("VisibilityChanged view=1,2 visible=true",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ changes2()->clear();
+ // Hide 1,1, connection 2 should be told the draw state changed.
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), false));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ changes2()->clear();
+ // Show 1,1 from connection 1. Connection 2 should see this.
+ ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ // Change visibility of 2,3, connection 1 should see this.
+ changes1()->clear();
+ ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(2, 3), false));
+ {
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("VisibilityChanged view=2,3 visible=false",
+ SingleChangeToDescription(*changes1()));
+ }
+
+ changes2()->clear();
+ // Remove 1,1 from the root, connection 2 should see drawn state changed.
+ ASSERT_TRUE(RemoveViewFromParent(vm1(), BuildViewId(1, 1)));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ changes2()->clear();
+ // Add 1,1 back to the root, connection 2 should see drawn state changed.
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true",
+ SingleChangeToDescription(*changes2()));
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, SetViewProperty) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false));
+ changes2()->clear();
+
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(0, 1), &views);
+ ASSERT_EQ(2u, views.size());
+ EXPECT_EQ(BuildViewId(0, 1), views[0].view_id);
+ EXPECT_EQ(BuildViewId(1, 1), views[1].view_id);
+ ASSERT_EQ(0u, views[1].properties.size());
+ }
+
+ // Set properties on 1.
+ changes2()->clear();
+ std::vector<uint8_t> one(1, '1');
+ ASSERT_TRUE(SetViewProperty(vm1(), BuildViewId(1, 1), "one", &one));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("PropertyChanged view=1,1 key=one value=1",
+ SingleChangeToDescription(*changes2()));
+ }
+
+ // Test that our properties exist in the view tree
+ {
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ ASSERT_EQ(1u, views.size());
+ ASSERT_EQ(1u, views[0].properties.size());
+ EXPECT_EQ(one, views[0].properties["one"]);
+ }
+
+ changes2()->clear();
+ // Set back to null.
+ ASSERT_TRUE(SetViewProperty(vm1(), BuildViewId(1, 1), "one", NULL));
+ {
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("PropertyChanged view=1,1 key=one value=NULL",
+ SingleChangeToDescription(*changes2()));
+ }
+}
+
+TEST_F(ViewManagerServiceAppTest, OnEmbeddedAppDisconnected) {
+ // Create connection 2 and 3.
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+ changes2()->clear();
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2)));
+
+ // Close connection 3. Connection 2 (which had previously embedded 3) should
+ // be notified of this.
+ vm_client3_.reset();
+ vm_client2_->WaitForChangeCount(1);
+ EXPECT_EQ("OnEmbeddedAppDisconnected view=2,2",
+ SingleChangeToDescription(*changes2()));
+}
+
+// Verifies when the parent of an Embed() is destroyed the embedded app gets
+// a ViewDeleted (and doesn't trigger a DCHECK).
+TEST_F(ViewManagerServiceAppTest, OnParentOfEmbedDisconnects) {
+ // Create connection 2 and 3.
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3)));
+ changes2()->clear();
+ ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 3)));
+ changes3()->clear();
+
+ // Close connection 2. Connection 3 should get a delete (for its root).
+ vm_client2_.reset();
+ vm_client3_->WaitForChangeCount(1);
+ EXPECT_EQ("ViewDeleted view=2,3", SingleChangeToDescription(*changes3()));
+}
+
+// Verifies ViewManagerServiceImpl doesn't incorrectly erase from its internal
+// map when a view from another connection with the same view_id is removed.
+TEST_F(ViewManagerServiceAppTest, DontCleanMapOnDestroy) {
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 1)));
+ changes1()->clear();
+ vm_client2_.reset();
+ vm_client1_.WaitForChangeCount(1);
+ EXPECT_EQ("OnEmbeddedAppDisconnected view=1,1",
+ SingleChangeToDescription(*changes1()));
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ EXPECT_FALSE(views.empty());
+}
+
+TEST_F(ViewManagerServiceAppTest, CloneAndAnimate) {
+ // Create connection 2 and 3.
+ ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true));
+ ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2)));
+ ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2)));
+ ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3)));
+ changes2()->clear();
+
+ ASSERT_TRUE(WaitForAllMessages(vm1()));
+ changes1()->clear();
+
+ wm_internal_client_->CloneAndAnimate(BuildViewId(2, 3));
+ ASSERT_TRUE(WaitForAllMessages(vm1()));
+
+ ASSERT_TRUE(WaitForAllMessages(vm1()));
+ ASSERT_TRUE(WaitForAllMessages(vm2()));
+
+ // No messages should have been received.
+ EXPECT_TRUE(changes1()->empty());
+ EXPECT_TRUE(changes2()->empty());
+
+ // No one should be able to see the cloned tree.
+ std::vector<TestView> views;
+ GetViewTree(vm1(), BuildViewId(1, 1), &views);
+ EXPECT_FALSE(HasClonedView(views));
+ views.clear();
+
+ GetViewTree(vm2(), BuildViewId(1, 1), &views);
+ EXPECT_FALSE(HasClonedView(views));
+}
+
+// Verifies Embed() works when supplying a ViewManagerClient.
+TEST_F(ViewManagerServiceAppTest, EmbedSupplyingViewManagerClient) {
+ ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1)));
+
+ ViewManagerClientImpl client2;
+ mojo::ViewManagerClientPtr client2_ptr;
+ mojo::Binding<ViewManagerClient> client2_binding(&client2, &client2_ptr);
+ ASSERT_TRUE(Embed(vm1(), BuildViewId(1, 1), client2_ptr.Pass()));
+ client2.WaitForOnEmbed();
+ EXPECT_EQ("OnEmbed creator=mojo:window_manager",
+ SingleChangeToDescription(*client2.tracker()->changes()));
+}
+
+// TODO(sky): need to better track changes to initial connection. For example,
+// that SetBounsdViews/AddView and the like don't result in messages to the
+// originating connection.
+
+// TODO(sky): make sure coverage of what was
+// ViewManagerTest.SecondEmbedRoot_InitService and
+// ViewManagerTest.MultipleEmbedRootsBeforeWTHReady gets added to window manager
+// tests.
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/view_manager_service_impl.cc b/mojo/services/view_manager/view_manager_service_impl.cc
new file mode 100644
index 0000000..3b045b9
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_service_impl.cc
@@ -0,0 +1,654 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "mojo/converters/surfaces/surfaces_type_converters.h"
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/default_access_policy.h"
+#include "mojo/services/view_manager/display_manager.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/window_manager_access_policy.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h"
+
+using mojo::Array;
+using mojo::Callback;
+using mojo::Id;
+using mojo::InterfaceRequest;
+using mojo::OrderDirection;
+using mojo::Rect;
+using mojo::ServiceProvider;
+using mojo::ServiceProviderPtr;
+using mojo::String;
+using mojo::ViewDataPtr;
+
+namespace view_manager {
+
+ViewManagerServiceImpl::ViewManagerServiceImpl(
+ ConnectionManager* connection_manager,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id)
+ : connection_manager_(connection_manager),
+ id_(connection_manager_->GetAndAdvanceNextConnectionId()),
+ url_(url),
+ creator_id_(creator_id),
+ creator_url_(creator_url),
+ client_(nullptr) {
+ CHECK(GetView(root_id));
+ root_.reset(new ViewId(root_id));
+ if (root_id == RootViewId())
+ access_policy_.reset(new WindowManagerAccessPolicy(id_, this));
+ else
+ access_policy_.reset(new DefaultAccessPolicy(id_, this));
+}
+
+ViewManagerServiceImpl::~ViewManagerServiceImpl() {
+ DestroyViews();
+}
+
+void ViewManagerServiceImpl::Init(mojo::ViewManagerClient* client,
+ mojo::ViewManagerServicePtr service_ptr,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) {
+ DCHECK(!client_);
+ client_ = client;
+ std::vector<const ServerView*> to_send;
+ if (root_.get())
+ GetUnknownViewsFrom(GetView(*root_), &to_send);
+
+ mojo::MessagePipe pipe;
+ connection_manager_->wm_internal()->CreateWindowManagerForViewManagerClient(
+ id_, pipe.handle1.Pass());
+ client->OnEmbed(id_, creator_url_, ViewToViewData(to_send.front()),
+ service_ptr.Pass(), services.Pass(), exposed_services.Pass(),
+ pipe.handle0.Pass());
+}
+
+const ServerView* ViewManagerServiceImpl::GetView(const ViewId& id) const {
+ if (id_ == id.connection_id) {
+ ViewMap::const_iterator i = view_map_.find(id.view_id);
+ return i == view_map_.end() ? NULL : i->second;
+ }
+ return connection_manager_->GetView(id);
+}
+
+bool ViewManagerServiceImpl::IsRoot(const ViewId& id) const {
+ return root_.get() && *root_ == id;
+}
+
+void ViewManagerServiceImpl::OnWillDestroyViewManagerServiceImpl(
+ ViewManagerServiceImpl* connection) {
+ if (creator_id_ == connection->id())
+ creator_id_ = kInvalidConnectionId;
+ if (connection->root_ && connection->root_->connection_id == id_ &&
+ view_map_.count(connection->root_->view_id) > 0) {
+ client()->OnEmbeddedAppDisconnected(
+ ViewIdToTransportId(*connection->root_));
+ }
+ if (root_.get() && root_->connection_id == connection->id())
+ root_.reset();
+}
+
+mojo::ErrorCode ViewManagerServiceImpl::CreateView(const ViewId& view_id) {
+ if (view_id.connection_id != id_)
+ return mojo::ERROR_CODE_ILLEGAL_ARGUMENT;
+ if (view_map_.find(view_id.view_id) != view_map_.end())
+ return mojo::ERROR_CODE_VALUE_IN_USE;
+ view_map_[view_id.view_id] = new ServerView(connection_manager_, view_id);
+ known_views_.insert(ViewIdToTransportId(view_id));
+ return mojo::ERROR_CODE_NONE;
+}
+
+bool ViewManagerServiceImpl::AddView(const ViewId& parent_id,
+ const ViewId& child_id) {
+ ServerView* parent = GetView(parent_id);
+ ServerView* child = GetView(child_id);
+ if (parent && child && child->parent() != parent &&
+ !child->Contains(parent) && access_policy_->CanAddView(parent, child)) {
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ parent->Add(child);
+ return true;
+ }
+ return false;
+}
+
+std::vector<const ServerView*> ViewManagerServiceImpl::GetViewTree(
+ const ViewId& view_id) const {
+ const ServerView* view = GetView(view_id);
+ std::vector<const ServerView*> views;
+ if (view)
+ GetViewTreeImpl(view, &views);
+ return views;
+}
+
+bool ViewManagerServiceImpl::SetViewVisibility(const ViewId& view_id,
+ bool visible) {
+ ServerView* view = GetView(view_id);
+ if (!view || view->visible() == visible ||
+ !access_policy_->CanChangeViewVisibility(view)) {
+ return false;
+ }
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->SetVisible(visible);
+ return true;
+}
+
+bool ViewManagerServiceImpl::EmbedUrl(
+ const std::string& url,
+ const ViewId& view_id,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) {
+ if (!PrepareForEmbed(view_id))
+ return false;
+ connection_manager_->EmbedAtView(id_, url, view_id, services.Pass(),
+ exposed_services.Pass());
+ return true;
+}
+
+bool ViewManagerServiceImpl::Embed(const ViewId& view_id,
+ mojo::ViewManagerClientPtr client) {
+ if (!client.get() || !PrepareForEmbed(view_id))
+ return false;
+ connection_manager_->EmbedAtView(id_, view_id, client.Pass());
+ return true;
+}
+
+void ViewManagerServiceImpl::ProcessViewBoundsChanged(
+ const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds,
+ bool originated_change) {
+ if (originated_change || !IsViewKnown(view))
+ return;
+ client()->OnViewBoundsChanged(ViewIdToTransportId(view->id()),
+ Rect::From(old_bounds),
+ Rect::From(new_bounds));
+}
+
+void ViewManagerServiceImpl::ProcessViewportMetricsChanged(
+ const mojo::ViewportMetrics& old_metrics,
+ const mojo::ViewportMetrics& new_metrics,
+ bool originated_change) {
+ client()->OnViewViewportMetricsChanged(old_metrics.Clone(),
+ new_metrics.Clone());
+}
+
+void ViewManagerServiceImpl::ProcessWillChangeViewHierarchy(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change) {
+ if (originated_change)
+ return;
+
+ const bool old_drawn = view->IsDrawn(connection_manager_->root());
+ const bool new_drawn = view->visible() && new_parent &&
+ new_parent->IsDrawn(connection_manager_->root());
+ if (old_drawn == new_drawn)
+ return;
+
+ NotifyDrawnStateChanged(view, new_drawn);
+}
+
+void ViewManagerServiceImpl::ProcessViewPropertyChanged(
+ const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data,
+ bool originated_change) {
+ if (originated_change)
+ return;
+
+ Array<uint8_t> data;
+ if (new_data)
+ data = Array<uint8_t>::From(*new_data);
+
+ client()->OnViewSharedPropertyChanged(ViewIdToTransportId(view->id()),
+ String(name), data.Pass());
+}
+
+void ViewManagerServiceImpl::ProcessViewHierarchyChanged(
+ const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change) {
+ if (originated_change && !IsViewKnown(view) && new_parent &&
+ IsViewKnown(new_parent)) {
+ std::vector<const ServerView*> unused;
+ GetUnknownViewsFrom(view, &unused);
+ }
+ if (originated_change || connection_manager_->is_processing_delete_view() ||
+ connection_manager_->DidConnectionMessageClient(id_)) {
+ return;
+ }
+
+ if (!access_policy_->ShouldNotifyOnHierarchyChange(
+ view, &new_parent, &old_parent)) {
+ return;
+ }
+ // Inform the client of any new views and update the set of views we know
+ // about.
+ std::vector<const ServerView*> to_send;
+ if (!IsViewKnown(view))
+ GetUnknownViewsFrom(view, &to_send);
+ const ViewId new_parent_id(new_parent ? new_parent->id() : ViewId());
+ const ViewId old_parent_id(old_parent ? old_parent->id() : ViewId());
+ client()->OnViewHierarchyChanged(ViewIdToTransportId(view->id()),
+ ViewIdToTransportId(new_parent_id),
+ ViewIdToTransportId(old_parent_id),
+ ViewsToViewDatas(to_send));
+ connection_manager_->OnConnectionMessagedClient(id_);
+}
+
+void ViewManagerServiceImpl::ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction,
+ bool originated_change) {
+ if (originated_change || !IsViewKnown(view) || !IsViewKnown(relative_view))
+ return;
+
+ client()->OnViewReordered(ViewIdToTransportId(view->id()),
+ ViewIdToTransportId(relative_view->id()),
+ direction);
+}
+
+void ViewManagerServiceImpl::ProcessViewDeleted(const ViewId& view,
+ bool originated_change) {
+ if (view.connection_id == id_)
+ view_map_.erase(view.view_id);
+
+ const bool in_known = known_views_.erase(ViewIdToTransportId(view)) > 0;
+
+ if (IsRoot(view))
+ root_.reset();
+
+ if (originated_change)
+ return;
+
+ if (in_known) {
+ client()->OnViewDeleted(ViewIdToTransportId(view));
+ connection_manager_->OnConnectionMessagedClient(id_);
+ }
+}
+
+void ViewManagerServiceImpl::ProcessWillChangeViewVisibility(
+ const ServerView* view,
+ bool originated_change) {
+ if (originated_change)
+ return;
+
+ if (IsViewKnown(view)) {
+ client()->OnViewVisibilityChanged(ViewIdToTransportId(view->id()),
+ !view->visible());
+ return;
+ }
+
+ bool view_target_drawn_state;
+ if (view->visible()) {
+ // View is being hidden, won't be drawn.
+ view_target_drawn_state = false;
+ } else {
+ // View is being shown. View will be drawn if its parent is drawn.
+ view_target_drawn_state =
+ view->parent() && view->parent()->IsDrawn(connection_manager_->root());
+ }
+
+ NotifyDrawnStateChanged(view, view_target_drawn_state);
+}
+
+bool ViewManagerServiceImpl::IsViewKnown(const ServerView* view) const {
+ return known_views_.count(ViewIdToTransportId(view->id())) > 0;
+}
+
+bool ViewManagerServiceImpl::CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ OrderDirection direction) const {
+ if (!view || !relative_view)
+ return false;
+
+ if (!view->parent() || view->parent() != relative_view->parent())
+ return false;
+
+ if (!access_policy_->CanReorderView(view, relative_view, direction))
+ return false;
+
+ std::vector<const ServerView*> children = view->parent()->GetChildren();
+ const size_t child_i =
+ std::find(children.begin(), children.end(), view) - children.begin();
+ const size_t target_i =
+ std::find(children.begin(), children.end(), relative_view) -
+ children.begin();
+ if ((direction == mojo::ORDER_DIRECTION_ABOVE && child_i == target_i + 1) ||
+ (direction == mojo::ORDER_DIRECTION_BELOW && child_i + 1 == target_i)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool ViewManagerServiceImpl::DeleteViewImpl(ViewManagerServiceImpl* source,
+ ServerView* view) {
+ DCHECK(view);
+ DCHECK_EQ(view->id().connection_id, id_);
+ ConnectionManager::ScopedChange change(source, connection_manager_, true);
+ delete view;
+ return true;
+}
+
+void ViewManagerServiceImpl::GetUnknownViewsFrom(
+ const ServerView* view,
+ std::vector<const ServerView*>* views) {
+ if (IsViewKnown(view) || !access_policy_->CanGetViewTree(view))
+ return;
+ views->push_back(view);
+ known_views_.insert(ViewIdToTransportId(view->id()));
+ if (!access_policy_->CanDescendIntoViewForViewTree(view))
+ return;
+ std::vector<const ServerView*> children(view->GetChildren());
+ for (size_t i = 0 ; i < children.size(); ++i)
+ GetUnknownViewsFrom(children[i], views);
+}
+
+void ViewManagerServiceImpl::RemoveFromKnown(
+ const ServerView* view,
+ std::vector<ServerView*>* local_views) {
+ if (view->id().connection_id == id_) {
+ if (local_views)
+ local_views->push_back(GetView(view->id()));
+ return;
+ }
+ known_views_.erase(ViewIdToTransportId(view->id()));
+ std::vector<const ServerView*> children = view->GetChildren();
+ for (size_t i = 0; i < children.size(); ++i)
+ RemoveFromKnown(children[i], local_views);
+}
+
+void ViewManagerServiceImpl::RemoveRoot() {
+ CHECK(root_.get());
+ const ViewId root_id(*root_);
+ root_.reset();
+ // No need to do anything if we created the view.
+ if (root_id.connection_id == id_)
+ return;
+
+ client()->OnViewDeleted(ViewIdToTransportId(root_id));
+ connection_manager_->OnConnectionMessagedClient(id_);
+
+ // This connection no longer knows about the view. Unparent any views that
+ // were parented to views in the root.
+ std::vector<ServerView*> local_views;
+ RemoveFromKnown(GetView(root_id), &local_views);
+ for (size_t i = 0; i < local_views.size(); ++i)
+ local_views[i]->parent()->Remove(local_views[i]);
+}
+
+void ViewManagerServiceImpl::RemoveChildrenAsPartOfEmbed(
+ const ViewId& view_id) {
+ ServerView* view = GetView(view_id);
+ CHECK(view);
+ CHECK(view->id().connection_id == view_id.connection_id);
+ std::vector<ServerView*> children = view->GetChildren();
+ for (size_t i = 0; i < children.size(); ++i)
+ view->Remove(children[i]);
+}
+
+Array<ViewDataPtr> ViewManagerServiceImpl::ViewsToViewDatas(
+ const std::vector<const ServerView*>& views) {
+ Array<ViewDataPtr> array(views.size());
+ for (size_t i = 0; i < views.size(); ++i)
+ array[i] = ViewToViewData(views[i]).Pass();
+ return array.Pass();
+}
+
+ViewDataPtr ViewManagerServiceImpl::ViewToViewData(const ServerView* view) {
+ DCHECK(IsViewKnown(view));
+ const ServerView* parent = view->parent();
+ // If the parent isn't known, it means the parent is not visible to us (not
+ // in roots), and should not be sent over.
+ if (parent && !IsViewKnown(parent))
+ parent = NULL;
+ ViewDataPtr view_data(mojo::ViewData::New());
+ view_data->parent_id = ViewIdToTransportId(parent ? parent->id() : ViewId());
+ view_data->view_id = ViewIdToTransportId(view->id());
+ view_data->bounds = Rect::From(view->bounds());
+ view_data->properties =
+ mojo::Map<String, Array<uint8_t>>::From(view->properties());
+ view_data->visible = view->visible();
+ view_data->drawn = view->IsDrawn(connection_manager_->root());
+ view_data->viewport_metrics =
+ connection_manager_->display_manager()->GetViewportMetrics().Clone();
+ return view_data.Pass();
+}
+
+void ViewManagerServiceImpl::GetViewTreeImpl(
+ const ServerView* view,
+ std::vector<const ServerView*>* views) const {
+ DCHECK(view);
+
+ if (!access_policy_->CanGetViewTree(view))
+ return;
+
+ views->push_back(view);
+
+ if (!access_policy_->CanDescendIntoViewForViewTree(view))
+ return;
+
+ std::vector<const ServerView*> children(view->GetChildren());
+ for (size_t i = 0 ; i < children.size(); ++i)
+ GetViewTreeImpl(children[i], views);
+}
+
+void ViewManagerServiceImpl::NotifyDrawnStateChanged(const ServerView* view,
+ bool new_drawn_value) {
+ // Even though we don't know about view, it may be an ancestor of our root, in
+ // which case the change may effect our roots drawn state.
+ if (!root_.get())
+ return;
+
+ const ServerView* root = GetView(*root_);
+ DCHECK(root);
+ if (view->Contains(root) &&
+ (new_drawn_value != root->IsDrawn(connection_manager_->root()))) {
+ client()->OnViewDrawnStateChanged(ViewIdToTransportId(root->id()),
+ new_drawn_value);
+ }
+}
+
+void ViewManagerServiceImpl::DestroyViews() {
+ if (!view_map_.empty()) {
+ ConnectionManager::ScopedChange change(this, connection_manager_, true);
+ // If we get here from the destructor we're not going to get
+ // ProcessViewDeleted(). Copy the map and delete from the copy so that we
+ // don't have to worry about whether |view_map_| changes or not.
+ ViewMap view_map_copy;
+ view_map_.swap(view_map_copy);
+ STLDeleteValues(&view_map_copy);
+ }
+}
+
+bool ViewManagerServiceImpl::PrepareForEmbed(const ViewId& view_id) {
+ const ServerView* view = GetView(view_id);
+ if (!view || !access_policy_->CanEmbed(view))
+ return false;
+
+ // Only allow a node to be the root for one connection.
+ ViewManagerServiceImpl* existing_owner =
+ connection_manager_->GetConnectionWithRoot(view_id);
+
+ ConnectionManager::ScopedChange change(this, connection_manager_, true);
+ RemoveChildrenAsPartOfEmbed(view_id);
+ if (existing_owner) {
+ // Never message the originating connection.
+ connection_manager_->OnConnectionMessagedClient(id_);
+ existing_owner->RemoveRoot();
+ }
+ return true;
+}
+
+void ViewManagerServiceImpl::CreateView(
+ Id transport_view_id,
+ const Callback<void(mojo::ErrorCode)>& callback) {
+ callback.Run(CreateView(ViewIdFromTransportId(transport_view_id)));
+}
+
+void ViewManagerServiceImpl::DeleteView(
+ Id transport_view_id,
+ const Callback<void(bool)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(transport_view_id));
+ bool success = false;
+ if (view && access_policy_->CanDeleteView(view)) {
+ ViewManagerServiceImpl* connection =
+ connection_manager_->GetConnection(view->id().connection_id);
+ success = connection && connection->DeleteViewImpl(this, view);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::AddView(
+ Id parent_id,
+ Id child_id,
+ const Callback<void(bool)>& callback) {
+ callback.Run(AddView(ViewIdFromTransportId(parent_id),
+ ViewIdFromTransportId(child_id)));
+}
+
+void ViewManagerServiceImpl::RemoveViewFromParent(
+ Id view_id,
+ const Callback<void(bool)>& callback) {
+ bool success = false;
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ if (view && view->parent() && access_policy_->CanRemoveViewFromParent(view)) {
+ success = true;
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->parent()->Remove(view);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::ReorderView(Id view_id,
+ Id relative_view_id,
+ OrderDirection direction,
+ const Callback<void(bool)>& callback) {
+ bool success = false;
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ ServerView* relative_view = GetView(ViewIdFromTransportId(relative_view_id));
+ if (CanReorderView(view, relative_view, direction)) {
+ success = true;
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->parent()->Reorder(view, relative_view, direction);
+ connection_manager_->ProcessViewReorder(view, relative_view, direction);
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::GetViewTree(
+ Id view_id,
+ const Callback<void(Array<ViewDataPtr>)>& callback) {
+ std::vector<const ServerView*> views(
+ GetViewTree(ViewIdFromTransportId(view_id)));
+ callback.Run(ViewsToViewDatas(views));
+}
+
+void ViewManagerServiceImpl::SetViewSurfaceId(
+ Id view_id,
+ mojo::SurfaceIdPtr surface_id,
+ const Callback<void(bool)>& callback) {
+ // TODO(sky): add coverage of not being able to set for random node.
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ if (!view || !access_policy_->CanSetViewSurfaceId(view)) {
+ callback.Run(false);
+ return;
+ }
+ view->SetSurfaceId(surface_id.To<cc::SurfaceId>());
+ callback.Run(true);
+}
+
+void ViewManagerServiceImpl::SetViewBounds(
+ Id view_id,
+ mojo::RectPtr bounds,
+ const Callback<void(bool)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ const bool success = view && access_policy_->CanSetViewBounds(view);
+ if (success) {
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+ view->SetBounds(bounds.To<gfx::Rect>());
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::SetViewVisibility(
+ Id transport_view_id,
+ bool visible,
+ const Callback<void(bool)>& callback) {
+ callback.Run(
+ SetViewVisibility(ViewIdFromTransportId(transport_view_id), visible));
+}
+
+void ViewManagerServiceImpl::SetViewProperty(
+ uint32_t view_id,
+ const mojo::String& name,
+ mojo::Array<uint8_t> value,
+ const mojo::Callback<void(bool)>& callback) {
+ ServerView* view = GetView(ViewIdFromTransportId(view_id));
+ const bool success = view && access_policy_->CanSetViewProperties(view);
+ if (success) {
+ ConnectionManager::ScopedChange change(this, connection_manager_, false);
+
+ if (value.is_null()) {
+ view->SetProperty(name, nullptr);
+ } else {
+ std::vector<uint8_t> data = value.To<std::vector<uint8_t>>();
+ view->SetProperty(name, &data);
+ }
+ }
+ callback.Run(success);
+}
+
+void ViewManagerServiceImpl::EmbedUrl(
+ const String& url,
+ Id transport_view_id,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const Callback<void(bool)>& callback) {
+ callback.Run(EmbedUrl(url.To<std::string>(),
+ ViewIdFromTransportId(transport_view_id),
+ services.Pass(), exposed_services.Pass()));
+}
+
+void ViewManagerServiceImpl::Embed(mojo::Id transport_view_id,
+ mojo::ViewManagerClientPtr client,
+ const mojo::Callback<void(bool)>& callback) {
+ callback.Run(Embed(ViewIdFromTransportId(transport_view_id), client.Pass()));
+}
+
+void ViewManagerServiceImpl::PerformAction(
+ mojo::Id transport_view_id,
+ const mojo::String& action,
+ const mojo::Callback<void(bool)>& callback) {
+ connection_manager_->GetWindowManagerViewManagerClient()->OnPerformAction(
+ transport_view_id, action, callback);
+}
+
+bool ViewManagerServiceImpl::IsRootForAccessPolicy(const ViewId& id) const {
+ return IsRoot(id);
+}
+
+bool ViewManagerServiceImpl::IsViewKnownForAccessPolicy(
+ const ServerView* view) const {
+ return IsViewKnown(view);
+}
+
+bool ViewManagerServiceImpl::IsViewRootOfAnotherConnectionForAccessPolicy(
+ const ServerView* view) const {
+ ViewManagerServiceImpl* connection =
+ connection_manager_->GetConnectionWithRoot(view->id());
+ return connection && connection != this;
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/view_manager_service_impl.h b/mojo/services/view_manager/view_manager_service_impl.h
new file mode 100644
index 0000000..bf156c3
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_service_impl.h
@@ -0,0 +1,260 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_
+#define SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/services/view_manager/access_policy_delegate.h"
+#include "mojo/services/view_manager/ids.h"
+#include "third_party/mojo_services/src/surfaces/public/interfaces/surface_id.mojom.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace view_manager {
+
+class AccessPolicy;
+class ConnectionManager;
+class ServerView;
+
+// An instance of ViewManagerServiceImpl is created for every ViewManagerService
+// request. ViewManagerServiceImpl tracks all the state and views created by a
+// client. ViewManagerServiceImpl coordinates with ConnectionManager to update
+// the client (and internal state) as necessary.
+class ViewManagerServiceImpl : public mojo::ViewManagerService,
+ public AccessPolicyDelegate {
+ public:
+ using ViewIdSet = base::hash_set<mojo::Id>;
+
+ ViewManagerServiceImpl(ConnectionManager* connection_manager,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id);
+ ~ViewManagerServiceImpl() override;
+
+ // |services| and |exposed_services| are the ServiceProviders to pass to the
+ // client via OnEmbed().
+ void Init(mojo::ViewManagerClient* client,
+ mojo::ViewManagerServicePtr service_ptr,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services);
+
+ mojo::ConnectionSpecificId id() const { return id_; }
+ mojo::ConnectionSpecificId creator_id() const { return creator_id_; }
+ const std::string& url() const { return url_; }
+
+ mojo::ViewManagerClient* client() { return client_; }
+
+ // Returns the View with the specified id.
+ ServerView* GetView(const ViewId& id) {
+ return const_cast<ServerView*>(
+ const_cast<const ViewManagerServiceImpl*>(this)->GetView(id));
+ }
+ const ServerView* GetView(const ViewId& id) const;
+
+ // Returns true if this connection's root is |id|.
+ bool IsRoot(const ViewId& id) const;
+
+ // Returns the id of the root node. This is null if the root has been
+ // destroyed but the connection is still valid.
+ const ViewId* root() const { return root_.get(); }
+
+ // Invoked when a connection is about to be destroyed.
+ void OnWillDestroyViewManagerServiceImpl(ViewManagerServiceImpl* connection);
+
+ // These functions are synchronous variants of those defined in the mojom. The
+ // ViewManagerService implementations all call into these. See the mojom for
+ // details.
+ mojo::ErrorCode CreateView(const ViewId& view_id);
+ bool AddView(const ViewId& parent_id, const ViewId& child_id);
+ std::vector<const ServerView*> GetViewTree(const ViewId& view_id) const;
+ bool SetViewVisibility(const ViewId& view_id, bool visible);
+ bool EmbedUrl(const std::string& url,
+ const ViewId& view_id,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services);
+ bool Embed(const ViewId& view_id, mojo::ViewManagerClientPtr client);
+
+ // The following methods are invoked after the corresponding change has been
+ // processed. They do the appropriate bookkeeping and update the client as
+ // necessary.
+ void ProcessViewBoundsChanged(const ServerView* view,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds,
+ bool originated_change);
+ void ProcessViewportMetricsChanged(const mojo::ViewportMetrics& old_metrics,
+ const mojo::ViewportMetrics& new_metrics,
+ bool originated_change);
+ void ProcessWillChangeViewHierarchy(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change);
+ void ProcessViewPropertyChanged(const ServerView* view,
+ const std::string& name,
+ const std::vector<uint8_t>* new_data,
+ bool originated_change);
+ void ProcessViewHierarchyChanged(const ServerView* view,
+ const ServerView* new_parent,
+ const ServerView* old_parent,
+ bool originated_change);
+ void ProcessViewReorder(const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction,
+ bool originated_change);
+ void ProcessViewDeleted(const ViewId& view, bool originated_change);
+ void ProcessWillChangeViewVisibility(const ServerView* view,
+ bool originated_change);
+ void ProcessViewPropertiesChanged(const ServerView* view,
+ bool originated_change);
+
+ private:
+ typedef std::map<mojo::ConnectionSpecificId, ServerView*> ViewMap;
+
+ bool IsViewKnown(const ServerView* view) const;
+
+ // These functions return true if the corresponding mojom function is allowed
+ // for this connection.
+ bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction) const;
+
+ // Deletes a view owned by this connection. Returns true on success. |source|
+ // is the connection that originated the change.
+ bool DeleteViewImpl(ViewManagerServiceImpl* source, ServerView* view);
+
+ // If |view| is known (in |known_views_|) does nothing. Otherwise adds |view|
+ // to |views|, marks |view| as known and recurses.
+ void GetUnknownViewsFrom(const ServerView* view,
+ std::vector<const ServerView*>* views);
+
+ // Removes |view| and all its descendants from |known_views_|. This does not
+ // recurse through views that were created by this connection. All views owned
+ // by this connection are added to |local_views|.
+ void RemoveFromKnown(const ServerView* view,
+ std::vector<ServerView*>* local_views);
+
+ // Resets the root of this connection.
+ void RemoveRoot();
+
+ void RemoveChildrenAsPartOfEmbed(const ViewId& view_id);
+
+ // Converts View(s) to ViewData(s) for transport. This assumes all the views
+ // are valid for the client. The parent of views the client is not allowed to
+ // see are set to NULL (in the returned ViewData(s)).
+ mojo::Array<mojo::ViewDataPtr> ViewsToViewDatas(
+ const std::vector<const ServerView*>& views);
+ mojo::ViewDataPtr ViewToViewData(const ServerView* view);
+
+ // Implementation of GetViewTree(). Adds |view| to |views| and recurses if
+ // CanDescendIntoViewForViewTree() returns true.
+ void GetViewTreeImpl(const ServerView* view,
+ std::vector<const ServerView*>* views) const;
+
+ // Notify the client if the drawn state of any of the roots changes.
+ // |view| is the view that is changing to the drawn state |new_drawn_value|.
+ void NotifyDrawnStateChanged(const ServerView* view, bool new_drawn_value);
+
+ // Deletes all Views we own.
+ void DestroyViews();
+
+ bool PrepareForEmbed(const ViewId& view_id);
+
+ // ViewManagerService:
+ void CreateView(
+ mojo::Id transport_view_id,
+ const mojo::Callback<void(mojo::ErrorCode)>& callback) override;
+ void DeleteView(mojo::Id transport_view_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void AddView(mojo::Id parent_id,
+ mojo::Id child_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void RemoveViewFromParent(
+ mojo::Id view_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void ReorderView(mojo::Id view_id,
+ mojo::Id relative_view_id,
+ mojo::OrderDirection direction,
+ const mojo::Callback<void(bool)>& callback) override;
+ void GetViewTree(mojo::Id view_id,
+ const mojo::Callback<void(mojo::Array<mojo::ViewDataPtr>)>&
+ callback) override;
+ void SetViewSurfaceId(mojo::Id view_id,
+ mojo::SurfaceIdPtr surface_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void SetViewBounds(mojo::Id view_id,
+ mojo::RectPtr bounds,
+ const mojo::Callback<void(bool)>& callback) override;
+ void SetViewVisibility(mojo::Id view_id,
+ bool visible,
+ const mojo::Callback<void(bool)>& callback) override;
+ void SetViewProperty(mojo::Id view_id,
+ const mojo::String& name,
+ mojo::Array<uint8_t> value,
+ const mojo::Callback<void(bool)>& callback) override;
+ void EmbedUrl(const mojo::String& url,
+ mojo::Id transport_view_id,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services,
+ const mojo::Callback<void(bool)>& callback) override;
+ void Embed(mojo::Id transport_view_id,
+ mojo::ViewManagerClientPtr client,
+ const mojo::Callback<void(bool)>& callback) override;
+ void PerformAction(mojo::Id transport_view_id,
+ const mojo::String& action,
+ const mojo::Callback<void(bool)>& callback) override;
+
+ // AccessPolicyDelegate:
+ bool IsRootForAccessPolicy(const ViewId& id) const override;
+ bool IsViewKnownForAccessPolicy(const ServerView* view) const override;
+ bool IsViewRootOfAnotherConnectionForAccessPolicy(
+ const ServerView* view) const override;
+
+ ConnectionManager* connection_manager_;
+
+ // Id of this connection as assigned by ConnectionManager.
+ const mojo::ConnectionSpecificId id_;
+
+ // URL this connection was created for.
+ const std::string url_;
+
+ // ID of the connection that created us. If 0 it indicates either we were
+ // created by the root, or the connection that created us has been destroyed.
+ mojo::ConnectionSpecificId creator_id_;
+
+ // The URL of the app that embedded the app this connection was created for.
+ // NOTE: this is empty if the connection was created by way of directly
+ // supplying the ViewManagerClient.
+ const std::string creator_url_;
+
+ mojo::ViewManagerClient* client_;
+
+ scoped_ptr<AccessPolicy> access_policy_;
+
+ // The views created by this connection. This connection owns these objects.
+ ViewMap view_map_;
+
+ // The set of views that has been communicated to the client.
+ ViewIdSet known_views_;
+
+ // The root of this connection. This is a scoped_ptr to reinforce the
+ // connection may have no root. A connection has no root if either the root
+ // is destroyed or Embed() is invoked on the root.
+ scoped_ptr<ViewId> root_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceImpl);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_
diff --git a/mojo/services/view_manager/view_manager_service_unittest.cc b/mojo/services/view_manager/view_manager_service_unittest.cc
new file mode 100644
index 0000000..45527e9
--- /dev/null
+++ b/mojo/services/view_manager/view_manager_service_unittest.cc
@@ -0,0 +1,471 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/view_manager/client_connection.h"
+#include "mojo/services/view_manager/connection_manager.h"
+#include "mojo/services/view_manager/connection_manager_delegate.h"
+#include "mojo/services/view_manager/display_manager.h"
+#include "mojo/services/view_manager/ids.h"
+#include "mojo/services/view_manager/server_view.h"
+#include "mojo/services/view_manager/test_change_tracker.h"
+#include "mojo/services/view_manager/view_manager_service_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/util.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h"
+#include "ui/gfx/geometry/rect.h"
+
+using mojo::Array;
+using mojo::ERROR_CODE_NONE;
+using mojo::InterfaceRequest;
+using mojo::ServiceProvider;
+using mojo::ServiceProviderPtr;
+using mojo::String;
+using mojo::ViewDataPtr;
+
+namespace view_manager {
+namespace {
+
+// -----------------------------------------------------------------------------
+
+// ViewManagerClient implementation that logs all calls to a TestChangeTracker.
+// TODO(sky): refactor so both this and ViewManagerServiceAppTest share code.
+class TestViewManagerClient : public mojo::ViewManagerClient {
+ public:
+ TestViewManagerClient() {}
+ ~TestViewManagerClient() override {}
+
+ TestChangeTracker* tracker() { return &tracker_; }
+
+ private:
+ // ViewManagerClient:
+ void OnEmbed(uint16_t connection_id,
+ const String& embedder_url,
+ ViewDataPtr root,
+ mojo::ViewManagerServicePtr view_manager_service,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ mojo::ScopedMessagePipeHandle window_manager_pipe) override {
+ tracker_.OnEmbed(connection_id, embedder_url, root.Pass());
+ }
+ void OnEmbeddedAppDisconnected(uint32_t view) override {
+ tracker_.OnEmbeddedAppDisconnected(view);
+ }
+ void OnViewBoundsChanged(uint32_t view,
+ mojo::RectPtr old_bounds,
+ mojo::RectPtr new_bounds) override {
+ tracker_.OnViewBoundsChanged(view, old_bounds.Pass(), new_bounds.Pass());
+ }
+ void OnViewViewportMetricsChanged(
+ mojo::ViewportMetricsPtr old_metrics,
+ mojo::ViewportMetricsPtr new_metrics) override {
+ tracker_.OnViewViewportMetricsChanged(old_metrics.Pass(),
+ new_metrics.Pass());
+ }
+ void OnViewHierarchyChanged(uint32_t view,
+ uint32_t new_parent,
+ uint32_t old_parent,
+ Array<ViewDataPtr> views) override {
+ tracker_.OnViewHierarchyChanged(view, new_parent, old_parent, views.Pass());
+ }
+ void OnViewReordered(uint32_t view_id,
+ uint32_t relative_view_id,
+ mojo::OrderDirection direction) override {
+ tracker_.OnViewReordered(view_id, relative_view_id, direction);
+ }
+ void OnViewDeleted(uint32_t view) override { tracker_.OnViewDeleted(view); }
+ void OnViewVisibilityChanged(uint32_t view, bool visible) override {
+ tracker_.OnViewVisibilityChanged(view, visible);
+ }
+ void OnViewDrawnStateChanged(uint32_t view, bool drawn) override {
+ tracker_.OnViewDrawnStateChanged(view, drawn);
+ }
+ void OnViewSharedPropertyChanged(uint32_t view,
+ const String& name,
+ Array<uint8_t> new_data) override {
+ tracker_.OnViewSharedPropertyChanged(view, name, new_data.Pass());
+ }
+ void OnViewInputEvent(uint32_t view,
+ mojo::EventPtr event,
+ const mojo::Callback<void()>& callback) override {
+ tracker_.OnViewInputEvent(view, event.Pass());
+ }
+ void OnPerformAction(uint32_t view_id,
+ const String& name,
+ const mojo::Callback<void(bool)>& callback) override {}
+
+ TestChangeTracker tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestViewManagerClient);
+};
+
+// -----------------------------------------------------------------------------
+
+// ClientConnection implementation that vends TestViewManagerClient.
+class TestClientConnection : public ClientConnection {
+ public:
+ explicit TestClientConnection(scoped_ptr<ViewManagerServiceImpl> service_impl)
+ : ClientConnection(service_impl.Pass(), &client_) {}
+ ~TestClientConnection() override {}
+
+ TestViewManagerClient* client() { return &client_; }
+
+ private:
+ TestViewManagerClient client_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestClientConnection);
+};
+
+// -----------------------------------------------------------------------------
+
+// Empty implementation of ConnectionManagerDelegate.
+class TestConnectionManagerDelegate : public ConnectionManagerDelegate {
+ public:
+ TestConnectionManagerDelegate() : last_connection_(nullptr) {}
+ ~TestConnectionManagerDelegate() override {}
+
+ TestViewManagerClient* last_client() {
+ return last_connection_ ? last_connection_->client() : nullptr;
+ }
+
+ TestClientConnection* last_connection() { return last_connection_; }
+
+ private:
+ // ConnectionManagerDelegate:
+ void OnLostConnectionToWindowManager() override {}
+
+ ClientConnection* CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const std::string& url,
+ const ViewId& root_id) override {
+ scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl(
+ connection_manager, creator_id, creator_url, url, root_id));
+ last_connection_ = new TestClientConnection(service.Pass());
+ return last_connection_;
+ }
+ ClientConnection* CreateClientConnectionForEmbedAtView(
+ ConnectionManager* connection_manager,
+ mojo::InterfaceRequest<mojo::ViewManagerService> service_request,
+ mojo::ConnectionSpecificId creator_id,
+ const std::string& creator_url,
+ const ViewId& root_id,
+ mojo::ViewManagerClientPtr client) override {
+ NOTIMPLEMENTED();
+ return nullptr;
+ }
+
+ TestClientConnection* last_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestConnectionManagerDelegate);
+};
+
+// -----------------------------------------------------------------------------
+
+// Empty implementation of DisplayManager.
+class TestDisplayManager : public DisplayManager {
+ public:
+ TestDisplayManager() {}
+ ~TestDisplayManager() override {}
+
+ // DisplayManager:
+ void Init(ConnectionManager* connection_manager) override {}
+ void SchedulePaint(const ServerView* view, const gfx::Rect& bounds) override {
+ }
+ void SetViewportSize(const gfx::Size& size) override {}
+ const mojo::ViewportMetrics& GetViewportMetrics() override {
+ return display_metrices_;
+ }
+
+ private:
+ mojo::ViewportMetrics display_metrices_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDisplayManager);
+};
+
+// -----------------------------------------------------------------------------
+
+// Empty implementation of WindowManagerInternal.
+class TestWindowManagerInternal : public mojo::WindowManagerInternal {
+ public:
+ TestWindowManagerInternal() {}
+ ~TestWindowManagerInternal() override {}
+
+ // WindowManagerInternal:
+ void CreateWindowManagerForViewManagerClient(
+ uint16_t connection_id,
+ mojo::ScopedMessagePipeHandle window_manager_pipe) override {}
+ void SetViewManagerClient(mojo::ScopedMessagePipeHandle) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestWindowManagerInternal);
+};
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+class ViewManagerServiceTest : public testing::Test {
+ public:
+ ViewManagerServiceTest() : wm_client_(nullptr) {}
+ ~ViewManagerServiceTest() override {}
+
+ // ViewManagerServiceImpl for the window manager.
+ ViewManagerServiceImpl* wm_connection() {
+ return connection_manager_->GetConnection(1);
+ }
+
+ TestViewManagerClient* last_view_manager_client() {
+ return delegate_.last_client();
+ }
+
+ TestClientConnection* last_client_connection() {
+ return delegate_.last_connection();
+ }
+
+ ConnectionManager* connection_manager() { return connection_manager_.get(); }
+
+ TestViewManagerClient* wm_client() { return wm_client_; }
+
+ protected:
+ // testing::Test:
+ void SetUp() override {
+ connection_manager_.reset(new ConnectionManager(
+ &delegate_, scoped_ptr<DisplayManager>(new TestDisplayManager),
+ &wm_internal_));
+ scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl(
+ connection_manager_.get(), kInvalidConnectionId, std::string(),
+ std::string("mojo:window_manager"), RootViewId()));
+ scoped_ptr<TestClientConnection> client_connection(
+ new TestClientConnection(service.Pass()));
+ wm_client_ = client_connection->client();
+ ASSERT_TRUE(wm_client_ != nullptr);
+ connection_manager_->SetWindowManagerClientConnection(
+ client_connection.Pass());
+ ASSERT_TRUE(wm_connection() != nullptr);
+ ASSERT_TRUE(wm_connection()->root() != nullptr);
+ }
+
+ private:
+ // TestViewManagerClient that is used for the WM connection.
+ TestViewManagerClient* wm_client_;
+
+ TestWindowManagerInternal wm_internal_;
+ TestConnectionManagerDelegate delegate_;
+ scoped_ptr<ConnectionManager> connection_manager_;
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceTest);
+};
+
+namespace {
+
+const ServerView* GetFirstCloned(const ServerView* view) {
+ for (const ServerView* child : view->GetChildren()) {
+ if (child->id() == ClonedViewId())
+ return child;
+ }
+ return nullptr;
+}
+
+// Provides common setup for animation tests. Creates the following views:
+// 0,1 (the root, provided by view manager)
+// 1,1 the second connection is embedded here (view owned by wm_connection()).
+// 2,1 bounds=1,2 11x22
+// 2,2 bounds=2,3 6x7
+// 2,3 bounds=3,4 6x7
+// CloneAndAnimate() is invoked for 2,2.
+void SetUpAnimate1(ViewManagerServiceTest* test, ViewId* embed_view_id) {
+ *embed_view_id = ViewId(test->wm_connection()->id(), 1);
+ EXPECT_EQ(ERROR_CODE_NONE, test->wm_connection()->CreateView(*embed_view_id));
+ EXPECT_TRUE(test->wm_connection()->SetViewVisibility(*embed_view_id, true));
+ EXPECT_TRUE(test->wm_connection()->AddView(*(test->wm_connection()->root()),
+ *embed_view_id));
+ test->wm_connection()->EmbedUrl(std::string(), *embed_view_id, nullptr,
+ nullptr);
+ ViewManagerServiceImpl* connection1 =
+ test->connection_manager()->GetConnectionWithRoot(*embed_view_id);
+ ASSERT_TRUE(connection1 != nullptr);
+ ASSERT_NE(connection1, test->wm_connection());
+
+ const ViewId child1(connection1->id(), 1);
+ EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child1));
+ const ViewId child2(connection1->id(), 2);
+ EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child2));
+ const ViewId child3(connection1->id(), 3);
+ EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child3));
+
+ ServerView* v1 = connection1->GetView(child1);
+ v1->SetVisible(true);
+ v1->SetBounds(gfx::Rect(1, 2, 11, 22));
+ ServerView* v2 = connection1->GetView(child2);
+ v2->SetVisible(true);
+ v2->SetBounds(gfx::Rect(2, 3, 6, 7));
+ ServerView* v3 = connection1->GetView(child3);
+ v3->SetVisible(true);
+ v3->SetBounds(gfx::Rect(3, 4, 6, 7));
+
+ EXPECT_TRUE(connection1->AddView(*embed_view_id, child1));
+ EXPECT_TRUE(connection1->AddView(child1, child2));
+ EXPECT_TRUE(connection1->AddView(child2, child3));
+
+ TestViewManagerClient* connection1_client = test->last_view_manager_client();
+ connection1_client->tracker()->changes()->clear();
+ test->wm_client()->tracker()->changes()->clear();
+ EXPECT_TRUE(test->connection_manager()->CloneAndAnimate(child2));
+ EXPECT_TRUE(connection1_client->tracker()->changes()->empty());
+ EXPECT_TRUE(test->wm_client()->tracker()->changes()->empty());
+
+ // We cloned v2. The cloned view ends up as a sibling of it.
+ const ServerView* cloned_view = GetFirstCloned(connection1->GetView(child1));
+ ASSERT_TRUE(cloned_view);
+ // |cloned_view| should have one and only one cloned child (corresponds to
+ // |child3|).
+ ASSERT_EQ(1u, cloned_view->GetChildren().size());
+ EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId());
+
+ // Cloned views should match the bounds of the view they were cloned from.
+ EXPECT_EQ(v2->bounds(), cloned_view->bounds());
+ EXPECT_EQ(v3->bounds(), cloned_view->GetChildren()[0]->bounds());
+
+ // Cloned views are owned by the ConnectionManager and shouldn't be returned
+ // from ViewManagerServiceImpl::GetView.
+ EXPECT_TRUE(connection1->GetView(ClonedViewId()) == nullptr);
+ EXPECT_TRUE(test->wm_connection()->GetView(ClonedViewId()) == nullptr);
+}
+
+} // namespace
+
+// Verifies ViewManagerService::GetViewTree() doesn't return cloned views.
+TEST_F(ViewManagerServiceTest, ConnectionsCantSeeClonedViews) {
+ ViewId embed_view_id;
+ EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id));
+
+ ViewManagerServiceImpl* connection1 =
+ connection_manager()->GetConnectionWithRoot(embed_view_id);
+
+ const ViewId child1(connection1->id(), 1);
+ const ViewId child2(connection1->id(), 2);
+ const ViewId child3(connection1->id(), 3);
+
+ // Verify the root doesn't see any cloned views.
+ std::vector<const ServerView*> views(
+ wm_connection()->GetViewTree(*wm_connection()->root()));
+ ASSERT_EQ(5u, views.size());
+ ASSERT_TRUE(views[0]->id() == *wm_connection()->root());
+ ASSERT_TRUE(views[1]->id() == embed_view_id);
+ ASSERT_TRUE(views[2]->id() == child1);
+ ASSERT_TRUE(views[3]->id() == child2);
+ ASSERT_TRUE(views[4]->id() == child3);
+
+ // Verify connection1 doesn't see any cloned views.
+ std::vector<const ServerView*> v1_views(
+ connection1->GetViewTree(embed_view_id));
+ ASSERT_EQ(4u, v1_views.size());
+ ASSERT_TRUE(v1_views[0]->id() == embed_view_id);
+ ASSERT_TRUE(v1_views[1]->id() == child1);
+ ASSERT_TRUE(v1_views[2]->id() == child2);
+ ASSERT_TRUE(v1_views[3]->id() == child3);
+}
+
+TEST_F(ViewManagerServiceTest, ClonedViewsPromotedOnConnectionClose) {
+ ViewId embed_view_id;
+ EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id));
+
+ // Destroy connection1, which should force the cloned view to become a child
+ // of where it was embedded (the embedded view still exists).
+ connection_manager()->OnConnectionError(last_client_connection());
+
+ ServerView* embed_view = wm_connection()->GetView(embed_view_id);
+ ASSERT_TRUE(embed_view != nullptr);
+ const ServerView* cloned_view = GetFirstCloned(embed_view);
+ ASSERT_TRUE(cloned_view);
+ ASSERT_EQ(1u, cloned_view->GetChildren().size());
+ EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId());
+
+ // Because the cloned view changed parents its bounds should have changed.
+ EXPECT_EQ(gfx::Rect(3, 5, 6, 7), cloned_view->bounds());
+ // The bounds of the cloned child should not have changed though.
+ EXPECT_EQ(gfx::Rect(3, 4, 6, 7), cloned_view->GetChildren()[0]->bounds());
+}
+
+TEST_F(ViewManagerServiceTest, ClonedViewsPromotedOnHide) {
+ ViewId embed_view_id;
+ EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id));
+
+ ViewManagerServiceImpl* connection1 =
+ connection_manager()->GetConnectionWithRoot(embed_view_id);
+
+ // Hide the parent of the cloned view, which should force the cloned view to
+ // become a sibling of the parent.
+ const ServerView* view_to_hide =
+ connection1->GetView(ViewId(connection1->id(), 1));
+ ASSERT_TRUE(connection1->SetViewVisibility(view_to_hide->id(), false));
+
+ const ServerView* cloned_view = GetFirstCloned(view_to_hide->parent());
+ ASSERT_TRUE(cloned_view);
+ ASSERT_EQ(1u, cloned_view->GetChildren().size());
+ EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId());
+ EXPECT_EQ(2u, cloned_view->parent()->GetChildren().size());
+ EXPECT_TRUE(cloned_view->parent()->GetChildren()[1] == cloned_view);
+}
+
+// Clone and animate on a tree with more depth. Basically that of
+// SetUpAnimate1() but cloning 2,1.
+TEST_F(ViewManagerServiceTest, CloneAndAnimateLargerDepth) {
+ const ViewId embed_view_id(wm_connection()->id(), 1);
+ EXPECT_EQ(ERROR_CODE_NONE, wm_connection()->CreateView(embed_view_id));
+ EXPECT_TRUE(wm_connection()->SetViewVisibility(embed_view_id, true));
+ EXPECT_TRUE(
+ wm_connection()->AddView(*(wm_connection()->root()), embed_view_id));
+ wm_connection()->EmbedUrl(std::string(), embed_view_id, nullptr, nullptr);
+ ViewManagerServiceImpl* connection1 =
+ connection_manager()->GetConnectionWithRoot(embed_view_id);
+ ASSERT_TRUE(connection1 != nullptr);
+ ASSERT_NE(connection1, wm_connection());
+
+ const ViewId child1(connection1->id(), 1);
+ EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child1));
+ const ViewId child2(connection1->id(), 2);
+ EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child2));
+ const ViewId child3(connection1->id(), 3);
+ EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child3));
+
+ ServerView* v1 = connection1->GetView(child1);
+ v1->SetVisible(true);
+ connection1->GetView(child2)->SetVisible(true);
+ connection1->GetView(child3)->SetVisible(true);
+
+ EXPECT_TRUE(connection1->AddView(embed_view_id, child1));
+ EXPECT_TRUE(connection1->AddView(child1, child2));
+ EXPECT_TRUE(connection1->AddView(child2, child3));
+
+ TestViewManagerClient* connection1_client = last_view_manager_client();
+ connection1_client->tracker()->changes()->clear();
+ wm_client()->tracker()->changes()->clear();
+ EXPECT_TRUE(connection_manager()->CloneAndAnimate(child1));
+ EXPECT_TRUE(connection1_client->tracker()->changes()->empty());
+ EXPECT_TRUE(wm_client()->tracker()->changes()->empty());
+
+ // We cloned v1. The cloned view ends up as a sibling of it.
+ const ServerView* cloned_view = GetFirstCloned(v1->parent());
+ ASSERT_TRUE(cloned_view);
+ // |cloned_view| should have a child and its child should have a child.
+ ASSERT_EQ(1u, cloned_view->GetChildren().size());
+ const ServerView* cloned_view_child = cloned_view->GetChildren()[0];
+ EXPECT_EQ(1u, cloned_view_child->GetChildren().size());
+ EXPECT_TRUE(cloned_view_child->id() == ClonedViewId());
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/window_manager_access_policy.cc b/mojo/services/view_manager/window_manager_access_policy.cc
new file mode 100644
index 0000000..18696a0
--- /dev/null
+++ b/mojo/services/view_manager/window_manager_access_policy.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/view_manager/window_manager_access_policy.h"
+
+#include "mojo/services/view_manager/access_policy_delegate.h"
+#include "mojo/services/view_manager/server_view.h"
+
+namespace view_manager {
+
+// TODO(sky): document why this differs from default for each case. Maybe want
+// to subclass DefaultAccessPolicy.
+
+WindowManagerAccessPolicy::WindowManagerAccessPolicy(
+ mojo::ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate)
+ : connection_id_(connection_id), delegate_(delegate) {
+}
+
+WindowManagerAccessPolicy::~WindowManagerAccessPolicy() {
+}
+
+bool WindowManagerAccessPolicy::CanRemoveViewFromParent(
+ const ServerView* view) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanAddView(const ServerView* parent,
+ const ServerView* child) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanReorderView(
+ const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction) const {
+ return true;
+}
+
+bool WindowManagerAccessPolicy::CanDeleteView(const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanGetViewTree(const ServerView* view) const {
+ return view->id() != ClonedViewId();
+}
+
+bool WindowManagerAccessPolicy::CanDescendIntoViewForViewTree(
+ const ServerView* view) const {
+ return view->id() != ClonedViewId();
+}
+
+bool WindowManagerAccessPolicy::CanEmbed(const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanChangeViewVisibility(
+ const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanSetViewSurfaceId(
+ const ServerView* view) const {
+ if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view))
+ return false;
+ return view->id().connection_id == connection_id_ ||
+ (delegate_->IsRootForAccessPolicy(view->id()));
+}
+
+bool WindowManagerAccessPolicy::CanSetViewBounds(const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::CanSetViewProperties(
+ const ServerView* view) const {
+ return view->id().connection_id == connection_id_;
+}
+
+bool WindowManagerAccessPolicy::ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const {
+ if (view->id() == ClonedViewId())
+ return false;
+
+ // Notify if we've already told the window manager about the view, or if we've
+ // already told the window manager about the parent. The later handles the
+ // case of a view that wasn't parented to the root getting added to the root.
+ return IsViewKnown(view) || (*new_parent && IsViewKnown(*new_parent));
+}
+
+bool WindowManagerAccessPolicy::IsViewKnown(const ServerView* view) const {
+ return delegate_->IsViewKnownForAccessPolicy(view);
+}
+
+} // namespace view_manager
diff --git a/mojo/services/view_manager/window_manager_access_policy.h b/mojo/services/view_manager/window_manager_access_policy.h
new file mode 100644
index 0000000..c1840ae
--- /dev/null
+++ b/mojo/services/view_manager/window_manager_access_policy.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_
+#define SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_
+
+#include "base/basictypes.h"
+#include "mojo/services/view_manager/access_policy.h"
+
+namespace view_manager {
+
+class AccessPolicyDelegate;
+
+class WindowManagerAccessPolicy : public AccessPolicy {
+ public:
+ WindowManagerAccessPolicy(mojo::ConnectionSpecificId connection_id,
+ AccessPolicyDelegate* delegate);
+ ~WindowManagerAccessPolicy() override;
+
+ // AccessPolicy:
+ bool CanRemoveViewFromParent(const ServerView* view) const override;
+ bool CanAddView(const ServerView* parent,
+ const ServerView* child) const override;
+ bool CanReorderView(const ServerView* view,
+ const ServerView* relative_view,
+ mojo::OrderDirection direction) const override;
+ bool CanDeleteView(const ServerView* view) const override;
+ bool CanGetViewTree(const ServerView* view) const override;
+ bool CanDescendIntoViewForViewTree(const ServerView* view) const override;
+ bool CanEmbed(const ServerView* view) const override;
+ bool CanChangeViewVisibility(const ServerView* view) const override;
+ bool CanSetViewSurfaceId(const ServerView* view) const override;
+ bool CanSetViewBounds(const ServerView* view) const override;
+ bool CanSetViewProperties(const ServerView* view) const override;
+ bool ShouldNotifyOnHierarchyChange(
+ const ServerView* view,
+ const ServerView** new_parent,
+ const ServerView** old_parent) const override;
+
+ private:
+ bool IsViewKnown(const ServerView* view) const;
+
+ const mojo::ConnectionSpecificId connection_id_;
+ AccessPolicyDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerAccessPolicy);
+};
+
+} // namespace view_manager
+
+#endif // SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_
diff --git a/mojo/services/window_manager/BUILD.gn b/mojo/services/window_manager/BUILD.gn
new file mode 100644
index 0000000..c34ebe1
--- /dev/null
+++ b/mojo/services/window_manager/BUILD.gn
@@ -0,0 +1,129 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ui.gni")
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("//testing/test.gni")
+
+mojo_native_application("window_manager") {
+ sources = [
+ "main.cc",
+ ]
+
+ public_deps = [
+ ":lib",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/common:tracing_impl",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ ]
+}
+
+source_set("lib") {
+ sources = [
+ "basic_focus_rules.cc",
+ "basic_focus_rules.h",
+ "capture_controller.cc",
+ "capture_controller.h",
+ "capture_controller_observer.h",
+ "focus_controller.cc",
+ "focus_controller.h",
+ "focus_controller_observer.h",
+ "focus_rules.h",
+ "native_viewport_event_dispatcher_impl.cc",
+ "native_viewport_event_dispatcher_impl.h",
+ "view_event_dispatcher.cc",
+ "view_event_dispatcher.h",
+ "view_target.cc",
+ "view_target.h",
+ "view_targeter.cc",
+ "view_targeter.h",
+ "window_manager_app.cc",
+ "window_manager_app.h",
+ "window_manager_app_android.cc",
+ "window_manager_app_linux.cc",
+ "window_manager_app_win.cc",
+ "window_manager_delegate.h",
+ "window_manager_impl.cc",
+ "window_manager_impl.h",
+ ]
+
+ deps = [
+ "//base",
+ "//ui/base",
+ "//ui/events",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/converters/geometry",
+ "//mojo/converters/input_events",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:bindings",
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ "//third_party/mojo_services/src/native_viewport/public/interfaces",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ ]
+}
+
+test("window_manager_unittests") {
+ sources = [
+ "focus_controller_unittest.cc",
+ "run_all_unittests.cc",
+ "view_target_unittest.cc",
+ "view_targeter_unittest.cc",
+ "window_manager_api_unittest.cc",
+ "window_manager_test_util.cc",
+ "window_manager_test_util.h",
+ ]
+
+ public_deps = [
+ ":lib",
+ ]
+
+ deps = [
+ "//base/test:test_support",
+ "//mojo/converters/geometry",
+ "//third_party/mojo/src/mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/application",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//third_party/mojo_services/src/view_manager/public/interfaces",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ "//mojo/shell/application_manager",
+ "//mojo/shell:test_support",
+ "//testing/gtest",
+ "//ui/events:test_support",
+ "//ui/gfx",
+ "//ui/gfx:test_support",
+ "//ui/gl",
+ ]
+
+ if (use_x11) {
+ deps += [ "//ui/gfx/x" ]
+ }
+}
+
+mojo_native_application("window_manager_apptests") {
+ testonly = true
+
+ sources = [
+ "window_manager_apptest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/application:test_support",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/system:system",
+ "//third_party/mojo_services/src/view_manager/public/cpp",
+ "//third_party/mojo_services/src/window_manager/public/interfaces",
+ ]
+
+ data_deps = [ ":window_manager($default_toolchain)" ]
+}
diff --git a/mojo/services/window_manager/DEPS b/mojo/services/window_manager/DEPS
new file mode 100644
index 0000000..4a637c6
--- /dev/null
+++ b/mojo/services/window_manager/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+mojo/application",
+ "+mojo/converters",
+ "+third_party/mojo_services",
+ "+ui",
+]
diff --git a/mojo/services/window_manager/basic_focus_rules.cc b/mojo/services/window_manager/basic_focus_rules.cc
new file mode 100644
index 0000000..5ba49af
--- /dev/null
+++ b/mojo/services/window_manager/basic_focus_rules.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/basic_focus_rules.h"
+
+#include "base/macros.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+
+using mojo::View;
+
+namespace window_manager {
+
+BasicFocusRules::BasicFocusRules(View* window_container)
+ : window_container_(window_container) {
+}
+
+BasicFocusRules::~BasicFocusRules() {}
+
+bool BasicFocusRules::SupportsChildActivation(View* view) const {
+ return true;
+}
+
+bool BasicFocusRules::IsToplevelView(View* view) const {
+ if (!IsViewParentedToWindowContainer(view))
+ return false;
+
+ // The window must exist within a container that supports activation.
+ // The window cannot be blocked by a modal transient.
+ return SupportsChildActivation(view->parent());
+}
+
+bool BasicFocusRules::CanActivateView(View* view) const {
+ if (!view)
+ return true;
+
+ // Only toplevel windows can be activated
+ if (!IsToplevelView(view))
+ return false;
+
+ // The view must be visible.
+ if (!view->visible())
+ return false;
+
+ // TODO(erg): The aura version of this class asks the aura::Window's
+ // ActivationDelegate whether the window is activatable.
+
+ // A window must be focusable to be activatable. We don't call
+ // CanFocusWindow() from here because it will call back to us via
+ // GetActivatableWindow().
+ if (!CanFocusViewImpl(view))
+ return false;
+
+ // TODO(erg): In the aura version, we also check whether the window is
+ // blocked by a modal transient window.
+
+ return true;
+}
+
+bool BasicFocusRules::CanFocusView(View* view) const {
+ // It is possible to focus a NULL window, it is equivalent to clearing focus.
+ if (!view)
+ return true;
+
+ // The focused view is always inside the active view, so views that aren't
+ // activatable can't contain the focused view.
+ View* activatable = GetActivatableView(view);
+ if (!activatable || !activatable->Contains(view))
+ return false;
+ return CanFocusViewImpl(view);
+}
+
+View* BasicFocusRules::GetToplevelView(View* view) const {
+ View* parent = view->parent();
+ View* child = view;
+ while (parent) {
+ if (IsToplevelView(child))
+ return child;
+
+ parent = parent->parent();
+ child = child->parent();
+ }
+
+ return nullptr;
+}
+
+View* BasicFocusRules::GetActivatableView(View* view) const {
+ View* parent = view->parent();
+ View* child = view;
+ while (parent) {
+ if (CanActivateView(child))
+ return child;
+
+ // TODO(erg): In the aura version of this class, we have a whole bunch of
+ // checks to support modal transient windows, and transient parents.
+
+ parent = parent->parent();
+ child = child->parent();
+ }
+
+ return nullptr;
+}
+
+View* BasicFocusRules::GetFocusableView(View* view) const {
+ if (CanFocusView(view))
+ return view;
+
+ // |view| may be in a hierarchy that is non-activatable, in which case we
+ // need to cut over to the activatable hierarchy.
+ View* activatable = GetActivatableView(view);
+ if (!activatable) {
+ // There may not be a related activatable hierarchy to cut over to, in which
+ // case we try an unrelated one.
+ View* toplevel = GetToplevelView(view);
+ if (toplevel)
+ activatable = GetNextActivatableView(toplevel);
+ if (!activatable)
+ return nullptr;
+ }
+
+ if (!activatable->Contains(view)) {
+ // If there's already a child window focused in the activatable hierarchy,
+ // just use that (i.e. don't shift focus), otherwise we need to at least cut
+ // over to the activatable hierarchy.
+ View* focused = GetFocusableView(activatable);
+ return activatable->Contains(focused) ? focused : activatable;
+ }
+
+ while (view && !CanFocusView(view))
+ view = view->parent();
+ return view;
+}
+
+View* BasicFocusRules::GetNextActivatableView(View* activatable) const {
+ DCHECK(activatable);
+
+ // In the basic scenarios handled by BasicFocusRules, the pool of activatable
+ // windows is limited to the |ignore|'s siblings.
+ const View::Children& siblings = activatable->parent()->children();
+ DCHECK(!siblings.empty());
+
+ for (auto rit = siblings.rbegin(); rit != siblings.rend(); ++rit) {
+ View* cur = *rit;
+ if (cur == activatable)
+ continue;
+ if (CanActivateView(cur))
+ return cur;
+ }
+ return nullptr;
+}
+
+// TODO(erg): aura::Window::CanFocus() exists. View::CanFocus() does
+// not. This is a hack that does everything that Window::CanFocus() currently
+// does that doesn't require a delegate or an EventClient.
+bool BasicFocusRules::CanFocusViewImpl(View* view) const {
+ // TODO(erg): In unit tests, views will never be drawn, so we can't rely on
+ // IsDrawn() here.
+ if (IsViewParentedToWindowContainer(view))
+ return view->visible();
+
+ // TODO(erg): Add the intermediary delegate and event client checks once we
+ // have those.
+
+ return CanFocusViewImpl(view->parent());
+}
+
+bool BasicFocusRules::IsViewParentedToWindowContainer(View* view) const {
+ return view->parent() == window_container_;
+}
+
+} // namespace mojo
diff --git a/mojo/services/window_manager/basic_focus_rules.h b/mojo/services/window_manager/basic_focus_rules.h
new file mode 100644
index 0000000..d14a9b0d
--- /dev/null
+++ b/mojo/services/window_manager/basic_focus_rules.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 SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_
+#define SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_
+
+#include "mojo/services/window_manager/focus_rules.h"
+
+namespace mojo {
+class View;
+}
+
+namespace window_manager {
+
+// The focusing rules used inside a window manager.
+//
+// This is intended to be a user supplyable, subclassable component passed to
+// WindowManagerApp, allowing for the creation of other window managers.
+class BasicFocusRules : public FocusRules {
+ public:
+ BasicFocusRules(mojo::View* window_container);
+ ~BasicFocusRules() override;
+
+ protected:
+ // Overridden from mojo::FocusRules:
+ bool SupportsChildActivation(mojo::View* view) const override;
+ bool IsToplevelView(mojo::View* view) const override;
+ bool CanActivateView(mojo::View* view) const override;
+ bool CanFocusView(mojo::View* view) const override;
+ mojo::View* GetToplevelView(mojo::View* view) const override;
+ mojo::View* GetActivatableView(mojo::View* view) const override;
+ mojo::View* GetFocusableView(mojo::View* view) const override;
+ mojo::View* GetNextActivatableView(mojo::View* activatable) const override;
+
+ private:
+ bool CanFocusViewImpl(mojo::View* view) const;
+
+ // Tests to see if |view| is in |window_container_|.
+ bool IsViewParentedToWindowContainer(mojo::View* view) const;
+
+ mojo::View* window_container_;
+
+ DISALLOW_COPY_AND_ASSIGN(BasicFocusRules);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_
diff --git a/mojo/services/window_manager/capture_controller.cc b/mojo/services/window_manager/capture_controller.cc
new file mode 100644
index 0000000..ad6891c
--- /dev/null
+++ b/mojo/services/window_manager/capture_controller.cc
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/capture_controller.h"
+
+#include "mojo/services/window_manager/capture_controller_observer.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_tracker.h"
+
+DECLARE_VIEW_PROPERTY_TYPE(window_manager::CaptureController*);
+
+namespace window_manager {
+
+namespace {
+DEFINE_VIEW_PROPERTY_KEY(CaptureController*,
+ kRootViewCaptureController,
+ nullptr);
+} // namespace
+
+CaptureController::CaptureController()
+ : capture_view_(nullptr) {}
+
+CaptureController::~CaptureController() {}
+
+void CaptureController::AddObserver(CaptureControllerObserver* observer) {
+ capture_controller_observers_.AddObserver(observer);
+}
+
+void CaptureController::RemoveObserver(CaptureControllerObserver* observer) {
+ capture_controller_observers_.RemoveObserver(observer);
+}
+
+void CaptureController::SetCapture(mojo::View* view) {
+ if (capture_view_ == view)
+ return;
+
+ if (capture_view_)
+ capture_view_->RemoveObserver(this);
+
+ mojo::View* old_capture_view = capture_view_;
+ capture_view_ = view;
+
+ if (capture_view_)
+ capture_view_->AddObserver(this);
+
+ NotifyCaptureChange(capture_view_, old_capture_view);
+}
+
+void CaptureController::ReleaseCapture(mojo::View* view) {
+ if (capture_view_ != view)
+ return;
+ SetCapture(nullptr);
+}
+
+mojo::View* CaptureController::GetCapture() {
+ return capture_view_;
+}
+
+void CaptureController::NotifyCaptureChange(mojo::View* new_capture,
+ mojo::View* old_capture) {
+ mojo::ViewTracker view_tracker;
+ if (new_capture)
+ view_tracker.Add(new_capture);
+ if (old_capture)
+ view_tracker.Add(old_capture);
+
+ FOR_EACH_OBSERVER(
+ CaptureControllerObserver, capture_controller_observers_,
+ OnCaptureChanged(view_tracker.Contains(new_capture) ? new_capture
+ : nullptr));
+}
+
+void CaptureController::OnViewDestroying(mojo::View* view) {
+ if (view == capture_view_) {
+ view->RemoveObserver(this);
+ NotifyCaptureChange(nullptr, view);
+ capture_view_ = nullptr;
+ }
+}
+
+void SetCaptureController(mojo::View* root_view,
+ CaptureController* capture_controller) {
+ DCHECK_EQ(root_view->GetRoot(), root_view);
+ root_view->SetLocalProperty(kRootViewCaptureController, capture_controller);
+}
+
+CaptureController* GetCaptureController(mojo::View* root_view) {
+ if (root_view)
+ DCHECK_EQ(root_view->GetRoot(), root_view);
+ return root_view ?
+ root_view->GetLocalProperty(kRootViewCaptureController) : nullptr;
+}
+
+mojo::View* GetCaptureView(mojo::View* view) {
+ mojo::View* root = view->GetRoot();
+ if (!root)
+ return nullptr;
+ CaptureController* controller = GetCaptureController(root);
+ return controller ? controller->GetCapture() : nullptr;
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/capture_controller.h b/mojo/services/window_manager/capture_controller.h
new file mode 100644
index 0000000..64a3339
--- /dev/null
+++ b/mojo/services/window_manager/capture_controller.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 SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_
+#define SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_
+
+#include "base/observer_list.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h"
+
+namespace window_manager {
+
+class CaptureControllerObserver;
+
+// Manages input capture. A view which has capture will take all input events.
+class CaptureController : public mojo::ViewObserver {
+ public:
+ CaptureController();
+ ~CaptureController() override;
+
+ void AddObserver(CaptureControllerObserver* observer);
+ void RemoveObserver(CaptureControllerObserver* observer);
+
+ void SetCapture(mojo::View* view);
+ void ReleaseCapture(mojo::View* view);
+ mojo::View* GetCapture();
+
+ private:
+ void NotifyCaptureChange(mojo::View* new_capture, mojo::View* old_capture);
+
+ // Overridden from ViewObserver:
+ void OnViewDestroying(mojo::View* view) override;
+
+ // The current capture view. Null if there is no capture view.
+ mojo::View* capture_view_;
+
+ ObserverList<CaptureControllerObserver> capture_controller_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(CaptureController);
+};
+
+void SetCaptureController(mojo::View* view,
+ CaptureController* capture_controller);
+CaptureController* GetCaptureController(mojo::View* view);
+mojo::View* GetCaptureView(mojo::View* view);
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_
diff --git a/mojo/services/window_manager/capture_controller_observer.h b/mojo/services/window_manager/capture_controller_observer.h
new file mode 100644
index 0000000..fc62d60
--- /dev/null
+++ b/mojo/services/window_manager/capture_controller_observer.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_
+#define SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_
+
+namespace window_manager {
+
+class CaptureControllerObserver {
+ public:
+ virtual void OnCaptureChanged(mojo::View* gained_capture) = 0;
+
+ protected:
+ virtual ~CaptureControllerObserver() {}
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_
diff --git a/mojo/services/window_manager/focus_controller.cc b/mojo/services/window_manager/focus_controller.cc
new file mode 100644
index 0000000..25d60d6
--- /dev/null
+++ b/mojo/services/window_manager/focus_controller.cc
@@ -0,0 +1,317 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/focus_controller.h"
+
+#include "base/auto_reset.h"
+#include "mojo/services/window_manager/focus_controller_observer.h"
+#include "mojo/services/window_manager/focus_rules.h"
+#include "mojo/services/window_manager/view_target.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_tracker.h"
+#include "ui/events/event.h"
+
+DECLARE_VIEW_PROPERTY_TYPE(window_manager::FocusController*);
+
+using mojo::View;
+
+namespace window_manager {
+
+namespace {
+DEFINE_VIEW_PROPERTY_KEY(FocusController*, kRootViewFocusController, nullptr);
+} // namespace
+
+FocusController::FocusController(scoped_ptr<FocusRules> rules)
+ : active_view_(nullptr),
+ focused_view_(nullptr),
+ updating_focus_(false),
+ updating_activation_(false),
+ rules_(rules.Pass()),
+ observer_manager_(this) {
+ DCHECK(rules_);
+}
+
+FocusController::~FocusController() {}
+
+void FocusController::AddObserver(FocusControllerObserver* observer) {
+ focus_controller_observers_.AddObserver(observer);
+}
+
+void FocusController::RemoveObserver(FocusControllerObserver* observer) {
+ focus_controller_observers_.RemoveObserver(observer);
+}
+
+void FocusController::ActivateView(View* view) {
+ FocusView(view);
+}
+
+void FocusController::DeactivateView(View* view) {
+ if (view)
+ FocusView(rules_->GetNextActivatableView(view));
+}
+
+View* FocusController::GetActiveView() {
+ return active_view_;
+}
+
+View* FocusController::GetActivatableView(View* view) {
+ return rules_->GetActivatableView(view);
+}
+
+View* FocusController::GetToplevelView(View* view) {
+ return rules_->GetToplevelView(view);
+}
+
+bool FocusController::CanActivateView(View* view) const {
+ return rules_->CanActivateView(view);
+}
+
+void FocusController::FocusView(View* view) {
+ if (view &&
+ (view->Contains(focused_view_) || view->Contains(active_view_))) {
+ return;
+ }
+
+ // Focusing a window also activates its containing activatable window. Note
+ // that the rules could redirect activation activation and/or focus.
+ View* focusable = rules_->GetFocusableView(view);
+ View* activatable =
+ focusable ? rules_->GetActivatableView(focusable) : nullptr;
+
+ // We need valid focusable/activatable windows in the event we're not clearing
+ // focus. "Clearing focus" is inferred by whether or not |window| passed to
+ // this function is non-null.
+ if (view && (!focusable || !activatable))
+ return;
+ DCHECK((focusable && activatable) || !view);
+
+ // Activation change observers may change the focused window. If this happens
+ // we must not adjust the focus below since this will clobber that change.
+ View* last_focused_view = focused_view_;
+ if (!updating_activation_)
+ SetActiveView(view, activatable);
+
+ // If the window's ActivationChangeObserver shifted focus to a valid window,
+ // we don't want to focus the window we thought would be focused by default.
+ bool activation_changed_focus = last_focused_view != focused_view_;
+ if (!updating_focus_ && (!activation_changed_focus || !focused_view_)) {
+ if (active_view_ && focusable)
+ DCHECK(active_view_->Contains(focusable));
+ SetFocusedView(focusable);
+ }
+}
+
+void FocusController::ResetFocusWithinActiveView(View* view) {
+ DCHECK(view);
+ if (!active_view_)
+ return;
+ if (!active_view_->Contains(view))
+ return;
+ SetFocusedView(view);
+}
+
+View* FocusController::GetFocusedView() {
+ return focused_view_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FocusController, ui::EventHandler implementation:
+
+void FocusController::OnKeyEvent(ui::KeyEvent* event) {
+}
+
+void FocusController::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled()) {
+ View* view = static_cast<ViewTarget*>(event->target())->view();
+ ViewFocusedFromInputEvent(view);
+ }
+}
+
+void FocusController::OnScrollEvent(ui::ScrollEvent* event) {
+}
+
+void FocusController::OnTouchEvent(ui::TouchEvent* event) {
+ if (event->type() == ui::ET_TOUCH_PRESSED && !event->handled()) {
+ View* view = static_cast<ViewTarget*>(event->target())->view();
+ ViewFocusedFromInputEvent(view);
+ }
+}
+
+void FocusController::OnGestureEvent(ui::GestureEvent* event) {
+ if (event->type() == ui::ET_GESTURE_BEGIN &&
+ event->details().touch_points() == 1 &&
+ !event->handled()) {
+ View* view = static_cast<ViewTarget*>(event->target())->view();
+ ViewFocusedFromInputEvent(view);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FocusController, mojo::ViewObserver implementation:
+
+void FocusController::OnViewVisibilityChanged(View* view) {
+ bool visible = view->visible();
+ if (!visible)
+ ViewLostFocusFromDispositionChange(view, view->parent());
+}
+
+void FocusController::OnViewDestroying(View* view) {
+ ViewLostFocusFromDispositionChange(view, view->parent());
+}
+
+void FocusController::OnTreeChanging(const TreeChangeParams& params) {
+ // TODO(erg): In the aura version, you could get into a situation where you
+ // have different focus clients, so you had to check for that. Does that
+ // happen here? Could we get away with not checking if it does?
+ if (params.receiver == active_view_ &&
+ params.target->Contains(params.receiver) &&
+ (!params.new_parent ||
+ /* different_focus_clients */ false)) {
+ ViewLostFocusFromDispositionChange(params.receiver, params.old_parent);
+ }
+}
+
+void FocusController::OnTreeChanged(const TreeChangeParams& params) {
+ // TODO(erg): Same as Changing version.
+ if (params.receiver == focused_view_ &&
+ params.target->Contains(params.receiver) &&
+ (!params.new_parent ||
+ /* different_focus_clients */ false)) {
+ ViewLostFocusFromDispositionChange(params.receiver, params.old_parent);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FocusController, private:
+
+void FocusController::SetFocusedView(View* view) {
+ if (updating_focus_ || view == focused_view_)
+ return;
+ DCHECK(rules_->CanFocusView(view));
+ if (view)
+ DCHECK_EQ(view, rules_->GetFocusableView(view));
+
+ base::AutoReset<bool> updating_focus(&updating_focus_, true);
+ View* lost_focus = focused_view_;
+
+ // TODO(erg): In the aura version, we reset the text input client here. Do
+ // that if we bring in something like the TextInputClient.
+
+ // Allow for the window losing focus to be deleted during dispatch. If it is
+ // deleted pass null to observers instead of a deleted window.
+ mojo::ViewTracker view_tracker;
+ if (lost_focus)
+ view_tracker.Add(lost_focus);
+ if (focused_view_ && observer_manager_.IsObserving(focused_view_) &&
+ focused_view_ != active_view_) {
+ observer_manager_.Remove(focused_view_);
+ }
+ focused_view_ = view;
+ if (focused_view_ && !observer_manager_.IsObserving(focused_view_))
+ observer_manager_.Add(focused_view_);
+
+ FOR_EACH_OBSERVER(FocusControllerObserver, focus_controller_observers_,
+ OnFocused(focused_view_));
+
+ // TODO(erg): In aura, there's a concept of a single FocusChangeObserver that
+ // is attached to an aura::Window. We don't currently have this in
+ // mojo::View, but if we add it later, we should make something analogous
+ // here.
+
+ // TODO(erg): In the aura version, we reset the TextInputClient here, too.
+}
+
+void FocusController::SetActiveView(View* requested_view, View* view) {
+ if (updating_activation_)
+ return;
+
+ if (view == active_view_) {
+ if (requested_view) {
+ FOR_EACH_OBSERVER(FocusControllerObserver,
+ focus_controller_observers_,
+ OnAttemptToReactivateView(requested_view,
+ active_view_));
+ }
+ return;
+ }
+
+ DCHECK(rules_->CanActivateView(view));
+ if (view)
+ DCHECK_EQ(view, rules_->GetActivatableView(view));
+
+ base::AutoReset<bool> updating_activation(&updating_activation_, true);
+ View* lost_activation = active_view_;
+ // Allow for the window losing activation to be deleted during dispatch. If
+ // it is deleted pass null to observers instead of a deleted window.
+ mojo::ViewTracker view_tracker;
+ if (lost_activation)
+ view_tracker.Add(lost_activation);
+ if (active_view_ && observer_manager_.IsObserving(active_view_) &&
+ focused_view_ != active_view_) {
+ observer_manager_.Remove(active_view_);
+ }
+ active_view_ = view;
+ if (active_view_ && !observer_manager_.IsObserving(active_view_))
+ observer_manager_.Add(active_view_);
+
+ if (active_view_) {
+ // TODO(erg): Reenable this when we have modal windows.
+ // StackTransientParentsBelowModalWindow(active_view_);
+
+ active_view_->MoveToFront();
+ }
+
+ // TODO(erg): Individual windows can have a single ActivationChangeObserver
+ // set on them. In the aura version of this code, it sends an
+ // OnWindowActivated message to both the window that lost activation, and the
+ // window that gained it.
+
+ FOR_EACH_OBSERVER(FocusControllerObserver, focus_controller_observers_,
+ OnActivated(active_view_));
+}
+
+void FocusController::ViewLostFocusFromDispositionChange(
+ View* view,
+ View* next) {
+ // TODO(erg): We clear the modality state here in the aura::Window version of
+ // this class, and should probably do the same once we have modal windows.
+
+ // TODO(beng): See if this function can be replaced by a call to
+ // FocusWindow().
+ // Activation adjustments are handled first in the event of a disposition
+ // changed. If an activation change is necessary, focus is reset as part of
+ // that process so there's no point in updating focus independently.
+ if (view == active_view_) {
+ View* next_activatable = rules_->GetNextActivatableView(view);
+ SetActiveView(nullptr, next_activatable);
+ if (!(active_view_ && active_view_->Contains(focused_view_)))
+ SetFocusedView(next_activatable);
+ } else if (view->Contains(focused_view_)) {
+ // Active window isn't changing, but focused window might be.
+ SetFocusedView(rules_->GetFocusableView(next));
+ }
+}
+
+void FocusController::ViewFocusedFromInputEvent(View* view) {
+ // Only focus |window| if it or any of its parents can be focused. Otherwise
+ // FocusWindow() will focus the topmost window, which may not be the
+ // currently focused one.
+ if (rules_->CanFocusView(GetToplevelView(view)))
+ FocusView(view);
+}
+
+void SetFocusController(View* root_view, FocusController* focus_controller) {
+ DCHECK_EQ(root_view->GetRoot(), root_view);
+ root_view->SetLocalProperty(kRootViewFocusController, focus_controller);
+}
+
+FocusController* GetFocusController(View* root_view) {
+ if (root_view)
+ DCHECK_EQ(root_view->GetRoot(), root_view);
+ return root_view ?
+ root_view->GetLocalProperty(kRootViewFocusController) : nullptr;
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/focus_controller.h b/mojo/services/window_manager/focus_controller.h
new file mode 100644
index 0000000..8a9e8ef
--- /dev/null
+++ b/mojo/services/window_manager/focus_controller.h
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_
+#define SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h"
+#include "ui/events/event_handler.h"
+
+namespace window_manager {
+
+class FocusControllerObserver;
+class FocusRules;
+
+// FocusController handles focus and activation changes in a mojo window
+// manager. Within the window manager, there can only be one focused and one
+// active window at a time. When focus or activation changes, notifications are
+// sent using the FocusControllerObserver interface.
+class FocusController : public ui::EventHandler, public mojo::ViewObserver {
+ public:
+ // |rules| cannot be null.
+ explicit FocusController(scoped_ptr<FocusRules> rules);
+ ~FocusController() override;
+
+ void AddObserver(FocusControllerObserver* observer);
+ void RemoveObserver(FocusControllerObserver* observer);
+
+ void ActivateView(mojo::View* view);
+ void DeactivateView(mojo::View* view);
+ mojo::View* GetActiveView();
+ mojo::View* GetActivatableView(mojo::View* view);
+ mojo::View* GetToplevelView(mojo::View* view);
+ bool CanActivateView(mojo::View* view) const;
+
+ void FocusView(mojo::View* view);
+
+ void ResetFocusWithinActiveView(mojo::View* view);
+ mojo::View* GetFocusedView();
+
+ // Overridden from ui::EventHandler:
+ void OnKeyEvent(ui::KeyEvent* event) override;
+ void OnMouseEvent(ui::MouseEvent* event) override;
+ void OnScrollEvent(ui::ScrollEvent* event) override;
+ void OnTouchEvent(ui::TouchEvent* event) override;
+ void OnGestureEvent(ui::GestureEvent* event) override;
+
+ // Overridden from ViewObserver:
+ void OnTreeChanging(const TreeChangeParams& params) override;
+ void OnTreeChanged(const TreeChangeParams& params) override;
+ void OnViewVisibilityChanged(mojo::View* view) override;
+ void OnViewDestroying(mojo::View* view) override;
+
+ private:
+ // Internal implementation that sets the focused view, fires events etc.
+ // This function must be called with a valid focusable view.
+ void SetFocusedView(mojo::View* view);
+
+ // Internal implementation that sets the active window, fires events etc.
+ // This function must be called with a valid |activatable_window|.
+ // |requested window| refers to the window that was passed in to an external
+ // request (e.g. FocusWindow or ActivateWindow). It may be null, e.g. if
+ // SetActiveWindow was not called by an external request. |activatable_window|
+ // refers to the actual window to be activated, which may be different.
+ void SetActiveView(mojo::View* requested_view, mojo::View* activatable_view);
+
+ // Called when a window's disposition changed such that it and its hierarchy
+ // are no longer focusable/activatable. |next| is a valid window that is used
+ // as a starting point for finding a window to focus next based on rules.
+ void ViewLostFocusFromDispositionChange(mojo::View* view, mojo::View* next);
+
+ // Called when an attempt is made to focus or activate a window via an input
+ // event targeted at that window. Rules determine the best focusable window
+ // for the input window.
+ void ViewFocusedFromInputEvent(mojo::View* view);
+
+ mojo::View* active_view_;
+ mojo::View* focused_view_;
+
+ bool updating_focus_;
+ bool updating_activation_;
+
+ scoped_ptr<FocusRules> rules_;
+
+ ObserverList<FocusControllerObserver> focus_controller_observers_;
+
+ ScopedObserver<mojo::View, ViewObserver> observer_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusController);
+};
+
+// Sets/Gets the focus controller for a view.
+void SetFocusController(mojo::View* view, FocusController* focus_controller);
+FocusController* GetFocusController(mojo::View* view);
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_
diff --git a/mojo/services/window_manager/focus_controller_observer.h b/mojo/services/window_manager/focus_controller_observer.h
new file mode 100644
index 0000000..c5960b0
--- /dev/null
+++ b/mojo/services/window_manager/focus_controller_observer.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 SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_
+#define SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_
+
+namespace mojo {
+class View;
+}
+
+namespace window_manager {
+
+class FocusControllerObserver {
+ public:
+ // Called when |active| gains focus, or there is no active view
+ // (|active| is null in this case.).
+ virtual void OnActivated(mojo::View* gained_active) = 0;
+
+ // Called when focus moves to |gained_focus|.
+ virtual void OnFocused(mojo::View* gained_focus) = 0;
+
+ // Called when during view activation the currently active view is
+ // selected for activation. This can happen when a view requested for
+ // activation cannot be activated because a system modal view is active.
+ virtual void OnAttemptToReactivateView(mojo::View* request_active,
+ mojo::View* actual_active) {}
+
+ protected:
+ virtual ~FocusControllerObserver() {}
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_
diff --git a/mojo/services/window_manager/focus_controller_unittest.cc b/mojo/services/window_manager/focus_controller_unittest.cc
new file mode 100644
index 0000000..ea60ac7
--- /dev/null
+++ b/mojo/services/window_manager/focus_controller_unittest.cc
@@ -0,0 +1,1197 @@
+// 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 "mojo/services/window_manager/focus_controller.h"
+
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/services/window_manager/basic_focus_rules.h"
+#include "mojo/services/window_manager/capture_controller.h"
+#include "mojo/services/window_manager/focus_controller_observer.h"
+#include "mojo/services/window_manager/view_event_dispatcher.h"
+#include "mojo/services/window_manager/view_targeter.h"
+#include "mojo/services/window_manager/window_manager_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_utils.h"
+#include "ui/gfx/geometry/rect.h"
+
+using mojo::View;
+
+namespace window_manager {
+
+// Counts the number of events that occur.
+class FocusNotificationObserver : public FocusControllerObserver {
+ public:
+ FocusNotificationObserver()
+ : activation_changed_count_(0),
+ focus_changed_count_(0),
+ reactivation_count_(0),
+ reactivation_requested_view_(NULL),
+ reactivation_actual_view_(NULL) {}
+ ~FocusNotificationObserver() override {}
+
+ void ExpectCounts(int activation_changed_count, int focus_changed_count) {
+ EXPECT_EQ(activation_changed_count, activation_changed_count_);
+ EXPECT_EQ(focus_changed_count, focus_changed_count_);
+ }
+ int reactivation_count() const { return reactivation_count_; }
+ View* reactivation_requested_view() const {
+ return reactivation_requested_view_;
+ }
+ View* reactivation_actual_view() const {
+ return reactivation_actual_view_;
+ }
+
+ protected:
+ // Overridden from FocusControllerObserver:
+ void OnActivated(View* gained_active) override {
+ ++activation_changed_count_;
+ }
+
+ void OnFocused(View* gained_focus) override { ++focus_changed_count_; }
+
+ void OnAttemptToReactivateView(View* request_active,
+ View* actual_active) override {
+ ++reactivation_count_;
+ reactivation_requested_view_ = request_active;
+ reactivation_actual_view_ = actual_active;
+ }
+
+ private:
+ int activation_changed_count_;
+ int focus_changed_count_;
+ int reactivation_count_;
+ View* reactivation_requested_view_;
+ View* reactivation_actual_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusNotificationObserver);
+};
+
+class ViewDestroyer {
+ public:
+ virtual View* GetDestroyedView() = 0;
+
+ protected:
+ virtual ~ViewDestroyer() {}
+};
+
+// FocusNotificationObserver that keeps track of whether it was notified about
+// activation changes or focus changes with a destroyed view.
+class RecordingFocusNotificationObserver : public FocusNotificationObserver {
+ public:
+ RecordingFocusNotificationObserver(FocusController* focus_controller,
+ ViewDestroyer* destroyer)
+ : focus_controller_(focus_controller),
+ destroyer_(destroyer),
+ active_(nullptr),
+ focus_(nullptr),
+ was_notified_with_destroyed_view_(false) {
+ focus_controller_->AddObserver(this);
+ }
+ ~RecordingFocusNotificationObserver() override {
+ focus_controller_->RemoveObserver(this);
+ }
+
+ bool was_notified_with_destroyed_view() const {
+ return was_notified_with_destroyed_view_;
+ }
+
+ // Overridden from FocusNotificationObserver:
+ void OnActivated(View* gained_active) override {
+ if (active_ && active_ == destroyer_->GetDestroyedView())
+ was_notified_with_destroyed_view_ = true;
+ active_ = gained_active;
+ }
+
+ void OnFocused(View* gained_focus) override {
+ if (focus_ && focus_ == destroyer_->GetDestroyedView())
+ was_notified_with_destroyed_view_ = true;
+ focus_ = gained_focus;
+ }
+
+ private:
+ FocusController* focus_controller_;
+
+ // Not owned.
+ ViewDestroyer* destroyer_;
+ View* active_;
+ View* focus_;
+
+ // Whether the observer was notified about the loss of activation or the
+ // loss of focus with a view already destroyed by |destroyer_| as the
+ // |lost_active| or |lost_focus| parameter.
+ bool was_notified_with_destroyed_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(RecordingFocusNotificationObserver);
+};
+
+class DestroyOnLoseActivationFocusNotificationObserver
+ : public FocusNotificationObserver,
+ public ViewDestroyer {
+ public:
+ DestroyOnLoseActivationFocusNotificationObserver(
+ FocusController* focus_controller,
+ View* view_to_destroy,
+ View* initial_active)
+ : focus_controller_(focus_controller),
+ view_to_destroy_(view_to_destroy),
+ active_(initial_active),
+ did_destroy_(false) {
+ focus_controller_->AddObserver(this);
+ }
+ ~DestroyOnLoseActivationFocusNotificationObserver() override {
+ focus_controller_->RemoveObserver(this);
+ }
+
+ // Overridden from FocusNotificationObserver:
+ void OnActivated(View* gained_active) override {
+ if (view_to_destroy_ && active_ == view_to_destroy_) {
+ view_to_destroy_->Destroy();
+ did_destroy_ = true;
+ }
+ active_ = gained_active;
+ }
+
+ // Overridden from ViewDestroyer:
+ View* GetDestroyedView() override {
+ return did_destroy_ ? view_to_destroy_ : nullptr;
+ }
+
+ private:
+ FocusController* focus_controller_;
+ View* view_to_destroy_;
+ View* active_;
+ bool did_destroy_;
+
+ DISALLOW_COPY_AND_ASSIGN(DestroyOnLoseActivationFocusNotificationObserver);
+};
+
+class ScopedFocusNotificationObserver : public FocusNotificationObserver {
+ public:
+ ScopedFocusNotificationObserver(FocusController* focus_controller)
+ : focus_controller_(focus_controller) {
+ focus_controller_->AddObserver(this);
+ }
+ ~ScopedFocusNotificationObserver() override {
+ focus_controller_->RemoveObserver(this);
+ }
+
+ private:
+ FocusController* focus_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFocusNotificationObserver);
+};
+
+// Only responds to events if a message contains |target| as a parameter.
+class ScopedFilteringFocusNotificationObserver
+ : public FocusNotificationObserver {
+ public:
+ ScopedFilteringFocusNotificationObserver(FocusController* focus_controller,
+ View* target,
+ View* initial_active,
+ View* initial_focus)
+ : focus_controller_(focus_controller),
+ target_(target),
+ active_(initial_active),
+ focus_(initial_focus) {
+ focus_controller_->AddObserver(this);
+ }
+ ~ScopedFilteringFocusNotificationObserver() override {
+ focus_controller_->RemoveObserver(this);
+ }
+
+ private:
+ // Overridden from FocusControllerObserver:
+ void OnActivated(View* gained_active) override {
+ if (gained_active == target_ || active_ == target_)
+ FocusNotificationObserver::OnActivated(gained_active);
+ active_ = gained_active;
+ }
+
+ void OnFocused(View* gained_focus) override {
+ if (gained_focus == target_ || focus_ == target_)
+ FocusNotificationObserver::OnFocused(gained_focus);
+ focus_ = gained_focus;
+ }
+
+ void OnAttemptToReactivateView(View* request_active,
+ View* actual_active) override {
+ if (request_active == target_ || actual_active == target_) {
+ FocusNotificationObserver::OnAttemptToReactivateView(request_active,
+ actual_active);
+ }
+ }
+
+ FocusController* focus_controller_;
+ View* target_;
+ View* active_;
+ View* focus_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFilteringFocusNotificationObserver);
+};
+
+// Used to fake the handling of events in the pre-target phase.
+class SimpleEventHandler : public ui::EventHandler {
+ public:
+ SimpleEventHandler() {}
+ ~SimpleEventHandler() override {}
+
+ // Overridden from ui::EventHandler:
+ void OnMouseEvent(ui::MouseEvent* event) override {
+ event->SetHandled();
+ }
+ void OnGestureEvent(ui::GestureEvent* event) override {
+ event->SetHandled();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SimpleEventHandler);
+};
+
+class FocusShiftingActivationObserver
+ : public FocusControllerObserver {
+ public:
+ explicit FocusShiftingActivationObserver(FocusController* focus_controller,
+ View* activated_view)
+ : focus_controller_(focus_controller),
+ activated_view_(activated_view),
+ shift_focus_to_(NULL) {}
+ ~FocusShiftingActivationObserver() override {}
+
+ void set_shift_focus_to(View* shift_focus_to) {
+ shift_focus_to_ = shift_focus_to;
+ }
+
+ private:
+ // Overridden from FocusControllerObserver:
+ void OnActivated(View* gained_active) override {
+ // Shift focus to a child. This should prevent the default focusing from
+ // occurring in FocusController::FocusView().
+ if (gained_active == activated_view_)
+ focus_controller_->FocusView(shift_focus_to_);
+ }
+
+ void OnFocused(View* gained_focus) override {}
+
+ FocusController* focus_controller_;
+ View* activated_view_;
+ View* shift_focus_to_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusShiftingActivationObserver);
+};
+
+// BasicFocusRules subclass that allows basic overrides of focus/activation to
+// be tested. This is intended more as a test that the override system works at
+// all, rather than as an exhaustive set of use cases, those should be covered
+// in tests for those FocusRules implementations.
+class TestFocusRules : public BasicFocusRules {
+ public:
+ TestFocusRules(View* root)
+ : BasicFocusRules(root), focus_restriction_(NULL) {}
+
+ // Restricts focus and activation to this view and its child hierarchy.
+ void set_focus_restriction(View* focus_restriction) {
+ focus_restriction_ = focus_restriction;
+ }
+
+ // Overridden from BasicFocusRules:
+ bool SupportsChildActivation(View* view) const override {
+ // In FocusControllerTests, only the Root has activatable children.
+ return view->GetRoot() == view;
+ }
+ bool CanActivateView(View* view) const override {
+ // Restricting focus to a non-activatable child view means the activatable
+ // parent outside the focus restriction is activatable.
+ bool can_activate =
+ CanFocusOrActivate(view) || view->Contains(focus_restriction_);
+ return can_activate ? BasicFocusRules::CanActivateView(view) : false;
+ }
+ bool CanFocusView(View* view) const override {
+ return CanFocusOrActivate(view) ? BasicFocusRules::CanFocusView(view)
+ : false;
+ }
+ View* GetActivatableView(View* view) const override {
+ return BasicFocusRules::GetActivatableView(
+ CanFocusOrActivate(view) ? view : focus_restriction_);
+ }
+ View* GetFocusableView(View* view) const override {
+ return BasicFocusRules::GetFocusableView(
+ CanFocusOrActivate(view) ? view : focus_restriction_);
+ }
+ View* GetNextActivatableView(View* ignore) const override {
+ View* next_activatable = BasicFocusRules::GetNextActivatableView(ignore);
+ return CanFocusOrActivate(next_activatable)
+ ? next_activatable
+ : GetActivatableView(focus_restriction_);
+ }
+
+ private:
+ bool CanFocusOrActivate(View* view) const {
+ return !focus_restriction_ || focus_restriction_->Contains(view);
+ }
+
+ View* focus_restriction_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestFocusRules);
+};
+
+// Common infrastructure shared by all FocusController test types.
+class FocusControllerTestBase : public testing::Test {
+ protected:
+ // Hierarchy used by all tests:
+ // root_view
+ // +-- w1
+ // | +-- w11
+ // | +-- w12
+ // +-- w2
+ // | +-- w21
+ // | +-- w211
+ // +-- w3
+ FocusControllerTestBase()
+ : root_view_(TestView::Build(0, gfx::Rect(0, 0, 800, 600))),
+ v1(TestView::Build(1, gfx::Rect(0, 0, 50, 50), root_view())),
+ v11(TestView::Build(11, gfx::Rect(5, 5, 10, 10), v1)),
+ v12(TestView::Build(12, gfx::Rect(15, 15, 10, 10), v1)),
+ v2(TestView::Build(2, gfx::Rect(75, 75, 50, 50), root_view())),
+ v21(TestView::Build(21, gfx::Rect(5, 5, 10, 10), v2)),
+ v211(TestView::Build(211, gfx::Rect(1, 1, 5, 5), v21)),
+ v3(TestView::Build(3, gfx::Rect(125, 125, 50, 50), root_view())) {}
+
+ // Overridden from testing::Test:
+ void SetUp() override {
+ testing::Test::SetUp();
+
+ test_focus_rules_ = new TestFocusRules(root_view());
+ focus_controller_.reset(
+ new FocusController(scoped_ptr<FocusRules>(test_focus_rules_)));
+ SetFocusController(root_view(), focus_controller_.get());
+
+ capture_controller_.reset(new CaptureController);
+ SetCaptureController(root_view(), capture_controller_.get());
+
+ ViewTarget* root_target = root_view_->target();
+ root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter()));
+ view_event_dispatcher_.reset(new ViewEventDispatcher());
+ view_event_dispatcher_->SetRootViewTarget(root_target);
+
+ GetRootViewTarget()->AddPreTargetHandler(focus_controller_.get());
+ }
+
+ void TearDown() override {
+ GetRootViewTarget()->RemovePreTargetHandler(focus_controller_.get());
+ view_event_dispatcher_.reset();
+
+ root_view_->Destroy();
+
+ capture_controller_.reset();
+ test_focus_rules_ = nullptr; // Owned by FocusController.
+ focus_controller_.reset();
+
+ testing::Test::TearDown();
+ }
+
+ void FocusView(View* view) { focus_controller_->FocusView(view); }
+ View* GetFocusedView() { return focus_controller_->GetFocusedView(); }
+ int GetFocusedViewId() {
+ View* focused_view = GetFocusedView();
+ return focused_view ? focused_view->id() : -1;
+ }
+ void ActivateView(View* view) { focus_controller_->ActivateView(view); }
+ void DeactivateView(View* view) { focus_controller_->DeactivateView(view); }
+ View* GetActiveView() { return focus_controller_->GetActiveView(); }
+ int GetActiveViewId() {
+ View* active_view = GetActiveView();
+ return active_view ? active_view->id() : -1;
+ }
+
+ View* GetViewById(int id) { return root_view_->GetChildById(id); }
+
+ void ClickLeftButton(View* view) {
+ // Get the center bounds of |target| in |root_view_| coordinate space.
+ gfx::Point center =
+ gfx::Rect(view->bounds().To<gfx::Rect>().size()).CenterPoint();
+ ViewTarget::ConvertPointToTarget(ViewTarget::TargetFromView(view),
+ root_view_->target(), &center);
+
+ ui::MouseEvent button_down(ui::ET_MOUSE_PRESSED, center, center,
+ ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_NONE);
+ ui::EventDispatchDetails details =
+ view_event_dispatcher_->OnEventFromSource(&button_down);
+ CHECK(!details.dispatcher_destroyed);
+
+ ui::MouseEvent button_up(ui::ET_MOUSE_RELEASED, center, center,
+ ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+ ui::EF_NONE);
+ details = view_event_dispatcher_->OnEventFromSource(&button_up);
+ CHECK(!details.dispatcher_destroyed);
+ }
+
+ ViewTarget* GetRootViewTarget() {
+ return ViewTarget::TargetFromView(root_view());
+ }
+
+ View* root_view() { return root_view_; }
+ TestFocusRules* test_focus_rules() { return test_focus_rules_; }
+ FocusController* focus_controller() { return focus_controller_.get(); }
+ CaptureController* capture_controller() { return capture_controller_.get(); }
+
+ // Test functions.
+ virtual void BasicFocus() = 0;
+ virtual void BasicActivation() = 0;
+ virtual void FocusEvents() = 0;
+ virtual void DuplicateFocusEvents() {}
+ virtual void ActivationEvents() = 0;
+ virtual void ReactivationEvents() {}
+ virtual void DuplicateActivationEvents() {}
+ virtual void ShiftFocusWithinActiveView() {}
+ virtual void ShiftFocusToChildOfInactiveView() {}
+ virtual void ShiftFocusToParentOfFocusedView() {}
+ virtual void FocusRulesOverride() = 0;
+ virtual void ActivationRulesOverride() = 0;
+ virtual void ShiftFocusOnActivation() {}
+ virtual void ShiftFocusOnActivationDueToHide() {}
+ virtual void NoShiftActiveOnActivation() {}
+ virtual void ChangeFocusWhenNothingFocusedAndCaptured() {}
+ virtual void DontPassDestroyedView() {}
+ // TODO(erg): Also, void FocusedTextInputClient() once we build the IME.
+
+ private:
+ TestView* root_view_;
+ scoped_ptr<FocusController> focus_controller_;
+ TestFocusRules* test_focus_rules_;
+ scoped_ptr<CaptureController> capture_controller_;
+ // TODO(erg): The aura version of this class also keeps track of WMState. Do
+ // we need something analogous here?
+
+ scoped_ptr<ViewEventDispatcher> view_event_dispatcher_;
+
+ TestView* v1;
+ TestView* v11;
+ TestView* v12;
+ TestView* v2;
+ TestView* v21;
+ TestView* v211;
+ TestView* v3;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerTestBase);
+};
+
+// Test base for tests where focus is directly set to a target view.
+class FocusControllerDirectTestBase : public FocusControllerTestBase {
+ protected:
+ FocusControllerDirectTestBase() {}
+
+ // Different test types shift focus in different ways.
+ virtual void FocusViewDirect(View* view) = 0;
+ virtual void ActivateViewDirect(View* view) = 0;
+ virtual void DeactivateViewDirect(View* view) = 0;
+
+ // Input events do not change focus if the view can not be focused.
+ virtual bool IsInputEvent() = 0;
+
+ void FocusViewById(int id) {
+ View* view = root_view()->GetChildById(id);
+ DCHECK(view);
+ FocusViewDirect(view);
+ }
+ void ActivateViewById(int id) {
+ View* view = root_view()->GetChildById(id);
+ DCHECK(view);
+ ActivateViewDirect(view);
+ }
+
+ // Overridden from FocusControllerTestBase:
+ void BasicFocus() override {
+ EXPECT_EQ(nullptr, GetFocusedView());
+ FocusViewById(1);
+ EXPECT_EQ(1, GetFocusedViewId());
+ FocusViewById(2);
+ EXPECT_EQ(2, GetFocusedViewId());
+ }
+ void BasicActivation() override {
+ EXPECT_EQ(nullptr, GetActiveView());
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetActiveViewId());
+ ActivateViewById(2);
+ EXPECT_EQ(2, GetActiveViewId());
+ // Verify that attempting to deactivate NULL does not crash and does not
+ // change activation.
+ DeactivateView(nullptr);
+ EXPECT_EQ(2, GetActiveViewId());
+ DeactivateView(GetActiveView());
+ EXPECT_EQ(1, GetActiveViewId());
+ }
+ void FocusEvents() override {
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ ScopedFilteringFocusNotificationObserver observer1(
+ focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView());
+ ScopedFilteringFocusNotificationObserver observer2(
+ focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView());
+
+ {
+ SCOPED_TRACE("initial state");
+ root_observer.ExpectCounts(0, 0);
+ observer1.ExpectCounts(0, 0);
+ observer2.ExpectCounts(0, 0);
+ }
+
+ FocusViewById(1);
+ {
+ SCOPED_TRACE("FocusViewById(1)");
+ root_observer.ExpectCounts(1, 1);
+ observer1.ExpectCounts(1, 1);
+ observer2.ExpectCounts(0, 0);
+ }
+
+ FocusViewById(2);
+ {
+ SCOPED_TRACE("FocusViewById(2)");
+ root_observer.ExpectCounts(2, 2);
+ observer1.ExpectCounts(2, 2);
+ observer2.ExpectCounts(1, 1);
+ }
+ }
+ void DuplicateFocusEvents() override {
+ // Focusing an existing focused view should not resend focus events.
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ ScopedFilteringFocusNotificationObserver observer1(
+ focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView());
+
+ root_observer.ExpectCounts(0, 0);
+ observer1.ExpectCounts(0, 0);
+
+ FocusViewById(1);
+ root_observer.ExpectCounts(1, 1);
+ observer1.ExpectCounts(1, 1);
+
+ FocusViewById(1);
+ root_observer.ExpectCounts(1, 1);
+ observer1.ExpectCounts(1, 1);
+ }
+ void ActivationEvents() override {
+ ActivateViewById(1);
+
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ ScopedFilteringFocusNotificationObserver observer1(
+ focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView());
+ ScopedFilteringFocusNotificationObserver observer2(
+ focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView());
+
+ root_observer.ExpectCounts(0, 0);
+ observer1.ExpectCounts(0, 0);
+ observer2.ExpectCounts(0, 0);
+
+ ActivateViewById(2);
+ root_observer.ExpectCounts(1, 1);
+ observer1.ExpectCounts(1, 1);
+ observer2.ExpectCounts(1, 1);
+ }
+ void ReactivationEvents() override {
+ ActivateViewById(1);
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ EXPECT_EQ(0, root_observer.reactivation_count());
+ GetViewById(2)->SetVisible(false);
+ // When we attempt to activate "2", which cannot be activated because it
+ // is not visible, "1" will be reactivated.
+ ActivateViewById(2);
+ EXPECT_EQ(1, root_observer.reactivation_count());
+ EXPECT_EQ(GetViewById(2),
+ root_observer.reactivation_requested_view());
+ EXPECT_EQ(GetViewById(1),
+ root_observer.reactivation_actual_view());
+ }
+ void DuplicateActivationEvents() override {
+ ActivateViewById(1);
+
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ ScopedFilteringFocusNotificationObserver observer1(
+ focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView());
+ ScopedFilteringFocusNotificationObserver observer2(
+ focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView());
+
+ root_observer.ExpectCounts(0, 0);
+ observer1.ExpectCounts(0, 0);
+ observer2.ExpectCounts(0, 0);
+
+ ActivateViewById(2);
+ root_observer.ExpectCounts(1, 1);
+ observer1.ExpectCounts(1, 1);
+ observer2.ExpectCounts(1, 1);
+
+ // Activating an existing active view should not resend activation events.
+ ActivateViewById(2);
+ root_observer.ExpectCounts(1, 1);
+ observer1.ExpectCounts(1, 1);
+ observer2.ExpectCounts(1, 1);
+ }
+ void ShiftFocusWithinActiveView() override {
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetActiveViewId());
+ EXPECT_EQ(1, GetFocusedViewId());
+ FocusViewById(11);
+ EXPECT_EQ(11, GetFocusedViewId());
+ FocusViewById(12);
+ EXPECT_EQ(12, GetFocusedViewId());
+ }
+ void ShiftFocusToChildOfInactiveView() override {
+ ActivateViewById(2);
+ EXPECT_EQ(2, GetActiveViewId());
+ EXPECT_EQ(2, GetFocusedViewId());
+ FocusViewById(11);
+ EXPECT_EQ(1, GetActiveViewId());
+ EXPECT_EQ(11, GetFocusedViewId());
+ }
+ void ShiftFocusToParentOfFocusedView() override {
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetFocusedViewId());
+ FocusViewById(11);
+ EXPECT_EQ(11, GetFocusedViewId());
+ FocusViewById(1);
+ // Focus should _not_ shift to the parent of the already-focused view.
+ EXPECT_EQ(11, GetFocusedViewId());
+ }
+ void FocusRulesOverride() override {
+ EXPECT_EQ(NULL, GetFocusedView());
+ FocusViewById(11);
+ EXPECT_EQ(11, GetFocusedViewId());
+
+ test_focus_rules()->set_focus_restriction(GetViewById(211));
+ FocusViewById(12);
+ // Input events leave focus unchanged; direct API calls will change focus
+ // to the restricted view.
+ int focused_view = IsInputEvent() ? 11 : 211;
+ EXPECT_EQ(focused_view, GetFocusedViewId());
+
+ test_focus_rules()->set_focus_restriction(NULL);
+ FocusViewById(12);
+ EXPECT_EQ(12, GetFocusedViewId());
+ }
+ void ActivationRulesOverride() override {
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetActiveViewId());
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ View* v3 = GetViewById(3);
+ test_focus_rules()->set_focus_restriction(v3);
+
+ ActivateViewById(2);
+ // Input events leave activation unchanged; direct API calls will activate
+ // the restricted view.
+ int active_view = IsInputEvent() ? 1 : 3;
+ EXPECT_EQ(active_view, GetActiveViewId());
+ EXPECT_EQ(active_view, GetFocusedViewId());
+
+ test_focus_rules()->set_focus_restriction(NULL);
+ ActivateViewById(2);
+ EXPECT_EQ(2, GetActiveViewId());
+ EXPECT_EQ(2, GetFocusedViewId());
+ }
+ void ShiftFocusOnActivation() override {
+ // When a view is activated, by default that view is also focused.
+ // An ActivationChangeObserver may shift focus to another view within the
+ // same activatable view.
+ ActivateViewById(2);
+ EXPECT_EQ(2, GetFocusedViewId());
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ ActivateViewById(2);
+
+ View* target = GetViewById(1);
+
+ scoped_ptr<FocusShiftingActivationObserver> observer(
+ new FocusShiftingActivationObserver(focus_controller(), target));
+ observer->set_shift_focus_to(target->GetChildById(11));
+ focus_controller()->AddObserver(observer.get());
+
+ ActivateViewById(1);
+
+ // w1's ActivationChangeObserver shifted focus to this child, pre-empting
+ // FocusController's default setting.
+ EXPECT_EQ(11, GetFocusedViewId());
+
+ ActivateViewById(2);
+ EXPECT_EQ(2, GetFocusedViewId());
+
+ // Simulate a focus reset by the ActivationChangeObserver. This should
+ // trigger the default setting in FocusController.
+ observer->set_shift_focus_to(nullptr);
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ focus_controller()->RemoveObserver(observer.get());
+
+ ActivateViewById(2);
+ EXPECT_EQ(2, GetFocusedViewId());
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetFocusedViewId());
+ }
+ void ShiftFocusOnActivationDueToHide() override {
+ // Similar to ShiftFocusOnActivation except the activation change is
+ // triggered by hiding the active view.
+ ActivateViewById(1);
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ // Removes view 3 as candidate for next activatable view.
+ root_view()->GetChildById(3)->SetVisible(false);
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ View* target = root_view()->GetChildById(2);
+
+ scoped_ptr<FocusShiftingActivationObserver> observer(
+ new FocusShiftingActivationObserver(focus_controller(), target));
+ observer->set_shift_focus_to(target->GetChildById(21));
+ focus_controller()->AddObserver(observer.get());
+
+ // Hide the active view.
+ root_view()->GetChildById(1)->SetVisible(false);
+
+ EXPECT_EQ(21, GetFocusedViewId());
+
+ focus_controller()->RemoveObserver(observer.get());
+ }
+ void NoShiftActiveOnActivation() override {
+ // When a view is activated, we need to prevent any change to activation
+ // from being made in response to an activation change notification.
+ }
+
+ // Verifies focus change is honored while capture held.
+ void ChangeFocusWhenNothingFocusedAndCaptured() override {
+ View* v1 = root_view()->GetChildById(1);
+ capture_controller()->SetCapture(v1);
+
+ EXPECT_EQ(-1, GetActiveViewId());
+ EXPECT_EQ(-1, GetFocusedViewId());
+
+ FocusViewById(1);
+
+ EXPECT_EQ(1, GetActiveViewId());
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ capture_controller()->ReleaseCapture(v1);
+ }
+
+ // Verifies if a view that loses activation or focus is destroyed during
+ // observer notification we don't pass the destroyed view to other observers.
+ void DontPassDestroyedView() override {
+ FocusViewById(1);
+
+ EXPECT_EQ(1, GetActiveViewId());
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ {
+ View* to_destroy = root_view()->GetChildById(1);
+ DestroyOnLoseActivationFocusNotificationObserver observer1(
+ focus_controller(), to_destroy, GetActiveView());
+ RecordingFocusNotificationObserver observer2(focus_controller(),
+ &observer1);
+
+ FocusViewById(2);
+
+ EXPECT_EQ(2, GetActiveViewId());
+ EXPECT_EQ(2, GetFocusedViewId());
+
+ EXPECT_EQ(to_destroy, observer1.GetDestroyedView());
+ EXPECT_FALSE(observer2.was_notified_with_destroyed_view());
+ }
+
+ {
+ View* to_destroy = root_view()->GetChildById(2);
+ DestroyOnLoseActivationFocusNotificationObserver observer1(
+ focus_controller(), to_destroy, GetActiveView());
+ RecordingFocusNotificationObserver observer2(focus_controller(),
+ &observer1);
+
+ FocusViewById(3);
+
+ EXPECT_EQ(3, GetActiveViewId());
+ EXPECT_EQ(3, GetFocusedViewId());
+
+ EXPECT_EQ(to_destroy, observer1.GetDestroyedView());
+ EXPECT_FALSE(observer2.was_notified_with_destroyed_view());
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerDirectTestBase);
+};
+
+// Focus and Activation changes via the FocusController API.
+class FocusControllerApiTest : public FocusControllerDirectTestBase {
+ public:
+ FocusControllerApiTest() {}
+
+ private:
+ // Overridden from FocusControllerTestBase:
+ void FocusViewDirect(View* view) override { FocusView(view); }
+ void ActivateViewDirect(View* view) override { ActivateView(view); }
+ void DeactivateViewDirect(View* view) override { DeactivateView(view); }
+ bool IsInputEvent() override { return false; }
+
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerApiTest);
+};
+
+// Focus and Activation changes via input events.
+class FocusControllerMouseEventTest : public FocusControllerDirectTestBase {
+ public:
+ FocusControllerMouseEventTest() {}
+
+ // Tests that a handled mouse event does not trigger a view activation.
+ void IgnoreHandledEvent() {
+ EXPECT_EQ(NULL, GetActiveView());
+ View* v1 = root_view()->GetChildById(1);
+ SimpleEventHandler handler;
+ GetRootViewTarget()->PrependPreTargetHandler(&handler);
+ ClickLeftButton(v1);
+ EXPECT_EQ(NULL, GetActiveView());
+ // TODO(erg): Add gesture testing when we get gestures working.
+ GetRootViewTarget()->RemovePreTargetHandler(&handler);
+ ClickLeftButton(v1);
+ EXPECT_EQ(1, GetActiveViewId());
+ }
+
+ private:
+ // Overridden from FocusControllerTestBase:
+ void FocusViewDirect(View* view) override { ClickLeftButton(view); }
+ void ActivateViewDirect(View* view) override { ClickLeftButton(view); }
+ void DeactivateViewDirect(View* view) override {
+ View* next_activatable = test_focus_rules()->GetNextActivatableView(view);
+ ClickLeftButton(next_activatable);
+ }
+ bool IsInputEvent() override { return true; }
+
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerMouseEventTest);
+};
+
+// TODO(erg): Add a FocusControllerGestureEventTest once we have working
+// gesture forwarding and handling.
+
+// Test base for tests where focus is implicitly set to a window as the result
+// of a disposition change to the focused window or the hierarchy that contains
+// it.
+class FocusControllerImplicitTestBase : public FocusControllerTestBase {
+ protected:
+ explicit FocusControllerImplicitTestBase(bool parent) : parent_(parent) {}
+
+ View* GetDispositionView(View* view) {
+ return parent_ ? view->parent() : view;
+ }
+
+ // Change the disposition of |view| in such a way as it will lose focus.
+ virtual void ChangeViewDisposition(View* view) = 0;
+
+ // Allow each disposition change test to add additional post-disposition
+ // change expectations.
+ virtual void PostDispostionChangeExpectations() {}
+
+ // Overridden from FocusControllerTestBase:
+ void BasicFocus() override {
+ EXPECT_EQ(NULL, GetFocusedView());
+
+ View* w211 = root_view()->GetChildById(211);
+ FocusView(w211);
+ EXPECT_EQ(211, GetFocusedViewId());
+
+ ChangeViewDisposition(w211);
+ // BasicFocusRules passes focus to the parent.
+ EXPECT_EQ(parent_ ? 2 : 21, GetFocusedViewId());
+ }
+
+ void BasicActivation() override {
+ DCHECK(!parent_) << "Activation tests don't support parent changes.";
+
+ EXPECT_EQ(NULL, GetActiveView());
+
+ View* w2 = root_view()->GetChildById(2);
+ ActivateView(w2);
+ EXPECT_EQ(2, GetActiveViewId());
+
+ ChangeViewDisposition(w2);
+ EXPECT_EQ(3, GetActiveViewId());
+ PostDispostionChangeExpectations();
+ }
+
+ void FocusEvents() override {
+ View* w211 = root_view()->GetChildById(211);
+ FocusView(w211);
+
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ ScopedFilteringFocusNotificationObserver observer211(
+ focus_controller(), GetViewById(211), GetActiveView(),
+ GetFocusedView());
+
+ {
+ SCOPED_TRACE("first");
+ root_observer.ExpectCounts(0, 0);
+ observer211.ExpectCounts(0, 0);
+ }
+
+ ChangeViewDisposition(w211);
+ {
+ SCOPED_TRACE("second");
+ {
+ SCOPED_TRACE("root_observer");
+ root_observer.ExpectCounts(0, 1);
+ }
+ {
+ SCOPED_TRACE("observer211");
+ observer211.ExpectCounts(0, 1);
+ }
+ }
+ }
+
+ void ActivationEvents() override {
+ DCHECK(!parent_) << "Activation tests don't support parent changes.";
+
+ View* w2 = root_view()->GetChildById(2);
+ ActivateView(w2);
+
+ ScopedFocusNotificationObserver root_observer(focus_controller());
+ ScopedFilteringFocusNotificationObserver observer2(
+ focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView());
+ ScopedFilteringFocusNotificationObserver observer3(
+ focus_controller(), GetViewById(3), GetActiveView(), GetFocusedView());
+ root_observer.ExpectCounts(0, 0);
+ observer2.ExpectCounts(0, 0);
+ observer3.ExpectCounts(0, 0);
+
+ ChangeViewDisposition(w2);
+ root_observer.ExpectCounts(1, 1);
+ observer2.ExpectCounts(1, 1);
+ observer3.ExpectCounts(1, 1);
+ }
+
+ void FocusRulesOverride() override {
+ EXPECT_EQ(NULL, GetFocusedView());
+ View* w211 = root_view()->GetChildById(211);
+ FocusView(w211);
+ EXPECT_EQ(211, GetFocusedViewId());
+
+ test_focus_rules()->set_focus_restriction(root_view()->GetChildById(11));
+ ChangeViewDisposition(w211);
+ // Normally, focus would shift to the parent (w21) but the override shifts
+ // it to 11.
+ EXPECT_EQ(11, GetFocusedViewId());
+
+ test_focus_rules()->set_focus_restriction(NULL);
+ }
+
+ void ActivationRulesOverride() override {
+ DCHECK(!parent_) << "Activation tests don't support parent changes.";
+
+ View* w1 = root_view()->GetChildById(1);
+ ActivateView(w1);
+
+ EXPECT_EQ(1, GetActiveViewId());
+ EXPECT_EQ(1, GetFocusedViewId());
+
+ View* w3 = root_view()->GetChildById(3);
+ test_focus_rules()->set_focus_restriction(w3);
+
+ // Normally, activation/focus would move to w2, but since we have a focus
+ // restriction, it should move to w3 instead.
+ ChangeViewDisposition(w1);
+ EXPECT_EQ(3, GetActiveViewId());
+ EXPECT_EQ(3, GetFocusedViewId());
+
+ test_focus_rules()->set_focus_restriction(NULL);
+ ActivateView(root_view()->GetChildById(2));
+ EXPECT_EQ(2, GetActiveViewId());
+ EXPECT_EQ(2, GetFocusedViewId());
+ }
+
+ private:
+ // When true, the disposition change occurs to the parent of the window
+ // instead of to the window. This verifies that changes occurring in the
+ // hierarchy that contains the window affect the window's focus.
+ bool parent_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerImplicitTestBase);
+};
+
+// Focus and Activation changes in response to window visibility changes.
+class FocusControllerHideTest : public FocusControllerImplicitTestBase {
+ public:
+ FocusControllerHideTest() : FocusControllerImplicitTestBase(false) {}
+
+ protected:
+ FocusControllerHideTest(bool parent)
+ : FocusControllerImplicitTestBase(parent) {}
+
+ // Overridden from FocusControllerImplicitTestBase:
+ void ChangeViewDisposition(View* view) override {
+ GetDispositionView(view)->SetVisible(false);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerHideTest);
+};
+
+// Focus and Activation changes in response to window parent visibility
+// changes.
+class FocusControllerParentHideTest : public FocusControllerHideTest {
+ public:
+ FocusControllerParentHideTest() : FocusControllerHideTest(true) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerParentHideTest);
+};
+
+// Focus and Activation changes in response to window destruction.
+class FocusControllerDestructionTest : public FocusControllerImplicitTestBase {
+ public:
+ FocusControllerDestructionTest() : FocusControllerImplicitTestBase(false) {}
+
+ protected:
+ FocusControllerDestructionTest(bool parent)
+ : FocusControllerImplicitTestBase(parent) {}
+
+ // Overridden from FocusControllerImplicitTestBase:
+ void ChangeViewDisposition(View* view) override {
+ GetDispositionView(view)->Destroy();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerDestructionTest);
+};
+
+// Focus and Activation changes in response to window removal.
+class FocusControllerRemovalTest : public FocusControllerImplicitTestBase {
+ public:
+ FocusControllerRemovalTest()
+ : FocusControllerImplicitTestBase(false),
+ window_to_destroy_(nullptr) {}
+
+ protected:
+ FocusControllerRemovalTest(bool parent)
+ : FocusControllerImplicitTestBase(parent),
+ window_to_destroy_(nullptr) {}
+
+ // Overridden from FocusControllerImplicitTestBase:
+ void ChangeViewDisposition(View* view) override {
+ View* disposition_view = GetDispositionView(view);
+ disposition_view->parent()->RemoveChild(disposition_view);
+ window_to_destroy_ = disposition_view;
+ }
+ void TearDown() override {
+ if (window_to_destroy_)
+ window_to_destroy_->Destroy();
+
+ FocusControllerImplicitTestBase::TearDown();
+ }
+
+ private:
+ View* window_to_destroy_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerRemovalTest);
+};
+
+// Focus and Activation changes in response to window parent removal.
+class FocusControllerParentRemovalTest : public FocusControllerRemovalTest {
+ public:
+ FocusControllerParentRemovalTest() : FocusControllerRemovalTest(true) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusControllerParentRemovalTest);
+};
+
+#define FOCUS_CONTROLLER_TEST(TESTCLASS, TESTNAME) \
+ TEST_F(TESTCLASS, TESTNAME) { TESTNAME(); }
+
+// Runs direct focus change tests (input events and API calls).
+//
+// TODO(erg): Enable gesture events in the future.
+#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME)
+
+// Runs implicit focus change tests for disposition changes to target.
+#define IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerHideTest, TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerDestructionTest, TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerRemovalTest, TESTNAME)
+
+// Runs implicit focus change tests for disposition changes to target's parent
+// hierarchy.
+#define IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerParentHideTest, TESTNAME) \
+ FOCUS_CONTROLLER_TEST(FocusControllerParentRemovalTest, TESTNAME)
+// TODO(erg): FocusControllerParentDestructionTest were commented out in the
+// aura version of this file, and don't work when I tried porting things over.
+
+// Runs all implicit focus change tests (changes to the target and target's
+// parent hierarchy)
+#define IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) \
+ IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \
+ IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME)
+
+// Runs all possible focus change tests.
+#define ALL_FOCUS_TESTS(TESTNAME) \
+ DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \
+ IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME)
+
+// Runs focus change tests that apply only to the target. For example,
+// implicit activation changes caused by window disposition changes do not
+// occur when changes to the containing hierarchy happen.
+#define TARGET_FOCUS_TESTS(TESTNAME) \
+ DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \
+ IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME)
+
+// - Focuses a window, verifies that focus changed.
+ALL_FOCUS_TESTS(BasicFocus);
+
+// - Activates a window, verifies that activation changed.
+TARGET_FOCUS_TESTS(BasicActivation);
+
+// - Focuses a window, verifies that focus events were dispatched.
+ALL_FOCUS_TESTS(FocusEvents);
+
+// - Focuses or activates a window multiple times, verifies that events are only
+// dispatched when focus/activation actually changes.
+DIRECT_FOCUS_CHANGE_TESTS(DuplicateFocusEvents);
+DIRECT_FOCUS_CHANGE_TESTS(DuplicateActivationEvents);
+
+// - Activates a window, verifies that activation events were dispatched.
+TARGET_FOCUS_TESTS(ActivationEvents);
+
+// - Attempts to active a hidden view, verifies that current view is
+// attempted to be reactivated and the appropriate event dispatched.
+FOCUS_CONTROLLER_TEST(FocusControllerApiTest, ReactivationEvents);
+
+// - Input events/API calls shift focus between focusable views within the
+// active view.
+DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusWithinActiveView);
+
+// - Input events/API calls to a child view of an inactive view shifts
+// activation to the activatable parent and focuses the child.
+DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToChildOfInactiveView);
+
+// - Input events/API calls to focus the parent of the focused view do not
+// shift focus away from the child.
+DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToParentOfFocusedView);
+
+// - Verifies that FocusRules determine what can be focused.
+ALL_FOCUS_TESTS(FocusRulesOverride);
+
+// - Verifies that FocusRules determine what can be activated.
+TARGET_FOCUS_TESTS(ActivationRulesOverride);
+
+// - Verifies that attempts to change focus or activation from a focus or
+// activation change observer are ignored.
+DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivation);
+DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivationDueToHide);
+DIRECT_FOCUS_CHANGE_TESTS(NoShiftActiveOnActivation);
+
+FOCUS_CONTROLLER_TEST(FocusControllerApiTest,
+ ChangeFocusWhenNothingFocusedAndCaptured);
+
+// See description above DontPassDestroyedView() for details.
+FOCUS_CONTROLLER_TEST(FocusControllerApiTest, DontPassDestroyedView);
+
+// TODO(erg): Add the TextInputClient tests here.
+
+// If a mouse event was handled, it should not activate a view.
+FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, IgnoreHandledEvent);
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/focus_rules.h b/mojo/services/window_manager/focus_rules.h
new file mode 100644
index 0000000..becd114
--- /dev/null
+++ b/mojo/services/window_manager/focus_rules.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_
+#define SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_
+
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+
+namespace window_manager {
+
+// Implemented by an object that establishes the rules about what can be
+// focused or activated.
+class FocusRules {
+ public:
+ virtual ~FocusRules() {}
+
+ // Returns true if the children of |window| can be activated.
+ virtual bool SupportsChildActivation(mojo::View* window) const = 0;
+
+ // Returns true if |view| is a toplevel view. Whether or not a view
+ // is considered toplevel is determined by a similar set of rules that
+ // govern activation and focus. Not all toplevel views are activatable,
+ // call CanActivateView() to determine if a view can be activated.
+ virtual bool IsToplevelView(mojo::View* view) const = 0;
+ // Returns true if |view| can be activated or focused.
+ virtual bool CanActivateView(mojo::View* view) const = 0;
+ // For CanFocusView(), null is supported, because null is a valid focusable
+ // view (in the case of clearing focus).
+ virtual bool CanFocusView(mojo::View* view) const = 0;
+
+ // Returns the toplevel view containing |view|. Not all toplevel views
+ // are activatable, call GetActivatableView() instead to return the
+ // activatable view, which might be in a different hierarchy.
+ // Will return null if |view| is not contained by a view considered to be
+ // a toplevel view.
+ virtual mojo::View* GetToplevelView(mojo::View* view) const = 0;
+ // Returns the activatable or focusable view given an attempt to activate or
+ // focus |view|. Some possible scenarios (not intended to be exhaustive):
+ // - |view| is a child of a non-focusable view and so focus must be set
+ // according to rules defined by the delegate, e.g. to a parent.
+ // - |view| is an activatable view that is the transient parent of a modal
+ // view, so attempts to activate |view| should result in the modal
+ // transient being activated instead.
+ // These methods may return null if they are unable to find an activatable
+ // or focusable view given |view|.
+ virtual mojo::View* GetActivatableView(mojo::View* view) const = 0;
+ virtual mojo::View* GetFocusableView(mojo::View* view) const = 0;
+
+ // Returns the next view to activate in the event that |ignore| is no longer
+ // activatable. This function is called when something is happening to
+ // |ignore| that means it can no longer have focus or activation, including
+ // but not limited to:
+ // - it or its parent hierarchy is being hidden, or removed from the
+ // RootView.
+ // - it is being destroyed.
+ // - it is being explicitly deactivated.
+ // |ignore| cannot be null.
+ virtual mojo::View* GetNextActivatableView(mojo::View* ignore) const = 0;
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_
diff --git a/mojo/services/window_manager/hit_test.h b/mojo/services/window_manager/hit_test.h
new file mode 100644
index 0000000..747dd01
--- /dev/null
+++ b/mojo/services/window_manager/hit_test.h
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_HIT_TEST_H_
+#define SERVICES_WINDOW_MANAGER_HIT_TEST_H_
+
+#if !defined(OS_WIN)
+
+// Defines the same symbolic names used by the WM_NCHITTEST Notification under
+// win32 (the integer values are not guaranteed to be equivalent). We do this
+// because we have a whole bunch of code that deals with window resizing and
+// such that requires these values.
+enum HitTestCompat {
+ HTNOWHERE = 0,
+ HTBORDER,
+ HTBOTTOM,
+ HTBOTTOMLEFT,
+ HTBOTTOMRIGHT,
+ HTCAPTION,
+ HTCLIENT,
+ HTCLOSE,
+ HTERROR,
+ HTGROWBOX,
+ HTHELP,
+ HTHSCROLL,
+ HTLEFT,
+ HTMENU,
+ HTMAXBUTTON,
+ HTMINBUTTON,
+ HTREDUCE,
+ HTRIGHT,
+ HTSIZE,
+ HTSYSMENU,
+ HTTOP,
+ HTTOPLEFT,
+ HTTOPRIGHT,
+ HTTRANSPARENT,
+ HTVSCROLL,
+ HTZOOM
+};
+
+#endif // !defined(OS_WIN)
+
+#endif // SERVICES_WINDOW_MANAGER_HIT_TEST_H_
diff --git a/mojo/services/window_manager/main.cc b/mojo/services/window_manager/main.cc
new file mode 100644
index 0000000..701a249
--- /dev/null
+++ b/mojo/services/window_manager/main.cc
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/common/tracing_impl.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/services/window_manager/basic_focus_rules.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "mojo/services/window_manager/window_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h"
+
+// ApplicationDelegate implementation file for WindowManager users (e.g.
+// core window manager tests) that do not want to provide their own
+// ApplicationDelegate::Create().
+
+using mojo::View;
+using mojo::ViewManager;
+
+namespace window_manager {
+
+class DefaultWindowManager : public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate,
+ public WindowManagerDelegate {
+ public:
+ DefaultWindowManager()
+ : window_manager_app_(new WindowManagerApp(this, this)),
+ root_(nullptr),
+ window_offset_(10) {
+ }
+ ~DefaultWindowManager() override {}
+
+ private:
+ // Overridden from mojo::ApplicationDelegate:
+ void Initialize(mojo::ApplicationImpl* impl) override {
+ window_manager_app_->Initialize(impl);
+ tracing_.Initialize(impl);
+ }
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ window_manager_app_->ConfigureIncomingConnection(connection);
+ return true;
+ }
+
+ // Overridden from ViewManagerDelegate:
+ void OnEmbed(View* root,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override {
+ root_ = root;
+ window_manager_app_->InitFocus(
+ make_scoped_ptr(new window_manager::BasicFocusRules(root_)));
+ }
+ void OnViewManagerDisconnected(ViewManager* view_manager) override {}
+
+ // Overridden from WindowManagerDelegate:
+ void Embed(const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override {
+ DCHECK(root_);
+ View* view = root_->view_manager()->CreateView();
+ root_->AddChild(view);
+
+ mojo::Rect rect;
+ rect.x = rect.y = window_offset_;
+ rect.width = rect.height = 100;
+ view->SetBounds(rect);
+ window_offset_ += 10;
+
+ view->SetVisible(true);
+ view->Embed(url, services.Pass(), exposed_services.Pass());
+ }
+
+ scoped_ptr<WindowManagerApp> window_manager_app_;
+
+ View* root_;
+ int window_offset_;
+ mojo::TracingImpl tracing_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DefaultWindowManager);
+};
+
+} // namespace window_manager
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunnerChromium runner(
+ new window_manager::DefaultWindowManager);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc
new file mode 100644
index 0000000..4bd0b80
--- /dev/null
+++ b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/native_viewport_event_dispatcher_impl.h"
+
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "mojo/services/window_manager/view_event_dispatcher.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+
+namespace window_manager {
+
+NativeViewportEventDispatcherImpl::NativeViewportEventDispatcherImpl(
+ WindowManagerApp* app,
+ mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request)
+ : app_(app), binding_(this, request.Pass()) {
+}
+NativeViewportEventDispatcherImpl::~NativeViewportEventDispatcherImpl() {
+}
+
+ui::EventProcessor* NativeViewportEventDispatcherImpl::GetEventProcessor() {
+ return app_->event_dispatcher();
+}
+
+void NativeViewportEventDispatcherImpl::OnEvent(
+ mojo::EventPtr event,
+ const mojo::Callback<void()>& callback) {
+ scoped_ptr<ui::Event> ui_event = event.To<scoped_ptr<ui::Event>>();
+
+ if (ui_event)
+ SendEventToProcessor(ui_event.get());
+
+ callback.Run();
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h
new file mode 100644
index 0000000..6624c93
--- /dev/null
+++ b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_
+#define SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h"
+#include "ui/events/event_source.h"
+
+namespace window_manager {
+
+class WindowManagerApp;
+
+class NativeViewportEventDispatcherImpl
+ : public ui::EventSource,
+ public mojo::NativeViewportEventDispatcher {
+ public:
+ NativeViewportEventDispatcherImpl(
+ WindowManagerApp* app,
+ mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request);
+ ~NativeViewportEventDispatcherImpl() override;
+
+ private:
+ // ui::EventSource:
+ ui::EventProcessor* GetEventProcessor() override;
+
+ // NativeViewportEventDispatcher:
+ void OnEvent(mojo::EventPtr event,
+ const mojo::Callback<void()>& callback) override;
+
+ WindowManagerApp* app_;
+
+ mojo::StrongBinding<mojo::NativeViewportEventDispatcher> binding_;
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportEventDispatcherImpl);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_
diff --git a/mojo/services/window_manager/run_all_unittests.cc b/mojo/services/window_manager/run_all_unittests.cc
new file mode 100644
index 0000000..51fd967
--- /dev/null
+++ b/mojo/services/window_manager/run_all_unittests.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "ui/gl/gl_surface.h"
+
+#if defined(USE_X11)
+#include "ui/gfx/x/x11_connection.h"
+#endif
+
+namespace window_manager {
+
+class WindowManagerTestSuite : public base::TestSuite {
+ public:
+ WindowManagerTestSuite(int argc, char** argv) : TestSuite(argc, argv) {}
+ ~WindowManagerTestSuite() override {}
+
+ protected:
+ void Initialize() override {
+#if defined(USE_X11)
+ // Each test ends up creating a new thread for the native viewport service.
+ // In other words we'll use X on different threads, so tell it that.
+ gfx::InitializeThreadedX11();
+#endif
+ base::TestSuite::Initialize();
+ gfx::GLSurface::InitializeOneOffForTests();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerTestSuite);
+};
+
+} // namespace window_manager
+
+int main(int argc, char** argv) {
+ window_manager::WindowManagerTestSuite test_suite(argc, argv);
+
+ return base::LaunchUnitTests(
+ argc, argv, base::Bind(&TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/services/window_manager/view_event_dispatcher.cc b/mojo/services/window_manager/view_event_dispatcher.cc
new file mode 100644
index 0000000..657cf88
--- /dev/null
+++ b/mojo/services/window_manager/view_event_dispatcher.cc
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/view_event_dispatcher.h"
+
+#include "mojo/services/window_manager/view_target.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+
+namespace window_manager {
+
+ViewEventDispatcher::ViewEventDispatcher()
+ : event_dispatch_target_(nullptr),
+ old_dispatch_target_(nullptr) {
+}
+
+ViewEventDispatcher::~ViewEventDispatcher() {}
+
+void ViewEventDispatcher::SetRootViewTarget(ViewTarget* root_view_target) {
+ root_view_target_ = root_view_target;
+}
+
+ui::EventTarget* ViewEventDispatcher::GetRootTarget() {
+ return root_view_target_;
+}
+
+void ViewEventDispatcher::OnEventProcessingStarted(ui::Event* event) {
+}
+
+bool ViewEventDispatcher::CanDispatchToTarget(ui::EventTarget* target) {
+ return event_dispatch_target_ == target;
+}
+
+ui::EventDispatchDetails ViewEventDispatcher::PreDispatchEvent(
+ ui::EventTarget* target,
+ ui::Event* event) {
+ // TODO(erg): PreDispatch in aura::WindowEventDispatcher does many, many
+ // things. It, and the functions split off for different event types, are
+ // most of the file.
+ old_dispatch_target_ = event_dispatch_target_;
+ event_dispatch_target_ = static_cast<ViewTarget*>(target);
+ return ui::EventDispatchDetails();
+}
+
+ui::EventDispatchDetails ViewEventDispatcher::PostDispatchEvent(
+ ui::EventTarget* target,
+ const ui::Event& event) {
+ // TODO(erg): Not at all as long as PreDispatchEvent, but still missing core
+ // details.
+ event_dispatch_target_ = old_dispatch_target_;
+ old_dispatch_target_ = nullptr;
+ return ui::EventDispatchDetails();
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/view_event_dispatcher.h b/mojo/services/window_manager/view_event_dispatcher.h
new file mode 100644
index 0000000..399939e
--- /dev/null
+++ b/mojo/services/window_manager/view_event_dispatcher.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_
+#define SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_processor.h"
+#include "ui/events/event_target.h"
+
+namespace window_manager {
+
+class ViewTarget;
+
+class ViewEventDispatcher : public ui::EventProcessor {
+ public:
+ ViewEventDispatcher();
+ ~ViewEventDispatcher() override;
+
+ void SetRootViewTarget(ViewTarget* root_view_target);
+
+ private:
+ // Overridden from ui::EventProcessor:
+ ui::EventTarget* GetRootTarget() override;
+ void OnEventProcessingStarted(ui::Event* event) override;
+
+ // Overridden from ui::EventDispatcherDelegate.
+ bool CanDispatchToTarget(ui::EventTarget* target) override;
+ ui::EventDispatchDetails PreDispatchEvent(ui::EventTarget* target,
+ ui::Event* event) override;
+ ui::EventDispatchDetails PostDispatchEvent(
+ ui::EventTarget* target, const ui::Event& event) override;
+
+ // We keep a weak reference to ViewTarget*, which corresponds to the root of
+ // the mojo::View tree.
+ ViewTarget* root_view_target_;
+
+ ViewTarget* event_dispatch_target_;
+ ViewTarget* old_dispatch_target_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewEventDispatcher);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_
diff --git a/mojo/services/window_manager/view_target.cc b/mojo/services/window_manager/view_target.cc
new file mode 100644
index 0000000..adb3ec2
--- /dev/null
+++ b/mojo/services/window_manager/view_target.cc
@@ -0,0 +1,192 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/view_target.h"
+
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/services/window_manager/view_targeter.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h"
+#include "ui/events/event.h"
+#include "ui/events/event_target_iterator.h"
+#include "ui/events/event_targeter.h"
+#include "ui/gfx/geometry/point3_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace window_manager {
+
+namespace {
+
+DEFINE_OWNED_VIEW_PROPERTY_KEY(ViewTarget, kViewTargetKey, nullptr);
+
+// Provides a version which keeps a copy of the data (for when it has to be
+// derived instead of pointed at).
+template <typename T>
+class CopyingEventTargetIteratorImpl : public ui::EventTargetIterator {
+ public:
+ explicit CopyingEventTargetIteratorImpl(const std::vector<T*>& children)
+ : children_(children),
+ begin_(children_.rbegin()),
+ end_(children_.rend()) {}
+ ~CopyingEventTargetIteratorImpl() override {}
+
+ ui::EventTarget* GetNextTarget() override {
+ if (begin_ == end_)
+ return nullptr;
+ ui::EventTarget* target = *(begin_);
+ ++begin_;
+ return target;
+ }
+
+ private:
+ typename std::vector<T*> children_;
+ typename std::vector<T*>::const_reverse_iterator begin_;
+ typename std::vector<T*>::const_reverse_iterator end_;
+};
+
+} // namespace
+
+ViewTarget::~ViewTarget() {
+}
+
+// static
+ViewTarget* ViewTarget::TargetFromView(mojo::View* view) {
+ if (!view)
+ return nullptr;
+
+ ViewTarget* target = view->GetLocalProperty(kViewTargetKey);
+ if (target)
+ return target;
+
+ return new ViewTarget(view);
+}
+
+void ViewTarget::ConvertPointToTarget(const ViewTarget* source,
+ const ViewTarget* target,
+ gfx::Point* point) {
+ // TODO(erg): Do we need to deal with |source| and |target| being in
+ // different trees?
+ DCHECK_EQ(source->GetRoot(), target->GetRoot());
+ if (source == target)
+ return;
+
+ const ViewTarget* root_target = source->GetRoot();
+ CHECK_EQ(root_target, target->GetRoot());
+
+ if (source != root_target)
+ source->ConvertPointForAncestor(root_target, point);
+ if (target != root_target)
+ target->ConvertPointFromAncestor(root_target, point);
+}
+
+std::vector<ViewTarget*> ViewTarget::GetChildren() const {
+ std::vector<ViewTarget*> targets;
+ for (mojo::View* child : view_->children())
+ targets.push_back(TargetFromView(child));
+ return targets;
+}
+
+const ViewTarget* ViewTarget::GetParent() const {
+ return TargetFromView(view_->parent());
+}
+
+gfx::Rect ViewTarget::GetBounds() const {
+ return view_->bounds().To<gfx::Rect>();
+}
+
+bool ViewTarget::HasParent() const {
+ return !!view_->parent();
+}
+
+bool ViewTarget::IsVisible() const {
+ return view_->visible();
+}
+
+const ViewTarget* ViewTarget::GetRoot() const {
+ const ViewTarget* root = this;
+ for (const ViewTarget* parent = this; parent; parent = parent->GetParent())
+ root = parent;
+ return root;
+}
+
+scoped_ptr<ViewTargeter> ViewTarget::SetEventTargeter(
+ scoped_ptr<ViewTargeter> targeter) {
+ scoped_ptr<ViewTargeter> old_targeter = targeter_.Pass();
+ targeter_ = targeter.Pass();
+ return old_targeter.Pass();
+}
+
+bool ViewTarget::CanAcceptEvent(const ui::Event& event) {
+ // We need to make sure that a touch cancel event and any gesture events it
+ // creates can always reach the window. This ensures that we receive a valid
+ // touch / gesture stream.
+ if (event.IsEndingEvent())
+ return true;
+
+ if (!view_->visible())
+ return false;
+
+ // The top-most window can always process an event.
+ if (!view_->parent())
+ return true;
+
+ // In aura, we only accept events if this is a key event or if the user
+ // supplied a TargetHandler, usually the aura::WindowDelegate. Here, we're
+ // just forwarding events to other Views which may be in other processes, so
+ // always accept.
+ return true;
+}
+
+ui::EventTarget* ViewTarget::GetParentTarget() {
+ return TargetFromView(view_->parent());
+}
+
+scoped_ptr<ui::EventTargetIterator> ViewTarget::GetChildIterator() const {
+ return scoped_ptr<ui::EventTargetIterator>(
+ new CopyingEventTargetIteratorImpl<ViewTarget>(GetChildren()));
+}
+
+ui::EventTargeter* ViewTarget::GetEventTargeter() {
+ return targeter_.get();
+}
+
+void ViewTarget::ConvertEventToTarget(ui::EventTarget* target,
+ ui::LocatedEvent* event) {
+ event->ConvertLocationToTarget(this, static_cast<ViewTarget*>(target));
+}
+
+ViewTarget::ViewTarget(mojo::View* view_to_wrap) : view_(view_to_wrap) {
+ DCHECK(view_->GetLocalProperty(kViewTargetKey) == nullptr);
+ view_->SetLocalProperty(kViewTargetKey, this);
+}
+
+bool ViewTarget::ConvertPointForAncestor(const ViewTarget* ancestor,
+ gfx::Point* point) const {
+ gfx::Vector2d offset;
+ bool result = GetTargetOffsetRelativeTo(ancestor, &offset);
+ *point += offset;
+ return result;
+}
+
+bool ViewTarget::ConvertPointFromAncestor(const ViewTarget* ancestor,
+ gfx::Point* point) const {
+ gfx::Vector2d offset;
+ bool result = GetTargetOffsetRelativeTo(ancestor, &offset);
+ *point -= offset;
+ return result;
+}
+
+bool ViewTarget::GetTargetOffsetRelativeTo(const ViewTarget* ancestor,
+ gfx::Vector2d* offset) const {
+ const ViewTarget* v = this;
+ for (; v && v != ancestor; v = v->GetParent()) {
+ gfx::Rect bounds = v->GetBounds();
+ *offset += gfx::Vector2d(bounds.x(), bounds.y());
+ }
+ return v == ancestor;
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/view_target.h b/mojo/services/window_manager/view_target.h
new file mode 100644
index 0000000..1119e1f
--- /dev/null
+++ b/mojo/services/window_manager/view_target.h
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_
+#define SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_
+
+#include "ui/events/event_target.h"
+
+namespace gfx {
+class Point;
+class Rect;
+class Vector2d;
+}
+
+namespace ui {
+class EventTargeter;
+}
+
+namespace mojo {
+class View;
+}
+
+namespace window_manager {
+
+class TestView;
+class ViewTargeter;
+class WindowManagerApp;
+
+// A wrapper class around mojo::View; we can't subclass View to implement the
+// event targeting interfaces, so we create a separate object which observes
+// the View and ties its lifetime to it.
+//
+// We set ourselves as a property of the view passed in, and we are owned by
+// said View.
+class ViewTarget : public ui::EventTarget {
+ public:
+ ~ViewTarget() override;
+
+ // Returns the ViewTarget for a View. ViewTargets are owned by the |view|
+ // passed in, and are created on demand.
+ static ViewTarget* TargetFromView(mojo::View* view);
+
+ // Converts |point| from |source|'s coordinates to |target|'s. If |source| is
+ // NULL, the function returns without modifying |point|. |target| cannot be
+ // NULL.
+ static void ConvertPointToTarget(const ViewTarget* source,
+ const ViewTarget* target,
+ gfx::Point* point);
+
+ mojo::View* view() { return view_; }
+
+ // TODO(erg): Make this const once we've removed aura from the tree and it's
+ // feasible to change all callers of the EventTargeter interface to pass and
+ // accept const objects. (When that gets done, re-const the
+ // EventTargetIterator::GetNextTarget and EventTarget::GetChildIterator
+ // interfaces.)
+ std::vector<ViewTarget*> GetChildren() const;
+
+ const ViewTarget* GetParent() const;
+ gfx::Rect GetBounds() const;
+ bool HasParent() const;
+ bool IsVisible() const;
+
+ const ViewTarget* GetRoot() const;
+
+ // Sets a new ViewTargeter for the view, and returns the previous
+ // ViewTargeter.
+ scoped_ptr<ViewTargeter> SetEventTargeter(scoped_ptr<ViewTargeter> targeter);
+
+ // Overridden from ui::EventTarget:
+ bool CanAcceptEvent(const ui::Event& event) override;
+ EventTarget* GetParentTarget() override;
+ scoped_ptr<ui::EventTargetIterator> GetChildIterator() const override;
+ ui::EventTargeter* GetEventTargeter() override;
+ void ConvertEventToTarget(ui::EventTarget* target,
+ ui::LocatedEvent* event) override;
+
+ private:
+ friend class TestView;
+ explicit ViewTarget(mojo::View* view_to_wrap);
+
+ bool ConvertPointForAncestor(const ViewTarget* ancestor,
+ gfx::Point* point) const;
+ bool ConvertPointFromAncestor(const ViewTarget* ancestor,
+ gfx::Point* point) const;
+ bool GetTargetOffsetRelativeTo(const ViewTarget* ancestor,
+ gfx::Vector2d* offset) const;
+
+ // The mojo::View that we dispatch to.
+ mojo::View* view_;
+
+ scoped_ptr<ViewTargeter> targeter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewTarget);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_
diff --git a/mojo/services/window_manager/view_target_unittest.cc b/mojo/services/window_manager/view_target_unittest.cc
new file mode 100644
index 0000000..bec5c37
--- /dev/null
+++ b/mojo/services/window_manager/view_target_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/view_target.h"
+
+#include <set>
+
+#include "mojo/services/window_manager/window_manager_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace window_manager {
+
+using ViewTargetTest = testing::Test;
+
+// V1
+// +-- V2
+// +-- V3
+TEST_F(ViewTargetTest, GetRoot) {
+ TestView v1(1, gfx::Rect(20, 20, 400, 400));
+ TestView v2(2, gfx::Rect(10, 10, 350, 350));
+ TestView v3(3, gfx::Rect(10, 10, 100, 100));
+ v1.AddChild(&v2);
+ v2.AddChild(&v3);
+
+ EXPECT_EQ(ViewTarget::TargetFromView(&v1),
+ ViewTarget::TargetFromView(&v1)->GetRoot());
+ EXPECT_EQ(ViewTarget::TargetFromView(&v1),
+ ViewTarget::TargetFromView(&v2)->GetRoot());
+ EXPECT_EQ(ViewTarget::TargetFromView(&v1),
+ ViewTarget::TargetFromView(&v3)->GetRoot());
+}
+
+// V1
+// +-- V2
+TEST_F(ViewTargetTest, ConvertPointToTarget_Simple) {
+ TestView v1(1, gfx::Rect(20, 20, 400, 400));
+ TestView v2(2, gfx::Rect(10, 10, 350, 350));
+ v1.AddChild(&v2);
+
+ ViewTarget* t1 = v1.target();
+ ViewTarget* t2 = v2.target();
+
+ gfx::Point point1_in_t2_coords(5, 5);
+ ViewTarget::ConvertPointToTarget(t2, t1, &point1_in_t2_coords);
+ gfx::Point point1_in_t1_coords(15, 15);
+ EXPECT_EQ(point1_in_t1_coords, point1_in_t2_coords);
+
+ gfx::Point point2_in_t1_coords(5, 5);
+ ViewTarget::ConvertPointToTarget(t1, t2, &point2_in_t1_coords);
+ gfx::Point point2_in_t2_coords(-5, -5);
+ EXPECT_EQ(point2_in_t2_coords, point2_in_t1_coords);
+}
+
+// V1
+// +-- V2
+// +-- V3
+TEST_F(ViewTargetTest, ConvertPointToTarget_Medium) {
+ TestView v1(1, gfx::Rect(20, 20, 400, 400));
+ TestView v2(2, gfx::Rect(10, 10, 350, 350));
+ TestView v3(3, gfx::Rect(10, 10, 100, 100));
+ v1.AddChild(&v2);
+ v2.AddChild(&v3);
+
+ ViewTarget* t1 = v1.target();
+ ViewTarget* t3 = v3.target();
+
+ gfx::Point point1_in_t3_coords(5, 5);
+ ViewTarget::ConvertPointToTarget(t3, t1, &point1_in_t3_coords);
+ gfx::Point point1_in_t1_coords(25, 25);
+ EXPECT_EQ(point1_in_t1_coords, point1_in_t3_coords);
+
+ gfx::Point point2_in_t1_coords(5, 5);
+ ViewTarget::ConvertPointToTarget(t1, t3, &point2_in_t1_coords);
+ gfx::Point point2_in_t3_coords(-15, -15);
+ EXPECT_EQ(point2_in_t3_coords, point2_in_t1_coords);
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/view_targeter.cc b/mojo/services/window_manager/view_targeter.cc
new file mode 100644
index 0000000..58fb370
--- /dev/null
+++ b/mojo/services/window_manager/view_targeter.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 "mojo/services/window_manager/view_targeter.h"
+
+#include "mojo/services/window_manager/capture_controller.h"
+#include "mojo/services/window_manager/focus_controller.h"
+#include "mojo/services/window_manager/view_target.h"
+
+namespace window_manager {
+
+ViewTargeter::ViewTargeter() {}
+
+ViewTargeter::~ViewTargeter() {}
+
+ui::EventTarget* ViewTargeter::FindTargetForEvent(ui::EventTarget* root,
+ ui::Event* event) {
+ ViewTarget* view = static_cast<ViewTarget*>(root);
+ ViewTarget* target =
+ event->IsKeyEvent()
+ ? FindTargetForKeyEvent(view, *static_cast<ui::KeyEvent*>(event))
+ : static_cast<ViewTarget*>(
+ EventTargeter::FindTargetForEvent(root, event));
+
+ // TODO(erg): The aura version of this method does a lot of work to handle
+ // dispatching to a target that isn't a child of |view|. For now, punt on
+ // this.
+ DCHECK_EQ(view->GetRoot(), target->GetRoot());
+
+ return target;
+}
+
+ui::EventTarget* ViewTargeter::FindTargetForLocatedEvent(
+ ui::EventTarget* root,
+ ui::LocatedEvent* event) {
+ ViewTarget* view = static_cast<ViewTarget*>(root);
+ if (!view->HasParent()) {
+ ViewTarget* target = FindTargetInRootView(view, *event);
+ if (target) {
+ view->ConvertEventToTarget(target, event);
+ return target;
+ }
+ }
+ return EventTargeter::FindTargetForLocatedEvent(view, event);
+}
+
+bool ViewTargeter::SubtreeCanAcceptEvent(ui::EventTarget* target,
+ const ui::LocatedEvent& event) const {
+ ViewTarget* view = static_cast<ViewTarget*>(target);
+
+ if (!view->IsVisible())
+ return false;
+
+ // TODO(erg): We may need to keep track of the parent on ViewTarget, because
+ // we have a check here about
+ // WindowDelegate::ShouldDescendIntoChildForEventHandling().
+
+ // TODO(sky): decide if we really want this. If we do, it should be a public
+ // constant and documented.
+ if (view->view()->shared_properties().count("deliver-events-to-parent"))
+ return false;
+
+ return true;
+}
+
+bool ViewTargeter::EventLocationInsideBounds(
+ ui::EventTarget* target,
+ const ui::LocatedEvent& event) const {
+ ViewTarget* view = static_cast<ViewTarget*>(target);
+ gfx::Point point = event.location();
+ const ViewTarget* parent = view->GetParent();
+ if (parent)
+ ViewTarget::ConvertPointToTarget(parent, view, &point);
+ return gfx::Rect(view->GetBounds().size()).Contains(point);
+}
+
+ViewTarget* ViewTargeter::FindTargetForKeyEvent(ViewTarget* view_target,
+ const ui::KeyEvent& key) {
+ FocusController* focus_controller = GetFocusController(view_target->view());
+ if (focus_controller) {
+ mojo::View* focused_view = focus_controller->GetFocusedView();
+ if (focused_view)
+ return ViewTarget::TargetFromView(focused_view);
+ }
+ return view_target;
+}
+
+ViewTarget* ViewTargeter::FindTargetInRootView(ViewTarget* root_view,
+ const ui::LocatedEvent& event) {
+ // TODO(erg): This here is important because it resolves
+ // mouse_pressed_handler() in the aura version. This is what makes sure
+ // that a view gets both the mouse down and up.
+
+ CaptureController* capture_controller =
+ GetCaptureController(root_view->view());
+ if (capture_controller) {
+ mojo::View* capture_view = capture_controller->GetCapture();
+ if (capture_view)
+ return ViewTarget::TargetFromView(capture_view);
+ }
+
+ // TODO(erg): There's a whole bunch of junk about handling touch events
+ // here. Handle later.
+
+ return nullptr;
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/view_targeter.h b/mojo/services/window_manager/view_targeter.h
new file mode 100644
index 0000000..3a01e57
--- /dev/null
+++ b/mojo/services/window_manager/view_targeter.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_
+#define SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_
+
+#include "ui/events/event_targeter.h"
+
+namespace window_manager {
+
+class ViewTarget;
+
+class ViewTargeter : public ui::EventTargeter {
+ public:
+ ViewTargeter();
+ ~ViewTargeter() override;
+
+ protected:
+ // ui::EventTargeter:
+ ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
+ ui::Event* event) override;
+ ui::EventTarget* FindTargetForLocatedEvent(ui::EventTarget* root,
+ ui::LocatedEvent* event) override;
+ bool SubtreeCanAcceptEvent(ui::EventTarget* target,
+ const ui::LocatedEvent& event) const override;
+ bool EventLocationInsideBounds(ui::EventTarget* target,
+ const ui::LocatedEvent& event) const override;
+
+ private:
+ // Targets either the root View or the currently focused view.
+ ViewTarget* FindTargetForKeyEvent(ViewTarget* view, const ui::KeyEvent& key);
+
+ // Deals with cases where the |root_view| needs to change how things are
+ // dispatched. (For example, in the case of capture.)
+ ViewTarget* FindTargetInRootView(ViewTarget* root_view,
+ const ui::LocatedEvent& event);
+
+ DISALLOW_COPY_AND_ASSIGN(ViewTargeter);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_
diff --git a/mojo/services/window_manager/view_targeter_unittest.cc b/mojo/services/window_manager/view_targeter_unittest.cc
new file mode 100644
index 0000000..0b3281d
--- /dev/null
+++ b/mojo/services/window_manager/view_targeter_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/view_targeter.h"
+
+#include "mojo/services/window_manager/basic_focus_rules.h"
+#include "mojo/services/window_manager/capture_controller.h"
+#include "mojo/services/window_manager/focus_controller.h"
+#include "mojo/services/window_manager/view_event_dispatcher.h"
+#include "mojo/services/window_manager/window_manager_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/test/test_event_handler.h"
+
+namespace window_manager {
+
+class ViewTargeterTest : public testing::Test {
+ public:
+ ViewTargeterTest() {}
+ ~ViewTargeterTest() override {}
+
+ void SetUp() override {
+ view_event_dispatcher_.reset(new ViewEventDispatcher());
+ }
+
+ void TearDown() override {
+ view_event_dispatcher_.reset();
+ testing::Test::TearDown();
+ }
+
+ protected:
+ scoped_ptr<ViewEventDispatcher> view_event_dispatcher_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ViewTargeterTest);
+};
+
+TEST_F(ViewTargeterTest, Basic) {
+ // The dispatcher will take ownership of the tree root.
+ TestView root(1, gfx::Rect(0, 0, 100, 100));
+ ViewTarget* root_target = root.target();
+ root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter()));
+ view_event_dispatcher_->SetRootViewTarget(root_target);
+
+ CaptureController capture_controller;
+ SetCaptureController(&root, &capture_controller);
+
+ TestView one(2, gfx::Rect(0, 0, 500, 100));
+ TestView two(3, gfx::Rect(501, 0, 500, 1000));
+
+ root.AddChild(&one);
+ root.AddChild(&two);
+
+ ui::test::TestEventHandler handler;
+ one.target()->AddPreTargetHandler(&handler);
+
+ ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::Point(20, 20),
+ gfx::Point(20, 20), ui::EventTimeForNow(), ui::EF_NONE,
+ ui::EF_NONE);
+ ui::EventDispatchDetails details =
+ view_event_dispatcher_->OnEventFromSource(&press);
+ ASSERT_FALSE(details.dispatcher_destroyed);
+
+ EXPECT_EQ(1, handler.num_mouse_events());
+
+ one.target()->RemovePreTargetHandler(&handler);
+}
+
+TEST_F(ViewTargeterTest, KeyTest) {
+ // The dispatcher will take ownership of the tree root.
+ TestView root(1, gfx::Rect(0, 0, 100, 100));
+ ViewTarget* root_target = root.target();
+ root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter()));
+ view_event_dispatcher_->SetRootViewTarget(root_target);
+
+ CaptureController capture_controller;
+ SetCaptureController(&root, &capture_controller);
+
+ TestView one(2, gfx::Rect(0, 0, 500, 100));
+ TestView two(3, gfx::Rect(501, 0, 500, 1000));
+
+ root.AddChild(&one);
+ root.AddChild(&two);
+
+ ui::test::TestEventHandler one_handler;
+ one.target()->AddPreTargetHandler(&one_handler);
+
+ ui::test::TestEventHandler two_handler;
+ two.target()->AddPreTargetHandler(&two_handler);
+
+ FocusController focus_controller(make_scoped_ptr(new BasicFocusRules(&root)));
+ SetFocusController(&root, &focus_controller);
+
+ // Focus |one|. Then test that it receives a key event.
+ focus_controller.FocusView(&one);
+ ui::KeyEvent key_event_one(ui::ET_KEY_PRESSED, ui::VKEY_A, 0);
+ ui::EventDispatchDetails details =
+ view_event_dispatcher_->OnEventFromSource(&key_event_one);
+ ASSERT_FALSE(details.dispatcher_destroyed);
+ EXPECT_EQ(1, one_handler.num_key_events());
+
+ // Focus |two|. Then test that it receives a key event.
+ focus_controller.FocusView(&two);
+ ui::KeyEvent key_event_two(ui::ET_KEY_PRESSED, ui::VKEY_A, 0);
+ details = view_event_dispatcher_->OnEventFromSource(&key_event_two);
+ ASSERT_FALSE(details.dispatcher_destroyed);
+ EXPECT_EQ(1, two_handler.num_key_events());
+
+ two.target()->RemovePreTargetHandler(&two_handler);
+ one.target()->RemovePreTargetHandler(&one_handler);
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_api_unittest.cc b/mojo/services/window_manager/window_manager_api_unittest.cc
new file mode 100644
index 0000000..7d5dccf
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_api_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/memory/scoped_vector.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/shell/application_manager/application_manager.h"
+#include "mojo/shell/shell_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h"
+
+using mojo::ApplicationImpl;
+using mojo::Id;
+using mojo::View;
+
+namespace window_manager {
+namespace {
+
+const char kTestServiceURL[] = "mojo:test_url";
+
+void EmptyResultCallback(bool result) {}
+
+class TestWindowManagerObserver : public mojo::WindowManagerObserver {
+ public:
+ using NodeIdCallback = base::Callback<void(Id)>;
+
+ explicit TestWindowManagerObserver(
+ mojo::InterfaceRequest<mojo::WindowManagerObserver> observer_request)
+ : binding_(this, observer_request.Pass()) {}
+ ~TestWindowManagerObserver() override {}
+
+ void set_focus_changed_callback(const NodeIdCallback& callback) {
+ focus_changed_callback_ = callback;
+ }
+ void set_active_window_changed_callback(const NodeIdCallback& callback) {
+ active_window_changed_callback_ = callback;
+ }
+
+ private:
+ // Overridden from mojo::WindowManagerObserver:
+ void OnCaptureChanged(Id new_capture_node_id) override {}
+ void OnFocusChanged(Id focused_node_id) override {
+ if (!focus_changed_callback_.is_null())
+ focus_changed_callback_.Run(focused_node_id);
+ }
+ void OnActiveWindowChanged(Id active_window) override {
+ if (!active_window_changed_callback_.is_null())
+ active_window_changed_callback_.Run(active_window);
+ }
+
+ NodeIdCallback focus_changed_callback_;
+ NodeIdCallback active_window_changed_callback_;
+ mojo::Binding<WindowManagerObserver> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindowManagerObserver);
+};
+
+class TestApplicationLoader : public mojo::shell::ApplicationLoader,
+ public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate {
+ public:
+ typedef base::Callback<void(View*)> RootAddedCallback;
+
+ explicit TestApplicationLoader(const RootAddedCallback& root_added_callback)
+ : root_added_callback_(root_added_callback) {}
+ ~TestApplicationLoader() override {}
+
+ private:
+ // Overridden from mojo::shell::ApplicationLoader:
+ void Load(
+ const GURL& url,
+ mojo::InterfaceRequest<mojo::Application> application_request) override {
+ ASSERT_TRUE(application_request.is_pending());
+ scoped_ptr<ApplicationImpl> app(
+ new ApplicationImpl(this, application_request.Pass()));
+ apps_.push_back(app.release());
+ }
+
+ // Overridden from mojo::ApplicationDelegate:
+ void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new mojo::ViewManagerClientFactory(app->shell(), this));
+ }
+
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // Overridden from mojo::ViewManagerDelegate:
+ void OnEmbed(View* root,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override {
+ root_added_callback_.Run(root);
+ }
+ void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override {}
+
+ RootAddedCallback root_added_callback_;
+
+ ScopedVector<ApplicationImpl> apps_;
+ scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader);
+};
+
+} // namespace
+
+class WindowManagerApiTest : public testing::Test {
+ public:
+ WindowManagerApiTest() {}
+ ~WindowManagerApiTest() override {}
+
+ protected:
+ Id WaitForEmbed() {
+ Id id;
+ base::RunLoop run_loop;
+ root_added_callback_ = base::Bind(&WindowManagerApiTest::OnEmbed,
+ base::Unretained(this), &id, &run_loop);
+ run_loop.Run();
+ return id;
+ }
+
+ Id WaitForFocusChange() {
+ Id new_focused;
+ base::RunLoop run_loop;
+ window_manager_observer()->set_focus_changed_callback(
+ base::Bind(&WindowManagerApiTest::OnFocusChanged,
+ base::Unretained(this), &new_focused, &run_loop));
+ run_loop.Run();
+ return new_focused;
+ }
+
+ Id WaitForActiveWindowChange() {
+ Id new_active;
+ base::RunLoop run_loop;
+ window_manager_observer()->set_active_window_changed_callback(
+ base::Bind(&WindowManagerApiTest::OnActiveWindowChanged,
+ base::Unretained(this), &new_active, &run_loop));
+ run_loop.Run();
+ return new_active;
+ }
+
+ Id OpenWindow() {
+ return OpenWindowWithURL(kTestServiceURL);
+ }
+
+ Id OpenWindowWithURL(const std::string& url) {
+ base::RunLoop run_loop;
+ window_manager_->Embed(url, nullptr, nullptr);
+ run_loop.Run();
+ return WaitForEmbed();
+ }
+
+ TestWindowManagerObserver* window_manager_observer() {
+ return window_manager_observer_.get();
+ }
+
+ mojo::WindowManagerPtr window_manager_;
+
+ private:
+ // Overridden from testing::Test:
+ void SetUp() override {
+ test_helper_.reset(new mojo::shell::ShellTestHelper);
+ test_helper_->Init();
+ test_helper_->AddURLMapping(GURL("mojo:window_manager"),
+ GURL("mojo:core_window_manager"));
+ test_helper_->SetLoaderForURL(
+ scoped_ptr<mojo::shell::ApplicationLoader>(
+ new TestApplicationLoader(base::Bind(
+ &WindowManagerApiTest::OnRootAdded, base::Unretained(this)))),
+ GURL(kTestServiceURL));
+ ConnectToWindowManager2();
+ }
+ void TearDown() override {}
+
+ void ConnectToWindowManager2() {
+ test_helper_->application_manager()->ConnectToService(
+ GURL("mojo:window_manager"), &window_manager_);
+ base::RunLoop connect_loop;
+ mojo::WindowManagerObserverPtr observer;
+ window_manager_observer_.reset(
+ new TestWindowManagerObserver(GetProxy(&observer)));
+
+ window_manager_->GetFocusedAndActiveViews(
+ observer.Pass(),
+ base::Bind(&WindowManagerApiTest::GotFocusedAndActiveViews,
+ base::Unretained(this)));
+ connect_loop.Run();
+
+ // The RunLoop above ensures the connection to the window manager completes.
+ // Without this the ApplicationManager would load the window manager twice.
+ test_helper_->application_manager()->ConnectToService(
+ GURL("mojo:core_window_manager"), &window_manager_);
+ }
+
+ void GotFocusedAndActiveViews(uint32_t, uint32_t, uint32_t) {}
+
+ void OnRootAdded(View* root) {
+ if (!root_added_callback_.is_null())
+ root_added_callback_.Run(root);
+ }
+
+ void OnEmbed(Id* root_id,
+ base::RunLoop* loop,
+ View* root) {
+ *root_id = root->id();
+ loop->Quit();
+ }
+
+ void OnFocusChanged(Id* new_focused,
+ base::RunLoop* run_loop,
+ Id focused_node_id) {
+ *new_focused = focused_node_id;
+ run_loop->Quit();
+ }
+
+ void OnActiveWindowChanged(Id* new_active,
+ base::RunLoop* run_loop,
+ Id active_node_id) {
+ *new_active = active_node_id;
+ run_loop->Quit();
+ }
+
+ scoped_ptr<mojo::shell::ShellTestHelper> test_helper_;
+ scoped_ptr<TestWindowManagerObserver> window_manager_observer_;
+ TestApplicationLoader::RootAddedCallback root_added_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerApiTest);
+};
+
+// TODO(sky): resolve this. Temporarily disabled as ApplicationManager ends up
+// loading windowmanager twice because of the mapping of window_manager to
+// core_window_manager.
+TEST_F(WindowManagerApiTest, DISABLED_FocusAndActivateWindow) {
+ Id first_window = OpenWindow();
+ window_manager_->FocusWindow(first_window, base::Bind(&EmptyResultCallback));
+ Id id = WaitForFocusChange();
+ EXPECT_EQ(id, first_window);
+
+ Id second_window = OpenWindow();
+ window_manager_->ActivateWindow(second_window,
+ base::Bind(&EmptyResultCallback));
+ id = WaitForActiveWindowChange();
+ EXPECT_EQ(id, second_window);
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_app.cc b/mojo/services/window_manager/window_manager_app.cc
new file mode 100644
index 0000000..99d2b35
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app.cc
@@ -0,0 +1,417 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/window_manager_app.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "mojo/converters/input_events/input_events_type_converters.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/window_manager/capture_controller.h"
+#include "mojo/services/window_manager/focus_controller.h"
+#include "mojo/services/window_manager/focus_rules.h"
+#include "mojo/services/window_manager/hit_test.h"
+#include "mojo/services/window_manager/view_event_dispatcher.h"
+#include "mojo/services/window_manager/view_target.h"
+#include "mojo/services/window_manager/view_targeter.h"
+#include "mojo/services/window_manager/window_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h"
+
+using mojo::ApplicationConnection;
+using mojo::Id;
+using mojo::ServiceProvider;
+using mojo::View;
+using mojo::WindowManager;
+
+namespace window_manager {
+
+namespace {
+
+Id GetIdForView(View* view) {
+ return view ? view->id() : 0;
+}
+
+} // namespace
+
+// Used for calls to Embed() that occur before we've connected to the
+// ViewManager.
+struct WindowManagerApp::PendingEmbed {
+ mojo::String url;
+ mojo::InterfaceRequest<ServiceProvider> services;
+ mojo::ServiceProviderPtr exposed_services;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, public:
+
+WindowManagerApp::WindowManagerApp(
+ ViewManagerDelegate* view_manager_delegate,
+ WindowManagerDelegate* window_manager_delegate)
+ : shell_(nullptr),
+ wrapped_view_manager_delegate_(view_manager_delegate),
+ window_manager_delegate_(window_manager_delegate),
+ root_(nullptr) {
+}
+
+WindowManagerApp::~WindowManagerApp() {
+ // TODO(msw|sky): Should this destructor explicitly delete the ViewManager?
+ mojo::ViewManager* cached_view_manager = view_manager();
+ for (RegisteredViewIdSet::const_iterator it = registered_view_id_set_.begin();
+ cached_view_manager && it != registered_view_id_set_.end(); ++it) {
+ View* view = cached_view_manager->GetViewById(*it);
+ if (view && view == root_)
+ root_ = nullptr;
+ if (view)
+ view->RemoveObserver(this);
+ }
+ registered_view_id_set_.clear();
+ DCHECK(!root_);
+
+ STLDeleteElements(&connections_);
+}
+
+void WindowManagerApp::AddConnection(WindowManagerImpl* connection) {
+ DCHECK(connections_.find(connection) == connections_.end());
+ connections_.insert(connection);
+}
+
+void WindowManagerApp::RemoveConnection(WindowManagerImpl* connection) {
+ DCHECK(connections_.find(connection) != connections_.end());
+ connections_.erase(connection);
+}
+
+bool WindowManagerApp::SetCapture(Id view_id) {
+ View* view = view_manager()->GetViewById(view_id);
+ return view && SetCaptureImpl(view);
+}
+
+bool WindowManagerApp::FocusWindow(Id view_id) {
+ View* view = view_manager()->GetViewById(view_id);
+ return view && FocusWindowImpl(view);
+}
+
+bool WindowManagerApp::ActivateWindow(Id view_id) {
+ View* view = view_manager()->GetViewById(view_id);
+ return view && ActivateWindowImpl(view);
+}
+
+bool WindowManagerApp::IsReady() const {
+ return !!root_;
+}
+
+void WindowManagerApp::InitFocus(scoped_ptr<FocusRules> rules) {
+ DCHECK(root_);
+
+ focus_controller_.reset(new FocusController(rules.Pass()));
+ focus_controller_->AddObserver(this);
+ SetFocusController(root_, focus_controller_.get());
+
+ capture_controller_.reset(new CaptureController);
+ capture_controller_->AddObserver(this);
+ SetCaptureController(root_, capture_controller_.get());
+}
+
+void WindowManagerApp::Embed(
+ const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) {
+ if (view_manager()) {
+ window_manager_delegate_->Embed(url, services.Pass(),
+ exposed_services.Pass());
+ return;
+ }
+ scoped_ptr<PendingEmbed> pending_embed(new PendingEmbed);
+ pending_embed->url = url;
+ pending_embed->services = services.Pass();
+ pending_embed->exposed_services = exposed_services.Pass();
+ pending_embeds_.push_back(pending_embed.release());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ApplicationDelegate implementation:
+
+void WindowManagerApp::Initialize(mojo::ApplicationImpl* impl) {
+ shell_ = impl->shell();
+ LaunchViewManager(impl);
+}
+
+bool WindowManagerApp::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService<WindowManager>(this);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ViewManagerDelegate implementation:
+
+void WindowManagerApp::OnEmbed(
+ View* root,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) {
+ DCHECK(!root_);
+ root_ = root;
+
+ view_event_dispatcher_.reset(new ViewEventDispatcher);
+
+ RegisterSubtree(root_);
+
+ if (wrapped_view_manager_delegate_) {
+ wrapped_view_manager_delegate_->OnEmbed(root, services.Pass(),
+ exposed_services.Pass());
+ }
+
+ for (PendingEmbed* pending_embed : pending_embeds_) {
+ Embed(pending_embed->url, pending_embed->services.Pass(),
+ pending_embed->exposed_services.Pass());
+ }
+ pending_embeds_.clear();
+}
+
+void WindowManagerApp::OnViewManagerDisconnected(
+ mojo::ViewManager* view_manager) {
+ if (wrapped_view_manager_delegate_)
+ wrapped_view_manager_delegate_->OnViewManagerDisconnected(view_manager);
+
+ base::MessageLoop* message_loop = base::MessageLoop::current();
+ if (message_loop && message_loop->is_running())
+ message_loop->Quit();
+}
+
+bool WindowManagerApp::OnPerformAction(mojo::View* view,
+ const std::string& action) {
+ if (!view)
+ return false;
+ if (action == "capture")
+ return SetCaptureImpl(view);
+ if (action == "focus")
+ return FocusWindowImpl(view);
+ else if (action == "activate")
+ return ActivateWindowImpl(view);
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ViewObserver implementation:
+
+void WindowManagerApp::OnTreeChanged(
+ const ViewObserver::TreeChangeParams& params) {
+ if (params.receiver != root_)
+ return;
+ DCHECK(params.old_parent || params.new_parent);
+ if (!params.target)
+ return;
+
+ if (params.new_parent) {
+ if (registered_view_id_set_.find(params.target->id()) ==
+ registered_view_id_set_.end()) {
+ RegisteredViewIdSet::const_iterator it =
+ registered_view_id_set_.find(params.new_parent->id());
+ DCHECK(it != registered_view_id_set_.end());
+ RegisterSubtree(params.target);
+ }
+ } else if (params.old_parent) {
+ UnregisterSubtree(params.target);
+ }
+}
+
+void WindowManagerApp::OnViewDestroying(View* view) {
+ Unregister(view);
+ if (view == root_) {
+ root_ = nullptr;
+ if (focus_controller_)
+ focus_controller_->RemoveObserver(this);
+ if (capture_controller_)
+ capture_controller_->RemoveObserver(this);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, ui::EventHandler implementation:
+
+void WindowManagerApp::OnEvent(ui::Event* event) {
+ if (!window_manager_client_)
+ return;
+
+ View* view = static_cast<ViewTarget*>(event->target())->view();
+ if (!view)
+ return;
+
+ if (event->IsKeyEvent()) {
+ const ui::KeyEvent* key_event = static_cast<const ui::KeyEvent*>(event);
+ if (key_event->type() == ui::ET_KEY_PRESSED) {
+ ui::Accelerator accelerator = ConvertEventToAccelerator(key_event);
+ if (accelerator_manager_.Process(accelerator))
+ return;
+ }
+ }
+
+ if (focus_controller_)
+ focus_controller_->OnEvent(event);
+
+ window_manager_client_->DispatchInputEventToView(view->id(),
+ mojo::Event::From(*event));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, mojo::FocusControllerObserver implementation:
+
+void WindowManagerApp::OnFocused(View* gained_focus) {
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ (*it)->NotifyViewFocused(GetIdForView(gained_focus));
+ }
+}
+
+void WindowManagerApp::OnActivated(View* gained_active) {
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ (*it)->NotifyWindowActivated(GetIdForView(gained_active));
+ }
+ if (gained_active)
+ gained_active->MoveToFront();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, mojo::CaptureControllerObserver implementation:
+
+void WindowManagerApp::OnCaptureChanged(View* gained_capture) {
+ for (Connections::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ (*it)->NotifyCaptureChanged(GetIdForView(gained_capture));
+ }
+ if (gained_capture)
+ gained_capture->MoveToFront();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowManagerApp, private:
+
+bool WindowManagerApp::SetCaptureImpl(View* view) {
+ CHECK(view);
+ capture_controller_->SetCapture(view);
+ return capture_controller_->GetCapture() == view;
+}
+
+bool WindowManagerApp::FocusWindowImpl(View* view) {
+ CHECK(view);
+ focus_controller_->FocusView(view);
+ return focus_controller_->GetFocusedView() == view;
+}
+
+bool WindowManagerApp::ActivateWindowImpl(View* view) {
+ CHECK(view);
+ focus_controller_->ActivateView(view);
+ return focus_controller_->GetActiveView() == view;
+}
+
+void WindowManagerApp::RegisterSubtree(View* view) {
+ view->AddObserver(this);
+ DCHECK(registered_view_id_set_.find(view->id()) ==
+ registered_view_id_set_.end());
+ // All events pass through the root during dispatch, so we only need a handler
+ // installed there.
+ if (view == root_) {
+ ViewTarget* target = ViewTarget::TargetFromView(view);
+ target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter()));
+ target->AddPreTargetHandler(this);
+ view_event_dispatcher_->SetRootViewTarget(target);
+ }
+ registered_view_id_set_.insert(view->id());
+ View::Children::const_iterator it = view->children().begin();
+ for (; it != view->children().end(); ++it)
+ RegisterSubtree(*it);
+}
+
+void WindowManagerApp::UnregisterSubtree(View* view) {
+ for (View* child : view->children())
+ UnregisterSubtree(child);
+ Unregister(view);
+}
+
+void WindowManagerApp::Unregister(View* view) {
+ RegisteredViewIdSet::iterator it = registered_view_id_set_.find(view->id());
+ if (it == registered_view_id_set_.end()) {
+ // Because we unregister in OnViewDestroying() we can still get a subsequent
+ // OnTreeChanged for the same view. Ignore this one.
+ return;
+ }
+ view->RemoveObserver(this);
+ DCHECK(it != registered_view_id_set_.end());
+ registered_view_id_set_.erase(it);
+}
+
+void WindowManagerApp::DispatchInputEventToView(View* view,
+ mojo::EventPtr event) {
+ window_manager_client_->DispatchInputEventToView(view->id(), event.Pass());
+}
+
+void WindowManagerApp::SetViewportSize(const gfx::Size& size) {
+ window_manager_client_->SetViewportSize(mojo::Size::From(size));
+}
+
+void WindowManagerApp::LaunchViewManager(mojo::ApplicationImpl* app) {
+ // TODO(sky): figure out logic if this connection goes away.
+ view_manager_client_factory_.reset(
+ new mojo::ViewManagerClientFactory(shell_, this));
+
+ ApplicationConnection* view_manager_app =
+ app->ConnectToApplication("mojo:view_manager");
+ view_manager_app->ConnectToService(&view_manager_service_);
+
+ view_manager_app->AddService<WindowManagerInternal>(this);
+ view_manager_app->AddService<mojo::NativeViewportEventDispatcher>(this);
+
+ view_manager_app->ConnectToService(&window_manager_client_);
+}
+
+void WindowManagerApp::Create(
+ ApplicationConnection* connection,
+ mojo::InterfaceRequest<WindowManagerInternal> request) {
+ if (wm_internal_binding_.get()) {
+ VLOG(1) <<
+ "WindowManager allows only one WindowManagerInternal connection.";
+ return;
+ }
+ wm_internal_binding_.reset(
+ new mojo::Binding<WindowManagerInternal>(this, request.Pass()));
+}
+
+void WindowManagerApp::Create(ApplicationConnection* connection,
+ mojo::InterfaceRequest<WindowManager> request) {
+ WindowManagerImpl* wm = new WindowManagerImpl(this, false);
+ wm->Bind(request.PassMessagePipe());
+ // WindowManagerImpl is deleted when the connection has an error, or from our
+ // destructor.
+}
+
+void WindowManagerApp::Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request) {
+ new NativeViewportEventDispatcherImpl(this, request.Pass());
+}
+
+void WindowManagerApp::CreateWindowManagerForViewManagerClient(
+ uint16_t connection_id,
+ mojo::ScopedMessagePipeHandle window_manager_pipe) {
+ // TODO(sky): pass in |connection_id| for validation.
+ WindowManagerImpl* wm = new WindowManagerImpl(this, true);
+ wm->Bind(window_manager_pipe.Pass());
+ // WindowManagerImpl is deleted when the connection has an error, or from our
+ // destructor.
+}
+
+void WindowManagerApp::SetViewManagerClient(
+ mojo::ScopedMessagePipeHandle view_manager_client_request) {
+ view_manager_client_.reset(
+ mojo::ViewManagerClientFactory::WeakBindViewManagerToPipe(
+ mojo::MakeRequest<mojo::ViewManagerClient>(
+ view_manager_client_request.Pass()),
+ view_manager_service_.Pass(), shell_, this));
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_app.h b/mojo/services/window_manager/window_manager_app.h
new file mode 100644
index 0000000..f91be46
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app.h
@@ -0,0 +1,213 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_
+#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_
+
+#include <set>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/services/window_manager/capture_controller_observer.h"
+#include "mojo/services/window_manager/focus_controller_observer.h"
+#include "mojo/services/window_manager/native_viewport_event_dispatcher_impl.h"
+#include "mojo/services/window_manager/view_target.h"
+#include "mojo/services/window_manager/window_manager_impl.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h"
+#include "ui/base/accelerators/accelerator_manager.h"
+#include "ui/events/event_handler.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace window_manager {
+
+class CaptureController;
+class FocusController;
+class FocusRules;
+class ViewEventDispatcher;
+class WindowManagerDelegate;
+class WindowManagerImpl;
+
+// Implements core window manager functionality that could conceivably be shared
+// across multiple window managers implementing superficially different user
+// experiences. Establishes communication with the view manager.
+// A window manager wishing to use this core should create and own an instance
+// of this object. They may implement the associated ViewManager/WindowManager
+// delegate interfaces exposed by the view manager, this object provides the
+// canonical implementation of said interfaces but will call out to the wrapped
+// instances.
+class WindowManagerApp
+ : public mojo::ApplicationDelegate,
+ public mojo::ViewManagerDelegate,
+ public mojo::ViewObserver,
+ public ui::EventHandler,
+ public FocusControllerObserver,
+ public CaptureControllerObserver,
+ public mojo::InterfaceFactory<mojo::WindowManager>,
+ public mojo::InterfaceFactory<mojo::WindowManagerInternal>,
+ public mojo::InterfaceFactory<mojo::NativeViewportEventDispatcher>,
+ public mojo::WindowManagerInternal {
+ public:
+ WindowManagerApp(ViewManagerDelegate* view_manager_delegate,
+ WindowManagerDelegate* window_manager_delegate);
+ ~WindowManagerApp() override;
+
+ ViewEventDispatcher* event_dispatcher() {
+ return view_event_dispatcher_.get();
+ }
+
+ // Register/deregister new connections to the window manager service.
+ void AddConnection(WindowManagerImpl* connection);
+ void RemoveConnection(WindowManagerImpl* connection);
+
+ // These are canonical implementations of the window manager API methods.
+ bool SetCapture(mojo::Id view);
+ bool FocusWindow(mojo::Id view);
+ bool ActivateWindow(mojo::Id view);
+
+ void DispatchInputEventToView(mojo::View* view, mojo::EventPtr event);
+ void SetViewportSize(const gfx::Size& size);
+
+ bool IsReady() const;
+
+ FocusController* focus_controller() { return focus_controller_.get(); }
+ CaptureController* capture_controller() { return capture_controller_.get(); }
+
+ void InitFocus(scoped_ptr<FocusRules> rules);
+
+ ui::AcceleratorManager* accelerator_manager() {
+ return &accelerator_manager_;
+ }
+
+ // WindowManagerImpl::Embed() forwards to this. If connected to ViewManager
+ // then forwards to delegate, otherwise waits for connection to establish then
+ // forwards.
+ void Embed(const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services);
+
+ // Overridden from ApplicationDelegate:
+ void Initialize(mojo::ApplicationImpl* impl) override;
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override;
+
+ private:
+ // TODO(sky): rename this. Connections is ambiguous.
+ typedef std::set<WindowManagerImpl*> Connections;
+ typedef std::set<mojo::Id> RegisteredViewIdSet;
+
+ struct PendingEmbed;
+ class WindowManagerInternalImpl;
+
+ mojo::ViewManager* view_manager() {
+ return root_ ? root_->view_manager() : nullptr;
+ }
+
+ bool SetCaptureImpl(mojo::View* view);
+ bool FocusWindowImpl(mojo::View* view);
+ bool ActivateWindowImpl(mojo::View* view);
+
+ ui::Accelerator ConvertEventToAccelerator(const ui::KeyEvent* event);
+
+ // Creates an ViewTarget for every view in the hierarchy beneath |view|,
+ // and adds to the registry so that it can be retrieved later via
+ // GetViewTargetForViewId().
+ // TODO(beng): perhaps View should have a property bag.
+ void RegisterSubtree(mojo::View* view);
+
+ // Recursively invokes Unregister() for |view| and all its descendants.
+ void UnregisterSubtree(mojo::View* view);
+
+ // Deletes the ViewTarget associated with the hierarchy beneath |id|,
+ // and removes from the registry.
+ void Unregister(mojo::View* view);
+
+ // Overridden from ViewManagerDelegate:
+ void OnEmbed(mojo::View* root,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override;
+ void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override;
+ bool OnPerformAction(mojo::View* view, const std::string& action) override;
+
+ // Overridden from ViewObserver:
+ void OnTreeChanged(const ViewObserver::TreeChangeParams& params) override;
+ void OnViewDestroying(mojo::View* view) override;
+
+ // Overridden from ui::EventHandler:
+ void OnEvent(ui::Event* event) override;
+
+ // Overridden from mojo::FocusControllerObserver:
+ void OnFocused(mojo::View* gained_focus) override;
+ void OnActivated(mojo::View* gained_active) override;
+
+ // Overridden from mojo::CaptureControllerObserver:
+ void OnCaptureChanged(mojo::View* gained_capture) override;
+
+ // Creates the connection to the ViewManager.
+ void LaunchViewManager(mojo::ApplicationImpl* app);
+
+ // InterfaceFactory<WindowManagerInternal>:
+ void Create(
+ mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::WindowManagerInternal> request) override;
+
+ // InterfaceFactory<WindowManager>:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::WindowManager> request) override;
+
+ // InterfaceFactory<NativeViewportEventDispatcher>:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher>
+ request) override;
+
+ // WindowManagerInternal:
+ void CreateWindowManagerForViewManagerClient(
+ uint16_t connection_id,
+ mojo::ScopedMessagePipeHandle window_manager_pipe) override;
+ void SetViewManagerClient(
+ mojo::ScopedMessagePipeHandle view_manager_client_request) override;
+
+ mojo::Shell* shell_;
+
+ ViewManagerDelegate* wrapped_view_manager_delegate_;
+ WindowManagerDelegate* window_manager_delegate_;
+
+ mojo::ViewManagerServicePtr view_manager_service_;
+ scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_;
+ mojo::View* root_;
+
+ scoped_ptr<FocusController> focus_controller_;
+ scoped_ptr<CaptureController> capture_controller_;
+
+ ui::AcceleratorManager accelerator_manager_;
+
+ Connections connections_;
+ RegisteredViewIdSet registered_view_id_set_;
+
+ mojo::WindowManagerInternalClientPtr window_manager_client_;
+
+ ScopedVector<PendingEmbed> pending_embeds_;
+
+ scoped_ptr<mojo::ViewManagerClient> view_manager_client_;
+
+ scoped_ptr<ViewEventDispatcher> view_event_dispatcher_;
+
+ scoped_ptr<mojo::Binding<WindowManagerInternal>> wm_internal_binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerApp);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_
diff --git a/mojo/services/window_manager/window_manager_app_android.cc b/mojo/services/window_manager/window_manager_app_android.cc
new file mode 100644
index 0000000..84feff1
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app_android.cc
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/window_manager_app.h"
+
+#include <android/keycodes.h>
+
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace window_manager {
+
+ui::Accelerator WindowManagerApp::ConvertEventToAccelerator(
+ const ui::KeyEvent* event) {
+ if (event->platform_keycode() == AKEYCODE_BACK)
+ return ui::Accelerator(ui::VKEY_BROWSER_BACK, 0);
+ return ui::Accelerator(event->key_code(), event->flags());
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_app_linux.cc b/mojo/services/window_manager/window_manager_app_linux.cc
new file mode 100644
index 0000000..11d43dc
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app_linux.cc
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/window_manager_app.h"
+
+namespace window_manager {
+
+ui::Accelerator WindowManagerApp::ConvertEventToAccelerator(
+ const ui::KeyEvent* event) {
+ return ui::Accelerator(event->key_code(), event->flags());
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_app_win.cc b/mojo/services/window_manager/window_manager_app_win.cc
new file mode 100644
index 0000000..11d43dc
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_app_win.cc
@@ -0,0 +1,14 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/window_manager_app.h"
+
+namespace window_manager {
+
+ui::Accelerator WindowManagerApp::ConvertEventToAccelerator(
+ const ui::KeyEvent* event) {
+ return ui::Accelerator(event->key_code(), event->flags());
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_apptest.cc b/mojo/services/window_manager/window_manager_apptest.cc
new file mode 100644
index 0000000..12a9aca
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_apptest.cc
@@ -0,0 +1,212 @@
+// Copyright 2014 The Chromium Authors. 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/run_loop.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h"
+
+namespace mojo {
+namespace {
+
+// TestApplication's view is embedded by the window manager.
+class TestApplication : public ApplicationDelegate, public ViewManagerDelegate {
+ public:
+ TestApplication() : root_(nullptr) {}
+ ~TestApplication() override {}
+
+ View* root() const { return root_; }
+
+ void set_embed_callback(const base::Closure& callback) {
+ embed_callback_ = callback;
+ }
+
+ private:
+ // ApplicationDelegate:
+ void Initialize(ApplicationImpl* app) override {
+ view_manager_client_factory_.reset(
+ new ViewManagerClientFactory(app->shell(), this));
+ }
+
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ connection->AddService(view_manager_client_factory_.get());
+ return true;
+ }
+
+ // ViewManagerDelegate:
+ void OnEmbed(View* root,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) override {
+ root_ = root;
+ embed_callback_.Run();
+ }
+ void OnViewManagerDisconnected(ViewManager* view_manager) override {}
+
+ View* root_;
+ base::Closure embed_callback_;
+ scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestApplication);
+};
+
+class TestWindowManagerObserver : public WindowManagerObserver {
+ public:
+ explicit TestWindowManagerObserver(
+ InterfaceRequest<WindowManagerObserver> observer_request)
+ : binding_(this, observer_request.Pass()) {}
+ ~TestWindowManagerObserver() override {}
+
+ private:
+ // Overridden from WindowManagerClient:
+ void OnCaptureChanged(Id new_capture_node_id) override {}
+ void OnFocusChanged(Id focused_node_id) override {}
+ void OnActiveWindowChanged(Id active_window) override {}
+
+ Binding<WindowManagerObserver> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindowManagerObserver);
+};
+
+class WindowManagerApplicationTest : public test::ApplicationTestBase {
+ public:
+ WindowManagerApplicationTest() {}
+ ~WindowManagerApplicationTest() override {}
+
+ protected:
+ // ApplicationTestBase:
+ void SetUp() override {
+ ApplicationTestBase::SetUp();
+ application_impl()->ConnectToService("mojo:window_manager",
+ &window_manager_);
+ }
+ ApplicationDelegate* GetApplicationDelegate() override {
+ return &test_application_;
+ }
+
+ void EmbedApplicationWithURL(const std::string& url) {
+ window_manager_->Embed(url, nullptr, nullptr);
+
+ base::RunLoop run_loop;
+ test_application_.set_embed_callback(run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ WindowManagerPtr window_manager_;
+ TestApplication test_application_;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(WindowManagerApplicationTest);
+};
+
+TEST_F(WindowManagerApplicationTest, Embed) {
+ EXPECT_EQ(nullptr, test_application_.root());
+ EmbedApplicationWithURL(application_impl()->url());
+ EXPECT_NE(nullptr, test_application_.root());
+}
+
+struct BoolCallback {
+ BoolCallback(bool* bool_value, base::RunLoop* run_loop)
+ : bool_value(bool_value), run_loop(run_loop) {}
+
+ void Run(bool value) const {
+ *bool_value = value;
+ run_loop->Quit();
+ }
+
+ bool* bool_value;
+ base::RunLoop* run_loop;
+};
+
+TEST_F(WindowManagerApplicationTest, SetCaptureFailsFromNonVM) {
+ EmbedApplicationWithURL(application_impl()->url());
+ bool callback_value = true;
+ base::RunLoop run_loop;
+ window_manager_->SetCapture(test_application_.root()->id(),
+ BoolCallback(&callback_value, &run_loop));
+ run_loop.Run();
+ // This call only succeeds for WindowManager connections from the ViewManager.
+ EXPECT_FALSE(callback_value);
+}
+
+TEST_F(WindowManagerApplicationTest, FocusWindowFailsFromNonVM) {
+ EmbedApplicationWithURL(application_impl()->url());
+ bool callback_value = true;
+ base::RunLoop run_loop;
+ window_manager_->FocusWindow(test_application_.root()->id(),
+ BoolCallback(&callback_value, &run_loop));
+ run_loop.Run();
+ // This call only succeeds for WindowManager connections from the ViewManager.
+ EXPECT_FALSE(callback_value);
+}
+
+TEST_F(WindowManagerApplicationTest, ActivateWindowFailsFromNonVM) {
+ EmbedApplicationWithURL(application_impl()->url());
+ bool callback_value = true;
+ base::RunLoop run_loop;
+ window_manager_->ActivateWindow(test_application_.root()->id(),
+ BoolCallback(&callback_value, &run_loop));
+ run_loop.Run();
+ // This call only succeeds for WindowManager connections from the ViewManager.
+ EXPECT_FALSE(callback_value);
+}
+
+struct FocusedAndActiveViewsCallback {
+ FocusedAndActiveViewsCallback(uint32* capture_view_id,
+ uint32* focused_view_id,
+ uint32* active_view_id,
+ base::RunLoop* run_loop)
+ : capture_view_id(capture_view_id),
+ focused_view_id(focused_view_id),
+ active_view_id(active_view_id),
+ run_loop(run_loop) {
+ }
+
+ void Run(uint32 capture, uint32 focused, uint32 active) const {
+ *capture_view_id = capture;
+ *focused_view_id = focused;
+ *active_view_id = active;
+ run_loop->Quit();
+ }
+
+ uint32* capture_view_id;
+ uint32* focused_view_id;
+ uint32* active_view_id;
+ base::RunLoop* run_loop;
+};
+
+TEST_F(WindowManagerApplicationTest, GetFocusedAndActiveViewsFailsWithoutFC) {
+ EmbedApplicationWithURL(application_impl()->url());
+ uint32 capture_view_id = -1;
+ uint32 focused_view_id = -1;
+ uint32 active_view_id = -1;
+ base::RunLoop run_loop;
+
+ WindowManagerObserverPtr observer;
+ scoped_ptr<TestWindowManagerObserver> window_manager_observer(
+ new TestWindowManagerObserver(GetProxy(&observer)));
+
+ window_manager_->GetFocusedAndActiveViews(
+ observer.Pass(),
+ FocusedAndActiveViewsCallback(&capture_view_id,
+ &focused_view_id,
+ &active_view_id,
+ &run_loop));
+ run_loop.Run();
+ // This call fails if the WindowManager does not have a FocusController.
+ EXPECT_EQ(0u, capture_view_id);
+ EXPECT_EQ(0u, focused_view_id);
+ EXPECT_EQ(0u, active_view_id);
+}
+
+// TODO(msw): Write tests exercising other WindowManager functionality.
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/services/window_manager/window_manager_delegate.h b/mojo/services/window_manager/window_manager_delegate.h
new file mode 100644
index 0000000..277e3a8
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_delegate.h
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
+#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
+
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+
+namespace window_manager {
+
+class WindowManagerDelegate {
+ public:
+ // See WindowManager::Embed() for details.
+ virtual void Embed(const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) = 0;
+
+ protected:
+ virtual ~WindowManagerDelegate() {}
+};
+
+} // namespace mojo
+
+#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
diff --git a/mojo/services/window_manager/window_manager_impl.cc b/mojo/services/window_manager/window_manager_impl.cc
new file mode 100644
index 0000000..99b3e2f
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_impl.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/window_manager_impl.h"
+
+#include "mojo/services/window_manager/capture_controller.h"
+#include "mojo/services/window_manager/focus_controller.h"
+#include "mojo/services/window_manager/window_manager_app.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+
+using mojo::Callback;
+using mojo::Id;
+
+namespace window_manager {
+
+WindowManagerImpl::WindowManagerImpl(WindowManagerApp* window_manager,
+ bool from_vm)
+ : window_manager_(window_manager), from_vm_(from_vm), binding_(this) {
+ window_manager_->AddConnection(this);
+ binding_.set_error_handler(this);
+}
+
+WindowManagerImpl::~WindowManagerImpl() {
+ window_manager_->RemoveConnection(this);
+}
+
+void WindowManagerImpl::Bind(
+ mojo::ScopedMessagePipeHandle window_manager_pipe) {
+ binding_.Bind(window_manager_pipe.Pass());
+}
+
+void WindowManagerImpl::NotifyViewFocused(Id focused_id) {
+ if (from_vm_ && observer_)
+ observer_->OnFocusChanged(focused_id);
+}
+
+void WindowManagerImpl::NotifyWindowActivated(Id active_id) {
+ if (from_vm_ && observer_)
+ observer_->OnActiveWindowChanged(active_id);
+}
+
+void WindowManagerImpl::NotifyCaptureChanged(Id capture_id) {
+ if (from_vm_ && observer_)
+ observer_->OnCaptureChanged(capture_id);
+}
+
+void WindowManagerImpl::Embed(
+ const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) {
+ window_manager_->Embed(url, services.Pass(), exposed_services.Pass());
+}
+
+void WindowManagerImpl::SetCapture(Id view,
+ const Callback<void(bool)>& callback) {
+ callback.Run(from_vm_ && window_manager_->IsReady() &&
+ window_manager_->SetCapture(view));
+}
+
+void WindowManagerImpl::FocusWindow(Id view,
+ const Callback<void(bool)>& callback) {
+ callback.Run(from_vm_ && window_manager_->IsReady() &&
+ window_manager_->FocusWindow(view));
+}
+
+void WindowManagerImpl::ActivateWindow(Id view,
+ const Callback<void(bool)>& callback) {
+ callback.Run(from_vm_ && window_manager_->IsReady() &&
+ window_manager_->ActivateWindow(view));
+}
+
+void WindowManagerImpl::GetFocusedAndActiveViews(
+ mojo::WindowManagerObserverPtr observer,
+ const mojo::WindowManager::GetFocusedAndActiveViewsCallback& callback) {
+ observer_ = observer.Pass();
+ if (!window_manager_->focus_controller()) {
+ // TODO(sky): add typedef for 0.
+ callback.Run(0, 0, 0);
+ return;
+ }
+ mojo::View* capture_view =
+ window_manager_->capture_controller()->GetCapture();
+ mojo::View* active_view =
+ window_manager_->focus_controller()->GetActiveView();
+ mojo::View* focused_view =
+ window_manager_->focus_controller()->GetFocusedView();
+ // TODO(sky): sanitize ids for client.
+ callback.Run(capture_view ? capture_view->id() : 0,
+ focused_view ? focused_view->id() : 0,
+ active_view ? active_view->id() : 0);
+}
+
+void WindowManagerImpl::OnConnectionError() {
+ delete this;
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_impl.h b/mojo/services/window_manager/window_manager_impl.h
new file mode 100644
index 0000000..f51941f
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_impl.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_
+#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/types.h"
+#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h"
+
+namespace window_manager {
+
+class WindowManagerApp;
+
+class WindowManagerImpl : public mojo::WindowManager,
+ public mojo::ErrorHandler {
+ public:
+ // See description above |from_vm_| for details on |from_vm|.
+ // WindowManagerImpl deletes itself on connection errors. WindowManagerApp
+ // also deletes WindowManagerImpl in its destructor.
+ WindowManagerImpl(WindowManagerApp* window_manager, bool from_vm);
+ ~WindowManagerImpl() override;
+
+ void Bind(mojo::ScopedMessagePipeHandle window_manager_pipe);
+
+ void NotifyViewFocused(mojo::Id focused_id);
+ void NotifyWindowActivated(mojo::Id active_id);
+ void NotifyCaptureChanged(mojo::Id capture_id);
+
+ private:
+ // mojo::WindowManager:
+ void Embed(const mojo::String& url,
+ mojo::InterfaceRequest<mojo::ServiceProvider> services,
+ mojo::ServiceProviderPtr exposed_services) override;
+ void SetCapture(uint32_t view_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void FocusWindow(uint32_t view_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void ActivateWindow(uint32_t view_id,
+ const mojo::Callback<void(bool)>& callback) override;
+ void GetFocusedAndActiveViews(
+ mojo::WindowManagerObserverPtr observer,
+ const mojo::WindowManager::GetFocusedAndActiveViewsCallback& callback)
+ override;
+
+ // mojo::ErrorHandler:
+ void OnConnectionError() override;
+
+ WindowManagerApp* window_manager_;
+
+ // Whether this connection originated from the ViewManager. Connections that
+ // originate from the view manager are expected to have clients. Connections
+ // that don't originate from the view manager do not have clients.
+ const bool from_vm_;
+
+ mojo::Binding<mojo::WindowManager> binding_;
+ mojo::WindowManagerObserverPtr observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowManagerImpl);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_
diff --git a/mojo/services/window_manager/window_manager_test_util.cc b/mojo/services/window_manager/window_manager_test_util.cc
new file mode 100644
index 0000000..7c3eb21
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_test_util.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/services/window_manager/window_manager_test_util.h"
+
+#include "base/stl_util.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace window_manager {
+
+TestView::TestView(int id, const gfx::Rect& rect)
+ : target_(new ViewTarget(this)) {
+ mojo::ViewPrivate(this).set_id(id);
+
+ mojo::Rect mojo_rect = *mojo::Rect::From(rect);
+ SetBounds(mojo_rect);
+}
+
+TestView::TestView(int id, const gfx::Rect& rect, View* parent)
+ : TestView(id, rect) {
+ parent->AddChild(this);
+}
+
+TestView::~TestView() {
+}
+
+// static
+TestView* TestView::Build(int id, const gfx::Rect& rect) {
+ return new TestView(id, rect);
+}
+
+// static
+TestView* TestView::Build(int id, const gfx::Rect& rect, mojo::View* parent) {
+ return new TestView(id, rect, parent);
+}
+
+} // namespace window_manager
diff --git a/mojo/services/window_manager/window_manager_test_util.h b/mojo/services/window_manager/window_manager_test_util.h
new file mode 100644
index 0000000..62424c7
--- /dev/null
+++ b/mojo/services/window_manager/window_manager_test_util.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 SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_
+#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_
+
+#include <set>
+
+#include "mojo/services/window_manager/view_target.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_private.h"
+#include "third_party/mojo_services/src/view_manager/public/cpp/view.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace window_manager {
+
+// A wrapper around View so we can instantiate these directly without a
+// ViewManager.
+class TestView : public mojo::View {
+ public:
+ TestView(int id, const gfx::Rect& rect);
+ TestView(int id, const gfx::Rect& rect, mojo::View* parent);
+ ~TestView();
+
+ // Builds a child view as a pointer. The caller is responsible for making
+ // sure that the root of any tree allocated this way is Destroy()ed.
+ static TestView* Build(int id, const gfx::Rect& rect);
+ static TestView* Build(int id, const gfx::Rect& rect, View* parent);
+
+ ViewTarget* target() { return target_; }
+
+ private:
+ ViewTarget* target_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestView);
+};
+
+} // namespace window_manager
+
+#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_
diff --git a/mojo/shell/BUILD.gn b/mojo/shell/BUILD.gn
new file mode 100644
index 0000000..c121d57
--- /dev/null
+++ b/mojo/shell/BUILD.gn
@@ -0,0 +1,473 @@
+# Copyright 2014 The Chromium 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("//third_party/mojo/src/mojo/public/mojo.gni")
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+import("//testing/test.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" ]
+ }
+
+ if (is_android) {
+ deps += [
+ ":mojo_shell_apk",
+ ":mojo_shell_tests_apk",
+ ]
+ }
+}
+
+group("tests") {
+ testonly = true
+ deps = [
+ ":mojo_shell_tests",
+ "//mojo/shell/application_manager:mojo_application_manager_unittests",
+ ]
+}
+
+if (is_android) {
+ import("//build/config/android/config.gni")
+ import("//build/config/android/rules.gni")
+}
+
+executable("mojo_shell") {
+ sources = []
+
+ deps = [
+ ":init",
+ ":lib",
+ "//base",
+ "//build/config/sanitizers:deps",
+ "//mojo/common",
+ "//mojo/environment:chromium",
+ ]
+
+ if (!is_android) {
+ sources += [ "desktop/main.cc" ]
+ } else {
+ sources += [
+ "android/library_loader.cc",
+ "android/main.cc",
+ "android/main.h",
+ ]
+
+ # On android, the executable is also the native library used by the apk.
+ # It means dynamic symbols must be preserved and exported.
+ ldflags = [ "-Wl,--export-dynamic" ]
+
+ deps += [
+ ":jni_headers",
+ "//mojo/services/native_viewport:lib",
+ "//mojo/shell/application_manager",
+ "//ui/gl",
+ ]
+ }
+}
+
+executable("mojo_launcher") {
+ sources = [
+ "launcher_main.cc",
+ ]
+
+ deps = [
+ ":init",
+ ":in_process_native_runner",
+ "//base",
+ "//build/config/sanitizers:deps",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//url",
+ ]
+}
+
+source_set("init") {
+ sources = [
+ "init.cc",
+ "init.h",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+source_set("in_process_native_runner") {
+ sources = [
+ "in_process_native_runner.cc",
+ "in_process_native_runner.h",
+ ]
+
+ public_deps = [
+ ":native_application_support",
+ "//mojo/shell/application_manager",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+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",
+ "command_line_util.cc",
+ "command_line_util.h",
+ "context.cc",
+ "context.h",
+ "filename_util.cc",
+ "filename_util.h",
+ "out_of_process_native_runner.cc",
+ "out_of_process_native_runner.h",
+ "task_runners.cc",
+ "task_runners.h",
+ "url_resolver.cc",
+ "url_resolver.h",
+ ]
+
+ deps = [
+ ":app_child_process_bindings",
+ ":init",
+ ":in_process_native_runner",
+ ":native_application_support",
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//base:base_static",
+ "//mojo/application",
+ "//mojo/common",
+ "//mojo/common:tracing_impl",
+ "//third_party/mojo/src/mojo/edk/system",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ "//mojo/services/network/public/interfaces",
+ "//mojo/shell/application_manager",
+ "//mojo/services/tracing:bindings",
+ "//url",
+ ]
+
+ public_deps = [
+ ":switches",
+ ]
+
+ if (is_android) {
+ sources += [
+ "android/android_handler.cc",
+ "android/android_handler.h",
+ "android/android_handler_loader.cc",
+ "android/android_handler_loader.h",
+ "android/background_application_loader.cc",
+ "android/background_application_loader.h",
+ "android/keyboard_impl.cc",
+ "android/keyboard_impl.h",
+ "android/native_viewport_application_loader.cc",
+ "android/native_viewport_application_loader.h",
+ "android/ui_application_loader_android.cc",
+ "android/ui_application_loader_android.h",
+ ]
+
+ deps += [
+ ":jni_headers",
+ ":run_android_application_function",
+ "//mojo/application:content_handler",
+ "//mojo/services/keyboard/public/interfaces",
+ "//mojo/services/gles2",
+ "//mojo/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
+}
+
+source_set("native_application_support") {
+ sources = [
+ "native_application_support.cc",
+ "native_application_support.h",
+ ]
+
+ public_deps = [
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/gles2",
+ ]
+
+ # 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("switches") {
+ sources = [
+ "switches.cc",
+ "switches.h",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+if (is_android) {
+ generate_jni("jni_headers") {
+ sources = [
+ "android/apk/src/org/chromium/mojo/shell/AndroidHandler.java",
+ "android/apk/src/org/chromium/mojo/shell/Bootstrap.java",
+ "android/apk/src/org/chromium/mojo/shell/Keyboard.java",
+ "android/apk/src/org/chromium/mojo/shell/ShellMain.java",
+ "android/tests/src/org/chromium/mojo/shell/ShellTestBase.java",
+ ]
+ jni_package = "mojo/shell"
+ }
+
+ android_library("bootstrap_java") {
+ java_files = [ "android/apk/src/org/chromium/mojo/shell/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",
+ ]
+
+ deps = [
+ "//base",
+ ]
+ }
+
+ android_library("java") {
+ java_files = [
+ "android/apk/src/org/chromium/mojo/shell/AndroidHandler.java",
+ "android/apk/src/org/chromium/mojo/shell/FileHelper.java",
+ "android/apk/src/org/chromium/mojo/shell/Keyboard.java",
+ "android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java",
+ "android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java",
+ "android/apk/src/org/chromium/mojo/shell/ShellMain.java",
+ ]
+
+ deps = [
+ "//base:base_java",
+ ]
+ }
+
+ android_resources("resources") {
+ resource_dirs = [ "android/apk/res" ]
+ custom_package = "org.chromium.mojo.shell"
+ }
+
+ mojo_shell_assets_dir = "$root_build_dir/mojo_shell_assets"
+ mojo_shell_test_assets_dir = "$root_build_dir/mojo_shell_test_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/network_service.mojo",
+ "$root_out_dir/obj/mojo/shell/bootstrap_java.dex.jar",
+ ]
+ }
+
+ copy("copy_mojo_shell") {
+ sources = [
+ "$root_out_dir/exe.stripped/mojo_shell",
+ ]
+ outputs = [
+ "$root_out_dir/lib.stripped/libmojo_shell.so",
+ ]
+ }
+
+ copy_ex("copy_mojo_shell_test_assets") {
+ clear_dir = true
+ dest = mojo_shell_test_assets_dir
+ sources = [
+ "$root_out_dir/test_app.mojo",
+ "$root_out_dir/test_request_tracker_app.mojo",
+ ]
+ }
+
+ 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",
+ ":copy_mojo_shell_assets",
+ ":java",
+ ":resources",
+ "//base:base_java",
+ "//mojo/services/native_viewport:native_viewport_java",
+ "//third_party/android_tools:google_play_services_default_resources",
+ ]
+ }
+
+ android_library("mojo_shell_tests_java") {
+ java_files =
+ [ "android/tests/src/org/chromium/mojo/shell/ShellTestBase.java" ]
+
+ deps = [
+ ":java",
+ "//base:base_java",
+ ]
+ }
+}
+
+mojom("app_child_process_bindings") {
+ sources = [
+ "app_child_process.mojom",
+ ]
+
+ deps = [
+ "//third_party/mojo/src/mojo/public/interfaces/application",
+ ]
+}
+
+# GYP version: mojo/mojo.gyp:mojo_shell_tests
+test("mojo_shell_tests") {
+ sources = [
+ "app_child_process_host_unittest.cc",
+ "command_line_util_unittest.cc",
+ "data_pipe_peek_unittest.cc",
+ "in_process_native_runner_unittest.cc",
+ "native_runner_unittest.cc",
+ "shell_test_base.cc",
+ "shell_test_base.h",
+ "shell_test_base_android.cc",
+ "shell_test_base_unittest.cc",
+ "shell_test_main.cc",
+ "url_resolver_unittest.cc",
+ ]
+
+ deps = [
+ ":in_process_native_runner",
+ ":lib",
+ "//base",
+ "//base:i18n",
+ "//base/test:test_support",
+ "//testing/gtest",
+ "//url",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//mojo/services/test_service:bindings",
+ "//mojo/shell/application_manager",
+ ]
+
+ datadeps = [
+ "//mojo/services/test_service:test_app",
+ "//mojo/services/test_service:test_request_tracker_app",
+ ]
+
+ if (is_android) {
+ sources += [ "android/background_application_loader_unittest.cc" ]
+
+ deps += [ ":jni_headers" ]
+
+ apk_deps = [
+ ":copy_mojo_shell_test_assets",
+ ":mojo_shell_tests_java",
+ ]
+
+ apk_asset_location = mojo_shell_test_assets_dir
+ }
+}
+
+# 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",
+ "//third_party/mojo/src/mojo/edk/system",
+ "//mojo/shell/application_manager",
+ ]
+}
+
+mojo_native_application("apptests") {
+ output_name = "shell_apptests"
+
+ testonly = true
+
+ sources = [
+ # TODO(jam): needs http_server service.
+ #"shell_apptest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/application",
+ "//mojo/application:test_support",
+ "//mojo/common:common",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:callback",
+ "//third_party/mojo/src/mojo/public/cpp/environment",
+ "//third_party/mojo/src/mojo/public/cpp/system:system",
+ # "//mojo/services/http_server/public/cpp",
+ # "//mojo/services/http_server/public/interfaces",
+ "//mojo/services/network/public/interfaces",
+ "//mojo/shell/test:bindings",
+ ]
+
+ #data_deps = [ "//services/http_server:http_server($default_toolchain)" ]
+}
diff --git a/mojo/shell/DEPS b/mojo/shell/DEPS
new file mode 100644
index 0000000..04fbd79
--- /dev/null
+++ b/mojo/shell/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+crypto",
+ "+jni",
+ "+third_party/mojo_services",
+ "+ui",
+]
diff --git a/mojo/shell/PRESUBMIT.py b/mojo/shell/PRESUBMIT.py
new file mode 100644
index 0000000..fb19030
--- /dev/null
+++ b/mojo/shell/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Presubmit script for shell.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += input_api.canned_checks.CheckChangeHasOnlyOneEol(input_api,
+ output_api)
+ results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
+ return results
diff --git a/mojo/shell/android/android_handler.cc b/mojo/shell/android/android_handler.cc
new file mode 100644
index 0000000..383dc72
--- /dev/null
+++ b/mojo/shell/android/android_handler.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/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 "mojo/shell/android/run_android_application_function.h"
+#include "mojo/shell/native_application_support.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetApplicationContext;
+
+namespace mojo {
+namespace shell {
+
+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) {
+ InterfaceRequest<Application> application_request =
+ MakeRequest<Application>(MakeScopedHandle(MessagePipeHandle(j_handle)));
+
+ // Load the library, so that we can set the application context there if
+ // needed.
+ // TODO(vtl): We'd use a ScopedNativeLibrary, but it doesn't have .get()!
+ base::NativeLibrary app_library =
+ LoadNativeApplication(app_path, NativeApplicationCleanup::DELETE);
+ if (!app_library)
+ 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>(
+ base::GetFunctionPointerFromNativeLibrary(
+ app_library, 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.
+ RunNativeApplication(app_library, application_request.Pass());
+ // TODO(vtl): See note about unloading and thread-local destructors above
+ // declaration of |LoadNativeApplication()|.
+ base::UnloadNativeLibrary(app_library);
+}
+
+} // namespace
+
+AndroidHandler::AndroidHandler() : content_handler_factory_(this) {
+}
+
+AndroidHandler::~AndroidHandler() {
+}
+
+void AndroidHandler::RunApplication(
+ InterfaceRequest<Application> application_request,
+ 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()));
+
+ common::BlockingCopyToFile(response->body.Pass(), archive_path);
+ RunAndroidApplicationFn run_android_application_fn = &RunAndroidApplication;
+ Java_AndroidHandler_bootstrap(
+ env, GetApplicationContext(), j_archive_path.obj(),
+ application_request.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 shell
+} // namespace mojo
diff --git a/mojo/shell/android/android_handler.h b/mojo/shell/android/android_handler.h
new file mode 100644
index 0000000..868c263
--- /dev/null
+++ b/mojo/shell/android/android_handler.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_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 "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace mojo {
+namespace shell {
+
+class AndroidHandler : public ApplicationDelegate,
+ public ContentHandlerFactory::Delegate {
+ public:
+ AndroidHandler();
+ ~AndroidHandler();
+
+ private:
+ // ApplicationDelegate:
+ void Initialize(ApplicationImpl* app) override;
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+
+ // ContentHandlerFactory::Delegate:
+ void RunApplication(InterfaceRequest<Application> application_request,
+ URLResponsePtr response) override;
+
+ ContentHandlerFactory content_handler_factory_;
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AndroidHandler);
+};
+
+bool RegisterAndroidHandlerJni(JNIEnv* env);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_ANDROID_CONTENT_HANDLER_H_
diff --git a/mojo/shell/android/android_handler_loader.cc b/mojo/shell/android/android_handler_loader.cc
new file mode 100644
index 0000000..922dbb5
--- /dev/null
+++ b/mojo/shell/android/android_handler_loader.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/android_handler_loader.h"
+
+namespace mojo {
+namespace shell {
+
+AndroidHandlerLoader::AndroidHandlerLoader() {
+}
+
+AndroidHandlerLoader::~AndroidHandlerLoader() {
+}
+
+void AndroidHandlerLoader::Load(
+ const GURL& url,
+ InterfaceRequest<Application> application_request) {
+ DCHECK(application_request.is_pending());
+ application_.reset(
+ new ApplicationImpl(&android_handler_, application_request.Pass()));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/android/android_handler_loader.h b/mojo/shell/android/android_handler_loader.h
new file mode 100644
index 0000000..91592737
--- /dev/null
+++ b/mojo/shell/android/android_handler_loader.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/public/cpp/application/application_impl.h"
+#include "mojo/shell/android/android_handler.h"
+#include "mojo/shell/application_manager/application_loader.h"
+
+namespace mojo {
+namespace shell {
+
+class AndroidHandlerLoader : public ApplicationLoader {
+ public:
+ AndroidHandlerLoader();
+ virtual ~AndroidHandlerLoader();
+
+ private:
+ // ApplicationLoader overrides:
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) 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/mojo/shell/android/apk/AndroidManifest.xml b/mojo/shell/android/apk/AndroidManifest.xml
new file mode 100644
index 0000000..0d374c9
--- /dev/null
+++ b/mojo/shell/android/apk/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+ <application android:name="MojoShellApplication"
+ android:label="Mojo Shell">
+ <meta-data android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+ <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.VIEW"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/mojo/shell/android/apk/res/values/strings.xml b/mojo/shell/android/apk/res/values/strings.xml
new file mode 100644
index 0000000..ff3f8bb
--- /dev/null
+++ b/mojo/shell/android/apk/res/values/strings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2013 The Chromium Authors. All rights reserved.
+
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+
+<resources>
+</resources>
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
new file mode 100644
index 0000000..610ff16
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/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;
+
+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::shell")
+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.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), true);
+ bootstrap_native_library = FileHelper.extractFromAssets(context,
+ BOOTSTRAP_NATIVE_LIBRARY, getAssetDir(context), true);
+ } 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/mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java
new file mode 100644
index 0000000..9916c83
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+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::shell")
+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/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java
new file mode 100644
index 0000000..c455251
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java
@@ -0,0 +1,179 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+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.FilenameFilter;
+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 {
+ public static final String TAG = "MojoFileHelper";
+
+ // 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-";
+ // Prefix used when naming timestamp files.
+ private static final String TIMESTAMP_PREFIX = "asset_timestamp-";
+
+ /**
+ * Looks for a timestamp file on disk that indicates the version of the APK that the resource
+ * assets were extracted from. Returns null if a timestamp was found and it indicates that the
+ * resources match the current APK. Otherwise returns a String that represents the filename of a
+ * timestamp to create.
+ */
+ private static String checkAssetTimestamp(Context context, File outputDir) {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pi = null;
+
+ try {
+ pi = pm.getPackageInfo(context.getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return TIMESTAMP_PREFIX;
+ }
+
+ if (pi == null) {
+ return TIMESTAMP_PREFIX;
+ }
+
+ String expectedTimestamp = TIMESTAMP_PREFIX + pi.versionCode + "-" + pi.lastUpdateTime;
+
+ String[] timestamps = outputDir.list(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.startsWith(TIMESTAMP_PREFIX);
+ }
+ });
+
+ if (timestamps.length != 1) {
+ // If there's no timestamp, nuke to be safe as we can't tell the age of the files.
+ // If there's multiple timestamps, something's gone wrong so nuke.
+ return expectedTimestamp;
+ }
+
+ if (!expectedTimestamp.equals(timestamps[0])) {
+ return expectedTimestamp;
+ }
+
+ // Timestamp file is already up-to date.
+ return null;
+ }
+
+ public static File extractFromAssets(Context context, String assetName, File outputDirectory,
+ boolean useTempFile) throws IOException, FileNotFoundException {
+ String timestampToCreate = null;
+ if (!useTempFile) {
+ timestampToCreate = checkAssetTimestamp(context, outputDirectory);
+ if (timestampToCreate != null) {
+ for (File child : outputDirectory.listFiles()) {
+ deleteRecursively(child);
+ }
+ }
+ }
+
+ File outputFile;
+ if (useTempFile) {
+ // Make the original filename part of the temp file name.
+ // TODO(ppi): do we need to sanitize the suffix?
+ String suffix = "-" + assetName;
+ outputFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, outputDirectory);
+ } else {
+ outputFile = new File(outputDirectory, assetName);
+ if (outputFile.exists()) {
+ return outputFile;
+ }
+ }
+
+ BufferedInputStream inputStream = new BufferedInputStream(
+ context.getAssets().open(assetName));
+ try {
+ writeStreamToFile(inputStream, outputFile);
+ } finally {
+ inputStream.close();
+ }
+
+ if (timestampToCreate != null) {
+ try {
+ new File(outputDirectory, timestampToCreate).createNewFile();
+ } catch (IOException e) {
+ // In the worst case we don't write a timestamp, so we'll re-extract the asset next
+ // time.
+ Log.w(TAG, "Failed to write asset timestamp!");
+ }
+ }
+
+ 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);
+ }
+ }
+ if (!file.delete()) {
+ Log.w(TAG, "Unable to delete file: " + file.getAbsolutePath());
+ }
+ }
+
+ private static void writeStreamToFile(InputStream inputStream, File outputFile)
+ throws IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
+ try {
+ int read;
+ while ((read = inputStream.read(buffer, 0, BUFFER_SIZE)) > 0) {
+ outputStream.write(buffer, 0, read);
+ }
+ } finally {
+ outputStream.close();
+ }
+ }
+}
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java
new file mode 100644
index 0000000..e13ddea
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java
@@ -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.
+
+package org.chromium.mojo.shell;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Interaction with the keyboard.
+ */
+@JNINamespace("mojo::shell")
+public class Keyboard {
+ @CalledByNative
+ private static void showSoftKeyboard(Activity activity) {
+ View v = activity.getCurrentFocus();
+ InputMethodManager imm =
+ (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT);
+ }
+
+ @CalledByNative
+ private static void hideSoftKeyboard(Activity activity) {
+ View v = activity.getCurrentFocus();
+ InputMethodManager imm =
+ (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(v.getApplicationWindowToken(), 0);
+ }
+}
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java
new file mode 100644
index 0000000..a56c43d
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java
@@ -0,0 +1,67 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.JsonReader;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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);
+
+ // 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.
+ ShellMain.ensureInitialized(this, getParametersFromIntent(getIntent()));
+
+ // TODO(eseidel): ShellMain can fail, but we're ignoring the return.
+ ShellMain.start();
+ }
+
+ private static String[] getParametersFromIntent(Intent intent) {
+ if (intent == null) {
+ return null;
+ }
+ String[] parameters = intent.getStringArrayExtra("parameters");
+ if (parameters != null) {
+ return parameters;
+ }
+ String encodedParameters = intent.getStringExtra("encodedParameters");
+ if (encodedParameters != null) {
+ JsonReader reader = new JsonReader(new StringReader(encodedParameters));
+ List<String> parametersList = new ArrayList<String>();
+ try {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ parametersList.add(reader.nextString());
+ }
+ reader.endArray();
+ reader.close();
+ return parametersList.toArray(new String[parametersList.size()]);
+ } catch (IOException e) {
+ Log.w(TAG, e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
new file mode 100644
index 0000000..f160260
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/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;
+
+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.LibraryProcessType;
+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.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized();
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "libmojo_shell initialization failed.", e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
new file mode 100644
index 0000000..1f55fa2
--- /dev/null
+++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
@@ -0,0 +1,105 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A placeholder class to call native functions.
+ **/
+@JNINamespace("mojo::shell")
+public class ShellMain {
+ private static final String TAG = "ShellMain";
+
+ // Directory where applications bundled with the shell will be extracted.
+ private static final String LOCAL_APP_DIRECTORY = "local_apps";
+ // Individual applications bundled with the shell as assets.
+ private static final String NETWORK_LIBRARY_APP = "network_service.mojo";
+ // The mojo_shell library is also an executable run in forked processes when running
+ // multi-process.
+ private static final String MOJO_SHELL_EXECUTABLE = "libmojo_shell.so";
+
+ /**
+ * 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;
+ try {
+ FileHelper.extractFromAssets(applicationContext, NETWORK_LIBRARY_APP,
+ getLocalAppsDir(applicationContext), false);
+ File mojoShell = new File(applicationContext.getApplicationInfo().nativeLibraryDir,
+ MOJO_SHELL_EXECUTABLE);
+
+ List<String> parametersList = new ArrayList<String>();
+ // Program name.
+ if (parameters != null) {
+ parametersList.addAll(Arrays.asList(parameters));
+ }
+
+ nativeInit(applicationContext, mojoShell.getAbsolutePath(),
+ parametersList.toArray(new String[parametersList.size()]),
+ getLocalAppsDir(applicationContext).getAbsolutePath(),
+ getTmpDir(applicationContext).getAbsolutePath());
+ sInitialized = true;
+ } catch (Exception e) {
+ Log.e(TAG, "ShellMain initialization failed.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Starts the specified application in the specified context.
+ *
+ * @return <code>true</code> if an application has been launched.
+ **/
+ static boolean start() {
+ return nativeStart();
+ }
+
+ /**
+ * Adds the given URL to the set of mojo applications to run on start.
+ */
+ static void addApplicationURL(String url) {
+ nativeAddApplicationURL(url);
+ }
+
+ private static File getLocalAppsDir(Context context) {
+ return context.getDir(LOCAL_APP_DIRECTORY, Context.MODE_PRIVATE);
+ }
+
+ private static File getTmpDir(Context context) {
+ return new File(context.getCacheDir(), "tmp");
+ }
+
+ @CalledByNative
+ private static void finishActivity(Activity activity) {
+ activity.finish();
+ }
+
+ /**
+ * Initializes the native system. This API should be called only once per process.
+ **/
+ private static native void nativeInit(Context context, String mojoShellPath,
+ String[] parameters, String bundledAppsDirectory, String tmpDir);
+
+ private static native boolean nativeStart();
+
+ private static native void nativeAddApplicationURL(String url);
+}
diff --git a/mojo/shell/android/background_application_loader.cc b/mojo/shell/android/background_application_loader.cc
new file mode 100644
index 0000000..cdb175e
--- /dev/null
+++ b/mojo/shell/android/background_application_loader.cc
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/background_application_loader.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "mojo/shell/application_manager/application_manager.h"
+
+namespace mojo {
+namespace shell {
+
+BackgroundApplicationLoader::BackgroundApplicationLoader(
+ scoped_ptr<ApplicationLoader> real_loader,
+ const std::string& thread_name,
+ base::MessageLoop::Type message_loop_type)
+ : loader_(real_loader.Pass()),
+ message_loop_type_(message_loop_type),
+ thread_name_(thread_name),
+ message_loop_created_(true, false) {
+}
+
+BackgroundApplicationLoader::~BackgroundApplicationLoader() {
+ if (thread_)
+ thread_->Join();
+}
+
+void BackgroundApplicationLoader::Load(
+ const GURL& url,
+ InterfaceRequest<Application> application_request) {
+ DCHECK(application_request.is_pending());
+ if (!thread_) {
+ // TODO(tim): It'd be nice if we could just have each Load call
+ // result in a new thread like DynamicService{Loader, Runner}. But some
+ // loaders are creating multiple ApplicationImpls (NetworkApplicationLoader)
+ // sharing a delegate (etc). So we have to keep it single threaded, wait
+ // for the thread to initialize, and post to the TaskRunner for subsequent
+ // Load calls for now.
+ thread_.reset(new base::DelegateSimpleThread(this, thread_name_));
+ thread_->Start();
+ message_loop_created_.Wait();
+ DCHECK(task_runner_.get());
+ }
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&BackgroundApplicationLoader::LoadOnBackgroundThread,
+ base::Unretained(this), url,
+ base::Passed(&application_request)));
+}
+
+void BackgroundApplicationLoader::Run() {
+ base::MessageLoop message_loop(message_loop_type_);
+ base::RunLoop loop;
+ task_runner_ = message_loop.task_runner();
+ quit_closure_ = loop.QuitClosure();
+ message_loop_created_.Signal();
+ loop.Run();
+
+ // Destroy |loader_| on the thread it's actually used on.
+ loader_.reset();
+}
+
+void BackgroundApplicationLoader::LoadOnBackgroundThread(
+ const GURL& url,
+ InterfaceRequest<Application> application_request) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ loader_->Load(url, application_request.Pass());
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/android/background_application_loader.h b/mojo/shell/android/background_application_loader.h
new file mode 100644
index 0000000..7c549f7
--- /dev/null
+++ b/mojo/shell/android/background_application_loader.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
+#define SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/shell/application_manager/application_loader.h"
+
+namespace mojo {
+namespace shell {
+
+class BackgroundApplicationLoader
+ : public ApplicationLoader,
+ public base::DelegateSimpleThread::Delegate {
+ public:
+ BackgroundApplicationLoader(scoped_ptr<ApplicationLoader> real_loader,
+ const std::string& thread_name,
+ base::MessageLoop::Type message_loop_type);
+ ~BackgroundApplicationLoader() override;
+
+ // ApplicationLoader overrides:
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) override;
+
+ private:
+ // |base::DelegateSimpleThread::Delegate| method:
+ void Run() override;
+
+ // These functions are exected on the background thread. They call through
+ // to |background_loader_| to do the actual loading.
+ void LoadOnBackgroundThread(
+ const GURL& url,
+ InterfaceRequest<Application> application_request);
+ bool quit_on_shutdown_;
+ scoped_ptr<ApplicationLoader> loader_;
+
+ const base::MessageLoop::Type message_loop_type_;
+ const std::string thread_name_;
+
+ // Created on |thread_| during construction of |this|. Protected against
+ // uninitialized use by |message_loop_created_|, and protected against
+ // use-after-free by holding a reference to the thread-safe object. Note
+ // that holding a reference won't hold |thread_| from exiting.
+ scoped_refptr<base::TaskRunner> task_runner_;
+ base::WaitableEvent message_loop_created_;
+
+ // Lives on |thread_|.
+ base::Closure quit_closure_;
+
+ scoped_ptr<base::DelegateSimpleThread> thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundApplicationLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
diff --git a/mojo/shell/android/background_application_loader_unittest.cc b/mojo/shell/android/background_application_loader_unittest.cc
new file mode 100644
index 0000000..4d18073
--- /dev/null
+++ b/mojo/shell/android/background_application_loader_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/background_application_loader.h"
+
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+class DummyLoader : public ApplicationLoader {
+ public:
+ DummyLoader() : simulate_app_quit_(true) {}
+ ~DummyLoader() override {}
+
+ // ApplicationLoader overrides:
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) override {
+ if (simulate_app_quit_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ void DontSimulateAppQuit() { simulate_app_quit_ = false; }
+
+ private:
+ bool simulate_app_quit_;
+};
+
+// Tests that the loader can start and stop gracefully.
+TEST(BackgroundApplicationLoaderTest, StartStop) {
+ scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
+ BackgroundApplicationLoader loader(real_loader.Pass(), "test",
+ base::MessageLoop::TYPE_DEFAULT);
+}
+
+// Tests that the loader can load a service that is well behaved (quits
+// itself).
+TEST(BackgroundApplicationLoaderTest, Load) {
+ scoped_ptr<ApplicationLoader> real_loader(new DummyLoader());
+ BackgroundApplicationLoader loader(real_loader.Pass(), "test",
+ base::MessageLoop::TYPE_DEFAULT);
+ ApplicationPtr application;
+ loader.Load(GURL(), GetProxy(&application));
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/android/bootstrap.cc b/mojo/shell/android/bootstrap.cc
new file mode 100644
index 0000000..4fcae37
--- /dev/null
+++ b/mojo/shell/android/bootstrap.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/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 "mojo/shell/android/run_android_application_function.h"
+
+namespace mojo {
+namespace shell {
+
+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 shell
+} // namespace mojo
+
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ base::android::InitVM(vm);
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ if (!mojo::shell::RegisterBootstrapJni(env))
+ return -1;
+
+ return JNI_VERSION_1_4;
+}
diff --git a/mojo/shell/android/keyboard_impl.cc b/mojo/shell/android/keyboard_impl.cc
new file mode 100644
index 0000000..60336bb
--- /dev/null
+++ b/mojo/shell/android/keyboard_impl.cc
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/keyboard_impl.h"
+
+#include "base/android/jni_android.h"
+#include "base/logging.h"
+#include "jni/Keyboard_jni.h"
+
+namespace mojo {
+namespace shell {
+
+KeyboardImpl::KeyboardImpl(InterfaceRequest<Keyboard> request)
+ : binding_(this, request.Pass()) {
+}
+
+KeyboardImpl::~KeyboardImpl() {
+}
+
+void KeyboardImpl::Show() {
+ Java_Keyboard_showSoftKeyboard(base::android::AttachCurrentThread(),
+ base::android::GetApplicationContext());
+}
+
+void KeyboardImpl::Hide() {
+ Java_Keyboard_hideSoftKeyboard(base::android::AttachCurrentThread(),
+ base::android::GetApplicationContext());
+}
+
+bool RegisterKeyboardJni(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/android/keyboard_impl.h b/mojo/shell/android/keyboard_impl.h
new file mode 100644
index 0000000..e40b7dd
--- /dev/null
+++ b/mojo/shell/android/keyboard_impl.h
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. 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_KEYBOARD_IMPL_H_
+#define SHELL_ANDROID_KEYBOARD_IMPL_H_
+
+#include <jni.h>
+
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/services/keyboard/public/interfaces/keyboard.mojom.h"
+
+namespace mojo {
+namespace shell {
+
+class KeyboardImpl : public Keyboard {
+ public:
+ KeyboardImpl(InterfaceRequest<Keyboard> request);
+ ~KeyboardImpl();
+
+ // Keyboard implementation
+ void Show() override;
+ void Hide() override;
+
+ private:
+ StrongBinding<Keyboard> binding_;
+};
+
+bool RegisterKeyboardJni(JNIEnv* env);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_ANDROID_KEYBOARD_IMPL_H_
diff --git a/mojo/shell/android/library_loader.cc b/mojo/shell/android/library_loader.cc
new file mode 100644
index 0000000..cd3f7d2
--- /dev/null
+++ b/mojo/shell/android/library_loader.cc
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/base_jni_onload.h"
+#include "base/android/base_jni_registrar.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "base/bind.h"
+#include "mojo/services/native_viewport/platform_viewport_android.h"
+#include "mojo/shell/android/android_handler.h"
+#include "mojo/shell/android/keyboard_impl.h"
+#include "mojo/shell/android/main.h"
+
+namespace {
+
+base::android::RegistrationMethod kMojoRegisteredMethods[] = {
+ {"AndroidHandler", mojo::shell::RegisterAndroidHandlerJni},
+ {"Keyboard", mojo::shell::RegisterKeyboardJni},
+ {"PlatformViewportAndroid",
+ native_viewport::PlatformViewportAndroid::Register},
+ {"ShellMain", mojo::shell::RegisterShellMain},
+};
+
+bool RegisterJNI(JNIEnv* env) {
+ if (!base::android::RegisterJni(env))
+ return false;
+
+ 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) {
+ std::vector<base::android::RegisterCallback> register_callbacks;
+ register_callbacks.push_back(base::Bind(&RegisterJNI));
+ if (!base::android::OnJNIOnLoadRegisterJNI(vm, register_callbacks) ||
+ !base::android::OnJNIOnLoadInit(
+ std::vector<base::android::InitCallback>())) {
+ return -1;
+ }
+
+ return JNI_VERSION_1_4;
+}
diff --git a/mojo/shell/android/main.cc b/mojo/shell/android/main.cc
new file mode 100644
index 0000000..41c507a
--- /dev/null
+++ b/mojo/shell/android/main.cc
@@ -0,0 +1,222 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/main.h"
+
+#include "base/android/fifo_utils.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/simple_thread.h"
+#include "jni/ShellMain_jni.h"
+#include "mojo/common/message_pump_mojo.h"
+#include "mojo/shell/android/android_handler_loader.h"
+#include "mojo/shell/android/background_application_loader.h"
+#include "mojo/shell/android/native_viewport_application_loader.h"
+#include "mojo/shell/android/ui_application_loader_android.h"
+#include "mojo/shell/application_manager/application_loader.h"
+#include "mojo/shell/command_line_util.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/init.h"
+#include "ui/gl/gl_surface_egl.h"
+
+using base::LazyInstance;
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+// Tag for logging.
+const char kLogTag[] = "chromium";
+
+// Command line argument for the communication fifo.
+const char kFifoPath[] = "fifo-path";
+
+class MojoShellRunner : public base::DelegateSimpleThread::Delegate {
+ public:
+ MojoShellRunner(const std::vector<std::string>& parameters)
+ : parameters_(parameters) {}
+ ~MojoShellRunner() override {}
+
+ private:
+ void Run() override;
+
+ std::vector<std::string> parameters_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoShellRunner);
+};
+
+LazyInstance<scoped_ptr<base::MessageLoop>> g_java_message_loop =
+ LAZY_INSTANCE_INITIALIZER;
+
+LazyInstance<scoped_ptr<Context>> g_context = LAZY_INSTANCE_INITIALIZER;
+
+LazyInstance<scoped_ptr<MojoShellRunner>> g_shell_runner =
+ LAZY_INSTANCE_INITIALIZER;
+
+LazyInstance<scoped_ptr<base::DelegateSimpleThread>> g_shell_thread =
+ LAZY_INSTANCE_INITIALIZER;
+
+LazyInstance<base::android::ScopedJavaGlobalRef<jobject>> g_main_activiy =
+ LAZY_INSTANCE_INITIALIZER;
+
+void ConfigureAndroidServices(Context* context) {
+ context->application_manager()->SetLoaderForURL(
+ make_scoped_ptr(new UIApplicationLoader(
+ make_scoped_ptr(new NativeViewportApplicationLoader()),
+ g_java_message_loop.Get().get())),
+ GURL("mojo:native_viewport_service"));
+
+ // Android handler is bundled with the Mojo shell, because it uses the
+ // MojoShell application as the JNI bridge to bootstrap execution of other
+ // Android Mojo apps that need JNI.
+ context->application_manager()->SetLoaderForURL(
+ make_scoped_ptr(new BackgroundApplicationLoader(
+ make_scoped_ptr(new AndroidHandlerLoader()), "android_handler",
+ base::MessageLoop::TYPE_DEFAULT)),
+ GURL("mojo:android_handler"));
+
+ // By default, the keyboard is handled by the native_viewport_service.
+ context->url_resolver()->AddURLMapping(GURL("mojo:keyboard"),
+ GURL("mojo:native_viewport_service"));
+}
+
+void QuitShellThread() {
+ g_shell_thread.Get()->Join();
+ g_shell_thread.Pointer()->reset();
+ Java_ShellMain_finishActivity(base::android::AttachCurrentThread(),
+ g_main_activiy.Get().obj());
+ exit(0);
+}
+
+void MojoShellRunner::Run() {
+ base::MessageLoop loop(common::MessagePumpMojo::Create());
+ Context* context = g_context.Pointer()->get();
+ ConfigureAndroidServices(context);
+ context->Init();
+
+ for (auto& args : parameters_)
+ ApplyApplicationArgs(context, args);
+
+ RunCommandLineApps(context);
+ loop.Run();
+
+ g_java_message_loop.Pointer()->get()->PostTask(FROM_HERE,
+ base::Bind(&QuitShellThread));
+}
+
+// Initialize stdout redirection if the command line switch is present.
+void InitializeRedirection() {
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(kFifoPath))
+ return;
+
+ base::FilePath fifo_path =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(kFifoPath);
+ base::FilePath directory = fifo_path.DirName();
+ CHECK(base::CreateDirectoryAndGetError(directory, nullptr))
+ << "Unable to create directory: " << directory.value();
+ unlink(fifo_path.value().c_str());
+ CHECK(base::android::CreateFIFO(fifo_path, 0666))
+ << "Unable to create fifo: " << fifo_path.value();
+ CHECK(base::android::RedirectStream(stdout, fifo_path, "w"))
+ << "Failed to redirect stdout to file: " << fifo_path.value();
+ CHECK(dup2(STDOUT_FILENO, STDERR_FILENO) != -1)
+ << "Unable to redirect stderr to stdout.";
+}
+
+} // namespace
+
+static void Init(JNIEnv* env,
+ jclass clazz,
+ jobject activity,
+ jstring mojo_shell_path,
+ jobjectArray jparameters,
+ jstring j_local_apps_directory,
+ jstring j_tmp_dir) {
+ g_main_activiy.Get().Reset(env, activity);
+
+ // Setting the TMPDIR environment variable so that applications can use it.
+ // TODO(qsr) We will need our subprocesses to inherit this.
+ int return_value =
+ setenv("TMPDIR",
+ base::android::ConvertJavaStringToUTF8(env, j_tmp_dir).c_str(), 1);
+ DCHECK_EQ(return_value, 0);
+
+ base::android::ScopedJavaLocalRef<jobject> scoped_activity(env, activity);
+ base::android::InitApplicationContext(env, scoped_activity);
+
+ std::vector<std::string> parameters;
+ parameters.push_back(
+ base::android::ConvertJavaStringToUTF8(env, mojo_shell_path));
+ base::android::AppendJavaStringArrayToStringVector(env, jparameters,
+ &parameters);
+ base::CommandLine::Init(0, nullptr);
+ base::CommandLine::ForCurrentProcess()->InitFromArgv(parameters);
+ g_shell_runner.Get().reset(new MojoShellRunner(parameters));
+
+ InitializeLogging();
+
+ InitializeRedirection();
+
+ // 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.
+ Context* shell_context = new Context();
+ shell_context->SetShellFileRoot(base::FilePath(
+ base::android::ConvertJavaStringToUTF8(env, j_local_apps_directory)));
+ 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 jboolean Start(JNIEnv* env, jclass clazz) {
+ if (!base::CommandLine::ForCurrentProcess()->GetArgs().size())
+ return false;
+
+#if defined(MOJO_SHELL_DEBUG_URL)
+ base::CommandLine::ForCurrentProcess()->AppendArg(MOJO_SHELL_DEBUG_URL);
+ // Sleep for 5 seconds to give the debugger a chance to attach.
+ sleep(5);
+#endif
+
+ g_shell_thread.Get().reset(new base::DelegateSimpleThread(
+ g_shell_runner.Get().get(), "ShellThread"));
+ g_shell_thread.Get()->Start();
+ return true;
+}
+
+static void AddApplicationURL(JNIEnv* env, jclass clazz, jstring jurl) {
+ base::CommandLine::ForCurrentProcess()->AppendArg(
+ base::android::ConvertJavaStringToUTF8(env, jurl));
+}
+
+bool RegisterShellMain(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace shell
+} // namespace mojo
+
+// TODO(vtl): Even though main() should never be called, mojo_shell fails to
+// link without it. Figure out if we can avoid this.
+int main(int argc, char** argv) {
+ NOTREACHED();
+}
diff --git a/mojo/shell/android/main.h b/mojo/shell/android/main.h
new file mode 100644
index 0000000..43ea16a
--- /dev/null
+++ b/mojo/shell/android/main.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_ANDROID_MAIN_H_
+#define SHELL_ANDROID_MAIN_H_
+
+#include <jni.h>
+
+namespace mojo {
+namespace shell {
+
+bool RegisterShellMain(JNIEnv* env);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_ANDROID_MAIN_H_
diff --git a/mojo/shell/android/native_viewport_application_loader.cc b/mojo/shell/android/native_viewport_application_loader.cc
new file mode 100644
index 0000000..e3e3767
--- /dev/null
+++ b/mojo/shell/android/native_viewport_application_loader.cc
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/native_viewport_application_loader.h"
+
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/gles2/gpu_state.h"
+#include "mojo/services/native_viewport/native_viewport_impl.h"
+#include "mojo/shell/android/keyboard_impl.h"
+
+namespace mojo {
+namespace shell {
+
+NativeViewportApplicationLoader::NativeViewportApplicationLoader() {
+}
+
+NativeViewportApplicationLoader::~NativeViewportApplicationLoader() {
+}
+
+void NativeViewportApplicationLoader::Load(
+ const GURL& url,
+ InterfaceRequest<Application> application_request) {
+ DCHECK(application_request.is_pending());
+ app_.reset(new ApplicationImpl(this, application_request.Pass()));
+}
+
+bool NativeViewportApplicationLoader::ConfigureIncomingConnection(
+ ApplicationConnection* connection) {
+ connection->AddService<NativeViewport>(this);
+ connection->AddService<Gpu>(this);
+ connection->AddService<Keyboard>(this);
+ return true;
+}
+
+void NativeViewportApplicationLoader::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<NativeViewport> request) {
+ if (!gpu_state_)
+ gpu_state_ = new gles2::GpuState;
+ new native_viewport::NativeViewportImpl(false, gpu_state_, request.Pass());
+}
+
+void NativeViewportApplicationLoader::Create(
+ ApplicationConnection* connection,
+ InterfaceRequest<Keyboard> request) {
+ new KeyboardImpl(request.Pass());
+}
+
+void NativeViewportApplicationLoader::Create(ApplicationConnection* connection,
+ InterfaceRequest<Gpu> request) {
+ if (!gpu_state_)
+ gpu_state_ = new gles2::GpuState;
+ new gles2::GpuImpl(request.Pass(), gpu_state_);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/android/native_viewport_application_loader.h b/mojo/shell/android/native_viewport_application_loader.h
new file mode 100644
index 0000000..ece77e1
--- /dev/null
+++ b/mojo/shell/android/native_viewport_application_loader.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_SHELL_ANDROID_NATIVE_VIEWPORT_APPLICATION_LOADER_H_
+#define MOJO_SHELL_ANDROID_NATIVE_VIEWPORT_APPLICATION_LOADER_H_
+
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/services/gles2/gpu_impl.h"
+#include "mojo/services/keyboard/public/interfaces/keyboard.mojom.h"
+#include "mojo/shell/application_manager/application_loader.h"
+#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h"
+#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h"
+
+namespace gles2 {
+class GpuState;
+}
+
+namespace mojo {
+
+class ApplicationImpl;
+
+namespace shell {
+
+class NativeViewportApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<Keyboard>,
+ public InterfaceFactory<NativeViewport>,
+ public InterfaceFactory<Gpu> {
+ public:
+ NativeViewportApplicationLoader();
+ ~NativeViewportApplicationLoader();
+
+ private:
+ // ApplicationLoader implementation.
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) override;
+
+ // ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override;
+
+ // InterfaceFactory<NativeViewport> implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<NativeViewport> request) override;
+
+ // InterfaceFactory<Gpu> implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<Gpu> request) override;
+
+ // InterfaceFactory<Keyboard> implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<Keyboard> request) override;
+
+ scoped_refptr<gles2::GpuState> gpu_state_;
+ scoped_ptr<ApplicationImpl> app_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeViewportApplicationLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // MOJO_SHELL_ANDROID_NATIVE_VIEWPORT_APPLICATION_LOADER_H_
diff --git a/mojo/shell/android/run_android_application_function.h b/mojo/shell/android/run_android_application_function.h
new file mode 100644
index 0000000..3ed1744
--- /dev/null
+++ b/mojo/shell/android/run_android_application_function.h
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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"
+
+namespace mojo {
+namespace shell {
+
+// 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.
+
+typedef void (*RunAndroidApplicationFn)(JNIEnv* env,
+ jobject j_context,
+ const base::FilePath& app_path,
+ jint j_handle);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_
diff --git a/mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java b/mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java
new file mode 100644
index 0000000..2aba46f
--- /dev/null
+++ b/mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Helper method for ShellTestBase.
+ */
+@JNINamespace("mojo::shell::test")
+public class ShellTestBase {
+ // Directory where applications bundled with the tests will be extracted.
+ private static final String TEST_APP_DIRECTORY = "test_apps";
+
+ /**
+ * Extracts the mojo applications from the apk assets and returns the directory where they are.
+ */
+ @CalledByNative
+ private static String extractMojoApplications(Context context) throws IOException {
+ final File outputDirectory = context.getDir(TEST_APP_DIRECTORY, Context.MODE_PRIVATE);
+
+ AssetManager manager = context.getResources().getAssets();
+ for (String asset : manager.list("")) {
+ if (asset.endsWith(".mojo")) {
+ FileHelper.extractFromAssets(context, asset, outputDirectory, false);
+ }
+ }
+
+ return outputDirectory.getAbsolutePath();
+ }
+}
diff --git a/mojo/shell/android/ui_application_loader_android.cc b/mojo/shell/android/ui_application_loader_android.cc
new file mode 100644
index 0000000..87f686f
--- /dev/null
+++ b/mojo/shell/android/ui_application_loader_android.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/android/ui_application_loader_android.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/shell/application_manager/application_manager.h"
+
+namespace mojo {
+namespace shell {
+
+UIApplicationLoader::UIApplicationLoader(
+ scoped_ptr<ApplicationLoader> real_loader,
+ base::MessageLoop* ui_message_loop)
+ : loader_(real_loader.Pass()), ui_message_loop_(ui_message_loop) {
+}
+
+UIApplicationLoader::~UIApplicationLoader() {
+ ui_message_loop_->PostTask(
+ FROM_HERE, base::Bind(&UIApplicationLoader::ShutdownOnUIThread,
+ base::Unretained(this)));
+}
+
+void UIApplicationLoader::Load(
+ const GURL& url,
+ InterfaceRequest<Application> application_request) {
+ DCHECK(application_request.is_pending());
+ ui_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&UIApplicationLoader::LoadOnUIThread, base::Unretained(this),
+ url, base::Passed(&application_request)));
+}
+
+void UIApplicationLoader::LoadOnUIThread(
+ const GURL& url,
+ InterfaceRequest<Application> application_request) {
+ loader_->Load(url, application_request.Pass());
+}
+
+void UIApplicationLoader::ShutdownOnUIThread() {
+ // Destroy |loader_| on the thread it's actually used on.
+ loader_.reset();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/android/ui_application_loader_android.h b/mojo/shell/android/ui_application_loader_android.h
new file mode 100644
index 0000000..37cc063
--- /dev/null
+++ b/mojo/shell/android/ui_application_loader_android.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_ANDROID_UI_APPLICATION_LOADER_ANDROID_H_
+#define SHELL_ANDROID_UI_APPLICATION_LOADER_ANDROID_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/shell/application_manager/application_loader.h"
+
+namespace base {
+class MessageLoop;
+}
+
+namespace mojo {
+namespace shell {
+
+class ApplicationManager;
+
+// ApplicationLoader implementation that creates a background thread and issues
+// load
+// requests there.
+class UIApplicationLoader : public ApplicationLoader {
+ public:
+ UIApplicationLoader(scoped_ptr<ApplicationLoader> real_loader,
+ base::MessageLoop* ui_message_loop);
+ ~UIApplicationLoader() override;
+
+ // ApplicationLoader overrides:
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) 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(const GURL& url,
+ InterfaceRequest<Application> application_request);
+ void ShutdownOnUIThread();
+
+ scoped_ptr<ApplicationLoader> loader_;
+ base::MessageLoop* ui_message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIApplicationLoader);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_ANDROID_UI_APPLICATION_LOADER_ANDROID_H_
diff --git a/mojo/shell/app_child_process.cc b/mojo/shell/app_child_process.cc
new file mode 100644
index 0000000..546c28c
--- /dev/null
+++ b/mojo/shell/app_child_process.cc
@@ -0,0 +1,302 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/app_child_process.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/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/process_delegate.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/shell/app_child_process.mojom.h"
+#include "mojo/shell/native_application_support.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:
+ explicit Unblocker(Blocker* blocker = nullptr) : blocker_(blocker) {}
+ ~Unblocker() {}
+
+ void Unblock(base::Closure run_after) {
+ DCHECK(blocker_);
+ DCHECK(blocker_->run_after_.is_null());
+ blocker_->run_after_ = run_after;
+ blocker_->event_.Signal();
+ blocker_ = nullptr;
+ }
+
+ private:
+ 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;
+
+// Should be created and initialized on the main thread.
+class AppContext : public embedder::ProcessDelegate {
+ public:
+ AppContext()
+ : io_thread_("io_thread"), controller_thread_("controller_thread") {}
+ ~AppContext() override {}
+
+ void Init() {
+ // Initialize Mojo before starting any threads.
+ embedder::Init(make_scoped_ptr(new 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());
+
+ // TODO(vtl): This should be SLAVE, not NONE.
+ embedder::InitIPCSupport(embedder::ProcessType::NONE, controller_runner_,
+ this, io_runner_,
+ embedder::ScopedPlatformHandle());
+ }
+
+ void Shutdown() {
+ Blocker blocker;
+ shutdown_unblocker_ = blocker.GetUnblocker();
+ controller_runner_->PostTask(
+ FROM_HERE, base::Bind(&AppContext::ShutdownOnControllerThread,
+ base::Unretained(this)));
+ blocker.Block();
+ }
+
+ 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:
+ void ShutdownOnControllerThread() {
+ // First, destroy the controller.
+ controller_.reset();
+
+ // Next shutdown IPC. We'll unblock the main thread in OnShutdownComplete().
+ embedder::ShutdownIPCSupport();
+ }
+
+ // ProcessDelegate implementation.
+ void OnShutdownComplete() override {
+ shutdown_unblocker_.Unblock(base::Closure());
+ }
+
+ base::Thread io_thread_;
+ scoped_refptr<base::SingleThreadTaskRunner> io_runner_;
+
+ base::Thread controller_thread_;
+ scoped_refptr<base::SingleThreadTaskRunner> controller_runner_;
+
+ // Accessed only on the controller thread.
+ scoped_ptr<AppChildControllerImpl> controller_;
+
+ // Used to unblock the main thread on shutdown.
+ Blocker::Unblocker shutdown_unblocker_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppContext);
+};
+
+// AppChildControllerImpl ------------------------------------------------------
+
+class AppChildControllerImpl : public AppChildController, public ErrorHandler {
+ public:
+ ~AppChildControllerImpl() override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // TODO(vtl): Pass in the result from |MainMain()|.
+ on_app_complete_.Run(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()));
+
+ impl->Bind(host_message_pipe.Pass());
+
+ app_context->set_controller(impl.Pass());
+ }
+
+ void Bind(ScopedMessagePipeHandle handle) { binding_.Bind(handle.Pass()); }
+
+ // |ErrorHandler| methods:
+ void OnConnectionError() override {
+ // A connection error means the connection to the shell is lost. This is not
+ // recoverable.
+ LOG(ERROR) << "Connection error to the shell.";
+ _exit(1);
+ }
+
+ // |AppChildController| methods:
+ void StartApp(const String& app_path,
+ bool clean_app_path,
+ InterfaceRequest<Application> application_request,
+ const StartAppCallback& on_app_complete) override {
+ DVLOG(2) << "AppChildControllerImpl::StartApp(" << app_path << ", ...)";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ on_app_complete_ = on_app_complete;
+ unblocker_.Unblock(base::Bind(&AppChildControllerImpl::StartAppOnMainThread,
+ base::FilePath::FromUTF8Unsafe(app_path),
+ clean_app_path
+ ? NativeApplicationCleanup::DELETE
+ : NativeApplicationCleanup::DONT_DELETE,
+ base::Passed(&application_request)));
+ }
+
+ void ExitNow(int32_t exit_code) override {
+ DVLOG(2) << "AppChildControllerImpl::ExitNow(" << exit_code << ")";
+ _exit(exit_code);
+ }
+
+ private:
+ AppChildControllerImpl(AppContext* app_context,
+ const Blocker::Unblocker& unblocker)
+ : app_context_(app_context),
+ unblocker_(unblocker),
+ channel_info_(nullptr),
+ binding_(this) {
+ binding_.set_error_handler(this);
+ }
+
+ // 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,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request) {
+ // TODO(vtl): This is copied from in_process_native_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.
+ base::NativeLibrary app_library = LoadNativeApplication(app_path, cleanup);
+ RunNativeApplication(app_library, application_request.Pass());
+ }
+
+ base::ThreadChecker thread_checker_;
+ AppContext* const app_context_;
+ Blocker::Unblocker unblocker_;
+ StartAppCallback on_app_complete_;
+
+ embedder::ChannelInfo* channel_info_;
+ Binding<AppChildController> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppChildControllerImpl);
+};
+
+} // namespace
+
+// AppChildProcess -------------------------------------------------------------
+
+AppChildProcess::AppChildProcess() {
+}
+
+AppChildProcess::~AppChildProcess() {
+}
+
+void AppChildProcess::Main() {
+ DVLOG(2) << "AppChildProcess::Main()";
+
+ DCHECK(!base::MessageLoop::current());
+
+ AppContext app_context;
+ app_context.Init();
+
+ Blocker blocker;
+ app_context.controller_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&AppChildControllerImpl::Init, base::Unretained(&app_context),
+ base::Passed(platform_channel()), blocker.GetUnblocker()));
+ // This will block, then run whatever the controller wants.
+ blocker.Block();
+
+ app_context.Shutdown();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/app_child_process.h b/mojo/shell/app_child_process.h
new file mode 100644
index 0000000..d63afda
--- /dev/null
+++ b/mojo/shell/app_child_process.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_APP_CHILD_PROCESS_H_
+#define SHELL_APP_CHILD_PROCESS_H_
+
+#include "base/macros.h"
+#include "mojo/shell/child_process.h"
+
+namespace mojo {
+namespace shell {
+
+// An implementation of |ChildProcess| for an 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/mojo/shell/app_child_process.mojom b/mojo/shell/app_child_process.mojom
new file mode 100644
index 0000000..4b7480d
--- /dev/null
+++ b/mojo/shell/app_child_process.mojom
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo.shell;
+
+import "mojo/public/interfaces/application/application.mojom";
+
+interface AppChildController {
+ // Starts the app at the given path (deleting it if |clean_app_path| is true).
+ StartApp(string app_path,
+ bool clean_app_path,
+ mojo.Application& application_request) => (int32 result);
+
+ // Exits the child process now (with no cleanup), with the given exit code.
+ ExitNow(int32 exit_code);
+};
diff --git a/mojo/shell/app_child_process_host.cc b/mojo/shell/app_child_process_host.cc
new file mode 100644
index 0000000..bf290ac
--- /dev/null
+++ b/mojo/shell/app_child_process_host.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/app_child_process_host.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/task_runners.h"
+
+namespace mojo {
+namespace shell {
+
+AppChildProcessHost::AppChildProcessHost(Context* context)
+ : ChildProcessHost(context), channel_info_(nullptr) {
+}
+
+AppChildProcessHost::~AppChildProcessHost() {
+}
+
+void AppChildProcessHost::StartApp(
+ const String& app_path,
+ bool clean_app_path,
+ InterfaceRequest<Application> application_request,
+ const AppChildController::StartAppCallback& on_app_complete) {
+ DCHECK(controller_);
+
+ on_app_complete_ = on_app_complete;
+ controller_->StartApp(
+ app_path, clean_app_path, application_request.Pass(),
+ base::Bind(&AppChildProcessHost::AppCompleted, base::Unretained(this)));
+}
+
+void AppChildProcessHost::ExitNow(int32_t exit_code) {
+ DCHECK(controller_);
+
+ controller_->ExitNow(exit_code);
+}
+
+void AppChildProcessHost::WillStart() {
+ DCHECK(platform_channel()->is_valid());
+
+ 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());
+}
+
+void AppChildProcessHost::DidStart(bool success) {
+ DVLOG(2) << "AppChildProcessHost::DidStart()";
+
+ if (!success) {
+ LOG(ERROR) << "Failed to start app child process";
+ 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;
+}
+
+void AppChildProcessHost::AppCompleted(int32_t result) {
+ if (!on_app_complete_.is_null()) {
+ auto on_app_complete = on_app_complete_;
+ on_app_complete_.reset();
+ on_app_complete.Run(result);
+ }
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/app_child_process_host.h b/mojo/shell/app_child_process_host.h
new file mode 100644
index 0000000..451fd7e
--- /dev/null
+++ b/mojo/shell/app_child_process_host.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_APP_CHILD_PROCESS_HOST_H_
+#define SHELL_APP_CHILD_PROCESS_HOST_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "mojo/edk/embedder/channel_info_forward.h"
+#include "mojo/shell/app_child_process.mojom.h"
+#include "mojo/shell/child_process_host.h"
+
+namespace mojo {
+namespace shell {
+
+// A subclass of |ChildProcessHost| to host an app child process, which runs a
+// single app (loaded from the file system).
+//
+// Note: After |Start()|, |StartApp| must be called and this object must
+// remained alive until the |on_app_complete| callback is called.
+class AppChildProcessHost : public ChildProcessHost {
+ public:
+ explicit AppChildProcessHost(Context* context);
+ ~AppChildProcessHost() override;
+
+ // See |AppChildController|:
+ void StartApp(const String& app_path,
+ bool clean_app_path,
+ InterfaceRequest<Application> application_request,
+ const AppChildController::StartAppCallback& on_app_complete);
+ void ExitNow(int32_t exit_code);
+
+ // |ChildProcessHost| methods:
+ void WillStart() override;
+ void DidStart(bool success) override;
+
+ private:
+ // Callback for |embedder::CreateChannel()|.
+ void DidCreateChannel(embedder::ChannelInfo* channel_info);
+
+ void AppCompleted(int32_t result);
+
+ AppChildControllerPtr controller_;
+ embedder::ChannelInfo* channel_info_;
+ AppChildController::StartAppCallback on_app_complete_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppChildProcessHost);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APP_CHILD_PROCESS_HOST_H_
diff --git a/mojo/shell/app_child_process_host_unittest.cc b/mojo/shell/app_child_process_host_unittest.cc
new file mode 100644
index 0000000..37624bf
--- /dev/null
+++ b/mojo/shell/app_child_process_host_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Note: This file also tests app_child_process.*.
+
+#include "mojo/shell/app_child_process_host.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/common/message_pump_mojo.h"
+#include "mojo/shell/context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+// Subclass just so we can observe |DidStart()|.
+class TestAppChildProcessHost : public AppChildProcessHost {
+ public:
+ explicit TestAppChildProcessHost(Context* context)
+ : AppChildProcessHost(context) {}
+ ~TestAppChildProcessHost() override {}
+
+ void DidStart(bool success) override {
+ EXPECT_TRUE(success);
+ AppChildProcessHost::DidStart(success);
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestAppChildProcessHost);
+};
+
+#if defined(OS_ANDROID)
+// TODO(qsr): Multiprocess shell tests are not supported on android.
+#define MAYBE_StartJoin DISABLED_StartJoin
+#else
+#define MAYBE_StartJoin StartJoin
+#endif // defined(OS_ANDROID)
+// Just tests starting the child process and joining it (without starting an
+// app).
+TEST(AppChildProcessHostTest, MAYBE_StartJoin) {
+ Context context;
+ base::MessageLoop message_loop(
+ scoped_ptr<base::MessagePump>(new common::MessagePumpMojo()));
+ context.Init();
+ TestAppChildProcessHost app_child_process_host(&context);
+ app_child_process_host.Start();
+ message_loop.Run();
+ app_child_process_host.ExitNow(123);
+ int exit_code = app_child_process_host.Join();
+ VLOG(2) << "Joined child: exit_code = " << exit_code;
+ EXPECT_EQ(123, exit_code);
+
+ context.Shutdown();
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/BUILD.gn b/mojo/shell/application_manager/BUILD.gn
new file mode 100644
index 0000000..f220b43
--- /dev/null
+++ b/mojo/shell/application_manager/BUILD.gn
@@ -0,0 +1,75 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+import("//testing/test.gni")
+
+source_set("application_manager") {
+ output_name = "mojo_application_manager"
+ sources = [
+ "application_loader.h",
+ "application_manager.cc",
+ "application_manager.h",
+ "data_pipe_peek.cc",
+ "data_pipe_peek.h",
+ "fetcher.cc",
+ "fetcher.h",
+ "identity.cc",
+ "identity.h",
+ "local_fetcher.cc",
+ "local_fetcher.h",
+ "native_runner.h",
+ "network_fetcher.cc",
+ "network_fetcher.h",
+ "query_util.cc",
+ "query_util.h",
+ "shell_impl.cc",
+ "shell_impl.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/public/interfaces/application:application",
+ "//mojo/services/network/public/interfaces",
+ "//url",
+ ]
+ deps = [
+ "//base/third_party/dynamic_annotations",
+ "//crypto:crypto",
+ "//url",
+ "//third_party/mojo/src/mojo/edk/system",
+ "//mojo/environment:chromium",
+ "//third_party/mojo_services/src/content_handler/public/interfaces",
+ "//mojo/shell:native_application_support",
+ "//mojo/shell:switches",
+ ]
+}
+
+test("mojo_application_manager_unittests") {
+ sources = [
+ "application_manager_unittest.cc",
+ "query_util_unittest.cc",
+ ]
+
+ deps = [
+ ":application_manager",
+ ":test_bindings",
+ "//base",
+ "//mojo/application",
+ "//mojo/common",
+ "//third_party/mojo/src/mojo/edk/test:run_all_unittests",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/application",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//testing/gtest",
+ "//url",
+ ]
+}
+
+mojom("test_bindings") {
+ sources = [
+ "test.mojom",
+ ]
+}
diff --git a/mojo/shell/application_manager/application_loader.h b/mojo/shell/application_manager/application_loader.h
new file mode 100644
index 0000000..11feb38
--- /dev/null
+++ b/mojo/shell/application_manager/application_loader.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_APPLICATION_MANAGER_APPLICATION_LOADER_H_
+#define SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_
+
+#include "base/callback.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class Application;
+
+namespace shell {
+
+class ApplicationManager;
+
+// Interface to implement special application loading behavior for a particular
+// URL or scheme.
+class ApplicationLoader {
+ public:
+ virtual ~ApplicationLoader() {}
+
+ virtual void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) = 0;
+
+ protected:
+ ApplicationLoader() {}
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_
diff --git a/mojo/shell/application_manager/application_manager.cc b/mojo/shell/application_manager/application_manager.cc
new file mode 100644
index 0000000..d137fb8
--- /dev/null
+++ b/mojo/shell/application_manager/application_manager.cc
@@ -0,0 +1,523 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/application_manager.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/trace_event/trace_event.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/shell/application_manager/fetcher.h"
+#include "mojo/shell/application_manager/local_fetcher.h"
+#include "mojo/shell/application_manager/network_fetcher.h"
+#include "mojo/shell/application_manager/query_util.h"
+#include "mojo/shell/application_manager/shell_impl.h"
+#include "mojo/shell/switches.h"
+#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+// Used by TestAPI.
+bool has_created_instance = false;
+
+std::vector<std::string> Concatenate(const std::vector<std::string>& v1,
+ const std::vector<std::string>& v2) {
+ if (!v1.size())
+ return v2;
+ if (!v2.size())
+ return v1;
+ std::vector<std::string> result(v1);
+ result.insert(result.end(), v1.begin(), v1.end());
+ return result;
+}
+
+} // namespace
+
+ApplicationManager::Delegate::~Delegate() {
+}
+
+GURL ApplicationManager::Delegate::ResolveURL(const GURL& url) {
+ return url;
+}
+
+GURL ApplicationManager::Delegate::ResolveMappings(const GURL& url) {
+ return url;
+}
+
+class ApplicationManager::ContentHandlerConnection : public ErrorHandler {
+ public:
+ ContentHandlerConnection(ApplicationManager* manager,
+ const GURL& content_handler_url)
+ : manager_(manager), content_handler_url_(content_handler_url) {
+ ServiceProviderPtr services;
+ manager->ConnectToApplication(content_handler_url, GURL(),
+ GetProxy(&services), nullptr,
+ base::Closure());
+ MessagePipe pipe;
+ content_handler_.Bind(pipe.handle0.Pass());
+ services->ConnectToService(ContentHandler::Name_, pipe.handle1.Pass());
+ content_handler_.set_error_handler(this);
+ }
+
+ ContentHandler* content_handler() { return content_handler_.get(); }
+
+ GURL content_handler_url() { return content_handler_url_; }
+
+ private:
+ // ErrorHandler implementation:
+ void OnConnectionError() override { manager_->OnContentHandlerError(this); }
+
+ ApplicationManager* manager_;
+ GURL content_handler_url_;
+ ContentHandlerPtr content_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentHandlerConnection);
+};
+
+// static
+ApplicationManager::TestAPI::TestAPI(ApplicationManager* manager)
+ : manager_(manager) {
+}
+
+ApplicationManager::TestAPI::~TestAPI() {
+}
+
+bool ApplicationManager::TestAPI::HasCreatedInstance() {
+ return has_created_instance;
+}
+
+bool ApplicationManager::TestAPI::HasFactoryForURL(const GURL& url) const {
+ return manager_->identity_to_shell_impl_.find(Identity(url)) !=
+ manager_->identity_to_shell_impl_.end();
+}
+
+ApplicationManager::ApplicationManager(Delegate* delegate)
+ : delegate_(delegate), weak_ptr_factory_(this) {
+}
+
+ApplicationManager::~ApplicationManager() {
+ STLDeleteValues(&url_to_content_handler_);
+ TerminateShellConnections();
+ STLDeleteValues(&url_to_loader_);
+ STLDeleteValues(&scheme_to_loader_);
+}
+
+void ApplicationManager::TerminateShellConnections() {
+ STLDeleteValues(&identity_to_shell_impl_);
+}
+
+void ApplicationManager::ConnectToApplication(
+ const GURL& requested_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end) {
+ ConnectToApplicationWithParameters(
+ requested_url, requestor_url, services.Pass(), exposed_services.Pass(),
+ on_application_end, std::vector<std::string>());
+}
+
+void ApplicationManager::ConnectToApplicationWithParameters(
+ const GURL& requested_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& pre_redirect_parameters) {
+ TRACE_EVENT_INSTANT1(
+ "mojo_shell", "ApplicationManager::ConnectToApplicationWithParameters",
+ TRACE_EVENT_SCOPE_THREAD, "requested_url", requested_url.spec());
+ DCHECK(requested_url.is_valid());
+
+ // We check both the mapped and resolved urls for existing shell_impls because
+ // external applications can be registered for the unresolved mojo:foo urls.
+
+ GURL mapped_url = delegate_->ResolveMappings(requested_url);
+ if (ConnectToRunningApplication(mapped_url, requestor_url, &services,
+ &exposed_services)) {
+ return;
+ }
+
+ GURL resolved_url = delegate_->ResolveURL(mapped_url);
+ if (ConnectToRunningApplication(resolved_url, requestor_url, &services,
+ &exposed_services)) {
+ return;
+ }
+
+ // The application is not running, let's compute the parameters.
+ std::vector<std::string> parameters =
+ Concatenate(pre_redirect_parameters, GetArgsForURL(resolved_url));
+
+ if (ConnectToApplicationWithLoader(mapped_url, requestor_url, &services,
+ &exposed_services, on_application_end,
+ parameters, GetLoaderForURL(mapped_url))) {
+ return;
+ }
+
+ if (ConnectToApplicationWithLoader(
+ resolved_url, requestor_url, &services, &exposed_services,
+ on_application_end, parameters, GetLoaderForURL(resolved_url))) {
+ return;
+ }
+
+ if (ConnectToApplicationWithLoader(resolved_url, requestor_url, &services,
+ &exposed_services, on_application_end,
+ parameters, default_loader_.get())) {
+ return;
+ }
+
+ auto callback = base::Bind(
+ &ApplicationManager::HandleFetchCallback, weak_ptr_factory_.GetWeakPtr(),
+ requestor_url, base::Passed(services.Pass()),
+ base::Passed(exposed_services.Pass()), on_application_end,
+ parameters);
+
+ if (resolved_url.SchemeIsFile()) {
+ new LocalFetcher(
+ resolved_url, GetBaseURLAndQuery(resolved_url, nullptr),
+ base::Bind(callback, NativeApplicationCleanup::DONT_DELETE));
+ return;
+ }
+
+ if (!network_service_)
+ ConnectToService(GURL("mojo:network_service"), &network_service_);
+
+ const NativeApplicationCleanup cleanup =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDontDeleteOnDownload)
+ ? NativeApplicationCleanup::DONT_DELETE
+ : NativeApplicationCleanup::DELETE;
+
+ new NetworkFetcher(disable_cache_, resolved_url, network_service_.get(),
+ base::Bind(callback, cleanup));
+}
+
+bool ApplicationManager::ConnectToRunningApplication(
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider>* services,
+ ServiceProviderPtr* exposed_services) {
+ GURL application_url = GetBaseURLAndQuery(resolved_url, nullptr);
+ ShellImpl* shell_impl = GetShellImpl(application_url);
+ if (!shell_impl)
+ return false;
+
+ ConnectToClient(shell_impl, resolved_url, requestor_url, services->Pass(),
+ exposed_services->Pass());
+ return true;
+}
+
+bool ApplicationManager::ConnectToApplicationWithLoader(
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider>* services,
+ ServiceProviderPtr* exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& parameters,
+ ApplicationLoader* loader) {
+ if (!loader)
+ return false;
+
+ loader->Load(
+ resolved_url,
+ RegisterShell(resolved_url, requestor_url, services->Pass(),
+ exposed_services->Pass(), on_application_end, parameters));
+ return true;
+}
+
+InterfaceRequest<Application> ApplicationManager::RegisterShell(
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& parameters) {
+ Identity app_identity(resolved_url);
+
+ ApplicationPtr application;
+ InterfaceRequest<Application> application_request = GetProxy(&application);
+ ShellImpl* shell =
+ new ShellImpl(application.Pass(), this, app_identity, on_application_end);
+ identity_to_shell_impl_[app_identity] = shell;
+ shell->InitializeApplication(Array<String>::From(parameters));
+ ConnectToClient(shell, resolved_url, requestor_url, services.Pass(),
+ exposed_services.Pass());
+ return application_request.Pass();
+}
+
+ShellImpl* ApplicationManager::GetShellImpl(const GURL& url) {
+ const auto& shell_it = identity_to_shell_impl_.find(Identity(url));
+ if (shell_it != identity_to_shell_impl_.end())
+ return shell_it->second;
+ return nullptr;
+}
+
+void ApplicationManager::ConnectToClient(
+ ShellImpl* shell_impl,
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) {
+ shell_impl->ConnectToClient(resolved_url, requestor_url, services.Pass(),
+ exposed_services.Pass());
+}
+
+void ApplicationManager::HandleFetchCallback(
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& parameters,
+ NativeApplicationCleanup cleanup,
+ scoped_ptr<Fetcher> fetcher) {
+ if (!fetcher) {
+ // Network error. Drop |application_request| to tell requestor.
+ return;
+ }
+
+ GURL redirect_url = fetcher->GetRedirectURL();
+ if (!redirect_url.is_empty()) {
+ // And around we go again... Whee!
+ ConnectToApplicationWithParameters(redirect_url, requestor_url,
+ services.Pass(), exposed_services.Pass(),
+ on_application_end, parameters);
+ return;
+ }
+
+ // We already checked if the application was running before we fetched it, but
+ // it might have started while the fetch was outstanding. We don't want to
+ // have two copies of the app running, so check again.
+ //
+ // Also, it's possible the original URL was redirected to an app that is
+ // already running.
+ if (ConnectToRunningApplication(fetcher->GetURL(), requestor_url, &services,
+ &exposed_services)) {
+ return;
+ }
+
+ InterfaceRequest<Application> request(
+ RegisterShell(fetcher->GetURL(), requestor_url, services.Pass(),
+ exposed_services.Pass(), on_application_end, parameters));
+
+ // If the response begins with a #!mojo <content-handler-url>, use it.
+ GURL content_handler_url;
+ std::string shebang;
+ if (fetcher->PeekContentHandler(&shebang, &content_handler_url)) {
+ LoadWithContentHandler(
+ content_handler_url, request.Pass(),
+ fetcher->AsURLResponse(blocking_pool_,
+ static_cast<int>(shebang.size())));
+ return;
+ }
+
+ MimeTypeToURLMap::iterator iter = mime_type_to_url_.find(fetcher->MimeType());
+ if (iter != mime_type_to_url_.end()) {
+ LoadWithContentHandler(iter->second, request.Pass(),
+ fetcher->AsURLResponse(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.
+ // TODO(vtl): (Maybe this should be done by the factory/runner?)
+
+ GURL base_resolved_url = GetBaseURLAndQuery(fetcher->GetURL(), nullptr);
+ NativeRunnerFactory::Options options;
+ if (url_to_native_options_.find(base_resolved_url) !=
+ url_to_native_options_.end()) {
+ DVLOG(2) << "Applying stored native options to resolved URL "
+ << fetcher->GetURL();
+ options = url_to_native_options_[base_resolved_url];
+ }
+
+ fetcher->AsPath(
+ blocking_pool_,
+ base::Bind(&ApplicationManager::RunNativeApplication,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(request.Pass()),
+ options, cleanup, base::Passed(fetcher.Pass())));
+}
+
+void ApplicationManager::RunNativeApplication(
+ InterfaceRequest<Application> application_request,
+ const NativeRunnerFactory::Options& options,
+ NativeApplicationCleanup cleanup,
+ scoped_ptr<Fetcher> fetcher,
+ const base::FilePath& path,
+ bool path_exists) {
+ // We only passed fetcher to keep it alive. Done with it now.
+ fetcher.reset();
+
+ DCHECK(application_request.is_pending());
+
+ if (!path_exists) {
+ LOG(ERROR) << "Library not started because library path '" << path.value()
+ << "' does not exist.";
+ return;
+ }
+
+ TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path",
+ path.AsUTF8Unsafe());
+ NativeRunner* runner = native_runner_factory_->Create(options).release();
+ native_runners_.push_back(runner);
+ runner->Start(path, cleanup, application_request.Pass(),
+ base::Bind(&ApplicationManager::CleanupRunner,
+ weak_ptr_factory_.GetWeakPtr(), runner));
+}
+
+void ApplicationManager::RegisterExternalApplication(
+ const GURL& url,
+ const std::vector<std::string>& args,
+ ApplicationPtr application) {
+ const auto& args_it = url_to_args_.find(url);
+ if (args_it != url_to_args_.end()) {
+ LOG(WARNING) << "--args-for provided for external application " << url
+ << " <ignored>";
+ }
+ Identity identity(url);
+ ShellImpl* shell_impl =
+ new ShellImpl(application.Pass(), this, identity, base::Closure());
+ identity_to_shell_impl_[identity] = shell_impl;
+ shell_impl->InitializeApplication(Array<String>::From(args));
+}
+
+void ApplicationManager::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 ApplicationManager::LoadWithContentHandler(
+ const GURL& content_handler_url,
+ InterfaceRequest<Application> application_request,
+ URLResponsePtr url_response) {
+ ContentHandlerConnection* connection = nullptr;
+ URLToContentHandlerMap::iterator iter =
+ url_to_content_handler_.find(content_handler_url);
+ if (iter != url_to_content_handler_.end()) {
+ connection = iter->second;
+ } else {
+ connection = new ContentHandlerConnection(this, content_handler_url);
+ url_to_content_handler_[content_handler_url] = connection;
+ }
+
+ connection->content_handler()->StartApplication(application_request.Pass(),
+ url_response.Pass());
+}
+
+void ApplicationManager::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader,
+ const GURL& url) {
+ URLToLoaderMap::iterator it = url_to_loader_.find(url);
+ if (it != url_to_loader_.end())
+ delete it->second;
+ url_to_loader_[url] = loader.release();
+}
+
+void ApplicationManager::SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader> loader,
+ const std::string& scheme) {
+ SchemeToLoaderMap::iterator it = scheme_to_loader_.find(scheme);
+ if (it != scheme_to_loader_.end())
+ delete it->second;
+ scheme_to_loader_[scheme] = loader.release();
+}
+
+void ApplicationManager::SetArgsForURL(const std::vector<std::string>& args,
+ const GURL& url) {
+ url_to_args_[url].insert(url_to_args_[url].end(), args.begin(), args.end());
+ GURL mapped_url = delegate_->ResolveMappings(url);
+ if (mapped_url != url) {
+ url_to_args_[mapped_url].insert(url_to_args_[mapped_url].end(),
+ args.begin(), args.end());
+ }
+ GURL resolved_url = delegate_->ResolveURL(mapped_url);
+ if (resolved_url != mapped_url) {
+ url_to_args_[resolved_url].insert(url_to_args_[resolved_url].end(),
+ args.begin(), args.end());
+ }
+}
+
+void ApplicationManager::SetNativeOptionsForURL(
+ const NativeRunnerFactory::Options& options,
+ const GURL& url) {
+ DCHECK(!url.has_query()); // Precondition.
+ // Apply mappings and resolution to get the resolved URL.
+ GURL resolved_url = delegate_->ResolveURL(delegate_->ResolveMappings(url));
+ DCHECK(!resolved_url.has_query()); // Still shouldn't have query.
+ // TODO(vtl): We should probably also remove/disregard the query string (and
+ // maybe canonicalize in other ways).
+ DVLOG(2) << "Storing native options for resolved URL " << resolved_url
+ << " (original URL " << url << ")";
+ url_to_native_options_[resolved_url] = options;
+}
+
+ApplicationLoader* ApplicationManager::GetLoaderForURL(const GURL& url) {
+ auto url_it = url_to_loader_.find(GetBaseURLAndQuery(url, nullptr));
+ if (url_it != url_to_loader_.end())
+ return url_it->second;
+ auto scheme_it = scheme_to_loader_.find(url.scheme());
+ if (scheme_it != scheme_to_loader_.end())
+ return scheme_it->second;
+ return nullptr;
+}
+
+void ApplicationManager::OnShellImplError(ShellImpl* shell_impl) {
+ // Called from ~ShellImpl, so we do not need to call Destroy here.
+ const Identity identity = shell_impl->identity();
+ base::Closure on_application_end = shell_impl->on_application_end();
+ // Remove the shell.
+ auto it = identity_to_shell_impl_.find(identity);
+ DCHECK(it != identity_to_shell_impl_.end());
+ delete it->second;
+ identity_to_shell_impl_.erase(it);
+ if (!on_application_end.is_null())
+ on_application_end.Run();
+}
+
+void ApplicationManager::OnContentHandlerError(
+ ContentHandlerConnection* content_handler) {
+ // Remove the mapping to the content handler.
+ auto it =
+ url_to_content_handler_.find(content_handler->content_handler_url());
+ DCHECK(it != url_to_content_handler_.end());
+ delete it->second;
+ url_to_content_handler_.erase(it);
+}
+
+ScopedMessagePipeHandle ApplicationManager::ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& interface_name) {
+ ServiceProviderPtr services;
+ ConnectToApplication(application_url, GURL(), GetProxy(&services), nullptr,
+ base::Closure());
+ MessagePipe pipe;
+ services->ConnectToService(interface_name, pipe.handle1.Pass());
+ return pipe.handle0.Pass();
+}
+
+std::vector<std::string> ApplicationManager::GetArgsForURL(const GURL& url) {
+ const auto& args_it = url_to_args_.find(url);
+ if (args_it != url_to_args_.end())
+ return args_it->second;
+ return std::vector<std::string>();
+}
+
+void ApplicationManager::CleanupRunner(NativeRunner* runner) {
+ native_runners_.erase(
+ std::find(native_runners_.begin(), native_runners_.end(), runner));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/application_manager.h b/mojo/shell/application_manager/application_manager.h
new file mode 100644
index 0000000..d0d3a40
--- /dev/null
+++ b/mojo/shell/application_manager/application_manager.h
@@ -0,0 +1,239 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
+#define SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
+
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/services/network/public/interfaces/network_service.mojom.h"
+#include "mojo/shell/application_manager/application_loader.h"
+#include "mojo/shell/application_manager/identity.h"
+#include "mojo/shell/application_manager/native_runner.h"
+#include "mojo/shell/native_application_support.h"
+#include "url/gurl.h"
+
+namespace base {
+class FilePath;
+class SequencedWorkerPool;
+}
+
+namespace mojo {
+namespace shell {
+
+class Fetcher;
+class ShellImpl;
+
+class ApplicationManager {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate();
+ virtual GURL ResolveURL(const GURL& url);
+ virtual GURL ResolveMappings(const GURL& url);
+ };
+
+ // API for testing.
+ class TestAPI {
+ public:
+ explicit TestAPI(ApplicationManager* manager);
+ ~TestAPI();
+
+ // Returns true if the shared instance has been created.
+ static bool HasCreatedInstance();
+ // Returns true if there is a ShellImpl for this URL.
+ bool HasFactoryForURL(const GURL& url) const;
+
+ private:
+ ApplicationManager* manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestAPI);
+ };
+
+ explicit ApplicationManager(Delegate* delegate);
+ ~ApplicationManager();
+
+ // Loads a service if necessary and establishes a new client connection.
+ void ConnectToApplication(const GURL& application_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end);
+
+ template <typename Interface>
+ inline void ConnectToService(const GURL& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ScopedMessagePipeHandle service_handle =
+ ConnectToServiceByName(application_url, Interface::Name_);
+ ptr->Bind(service_handle.Pass());
+ }
+
+ ScopedMessagePipeHandle ConnectToServiceByName(
+ const GURL& application_url,
+ const std::string& interface_name);
+
+ void RegisterContentHandler(const std::string& mime_type,
+ const GURL& content_handler_url);
+
+ void RegisterExternalApplication(const GURL& application_url,
+ const std::vector<std::string>& args,
+ ApplicationPtr application);
+
+ // Sets the default Loader to be used if not overridden by SetLoaderForURL()
+ // or SetLoaderForScheme().
+ void set_default_loader(scoped_ptr<ApplicationLoader> loader) {
+ default_loader_ = loader.Pass();
+ }
+ void set_native_runner_factory(
+ scoped_ptr<NativeRunnerFactory> runner_factory) {
+ native_runner_factory_ = runner_factory.Pass();
+ }
+ void set_blocking_pool(base::SequencedWorkerPool* blocking_pool) {
+ blocking_pool_ = blocking_pool;
+ }
+ void set_disable_cache(bool disable_cache) { disable_cache_ = disable_cache; }
+ // Sets a Loader to be used for a specific url.
+ void SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, const GURL& url);
+ // Sets a Loader to be used for a specific url scheme.
+ void SetLoaderForScheme(scoped_ptr<ApplicationLoader> loader,
+ const std::string& scheme);
+ // These strings will be passed to the Initialize() method when an Application
+ // is instantiated.
+ // TODO(vtl): Maybe we should store/compare resolved URLs, like
+ // SetNativeOptionsForURL() below?
+ void SetArgsForURL(const std::vector<std::string>& args, const GURL& url);
+ // These options will be used in running any native application at |url|
+ // (which shouldn't contain a query string). (|url| will be mapped and
+ // resolved, and any application whose base resolved URL matches it will have
+ // |options| applied.)
+ // TODO(vtl): This may not do what's desired if the resolved URL results in an
+ // HTTP redirect. Really, we want options to be identified with a particular
+ // implementation, maybe via a signed manifest or something like that.
+ void SetNativeOptionsForURL(const NativeRunnerFactory::Options& options,
+ const GURL& url);
+
+ // Destroys all Shell-ends of connections established with Applications.
+ // Applications connected by this ApplicationManager will observe pipe errors
+ // and have a chance to shutdown.
+ void TerminateShellConnections();
+
+ // Removes a ShellImpl when it encounters an error.
+ void OnShellImplError(ShellImpl* shell_impl);
+
+ private:
+ class ContentHandlerConnection;
+
+ typedef std::map<std::string, ApplicationLoader*> SchemeToLoaderMap;
+ typedef std::map<GURL, ApplicationLoader*> URLToLoaderMap;
+ typedef std::map<Identity, ShellImpl*> IdentityToShellImplMap;
+ typedef std::map<GURL, ContentHandlerConnection*> URLToContentHandlerMap;
+ typedef std::map<GURL, std::vector<std::string>> URLToArgsMap;
+ typedef std::map<std::string, GURL> MimeTypeToURLMap;
+ typedef std::map<GURL, NativeRunnerFactory::Options> URLToNativeOptionsMap;
+
+ void ConnectToApplicationWithParameters(
+ const GURL& application_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& pre_redirect_parameters);
+
+ bool ConnectToRunningApplication(const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider>* services,
+ ServiceProviderPtr* exposed_services);
+
+ bool ConnectToApplicationWithLoader(
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider>* services,
+ ServiceProviderPtr* exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& parameters,
+ ApplicationLoader* loader);
+
+ InterfaceRequest<Application> RegisterShell(
+ // The URL after resolution and redirects, including the querystring.
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& parameters);
+
+ ShellImpl* GetShellImpl(const GURL& url);
+
+ void ConnectToClient(ShellImpl* shell_impl,
+ const GURL& resolved_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services);
+
+ void HandleFetchCallback(const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services,
+ const base::Closure& on_application_end,
+ const std::vector<std::string>& parameters,
+ NativeApplicationCleanup cleanup,
+ scoped_ptr<Fetcher> fetcher);
+
+ void RunNativeApplication(InterfaceRequest<Application> application_request,
+ const NativeRunnerFactory::Options& options,
+ NativeApplicationCleanup cleanup,
+ scoped_ptr<Fetcher> fetcher,
+ const base::FilePath& file_path,
+ bool path_exists);
+
+ void LoadWithContentHandler(const GURL& content_handler_url,
+ InterfaceRequest<Application> application_request,
+ URLResponsePtr url_response);
+
+ // Returns the appropriate loader for |url|, or null if there is no loader
+ // configured for the URL.
+ ApplicationLoader* GetLoaderForURL(const GURL& url);
+
+ // Removes a ContentHandler when it encounters an error.
+ void OnContentHandlerError(ContentHandlerConnection* content_handler);
+
+ // Returns the arguments for the given url.
+ std::vector<std::string> GetArgsForURL(const GURL& url);
+
+ void CleanupRunner(NativeRunner* runner);
+
+ Delegate* const delegate_;
+ // Loader management.
+ // Loaders are chosen in the order they are listed here.
+ URLToLoaderMap url_to_loader_;
+ SchemeToLoaderMap scheme_to_loader_;
+ scoped_ptr<ApplicationLoader> default_loader_;
+ scoped_ptr<NativeRunnerFactory> native_runner_factory_;
+
+ IdentityToShellImplMap identity_to_shell_impl_;
+ URLToContentHandlerMap url_to_content_handler_;
+ URLToArgsMap url_to_args_;
+ // Note: The keys are URLs after mapping and resolving.
+ URLToNativeOptionsMap url_to_native_options_;
+
+ base::SequencedWorkerPool* blocking_pool_;
+ NetworkServicePtr network_service_;
+ MimeTypeToURLMap mime_type_to_url_;
+ ScopedVector<NativeRunner> native_runners_;
+ bool disable_cache_;
+ base::WeakPtrFactory<ApplicationManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ApplicationManager);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_
diff --git a/mojo/shell/application_manager/application_manager_unittest.cc b/mojo/shell/application_manager/application_manager_unittest.cc
new file mode 100644
index 0000000..ed16d15
--- /dev/null
+++ b/mojo/shell/application_manager/application_manager_unittest.cc
@@ -0,0 +1,829 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/shell/application_manager/application_loader.h"
+#include "mojo/shell/application_manager/application_manager.h"
+#include "mojo/shell/application_manager/test.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+const char kTestURLString[] = "test:testService";
+const char kTestAURLString[] = "test:TestA";
+const char kTestBURLString[] = "test:TestB";
+
+struct TestContext {
+ TestContext() : num_impls(0), num_loader_deletes(0) {}
+ std::string last_test_string;
+ int num_impls;
+ int num_loader_deletes;
+};
+
+void QuitClosure(bool* value) {
+ *value = true;
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+class QuitMessageLoopErrorHandler : public ErrorHandler {
+ public:
+ QuitMessageLoopErrorHandler() {}
+ ~QuitMessageLoopErrorHandler() override {}
+
+ // |ErrorHandler| implementation:
+ void OnConnectionError() override {
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler);
+};
+
+class TestServiceImpl : public TestService {
+ public:
+ TestServiceImpl(TestContext* context, InterfaceRequest<TestService> request)
+ : context_(context), binding_(this, request.Pass()) {
+ ++context_->num_impls;
+ }
+
+ ~TestServiceImpl() override {
+ --context_->num_impls;
+ if (!base::MessageLoop::current()->is_running())
+ return;
+ base::MessageLoop::current()->Quit();
+ }
+
+ // TestService implementation:
+ void Test(const String& test_string,
+ const Callback<void()>& callback) override {
+ context_->last_test_string = test_string;
+ callback.Run();
+ }
+
+ private:
+ TestContext* context_;
+ StrongBinding<TestService> binding_;
+};
+
+class TestClient {
+ public:
+ explicit TestClient(TestServicePtr service)
+ : service_(service.Pass()), quit_after_ack_(false) {}
+
+ void AckTest() {
+ if (quit_after_ack_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ void Test(const std::string& test_string) {
+ quit_after_ack_ = true;
+ service_->Test(test_string,
+ base::Bind(&TestClient::AckTest, base::Unretained(this)));
+ }
+
+ private:
+ TestServicePtr service_;
+ bool quit_after_ack_;
+ DISALLOW_COPY_AND_ASSIGN(TestClient);
+};
+
+class TestApplicationLoader : public ApplicationLoader,
+ public ApplicationDelegate,
+ public InterfaceFactory<TestService> {
+ public:
+ TestApplicationLoader() : context_(nullptr), num_loads_(0) {}
+
+ ~TestApplicationLoader() override {
+ if (context_)
+ ++context_->num_loader_deletes;
+ test_app_.reset();
+ }
+
+ void set_context(TestContext* context) { context_ = context; }
+ int num_loads() const { return num_loads_; }
+ const std::vector<std::string>& GetArgs() const { return test_app_->args(); }
+
+ private:
+ // ApplicationLoader implementation.
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) override {
+ ++num_loads_;
+ test_app_.reset(new ApplicationImpl(this, application_request.Pass()));
+ }
+
+ // ApplicationDelegate implementation.
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory implementation.
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestService> request) override {
+ new TestServiceImpl(context_, request.Pass());
+ }
+
+ scoped_ptr<ApplicationImpl> test_app_;
+ TestContext* context_;
+ int num_loads_;
+ DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader);
+};
+
+class ClosingApplicationLoader : public ApplicationLoader {
+ private:
+ // ApplicationLoader implementation.
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) override {}
+};
+
+class TesterContext {
+ public:
+ explicit TesterContext(base::MessageLoop* loop)
+ : num_b_calls_(0),
+ num_c_calls_(0),
+ num_a_deletes_(0),
+ num_b_deletes_(0),
+ num_c_deletes_(0),
+ tester_called_quit_(false),
+ a_called_quit_(false),
+ loop_(loop) {}
+
+ void IncrementNumBCalls() {
+ base::AutoLock lock(lock_);
+ num_b_calls_++;
+ }
+
+ void IncrementNumCCalls() {
+ base::AutoLock lock(lock_);
+ num_c_calls_++;
+ }
+
+ void IncrementNumADeletes() {
+ base::AutoLock lock(lock_);
+ num_a_deletes_++;
+ }
+
+ void IncrementNumBDeletes() {
+ base::AutoLock lock(lock_);
+ num_b_deletes_++;
+ }
+
+ void IncrementNumCDeletes() {
+ base::AutoLock lock(lock_);
+ num_c_deletes_++;
+ }
+
+ void set_tester_called_quit() {
+ base::AutoLock lock(lock_);
+ tester_called_quit_ = true;
+ }
+
+ void set_a_called_quit() {
+ base::AutoLock lock(lock_);
+ a_called_quit_ = true;
+ }
+
+ int num_b_calls() {
+ base::AutoLock lock(lock_);
+ return num_b_calls_;
+ }
+ int num_c_calls() {
+ base::AutoLock lock(lock_);
+ return num_c_calls_;
+ }
+ int num_a_deletes() {
+ base::AutoLock lock(lock_);
+ return num_a_deletes_;
+ }
+ int num_b_deletes() {
+ base::AutoLock lock(lock_);
+ return num_b_deletes_;
+ }
+ int num_c_deletes() {
+ base::AutoLock lock(lock_);
+ return num_c_deletes_;
+ }
+ bool tester_called_quit() {
+ base::AutoLock lock(lock_);
+ return tester_called_quit_;
+ }
+ bool a_called_quit() {
+ base::AutoLock lock(lock_);
+ return a_called_quit_;
+ }
+
+ void QuitSoon() {
+ loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
+ }
+
+ private:
+ // lock_ protects all members except for loop_ which must be unchanged for the
+ // lifetime of this class.
+ base::Lock lock_;
+ int num_b_calls_;
+ int num_c_calls_;
+ int num_a_deletes_;
+ int num_b_deletes_;
+ int num_c_deletes_;
+ bool tester_called_quit_;
+ bool a_called_quit_;
+
+ base::MessageLoop* loop_;
+};
+
+// Used to test that the requestor url will be correctly passed.
+class TestAImpl : public TestA {
+ public:
+ TestAImpl(ApplicationImpl* app_impl,
+ TesterContext* test_context,
+ InterfaceRequest<TestA> request)
+ : test_context_(test_context), binding_(this, request.Pass()) {
+ app_impl->ConnectToApplication(kTestBURLString)->ConnectToService(&b_);
+ }
+
+ ~TestAImpl() override {
+ test_context_->IncrementNumADeletes();
+ if (base::MessageLoop::current()->is_running())
+ Quit();
+ }
+
+ private:
+ void CallB() override {
+ b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
+ }
+
+ void CallCFromB() override {
+ b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
+ }
+
+ void Quit() {
+ base::MessageLoop::current()->Quit();
+ test_context_->set_a_called_quit();
+ test_context_->QuitSoon();
+ }
+
+ TesterContext* test_context_;
+ TestBPtr b_;
+ StrongBinding<TestA> binding_;
+};
+
+class TestBImpl : public TestB {
+ public:
+ TestBImpl(ApplicationConnection* connection,
+ TesterContext* test_context,
+ InterfaceRequest<TestB> request)
+ : test_context_(test_context), binding_(this, request.Pass()) {
+ connection->ConnectToService(&c_);
+ }
+
+ ~TestBImpl() override {
+ test_context_->IncrementNumBDeletes();
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+ test_context_->QuitSoon();
+ }
+
+ private:
+ void B(const Callback<void()>& callback) override {
+ test_context_->IncrementNumBCalls();
+ callback.Run();
+ }
+
+ void CallC(const Callback<void()>& callback) override {
+ test_context_->IncrementNumBCalls();
+ c_->C(callback);
+ }
+
+ TesterContext* test_context_;
+ TestCPtr c_;
+ StrongBinding<TestB> binding_;
+};
+
+class TestCImpl : public TestC {
+ public:
+ TestCImpl(ApplicationConnection* connection,
+ TesterContext* test_context,
+ InterfaceRequest<TestC> request)
+ : test_context_(test_context), binding_(this, request.Pass()) {}
+
+ ~TestCImpl() override { test_context_->IncrementNumCDeletes(); }
+
+ private:
+ void C(const Callback<void()>& callback) override {
+ test_context_->IncrementNumCCalls();
+ callback.Run();
+ }
+
+ TesterContext* test_context_;
+ StrongBinding<TestC> binding_;
+};
+
+class Tester : public ApplicationDelegate,
+ public ApplicationLoader,
+ public InterfaceFactory<TestA>,
+ public InterfaceFactory<TestB>,
+ public InterfaceFactory<TestC> {
+ public:
+ Tester(TesterContext* context, const std::string& requestor_url)
+ : context_(context), requestor_url_(requestor_url) {}
+ ~Tester() override {}
+
+ private:
+ void Load(const GURL& url,
+ InterfaceRequest<Application> application_request) override {
+ app_.reset(new ApplicationImpl(this, application_request.Pass()));
+ }
+
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ if (!requestor_url_.empty() &&
+ requestor_url_ != connection->GetRemoteApplicationURL()) {
+ context_->set_tester_called_quit();
+ context_->QuitSoon();
+ base::MessageLoop::current()->Quit();
+ return false;
+ }
+ // If we're coming from A, then add B, otherwise A.
+ if (connection->GetRemoteApplicationURL() == kTestAURLString)
+ connection->AddService<TestB>(this);
+ else
+ connection->AddService<TestA>(this);
+ return true;
+ }
+
+ bool ConfigureOutgoingConnection(ApplicationConnection* connection) override {
+ // If we're connecting to B, then add C.
+ if (connection->GetRemoteApplicationURL() == kTestBURLString)
+ connection->AddService<TestC>(this);
+ return true;
+ }
+
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestA> request) override {
+ a_bindings_.push_back(new TestAImpl(app_.get(), context_, request.Pass()));
+ }
+
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestB> request) override {
+ new TestBImpl(connection, context_, request.Pass());
+ }
+
+ void Create(ApplicationConnection* connection,
+ InterfaceRequest<TestC> request) override {
+ new TestCImpl(connection, context_, request.Pass());
+ }
+
+ TesterContext* context_;
+ scoped_ptr<ApplicationImpl> app_;
+ std::string requestor_url_;
+ ScopedVector<TestAImpl> a_bindings_;
+};
+
+class TestDelegate : public ApplicationManager::Delegate {
+ public:
+ void AddMapping(const GURL& from, const GURL& to) { mappings_[from] = to; }
+
+ // ApplicationManager::Delegate
+ GURL ResolveMappings(const GURL& url) override {
+ auto it = mappings_.find(url);
+ if (it != mappings_.end())
+ return it->second;
+ return url;
+ }
+
+ // ApplicationManager::Delegate
+ GURL ResolveURL(const GURL& url) override {
+ GURL mapped_url = ResolveMappings(url);
+ // The shell automatically map mojo URLs.
+ if (mapped_url.scheme() == "mojo") {
+ url::Replacements<char> replacements;
+ replacements.SetScheme("file", url::Component(0, 4));
+ mapped_url = mapped_url.ReplaceComponents(replacements);
+ }
+ return mapped_url;
+ }
+
+ private:
+ std::map<GURL, GURL> mappings_;
+};
+
+class TestExternal : public ApplicationDelegate {
+ public:
+ TestExternal() : configure_incoming_connection_called_(false) {}
+
+ void Initialize(ApplicationImpl* app) override {
+ initialize_args_ = app->args();
+ base::MessageLoop::current()->Quit();
+ }
+
+ bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
+ configure_incoming_connection_called_ = true;
+ base::MessageLoop::current()->Quit();
+ return true;
+ }
+
+ const std::vector<std::string>& initialize_args() const {
+ return initialize_args_;
+ }
+
+ bool configure_incoming_connection_called() const {
+ return configure_incoming_connection_called_;
+ }
+
+ private:
+ std::vector<std::string> initialize_args_;
+ bool configure_incoming_connection_called_;
+};
+
+class ApplicationManagerTest : public testing::Test {
+ public:
+ ApplicationManagerTest() : tester_context_(&loop_) {}
+
+ ~ApplicationManagerTest() override {}
+
+ void SetUp() override {
+ application_manager_.reset(new ApplicationManager(&test_delegate_));
+ test_loader_ = new TestApplicationLoader;
+ test_loader_->set_context(&context_);
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(test_loader_));
+
+ TestServicePtr service_proxy;
+ application_manager_->ConnectToService(GURL(kTestURLString),
+ &service_proxy);
+ test_client_.reset(new TestClient(service_proxy.Pass()));
+ }
+
+ void TearDown() override {
+ test_client_.reset();
+ application_manager_.reset();
+ }
+
+ void AddLoaderForURL(const GURL& url, const std::string& requestor_url) {
+ application_manager_->SetLoaderForURL(
+ make_scoped_ptr(new Tester(&tester_context_, requestor_url)), url);
+ }
+
+ bool HasFactoryForTestURL() {
+ ApplicationManager::TestAPI manager_test_api(application_manager_.get());
+ return manager_test_api.HasFactoryForURL(GURL(kTestURLString));
+ }
+
+ protected:
+ base::ShadowingAtExitManager at_exit_;
+ TestDelegate test_delegate_;
+ TestApplicationLoader* test_loader_;
+ TesterContext tester_context_;
+ TestContext context_;
+ base::MessageLoop loop_;
+ scoped_ptr<TestClient> test_client_;
+ scoped_ptr<ApplicationManager> application_manager_;
+ DISALLOW_COPY_AND_ASSIGN(ApplicationManagerTest);
+};
+
+TEST_F(ApplicationManagerTest, Basic) {
+ test_client_->Test("test");
+ loop_.Run();
+ EXPECT_EQ(std::string("test"), context_.last_test_string);
+}
+
+// Confirm that no arguments are sent to an application by default.
+TEST_F(ApplicationManagerTest, NoArgs) {
+ ApplicationManager am(&test_delegate_);
+ GURL test_url("test:test");
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context_);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClient test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ EXPECT_EQ(0U, app_args.size());
+}
+
+// Confirm that arguments are sent to an application.
+TEST_F(ApplicationManagerTest, Args) {
+ ApplicationManager am(&test_delegate_);
+ GURL test_url("test:test");
+ std::vector<std::string> args;
+ args.push_back("test_arg1");
+ args.push_back("test_arg2");
+ am.SetArgsForURL(args, test_url);
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context_);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClient test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ ASSERT_EQ(args.size(), app_args.size());
+ EXPECT_EQ(args[0], app_args[0]);
+ EXPECT_EQ(args[1], app_args[1]);
+}
+
+// Confirm that arguments are aggregated through mappings.
+TEST_F(ApplicationManagerTest, ArgsAndMapping) {
+ ApplicationManager am(&test_delegate_);
+ GURL test_url("test:test");
+ GURL test_url2("test:test2");
+ test_delegate_.AddMapping(test_url, test_url2);
+ std::vector<std::string> args;
+ args.push_back("test_arg1");
+ args.push_back("test_arg2");
+ am.SetArgsForURL(args, test_url);
+ std::vector<std::string> args2;
+ args2.push_back("test_arg3");
+ args2.push_back("test_arg4");
+ am.SetArgsForURL(args2, test_url2);
+ TestApplicationLoader* loader = new TestApplicationLoader;
+ loader->set_context(&context_);
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url2);
+ {
+ // Connext to the mapped url
+ TestServicePtr test_service;
+ am.ConnectToService(test_url, &test_service);
+ TestClient test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ ASSERT_EQ(args.size() + args2.size(), app_args.size());
+ EXPECT_EQ(args[0], app_args[0]);
+ EXPECT_EQ(args[1], app_args[1]);
+ EXPECT_EQ(args2[0], app_args[2]);
+ EXPECT_EQ(args2[1], app_args[3]);
+ }
+ {
+ // Connext to the target url
+ TestServicePtr test_service;
+ am.ConnectToService(test_url2, &test_service);
+ TestClient test_client(test_service.Pass());
+ test_client.Test("test");
+ loop_.Run();
+ std::vector<std::string> app_args = loader->GetArgs();
+ ASSERT_EQ(args.size() + args2.size(), app_args.size());
+ EXPECT_EQ(args[0], app_args[0]);
+ EXPECT_EQ(args[1], app_args[1]);
+ EXPECT_EQ(args2[0], app_args[2]);
+ EXPECT_EQ(args2[1], app_args[3]);
+ }
+}
+
+TEST_F(ApplicationManagerTest, ClientError) {
+ test_client_->Test("test");
+ EXPECT_TRUE(HasFactoryForTestURL());
+ loop_.Run();
+ EXPECT_EQ(1, context_.num_impls);
+ test_client_.reset();
+ loop_.Run();
+ EXPECT_EQ(0, context_.num_impls);
+ EXPECT_TRUE(HasFactoryForTestURL());
+}
+
+TEST_F(ApplicationManagerTest, Deletes) {
+ {
+ ApplicationManager am(&test_delegate_);
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ default_loader->set_context(&context_);
+ TestApplicationLoader* url_loader1 = new TestApplicationLoader;
+ TestApplicationLoader* url_loader2 = new TestApplicationLoader;
+ url_loader1->set_context(&context_);
+ url_loader2->set_context(&context_);
+ TestApplicationLoader* scheme_loader1 = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader2 = new TestApplicationLoader;
+ scheme_loader1->set_context(&context_);
+ scheme_loader2->set_context(&context_);
+ am.set_default_loader(scoped_ptr<ApplicationLoader>(default_loader));
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader1),
+ GURL("test:test1"));
+ am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader2),
+ GURL("test:test1"));
+ am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader1),
+ "test");
+ am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader2),
+ "test");
+ }
+ EXPECT_EQ(5, context_.num_loader_deletes);
+}
+
+// Confirm that both urls and schemes can have their loaders explicitly set.
+TEST_F(ApplicationManagerTest, SetLoaders) {
+ TestApplicationLoader* default_loader = new TestApplicationLoader;
+ TestApplicationLoader* url_loader = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader = new TestApplicationLoader;
+ application_manager_->set_default_loader(
+ scoped_ptr<ApplicationLoader>(default_loader));
+ application_manager_->SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1"));
+ application_manager_->SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader>(scheme_loader), "test");
+
+ // test::test1 should go to url_loader.
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL("test:test1"), &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(0, scheme_loader->num_loads());
+ EXPECT_EQ(0, default_loader->num_loads());
+
+ // test::test2 should go to scheme loader.
+ application_manager_->ConnectToService(GURL("test:test2"), &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(1, scheme_loader->num_loads());
+ EXPECT_EQ(0, default_loader->num_loads());
+
+ // http::test1 should go to default loader.
+ application_manager_->ConnectToService(GURL("http:test1"), &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(1, scheme_loader->num_loads());
+ EXPECT_EQ(1, default_loader->num_loads());
+}
+
+// Confirm that the url of a service is correctly passed to another service that
+// it loads.
+TEST_F(ApplicationManagerTest, ACallB) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only a can load b.
+ AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallB();
+ loop_.Run();
+ EXPECT_EQ(1, tester_context_.num_b_calls());
+ EXPECT_TRUE(tester_context_.a_called_quit());
+}
+
+// A calls B which calls C.
+TEST_F(ApplicationManagerTest, BCallC) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only a can load b.
+ AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallCFromB();
+ loop_.Run();
+
+ EXPECT_EQ(1, tester_context_.num_b_calls());
+ EXPECT_EQ(1, tester_context_.num_c_calls());
+ EXPECT_TRUE(tester_context_.a_called_quit());
+}
+
+// Confirm that a service impl will be deleted if the app that connected to
+// it goes away.
+TEST_F(ApplicationManagerTest, BDeleted) {
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+ AddLoaderForURL(GURL(kTestBURLString), std::string());
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+
+ a->CallB();
+ loop_.Run();
+
+ // Kills the a app.
+ application_manager_->SetLoaderForURL(scoped_ptr<ApplicationLoader>(),
+ GURL(kTestAURLString));
+ loop_.Run();
+
+ EXPECT_EQ(1, tester_context_.num_b_deletes());
+}
+
+// Confirm that the url of a service is correctly passed to another service that
+// it loads, and that it can be rejected.
+TEST_F(ApplicationManagerTest, ANoLoadB) {
+ // Any url can load a.
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // Only c can load b, so this will fail.
+ AddLoaderForURL(GURL(kTestBURLString), "test:TestC");
+
+ TestAPtr a;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &a);
+ a->CallB();
+ loop_.Run();
+ EXPECT_EQ(0, tester_context_.num_b_calls());
+
+ EXPECT_FALSE(tester_context_.a_called_quit());
+ EXPECT_TRUE(tester_context_.tester_called_quit());
+}
+
+TEST_F(ApplicationManagerTest, NoServiceNoLoad) {
+ AddLoaderForURL(GURL(kTestAURLString), std::string());
+
+ // There is no TestC service implementation registered with
+ // ApplicationManager, so this cannot succeed (but also shouldn't crash).
+ TestCPtr c;
+ application_manager_->ConnectToService(GURL(kTestAURLString), &c);
+ QuitMessageLoopErrorHandler quitter;
+ c.set_error_handler(&quitter);
+
+ loop_.Run();
+ EXPECT_TRUE(c.encountered_error());
+}
+
+TEST_F(ApplicationManagerTest, MappedURLsShouldNotCauseDuplicateLoad) {
+ test_delegate_.AddMapping(GURL("foo:foo2"), GURL("foo:foo"));
+ // 1 because ApplicationManagerTest connects once at startup.
+ EXPECT_EQ(1, test_loader_->num_loads());
+
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL("foo:foo"), &test_service);
+ EXPECT_EQ(2, test_loader_->num_loads());
+
+ TestServicePtr test_service2;
+ application_manager_->ConnectToService(GURL("foo:foo2"), &test_service2);
+ EXPECT_EQ(2, test_loader_->num_loads());
+
+ TestServicePtr test_service3;
+ application_manager_->ConnectToService(GURL("bar:bar"), &test_service2);
+ EXPECT_EQ(3, test_loader_->num_loads());
+}
+
+TEST_F(ApplicationManagerTest, MappedURLsShouldWorkWithLoaders) {
+ TestApplicationLoader* custom_loader = new TestApplicationLoader;
+ TestContext context;
+ custom_loader->set_context(&context);
+ application_manager_->SetLoaderForURL(make_scoped_ptr(custom_loader),
+ GURL("mojo:foo"));
+ test_delegate_.AddMapping(GURL("mojo:foo2"), GURL("mojo:foo"));
+
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL("mojo:foo2"), &test_service);
+ EXPECT_EQ(1, custom_loader->num_loads());
+ custom_loader->set_context(nullptr);
+}
+
+TEST_F(ApplicationManagerTest, ExternalApp) {
+ ApplicationPtr application;
+ TestExternal external;
+ std::vector<std::string> args;
+ args.push_back("test");
+ ApplicationImpl app(&external, GetProxy(&application));
+ application_manager_->RegisterExternalApplication(GURL("mojo:test"), args,
+ application.Pass());
+ loop_.Run();
+ EXPECT_EQ(args, external.initialize_args());
+ application_manager_->ConnectToServiceByName(GURL("mojo:test"),
+ std::string());
+ loop_.Run();
+ EXPECT_TRUE(external.configure_incoming_connection_called());
+};
+
+TEST_F(ApplicationManagerTest, TestQueryWithLoaders) {
+ TestApplicationLoader* url_loader = new TestApplicationLoader;
+ TestApplicationLoader* scheme_loader = new TestApplicationLoader;
+ application_manager_->SetLoaderForURL(
+ scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1"));
+ application_manager_->SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader>(scheme_loader), "test");
+
+ // test::test1 should go to url_loader.
+ TestServicePtr test_service;
+ application_manager_->ConnectToService(GURL("test:test1?foo=bar"),
+ &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(0, scheme_loader->num_loads());
+
+ // test::test2 should go to scheme loader.
+ application_manager_->ConnectToService(GURL("test:test2?foo=bar"),
+ &test_service);
+ EXPECT_EQ(1, url_loader->num_loads());
+ EXPECT_EQ(1, scheme_loader->num_loads());
+}
+
+TEST_F(ApplicationManagerTest, TestEndApplicationClosure) {
+ ClosingApplicationLoader* loader = new ClosingApplicationLoader();
+ application_manager_->SetLoaderForScheme(
+ scoped_ptr<ApplicationLoader>(loader), "test");
+
+ bool called = false;
+ application_manager_->ConnectToApplication(
+ GURL("test:test"), GURL(), nullptr, nullptr,
+ base::Bind(&QuitClosure, base::Unretained(&called)));
+ loop_.Run();
+ EXPECT_TRUE(called);
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/data_pipe_peek.cc b/mojo/shell/application_manager/data_pipe_peek.cc
new file mode 100644
index 0000000..9109b51
--- /dev/null
+++ b/mojo/shell/application_manager/data_pipe_peek.cc
@@ -0,0 +1,160 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/data_pipe_peek.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/macros.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),
+ last_number_bytes_read_(0) {}
+
+ bool MaybeSleep(uint32_t 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, kMaxSleepMicros);
+ base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(sleep_time));
+ return true;
+ }
+
+ private:
+ static const MojoTimeTicks kMaxSleepMicros = 1000 * 10; // 10 ms
+
+ const MojoTimeTicks deadline_; // 0 => MOJO_DEADLINE_INDEFINITE
+ uint32_t last_number_bytes_read_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeekSleeper);
+};
+
+const MojoTimeTicks PeekSleeper::kMaxSleepMicros;
+
+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, nullptr);
+ }
+ } while (result == MOJO_RESULT_OK);
+
+ return false;
+}
+
+PeekStatus PeekLine(size_t max_line_length,
+ const void* buffer,
+ uint32_t 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_t 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/mojo/shell/application_manager/data_pipe_peek.h b/mojo/shell/application_manager/data_pipe_peek.h
new file mode 100644
index 0000000..5ffbc5b
--- /dev/null
+++ b/mojo/shell/application_manager/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_APPLICATION_MANAGER_DATA_PIPE_SEEK_H_
+#define SHELL_APPLICATION_MANAGER_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_APPLICATION_MANAGER_DATA_PIPE_SEEK_H_
diff --git a/mojo/shell/application_manager/fetcher.cc b/mojo/shell/application_manager/fetcher.cc
new file mode 100644
index 0000000..78fd125
--- /dev/null
+++ b/mojo/shell/application_manager/fetcher.cc
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/fetcher.h"
+
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+const char Fetcher::kMojoMagic[] = "#!mojo ";
+const size_t Fetcher::kMaxShebangLength = 2048;
+
+Fetcher::Fetcher(const FetchCallback& loader_callback)
+ : loader_callback_(loader_callback) {
+}
+
+Fetcher::~Fetcher() {
+}
+
+bool Fetcher::PeekContentHandler(std::string* mojo_shebang,
+ GURL* mojo_content_handler_url) {
+ // TODO(aa): I guess this should just go in ApplicationManager now.
+ std::string shebang;
+ if (HasMojoMagic() && PeekFirstLine(&shebang)) {
+ GURL url(shebang.substr(arraysize(kMojoMagic) - 1, std::string::npos));
+ if (url.is_valid()) {
+ *mojo_shebang = shebang;
+ *mojo_content_handler_url = url;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/fetcher.h b/mojo/shell/application_manager/fetcher.h
new file mode 100644
index 0000000..2257f75
--- /dev/null
+++ b/mojo/shell/application_manager/fetcher.h
@@ -0,0 +1,76 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_FETCHER_H_
+#define SHELL_APPLICATION_MANAGER_FETCHER_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+
+#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class TaskRunner;
+}
+
+namespace mojo {
+namespace shell {
+
+// Fetcher abstracts getting an application by either file or http[s] URL.
+//
+// Although it is possible to use the Network implementation for http[s] URLs
+// (because the underlying net library knows how to handle them), it is
+// extremely slow because network responses must be copied to disk in order to
+// get a file handle we can use with dlopen.
+//
+// Until this is solved, we use two different implementations so that
+// performance isn't completely absymal.
+class Fetcher {
+ public:
+ // The param will be null in the case where the content could not be fetched.
+ // Reasons include:
+ // - network error
+ // - 4x or 5x HTTP errors
+ typedef base::Callback<void(scoped_ptr<Fetcher>)> FetchCallback;
+
+ Fetcher(const FetchCallback& fetch_callback);
+ virtual ~Fetcher();
+
+ // Returns the original URL that was fetched.
+ virtual const GURL& GetURL() const = 0;
+
+ // If the fetch resulted in a redirect, this returns the final URL after all
+ // redirects. Otherwise, it returns an empty URL.
+ virtual GURL GetRedirectURL() const = 0;
+
+ 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;
+
+ bool PeekContentHandler(std::string* mojo_shebang,
+ GURL* mojo_content_handler_url);
+
+ protected:
+ static const char kMojoMagic[];
+ static const size_t kMaxShebangLength;
+
+ FetchCallback loader_callback_;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_FETCHER_H_
diff --git a/mojo/shell/application_manager/identity.cc b/mojo/shell/application_manager/identity.cc
new file mode 100644
index 0000000..c54f5d8
--- /dev/null
+++ b/mojo/shell/application_manager/identity.cc
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/identity.h"
+
+#include "mojo/shell/application_manager/query_util.h"
+
+namespace mojo {
+namespace shell {
+
+Identity::Identity(const GURL& url, const std::string& qualifier)
+ : url(GetBaseURLAndQuery(url, nullptr)), qualifier(qualifier) {
+}
+
+// explicit
+Identity::Identity(const GURL& base_url)
+ : url(GetBaseURLAndQuery(base_url, nullptr)), qualifier(url.spec()) {
+}
+
+bool Identity::operator<(const Identity& other) const {
+ if (url != other.url)
+ return url < other.url;
+ return qualifier < other.qualifier;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/identity.h b/mojo/shell/application_manager/identity.h
new file mode 100644
index 0000000..3d7e1d6
--- /dev/null
+++ b/mojo/shell/application_manager/identity.h
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_IDENTITY_H_
+#define SHELL_APPLICATION_MANAGER_IDENTITY_H_
+
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+/**
+ * Represents the identity of an application. |url| is the url of the
+ * application. |qualifier| is a string that allows to tie a specific instance
+ * of an application to another. It is used by content handlers that need to be
+ * run in the context of another application.
+ */
+struct Identity {
+ Identity(const GURL& url, const std::string& qualifier);
+ explicit Identity(const GURL& url);
+ bool operator<(const Identity& other) const;
+
+ const GURL url;
+ const std::string qualifier;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_IDENTITY_H_
diff --git a/mojo/shell/application_manager/local_fetcher.cc b/mojo/shell/application_manager/local_fetcher.cc
new file mode 100644
index 0000000..a8b889d
--- /dev/null
+++ b/mojo/shell/application_manager/local_fetcher.cc
@@ -0,0 +1,107 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/local_fetcher.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/common/url_type_converters.h"
+#include "url/url_util.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+void IgnoreResult(bool result) {
+}
+
+} // namespace
+
+// A loader for local files.
+LocalFetcher::LocalFetcher(const GURL& url,
+ const GURL& url_without_query,
+ const FetchCallback& loader_callback)
+ : Fetcher(loader_callback), url_(url), path_(UrlToFile(url_without_query)) {
+ TRACE_EVENT1("mojo_shell", "LocalFetcher::LocalFetcher", "url", url.spec());
+ loader_callback_.Run(make_scoped_ptr(this));
+}
+
+base::FilePath LocalFetcher::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;
+}
+
+const GURL& LocalFetcher::GetURL() const {
+ return url_;
+}
+
+GURL LocalFetcher::GetRedirectURL() const {
+ return GURL::EmptyGURL();
+}
+
+URLResponsePtr LocalFetcher::AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) {
+ 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 LocalFetcher::AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) {
+ // Async for consistency with network case.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
+}
+
+std::string LocalFetcher::MimeType() {
+ return "";
+}
+
+bool LocalFetcher::HasMojoMagic() {
+ std::string magic;
+ ReadFileToString(path_, &magic, strlen(kMojoMagic));
+ return magic == kMojoMagic;
+}
+
+bool LocalFetcher::PeekFirstLine(std::string* line) {
+ 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;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/local_fetcher.h b/mojo/shell/application_manager/local_fetcher.h
new file mode 100644
index 0000000..843108f
--- /dev/null
+++ b/mojo/shell/application_manager/local_fetcher.h
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_LOCAL_FETCHER_H_
+#define SHELL_APPLICATION_MANAGER_LOCAL_FETCHER_H_
+
+#include "base/files/file_path.h"
+#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "mojo/shell/application_manager/fetcher.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+// Implements Fetcher for file:// URLs.
+class LocalFetcher : public Fetcher {
+ public:
+ LocalFetcher(const GURL& url,
+ const GURL& url_without_query,
+ const FetchCallback& loader_callback);
+
+ private:
+ static base::FilePath UrlToFile(const GURL& url);
+
+ const GURL& GetURL() const override;
+ GURL GetRedirectURL() const override;
+
+ URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) override;
+
+ void AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) override;
+
+ std::string MimeType() override;
+
+ bool HasMojoMagic() override;
+
+ bool PeekFirstLine(std::string* line) override;
+
+ GURL url_;
+ base::FilePath path_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFetcher);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_LOCAL_FETCHER_H_
diff --git a/mojo/shell/application_manager/native_runner.h b/mojo/shell/application_manager/native_runner.h
new file mode 100644
index 0000000..bc62b77
--- /dev/null
+++ b/mojo/shell/application_manager/native_runner.h
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_NATIVE_RUNNER_H_
+#define SHELL_APPLICATION_MANAGER_NATIVE_RUNNER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/shell/native_application_support.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace mojo {
+namespace shell {
+
+// ApplicationManager requires implementations of NativeRunner and
+// NativeRunnerFactory to run native applications.
+class NativeRunner {
+ public:
+ virtual ~NativeRunner() {}
+
+ // Loads the app in the file at |app_path| and runs it on some other
+ // thread/process. If |cleanup| is |DELETE|, this takes ownership of the file.
+ // |app_completed_callback| is posted (to the thread on which |Start()| was
+ // called) after |MojoMain()| completes.
+ // TODO(vtl): |app_path| and |cleanup| should probably be moved to the
+ // factory's Create(). Rationale: The factory may need information from the
+ // file to decide what kind of NativeRunner to make.
+ virtual void Start(const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request,
+ const base::Closure& app_completed_callback) = 0;
+};
+
+class NativeRunnerFactory {
+ public:
+ // Options for running the native app. (This will contain, e.g., information
+ // about the sandbox profile, etc.)
+ struct Options {
+ // Constructs with default options.
+ Options() : force_in_process(false) {}
+
+ bool force_in_process;
+ };
+
+ virtual ~NativeRunnerFactory() {}
+ virtual scoped_ptr<NativeRunner> Create(const Options& options) = 0;
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_NATIVE_RUNNER_H_
diff --git a/mojo/shell/application_manager/network_fetcher.cc b/mojo/shell/application_manager/network_fetcher.cc
new file mode 100644
index 0000000..5bf3086
--- /dev/null
+++ b/mojo/shell/application_manager/network_fetcher.cc
@@ -0,0 +1,250 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/network_fetcher.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/common/url_type_converters.h"
+#include "mojo/services/network/public/interfaces/network_service.mojom.h"
+#include "mojo/shell/application_manager/data_pipe_peek.h"
+#include "mojo/shell/switches.h"
+
+namespace mojo {
+namespace shell {
+
+NetworkFetcher::NetworkFetcher(bool disable_cache,
+ const GURL& url,
+ NetworkService* network_service,
+ const FetchCallback& loader_callback)
+ : Fetcher(loader_callback),
+ disable_cache_(false),
+ url_(url),
+ weak_ptr_factory_(this) {
+ StartNetworkRequest(url, network_service);
+}
+
+NetworkFetcher::~NetworkFetcher() {
+}
+
+const GURL& NetworkFetcher::GetURL() const {
+ return url_;
+}
+
+GURL NetworkFetcher::GetRedirectURL() const {
+ if (!response_)
+ return GURL::EmptyGURL();
+
+ if (response_->redirect_url.is_null())
+ return GURL::EmptyGURL();
+
+ return GURL(response_->redirect_url);
+}
+
+URLResponsePtr NetworkFetcher::AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) {
+ 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 NetworkFetcher::RecordCacheToURLMapping(const base::FilePath& path,
+ const GURL& url) {
+ // This is used to extract symbols on android.
+ // TODO(eseidel): All users of this log should move to using the map file.
+ VLOG(INFO) << "Caching mojo app " << url << " at " << path.value();
+
+ base::FilePath temp_dir;
+ base::GetTempDir(&temp_dir);
+ base::ProcessId pid = base::Process::Current().Pid();
+ std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid);
+ base::FilePath map_path = temp_dir.AppendASCII(map_name);
+
+ // TODO(eseidel): Paths or URLs with spaces will need quoting.
+ std::string map_entry =
+ base::StringPrintf("%s %s\n", path.value().c_str(), url.spec().c_str());
+ // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696
+ if (!PathExists(map_path)) {
+ base::WriteFile(map_path, map_entry.data(),
+ static_cast<int>(map_entry.length()));
+ } else {
+ base::AppendToFile(map_path, map_entry.data(),
+ static_cast<int>(map_entry.length()));
+ }
+}
+
+// For remote debugging, GDB needs to be, a apriori, aware of the filename a
+// library will be loaded from. AppIds should be be both predictable and unique,
+// but any hash would work. Currently we use sha256 from crypto/secure_hash.h
+bool NetworkFetcher::ComputeAppId(const base::FilePath& path,
+ std::string* digest_string) {
+ scoped_ptr<crypto::SecureHash> ctx(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid()) {
+ LOG(ERROR) << "Failed to open " << path.value() << " for computing AppId";
+ return false;
+ }
+ char buf[1024];
+ while (file.IsValid()) {
+ int bytes_read = file.ReadAtCurrentPos(buf, sizeof(buf));
+ if (bytes_read == 0)
+ break;
+ ctx->Update(buf, bytes_read);
+ }
+ if (!file.IsValid()) {
+ LOG(ERROR) << "Error reading " << path.value();
+ return false;
+ }
+ // The output is really a vector of unit8, we're cheating by using a string.
+ std::string output(crypto::kSHA256Length, 0);
+ ctx->Finish(string_as_array(&output), output.size());
+ output = base::HexEncode(output.c_str(), output.size());
+ // Using lowercase for compatiblity with sha256sum output.
+ *digest_string = base::StringToLowerASCII(output);
+ return true;
+}
+
+bool NetworkFetcher::RenameToAppId(const GURL& url,
+ const base::FilePath& old_path,
+ base::FilePath* new_path) {
+ std::string app_id;
+ if (!ComputeAppId(old_path, &app_id))
+ return false;
+
+ // Using a hash of the url as a directory to prevent a race when the same
+ // bytes are downloaded from 2 different urls. In particular, if the same
+ // application is connected to twice concurrently with different query
+ // parameters, the directory will be different, which will prevent the
+ // collision.
+ std::string dirname = base::HexEncode(
+ crypto::SHA256HashString(url.spec()).data(), crypto::kSHA256Length);
+
+ base::FilePath temp_dir;
+ base::GetTempDir(&temp_dir);
+ base::FilePath app_dir = temp_dir.AppendASCII(dirname);
+ // The directory is leaked, because it can be reused at any time if the same
+ // application is downloaded. Deleting it would be racy. This is only
+ // happening when --predictable-app-filenames is used.
+ bool result = base::CreateDirectoryAndGetError(app_dir, nullptr);
+ DCHECK(result);
+ std::string unique_name = base::StringPrintf("%s.mojo", app_id.c_str());
+ *new_path = app_dir.AppendASCII(unique_name);
+ return base::Move(old_path, *new_path);
+}
+
+void NetworkFetcher::CopyCompleted(
+ base::Callback<void(const base::FilePath&, bool)> callback,
+ bool success) {
+ if (success) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kPredictableAppFilenames)) {
+ // The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen.
+ success = false;
+ base::FilePath new_path;
+ if (RenameToAppId(url_, path_, &new_path)) {
+ if (base::PathExists(new_path)) {
+ path_ = new_path;
+ success = true;
+ }
+ }
+ }
+ }
+
+ if (success)
+ RecordCacheToURLMapping(path_, url_);
+
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback, path_, success));
+}
+
+void NetworkFetcher::AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) {
+ 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(&NetworkFetcher::CopyCompleted,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+std::string NetworkFetcher::MimeType() {
+ return response_->mime_type;
+}
+
+bool NetworkFetcher::HasMojoMagic() {
+ std::string magic;
+ return BlockingPeekNBytes(response_->body.get(), &magic, strlen(kMojoMagic),
+ kPeekTimeout) &&
+ magic == kMojoMagic;
+}
+
+bool NetworkFetcher::PeekFirstLine(std::string* line) {
+ return BlockingPeekLine(response_->body.get(), line, kMaxShebangLength,
+ kPeekTimeout);
+}
+
+void NetworkFetcher::StartNetworkRequest(const GURL& url,
+ NetworkService* network_service) {
+ TRACE_EVENT_ASYNC_BEGIN1("mojo_shell", "NetworkFetcher::NetworkRequest", this,
+ "url", url.spec());
+ URLRequestPtr request(URLRequest::New());
+ request->url = String::From(url);
+ request->auto_follow_redirects = false;
+ request->bypass_cache = disable_cache_;
+
+ network_service->CreateURLLoader(GetProxy(&url_loader_));
+ url_loader_->Start(request.Pass(),
+ base::Bind(&NetworkFetcher::OnLoadComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NetworkFetcher::OnLoadComplete(URLResponsePtr response) {
+ TRACE_EVENT_ASYNC_END0("mojo_shell", "NetworkFetcher::NetworkRequest", this);
+ if (response->error) {
+ LOG(ERROR) << "Error (" << response->error->code << ": "
+ << response->error->description << ") while fetching "
+ << response->url;
+ loader_callback_.Run(nullptr);
+ return;
+ }
+
+ if (response->status_code >= 400 && response->status_code < 600) {
+ LOG(ERROR) << "Error (" << response->status_code << ": "
+ << response->status_line << "): "
+ << "while fetching " << response->url;
+ loader_callback_.Run(nullptr);
+ return;
+ }
+
+ response_ = response.Pass();
+ loader_callback_.Run(make_scoped_ptr(this));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/network_fetcher.h b/mojo/shell/application_manager/network_fetcher.h
new file mode 100644
index 0000000..3ee90a9
--- /dev/null
+++ b/mojo/shell/application_manager/network_fetcher.h
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_NETWORK_FETCHER_H_
+#define SHELL_APPLICATION_MANAGER_NETWORK_FETCHER_H_
+
+#include "mojo/shell/application_manager/fetcher.h"
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "url/gurl.h"
+
+namespace mojo {
+
+class NetworkService;
+
+namespace shell {
+
+// Implements Fetcher for http[s] files.
+class NetworkFetcher : public Fetcher {
+ public:
+ NetworkFetcher(bool disable_cache,
+ const GURL& url,
+ NetworkService* network_service,
+ const FetchCallback& loader_callback);
+
+ ~NetworkFetcher() override;
+
+ private:
+ // TODO(hansmuller): Revisit this when a real peek operation is available.
+ static const MojoDeadline kPeekTimeout = MOJO_DEADLINE_INDEFINITE;
+
+ const GURL& GetURL() const override;
+ GURL GetRedirectURL() const override;
+
+ URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
+ uint32_t skip) override;
+
+ static void RecordCacheToURLMapping(const base::FilePath& path,
+ const GURL& url);
+
+ // AppIds should be be both predictable and unique, but any hash would work.
+ // Currently we use sha256 from crypto/secure_hash.h
+ static bool ComputeAppId(const base::FilePath& path,
+ std::string* digest_string);
+
+ static bool RenameToAppId(const GURL& url,
+ const base::FilePath& old_path,
+ base::FilePath* new_path);
+
+ void CopyCompleted(base::Callback<void(const base::FilePath&, bool)> callback,
+ bool success);
+
+ void AsPath(
+ base::TaskRunner* task_runner,
+ base::Callback<void(const base::FilePath&, bool)> callback) override;
+
+ std::string MimeType() override;
+
+ bool HasMojoMagic() override;
+
+ bool PeekFirstLine(std::string* line) override;
+
+ void StartNetworkRequest(const GURL& url, NetworkService* network_service);
+
+ void OnLoadComplete(URLResponsePtr response);
+
+ bool disable_cache_;
+ const GURL url_;
+ URLLoaderPtr url_loader_;
+ URLResponsePtr response_;
+ base::FilePath path_;
+ base::WeakPtrFactory<NetworkFetcher> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkFetcher);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_NETWORK_FETCHER_H_
diff --git a/mojo/shell/application_manager/query_util.cc b/mojo/shell/application_manager/query_util.cc
new file mode 100644
index 0000000..252dcb6
--- /dev/null
+++ b/mojo/shell/application_manager/query_util.cc
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/query_util.h"
+
+#include "base/strings/string_util.h"
+
+namespace mojo {
+namespace shell {
+
+GURL GetBaseURLAndQuery(const GURL& url, std::string* query) {
+ if (!url.has_query()) {
+ if (query)
+ *query = "";
+ return url;
+ }
+
+ if (query)
+ *query = "?" + url.query();
+ GURL::Replacements repl;
+ repl.SetQueryStr("");
+ std::string result = url.ReplaceComponents(repl).spec();
+
+ // Remove the dangling '?' because it's ugly.
+ base::ReplaceChars(result, "?", "", &result);
+ return GURL(result);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/query_util.h b/mojo/shell/application_manager/query_util.h
new file mode 100644
index 0000000..a4ca3f7e
--- /dev/null
+++ b/mojo/shell/application_manager/query_util.h
@@ -0,0 +1,23 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_QUERY_UTIL_H_
+#define SHELL_APPLICATION_MANAGER_QUERY_UTIL_H_
+
+#include <utility>
+
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+// Returns the base URL (without the query string). If |query| is not nullptr,
+// set |*query| to the query string. If the url doesn't have a query string,
+// |*query| is set to the empty string.
+GURL GetBaseURLAndQuery(const GURL& url, std::string* query);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_QUERY_UTIL_H_
diff --git a/mojo/shell/application_manager/query_util_unittest.cc b/mojo/shell/application_manager/query_util_unittest.cc
new file mode 100644
index 0000000..8a995c4
--- /dev/null
+++ b/mojo/shell/application_manager/query_util_unittest.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/query_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+TEST(QueryUtil, NoQuery) {
+ GURL url("http://www.example.com/");
+ std::string query;
+ GURL base_url = GetBaseURLAndQuery(url, &query);
+ EXPECT_EQ(base_url, url);
+ EXPECT_EQ(query, "");
+}
+
+TEST(QueryUtil, WithEmptyQuery) {
+ GURL url("http://www.example.com/?");
+ std::string query;
+ GURL base_url = GetBaseURLAndQuery(url, &query);
+ EXPECT_EQ(base_url, GURL("http://www.example.com/"));
+ EXPECT_EQ(query, "?");
+}
+
+TEST(QueryUtil, WithFullQuery) {
+ GURL url("http://www.example.com/?a=b&c=d");
+ std::string query;
+ GURL base_url = GetBaseURLAndQuery(url, &query);
+ EXPECT_EQ(base_url, GURL("http://www.example.com/"));
+ EXPECT_EQ(query, "?a=b&c=d");
+}
+
+TEST(QueryUtil, ForFileURL) {
+ GURL url("file:///tmp/file?a=b&c=d");
+ std::string query;
+ GURL base_url = GetBaseURLAndQuery(url, &query);
+ EXPECT_EQ(base_url, GURL("file:///tmp/file"));
+ EXPECT_EQ(query, "?a=b&c=d");
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/shell_impl.cc b/mojo/shell/application_manager/shell_impl.cc
new file mode 100644
index 0000000..ed8c5ea
--- /dev/null
+++ b/mojo/shell/application_manager/shell_impl.cc
@@ -0,0 +1,62 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/application_manager/shell_impl.h"
+
+#include "mojo/common/common_type_converters.h"
+#include "mojo/common/url_type_converters.h"
+#include "mojo/shell/application_manager/application_manager.h"
+#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h"
+
+namespace mojo {
+namespace shell {
+
+ShellImpl::ShellImpl(ApplicationPtr application,
+ ApplicationManager* manager,
+ const Identity& identity,
+ const base::Closure& on_application_end)
+ : manager_(manager),
+ identity_(identity),
+ on_application_end_(on_application_end),
+ application_(application.Pass()),
+ binding_(this) {
+ binding_.set_error_handler(this);
+}
+
+ShellImpl::~ShellImpl() {
+}
+
+void ShellImpl::InitializeApplication(Array<String> args) {
+ ShellPtr shell;
+ binding_.Bind(GetProxy(&shell));
+ application_->Initialize(shell.Pass(), args.Pass(), identity_.url.spec());
+}
+
+void ShellImpl::ConnectToClient(const GURL& requested_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) {
+ application_->AcceptConnection(String::From(requestor_url), services.Pass(),
+ exposed_services.Pass(), requested_url.spec());
+}
+
+// Shell implementation:
+void ShellImpl::ConnectToApplication(const String& app_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) {
+ GURL app_gurl(app_url);
+ if (!app_gurl.is_valid()) {
+ LOG(ERROR) << "Error: invalid URL: " << app_url;
+ return;
+ }
+ manager_->ConnectToApplication(app_gurl, identity_.url, services.Pass(),
+ exposed_services.Pass(), base::Closure());
+}
+
+void ShellImpl::OnConnectionError() {
+ manager_->OnShellImplError(this);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/application_manager/shell_impl.h b/mojo/shell/application_manager/shell_impl.h
new file mode 100644
index 0000000..ef557a6
--- /dev/null
+++ b/mojo/shell/application_manager/shell_impl.h
@@ -0,0 +1,62 @@
+// Copyright 2015 The Chromium Authors. 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_APPLICATION_MANAGER_SHELL_IMPL_H_
+#define SHELL_APPLICATION_MANAGER_SHELL_IMPL_H_
+
+#include "base/callback.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/interfaces/application/application.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/shell/application_manager/identity.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+class ApplicationManager;
+
+class ShellImpl : public Shell, public ErrorHandler {
+ public:
+ ShellImpl(ApplicationPtr application,
+ ApplicationManager* manager,
+ const Identity& resolved_identity,
+ const base::Closure& on_application_end);
+
+ ~ShellImpl() override;
+
+ void InitializeApplication(Array<String> args);
+
+ void ConnectToClient(const GURL& requested_url,
+ const GURL& requestor_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services);
+
+ Application* application() { return application_.get(); }
+ const Identity& identity() const { return identity_; }
+ base::Closure on_application_end() const { return on_application_end_; }
+
+ private:
+ // Shell implementation:
+ void ConnectToApplication(const String& app_url,
+ InterfaceRequest<ServiceProvider> services,
+ ServiceProviderPtr exposed_services) override;
+
+ // ErrorHandler implementation:
+ void OnConnectionError() override;
+
+ ApplicationManager* const manager_;
+ const Identity identity_;
+ base::Closure on_application_end_;
+ ApplicationPtr application_;
+ Binding<Shell> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellImpl);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_APPLICATION_MANAGER_SHELL_IMPL_H_
diff --git a/mojo/shell/application_manager/test.mojom b/mojo/shell/application_manager/test.mojom
new file mode 100644
index 0000000..25700b5
--- /dev/null
+++ b/mojo/shell/application_manager/test.mojom
@@ -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.
+
+module mojo.shell;
+
+interface TestService {
+ Test(string test_string) => ();
+};
+
+interface TestA {
+ CallB();
+ CallCFromB();
+};
+
+interface TestB {
+ B() => ();
+ CallC() => ();
+};
+
+interface TestC {
+ C() => ();
+};
diff --git a/mojo/shell/child_process.cc b/mojo/shell/child_process.cc
new file mode 100644
index 0000000..1140c14
--- /dev/null
+++ b/mojo/shell/child_process.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/child_process.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/shell/app_child_process.h"
+#include "mojo/shell/switches.h"
+
+namespace mojo {
+namespace shell {
+
+ChildProcess::~ChildProcess() {
+}
+
+// static
+scoped_ptr<ChildProcess> ChildProcess::Create(
+ const base::CommandLine& command_line) {
+ if (!command_line.HasSwitch(switches::kChildProcess))
+ return scoped_ptr<ChildProcess>();
+
+ scoped_ptr<ChildProcess> rv(new AppChildProcess());
+ if (!rv)
+ return nullptr;
+
+ rv->platform_channel_ =
+ embedder::PlatformChannelPair::PassClientHandleFromParentProcess(
+ command_line);
+ CHECK(rv->platform_channel_.is_valid());
+ return rv;
+}
+
+ChildProcess::ChildProcess() {
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/child_process.h b/mojo/shell/child_process.h
new file mode 100644
index 0000000..3da1c8f
--- /dev/null
+++ b/mojo/shell/child_process.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_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:
+ 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/mojo/shell/child_process_host.cc b/mojo/shell/child_process_host.cc
new file mode 100644
index 0000000..b8f7508
--- /dev/null
+++ b/mojo/shell/child_process_host.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/child_process_host.h"
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/task_runner.h"
+#include "base/task_runner_util.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/switches.h"
+
+namespace mojo {
+namespace shell {
+
+ChildProcessHost::ChildProcessHost(Context* context) : context_(context) {
+ platform_channel_ = platform_channel_pair_.PassServerHandle();
+ CHECK(platform_channel_.is_valid());
+}
+
+ChildProcessHost::~ChildProcessHost() {
+ if (child_process_.IsValid()) {
+ LOG(WARNING) << "Destroying ChildProcessHost with unjoined child";
+ child_process_.Close();
+ }
+}
+
+void ChildProcessHost::Start() {
+ DCHECK(!child_process_.IsValid());
+
+ WillStart();
+
+ CHECK(base::PostTaskAndReplyWithResult(
+ context_->task_runners()->blocking_pool(), FROM_HERE,
+ base::Bind(&ChildProcessHost::DoLaunch, base::Unretained(this)),
+ base::Bind(&ChildProcessHost::DidStart, base::Unretained(this))));
+}
+
+int ChildProcessHost::Join() {
+ DCHECK(child_process_.IsValid());
+ int rv = -1;
+ LOG_IF(ERROR, !child_process_.WaitForExit(&rv))
+ << "Failed to wait for child process";
+ child_process_.Close();
+ 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.AppendSwitch(switches::kChildProcess);
+
+ 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
+ DVLOG(2) << "Launching child with command line: "
+ << child_command_line.GetCommandLineString();
+ child_process_ = base::LaunchProcess(child_command_line, options);
+ if (!child_process_.IsValid())
+ return false;
+
+ platform_channel_pair_.ChildProcessLaunched();
+ return true;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/child_process_host.h b/mojo/shell/child_process_host.h
new file mode 100644
index 0000000..21b3a4c8
--- /dev/null
+++ b/mojo/shell/child_process_host.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_CHILD_PROCESS_HOST_H_
+#define SHELL_CHILD_PROCESS_HOST_H_
+
+#include "base/macros.h"
+#include "base/process/process.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/shell/child_process.h" // For |ChildProcess::Type|.
+
+namespace mojo {
+namespace shell {
+
+class Context;
+
+// (Base) class for a "child process host". Handles launching and connecting a
+// platform-specific "pipe" to the child, and supports joining the child
+// process.
+//
+// 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:
+ explicit ChildProcessHost(Context* context);
+ virtual ~ChildProcessHost();
+
+ // |Start()|s the child process; calls |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_;
+ }
+
+ virtual void WillStart() = 0;
+ virtual void DidStart(bool success) = 0;
+
+ protected:
+ Context* context() const { return context_; }
+
+ private:
+ bool DoLaunch();
+
+ Context* const context_;
+
+ base::Process child_process_;
+
+ 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/mojo/shell/command_line_util.cc b/mojo/shell/command_line_util.cc
new file mode 100644
index 0000000..68bdfa4
--- /dev/null
+++ b/mojo/shell/command_line_util.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/command_line_util.h"
+
+#include <functional>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/switches.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+GURL GetAppURLAndSetArgs(const std::string& app_url_and_args,
+ Context* context) {
+ std::vector<std::string> args;
+ GURL app_url = GetAppURLAndArgs(context, app_url_and_args, &args);
+
+ if (args.size() > 1)
+ context->application_manager()->SetArgsForURL(args, app_url);
+ return app_url;
+}
+} // namespace
+
+bool ParseArgsFor(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++) {
+ const std::string& argsfor_switch = kArgsForSwitches[i];
+ if (arg.compare(0, argsfor_switch.size(), argsfor_switch) == 0) {
+ *value = arg.substr(argsfor_switch.size(), std::string::npos);
+ return true;
+ }
+ }
+ return false;
+}
+
+GURL GetAppURLAndArgs(Context* context,
+ const std::string& app_url_and_args,
+ std::vector<std::string>* args) {
+ // SplitString() returns empty strings for extra delimeter characters (' ').
+ base::SplitString(app_url_and_args, ' ', args);
+ args->erase(std::remove_if(args->begin(), args->end(),
+ std::mem_fun_ref(&std::string::empty)),
+ args->end());
+
+ if (args->empty())
+ return GURL();
+ GURL app_url = context->ResolveCommandLineURL((*args)[0]);
+ if (!app_url.is_valid()) {
+ LOG(ERROR) << "Error: invalid URL: " << (*args)[0];
+ return app_url;
+ }
+ if (args->size() == 1)
+ args->clear();
+ return app_url;
+}
+
+void ApplyApplicationArgs(Context* context, const std::string& args) {
+ std::string args_for_value;
+ if (ParseArgsFor(args, &args_for_value))
+ GetAppURLAndSetArgs(args_for_value, context);
+}
+
+void RunCommandLineApps(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(url);
+ }
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/command_line_util.h b/mojo/shell/command_line_util.h
new file mode 100644
index 0000000..aa80002
--- /dev/null
+++ b/mojo/shell/command_line_util.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_COMMAND_LINE_UTIL_H_
+#define SHELL_COMMAND_LINE_UTIL_H_
+
+#include "mojo/shell/context.h"
+
+namespace mojo {
+namespace shell {
+
+// Parse the given arg, looking for an --args-for switch. If this is not the
+// case, returns |false|. Otherwise, returns |true| and set |*value| to the
+// value of the switch.
+bool ParseArgsFor(const std::string& arg, std::string* value);
+
+// 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. This returns the mojo_app_url, and set args to the list of
+// arguments.
+GURL GetAppURLAndArgs(Context* context,
+ const std::string& app_url_and_args,
+ std::vector<std::string>* args);
+
+// Apply arguments for an application from a line with the following format:
+// '--args-for=application_url arg1 arg2 arg3'
+// This does nothing if the line has not the right format.
+void ApplyApplicationArgs(Context* context, const std::string& args);
+
+// Run all application defined on the command line, using the given context.
+void RunCommandLineApps(Context* context);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_COMMAND_LINE_UTIL_H_
diff --git a/mojo/shell/command_line_util_unittest.cc b/mojo/shell/command_line_util_unittest.cc
new file mode 100644
index 0000000..547aa86
--- /dev/null
+++ b/mojo/shell/command_line_util_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/command_line_util.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+TEST(CommandLineUtil, ParseArgsFor) {
+ static const struct Expectation {
+ const char* args;
+ const char* value;
+ } EXPECTATIONS[] = {
+ {"", nullptr},
+ {"hello", nullptr},
+ {"args-for=mojo:app1", nullptr},
+ {"--args-for", nullptr},
+ {"--args-for=", ""},
+ {"--args-for=mojo:app1", "mojo:app1"},
+ {"--args-for=mojo:app1 hello world", "mojo:app1 hello world"},
+ {"-args-for", nullptr},
+ {"-args-for=", ""},
+ {"-args-for=mojo:app1", "mojo:app1"},
+ {"-args-for=mojo:app1 hello world", "mojo:app1 hello world"}};
+ for (auto& expectation : EXPECTATIONS) {
+ std::string value;
+ bool result = ParseArgsFor(expectation.args, &value);
+ EXPECT_EQ(bool(expectation.value), result);
+ if (expectation.value && result)
+ EXPECT_EQ(value, expectation.value);
+ }
+}
+
+TEST(CommandLineUtil, GetAppURLAndArgs) {
+ const char* NO_ARGUMENTS[] = {nullptr};
+ const char* ONE_ARGUMENTS[] = {"1", nullptr};
+ const char* TWO_ARGUMENTS[] = {"1", "two", nullptr};
+ static const struct Expectation {
+ const char* args;
+ const char* url;
+ const char** values;
+ } EXPECTATIONS[] = {
+ {"", nullptr, nullptr},
+ {"foo", "file:///root/foo", NO_ARGUMENTS},
+ {"/foo", "file:///foo", NO_ARGUMENTS},
+ {"file:foo", "file:///root/foo", NO_ARGUMENTS},
+ {"file:///foo", "file:///foo", NO_ARGUMENTS},
+ {"http://example.com", "http://example.com", NO_ARGUMENTS},
+ {"http://example.com 1", "http://example.com", ONE_ARGUMENTS},
+ {"http://example.com 1 ", "http://example.com", ONE_ARGUMENTS},
+ {"http://example.com 1 ", "http://example.com", ONE_ARGUMENTS},
+ {"http://example.com 1 two", "http://example.com", TWO_ARGUMENTS},
+ {" http://example.com 1 two ",
+ "http://example.com",
+ TWO_ARGUMENTS}};
+ Context context;
+ context.SetCommandLineCWD(base::FilePath(FILE_PATH_LITERAL("/root")));
+ for (auto& expectation : EXPECTATIONS) {
+ std::vector<std::string> args;
+ GURL result(GetAppURLAndArgs(&context, expectation.args, &args));
+ EXPECT_EQ(bool(expectation.url), result.is_valid());
+ if (expectation.url && result.is_valid()) {
+ EXPECT_EQ(GURL(expectation.url), result);
+ std::vector<std::string> expected_args;
+ if (expectation.values) {
+ if (*expectation.values)
+ expected_args.push_back(expectation.url);
+ for (const char** value = expectation.values; *value; ++value)
+ expected_args.push_back(*value);
+ }
+ EXPECT_EQ(expected_args, args);
+ }
+ }
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/context.cc b/mojo/shell/context.cc
new file mode 100644
index 0000000..fbde07d
--- /dev/null
+++ b/mojo/shell/context.cc
@@ -0,0 +1,328 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/context.h"
+
+#include <vector>
+
+#include "base/base_switches.h"
+#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/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "mojo/common/trace_controller_impl.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/services/tracing/tracing.mojom.h"
+#include "mojo/shell/application_manager/application_loader.h"
+#include "mojo/shell/application_manager/application_manager.h"
+#include "mojo/shell/command_line_util.h"
+#include "mojo/shell/filename_util.h"
+#include "mojo/shell/in_process_native_runner.h"
+#include "mojo/shell/out_of_process_native_runner.h"
+#include "mojo/shell/switches.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+namespace {
+
+// Used to ensure we only init once.
+class Setup {
+ public:
+ Setup() {
+ embedder::Init(make_scoped_ptr(new embedder::SimplePlatformSupport()));
+ }
+
+ ~Setup() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Setup);
+};
+
+bool ConfigureURLMappings(const base::CommandLine& command_line,
+ Context* context) {
+ URLResolver* resolver = context->url_resolver();
+
+ // Configure the resolution of unknown mojo: URLs.
+ GURL base_url;
+ if (command_line.HasSwitch(switches::kOrigin))
+ base_url = GURL(command_line.GetSwitchValueASCII(switches::kOrigin));
+ else
+ // Use the shell's file root if the base was not specified.
+ base_url = context->ResolveShellFileURL("");
+
+ if (!base_url.is_valid())
+ return false;
+
+ resolver->SetMojoBaseURL(base_url);
+
+ // The network service must be loaded from the filesystem.
+ // This mapping is done before the command line URL mapping are processed, so
+ // that it can be overridden.
+ resolver->AddURLMapping(
+ GURL("mojo:network_service"),
+ context->ResolveShellFileURL("file:network_service.mojo"));
+
+ // Command line URL mapping.
+ std::vector<URLResolver::OriginMapping> origin_mappings =
+ URLResolver::GetOriginMappings(command_line.argv());
+ for (const auto& origin_mapping : origin_mappings)
+ resolver->AddOriginMapping(GURL(origin_mapping.origin),
+ GURL(origin_mapping.base_url));
+
+ if (command_line.HasSwitch(switches::kURLMappings)) {
+ const std::string mappings =
+ command_line.GetSwitchValueASCII(switches::kURLMappings);
+
+ 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 = context->ResolveCommandLineURL(pair.second);
+ if (!from.is_valid() || !to.is_valid())
+ return false;
+ resolver->AddURLMapping(from, to);
+ }
+ }
+ return true;
+}
+
+void InitContentHandlers(ApplicationManager* manager,
+ const base::CommandLine& command_line) {
+ // Default content handlers.
+ manager->RegisterContentHandler("application/pdf", GURL("mojo:pdf_viewer"));
+ manager->RegisterContentHandler("image/png", GURL("mojo:png_viewer"));
+ manager->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;
+
+#if defined(OS_ANDROID)
+ // TODO(eseidel): On Android we pass command line arguments is via the
+ // 'parameters' key on the intent, which we specify during 'am shell start'
+ // via --esa, however that expects comma-separated values and says:
+ // am shell --help:
+ // [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]
+ // (to embed a comma into a string escape it using "\,")
+ // Whatever takes 'parameters' and constructs a CommandLine is failing to
+ // un-escape the commas, we need to move this fix to that file.
+ ReplaceSubstringsAfterOffset(&handlers_spec, 0, "\\,", ",");
+#endif
+
+ 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."
+ << handlers_spec;
+ 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;
+ }
+ // TODO(eseidel): We should also validate that the mimetype is valid
+ // net/base/mime_util.h could do this, but we don't want to depend on net.
+ manager->RegisterContentHandler(parts[i], url);
+ }
+}
+
+void InitNativeOptions(ApplicationManager* manager,
+ const base::CommandLine& command_line) {
+ std::vector<std::string> force_in_process_url_list;
+ base::SplitString(command_line.GetSwitchValueASCII(switches::kForceInProcess),
+ ',', &force_in_process_url_list);
+ for (const auto& force_in_process_url : force_in_process_url_list) {
+ GURL gurl(force_in_process_url);
+ if (!gurl.is_valid()) {
+ LOG(ERROR) << "Invalid value for switch " << switches::kForceInProcess
+ << ": '" << force_in_process_url << "'is not a valid URL.";
+ return;
+ }
+
+ NativeRunnerFactory::Options options;
+ options.force_in_process = true;
+ manager->SetNativeOptionsForURL(options, gurl);
+ }
+}
+
+class TracingServiceProvider : public ServiceProvider {
+ public:
+ explicit TracingServiceProvider(InterfaceRequest<ServiceProvider> request)
+ : binding_(this, request.Pass()) {}
+ ~TracingServiceProvider() override {}
+
+ void ConnectToService(const String& service_name,
+ ScopedMessagePipeHandle client_handle) override {
+ if (service_name == tracing::TraceController::Name_) {
+ new TraceControllerImpl(
+ MakeRequest<tracing::TraceController>(client_handle.Pass()));
+ }
+ }
+
+ private:
+ StrongBinding<ServiceProvider> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TracingServiceProvider);
+};
+
+} // namespace
+
+Context::Context() : application_manager_(this) {
+ DCHECK(!base::MessageLoop::current());
+
+ // By default assume that the local apps reside alongside the shell.
+ // TODO(ncbray): really, this should be passed in rather than defaulting.
+ // This default makes sense for desktop but not Android.
+ base::FilePath shell_dir;
+ PathService::Get(base::DIR_MODULE, &shell_dir);
+ SetShellFileRoot(shell_dir);
+
+ base::FilePath cwd;
+ PathService::Get(base::DIR_CURRENT, &cwd);
+ SetCommandLineCWD(cwd);
+}
+
+Context::~Context() {
+ DCHECK(!base::MessageLoop::current());
+}
+
+// static
+void Context::EnsureEmbedderIsInitialized() {
+ static base::LazyInstance<Setup>::Leaky setup = LAZY_INSTANCE_INITIALIZER;
+ setup.Get();
+}
+
+void Context::SetShellFileRoot(const base::FilePath& path) {
+ shell_file_root_ = AddTrailingSlashIfNeeded(FilePathToFileURL(path));
+}
+
+GURL Context::ResolveShellFileURL(const std::string& path) {
+ return shell_file_root_.Resolve(path);
+}
+
+void Context::SetCommandLineCWD(const base::FilePath& path) {
+ command_line_cwd_ = AddTrailingSlashIfNeeded(FilePathToFileURL(path));
+}
+
+GURL Context::ResolveCommandLineURL(const std::string& path) {
+ return command_line_cwd_.Resolve(path);
+}
+
+bool Context::Init() {
+ TRACE_EVENT0("mojo_shell", "Context::Init");
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+
+ if (command_line.HasSwitch(switches::kWaitForDebugger))
+ base::debug::WaitForDebugger(60, true);
+
+ EnsureEmbedderIsInitialized();
+ task_runners_.reset(
+ new TaskRunners(base::MessageLoop::current()->message_loop_proxy()));
+
+ // TODO(vtl): Probably these failures should be checked before |Init()|, and
+ // this function simply shouldn't fail.
+ if (!shell_file_root_.is_valid())
+ return false;
+ if (!ConfigureURLMappings(command_line, this))
+ return false;
+
+ // TODO(vtl): This should be MASTER, not NONE.
+ embedder::InitIPCSupport(
+ embedder::ProcessType::NONE, task_runners_->shell_runner(), this,
+ task_runners_->io_runner(), embedder::ScopedPlatformHandle());
+
+ scoped_ptr<NativeRunnerFactory> runner_factory;
+ if (command_line.HasSwitch(switches::kEnableMultiprocess))
+ runner_factory.reset(new OutOfProcessNativeRunnerFactory(this));
+ else
+ runner_factory.reset(new InProcessNativeRunnerFactory(this));
+ application_manager_.set_blocking_pool(task_runners_->blocking_pool());
+ application_manager_.set_native_runner_factory(runner_factory.Pass());
+ application_manager_.set_disable_cache(
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableCache));
+
+ InitContentHandlers(&application_manager_, command_line);
+ InitNativeOptions(&application_manager_, command_line);
+
+ ServiceProviderPtr tracing_service_provider_ptr;
+ new TracingServiceProvider(GetProxy(&tracing_service_provider_ptr));
+ application_manager_.ConnectToApplication(
+ GURL("mojo:tracing"), GURL(""), nullptr,
+ tracing_service_provider_ptr.Pass(), base::Closure());
+
+ return true;
+}
+
+void Context::Shutdown() {
+ TRACE_EVENT0("mojo_shell", "Context::Shutdown");
+ DCHECK_EQ(base::MessageLoop::current()->task_runner(),
+ task_runners_->shell_runner());
+ embedder::ShutdownIPCSupport();
+ // We'll quit when we get OnShutdownComplete().
+ base::MessageLoop::current()->Run();
+}
+
+GURL Context::ResolveURL(const GURL& url) {
+ return url_resolver_.ResolveMojoURL(url);
+}
+
+GURL Context::ResolveMappings(const GURL& url) {
+ return url_resolver_.ApplyMappings(url);
+}
+
+void Context::OnShutdownComplete() {
+ DCHECK_EQ(base::MessageLoop::current()->task_runner(),
+ task_runners_->shell_runner());
+ base::MessageLoop::current()->Quit();
+}
+
+void Context::Run(const GURL& url) {
+ ServiceProviderPtr services;
+ ServiceProviderPtr exposed_services;
+
+ app_urls_.insert(url);
+ application_manager_.ConnectToApplication(
+ url, GURL(), GetProxy(&services), exposed_services.Pass(),
+ base::Bind(&Context::OnApplicationEnd, base::Unretained(this), url));
+}
+
+void Context::OnApplicationEnd(const GURL& url) {
+ if (app_urls_.find(url) != app_urls_.end()) {
+ app_urls_.erase(url);
+ if (app_urls_.empty() && base::MessageLoop::current()->is_running()) {
+ DCHECK_EQ(base::MessageLoop::current()->task_runner(),
+ task_runners_->shell_runner());
+ base::MessageLoop::current()->Quit();
+ }
+ }
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/context.h b/mojo/shell/context.h
new file mode 100644
index 0000000..4be704e
--- /dev/null
+++ b/mojo/shell/context.h
@@ -0,0 +1,87 @@
+// Copyright 2013 The Chromium Authors. 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/edk/embedder/process_delegate.h"
+#include "mojo/shell/application_manager/application_manager.h"
+#include "mojo/shell/task_runners.h"
+#include "mojo/shell/url_resolver.h"
+
+namespace mojo {
+namespace shell {
+
+class NativeApplicationLoader;
+
+// The "global" context for the shell's main process.
+class Context : public ApplicationManager::Delegate,
+ public embedder::ProcessDelegate {
+ public:
+ Context();
+ ~Context() override;
+
+ static void EnsureEmbedderIsInitialized();
+
+ // Point to the directory containing installed services, such as the network
+ // service. By default this directory is used as the base URL for resolving
+ // unknown mojo: URLs. The network service will be loaded from this directory,
+ // even when the base URL for unknown mojo: URLs is overridden.
+ void SetShellFileRoot(const base::FilePath& path);
+
+ // Resolve an URL relative to the shell file root. This is a nop for
+ // everything but relative file URLs or URLs without a scheme.
+ GURL ResolveShellFileURL(const std::string& path);
+
+ // Override the CWD, which is used for resolving file URLs passed in from the
+ // command line.
+ void SetCommandLineCWD(const base::FilePath& path);
+
+ // Resolve an URL relative to the CWD mojo_shell was invoked from. This is a
+ // nop for everything but relative file URLs or URLs without a scheme.
+ GURL ResolveCommandLineURL(const std::string& path);
+
+ // This must be called with a message loop set up for the current thread,
+ // which must remain alive until after Shutdown() is called. Returns true on
+ // success.
+ bool Init();
+
+ // If Init() was called and succeeded, this must be called before destruction.
+ void Shutdown();
+
+ void Run(const GURL& url);
+
+ TaskRunners* task_runners() { return task_runners_.get(); }
+ ApplicationManager* application_manager() { return &application_manager_; }
+ URLResolver* url_resolver() { return &url_resolver_; }
+
+ private:
+ class NativeViewportApplicationLoader;
+
+ // ApplicationManager::Delegate overrides.
+ GURL ResolveURL(const GURL& url) override;
+ GURL ResolveMappings(const GURL& url) override;
+
+ // ProcessDelegate implementation.
+ void OnShutdownComplete() override;
+
+ void OnApplicationEnd(const GURL& url);
+
+ std::set<GURL> app_urls_;
+ scoped_ptr<TaskRunners> task_runners_;
+ ApplicationManager application_manager_;
+ URLResolver url_resolver_;
+ GURL shell_file_root_;
+ GURL command_line_cwd_;
+
+ DISALLOW_COPY_AND_ASSIGN(Context);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_CONTEXT_H_
diff --git a/mojo/shell/data_pipe_peek_unittest.cc b/mojo/shell/data_pipe_peek_unittest.cc
new file mode 100644
index 0000000..126901b
--- /dev/null
+++ b/mojo/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 "mojo/shell/application_manager/data_pipe_peek.h"
+
+#include "mojo/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/mojo/shell/desktop/main.cc b/mojo/shell/desktop/main.cc
new file mode 100644
index 0000000..9214985
--- /dev/null
+++ b/mojo/shell/desktop/main.cc
@@ -0,0 +1,185 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+#include <string.h>
+
+#include <algorithm>
+#include <iostream>
+
+#include "base/at_exit.h"
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/trace_event/trace_event.h"
+#include "mojo/shell/child_process.h"
+#include "mojo/shell/command_line_util.h"
+#include "mojo/shell/context.h"
+#include "mojo/shell/init.h"
+#include "mojo/shell/switches.h"
+
+namespace {
+
+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::kTraceStartup << "]"
+ << " [--" << switches::kURLMappings << "=from1=to1,from2=to2]"
+ << " [--" << switches::kPredictableAppFilenames << "]"
+ << " [--" << switches::kWaitForDebugger << "]"
+ << " <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";
+}
+
+// Whether we're currently tracing.
+bool g_tracing = false;
+
+// Number of tracing blocks written.
+uint32_t g_blocks = 0;
+
+// Trace file, if open.
+FILE* g_trace_file = nullptr;
+
+void WriteTraceDataCollected(
+ base::WaitableEvent* event,
+ const scoped_refptr<base::RefCountedString>& events_str,
+ bool has_more_events) {
+ if (g_blocks) {
+ fwrite(",", 1, 1, g_trace_file);
+ }
+
+ ++g_blocks;
+ fwrite(events_str->data().c_str(), 1, events_str->data().length(),
+ g_trace_file);
+ if (!has_more_events) {
+ static const char kEnd[] = "]}";
+ fwrite(kEnd, 1, strlen(kEnd), g_trace_file);
+ PCHECK(fclose(g_trace_file) == 0);
+ g_trace_file = nullptr;
+ event->Signal();
+ }
+}
+
+void EndTraceAndFlush(base::WaitableEvent* event) {
+ g_trace_file = fopen("mojo_shell.trace", "w+");
+ PCHECK(g_trace_file);
+ static const char kStart[] = "{\"traceEvents\":[";
+ fwrite(kStart, 1, strlen(kStart), g_trace_file);
+ base::trace_event::TraceLog::GetInstance()->SetDisabled();
+ base::trace_event::TraceLog::GetInstance()->Flush(
+ base::Bind(&WriteTraceDataCollected, base::Unretained(event)));
+}
+
+void StopTracingAndFlushToDisk() {
+ g_tracing = false;
+ base::trace_event::TraceLog::GetInstance()->SetDisabled();
+ base::WaitableEvent flush_complete_event(false, false);
+ // TraceLog::Flush requires a message loop but we've already shut ours down.
+ // Spin up a new thread to flush things out.
+ base::Thread flush_thread("mojo_shell_trace_event_flush");
+ flush_thread.Start();
+ flush_thread.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(EndTraceAndFlush, base::Unretained(&flush_complete_event)));
+ flush_complete_event.Wait();
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit;
+ base::CommandLine::Init(argc, argv);
+
+ mojo::shell::InitializeLogging();
+
+ // TODO(vtl): Unify parent and child process cases to the extent possible.
+ if (scoped_ptr<mojo::shell::ChildProcess> child_process =
+ mojo::shell::ChildProcess::Create(
+ *base::CommandLine::ForCurrentProcess())) {
+ child_process->Main();
+ } else {
+ // Only check the command line for the main process.
+ 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 (command_line.HasSwitch(switches::kTraceStartup)) {
+ g_tracing = true;
+ base::trace_event::CategoryFilter category_filter(
+ command_line.GetSwitchValueASCII(switches::kTraceStartup));
+ base::trace_event::TraceLog::GetInstance()->SetEnabled(
+ category_filter, base::trace_event::TraceLog::RECORDING_MODE,
+ base::trace_event::TraceOptions(
+ base::trace_event::RECORD_UNTIL_FULL));
+ }
+
+ // 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;
+ }
+ if (g_tracing) {
+ message_loop.PostDelayedTask(FROM_HERE,
+ base::Bind(StopTracingAndFlushToDisk),
+ base::TimeDelta::FromSeconds(5));
+ }
+
+ // 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++) {
+ ApplyApplicationArgs(&shell_context, argv[i]);
+ }
+
+ message_loop.PostTask(
+ FROM_HERE,
+ base::Bind(&mojo::shell::RunCommandLineApps, &shell_context));
+ message_loop.Run();
+
+ // Must be called before |message_loop| is destroyed.
+ shell_context.Shutdown();
+ }
+ }
+
+ if (g_tracing)
+ StopTracingAndFlushToDisk();
+ return 0;
+}
diff --git a/mojo/shell/filename_util.cc b/mojo/shell/filename_util.cc
new file mode 100644
index 0000000..073bdda
--- /dev/null
+++ b/mojo/shell/filename_util.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/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 {
+namespace shell {
+
+// 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, &current_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);
+}
+
+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 shell
+} // namespace mojo
diff --git a/mojo/shell/filename_util.h b/mojo/shell/filename_util.h
new file mode 100644
index 0000000..28d0e78
--- /dev/null
+++ b/mojo/shell/filename_util.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_FILENAME_UTIL_H_
+#define SHELL_FILENAME_UTIL_H_
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace mojo {
+namespace shell {
+
+// 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);
+
+// This URL is going to be treated as a directory. Ensure there is a trailing
+// slash so that GURL.Resolve(...) works correctly.
+GURL AddTrailingSlashIfNeeded(const GURL& url);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_FILENAME_UTIL_H_
diff --git a/mojo/shell/in_process_native_runner.cc b/mojo/shell/in_process_native_runner.cc
new file mode 100644
index 0000000..7431fe0
--- /dev/null
+++ b/mojo/shell/in_process_native_runner.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/in_process_native_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"
+#include "mojo/shell/native_application_support.h"
+
+namespace mojo {
+namespace shell {
+
+InProcessNativeRunner::InProcessNativeRunner(Context* context)
+ : cleanup_(NativeApplicationCleanup::DONT_DELETE), app_library_(nullptr) {
+}
+
+InProcessNativeRunner::~InProcessNativeRunner() {
+ // It is important to let the thread exit before unloading the DSO (when
+ // app_library_ is destructed), because the library may have registered
+ // thread-local data and destructors to run on thread termination.
+ if (thread_) {
+ DCHECK(thread_->HasBeenStarted());
+ DCHECK(!thread_->HasBeenJoined());
+ thread_->Join();
+ }
+}
+
+void InProcessNativeRunner::Start(
+ const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request,
+ const base::Closure& app_completed_callback) {
+ app_path_ = app_path;
+ cleanup_ = cleanup;
+
+ DCHECK(!application_request_.is_pending());
+ application_request_ = application_request.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 InProcessNativeRunner::Run() {
+ DVLOG(2) << "Loading/running Mojo app in process from library: "
+ << app_path_.value()
+ << " thread id=" << base::PlatformThread::CurrentId();
+
+ // TODO(vtl): ScopedNativeLibrary doesn't have a .get() method!
+ base::NativeLibrary app_library = LoadNativeApplication(app_path_, cleanup_);
+ app_library_.Reset(app_library);
+ RunNativeApplication(app_library, application_request_.Pass());
+ app_completed_callback_runner_.Run();
+ app_completed_callback_runner_.Reset();
+}
+
+scoped_ptr<NativeRunner> InProcessNativeRunnerFactory::Create(
+ const Options& options) {
+ return make_scoped_ptr(new InProcessNativeRunner(context_));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/in_process_native_runner.h b/mojo/shell/in_process_native_runner.h
new file mode 100644
index 0000000..a5639f4
--- /dev/null
+++ b/mojo/shell/in_process_native_runner.h
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_IN_PROCESS_NATIVE_RUNNER_H_
+#define SHELL_IN_PROCESS_NATIVE_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/scoped_native_library.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/shell/application_manager/native_runner.h"
+#include "mojo/shell/native_application_support.h"
+
+namespace mojo {
+namespace shell {
+
+class Context;
+
+// An implementation of |NativeRunner| that loads/runs the given app (from the
+// file system) on a separate thread (in the current process).
+class InProcessNativeRunner : public NativeRunner,
+ public base::DelegateSimpleThread::Delegate {
+ public:
+ explicit InProcessNativeRunner(Context* context);
+ ~InProcessNativeRunner() override;
+
+ // |NativeRunner| method:
+ void Start(const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request,
+ const base::Closure& app_completed_callback) override;
+
+ private:
+ // |base::DelegateSimpleThread::Delegate| method:
+ void Run() override;
+
+ base::FilePath app_path_;
+ NativeApplicationCleanup cleanup_;
+ InterfaceRequest<Application> application_request_;
+ base::Callback<bool(void)> app_completed_callback_runner_;
+
+ base::ScopedNativeLibrary app_library_;
+ scoped_ptr<base::DelegateSimpleThread> thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(InProcessNativeRunner);
+};
+
+class InProcessNativeRunnerFactory : public NativeRunnerFactory {
+ public:
+ explicit InProcessNativeRunnerFactory(Context* context) : context_(context) {}
+ ~InProcessNativeRunnerFactory() override {}
+
+ scoped_ptr<NativeRunner> Create(const Options& options) override;
+
+ private:
+ Context* const context_;
+
+ DISALLOW_COPY_AND_ASSIGN(InProcessNativeRunnerFactory);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_IN_PROCESS_NATIVE_RUNNER_H_
diff --git a/mojo/shell/in_process_native_runner_unittest.cc b/mojo/shell/in_process_native_runner_unittest.cc
new file mode 100644
index 0000000..28608c8
--- /dev/null
+++ b/mojo/shell/in_process_native_runner_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/in_process_native_runner.h"
+
+#include "mojo/shell/context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+
+TEST(InProcessNativeRunnerTest, NotStarted) {
+ Context context;
+ base::MessageLoop loop;
+ context.Init();
+ InProcessNativeRunner runner(&context);
+ context.Shutdown();
+ // Shouldn't crash or DCHECK on destruction.
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/init.cc b/mojo/shell/init.cc
new file mode 100644
index 0000000..71aec63
--- /dev/null
+++ b/mojo/shell/init.cc
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/init.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace shell {
+
+void InitializeLogging() {
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+ // To view log output with IDs and timestamps use "adb logcat -v threadtime".
+ logging::SetLogItems(false, // Process ID
+ false, // Thread ID
+ false, // Timestamp
+ false); // Tick count
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/init.h b/mojo/shell/init.h
new file mode 100644
index 0000000..23a5140
--- /dev/null
+++ b/mojo/shell/init.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/mojo/shell/launcher_main.cc b/mojo/shell/launcher_main.cc
new file mode 100644
index 0000000..dfe634c
--- /dev/null
+++ b/mojo/shell/launcher_main.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_split.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/process_delegate.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/shell/in_process_native_runner.h"
+#include "mojo/shell/init.h"
+#include "mojo/shell/native_application_support.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+const char kAppArgs[] = "app-args";
+const char kAppPath[] = "app-path";
+const char kAppURL[] = "app-url";
+const char kShellPath[] = "shell-path";
+
+class Launcher : public embedder::ProcessDelegate {
+ public:
+ explicit Launcher(const base::CommandLine& command_line)
+ : app_path_(command_line.GetSwitchValuePath(kAppPath)),
+ app_url_(command_line.GetSwitchValueASCII(kAppURL)),
+ loop_(base::MessageLoop::TYPE_IO) {
+ // TODO(vtl): I guess this should be SLAVE, not NONE?
+ embedder::InitIPCSupport(embedder::ProcessType::NONE, loop_.task_runner(),
+ this, loop_.task_runner(),
+ embedder::ScopedPlatformHandle());
+
+ base::SplitStringAlongWhitespace(command_line.GetSwitchValueASCII(kAppArgs),
+ &app_args_);
+ }
+
+ ~Launcher() override {
+ DCHECK(!application_request_.is_pending());
+
+ embedder::ShutdownIPCSupportOnIOThread();
+ }
+
+ bool Connect() { return false; }
+
+ bool Register() {
+ return application_request_.is_pending();
+ }
+
+ void Run() {
+ DCHECK(application_request_.is_pending());
+ InProcessNativeRunner service_runner(nullptr);
+ base::RunLoop run_loop;
+ service_runner.Start(app_path_, NativeApplicationCleanup::DONT_DELETE,
+ application_request_.Pass(), run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ private:
+ void OnRegistered(base::RunLoop* run_loop,
+ InterfaceRequest<Application> application_request) {
+ application_request_ = application_request.Pass();
+ run_loop->Quit();
+ }
+
+ // embedder::ProcessDelegate implementation:
+ void OnShutdownComplete() override {
+ NOTREACHED(); // Not called since we use ShutdownIPCSupportOnIOThread().
+ }
+
+ const base::FilePath app_path_;
+ const GURL app_url_;
+ std::vector<std::string> app_args_;
+ base::MessageLoop loop_;
+ InterfaceRequest<Application> application_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(Launcher);
+};
+
+} // namespace shell
+} // namespace mojo
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit;
+ mojo::embedder::Init(
+ make_scoped_ptr(new mojo::embedder::SimplePlatformSupport()));
+
+ base::CommandLine::Init(argc, argv);
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+ mojo::shell::InitializeLogging();
+
+ mojo::shell::Launcher launcher(*command_line);
+ if (!launcher.Connect()) {
+ LOG(ERROR) << "Failed to connect on socket "
+ << command_line->GetSwitchValueASCII(mojo::shell::kShellPath);
+ return 1;
+ }
+
+ if (!launcher.Register()) {
+ LOG(ERROR) << "Error registering "
+ << command_line->GetSwitchValueASCII(mojo::shell::kAppURL);
+ return 1;
+ }
+
+ launcher.Run();
+ return 0;
+}
diff --git a/mojo/shell/native_application_support.cc b/mojo/shell/native_application_support.cc
new file mode 100644
index 0000000..55ae2cf
--- /dev/null
+++ b/mojo/shell/native_application_support.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/native_application_support.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "mojo/public/platform/native/gles2_impl_chromium_miscellaneous_thunks.h"
+#include "mojo/public/platform/native/gles2_impl_chromium_sub_image_thunks.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_occlusion_query_ext_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 LoadNativeApplication(const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup) {
+ DVLOG(2) << "Loading Mojo app in process from library: " << app_path.value();
+
+ base::NativeLibraryLoadError error;
+ base::NativeLibrary app_library = base::LoadNativeLibrary(app_path, &error);
+ if (cleanup == NativeApplicationCleanup::DELETE)
+ DeleteFile(app_path, false);
+ LOG_IF(ERROR, !app_library)
+ << "Failed to load app library (error: " << error.ToString() << ")";
+ return app_library;
+}
+
+bool RunNativeApplication(base::NativeLibrary app_library,
+ InterfaceRequest<Application> application_request) {
+ // Tolerate |app_library| being null, to make life easier for callers.
+ if (!app_library)
+ return false;
+
+ if (!SetThunks(&MojoMakeSystemThunks, "MojoSetSystemThunks", app_library)) {
+ LOG(ERROR) << "MojoSetSystemThunks not found";
+ return false;
+ }
+
+ 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)
+ << "MojoSetGLES2ControlThunks found, but not MojoSetGLES2ImplThunks";
+ return false;
+ }
+
+ // 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(MojoMakeGLES2ImplChromiumMiscellaneousThunks,
+ "MojoSetGLES2ImplChromiumMiscellaneousThunks", app_library);
+ SetThunks(MojoMakeGLES2ImplChromiumSubImageThunks,
+ "MojoSetGLES2ImplChromiumSubImageThunks", app_library);
+ SetThunks(MojoMakeGLES2ImplChromiumTextureMailboxThunks,
+ "MojoSetGLES2ImplChromiumTextureMailboxThunks", app_library);
+ SetThunks(MojoMakeGLES2ImplChromiumSyncPointThunks,
+ "MojoSetGLES2ImplChromiumSyncPointThunks", app_library);
+ SetThunks(MojoMakeGLES2ImplOcclusionQueryExtThunks,
+ "MojoSetGLES2ImplOcclusionQueryExtThunks", app_library);
+ }
+ // Unlike system thunks, we don't warn on a lack of GLES2 thunks because
+ // not everything is a visual app.
+
+ // 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();
+ }
+
+ typedef MojoResult (*MojoMainFunction)(MojoHandle);
+ MojoMainFunction main_function = reinterpret_cast<MojoMainFunction>(
+ base::GetFunctionPointerFromNativeLibrary(app_library, "MojoMain"));
+ if (!main_function) {
+ LOG(ERROR) << "MojoMain not found";
+ return false;
+ }
+ // |MojoMain()| takes ownership of the service handle.
+ MojoHandle handle = application_request.PassMessagePipe().release().value();
+ MojoResult result = main_function(handle);
+ if (result < MOJO_RESULT_OK) {
+ LOG(ERROR) << "MojoMain returned error (result: " << result << ")";
+ }
+ return true;
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/native_application_support.h b/mojo/shell/native_application_support.h
new file mode 100644
index 0000000..572c089
--- /dev/null
+++ b/mojo/shell/native_application_support.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_NATIVE_APPLICATION_SUPPORT_H_
+#define SHELL_NATIVE_APPLICATION_SUPPORT_H_
+
+#include "base/native_library.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+
+#if defined(OS_WIN)
+#undef DELETE
+#endif
+
+namespace base {
+class FilePath;
+}
+
+namespace mojo {
+
+class Application;
+
+namespace shell {
+
+enum class NativeApplicationCleanup { DELETE, DONT_DELETE };
+
+// Loads the native Mojo application from the DSO specified by |app_path|.
+// Returns the |base::NativeLibrary| for the application on success (or null on
+// failure). If |cleanup| is |DELETE|, it will delete |app_path| (regardless of
+// sucess or failure).
+//
+// Note: The caller may choose to eventually unload the returned DSO. If so,
+// this should be done only after the thread on which |LoadNativeApplication()|
+// and |RunNativeApplication()| were called has terminated, so that any
+// thread-local destructors have been executed.
+base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup);
+
+// Runs the native Mojo application from the DSO that was loaded using
+// |LoadNativeApplication()|; this tolerates |app_library| being null. This
+// should be called on the same thread as |LoadNativeApplication()|. Returns
+// true if |MojoMain()| was called (even if it returns an error), and false
+// otherwise.
+// TODO(vtl): Maybe this should also have a |MojoResult| as an out parameter?
+bool RunNativeApplication(base::NativeLibrary app_library,
+ InterfaceRequest<Application> application_request);
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_NATIVE_APPLICATION_SUPPORT_H_
diff --git a/mojo/shell/native_runner_unittest.cc b/mojo/shell/native_runner_unittest.cc
new file mode 100644
index 0000000..4b8b5a0
--- /dev/null
+++ b/mojo/shell/native_runner_unittest.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_temp_dir.h"
+#include "mojo/shell/application_manager/application_manager.h"
+#include "mojo/shell/context.h"
+#include "mojo/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 TestNativeRunner : public NativeRunner {
+ public:
+ explicit TestNativeRunner(TestState* state) : state_(state) {
+ state_->runner_was_created = true;
+ }
+ ~TestNativeRunner() override {
+ state_->runner_was_destroyed = true;
+ base::MessageLoop::current()->Quit();
+ }
+ void Start(const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request,
+ const base::Closure& app_completed_callback) override {
+ state_->runner_was_started = true;
+ }
+
+ private:
+ TestState* state_;
+};
+
+class TestNativeRunnerFactory : public NativeRunnerFactory {
+ public:
+ explicit TestNativeRunnerFactory(TestState* state) : state_(state) {}
+ ~TestNativeRunnerFactory() override {}
+ scoped_ptr<NativeRunner> Create(const Options& options) override {
+ return scoped_ptr<NativeRunner>(new TestNativeRunner(state_));
+ }
+
+ private:
+ TestState* state_;
+};
+
+class NativeApplicationLoaderTest : public testing::Test,
+ public ApplicationManager::Delegate {
+ public:
+ NativeApplicationLoaderTest() : application_manager_(this) {}
+ ~NativeApplicationLoaderTest() override {}
+ void SetUp() override {
+ context_.Init();
+ scoped_ptr<NativeRunnerFactory> factory(
+ new TestNativeRunnerFactory(&state_));
+ application_manager_.set_native_runner_factory(factory.Pass());
+ application_manager_.set_blocking_pool(
+ context_.task_runners()->blocking_pool());
+ }
+ void TearDown() override { context_.Shutdown(); }
+
+ protected:
+ shell::Context context_;
+ base::MessageLoop loop_;
+ ApplicationManager application_manager_;
+ TestState state_;
+
+ private:
+ // ApplicationManager::Delegate
+ GURL ResolveURL(const GURL& url) override { return url; }
+
+ GURL ResolveMappings(const GURL& url) override { return url; }
+};
+
+TEST_F(NativeApplicationLoaderTest, 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)));
+ InterfaceRequest<ServiceProvider> services;
+ ServiceProviderPtr service_provider;
+ application_manager_.ConnectToApplication(
+ url, GURL(), services.Pass(), service_provider.Pass(), base::Closure());
+ EXPECT_FALSE(state_.runner_was_created);
+ EXPECT_FALSE(state_.runner_was_started);
+ EXPECT_FALSE(state_.runner_was_destroyed);
+}
+
+} // namespace
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/out_of_process_native_runner.cc b/mojo/shell/out_of_process_native_runner.cc
new file mode 100644
index 0000000..4c87271
--- /dev/null
+++ b/mojo/shell/out_of_process_native_runner.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/out_of_process_native_runner.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "mojo/shell/app_child_process.mojom.h"
+#include "mojo/shell/app_child_process_host.h"
+#include "mojo/shell/in_process_native_runner.h"
+
+namespace mojo {
+namespace shell {
+
+OutOfProcessNativeRunner::OutOfProcessNativeRunner(Context* context)
+ : context_(context) {
+}
+
+OutOfProcessNativeRunner::~OutOfProcessNativeRunner() {
+ 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 OutOfProcessNativeRunner::Start(
+ const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request,
+ 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_));
+ app_child_process_host_->Start();
+
+ // TODO(vtl): |app_path.AsUTF8Unsafe()| is unsafe.
+ app_child_process_host_->StartApp(
+ app_path.AsUTF8Unsafe(), cleanup == NativeApplicationCleanup::DELETE,
+ application_request.Pass(),
+ base::Bind(&OutOfProcessNativeRunner::AppCompleted,
+ base::Unretained(this)));
+}
+
+void OutOfProcessNativeRunner::AppCompleted(int32_t result) {
+ DVLOG(2) << "OutOfProcessNativeRunner::AppCompleted(" << result << ")";
+
+ app_child_process_host_.reset();
+ // This object may be deleted by this callback.
+ base::Closure app_completed_callback = app_completed_callback_;
+ app_completed_callback_.Reset();
+ app_completed_callback.Run();
+}
+
+scoped_ptr<NativeRunner> OutOfProcessNativeRunnerFactory::Create(
+ const Options& options) {
+ if (options.force_in_process)
+ return make_scoped_ptr(new InProcessNativeRunner(context_));
+
+ return make_scoped_ptr(new OutOfProcessNativeRunner(context_));
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/out_of_process_native_runner.h b/mojo/shell/out_of_process_native_runner.h
new file mode 100644
index 0000000..edbaaf0
--- /dev/null
+++ b/mojo/shell/out_of_process_native_runner.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_OUT_OF_PROCESS_NATIVE_RUNNER_H_
+#define SHELL_OUT_OF_PROCESS_NATIVE_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/shell/application_manager/native_runner.h"
+
+namespace mojo {
+namespace shell {
+
+class AppChildProcessHost;
+class Context;
+
+// An implementation of |NativeRunner| that loads/runs the given app (from the
+// file system) in a separate process (of its own).
+class OutOfProcessNativeRunner : public NativeRunner {
+ public:
+ explicit OutOfProcessNativeRunner(Context* context);
+ ~OutOfProcessNativeRunner() override;
+
+ // |NativeRunner| method:
+ void Start(const base::FilePath& app_path,
+ NativeApplicationCleanup cleanup,
+ InterfaceRequest<Application> application_request,
+ const base::Closure& app_completed_callback) override;
+
+ private:
+ // |AppChildController::StartApp()| callback:
+ void AppCompleted(int32_t result);
+
+ Context* const context_;
+
+ base::FilePath app_path_;
+ base::Closure app_completed_callback_;
+
+ scoped_ptr<AppChildProcessHost> app_child_process_host_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutOfProcessNativeRunner);
+};
+
+class OutOfProcessNativeRunnerFactory : public NativeRunnerFactory {
+ public:
+ explicit OutOfProcessNativeRunnerFactory(Context* context)
+ : context_(context) {}
+ ~OutOfProcessNativeRunnerFactory() override {}
+
+ scoped_ptr<NativeRunner> Create(const Options& options) override;
+
+ private:
+ Context* const context_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutOfProcessNativeRunnerFactory);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_OUT_OF_PROCESS_NATIVE_RUNNER_H_
diff --git a/mojo/shell/shell_apptest.cc b/mojo/shell/shell_apptest.cc
new file mode 100644
index 0000000..1acdf7d
--- /dev/null
+++ b/mojo/shell/shell_apptest.cc
@@ -0,0 +1,197 @@
+// Copyright 2015 The Chromium Authors. 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/base_paths.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/http_server/public/cpp/http_server_util.h"
+#include "mojo/services/http_server/public/interfaces/http_server.mojom.h"
+#include "mojo/services/http_server/public/interfaces/http_server_factory.mojom.h"
+#include "mojo/services/network/public/interfaces/net_address.mojom.h"
+#include "mojo/shell/kPingable.h"
+#include "mojo/shell/test/pingable.mojom.h"
+
+namespace mojo {
+namespace {
+
+std::string GetURL(uint16_t port, const std::string& path) {
+ return base::StringPrintf("http://127.0.0.1:%u/%s",
+ static_cast<unsigned>(port), path.c_str());
+}
+
+class GetHandler : public http_server::HttpHandler {
+ public:
+ GetHandler(InterfaceRequest<http_server::HttpHandler> request, uint16_t port)
+ : binding_(this, request.Pass()), port_(port) {
+ }
+ ~GetHandler() override {}
+
+ private:
+ // http_server::HttpHandler:
+ void HandleRequest(
+ http_server::HttpRequestPtr request,
+ const Callback<void(http_server::HttpResponsePtr)>& callback) override {
+ http_server::HttpResponsePtr response;
+ if (StartsWithASCII(request->relative_url, "/app", true)) {
+ response = http_server::CreateHttpResponse(
+ 200, std::string(kPingable.data, kPingable.size));
+ response->content_type = "application/octet-stream";
+ } else if (request->relative_url == "/redirect") {
+ response = http_server::HttpResponse::New();
+ response->status_code = 302;
+ response->custom_headers.insert("Location", GetURL(port_, "app"));
+ } else {
+ NOTREACHED();
+ }
+
+ callback.Run(response.Pass());
+ }
+
+ Binding<http_server::HttpHandler> binding_;
+ uint16_t port_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(GetHandler);
+};
+
+typedef test::ApplicationTestBase ShellAppTest;
+
+class ShellHTTPAppTest : public test::ApplicationTestBase {
+ public:
+ ShellHTTPAppTest() : ApplicationTestBase() {}
+ ~ShellHTTPAppTest() override {}
+
+ protected:
+ // ApplicationTestBase:
+ void SetUp() override {
+ ApplicationTestBase::SetUp();
+
+ application_impl()->ConnectToService("mojo:http_server",
+ &http_server_factory_);
+
+ NetAddressPtr local_address(NetAddress::New());
+ local_address->family = NET_ADDRESS_FAMILY_IPV4;
+ local_address->ipv4 = NetAddressIPv4::New();
+ local_address->ipv4->addr.resize(4);
+ local_address->ipv4->addr[0] = 127;
+ local_address->ipv4->addr[1] = 0;
+ local_address->ipv4->addr[2] = 0;
+ local_address->ipv4->addr[3] = 1;
+ local_address->ipv4->port = 0;
+ http_server_factory_->CreateHttpServer(GetProxy(&http_server_),
+ local_address.Pass());
+
+ http_server_->GetPort([this](uint16_t p) { port_ = p; });
+ EXPECT_TRUE(http_server_.WaitForIncomingMethodCall());
+
+ InterfacePtr<http_server::HttpHandler> http_handler;
+ handler_.reset(new GetHandler(GetProxy(&http_handler).Pass(), port_));
+ http_server_->SetHandler(".*", http_handler.Pass(),
+ [](bool result) { EXPECT_TRUE(result); });
+ EXPECT_TRUE(http_server_.WaitForIncomingMethodCall());
+ }
+
+ std::string GetURL(const std::string& path) {
+ return ::mojo::GetURL(port_, path);
+ }
+
+ http_server::HttpServerFactoryPtr http_server_factory_;
+ http_server::HttpServerPtr http_server_;
+ scoped_ptr<GetHandler> handler_;
+ uint16_t port_;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ShellHTTPAppTest);
+};
+
+// Test that we can load apps over http.
+TEST_F(ShellHTTPAppTest, Http) {
+ InterfacePtr<Pingable> pingable;
+ application_impl()->ConnectToService(GetURL("app"), &pingable);
+ pingable->Ping("hello",
+ [this](const String& app_url, const String& connection_url,
+ const String& message) {
+ EXPECT_EQ(GetURL("app"), app_url);
+ EXPECT_EQ(GetURL("app"), connection_url);
+ EXPECT_EQ("hello", message);
+ base::MessageLoop::current()->Quit();
+ });
+ base::RunLoop().Run();
+}
+
+// Test that redirects work.
+// TODO(aa): Test that apps receive the correct URL parameters.
+TEST_F(ShellHTTPAppTest, Redirect) {
+ InterfacePtr<Pingable> pingable;
+ application_impl()->ConnectToService(GetURL("redirect"), &pingable);
+ pingable->Ping("hello",
+ [this](const String& app_url, const String& connection_url,
+ const String& message) {
+ EXPECT_EQ(GetURL("app"), app_url);
+ EXPECT_EQ(GetURL("app"), connection_url);
+ EXPECT_EQ("hello", message);
+ base::MessageLoop::current()->Quit();
+ });
+ base::RunLoop().Run();
+}
+
+// Test that querystring is not considered when resolving http applications.
+// TODO(aa|qsr): Fix this test on Linux ASAN http://crbug.com/463662
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_QueryHandling DISABLED_QueryHandling
+#else
+#define MAYBE_QueryHandling QueryHandling
+#endif // ADDRESS_SANITIZER
+TEST_F(ShellHTTPAppTest, MAYBE_QueryHandling) {
+ InterfacePtr<Pingable> pingable1;
+ InterfacePtr<Pingable> pingable2;
+ application_impl()->ConnectToService(GetURL("app?foo"), &pingable1);
+ application_impl()->ConnectToService(GetURL("app?bar"), &pingable2);
+
+ int num_responses = 0;
+ auto callback = [this, &num_responses](const String& app_url,
+ const String& connection_url,
+ const String& message) {
+ EXPECT_EQ(GetURL("app"), app_url);
+ EXPECT_EQ("hello", message);
+ ++num_responses;
+ if (num_responses == 1) {
+ EXPECT_EQ(GetURL("app?foo"), connection_url);
+ } else if (num_responses == 2) {
+ EXPECT_EQ(GetURL("app?bar"), connection_url);
+ base::MessageLoop::current()->Quit();
+ } else {
+ CHECK(false);
+ }
+ };
+ pingable1->Ping("hello", callback);
+ pingable2->Ping("hello", callback);
+ base::RunLoop().Run();
+}
+
+// mojo: URLs can have querystrings too
+TEST_F(ShellAppTest, MojoURLQueryHandling) {
+ InterfacePtr<Pingable> pingable;
+ application_impl()->ConnectToService("mojo:pingable_app?foo", &pingable);
+ auto callback = [this](const String& app_url, const String& connection_url,
+ const String& message) {
+ EXPECT_TRUE(EndsWith(app_url, "/pingable_app.mojo", true));
+ EXPECT_EQ(app_url.To<std::string>() + "?foo", connection_url);
+ EXPECT_EQ("hello", message);
+ base::MessageLoop::current()->Quit();
+ };
+ pingable->Ping("hello", callback);
+ base::RunLoop().Run();
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/shell/shell_test_base.cc b/mojo/shell/shell_test_base.cc
new file mode 100644
index 0000000..da9a7d9
--- /dev/null
+++ b/mojo/shell/shell_test_base.cc
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/shell_test_base.h"
+
+#include "base/bind.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 "mojo/shell/filename_util.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+
+namespace {
+
+void QuitIfRunning() {
+ if (base::MessageLoop::current() &&
+ base::MessageLoop::current()->is_running()) {
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+}
+
+} // namespace
+
+ShellTestBase::ShellTestBase() {
+}
+
+ShellTestBase::~ShellTestBase() {
+}
+
+void ShellTestBase::SetUp() {
+ CHECK(shell_context_.Init());
+ SetUpTestApplications();
+}
+
+void ShellTestBase::TearDown() {
+ shell_context_.Shutdown();
+}
+
+ScopedMessagePipeHandle ShellTestBase::ConnectToService(
+ const GURL& application_url,
+ const std::string& service_name) {
+ ServiceProviderPtr services;
+ shell_context_.application_manager()->ConnectToApplication(
+ application_url, GURL(), GetProxy(&services), nullptr,
+ base::Bind(&QuitIfRunning));
+ MessagePipe pipe;
+ services->ConnectToService(service_name, pipe.handle1.Pass());
+ return pipe.handle0.Pass();
+}
+
+#if !defined(OS_ANDROID)
+void ShellTestBase::SetUpTestApplications() {
+ // Set the URLResolver 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_.url_resolver()->SetMojoBaseURL(FilePathToFileURL(service_dir));
+}
+#endif
+
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_base.h b/mojo/shell/shell_test_base.h
new file mode 100644
index 0000000..e119a0a
--- /dev/null
+++ b/mojo/shell/shell_test_base.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_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 "mojo/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;
+ void TearDown() 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);
+
+ template <typename Interface>
+ void ConnectToService(const GURL& application_url,
+ InterfacePtr<Interface>* ptr) {
+ ptr->Bind(ConnectToService(application_url, Interface::Name_).Pass());
+ }
+
+ base::MessageLoop* message_loop() { return &message_loop_; }
+ Context* shell_context() { return &shell_context_; }
+
+ private:
+ // Set up the test applications so that mojo: URL resolves to those.
+ void SetUpTestApplications();
+
+ 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/mojo/shell/shell_test_base_android.cc b/mojo/shell/shell_test_base_android.cc
new file mode 100644
index 0000000..4b3b736
--- /dev/null
+++ b/mojo/shell/shell_test_base_android.cc
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/shell_test_base.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "jni/ShellTestBase_jni.h"
+#include "mojo/shell/filename_util.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+
+namespace {
+
+JNIEnv* InitEnv() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ static bool initialized = false;
+ if (!initialized) {
+ RegisterNativesImpl(env);
+ initialized = true;
+ }
+ return env;
+}
+
+} // namespace
+
+void ShellTestBase::SetUpTestApplications() {
+ // Extract mojo applications, and set the resolve base URL to the directory
+ // containing those.
+ JNIEnv* env = InitEnv();
+ base::android::ScopedJavaLocalRef<jstring> service_dir(
+ Java_ShellTestBase_extractMojoApplications(
+ env, base::android::GetApplicationContext()));
+ shell_context_.url_resolver()->SetMojoBaseURL(
+ FilePathToFileURL(base::FilePath(
+ base::android::ConvertJavaStringToUTF8(env, service_dir.obj()))));
+}
+
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_base_unittest.cc b/mojo/shell/shell_test_base_unittest.cc
new file mode 100644
index 0000000..4e94511
--- /dev/null
+++ b/mojo/shell/shell_test_base_unittest.cc
@@ -0,0 +1,309 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/shell_test_base.h"
+
+#include "base/bind.h"
+#include "base/i18n/time_formatting.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/services/test_service/test_request_tracker.mojom.h"
+#include "mojo/services/test_service/test_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using mojo::test::ServiceReport;
+using mojo::test::ServiceReportPtr;
+using mojo::test::TestService;
+using mojo::test::TestTimeService;
+using mojo::test::TestServicePtr;
+using mojo::test::TestTimeServicePtr;
+using mojo::test::TestTrackedRequestService;
+using mojo::test::TestTrackedRequestServicePtr;
+
+namespace mojo {
+namespace shell {
+namespace test {
+namespace {
+
+void GetReportCallback(base::MessageLoop* loop,
+ std::vector<ServiceReport>* reports_out,
+ 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;
+ ConnectToService(GURL("http://example.com/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(Callback<void()>());
+ message_loop()->Run();
+
+ for (int i = 0; i < 8; i++)
+ service->Ping(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(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(Callback<void(uint64_t)>());
+ message_loop()->Run();
+ for (int i = 0; i < 18; i++)
+ time_service->GetPartyTime(Callback<void(uint64_t)>());
+ // Flush the tasks with one more to quit.
+ int64 party_time = 0;
+ time_service->GetPartyTime(SetAndQuit<int64>(&party_time));
+ message_loop()->Run();
+
+ std::vector<ServiceReport> reports;
+ GetReport(&reports);
+ ASSERT_EQ(3U, reports.size());
+ EXPECT_EQ(TestService::Name_, reports[0].service_name);
+ EXPECT_EQ(6U, reports[0].total_requests);
+ EXPECT_EQ(TestTimeService::Name_, reports[1].service_name);
+ EXPECT_EQ(1U, reports[1].total_requests);
+ EXPECT_EQ(TestTimeService::Name_, reports[2].service_name);
+ EXPECT_EQ(20U, reports[2].total_requests);
+}
+
+} // namespace
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_helper.cc b/mojo/shell/shell_test_helper.cc
new file mode 100644
index 0000000..0a467303
--- /dev/null
+++ b/mojo/shell/shell_test_helper.cc
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/shell_test_helper.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "mojo/shell/filename_util.h"
+#include "mojo/shell/init.h"
+#include "mojo/shell/url_resolver.h"
+
+namespace mojo {
+namespace shell {
+
+ShellTestHelper::ShellTestHelper() {
+ base::CommandLine::Init(0, nullptr);
+ 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_.url_resolver()->SetMojoBaseURL(FilePathToFileURL(service_dir));
+}
+
+void ShellTestHelper::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader,
+ const GURL& url) {
+ context_.application_manager()->SetLoaderForURL(loader.Pass(), url);
+}
+
+void ShellTestHelper::AddURLMapping(const GURL& url, const GURL& resolved_url) {
+ context_.url_resolver()->AddURLMapping(url, resolved_url);
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/shell_test_helper.h b/mojo/shell/shell_test_helper.h
new file mode 100644
index 0000000..185d367
--- /dev/null
+++ b/mojo/shell/shell_test_helper.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/shell/application_manager/application_loader.h"
+#include "mojo/shell/context.h"
+
+class GURL;
+
+namespace mojo {
+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 URLResolver
+ // for details.
+ void AddURLMapping(const GURL& 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/mojo/shell/shell_test_main.cc b/mojo/shell/shell_test_main.cc
new file mode 100644
index 0000000..179eb41
--- /dev/null
+++ b/mojo/shell/shell_test_main.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "mojo/shell/child_process.h"
+#include "mojo/shell/switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+ base::CommandLine::Init(argc, argv);
+ const base::CommandLine& command_line =
+ *base::CommandLine::ForCurrentProcess();
+
+ if (command_line.HasSwitch(switches::kChildProcess)) {
+ base::AtExitManager at_exit;
+ scoped_ptr<mojo::shell::ChildProcess> child_process =
+ mojo::shell::ChildProcess::Create(command_line);
+ CHECK(child_process);
+ child_process->Main();
+ return 0;
+ }
+
+ base::TestSuite test_suite(argc, argv);
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/shell/switches.cc b/mojo/shell/switches.cc
new file mode 100644
index 0000000..6aa5cda
--- /dev/null
+++ b/mojo/shell/switches.cc
@@ -0,0 +1,106 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/switches.h"
+
+#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 internally by the main process to indicate that a new process should be
+// a child process. Not for user use.
+const char kChildProcess[] = "child-process";
+
+// 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";
+
+// If set apps downloaded are not deleted.
+const char kDontDeleteOnDownload[] = "dont-delete-on-download";
+
+// 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";
+
+// In multiprocess mode, force these apps to be loaded in the main process.
+// Comma-separate list of URLs. Example:
+// --force-in-process=mojo:native_viewport_service,mojo:network_service
+const char kForceInProcess[] = "force-in-process";
+
+// Print the usage message and exit.
+const char kHelp[] = "help";
+
+// Specify origin to map to base url. See url_resolver.cc for details.
+// Can be used multiple times.
+const char kMapOrigin[] = "map-origin";
+
+// Map mojo: URLs to a shared library of similar name at this origin. See
+// url_resolver.cc for details.
+const char kOrigin[] = "origin";
+
+// If set apps downloaded are saved in with a predictable filename, to help
+// remote debugging: when gdb is used through gdbserver, it needs to be able to
+// find locally any loaded library. For this, gdb use the filename of the
+// library. When using this flag, the application are named with the sha256 of
+// their content.
+const char kPredictableAppFilenames[] = "predictable-app-filenames";
+
+// Starts tracing when the shell starts up, saving a trace file on disk after 5
+// seconds or when the shell exits.
+const char kTraceStartup[] = "trace-startup";
+
+// Specifies a set of mappings to apply when resolving urls. The value is a 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";
+
+// Switches valid for the main process (i.e., that the user may pass in).
+const char* kSwitchArray[] = {kV,
+ kArgsFor,
+ // |kChildProcess| not for user use.
+ kContentHandlers,
+ kDisableCache,
+ kDontDeleteOnDownload,
+ kEnableExternalApplications,
+ kEnableMultiprocess,
+ kForceInProcess,
+ kHelp,
+ kMapOrigin,
+ kOrigin,
+ kPredictableAppFilenames,
+ kTraceStartup,
+ 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/mojo/shell/switches.h b/mojo/shell/switches.h
new file mode 100644
index 0000000..5c8056f
--- /dev/null
+++ b/mojo/shell/switches.h
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. 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 kChildProcess[];
+extern const char kContentHandlers[];
+extern const char kDisableCache[];
+extern const char kDontDeleteOnDownload[];
+extern const char kEnableExternalApplications[];
+extern const char kEnableMultiprocess[];
+extern const char kForceInProcess[];
+extern const char kHelp[];
+extern const char kMapOrigin[];
+extern const char kOrigin[];
+extern const char kPredictableAppFilenames[];
+extern const char kTraceStartup[];
+extern const char kURLMappings[];
+
+extern const std::set<std::string> GetAllSwitches();
+
+} // namespace switches
+
+#endif // SHELL_SWITCHES_H_
diff --git a/mojo/shell/task_runners.cc b/mojo/shell/task_runners.cc
new file mode 100644
index 0000000..cb157ee
--- /dev/null
+++ b/mojo/shell/task_runners.cc
@@ -0,0 +1,39 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/task_runners.h"
+
+#include "base/threading/sequenced_worker_pool.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+const size_t kMaxBlockingPoolThreads = 3;
+
+scoped_ptr<base::Thread> CreateIOThread(const char* name) {
+ scoped_ptr<base::Thread> thread(new base::Thread(name));
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ thread->StartWithOptions(options);
+ return thread.Pass();
+}
+
+} // namespace
+
+TaskRunners::TaskRunners(
+ const scoped_refptr<base::SingleThreadTaskRunner>& shell_runner)
+ : shell_runner_(shell_runner),
+ io_thread_(CreateIOThread("io_thread")),
+ blocking_pool_(new base::SequencedWorkerPool(kMaxBlockingPoolThreads,
+ "blocking_pool")) {
+}
+
+TaskRunners::~TaskRunners() {
+ blocking_pool_->Shutdown();
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/task_runners.h b/mojo/shell/task_runners.h
new file mode 100644
index 0000000..891d3e1b
--- /dev/null
+++ b/mojo/shell/task_runners.h
@@ -0,0 +1,53 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/mojo/shell/test/BUILD.gn b/mojo/shell/test/BUILD.gn
new file mode 100644
index 0000000..7ca52ff
--- /dev/null
+++ b/mojo/shell/test/BUILD.gn
@@ -0,0 +1,28 @@
+import("//third_party/mojo/src/mojo/public/mojo.gni")
+import("//third_party/mojo/src/mojo/public/mojo_application.gni")
+import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+import("//testing/test.gni")
+
+mojom("bindings") {
+ sources = [
+ "pingable.mojom",
+ ]
+}
+
+mojo_native_application("pingable_app") {
+ output_name = "pingable_app"
+
+ testonly = true
+
+ sources = [
+ "pingable_app.cc",
+ ]
+
+ deps = [
+ ":bindings",
+ "//third_party/mojo/src/mojo/public/cpp/application:standalone",
+ "//third_party/mojo/src/mojo/public/cpp/bindings:callback",
+ "//third_party/mojo/src/mojo/public/cpp/environment",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+}
diff --git a/mojo/shell/test/pingable.mojom b/mojo/shell/test/pingable.mojom
new file mode 100644
index 0000000..74ed38d
--- /dev/null
+++ b/mojo/shell/test/pingable.mojom
@@ -0,0 +1,9 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo;
+
+interface Pingable {
+ Ping(string message) => (string app_url, string connection_url, string message);
+};
diff --git a/mojo/shell/test/pingable_app.cc b/mojo/shell/test/pingable_app.cc
new file mode 100644
index 0000000..1849f71
--- /dev/null
+++ b/mojo/shell/test/pingable_app.cc
@@ -0,0 +1,69 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_runner.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/shell/test/pingable.mojom.h"
+
+namespace mojo {
+
+class PingableImpl : public Pingable {
+ public:
+ PingableImpl(InterfaceRequest<Pingable> request,
+ const std::string& app_url,
+ const std::string& connection_url)
+ : binding_(this, request.Pass()),
+ app_url_(app_url),
+ connection_url_(connection_url) {}
+
+ ~PingableImpl() override {}
+
+ private:
+ void Ping(const String& message,
+ const Callback<void(String, String, String)>& callback) override {
+ callback.Run(app_url_, connection_url_, message);
+ }
+
+ StrongBinding<Pingable> binding_;
+ std::string app_url_;
+ std::string connection_url_;
+};
+
+class PingableApp : public mojo::ApplicationDelegate,
+ public mojo::InterfaceFactory<Pingable> {
+ public:
+ PingableApp() {}
+ ~PingableApp() override {}
+
+ private:
+ // ApplicationDelegate:
+ void Initialize(ApplicationImpl* impl) override { app_url_ = impl->url(); }
+
+ bool ConfigureIncomingConnection(
+ mojo::ApplicationConnection* connection) override {
+ connection->AddService(this);
+ return true;
+ }
+
+ // InterfaceFactory<Pingable>:
+ void Create(mojo::ApplicationConnection* connection,
+ mojo::InterfaceRequest<Pingable> request) override {
+ new PingableImpl(request.Pass(), app_url_, connection->GetConnectionURL());
+ }
+
+ std::string app_url_;
+};
+
+} // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+ mojo::ApplicationRunner runner(new mojo::PingableApp);
+ return runner.Run(shell_handle);
+}
diff --git a/mojo/shell/url_resolver.cc b/mojo/shell/url_resolver.cc
new file mode 100644
index 0000000..20cb000
--- /dev/null
+++ b/mojo/shell/url_resolver.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/url_resolver.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/shell/application_manager/query_util.h"
+#include "mojo/shell/filename_util.h"
+#include "mojo/shell/switches.h"
+#include "url/url_util.h"
+
+namespace mojo {
+namespace shell {
+
+URLResolver::URLResolver() {
+ // Needed to treat first component of mojo URLs as host, not path.
+ url::AddStandardScheme("mojo");
+}
+
+URLResolver::~URLResolver() {
+}
+
+// static
+std::vector<URLResolver::OriginMapping> URLResolver::GetOriginMappings(
+ const base::CommandLine::StringVector& args) {
+ std::vector<OriginMapping> origin_mappings;
+ const std::string kArgsForSwitches[] = {
+ "-" + std::string(switches::kMapOrigin) + "=",
+ "--" + std::string(switches::kMapOrigin) + "=",
+ };
+ for (auto& arg : args) {
+ for (size_t i = 0; i < arraysize(kArgsForSwitches); i++) {
+ const std::string& argsfor_switch = kArgsForSwitches[i];
+ std::string arg_string;
+#if defined(OS_WIN)
+ arg_string = base::UTF16ToUTF8(arg);
+#else
+ arg_string = arg;
+#endif
+ if (arg_string.compare(0, argsfor_switch.size(), argsfor_switch) == 0) {
+ std::string value =
+ arg_string.substr(argsfor_switch.size(), std::string::npos);
+ size_t delim = value.find('=');
+ if (delim <= 0 || delim >= value.size())
+ continue;
+ origin_mappings.push_back(
+ OriginMapping(value.substr(0, delim),
+ value.substr(delim + 1, std::string::npos)));
+ }
+ }
+ }
+ return origin_mappings;
+}
+
+void URLResolver::AddURLMapping(const GURL& url, const GURL& mapped_url) {
+ url_map_[url] = mapped_url;
+}
+
+void URLResolver::AddOriginMapping(const GURL& origin, const GURL& base_url) {
+ if (!origin.is_valid() || !base_url.is_valid() ||
+ origin != origin.GetOrigin()) {
+ // Disallow invalid mappings.
+ LOG(ERROR) << "Invalid origin for mapping: " << origin;
+ return;
+ }
+ // Force both origin and base_url to have trailing slashes.
+ origin_map_[origin] = AddTrailingSlashIfNeeded(base_url);
+}
+
+GURL URLResolver::ApplyMappings(const GURL& url) const {
+ std::string query;
+ GURL mapped_url = GetBaseURLAndQuery(url, &query);
+ for (;;) {
+ const auto& url_it = url_map_.find(mapped_url);
+ if (url_it != url_map_.end()) {
+ mapped_url = url_it->second;
+ continue;
+ }
+
+ GURL origin = mapped_url.GetOrigin();
+ const auto& origin_it = origin_map_.find(origin);
+ if (origin_it == origin_map_.end())
+ break;
+ mapped_url = GURL(origin_it->second.spec() +
+ mapped_url.spec().substr(origin.spec().length()));
+ }
+
+ if (query.length())
+ mapped_url = GURL(mapped_url.spec() + query);
+ return mapped_url;
+}
+
+void URLResolver::SetMojoBaseURL(const GURL& mojo_base_url) {
+ DCHECK(mojo_base_url.is_valid());
+ // Force a trailing slash on the base_url to simplify resolving
+ // relative files and URLs below.
+ mojo_base_url_ = AddTrailingSlashIfNeeded(mojo_base_url);
+}
+
+GURL URLResolver::ResolveMojoURL(const GURL& mojo_url) const {
+ if (mojo_url.scheme() != "mojo") {
+ // The mapping has produced some sort of non-mojo: URL - file:, http:, etc.
+ return mojo_url;
+ } else {
+ // It's still a mojo: URL, use the default mapping scheme.
+ std::string query;
+ GURL base_url = GetBaseURLAndQuery(mojo_url, &query);
+ std::string lib = base_url.host() + ".mojo" + query;
+ return mojo_base_url_.Resolve(lib);
+ }
+}
+
+} // namespace shell
+} // namespace mojo
diff --git a/mojo/shell/url_resolver.h b/mojo/shell/url_resolver.h
new file mode 100644
index 0000000..03f9076
--- /dev/null
+++ b/mojo/shell/url_resolver.h
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_URL_RESOLVER_H_
+#define SHELL_URL_RESOLVER_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "url/gurl.h"
+
+namespace mojo {
+namespace shell {
+
+// This class supports the mapping of URLs to other URLs.
+// It's commonly used with mojo: URL, to provide a physical location (i.e.
+// file: or https:) but works with any URL.
+// By default, "mojo:" URLs resolve to a file location, with ".mojo" appended,
+// but that resolution can be customized via the AddCustomMapping method.
+class URLResolver {
+ public:
+ URLResolver();
+ ~URLResolver();
+
+ // Used for the return value of GetOriginMappings().
+ struct OriginMapping {
+ OriginMapping(const std::string& origin, const std::string& base_url)
+ : origin(origin), base_url(base_url) {}
+
+ std::string origin;
+ std::string base_url;
+ };
+
+ // Returns a list of origin mappings based on command line args.
+ // The switch --map-origin can be specified multiple times. Each occurance
+ // has the format of --map-origin={origin}={base_url}
+ // For example:
+ // --map-origin=http://domokit.org=file:///source/out
+ static std::vector<OriginMapping> GetOriginMappings(
+ const base::CommandLine::StringVector& argv);
+
+ // Add a custom mapping for a particular URL. If |mapped_url| is
+ // itself a mojo url normal resolution rules apply.
+ void AddURLMapping(const GURL& url, const GURL& mapped_url);
+
+ // Add a custom mapping for all urls rooted at |origin|.
+ void AddOriginMapping(const GURL& origin, const GURL& base_url);
+
+ // 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 ApplyMappings(const GURL& url) const;
+
+ // If specified, then "mojo:" URLs will be resolved relative to this
+ // URL. That is, the portion after the colon will be appeneded to
+ // |mojo_base_url| with .mojo appended.
+ void SetMojoBaseURL(const GURL& mojo_base_url);
+
+ // Resolve the given "mojo:" URL to the URL that should be used to fetch the
+ // code for the corresponding Mojo App.
+ GURL ResolveMojoURL(const GURL& mojo_url) const;
+
+ private:
+ using GURLToGURLMap = std::map<GURL, GURL>;
+ GURLToGURLMap url_map_;
+ GURLToGURLMap origin_map_;
+ GURL mojo_base_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLResolver);
+};
+
+} // namespace shell
+} // namespace mojo
+
+#endif // SHELL_URL_RESOLVER_H_
diff --git a/mojo/shell/url_resolver_unittest.cc b/mojo/shell/url_resolver_unittest.cc
new file mode 100644
index 0000000..f959321
--- /dev/null
+++ b/mojo/shell/url_resolver_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/shell/url_resolver.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace shell {
+namespace test {
+namespace {
+
+typedef testing::Test URLResolverTest;
+
+TEST_F(URLResolverTest, MojoURLsFallThrough) {
+ URLResolver resolver;
+ resolver.AddURLMapping(GURL("mojo:test"), GURL("mojo:foo"));
+ const GURL base_url("file:/base");
+ resolver.SetMojoBaseURL(base_url);
+ GURL mapped_url = resolver.ApplyMappings(GURL("mojo:test"));
+ std::string resolved(resolver.ResolveMojoURL(mapped_url).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"));
+}
+
+TEST_F(URLResolverTest, MapURL) {
+ URLResolver resolver;
+ resolver.AddURLMapping(GURL("https://domokit.org/test.mojo"),
+ GURL("file:///mojo/src/out/Debug/test.mojo"));
+ GURL mapped_url =
+ resolver.ApplyMappings(GURL("https://domokit.org/test.mojo"));
+ EXPECT_EQ("file:///mojo/src/out/Debug/test.mojo", mapped_url.spec());
+}
+
+TEST_F(URLResolverTest, MultipleMapURL) {
+ URLResolver resolver;
+ resolver.AddURLMapping(GURL("https://a.org/foo"),
+ GURL("https://b.org/a/foo"));
+ resolver.AddURLMapping(GURL("https://b.org/a/foo"),
+ GURL("https://c.org/b/a/foo"));
+ GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo"));
+ EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec());
+}
+
+TEST_F(URLResolverTest, MapOrigin) {
+ URLResolver resolver;
+ resolver.AddOriginMapping(GURL("https://domokit.org"),
+ GURL("file:///mojo/src/out/Debug"));
+ GURL mapped_url =
+ resolver.ApplyMappings(GURL("https://domokit.org/test.mojo"));
+ EXPECT_EQ("file:///mojo/src/out/Debug/test.mojo", mapped_url.spec());
+}
+
+TEST_F(URLResolverTest, MultipleMapOrigin) {
+ URLResolver resolver;
+ resolver.AddOriginMapping(GURL("https://a.org"), GURL("https://b.org/a"));
+ resolver.AddOriginMapping(GURL("https://b.org"), GURL("https://c.org/b"));
+ GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo"));
+ EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec());
+}
+
+TEST_F(URLResolverTest, MapOriginThenURL) {
+ URLResolver resolver;
+ resolver.AddOriginMapping(GURL("https://a.org"), GURL("https://b.org/a"));
+ resolver.AddURLMapping(GURL("https://b.org/a/foo"),
+ GURL("https://c.org/b/a/foo"));
+ GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo"));
+ EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec());
+}
+
+TEST_F(URLResolverTest, MapURLThenOrigin) {
+ URLResolver resolver;
+ resolver.AddURLMapping(GURL("https://a.org/foo"),
+ GURL("https://b.org/a/foo"));
+ resolver.AddOriginMapping(GURL("https://b.org"), GURL("https://c.org/b"));
+ GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo"));
+ EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec());
+}
+
+#if defined(OS_POSIX)
+#define ARG_LITERAL(x) x
+#elif defined(OS_WIN)
+#define ARG_LITERAL(x) L ## x
+#endif
+
+TEST_F(URLResolverTest, GetOriginMappings) {
+ base::CommandLine::StringVector args;
+ args.push_back(ARG_LITERAL("--map-origin=https://a.org=https://b.org/a"));
+ std::vector<URLResolver::OriginMapping> mappings =
+ URLResolver::GetOriginMappings(args);
+ ASSERT_EQ(1U, mappings.size());
+ EXPECT_EQ("https://a.org", mappings[0].origin);
+ EXPECT_EQ("https://b.org/a", mappings[0].base_url);
+
+ args.clear();
+ args.push_back(ARG_LITERAL("-map-origin=https://a.org=https://b.org/a"));
+ mappings = URLResolver::GetOriginMappings(args);
+ ASSERT_EQ(1U, mappings.size());
+ EXPECT_EQ("https://a.org", mappings[0].origin);
+ EXPECT_EQ("https://b.org/a", mappings[0].base_url);
+
+ args.clear();
+ args.push_back(ARG_LITERAL("--map-origin"));
+ mappings = URLResolver::GetOriginMappings(args);
+ EXPECT_EQ(0U, mappings.size());
+
+ args.clear();
+ args.push_back(ARG_LITERAL("--map-origin="));
+ mappings = URLResolver::GetOriginMappings(args);
+ EXPECT_EQ(0U, mappings.size());
+
+ args.clear();
+ args.push_back(ARG_LITERAL("mojo_shell"));
+ args.push_back(ARG_LITERAL("--args-for=https://a.org/foo --test"));
+ args.push_back(ARG_LITERAL("--map-origin=https://a.org=https://b.org/a"));
+ args.push_back(ARG_LITERAL("--map-origin=https://b.org=https://c.org/b"));
+ args.push_back(ARG_LITERAL("https://a.org/foo"));
+ mappings = URLResolver::GetOriginMappings(args);
+ ASSERT_EQ(2U, mappings.size());
+ EXPECT_EQ("https://a.org", mappings[0].origin);
+ EXPECT_EQ("https://b.org/a", mappings[0].base_url);
+ EXPECT_EQ("https://b.org", mappings[1].origin);
+ EXPECT_EQ("https://c.org/b", mappings[1].base_url);
+}
+
+TEST_F(URLResolverTest, TestQueryForURLMapping) {
+ URLResolver resolver;
+ resolver.SetMojoBaseURL(GURL("file:/base"));
+ resolver.AddURLMapping(GURL("https://a.org/foo"),
+ GURL("https://b.org/a/foo"));
+ resolver.AddURLMapping(GURL("https://b.org/a/foo"),
+ GURL("https://c.org/b/a/foo"));
+ GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo?a=b"));
+ EXPECT_EQ("https://c.org/b/a/foo?a=b", mapped_url.spec());
+}
+
+TEST_F(URLResolverTest, TestQueryForBaseURL) {
+ URLResolver resolver;
+ resolver.SetMojoBaseURL(GURL("file:///base"));
+ GURL mapped_url = resolver.ResolveMojoURL(GURL("mojo:foo?a=b"));
+ EXPECT_EQ("file:///base/foo.mojo?a=b", mapped_url.spec());
+}
+
+} // namespace
+} // namespace test
+} // namespace shell
+} // namespace mojo
diff --git a/third_party/mojo/src/mojo/public/mojo.gni b/third_party/mojo/src/mojo/public/mojo.gni
index f2631a0..e7b078d 100644
--- a/third_party/mojo/src/mojo/public/mojo.gni
+++ b/third_party/mojo/src/mojo/public/mojo.gni
@@ -4,14 +4,6 @@
import("//build/module_args/mojo.gni")
-# If using the prebuilt shell, gate its usage by the platforms for which it is
-# published.
-mojo_use_prebuilt_mojo_shell = false
-if (!defined(mojo_build_mojo_shell_from_source) ||
- !mojo_build_mojo_shell_from_source) {
- mojo_use_prebuilt_mojo_shell = is_linux || is_android
-}
-
# If using the prebuilt network service, gate its usage by the platforms for
# which it is published.
mojo_use_prebuilt_network_service = false
diff --git a/third_party/mojo/src/mojo/public/mojo_application.gni b/third_party/mojo/src/mojo/public/mojo_application.gni
index 2c7ee4a..fd86ae6 100644
--- a/third_party/mojo/src/mojo/public/mojo_application.gni
+++ b/third_party/mojo/src/mojo/public/mojo_application.gni
@@ -77,10 +77,6 @@ template("mojo_native_application") {
}
# Copy any necessary prebuilt artifacts.
- if (mojo_use_prebuilt_mojo_shell) {
- data_deps +=
- [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ]
- }
if (mojo_use_prebuilt_network_service) {
data_deps += [ rebase_path("mojo/public/tools:copy_network_service",
".",
@@ -197,10 +193,6 @@ template("mojo_native_application") {
}
# Copy any necessary prebuilt artifacts.
- if (mojo_use_prebuilt_mojo_shell) {
- data_deps +=
- [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ]
- }
if (mojo_use_prebuilt_network_service) {
data_deps += [ rebase_path("mojo/public/tools:copy_network_service",
".",
diff --git a/third_party/mojo/src/mojo/public/tools/BUILD.gn b/third_party/mojo/src/mojo/public/tools/BUILD.gn
index 9344cf3..607ee7a 100644
--- a/third_party/mojo/src/mojo/public/tools/BUILD.gn
+++ b/third_party/mojo/src/mojo/public/tools/BUILD.gn
@@ -5,29 +5,6 @@
import("//build/module_args/mojo.gni")
import("../mojo.gni")
-if (mojo_use_prebuilt_mojo_shell) {
- copy("copy_mojo_shell") {
- filename = "mojo_shell"
- if (is_android) {
- filename = "MojoShell.apk"
- sources = [
- "prebuilt/shell/android-arm/$filename",
- ]
- outputs = [
- "$root_out_dir/apks/$filename",
- ]
- } else {
- assert(is_linux)
- sources = [
- "prebuilt/shell/linux-x64/$filename",
- ]
- outputs = [
- "$root_out_dir/$filename",
- ]
- }
- }
-}
-
if (mojo_use_prebuilt_network_service) {
copy("copy_network_service") {
filename = "network_service.mojo"