summaryrefslogtreecommitdiff
path: root/chromium/fuchsia
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-02-13 16:23:34 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-02-14 10:37:21 +0000
commit38a9a29f4f9436cace7f0e7abf9c586057df8a4e (patch)
treec4e8c458dc595bc0ddb435708fa2229edfd00bd4 /chromium/fuchsia
parente684a3455bcc29a6e3e66a004e352dea4e1141e7 (diff)
downloadqtwebengine-chromium-38a9a29f4f9436cace7f0e7abf9c586057df8a4e.tar.gz
BASELINE: Update Chromium to 73.0.3683.37
Change-Id: I08c9af2948b645f671e5d933aca1f7a90ea372f2 Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/fuchsia')
-rw-r--r--chromium/fuchsia/BUILD.gn387
-rw-r--r--chromium/fuchsia/DEPS5
-rw-r--r--chromium/fuchsia/OWNERS1
-rw-r--r--chromium/fuchsia/README.md154
-rw-r--r--chromium/fuchsia/browser/DEPS17
-rw-r--r--chromium/fuchsia/browser/context_impl.cc65
-rw-r--r--chromium/fuchsia/browser/context_impl.h69
-rw-r--r--chromium/fuchsia/browser/context_impl_browsertest.cc188
-rw-r--r--chromium/fuchsia/browser/frame_impl.cc580
-rw-r--r--chromium/fuchsia/browser/frame_impl.h161
-rw-r--r--chromium/fuchsia/browser/frame_impl_browsertest.cc968
-rw-r--r--chromium/fuchsia/browser/message_port_impl.cc190
-rw-r--r--chromium/fuchsia/browser/message_port_impl.h75
-rw-r--r--chromium/fuchsia/browser/webrunner_browser_context.cc177
-rw-r--r--chromium/fuchsia/browser/webrunner_browser_context.h73
-rw-r--r--chromium/fuchsia/browser/webrunner_browser_main.cc29
-rw-r--r--chromium/fuchsia/browser/webrunner_browser_main.h18
-rw-r--r--chromium/fuchsia/browser/webrunner_browser_main_parts.cc84
-rw-r--r--chromium/fuchsia/browser/webrunner_browser_main_parts.h52
-rw-r--r--chromium/fuchsia/browser/webrunner_content_browser_client.cc34
-rw-r--r--chromium/fuchsia/browser/webrunner_content_browser_client.h38
-rw-r--r--chromium/fuchsia/browser/webrunner_net_log.cc54
-rw-r--r--chromium/fuchsia/browser/webrunner_net_log.h36
-rw-r--r--chromium/fuchsia/browser/webrunner_screen.cc19
-rw-r--r--chromium/fuchsia/browser/webrunner_screen.h26
-rw-r--r--chromium/fuchsia/browser/webrunner_url_request_context_getter.cc77
-rw-r--r--chromium/fuchsia/browser/webrunner_url_request_context_getter.h57
-rw-r--r--chromium/fuchsia/cipd/build_id.template1
-rw-r--r--chromium/fuchsia/cipd/castrunner.yaml33
-rw-r--r--chromium/fuchsia/cipd/fidl.yaml22
-rw-r--r--chromium/fuchsia/cipd/http.yaml32
-rw-r--r--chromium/fuchsia/cipd/webrunner.yaml33
-rw-r--r--chromium/fuchsia/common/DEPS8
-rw-r--r--chromium/fuchsia/common/OWNERS2
-rw-r--r--chromium/fuchsia/common/fuchsia_export.h20
-rw-r--r--chromium/fuchsia/common/mem_buffer_util.cc88
-rw-r--r--chromium/fuchsia/common/mem_buffer_util.h42
-rw-r--r--chromium/fuchsia/common/named_message_port_connector.cc127
-rw-r--r--chromium/fuchsia/common/named_message_port_connector.h70
-rw-r--r--chromium/fuchsia/common/named_message_port_connector.js117
-rw-r--r--chromium/fuchsia/common/named_message_port_connector_browsertest.cc123
-rw-r--r--chromium/fuchsia/common/on_load_script_injector.mojom14
-rw-r--r--chromium/fuchsia/common/webrunner_content_client.cc43
-rw-r--r--chromium/fuchsia/common/webrunner_content_client.h33
-rw-r--r--chromium/fuchsia/fidl/cast/application_config.fidl24
-rw-r--r--chromium/fuchsia/fidl/cast/cast_channel.fidl17
-rw-r--r--chromium/fuchsia/fidl/web/context.fidl14
-rw-r--r--chromium/fuchsia/fidl/web/context_provider.fidl29
-rw-r--r--chromium/fuchsia/fidl/web/frame.fidl137
-rw-r--r--chromium/fuchsia/fidl/web/navigation_controller.fidl71
-rw-r--r--chromium/fuchsia/fidl/web/navigation_event_observer.fidl25
-rw-r--r--chromium/fuchsia/http/BUILD.gn63
-rw-r--r--chromium/fuchsia/http/http_service_impl.cc22
-rw-r--r--chromium/fuchsia/http/http_service_impl.h28
-rw-r--r--chromium/fuchsia/http/http_service_main.cc40
-rw-r--r--chromium/fuchsia/http/http_service_unittest.cc442
-rw-r--r--chromium/fuchsia/http/sandbox_policy7
-rw-r--r--chromium/fuchsia/http/url_loader_impl.cc429
-rw-r--r--chromium/fuchsia/http/url_loader_impl.h121
-rw-r--r--chromium/fuchsia/renderer/DEPS6
-rw-r--r--chromium/fuchsia/renderer/on_load_script_injector.cc66
-rw-r--r--chromium/fuchsia/renderer/on_load_script_injector.h50
-rw-r--r--chromium/fuchsia/renderer/webrunner_content_renderer_client.cc26
-rw-r--r--chromium/fuchsia/renderer/webrunner_content_renderer_client.h27
-rw-r--r--chromium/fuchsia/runners/BUILD.gn205
-rw-r--r--chromium/fuchsia/runners/cast/bindings/cast_channel.cc117
-rw-r--r--chromium/fuchsia/runners/cast/bindings/cast_channel.h66
-rw-r--r--chromium/fuchsia/runners/cast/bindings/cast_channel.js74
-rw-r--r--chromium/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc218
-rw-r--r--chromium/fuchsia/runners/cast/cast_runner.cc75
-rw-r--r--chromium/fuchsia/runners/cast/cast_runner.h42
-rw-r--r--chromium/fuchsia/runners/cast/cast_runner_integration_test.cc153
-rw-r--r--chromium/fuchsia/runners/cast/cast_runner_unittest.cc125
-rw-r--r--chromium/fuchsia/runners/cast/fake_application_config_manager.cc30
-rw-r--r--chromium/fuchsia/runners/cast/fake_application_config_manager.h32
-rw-r--r--chromium/fuchsia/runners/cast/main.cc27
-rw-r--r--chromium/fuchsia/runners/cast/sandbox_policy16
-rw-r--r--chromium/fuchsia/runners/cast/test_common.cc37
-rw-r--r--chromium/fuchsia/runners/cast/test_common.h20
-rw-r--r--chromium/fuchsia/runners/common/web_component.cc121
-rw-r--r--chromium/fuchsia/runners/common/web_component.h110
-rw-r--r--chromium/fuchsia/runners/common/web_content_runner.cc109
-rw-r--r--chromium/fuchsia/runners/common/web_content_runner.h81
-rw-r--r--chromium/fuchsia/runners/web/main.cc23
-rw-r--r--chromium/fuchsia/runners/web/sandbox_policy17
-rw-r--r--chromium/fuchsia/runners/web/web_runner_smoke_test.cc78
-rw-r--r--chromium/fuchsia/service/DEPS6
-rw-r--r--chromium/fuchsia/service/common.cc11
-rw-r--r--chromium/fuchsia/service/common.h27
-rw-r--r--chromium/fuchsia/service/context_provider_impl.cc140
-rw-r--r--chromium/fuchsia/service/context_provider_impl.h71
-rw-r--r--chromium/fuchsia/service/context_provider_impl_unittest.cc271
-rw-r--r--chromium/fuchsia/service/context_provider_main.cc30
-rw-r--r--chromium/fuchsia/service/context_provider_main.h19
-rw-r--r--chromium/fuchsia/service/sandbox_policy6
-rw-r--r--chromium/fuchsia/service/web_content_service_main.cc45
-rw-r--r--chromium/fuchsia/service/webrunner_main_delegate.cc101
-rw-r--r--chromium/fuchsia/service/webrunner_main_delegate.h58
98 files changed, 8647 insertions, 0 deletions
diff --git a/chromium/fuchsia/BUILD.gn b/chromium/fuchsia/BUILD.gn
new file mode 100644
index 00000000000..e681ba0a0e7
--- /dev/null
+++ b/chromium/fuchsia/BUILD.gn
@@ -0,0 +1,387 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(is_fuchsia)
+
+import("//build/config/fuchsia/fidl_library.gni")
+import("//build/config/fuchsia/rules.gni")
+import("//build/util/process_version.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+import("//testing/test.gni")
+import("//tools/grit/repack.gni")
+
+config("webrunner_implementation") {
+ defines = [ "WEBRUNNER_IMPLEMENTATION" ]
+}
+
+source_set("named_message_port_connector") {
+ sources = [
+ "common/named_message_port_connector.cc",
+ "common/named_message_port_connector.h",
+ ]
+ data = [
+ "common/named_message_port_connector.js",
+ ]
+ deps = [
+ ":mem_buffer_common",
+ ":web_fidl",
+ "//base",
+ "//third_party/fuchsia-sdk/sdk:mem",
+ ]
+ configs += [ ":webrunner_implementation" ]
+}
+
+source_set("mem_buffer_common") {
+ sources = [
+ "common/fuchsia_export.h",
+ "common/mem_buffer_util.cc",
+ "common/mem_buffer_util.h",
+ ]
+ deps = [
+ "//base",
+ "//third_party/fuchsia-sdk/sdk:mem",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ # TODO(kmarshall): Move common/test/* to test/.
+ "common/test/test_common.cc",
+ "common/test/test_common.h",
+ "test/fake_context.cc",
+ "test/fake_context.h",
+ "test/promise.h",
+ ]
+ deps = [
+ ":mem_buffer_common",
+ ":web_fidl",
+ "//base",
+ "//base/test:test_config",
+ "//content/test:test_support",
+ ]
+ public_deps = [
+ ":web_fidl",
+ ]
+}
+
+fuchsia_package("service_pkg") {
+ binary = ":service_exe"
+ package_name_override = "chromium"
+ sandbox_policy = "service/sandbox_policy"
+ excluded_files = [
+ "lib/libswiftshader_libEGL.so",
+ "lib/libswiftshader_libGLESv2.so",
+ ]
+}
+
+executable("service_exe") {
+ deps = [
+ ":service_lib",
+ ":web_fidl",
+ "//base",
+ "//content/public/app:both",
+ "//services/service_manager/embedder:embedder_switches",
+ ]
+ sources = [
+ "service/web_content_service_main.cc",
+ ]
+}
+
+fuchsia_package_runner("service_runner") {
+ package = ":service_pkg"
+ package_name_override = "chromium"
+ install_only = true
+}
+
+mojom("mojom") {
+ sources = [
+ "common/on_load_script_injector.mojom",
+ ]
+
+ public_deps = [
+ "//mojo/public/mojom/base",
+ ]
+}
+
+component("service_lib") {
+ deps = [
+ ":mem_buffer_common",
+ ":mojom",
+ ":service_pak",
+ "//base",
+ "//components/version_info",
+ "//content/public/app:both",
+ "//content/public/browser",
+ "//content/public/child",
+ "//content/public/common",
+ "//content/public/renderer",
+ "//mojo/public/cpp/bindings",
+ "//services/network/public/cpp",
+ "//services/service_manager/sandbox",
+ "//skia/public/interfaces",
+ "//third_party/blink/public/common",
+ "//ui/aura",
+ "//ui/base/ime",
+ "//ui/display",
+ "//ui/ozone",
+ "//ui/platform_window",
+ "//ui/wm",
+ "//ui/wm/public",
+ ]
+
+ data_deps = [
+ ":service_pak",
+ ]
+ data = [
+ "$root_out_dir/webrunner.pak",
+ ]
+ public_deps = [
+ ":web_fidl",
+ ]
+ configs += [ ":webrunner_implementation" ]
+
+ sources = [
+ "browser/context_impl.cc",
+ "browser/context_impl.h",
+ "browser/frame_impl.cc",
+ "browser/frame_impl.h",
+ "browser/message_port_impl.cc",
+ "browser/message_port_impl.h",
+ "browser/webrunner_browser_context.cc",
+ "browser/webrunner_browser_context.h",
+ "browser/webrunner_browser_main.cc",
+ "browser/webrunner_browser_main.h",
+ "browser/webrunner_browser_main_parts.cc",
+ "browser/webrunner_browser_main_parts.h",
+ "browser/webrunner_content_browser_client.cc",
+ "browser/webrunner_content_browser_client.h",
+ "browser/webrunner_net_log.cc",
+ "browser/webrunner_net_log.h",
+ "browser/webrunner_screen.cc",
+ "browser/webrunner_screen.h",
+ "browser/webrunner_url_request_context_getter.cc",
+ "browser/webrunner_url_request_context_getter.h",
+ "common/webrunner_content_client.cc",
+ "common/webrunner_content_client.h",
+ "renderer/on_load_script_injector.cc",
+ "renderer/on_load_script_injector.h",
+ "renderer/webrunner_content_renderer_client.cc",
+ "renderer/webrunner_content_renderer_client.h",
+ "service/common.cc",
+ "service/common.h",
+ "service/context_provider_impl.cc",
+ "service/context_provider_impl.h",
+ "service/context_provider_main.cc",
+ "service/context_provider_main.h",
+ "service/webrunner_main_delegate.cc",
+ "service/webrunner_main_delegate.h",
+ ]
+}
+
+repack("service_pak") {
+ sources = [
+ "$root_gen_dir/components/components_resources.pak",
+ "$root_gen_dir/components/strings/components_strings_en-US.pak",
+ "$root_gen_dir/content/app/resources/content_resources_100_percent.pak",
+ "$root_gen_dir/content/app/strings/content_strings_en-US.pak",
+ "$root_gen_dir/content/browser/tracing/tracing_resources.pak",
+ "$root_gen_dir/content/content_resources.pak",
+ "$root_gen_dir/mojo/public/js/mojo_bindings_resources.pak",
+ "$root_gen_dir/net/net_resources.pak",
+ "$root_gen_dir/third_party/blink/public/resources/blink_resources.pak",
+ "$root_gen_dir/third_party/blink/public/resources/blink_scaled_resources_100_percent.pak",
+ "$root_gen_dir/ui/resources/ui_resources_100_percent.pak",
+ "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+ "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
+ ]
+
+ deps = [
+ "//components/resources:components_resources",
+ "//components/strings",
+ "//content:resources",
+ "//content/app/resources",
+ "//content/app/strings",
+ "//content/browser/tracing:resources",
+ "//mojo/public/js:resources",
+ "//net:net_resources",
+ "//third_party/blink/public:resources",
+ "//third_party/blink/public:scaled_resources_100_percent",
+ "//ui/resources",
+ "//ui/strings",
+ ]
+
+ output = "$root_out_dir/webrunner.pak"
+}
+
+source_set("browsertest_common") {
+ testonly = true
+ sources = [
+ "common/test/webrunner_browser_test.cc",
+ "common/test/webrunner_browser_test.h",
+ "common/test/webrunner_test_launcher.cc",
+ ]
+ deps = [
+ ":service_lib",
+ ":web_fidl",
+ "//content/public/browser",
+ "//content/test:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ "//ui/ozone",
+ ]
+}
+
+test("webrunner_browsertests") {
+ sources = [
+ "browser/context_impl_browsertest.cc",
+ "browser/frame_impl_browsertest.cc",
+ "common/named_message_port_connector_browsertest.cc",
+ ]
+ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+ data = [
+ "browser/test/data",
+ "common/test/data",
+ ]
+ data_deps = [
+ ":named_message_port_connector",
+ ]
+ deps = [
+ ":browsertest_common",
+ ":mem_buffer_common",
+ ":named_message_port_connector",
+ ":service_lib",
+ ":test_support",
+ ":test_support",
+ ":web_fidl",
+ "//base/test:test_support",
+ "//content/public/browser",
+ "//net:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//ui/ozone",
+ ]
+}
+
+test("webrunner_unittests") {
+ sources = [
+ "service/context_provider_impl_unittest.cc",
+ ]
+ deps = [
+ ":service_lib",
+ ":test_support",
+ ":web_fidl",
+ "//base/test:run_all_unittests",
+ "//base/test:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+fidl_library("web_fidl") {
+ library_name = "web"
+ namespace = "chromium"
+
+ sources = [
+ "fidl/web/context.fidl",
+ "fidl/web/context_provider.fidl",
+ "fidl/web/frame.fidl",
+ "fidl/web/navigation_controller.fidl",
+ "fidl/web/navigation_event_observer.fidl",
+ ]
+
+ public_deps = [
+ "//third_party/fuchsia-sdk/sdk:sys",
+ "//third_party/fuchsia-sdk/sdk:ui_gfx",
+ "//third_party/fuchsia-sdk/sdk:ui_viewsv1token",
+ ]
+}
+
+fidl_library("cast_fidl") {
+ library_name = "cast"
+ namespace = "chromium"
+
+ sources = [
+ "fidl/cast/application_config.fidl",
+ "fidl/cast/cast_channel.fidl",
+ ]
+
+ public_deps = [
+ ":web_fidl",
+ ]
+}
+
+# gn binary location.
+if (host_os == "mac") {
+ _gn_path = "//buildtools/mac/gn"
+} else if (host_os == "linux") {
+ _gn_path = "//buildtools/linux64/gn"
+}
+
+# Build location where all Fuchsia archive source files are placed.
+_artifact_root = "$root_out_dir/fuchsia_artifacts"
+
+# Produces a LICENSE file for webrunner and its transitive dependencies.
+_license_path = "$_artifact_root/LICENSE"
+action("license") {
+ script = "//tools/licenses.py"
+ inputs = [
+ "$_gn_path",
+ ]
+ outputs = [
+ _license_path,
+ ]
+ args = [
+ "license_file",
+ rebase_path(_license_path, root_build_dir),
+ "--gn-target",
+ "//fuchsia:webrunner_pkg",
+ "--gn-out-dir",
+ ".",
+ ]
+}
+
+# Extracts the numeric Chrome build ID and writes it to a file in the output
+# directory.
+#
+# To check out the repository on the commit where the build ID was generated,
+# simply call `git checkout <build-id>`, and Git will check out the commit
+# associated with the <build-id> tag.
+process_version("build_id") {
+ template_file = "cipd/build_id.template"
+ sources = [
+ "//chrome/VERSION",
+ ]
+ output = "$_artifact_root/build_id.txt"
+ process_only = true
+}
+
+# Puts copies of files at the top level of the CIPD archive's structure.
+copy("restaged_packages") {
+ sources = [
+ "$root_gen_dir/fuchsia/chromium/chromium.far",
+ "$root_gen_dir/fuchsia/http/http/http.far",
+ "$root_gen_dir/fuchsia/runners/cast_runner/cast_runner.far",
+ "$root_gen_dir/fuchsia/runners/web_runner/web_runner.far",
+ ]
+ outputs = [
+ "$_artifact_root/{{source_file_part}}",
+ ]
+ deps = [
+ ":service_pkg",
+ "//fuchsia/http:http_pkg",
+ "//fuchsia/runners:cast_runner_pkg",
+ "//fuchsia/runners:web_runner_pkg",
+ ]
+}
+
+# Specifies the build steps that must be performed before the creation of
+# a CIPD archive.
+group("archive_sources") {
+ deps = [
+ ":build_id",
+ ":license",
+ ":restaged_packages",
+ ]
+}
diff --git a/chromium/fuchsia/DEPS b/chromium/fuchsia/DEPS
new file mode 100644
index 00000000000..e04e926f04e
--- /dev/null
+++ b/chromium/fuchsia/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+content/public/common",
+ "+net",
+ "+services/network/public",
+] \ No newline at end of file
diff --git a/chromium/fuchsia/OWNERS b/chromium/fuchsia/OWNERS
new file mode 100644
index 00000000000..e7034eabb1e
--- /dev/null
+++ b/chromium/fuchsia/OWNERS
@@ -0,0 +1 @@
+file://build/fuchsia/OWNERS
diff --git a/chromium/fuchsia/README.md b/chromium/fuchsia/README.md
new file mode 100644
index 00000000000..e2a84e2805b
--- /dev/null
+++ b/chromium/fuchsia/README.md
@@ -0,0 +1,154 @@
+# Chromium web_runner
+This directory contains the web_runner implementation. Web_runner enables
+Fuchsia applications to embed Chrome frames for rendering web content.
+
+
+### Building and deploying web_runner
+When you build web_runner, Chromium will automatically generate scripts for
+you that will automatically provision a device with Fuchsia and then install
+`web_runner` and its dependencies.
+
+To build and run web_runner, follow these steps:
+
+0. Ensure that you have a device ready to boot into Fuchsia.
+
+ If you wish to have WebRunner manage the OS deployment process, then you
+ should have the device booting into
+ [Zedboot](https://fuchsia.googlesource.com/zircon/+/master/docs/targets/bootloader_setup.md).
+
+1. Build web_runner.
+
+ ```
+ $ autoninja -C out/Debug webrunner
+ ```
+
+2. Install web_runner.
+
+ * **For devices running Zedboot**
+
+ ```
+ $ out/Debug/bin/install_webrunner -d
+ ```
+
+ * **For devices already running Fuchsia**
+
+ You will need to add command line flags specifying the device's IP
+ address and the path to the `ssh_config` used by the device
+ (located at `FUCHSIA_OUT_DIR/ssh-keys/ssh_config`):
+
+ ```
+ $ out/Debug/bin/install_webrunner -d --ssh-config PATH_TO_SSH_CONFIG
+ --host DEVICE_IP
+ ```
+
+3. Run "tiles" on the device.
+
+ ```
+ $ run tiles&
+ ```
+
+4. Press the OS key on your device to switch back to terminal mode.
+ (Also known as the "Windows key" or "Super key" on many keyboards).
+
+5. Launch a webpage.
+
+ ```
+ $ tiles_ctl add https://www.chromium.org/
+ ```
+
+6. Press the OS key to switch back to graphical view. The browser window should
+ be displayed and ready to use.
+
+7. You can deploy and run new versions of Chromium without needing to reboot.
+
+ First kill any running processes:
+
+ ```
+ $ killall chromium; killall web_runner
+ ```
+
+ Then repeat steps 1 through 6 from the installation instructions, excluding
+ step #3 (running Tiles).
+
+
+### Closing a webpage
+
+1. Press the Windows key to return to the terminal.
+
+2. Instruct tiles_ctl to remove the webpage's window tile. The tile's number is
+ reported by step 6, or it can be found by running `tiles_ctl list` and
+ noting the ID of the "url" entry.
+
+ ```shell
+ $ tiles_ctl remove TILE_NUMBER
+ ```
+
+### Debugging
+
+Rudimentary debugging is now possible with zxdb which is included in the SDK.
+It is still early and fairly manual to set up. After following the steps above:
+
+1. On device, run `sysinfo` to see your device's IP address.
+
+1. On device, run `debug_agent --port=2345`.
+
+1. On the host, run
+
+```
+third_party/fuchsia_sdk/sdk/tools/zxdb -s out/Debug/exe.unstripped -s out/Debug/lib.unstripped
+```
+
+1. In zxdb, `connect <ip-from-sysinfo-above> 2345`.
+
+1. On the host, run `ps` and find the pid of the process you want to debug, e.g.
+ `web_runner`.
+
+1. In zxdb, `attach <pid>`. You should be able to attach to multiple processes.
+
+1. In zxdb, `b ComponentControllerImpl::CreateForRequest` to set a breakpoint.
+
+1. On device, do something to make your breakpoint be hit. In this case
+ `tiles_ctl add https://www.google.com/` should cause a new request.
+
+At this point, you should hit the breakpoint in zxdb.
+
+```
+[zxdb] l
+ 25 fuchsia::sys::Package package,
+ 26 fuchsia::sys::StartupInfo startup_info,
+ 27 fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ 28 controller_request) {
+ 29 std::unique_ptr<ComponentControllerImpl> result{
+ â–¶ 30 new ComponentControllerImpl(runner)};
+ 31 if (!result->BindToRequest(std::move(package), std::move(startup_info),
+ 32 std::move(controller_request))) {
+ 33 return nullptr;
+ 34 }
+ 35 return result;
+ 36 }
+ 37
+ 38 ComponentControllerImpl::ComponentControllerImpl(WebContentRunner* runner)
+ 39 : runner_(runner), controller_binding_(this) {
+ 40 DCHECK(runner);
+[zxdb] f
+▶ 0 webrunner::ComponentControllerImpl::CreateForRequest() • component_controller_impl.cc:30
+ 1 webrunner::WebContentRunner::StartComponent() • web_content_runner.cc:34
+ 2 fuchsia::sys::Runner_Stub::Dispatch_() • fidl.cc:1255
+ 3 fidl::internal::StubController::OnMessage() • stub_controller.cc:38
+ 4 fidl::internal::MessageReader::ReadAndDispatchMessage() • message_reader.cc:213
+ 5 fidl::internal::MessageReader::OnHandleReady() • message_reader.cc:179
+ 6 fidl::internal::MessageReader::CallHandler() • message_reader.cc:166
+ 7 base::AsyncDispatcher::DispatchOrWaitUntil() • async_dispatcher.cc:183
+ 8 base::MessagePumpFuchsia::HandleEvents() • message_pump_fuchsia.cc:236
+ 9 base::MessagePumpFuchsia::Run() • message_pump_fuchsia.cc:282
+ 10 base::MessageLoop::Run() + 0x22b (no line info)
+ 11 base::RunLoop::Run() • run_loop.cc:102
+ 12 main() • main.cc:74
+ 13 0x472010320b8f
+ 14 0x0
+[zxdb]
+```
+
+https://fuchsia.googlesource.com/garnet/+/master/docs/debugger.md#diagnosing-symbol-problems
+maybe be a useful reference if you do not see symbols. That page also has
+general help on using the debugger.
diff --git a/chromium/fuchsia/browser/DEPS b/chromium/fuchsia/browser/DEPS
new file mode 100644
index 00000000000..f0d14a4ff39
--- /dev/null
+++ b/chromium/fuchsia/browser/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+ "+components/version_info",
+ "+content/public/common",
+ "+content/public/browser",
+ "+content/public/test",
+ "+mojo/public/cpp/bindings",
+ "+mojo/public/cpp/system",
+ "+third_party/blink/public/common/associated_interfaces",
+ "+third_party/blink/public/common/messaging",
+ "+third_party/blink/public/mojom/messaging",
+ "+ui/aura",
+ "+ui/base/ime",
+ "+ui/display",
+ "+ui/ozone/public",
+ "+ui/platform_window",
+ "+ui/wm/core",
+]
diff --git a/chromium/fuchsia/browser/context_impl.cc b/chromium/fuchsia/browser/context_impl.cc
new file mode 100644
index 00000000000..d1f471add40
--- /dev/null
+++ b/chromium/fuchsia/browser/context_impl.cc
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/context_impl.h"
+
+#include <lib/zx/object.h>
+#include <memory>
+#include <utility>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "content/public/browser/web_contents.h"
+#include "fuchsia/browser/frame_impl.h"
+
+namespace webrunner {
+
+ContextImpl::ContextImpl(content::BrowserContext* browser_context)
+ : browser_context_(browser_context) {}
+
+ContextImpl::~ContextImpl() = default;
+
+void ContextImpl::CreateFrame(
+ fidl::InterfaceRequest<chromium::web::Frame> frame_request) {
+ auto web_contents = content::WebContents::Create(
+ content::WebContents::CreateParams(browser_context_, nullptr));
+ frames_.insert(std::make_unique<FrameImpl>(std::move(web_contents), this,
+ std::move(frame_request)));
+}
+
+void ContextImpl::DestroyFrame(FrameImpl* frame) {
+ DCHECK(frames_.find(frame) != frames_.end());
+ frames_.erase(frames_.find(frame));
+}
+
+bool ContextImpl::IsJavaScriptInjectionAllowed() {
+ return allow_javascript_injection_;
+}
+
+FrameImpl* ContextImpl::GetFrameImplForTest(
+ chromium::web::FramePtr* frame_ptr) {
+ DCHECK(frame_ptr);
+
+ // Find the FrameImpl whose channel is connected to |frame_ptr| by inspecting
+ // the related_koids of active FrameImpls.
+ zx_info_handle_basic_t handle_info;
+ zx_status_t status = frame_ptr->channel().get_info(
+ ZX_INFO_HANDLE_BASIC, &handle_info, sizeof(zx_info_handle_basic_t),
+ nullptr, nullptr);
+ ZX_CHECK(status == ZX_OK, status) << "zx_object_get_info";
+ zx_handle_t client_handle_koid = handle_info.koid;
+
+ for (const std::unique_ptr<FrameImpl>& frame : frames_) {
+ status = frame->GetBindingChannelForTest()->get_info(
+ ZX_INFO_HANDLE_BASIC, &handle_info, sizeof(zx_info_handle_basic_t),
+ nullptr, nullptr);
+ ZX_CHECK(status == ZX_OK, status) << "zx_object_get_info";
+
+ if (client_handle_koid == handle_info.related_koid)
+ return frame.get();
+ }
+
+ return nullptr;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/context_impl.h b/chromium/fuchsia/browser/context_impl.h
new file mode 100644
index 00000000000..cfa95da5802
--- /dev/null
+++ b/chromium/fuchsia/browser/context_impl.h
@@ -0,0 +1,69 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_CONTEXT_IMPL_H_
+#define FUCHSIA_BROWSER_CONTEXT_IMPL_H_
+
+#include <lib/fidl/cpp/binding_set.h>
+#include <memory>
+#include <set>
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/macros.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace webrunner {
+
+class FrameImpl;
+
+// Implementation of Context from //fuchsia/fidl/context.fidl.
+// Owns a BrowserContext instance and uses it to create new WebContents/Frames.
+// All created Frames are owned by this object.
+class FUCHSIA_EXPORT ContextImpl : public chromium::web::Context {
+ public:
+ // |browser_context| must outlive ContextImpl.
+ explicit ContextImpl(content::BrowserContext* browser_context);
+
+ // Tears down the Context, destroying any active Frames in the process.
+ ~ContextImpl() override;
+
+ content::BrowserContext* browser_context_for_test() {
+ return browser_context_;
+ }
+
+ // Removes and destroys the specified |frame|.
+ void DestroyFrame(FrameImpl* frame);
+
+ // Returns |true| if JS injection was enabled for this Context.
+ bool IsJavaScriptInjectionAllowed();
+
+ // chromium::web::Context implementation.
+ void CreateFrame(fidl::InterfaceRequest<chromium::web::Frame> frame) override;
+
+ // Gets the underlying FrameImpl service object associated with a connected
+ // |frame_ptr| client.
+ FrameImpl* GetFrameImplForTest(chromium::web::FramePtr* frame_ptr);
+
+ private:
+ content::BrowserContext* browser_context_;
+
+ // TODO(crbug.com/893236): Make this false by default, and allow it to be
+ // initialized at Context creation time.
+ bool allow_javascript_injection_ = true;
+
+ // Tracks all active FrameImpl instances, so that we can request their
+ // destruction when this ContextImpl is destroyed.
+ std::set<std::unique_ptr<FrameImpl>, base::UniquePtrComparator> frames_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextImpl);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_CONTEXT_IMPL_H_
diff --git a/chromium/fuchsia/browser/context_impl_browsertest.cc b/chromium/fuchsia/browser/context_impl_browsertest.cc
new file mode 100644
index 00000000000..797be530e47
--- /dev/null
+++ b/chromium/fuchsia/browser/context_impl_browsertest.cc
@@ -0,0 +1,188 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/task/post_task.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/service/common.h"
+#include "net/cookies/cookie_store.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_constants.h"
+
+namespace webrunner {
+
+using testing::_;
+using testing::Field;
+using testing::InvokeWithoutArgs;
+
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
+
+// Defines a suite of tests that exercise browser-level configuration and
+// functionality.
+class ContextImplTest : public WebRunnerBrowserTest {
+ public:
+ ContextImplTest() = default;
+ ~ContextImplTest() = default;
+
+ protected:
+ // Creates a Frame with |navigation_observer_| attached.
+ chromium::web::FramePtr CreateFrame() {
+ return WebRunnerBrowserTest::CreateFrame(&navigation_observer_);
+ }
+
+ // Synchronously gets a list of cookies for this BrowserContext.
+ net::CookieList GetCookies();
+
+ testing::StrictMock<MockNavigationObserver> navigation_observer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContextImplTest);
+};
+
+void OnCookiesReceived(net::CookieList* output,
+ base::OnceClosure on_received_cb,
+ const net::CookieList& cookies) {
+ *output = cookies;
+ std::move(on_received_cb).Run();
+}
+
+net::CookieList ContextImplTest::GetCookies() {
+ net::CookieStore* cookie_store =
+ content::BrowserContext::GetDefaultStoragePartition(
+ context_impl()->browser_context_for_test())
+ ->GetURLRequestContext()
+ ->GetURLRequestContext()
+ ->cookie_store();
+
+ base::RunLoop run_loop;
+ net::CookieList cookies;
+ base::PostTaskWithTraits(
+ FROM_HERE, {content::BrowserThread::IO},
+ base::BindOnce(
+ &net::CookieStore::GetAllCookiesAsync, base::Unretained(cookie_store),
+ base::BindOnce(&OnCookiesReceived, base::Unretained(&cookies),
+ run_loop.QuitClosure())));
+ run_loop.Run();
+ return cookies;
+}
+
+// Verifies that the BrowserContext has a working cookie store by setting
+// cookies in the content layer and then querying the CookieStore afterward.
+IN_PROC_BROWSER_TEST_F(ContextImplTest, VerifyPersistentCookieStore) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL cookie_url(embedded_test_server()->GetURL("/set-cookie?foo=bar"));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr nav;
+ frame->GetNavigationController(nav.NewRequest());
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, MockableOnNavigationStateChanged(_))
+ .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+
+ nav->LoadUrl(cookie_url.spec(), nullptr);
+ run_loop.Run();
+ }
+
+ auto cookies = GetCookies();
+ bool found = false;
+ for (auto c : cookies) {
+ if (c.Name() == "foo" && c.Value() == "bar") {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+
+ // Check that the cookie persists beyond the lifetime of the Frame by
+ // releasing the Frame and re-querying the CookieStore.
+ frame.Unbind();
+ base::RunLoop().RunUntilIdle();
+
+ found = false;
+ for (auto c : cookies) {
+ if (c.Name() == "foo" && c.Value() == "bar") {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+}
+
+// Suite for tests which run the BrowserContext in incognito mode (no data
+// directory).
+class IncognitoContextImplTest : public ContextImplTest {
+ public:
+ IncognitoContextImplTest() = default;
+ ~IncognitoContextImplTest() override = default;
+
+ void SetUp() override {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(kIncognitoSwitch);
+ ContextImplTest::SetUp();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IncognitoContextImplTest);
+};
+
+// Verify that the browser can be initialized without a persistent data
+// directory.
+IN_PROC_BROWSER_TEST_F(IncognitoContextImplTest, NavigateFrame) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(
+ Field(&NavigationDetails::url, url::kAboutBlankURL)))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(url::kAboutBlankURL, nullptr);
+ run_loop.Run();
+
+ frame.Unbind();
+}
+
+IN_PROC_BROWSER_TEST_F(IncognitoContextImplTest, VerifyInMemoryCookieStore) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL cookie_url(embedded_test_server()->GetURL("/set-cookie?foo=bar"));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr nav;
+ frame->GetNavigationController(nav.NewRequest());
+
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, MockableOnNavigationStateChanged(_))
+ .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+
+ nav->LoadUrl(cookie_url.spec(), nullptr);
+ run_loop.Run();
+
+ auto cookies = GetCookies();
+ bool found = false;
+ for (auto c : cookies) {
+ if (c.Name() == "foo" && c.Value() == "bar") {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/frame_impl.cc b/chromium/fuchsia/browser/frame_impl.cc
new file mode 100644
index 00000000000..b2fd2d30a37
--- /dev/null
+++ b/chromium/fuchsia/browser/frame_impl.cc
@@ -0,0 +1,580 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/frame_impl.h"
+
+#include <zircon/syscalls.h>
+
+#include <string>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/message_port_provider.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/renderer_preferences_util.h"
+#include "content/public/common/was_activated_option.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/browser/message_port_impl.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tree_host_platform.h"
+#include "ui/base/ime/input_method_base.h"
+#include "ui/platform_window/platform_window_init_properties.h"
+#include "ui/wm/core/base_focus_rules.h"
+#include "url/gurl.h"
+
+namespace webrunner {
+
+namespace {
+
+// Layout manager that allows only one child window and stretches it to fill the
+// parent.
+class LayoutManagerImpl : public aura::LayoutManager {
+ public:
+ LayoutManagerImpl() = default;
+ ~LayoutManagerImpl() override = default;
+
+ // aura::LayoutManager.
+ void OnWindowResized() override {
+ // Resize the child to match the size of the parent
+ if (child_) {
+ SetChildBoundsDirect(child_,
+ gfx::Rect(child_->parent()->bounds().size()));
+ }
+ }
+ void OnWindowAddedToLayout(aura::Window* child) override {
+ DCHECK(!child_);
+ child_ = child;
+ SetChildBoundsDirect(child_, gfx::Rect(child_->parent()->bounds().size()));
+ }
+
+ void OnWillRemoveWindowFromLayout(aura::Window* child) override {
+ DCHECK_EQ(child, child_);
+ child_ = nullptr;
+ }
+
+ void OnWindowRemovedFromLayout(aura::Window* child) override {}
+ void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) override {}
+ void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) override {}
+
+ private:
+ aura::Window* child_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(LayoutManagerImpl);
+};
+
+chromium::web::NavigationEntry ConvertContentNavigationEntry(
+ content::NavigationEntry* entry) {
+ DCHECK(entry);
+ chromium::web::NavigationEntry converted;
+ converted.title = base::UTF16ToUTF8(entry->GetTitleForDisplay());
+ converted.url = entry->GetURL().spec();
+ converted.is_error =
+ entry->GetPageType() == content::PageType::PAGE_TYPE_ERROR;
+ return converted;
+}
+
+// Computes the observable differences between |entry_1| and |entry_2|.
+// Returns true if they are different, |false| if their observable fields are
+// identical.
+bool ComputeNavigationEvent(const chromium::web::NavigationEntry& old_entry,
+ const chromium::web::NavigationEntry& new_entry,
+ chromium::web::NavigationEvent* computed_event) {
+ DCHECK(computed_event);
+
+ bool is_changed = false;
+
+ if (old_entry.title != new_entry.title) {
+ is_changed = true;
+ computed_event->title = new_entry.title;
+ }
+
+ if (old_entry.url != new_entry.url) {
+ is_changed = true;
+ computed_event->url = new_entry.url;
+ }
+
+ computed_event->is_error = new_entry.is_error;
+ if (old_entry.is_error != new_entry.is_error)
+ is_changed = true;
+
+ return is_changed;
+}
+
+class FrameFocusRules : public wm::BaseFocusRules {
+ public:
+ FrameFocusRules() = default;
+ ~FrameFocusRules() override = default;
+
+ // wm::BaseFocusRules implementation.
+ bool SupportsChildActivation(const aura::Window*) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FrameFocusRules);
+};
+
+bool FrameFocusRules::SupportsChildActivation(const aura::Window*) const {
+ // TODO(crbug.com/878439): Return a result based on window properties such as
+ // visibility.
+ return true;
+}
+
+bool IsOriginWhitelisted(const GURL& url,
+ const std::vector<std::string>& allowed_origins) {
+ constexpr const char kWildcard[] = "*";
+
+ for (const std::string& origin : allowed_origins) {
+ if (origin == kWildcard)
+ return true;
+
+ GURL origin_url(origin);
+ if (!origin_url.is_valid()) {
+ DLOG(WARNING) << "Ignored invalid origin spec for whitelisting: "
+ << origin;
+ continue;
+ }
+
+ if (origin_url != url.GetOrigin())
+ continue;
+
+ // TODO(crbug.com/893236): Add handling for nonstandard origins
+ // (e.g. data: URIs).
+
+ return true;
+ }
+ return false;
+}
+
+class ScenicWindowTreeHost : public aura::WindowTreeHostPlatform {
+ public:
+ explicit ScenicWindowTreeHost(ui::PlatformWindowInitProperties properties)
+ : aura::WindowTreeHostPlatform(std::move(properties)) {}
+
+ ~ScenicWindowTreeHost() override = default;
+
+ // Route focus & blur events to the window's focus observer and its
+ // InputMethod.
+ void OnActivationChanged(bool active) override {
+ if (active) {
+ aura::client::GetFocusClient(window())->FocusWindow(window());
+ GetInputMethod()->OnFocus();
+ } else {
+ aura::client::GetFocusClient(window())->FocusWindow(nullptr);
+ GetInputMethod()->OnBlur();
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScenicWindowTreeHost);
+};
+
+} // namespace
+
+FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
+ ContextImpl* context,
+ fidl::InterfaceRequest<chromium::web::Frame> frame_request)
+ : web_contents_(std::move(web_contents)),
+ focus_controller_(
+ std::make_unique<wm::FocusController>(new FrameFocusRules)),
+ context_(context),
+ binding_(this, std::move(frame_request)) {
+ web_contents_->SetDelegate(this);
+ Observe(web_contents_.get());
+ binding_.set_error_handler(
+ [this](zx_status_t status) { context_->DestroyFrame(this); });
+
+ content::UpdateFontRendererPreferencesFromSystemSettings(
+ web_contents_->GetMutableRendererPrefs());
+}
+
+FrameImpl::~FrameImpl() {
+ if (window_tree_host_) {
+ aura::client::SetFocusClient(root_window(), nullptr);
+ wm::SetActivationClient(root_window(), nullptr);
+ root_window()->RemovePreTargetHandler(focus_controller_.get());
+ web_contents_->ClosePage();
+ window_tree_host_->Hide();
+ window_tree_host_->compositor()->SetVisible(false);
+
+ // Allows posted focus events to process before the FocusController
+ // is torn down.
+ content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
+ focus_controller_.release());
+ }
+}
+
+zx::unowned_channel FrameImpl::GetBindingChannelForTest() const {
+ return zx::unowned_channel(binding_.channel());
+}
+
+bool FrameImpl::ShouldCreateWebContents(
+ content::WebContents* web_contents,
+ content::RenderFrameHost* opener,
+ content::SiteInstance* source_site_instance,
+ int32_t route_id,
+ int32_t main_frame_route_id,
+ int32_t main_frame_widget_route_id,
+ content::mojom::WindowContainerType window_container_type,
+ const GURL& opener_url,
+ const std::string& frame_name,
+ const GURL& target_url,
+ const std::string& partition_id,
+ content::SessionStorageNamespace* session_storage_namespace) {
+ DCHECK_EQ(web_contents, web_contents_.get());
+
+ // Prevent any child WebContents (popup windows, tabs, etc.) from spawning.
+ // TODO(crbug.com/888131): Implement support for popup windows.
+ NOTIMPLEMENTED() << "Ignored popup window request for URL: "
+ << target_url.spec();
+
+ return false;
+}
+
+void FrameImpl::CreateView(
+ fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) {
+ // Cast |view_owner| request to a eventpair.
+ CreateView2(zx::eventpair(view_owner.TakeChannel().release()),
+ std::move(services), nullptr);
+}
+
+void FrameImpl::CreateView2(
+ zx::eventpair view_token,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
+ fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) {
+ ui::PlatformWindowInitProperties properties;
+ properties.view_token = std::move(view_token);
+
+ window_tree_host_ =
+ std::make_unique<ScenicWindowTreeHost>(std::move(properties));
+ window_tree_host_->InitHost();
+
+ aura::client::SetFocusClient(root_window(), focus_controller_.get());
+ wm::SetActivationClient(root_window(), focus_controller_.get());
+
+ // Add hooks which automatically set the focus state when input events are
+ // received.
+ root_window()->AddPreTargetHandler(focus_controller_.get());
+
+ // Track child windows for enforcement of window management policies and
+ // propagate window manager events to them (e.g. window resizing).
+ root_window()->SetLayoutManager(new LayoutManagerImpl());
+
+ root_window()->AddChild(web_contents_->GetNativeView());
+ web_contents_->GetNativeView()->Show();
+ window_tree_host_->Show();
+}
+
+void FrameImpl::GetNavigationController(
+ fidl::InterfaceRequest<chromium::web::NavigationController> controller) {
+ controller_bindings_.AddBinding(this, std::move(controller));
+}
+
+void FrameImpl::SetJavaScriptLogLevel(chromium::web::LogLevel level) {
+ log_level_ = level;
+}
+
+void FrameImpl::LoadUrl(std::string url,
+ std::unique_ptr<chromium::web::LoadUrlParams> params) {
+ GURL validated_url(url);
+ if (!validated_url.is_valid()) {
+ DLOG(WARNING) << "Invalid URL: " << url;
+ return;
+ }
+
+ content::NavigationController::LoadURLParams params_converted(validated_url);
+
+ if (validated_url.scheme() == url::kDataScheme)
+ params_converted.load_type = content::NavigationController::LOAD_TYPE_DATA;
+
+ params_converted.transition_type = ui::PageTransitionFromInt(
+ ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+ params_converted.was_activated = (params && params->user_activated)
+ ? content::WasActivatedOption::kYes
+ : content::WasActivatedOption::kNo;
+ web_contents_->GetController().LoadURLWithParams(params_converted);
+}
+
+void FrameImpl::GoBack() {
+ if (web_contents_->GetController().CanGoBack())
+ web_contents_->GetController().GoBack();
+}
+
+void FrameImpl::GoForward() {
+ if (web_contents_->GetController().CanGoForward())
+ web_contents_->GetController().GoForward();
+}
+
+void FrameImpl::Stop() {
+ web_contents_->Stop();
+}
+
+void FrameImpl::Reload(chromium::web::ReloadType type) {
+ content::ReloadType internal_reload_type;
+ switch (type) {
+ case chromium::web::ReloadType::PARTIAL_CACHE:
+ internal_reload_type = content::ReloadType::NORMAL;
+ break;
+ case chromium::web::ReloadType::NO_CACHE:
+ internal_reload_type = content::ReloadType::BYPASSING_CACHE;
+ break;
+ }
+ web_contents_->GetController().Reload(internal_reload_type, false);
+}
+
+void FrameImpl::GetVisibleEntry(GetVisibleEntryCallback callback) {
+ content::NavigationEntry* entry =
+ web_contents_->GetController().GetVisibleEntry();
+ if (!entry) {
+ callback(nullptr);
+ return;
+ }
+
+ chromium::web::NavigationEntry output = ConvertContentNavigationEntry(entry);
+ callback(std::make_unique<chromium::web::NavigationEntry>(std::move(output)));
+}
+
+void FrameImpl::SetNavigationEventObserver(
+ fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer) {
+ // Reset the event buffer state.
+ waiting_for_navigation_event_ack_ = false;
+ cached_navigation_state_ = {};
+ pending_navigation_event_ = {};
+ pending_navigation_event_is_dirty_ = false;
+
+ if (observer) {
+ navigation_observer_.Bind(std::move(observer));
+ navigation_observer_.set_error_handler(
+ [this](zx_status_t status) { SetNavigationEventObserver(nullptr); });
+ } else {
+ navigation_observer_.Unbind();
+ }
+}
+
+void FrameImpl::ExecuteJavaScript(std::vector<std::string> origins,
+ fuchsia::mem::Buffer script,
+ chromium::web::ExecuteMode mode,
+ ExecuteJavaScriptCallback callback) {
+ if (!context_->IsJavaScriptInjectionAllowed()) {
+ callback(false);
+ return;
+ }
+
+ if (origins.empty()) {
+ callback(false);
+ return;
+ }
+
+ std::vector<std::string> origins_strings;
+ for (const auto& origin : origins)
+ origins_strings.push_back(origin);
+
+ base::string16 script_utf16;
+ if (!ReadUTF8FromVMOAsUTF16(script, &script_utf16)) {
+ callback(false);
+ return;
+ }
+
+ if (mode == chromium::web::ExecuteMode::IMMEDIATE_ONCE) {
+ if (!IsOriginWhitelisted(web_contents_->GetLastCommittedURL(),
+ origins_strings)) {
+ callback(false);
+ return;
+ }
+
+ web_contents_->GetMainFrame()->ExecuteJavaScript(script_utf16);
+ } else {
+ // Store the script as UTF16 shared memory buffer, so that it can be
+ // used directly by renderers without string format conversions.
+
+ // Create a read-only VMO from |script|.
+ fuchsia::mem::Buffer script_buffer = MemBufferFromString16(script_utf16);
+ if (!script_buffer.vmo) {
+ LOG(WARNING) << "Couldn't read script contents from VMO.";
+ callback(false);
+ return;
+ }
+
+ // Wrap the VMO into a read-only shared-memory container that Mojo can work
+ // with.
+ base::subtle::PlatformSharedMemoryRegion script_region =
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(script_buffer.vmo),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable,
+ script_buffer.size, base::UnguessableToken::Create());
+ script_region.ConvertToReadOnly();
+ before_load_scripts_.emplace_back(
+ origins_strings, base::ReadOnlySharedMemoryRegion::Deserialize(
+ std::move(script_region)));
+ }
+ callback(true);
+}
+
+void FrameImpl::DidFinishLoad(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url) {
+ if (web_contents_->GetMainFrame() != render_frame_host) {
+ return;
+ }
+
+ chromium::web::NavigationEntry current_navigation_state =
+ ConvertContentNavigationEntry(
+ web_contents_->GetController().GetVisibleEntry());
+ pending_navigation_event_is_dirty_ |=
+ ComputeNavigationEvent(cached_navigation_state_, current_navigation_state,
+ &pending_navigation_event_);
+ cached_navigation_state_ = std::move(current_navigation_state);
+
+ MaybeSendNavigationEvent();
+}
+
+void FrameImpl::MaybeSendNavigationEvent() {
+ if (!navigation_observer_)
+ return;
+
+ if (!pending_navigation_event_is_dirty_ ||
+ waiting_for_navigation_event_ack_) {
+ return;
+ }
+
+ pending_navigation_event_is_dirty_ = false;
+ waiting_for_navigation_event_ack_ = true;
+
+ // Send the event to the observer and, upon acknowledgement, revisit this
+ // function to send another update.
+ navigation_observer_->OnNavigationStateChanged(
+ std::move(pending_navigation_event_), [this]() {
+ waiting_for_navigation_event_ack_ = false;
+ MaybeSendNavigationEvent();
+ });
+}
+
+void FrameImpl::ReadyToCommitNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (before_load_scripts_.empty())
+ return;
+
+ if (!navigation_handle->IsInMainFrame() ||
+ navigation_handle->IsSameDocument() || navigation_handle->IsErrorPage())
+ return;
+
+ mojom::OnLoadScriptInjectorAssociatedPtr before_load_script_injector;
+ navigation_handle->GetRenderFrameHost()
+ ->GetRemoteAssociatedInterfaces()
+ ->GetInterface(&before_load_script_injector);
+
+ // Provision the renderer's ScriptInjector with the scripts scoped to this
+ // page's origin.
+ before_load_script_injector->ClearOnLoadScripts();
+ for (auto i = before_load_scripts_.begin();
+ i != before_load_scripts_.end();) {
+ if (IsOriginWhitelisted(navigation_handle->GetURL(), i->origins)) {
+ before_load_script_injector->AddOnLoadScript(
+ mojo::WrapReadOnlySharedMemoryRegion(i->script.Duplicate()));
+ }
+
+ i++;
+ }
+}
+
+bool FrameImpl::DidAddMessageToConsole(content::WebContents* source,
+ int32_t level,
+ const base::string16& message,
+ int32_t line_no,
+ const base::string16& source_id) {
+ if (static_cast<std::underlying_type<chromium::web::LogLevel>::type>(
+ log_level_) > level) {
+ return false;
+ }
+
+ std::string message_formatted =
+ base::StringPrintf("%s:%d : %s", base::UTF16ToUTF8(source_id).data(),
+ line_no, base::UTF16ToUTF8(message).data());
+ switch (level) {
+ case static_cast<std::underlying_type<chromium::web::LogLevel>::type>(
+ chromium::web::LogLevel::DEBUG):
+ LOG(INFO) << "debug:" << message_formatted;
+ break;
+ case static_cast<std::underlying_type<chromium::web::LogLevel>::type>(
+ chromium::web::LogLevel::INFO):
+ LOG(INFO) << "info:" << message_formatted;
+ break;
+ case static_cast<std::underlying_type<chromium::web::LogLevel>::type>(
+ chromium::web::LogLevel::WARN):
+ LOG(WARNING) << "warn:" << message_formatted;
+ break;
+ case static_cast<std::underlying_type<chromium::web::LogLevel>::type>(
+ chromium::web::LogLevel::ERROR):
+ LOG(ERROR) << "error:" << message_formatted;
+ break;
+ default:
+ DLOG(WARNING) << "Unknown log level: " << level;
+ return false;
+ }
+
+ return true;
+}
+
+FrameImpl::OriginScopedScript::OriginScopedScript(
+ std::vector<std::string> origins,
+ base::ReadOnlySharedMemoryRegion script)
+ : origins(std::move(origins)), script(std::move(script)) {}
+
+FrameImpl::OriginScopedScript::~OriginScopedScript() = default;
+
+void FrameImpl::PostMessage(chromium::web::WebMessage message,
+ std::string target_origin,
+ PostMessageCallback callback) {
+ constexpr char kWildcardOrigin[] = "*";
+
+ if (target_origin.empty()) {
+ callback(false);
+ return;
+ }
+
+ base::Optional<base::string16> target_origin_utf16;
+ if (target_origin != kWildcardOrigin)
+ target_origin_utf16 = base::UTF8ToUTF16(target_origin);
+
+ base::string16 data_utf16;
+ if (!ReadUTF8FromVMOAsUTF16(message.data, &data_utf16)) {
+ DLOG(WARNING) << "PostMessage() rejected non-UTF8 |message.data|.";
+ callback(false);
+ return;
+ }
+
+ // Include outgoing MessagePorts in the message.
+ std::vector<mojo::ScopedMessagePipeHandle> message_ports;
+ if (message.outgoing_transfer) {
+ if (!message.outgoing_transfer->is_message_port()) {
+ DLOG(WARNING) << "|outgoing_transfer| is not a MessagePort.";
+ callback(false);
+ return;
+ }
+
+ mojo::ScopedMessagePipeHandle port = MessagePortImpl::FromFidl(
+ std::move(message.outgoing_transfer->message_port()));
+ if (!port) {
+ callback(false);
+ return;
+ }
+ message_ports.push_back(std::move(port));
+ }
+
+ content::MessagePortProvider::PostMessageToFrame(
+ web_contents_.get(), base::string16(), target_origin_utf16,
+ std::move(data_utf16), std::move(message_ports));
+ callback(true);
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/frame_impl.h b/chromium/fuchsia/browser/frame_impl.h
new file mode 100644
index 00000000000..65faf0ac48e
--- /dev/null
+++ b/chromium/fuchsia/browser/frame_impl.h
@@ -0,0 +1,161 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_FRAME_IMPL_H_
+#define FUCHSIA_BROWSER_FRAME_IMPL_H_
+
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/zx/channel.h>
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "fuchsia/common/on_load_script_injector.mojom.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "ui/aura/window_tree_host.h"
+#include "ui/wm/core/focus_controller.h"
+#include "url/gurl.h"
+
+namespace aura {
+class WindowTreeHost;
+} // namespace aura
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace webrunner {
+
+class ContextImpl;
+
+// Implementation of Frame from //fuchsia/fidl/frame.fidl.
+// Implements a Frame service, which is a wrapper for a WebContents instance.
+class FrameImpl : public chromium::web::Frame,
+ public chromium::web::NavigationController,
+ public content::WebContentsObserver,
+ public content::WebContentsDelegate {
+ public:
+ FrameImpl(std::unique_ptr<content::WebContents> web_contents,
+ ContextImpl* context,
+ fidl::InterfaceRequest<chromium::web::Frame> frame_request);
+ ~FrameImpl() override;
+
+ zx::unowned_channel GetBindingChannelForTest() const;
+
+ content::WebContents* web_contents_for_test() { return web_contents_.get(); }
+
+ // chromium::web::Frame implementation.
+ void CreateView(
+ fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) override;
+ void CreateView2(
+ zx::eventpair view_token,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
+ fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
+ override;
+ void GetNavigationController(
+ fidl::InterfaceRequest<chromium::web::NavigationController> controller)
+ override;
+ void SetJavaScriptLogLevel(chromium::web::LogLevel level) override;
+ void SetNavigationEventObserver(
+ fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer)
+ override;
+ void ExecuteJavaScript(std::vector<std::string> origins,
+ fuchsia::mem::Buffer script,
+ chromium::web::ExecuteMode mode,
+ ExecuteJavaScriptCallback callback) override;
+ void PostMessage(chromium::web::WebMessage message,
+ std::string target_origin,
+ PostMessageCallback callback) override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FrameImplTest, DelayedNavigationEventAck);
+ FRIEND_TEST_ALL_PREFIXES(FrameImplTest, NavigationObserverDisconnected);
+ FRIEND_TEST_ALL_PREFIXES(FrameImplTest, NoNavigationObserverAttached);
+ FRIEND_TEST_ALL_PREFIXES(FrameImplTest, ReloadFrame);
+ FRIEND_TEST_ALL_PREFIXES(FrameImplTest, Stop);
+
+ struct OriginScopedScript {
+ OriginScopedScript(std::vector<std::string> origins,
+ base::ReadOnlySharedMemoryRegion script);
+ ~OriginScopedScript();
+
+ std::vector<std::string> origins;
+
+ // A shared memory buffer containing the script, encoded as UTF16.
+ base::ReadOnlySharedMemoryRegion script;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OriginScopedScript);
+ };
+
+ // chromium::web::NavigationController implementation.
+ void LoadUrl(std::string url,
+ std::unique_ptr<chromium::web::LoadUrlParams> params) override;
+ void GoBack() override;
+ void GoForward() override;
+ void Stop() override;
+ void Reload(chromium::web::ReloadType type) override;
+ void GetVisibleEntry(GetVisibleEntryCallback callback) override;
+
+ aura::Window* root_window() const { return window_tree_host_->window(); }
+
+ // Sends |pending_navigation_event_| to the observer if there are any changes
+ // to be reported.
+ void MaybeSendNavigationEvent();
+
+ // content::WebContentsDelegate implementation.
+ bool ShouldCreateWebContents(
+ content::WebContents* web_contents,
+ content::RenderFrameHost* opener,
+ content::SiteInstance* source_site_instance,
+ int32_t route_id,
+ int32_t main_frame_route_id,
+ int32_t main_frame_widget_route_id,
+ content::mojom::WindowContainerType window_container_type,
+ const GURL& opener_url,
+ const std::string& frame_name,
+ const GURL& target_url,
+ const std::string& partition_id,
+ content::SessionStorageNamespace* session_storage_namespace) override;
+ bool DidAddMessageToConsole(content::WebContents* source,
+ int32_t level,
+ const base::string16& message,
+ int32_t line_no,
+ const base::string16& source_id) override;
+
+ // content::WebContentsObserver implementation.
+ void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+ const GURL& validated_url) override;
+ void ReadyToCommitNavigation(
+ content::NavigationHandle* navigation_handle) override;
+
+ std::unique_ptr<aura::WindowTreeHost> window_tree_host_;
+ std::unique_ptr<content::WebContents> web_contents_;
+ std::unique_ptr<wm::FocusController> focus_controller_;
+
+ chromium::web::NavigationEventObserverPtr navigation_observer_;
+ chromium::web::NavigationEntry cached_navigation_state_;
+ chromium::web::NavigationEvent pending_navigation_event_;
+ bool waiting_for_navigation_event_ack_;
+ bool pending_navigation_event_is_dirty_;
+ chromium::web::LogLevel log_level_ = chromium::web::LogLevel::NONE;
+ std::list<OriginScopedScript> before_load_scripts_;
+
+ ContextImpl* context_ = nullptr;
+ fidl::Binding<chromium::web::Frame> binding_;
+ fidl::BindingSet<chromium::web::NavigationController> controller_bindings_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameImpl);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_FRAME_IMPL_H_
diff --git a/chromium/fuchsia/browser/frame_impl_browsertest.cc b/chromium/fuchsia/browser/frame_impl_browsertest.cc
new file mode 100644
index 00000000000..f2bc371d527
--- /dev/null
+++ b/chromium/fuchsia/browser/frame_impl_browsertest.cc
@@ -0,0 +1,968 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/macros.h"
+#include "base/test/test_timeouts.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "fuchsia/browser/frame_impl.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/test/promise.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_constants.h"
+
+namespace webrunner {
+
+using testing::_;
+using testing::AllOf;
+using testing::Field;
+using testing::InvokeWithoutArgs;
+using testing::Mock;
+
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
+
+const char kPage1Path[] = "/title1.html";
+const char kPage2Path[] = "/title2.html";
+const char kDynamicTitlePath[] = "/dynamic_title.html";
+const char kPage1Title[] = "title 1";
+const char kPage2Title[] = "title 2";
+const char kDataUrl[] =
+ "data:text/html;base64,PGI+SGVsbG8sIHdvcmxkLi4uPC9iPg==";
+const char kTestServerRoot[] = FILE_PATH_LITERAL("fuchsia/browser/test/data");
+
+MATCHER(IsSet, "Checks if an optional field is set.") {
+ return !arg.is_null();
+}
+
+// Defines a suite of tests that exercise Frame-level functionality, such as
+// navigation commands and page events.
+class FrameImplTest : public WebRunnerBrowserTest {
+ public:
+ FrameImplTest() : run_timeout_(TestTimeouts::action_timeout()) {
+ set_test_server_root(base::FilePath(kTestServerRoot));
+ }
+
+ ~FrameImplTest() = default;
+
+ MOCK_METHOD1(OnServeHttpRequest,
+ void(const net::test_server::HttpRequest& request));
+
+ protected:
+ // Creates a Frame with |navigation_observer_| attached.
+ chromium::web::FramePtr CreateFrame() {
+ return WebRunnerBrowserTest::CreateFrame(&navigation_observer_);
+ }
+
+ // Navigates a |controller| to |url|, blocking until navigation is complete.
+ void CheckLoadUrl(const std::string& url,
+ const std::string& expected_title,
+ chromium::web::NavigationController* controller) {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, expected_title),
+ Field(&NavigationDetails::url, url))))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(url, nullptr);
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ navigation_observer_.Acknowledge();
+ }
+
+ testing::StrictMock<MockNavigationObserver> navigation_observer_;
+
+ private:
+ const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameImplTest);
+};
+
+class WebContentsDeletionObserver : public content::WebContentsObserver {
+ public:
+ explicit WebContentsDeletionObserver(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents) {}
+
+ MOCK_METHOD1(RenderViewDeleted,
+ void(content::RenderViewHost* render_view_host));
+};
+
+// Verifies that the browser will navigate and generate a navigation observer
+// event when LoadUrl() is called.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateFrame) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigateDataFrame) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ CheckLoadUrl(kDataUrl, kDataUrl, controller.get());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, FrameDeletedBeforeContext) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ // Process the frame creation message.
+ base::RunLoop().RunUntilIdle();
+
+ FrameImpl* frame_impl = context_impl()->GetFrameImplForTest(&frame);
+ WebContentsDeletionObserver deletion_observer(
+ frame_impl->web_contents_for_test());
+ base::RunLoop run_loop;
+ EXPECT_CALL(deletion_observer, RenderViewDeleted(_))
+ .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ controller->LoadUrl(url::kAboutBlankURL, nullptr);
+
+ frame.Unbind();
+ run_loop.Run();
+
+ // Check that |context| remains bound after the frame is closed.
+ EXPECT_TRUE(context());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ContextDeletedBeforeFrame) {
+ chromium::web::FramePtr frame = CreateFrame();
+ EXPECT_TRUE(frame);
+
+ base::RunLoop run_loop;
+ frame.set_error_handler([&run_loop](zx_status_t status) { run_loop.Quit(); });
+ context().Unbind();
+ run_loop.Run();
+ EXPECT_FALSE(frame);
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, GoBackAndForward) {
+ chromium::web::FramePtr frame = CreateFrame();
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL title1(embedded_test_server()->GetURL(kPage1Path));
+ GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+ CheckLoadUrl(title1.spec(), kPage1Title, controller.get());
+ CheckLoadUrl(title2.spec(), kPage2Title, controller.get());
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+ controller->GoBack();
+ run_loop.Run();
+ navigation_observer_.Acknowledge();
+ }
+
+ // At the top of the navigation entry list; this should be a no-op.
+ controller->GoBack();
+
+ // Process the navigation request message.
+ base::RunLoop().RunUntilIdle();
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage2Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+ controller->GoForward();
+ run_loop.Run();
+ navigation_observer_.Acknowledge();
+ }
+
+ // At the end of the navigation entry list; this should be a no-op.
+ controller->GoForward();
+
+ // Process the navigation request message.
+ base::RunLoop().RunUntilIdle();
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ReloadFrame) {
+ chromium::web::FramePtr frame = CreateFrame();
+ chromium::web::NavigationControllerPtr navigation_controller;
+ frame->GetNavigationController(navigation_controller.NewRequest());
+
+ embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
+ &FrameImplTest::OnServeHttpRequest, base::Unretained(this)));
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kPage1Path));
+
+ EXPECT_CALL(*this, OnServeHttpRequest(_));
+ CheckLoadUrl(url.spec(), kPage1Title, navigation_controller.get());
+
+ navigation_observer_.Observe(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+ // Reload with NO_CACHE.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(*this, OnServeHttpRequest(_));
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ navigation_controller->Reload(chromium::web::ReloadType::NO_CACHE);
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ navigation_observer_.Acknowledge();
+ }
+ // Reload with PARTIAL_CACHE.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(*this, OnServeHttpRequest(_));
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, url))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ navigation_controller->Reload(chromium::web::ReloadType::PARTIAL_CACHE);
+ run_loop.Run();
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, GetVisibleEntry) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ // Verify that a Frame returns a null NavigationEntry prior to receiving any
+ // LoadUrl() calls.
+ {
+ base::RunLoop run_loop;
+ controller->GetVisibleEntry(
+ [&run_loop](std::unique_ptr<chromium::web::NavigationEntry> details) {
+ EXPECT_EQ(nullptr, details.get());
+ run_loop.Quit();
+ });
+ run_loop.Run();
+ }
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL title1(embedded_test_server()->GetURL(kPage1Path));
+ GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+ // Navigate to a page.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+ controller->LoadUrl(title1.spec(), nullptr);
+ run_loop.Run();
+ navigation_observer_.Acknowledge();
+ }
+
+ // Verify that GetVisibleEntry() reflects the new Frame navigation state.
+ {
+ base::RunLoop run_loop;
+ controller->GetVisibleEntry(
+ [&run_loop,
+ &title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
+ EXPECT_TRUE(details);
+ EXPECT_EQ(details->url, title1.spec());
+ EXPECT_EQ(details->title, kPage1Title);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+ }
+
+ // Navigate to another page.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage2Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+ controller->LoadUrl(title2.spec(), nullptr);
+ run_loop.Run();
+ navigation_observer_.Acknowledge();
+ }
+
+ // Verify the navigation with GetVisibleEntry().
+ {
+ base::RunLoop run_loop;
+ controller->GetVisibleEntry(
+ [&run_loop,
+ &title2](std::unique_ptr<chromium::web::NavigationEntry> details) {
+ EXPECT_TRUE(details);
+ EXPECT_EQ(details->url, title2.spec());
+ EXPECT_EQ(details->title, kPage2Title);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+ }
+
+ // Navigate back to the first page.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(testing::InvokeWithoutArgs([&run_loop] { run_loop.Quit(); }));
+ controller->GoBack();
+ run_loop.Run();
+ navigation_observer_.Acknowledge();
+ }
+
+ // Verify the navigation with GetVisibleEntry().
+ {
+ base::RunLoop run_loop;
+ controller->GetVisibleEntry(
+ [&run_loop,
+ &title1](std::unique_ptr<chromium::web::NavigationEntry> details) {
+ EXPECT_TRUE(details);
+ EXPECT_EQ(details->url, title1.spec());
+ EXPECT_EQ(details->title, kPage1Title);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NoNavigationObserverAttached) {
+ chromium::web::FramePtr frame;
+ context()->CreateFrame(frame.NewRequest());
+ base::RunLoop().RunUntilIdle();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL title1(embedded_test_server()->GetURL(kPage1Path));
+ GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+ navigation_observer_.Observe(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title1.spec(), nullptr);
+ run_loop.Run();
+ }
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title2.spec(), nullptr);
+ run_loop.Run();
+ }
+}
+
+// Test JS injection by using Javascript to trigger document navigation.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptImmediate) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL title1(embedded_test_server()->GetURL(kPage1Path));
+ GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(title1.spec(), kPage1Title, controller.get());
+ std::vector<std::string> origins = {title1.GetOrigin().spec()};
+
+ frame->ExecuteJavaScript(
+ std::move(origins),
+ MemBufferFromString("window.location.href = \"" + title2.spec() + "\";"),
+ chromium::web::ExecuteMode::IMMEDIATE_ONCE,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(
+ testing::AllOf(Field(&NavigationDetails::title, kPage2Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoad) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ std::vector<std::string> origins = {url.GetOrigin().spec()};
+
+ frame->ExecuteJavaScript(std::move(origins),
+ MemBufferFromString("stashed_title = 'hello';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(url.spec(), "hello", controller.get());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoadVmoDestroyed) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ std::vector<std::string> origins = {url.GetOrigin().spec()};
+
+ frame->ExecuteJavaScript(std::move(origins),
+ MemBufferFromString("stashed_title = 'hello';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(url.spec(), "hello", controller.get());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavascriptOnLoadWrongOrigin) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ std::vector<std::string> origins = {"http://example.com"};
+
+ frame->ExecuteJavaScript(std::move(origins),
+ MemBufferFromString("stashed_title = 'hello';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ // Expect that the original HTML title is used, because we didn't inject a
+ // script with a replacement title.
+ CheckLoadUrl(url.spec(), "Welcome to Stan the Offline Dino's Homepage",
+ controller.get());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoadWildcardOrigin) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ std::vector<std::string> origins = {"*"};
+
+ frame->ExecuteJavaScript(std::move(origins),
+ MemBufferFromString("stashed_title = 'hello';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ // Test script injection for the origin 127.0.0.1.
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(url.spec(), "hello", controller.get());
+
+ CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
+
+ // Test script injection using a different origin ("localhost"), which should
+ // still be picked up by the wildcard.
+ GURL alt_url = embedded_test_server()->GetURL("localhost", kDynamicTitlePath);
+ CheckLoadUrl(alt_url.spec(), "hello", controller.get());
+}
+
+// Test that consecutive scripts are executed in order by computing a cumulative
+// result.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteMultipleJavaScriptsOnLoad) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ std::vector<std::string> origins = {url.GetOrigin().spec()};
+ frame->ExecuteJavaScript(origins,
+ MemBufferFromString("stashed_title = 'hello';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+ frame->ExecuteJavaScript(std::move(origins),
+ MemBufferFromString("stashed_title += ' there';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(url.spec(), "hello there", controller.get());
+}
+
+// Test that we can inject scripts before and after RenderFrame creation.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteOnLoadEarlyAndLateRegistrations) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
+ chromium::web::FramePtr frame = CreateFrame();
+
+ std::vector<std::string> origins = {url.GetOrigin().spec()};
+
+ frame->ExecuteJavaScript(origins,
+ MemBufferFromString("stashed_title = 'hello';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(url.spec(), "hello", controller.get());
+
+ frame->ExecuteJavaScript(std::move(origins),
+ MemBufferFromString("stashed_title += ' there';"),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { EXPECT_TRUE(success); });
+
+ // Navigate away to clean the slate.
+ CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
+
+ // Navigate back and see if both scripts are working.
+ CheckLoadUrl(url.spec(), "hello there", controller.get());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptBadEncoding) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(kPage1Path));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(url.spec(), kPage1Title, controller.get());
+
+ base::RunLoop run_loop;
+
+ // 0xFE is an illegal UTF-8 byte; it should cause UTF-8 conversion to fail.
+ std::vector<std::string> origins = {url.host()};
+ frame->ExecuteJavaScript(std::move(origins), MemBufferFromString("true;\xfe"),
+ chromium::web::ExecuteMode::IMMEDIATE_ONCE,
+ [&run_loop](bool success) {
+ EXPECT_FALSE(success);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+}
+
+// Verifies that a Frame will handle navigation observer disconnection events
+// gracefully.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationObserverDisconnected) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL title1(embedded_test_server()->GetURL(kPage1Path));
+ GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+ navigation_observer_.Observe(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1));
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title1.spec(), nullptr);
+ run_loop.Run();
+ }
+
+ // Disconnect the observer & spin the runloop to propagate the disconnection
+ // event over IPC.
+ navigation_observer_bindings().CloseAll();
+ base::RunLoop().RunUntilIdle();
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title2.spec(), nullptr);
+ run_loop.Run();
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, DelayedNavigationEventAck) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL title1(embedded_test_server()->GetURL(kPage1Path));
+ GURL title2(embedded_test_server()->GetURL(kPage2Path));
+
+ // Expect an navigation event here, but deliberately postpone acknowledgement
+ // until the end of the test.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title1.spec(), nullptr);
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ // Since we have blocked NavigationEventObserver's flow, we must observe the
+ // WebContents events directly via a test-only seam.
+ navigation_observer_.Observe(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+ // Navigate to a second page.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title2))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title2.spec(), nullptr);
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ // Navigate to the first page.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_, DidFinishLoad(_, title1))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(title1.spec(), nullptr);
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ // Since there was no observable change in navigation state since the last
+ // ack, there should be no more NavigationEvents generated.
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(testing::AllOf(
+ Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ navigation_observer_.Acknowledge();
+ run_loop.Run();
+ }
+}
+
+// Observes events specific to the Stop() test case.
+struct WebContentsObserverForStop : public content::WebContentsObserver {
+ using content::WebContentsObserver::Observe;
+ MOCK_METHOD1(DidStartNavigation, void(content::NavigationHandle*));
+ MOCK_METHOD0(NavigationStopped, void());
+};
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, Stop) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ // Use a request handler that will accept the connection and stall
+ // indefinitely.
+ GURL hung_url(embedded_test_server()->GetURL("/hung"));
+
+ WebContentsObserverForStop observer;
+ observer.Observe(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_.get());
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(observer, DidStartNavigation(_))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->LoadUrl(hung_url.spec(), nullptr);
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ EXPECT_TRUE(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
+
+ {
+ base::RunLoop run_loop;
+ EXPECT_CALL(observer, NavigationStopped())
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ controller->Stop();
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(this);
+ }
+
+ EXPECT_FALSE(
+ context_impl()->GetFrameImplForTest(&frame)->web_contents_->IsLoading());
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessage) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL post_message_url(
+ embedded_test_server()->GetURL("/window_post_message.html"));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(post_message_url.spec(), "postmessage", controller.get());
+
+ chromium::web::WebMessage message;
+ message.data = MemBufferFromString(kPage1Path);
+ Promise<bool> post_result;
+ frame->PostMessage(std::move(message), post_message_url.GetOrigin().spec(),
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+ base::RunLoop run_loop;
+ EXPECT_CALL(navigation_observer_,
+ MockableOnNavigationStateChanged(
+ testing::AllOf(Field(&NavigationDetails::title, kPage1Title),
+ Field(&NavigationDetails::url, IsSet()))))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+ run_loop.Run();
+ EXPECT_TRUE(*post_result);
+}
+
+// Send a MessagePort to the content, then perform bidirectional messaging
+// through the port.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessagePassMessagePort) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
+
+ chromium::web::MessagePortPtr message_port;
+ chromium::web::WebMessage msg;
+ {
+ msg.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ msg.outgoing_transfer->set_message_port(message_port.NewRequest());
+ msg.data = MemBufferFromString("hi");
+ Promise<bool> post_result;
+ frame->PostMessage(std::move(msg), post_message_url.GetOrigin().spec(),
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data));
+ }
+
+ {
+ msg.data = MemBufferFromString("ping");
+ Promise<bool> post_result;
+ message_port->PostMessage(
+ std::move(msg), ConvertToFitFunction(post_result.GetReceiveCallback()));
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("ack ping", StringFromMemBufferOrDie(receiver->data));
+ EXPECT_TRUE(*post_result);
+ }
+}
+
+// Send a MessagePort to the content, then perform bidirectional messaging
+// over its channel.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageMessagePortDisconnected) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
+
+ chromium::web::MessagePortPtr message_port;
+ chromium::web::WebMessage msg;
+ {
+ msg.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ msg.outgoing_transfer->set_message_port(message_port.NewRequest());
+ msg.data = MemBufferFromString("hi");
+ Promise<bool> post_result;
+ frame->PostMessage(std::move(msg), post_message_url.GetOrigin().spec(),
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data));
+ EXPECT_TRUE(*post_result);
+ }
+
+ // Navigating off-page should tear down the Mojo channel, thereby causing the
+ // MessagePortImpl to self-destruct and tear down its FIDL channel.
+ {
+ base::RunLoop run_loop;
+ message_port.set_error_handler(
+ [&run_loop](zx_status_t) { run_loop.Quit(); });
+ controller->LoadUrl(url::kAboutBlankURL, nullptr);
+ run_loop.Run();
+ }
+}
+
+// Send a MessagePort to the content, and through that channel, receive a
+// different MessagePort that was created by the content. Verify the second
+// channel's liveness by sending a ping to it.
+IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageUseContentProvidedPort) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
+
+ chromium::web::MessagePortPtr incoming_message_port;
+ chromium::web::WebMessage msg;
+ {
+ chromium::web::MessagePortPtr message_port;
+ msg.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ msg.outgoing_transfer->set_message_port(message_port.NewRequest());
+ msg.data = MemBufferFromString("hi");
+ Promise<bool> post_result;
+ frame->PostMessage(std::move(msg), "*",
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data));
+ incoming_message_port = receiver->incoming_transfer->message_port().Bind();
+ EXPECT_TRUE(*post_result);
+ }
+
+ // Get the content to send three 'ack ping' messages, which will accumulate in
+ // the MessagePortImpl buffer.
+ for (int i = 0; i < 3; ++i) {
+ base::RunLoop run_loop;
+ Promise<bool> post_result(run_loop.QuitClosure());
+ msg.data = MemBufferFromString("ping");
+ incoming_message_port->PostMessage(
+ std::move(msg), ConvertToFitFunction(post_result.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_TRUE(*post_result);
+ }
+
+ // Receive another acknowledgement from content on a side channel to ensure
+ // that all the "ack pings" are ready to be consumed.
+ {
+ chromium::web::MessagePortPtr ack_message_port;
+ chromium::web::WebMessage msg;
+ msg.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ msg.outgoing_transfer->set_message_port(ack_message_port.NewRequest());
+ msg.data = MemBufferFromString("hi");
+
+ // Quit the runloop only after we've received a WebMessage AND a PostMessage
+ // result.
+ Promise<bool> post_result;
+ frame->PostMessage(std::move(msg), "*",
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ ack_message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data));
+ EXPECT_TRUE(*post_result);
+ }
+
+ // Pull the three 'ack ping's from the buffer.
+ for (int i = 0; i < 3; ++i) {
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ incoming_message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("ack ping", StringFromMemBufferOrDie(receiver->data));
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageBadOriginDropped) {
+ chromium::web::FramePtr frame = CreateFrame();
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL post_message_url(embedded_test_server()->GetURL("/message_port.html"));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame->GetNavigationController(controller.NewRequest());
+ CheckLoadUrl(post_message_url.spec(), "messageport", controller.get());
+
+ chromium::web::MessagePortPtr bad_origin_incoming_message_port;
+ chromium::web::WebMessage msg;
+
+ // PostMessage() to invalid origins should be ignored. We pass in a
+ // MessagePort but nothing should happen to it.
+ chromium::web::MessagePortPtr unused_message_port;
+ msg.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ msg.outgoing_transfer->set_message_port(unused_message_port.NewRequest());
+ msg.data = MemBufferFromString("bad origin, bad!");
+ Promise<bool> unused_post_result;
+ frame->PostMessage(
+ std::move(msg), "https://example.com",
+ ConvertToFitFunction(unused_post_result.GetReceiveCallback()));
+ Promise<chromium::web::WebMessage> unused_message_read;
+ bad_origin_incoming_message_port->ReceiveMessage(
+ ConvertToFitFunction(unused_message_read.GetReceiveCallback()));
+
+ // PostMessage() with a valid origin should succeed.
+ // Verify it by looking for an ack message on the MessagePort we passed in.
+ // Since message events are handled in order, observing the result of this
+ // operation will verify whether the previous PostMessage() was received but
+ // discarded.
+ chromium::web::MessagePortPtr incoming_message_port;
+ chromium::web::MessagePortPtr message_port;
+ msg.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ msg.outgoing_transfer->set_message_port(message_port.NewRequest());
+ msg.data = MemBufferFromString("good origin");
+ Promise<bool> post_result;
+ frame->PostMessage(std::move(msg), "*",
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> receiver(run_loop.QuitClosure());
+ message_port->ReceiveMessage(
+ ConvertToFitFunction(receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ("got_port", StringFromMemBufferOrDie(receiver->data));
+ incoming_message_port = receiver->incoming_transfer->message_port().Bind();
+ EXPECT_TRUE(*post_result);
+
+ // Verify that the first PostMessage() call wasn't handled.
+ EXPECT_FALSE(unused_message_read.has_value());
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/message_port_impl.cc b/chromium/fuchsia/browser/message_port_impl.cc
new file mode 100644
index 00000000000..d416022d067
--- /dev/null
+++ b/chromium/fuchsia/browser/message_port_impl.cc
@@ -0,0 +1,190 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/message_port_impl.h"
+
+#include <stdint.h>
+
+#include <lib/fit/function.h>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/macros.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "third_party/blink/public/common/messaging/message_port_channel.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
+#include "third_party/blink/public/common/messaging/transferable_message_struct_traits.h"
+#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
+
+namespace webrunner {
+namespace {
+
+// Converts a message posted to a JS MessagePort to a WebMessage.
+// Returns an unset Optional<> if the message could not be converted.
+base::Optional<chromium::web::WebMessage> FromMojoMessage(
+ mojo::Message message) {
+ chromium::web::WebMessage converted;
+
+ blink::TransferableMessage transferable_message;
+ if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
+ std::move(message), &transferable_message))
+ return {};
+
+ if (!transferable_message.ports.empty()) {
+ if (transferable_message.ports.size() != 1u) {
+ // TODO(crbug.com/893236): support >1 transferable when fidlc cycle
+ // detection is fixed (FIDL-354).
+ LOG(ERROR) << "FIXME: Request to transfer >1 MessagePort was ignored.";
+ return {};
+ }
+ converted.incoming_transfer =
+ std::make_unique<chromium::web::IncomingTransferable>();
+ converted.incoming_transfer->set_message_port(MessagePortImpl::FromMojo(
+ transferable_message.ports[0].ReleaseHandle()));
+ }
+
+ base::string16 data_utf16;
+ if (!blink::DecodeStringMessage(transferable_message.encoded_message,
+ &data_utf16)) {
+ return {};
+ }
+
+ std::string data_utf8;
+ if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &data_utf8))
+ return {};
+ base::STLClearObject(&data_utf16);
+
+ converted.data.size = data_utf8.size();
+ zx_status_t status = zx::vmo::create(data_utf8.size(), ZX_VMO_NON_RESIZABLE,
+ &converted.data.vmo);
+ if (status != ZX_OK) {
+ ZX_LOG(ERROR, status) << "zx_vmo_create";
+ return {};
+ }
+
+ status = converted.data.vmo.write(data_utf8.data(), 0, data_utf8.length());
+ if (status != ZX_OK) {
+ ZX_LOG(ERROR, status) << "zx_vmo_write";
+ return {};
+ }
+
+ return converted;
+}
+
+// Converts a FIDL chromium::web::WebMessage into a MessagePipe message, for
+// sending over Mojo.
+//
+// Returns a null mojo::Message if |message| was invalid.
+mojo::Message FromFidlMessage(chromium::web::WebMessage message) {
+ base::string16 data_utf16;
+ if (!ReadUTF8FromVMOAsUTF16(message.data, &data_utf16))
+ return mojo::Message();
+
+ // TODO(crbug.com/893236): support >1 transferable when fidlc cycle detection
+ // is fixed (FIDL-354).
+ blink::TransferableMessage transfer_message;
+ if (message.outgoing_transfer) {
+ transfer_message.ports.emplace_back(MessagePortImpl::FromFidl(
+ std::move(message.outgoing_transfer->message_port())));
+ }
+
+ transfer_message.owned_encoded_message =
+ blink::EncodeStringMessage(data_utf16);
+ transfer_message.encoded_message = transfer_message.owned_encoded_message;
+ return blink::mojom::TransferableMessage::SerializeAsMessage(
+ &transfer_message);
+}
+
+} // namespace
+
+MessagePortImpl::MessagePortImpl(mojo::ScopedMessagePipeHandle mojo_port)
+ : binding_(this) {
+ connector_ = std::make_unique<mojo::Connector>(
+ std::move(mojo_port), mojo::Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get());
+ connector_->set_incoming_receiver(this);
+ connector_->set_connection_error_handler(
+ base::BindOnce(&MessagePortImpl::OnDisconnected, base::Unretained(this)));
+ binding_.set_error_handler([this](zx_status_t status) {
+ if (status != ZX_OK && status != ZX_ERR_PEER_CLOSED)
+ ZX_DLOG(INFO, status) << "Disconnected";
+
+ OnDisconnected();
+ });
+}
+
+MessagePortImpl::~MessagePortImpl() {}
+
+void MessagePortImpl::OnDisconnected() {
+ // |connector_| and |binding_| are implicitly unbound.
+ delete this;
+}
+
+void MessagePortImpl::PostMessage(chromium::web::WebMessage message,
+ PostMessageCallback callback) {
+ mojo::Message mojo_message = FromFidlMessage(std::move(message));
+ if (mojo_message.IsNull()) {
+ callback(false);
+ delete this;
+ return;
+ }
+ CHECK(connector_->Accept(&mojo_message));
+ callback(true);
+}
+
+void MessagePortImpl::ReceiveMessage(ReceiveMessageCallback callback) {
+ pending_client_read_cb_ = std::move(callback);
+ MaybeDeliverToClient();
+}
+
+void MessagePortImpl::MaybeDeliverToClient() {
+ // Do nothing if the client hasn't requested a read, or if there's nothing to
+ // read.
+ if (!pending_client_read_cb_ || message_queue_.empty())
+ return;
+
+ base::ResetAndReturn (&pending_client_read_cb_)(
+ std::move(message_queue_.front()));
+ message_queue_.pop_front();
+}
+
+bool MessagePortImpl::Accept(mojo::Message* message) {
+ base::Optional<chromium::web::WebMessage> message_converted =
+ FromMojoMessage(std::move(*message));
+ if (!message_converted) {
+ DLOG(ERROR) << "Couldn't decode MessageChannel from Mojo pipe.";
+ return false;
+ }
+
+ message_queue_.push_back(std::move(message_converted.value()));
+ MaybeDeliverToClient();
+ return true;
+}
+
+// static
+mojo::ScopedMessagePipeHandle MessagePortImpl::FromFidl(
+ fidl::InterfaceRequest<chromium::web::MessagePort> port) {
+ mojo::ScopedMessagePipeHandle client_port;
+ mojo::ScopedMessagePipeHandle content_port;
+ mojo::CreateMessagePipe(0, &content_port, &client_port);
+
+ MessagePortImpl* port_impl = new MessagePortImpl(std::move(client_port));
+ port_impl->binding_.Bind(std::move(port));
+
+ return content_port;
+}
+
+// static
+fidl::InterfaceHandle<chromium::web::MessagePort> MessagePortImpl::FromMojo(
+ mojo::ScopedMessagePipeHandle port) {
+ MessagePortImpl* created_port = new MessagePortImpl(std::move(port));
+ return created_port->binding_.NewBinding();
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/message_port_impl.h b/chromium/fuchsia/browser/message_port_impl.h
new file mode 100644
index 00000000000..835d9fcf343
--- /dev/null
+++ b/chromium/fuchsia/browser/message_port_impl.h
@@ -0,0 +1,75 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_MESSAGE_PORT_IMPL_H_
+#define FUCHSIA_BROWSER_MESSAGE_PORT_IMPL_H_
+
+#include <lib/fidl/cpp/binding.h>
+#include <deque>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace webrunner {
+
+// Defines the implementation of a MessagePort which routes messages from
+// FIDL clients to web content, or vice versa. Every MessagePortImpl has a FIDL
+// port and a Mojo port.
+//
+// MessagePortImpl instances are self-managed; they destroy themselves when
+// the connection is terminated from either the Mojo or FIDL side.
+class MessagePortImpl : public chromium::web::MessagePort,
+ public mojo::MessageReceiver {
+ public:
+ // Creates a connected MessagePort from a FIDL MessagePort request and
+ // returns a handle to its peer Mojo pipe.
+ static mojo::ScopedMessagePipeHandle FromFidl(
+ fidl::InterfaceRequest<chromium::web::MessagePort> client_fidl_port);
+
+ // Creates a connected MessagePort from a transferred Mojo MessagePort and
+ // returns a handle to its FIDL interface peer.
+ static fidl::InterfaceHandle<chromium::web::MessagePort> FromMojo(
+ mojo::ScopedMessagePipeHandle port);
+
+ protected:
+ friend class base::DeleteHelper<MessagePortImpl>;
+
+ explicit MessagePortImpl(mojo::ScopedMessagePipeHandle mojo_port);
+
+ // Non-public to ensure that only this object may destroy itself.
+ ~MessagePortImpl() override;
+
+ fidl::Binding<chromium::web::MessagePort> binding_;
+
+ private:
+ // chromium::web::MessagePortImpl implementation.
+ void PostMessage(chromium::web::WebMessage message,
+ PostMessageCallback callback) override;
+ void ReceiveMessage(ReceiveMessageCallback callback) override;
+
+ // Called when the connection to Blink or FIDL is terminated.
+ void OnDisconnected();
+
+ // Sends the next message enqueued in |message_queue_| to the client,
+ // if the client has requested a message.
+ void MaybeDeliverToClient();
+
+ // mojo::MessageReceiver implementation.
+ bool Accept(mojo::Message* message) override;
+
+ std::deque<chromium::web::WebMessage> message_queue_;
+ ReceiveMessageCallback pending_client_read_cb_;
+ std::unique_ptr<mojo::Connector> connector_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePortImpl);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_MESSAGE_PORT_IMPL_H_
diff --git a/chromium/fuchsia/browser/webrunner_browser_context.cc b/chromium/fuchsia/browser/webrunner_browser_context.cc
new file mode 100644
index 00000000000..93775d2aaf0
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_browser_context.cc
@@ -0,0 +1,177 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_browser_context.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/base_paths_fuchsia.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/task/post_task.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_context.h"
+#include "fuchsia/browser/webrunner_net_log.h"
+#include "fuchsia/browser/webrunner_url_request_context_getter.h"
+#include "fuchsia/service/common.h"
+#include "net/url_request/url_request_context.h"
+#include "services/network/public/cpp/network_switches.h"
+
+namespace webrunner {
+
+class WebRunnerBrowserContext::ResourceContext
+ : public content::ResourceContext {
+ public:
+ ResourceContext() = default;
+ ~ResourceContext() override = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceContext);
+};
+
+std::unique_ptr<WebRunnerNetLog> CreateNetLog() {
+ std::unique_ptr<WebRunnerNetLog> result;
+
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(network::switches::kLogNetLog)) {
+ base::FilePath log_path =
+ command_line->GetSwitchValuePath(network::switches::kLogNetLog);
+ result = std::make_unique<WebRunnerNetLog>(log_path);
+ }
+
+ return result;
+}
+
+WebRunnerBrowserContext::WebRunnerBrowserContext(bool force_incognito)
+ : net_log_(CreateNetLog()), resource_context_(new ResourceContext()) {
+ if (!force_incognito) {
+ base::PathService::Get(base::DIR_APP_DATA, &data_dir_path_);
+ if (!base::PathExists(data_dir_path_)) {
+ // Run in incognito mode if /data doesn't exist.
+ data_dir_path_.clear();
+ }
+ }
+
+ BrowserContext::Initialize(this, data_dir_path_);
+}
+
+WebRunnerBrowserContext::~WebRunnerBrowserContext() {
+ NotifyWillBeDestroyed(this);
+
+ if (resource_context_) {
+ content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE,
+ std::move(resource_context_));
+ }
+
+ ShutdownStoragePartitions();
+}
+
+std::unique_ptr<content::ZoomLevelDelegate>
+WebRunnerBrowserContext::CreateZoomLevelDelegate(
+ const base::FilePath& partition_path) {
+ return nullptr;
+}
+
+base::FilePath WebRunnerBrowserContext::GetPath() const {
+ return data_dir_path_;
+}
+
+bool WebRunnerBrowserContext::IsOffTheRecord() const {
+ return data_dir_path_.empty();
+}
+
+content::ResourceContext* WebRunnerBrowserContext::GetResourceContext() {
+ return resource_context_.get();
+}
+
+content::DownloadManagerDelegate*
+WebRunnerBrowserContext::GetDownloadManagerDelegate() {
+ NOTIMPLEMENTED();
+ return nullptr;
+}
+
+content::BrowserPluginGuestManager* WebRunnerBrowserContext::GetGuestManager() {
+ return nullptr;
+}
+
+storage::SpecialStoragePolicy*
+WebRunnerBrowserContext::GetSpecialStoragePolicy() {
+ return nullptr;
+}
+
+content::PushMessagingService*
+WebRunnerBrowserContext::GetPushMessagingService() {
+ return nullptr;
+}
+
+content::SSLHostStateDelegate*
+WebRunnerBrowserContext::GetSSLHostStateDelegate() {
+ return nullptr;
+}
+
+content::PermissionControllerDelegate*
+WebRunnerBrowserContext::GetPermissionControllerDelegate() {
+ return nullptr;
+}
+
+content::ClientHintsControllerDelegate*
+WebRunnerBrowserContext::GetClientHintsControllerDelegate() {
+ return nullptr;
+}
+
+content::BackgroundFetchDelegate*
+WebRunnerBrowserContext::GetBackgroundFetchDelegate() {
+ return nullptr;
+}
+
+content::BackgroundSyncController*
+WebRunnerBrowserContext::GetBackgroundSyncController() {
+ return nullptr;
+}
+
+content::BrowsingDataRemoverDelegate*
+WebRunnerBrowserContext::GetBrowsingDataRemoverDelegate() {
+ return nullptr;
+}
+
+net::URLRequestContextGetter* WebRunnerBrowserContext::CreateRequestContext(
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors) {
+ DCHECK(!url_request_getter_);
+ url_request_getter_ = new WebRunnerURLRequestContextGetter(
+ base::CreateSingleThreadTaskRunnerWithTraits(
+ {content::BrowserThread::IO}),
+ net_log_.get(), std::move(*protocol_handlers),
+ std::move(request_interceptors), data_dir_path_);
+ return url_request_getter_.get();
+}
+
+net::URLRequestContextGetter*
+WebRunnerBrowserContext::CreateRequestContextForStoragePartition(
+ const base::FilePath& partition_path,
+ bool in_memory,
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors) {
+ return nullptr;
+}
+
+net::URLRequestContextGetter*
+WebRunnerBrowserContext::CreateMediaRequestContext() {
+ DCHECK(url_request_getter_.get());
+ return url_request_getter_.get();
+}
+
+net::URLRequestContextGetter*
+WebRunnerBrowserContext::CreateMediaRequestContextForStoragePartition(
+ const base::FilePath& partition_path,
+ bool in_memory) {
+ return nullptr;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_browser_context.h b/chromium/fuchsia/browser/webrunner_browser_context.h
new file mode 100644
index 00000000000..d1be10e1bce
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_browser_context.h
@@ -0,0 +1,73 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "content/public/browser/browser_context.h"
+
+namespace webrunner {
+
+class WebRunnerNetLog;
+class WebRunnerURLRequestContextGetter;
+
+class WebRunnerBrowserContext : public content::BrowserContext {
+ public:
+ // |force_incognito|: If set, then this BrowserContext will run in incognito
+ // mode even if /data is available.
+ explicit WebRunnerBrowserContext(bool force_incognito);
+ ~WebRunnerBrowserContext() override;
+
+ // BrowserContext implementation.
+ std::unique_ptr<content::ZoomLevelDelegate> CreateZoomLevelDelegate(
+ const base::FilePath& partition_path) override;
+ base::FilePath GetPath() const override;
+ bool IsOffTheRecord() const override;
+ content::ResourceContext* GetResourceContext() override;
+ content::DownloadManagerDelegate* GetDownloadManagerDelegate() override;
+ content::BrowserPluginGuestManager* GetGuestManager() override;
+ storage::SpecialStoragePolicy* GetSpecialStoragePolicy() override;
+ content::PushMessagingService* GetPushMessagingService() override;
+ content::SSLHostStateDelegate* GetSSLHostStateDelegate() override;
+ content::PermissionControllerDelegate* GetPermissionControllerDelegate()
+ override;
+ content::ClientHintsControllerDelegate* GetClientHintsControllerDelegate()
+ override;
+ content::BackgroundFetchDelegate* GetBackgroundFetchDelegate() override;
+ content::BackgroundSyncController* GetBackgroundSyncController() override;
+ content::BrowsingDataRemoverDelegate* GetBrowsingDataRemoverDelegate()
+ override;
+ net::URLRequestContextGetter* CreateRequestContext(
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors) override;
+ net::URLRequestContextGetter* CreateRequestContextForStoragePartition(
+ const base::FilePath& partition_path,
+ bool in_memory,
+ content::ProtocolHandlerMap* protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors) override;
+ net::URLRequestContextGetter* CreateMediaRequestContext() override;
+ net::URLRequestContextGetter* CreateMediaRequestContextForStoragePartition(
+ const base::FilePath& partition_path,
+ bool in_memory) override;
+
+ private:
+ // Contains URLRequestContextGetter required for resource loading.
+ class ResourceContext;
+
+ base::FilePath data_dir_path_;
+
+ std::unique_ptr<WebRunnerNetLog> net_log_;
+ scoped_refptr<WebRunnerURLRequestContextGetter> url_request_getter_;
+ std::unique_ptr<ResourceContext> resource_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerBrowserContext);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
diff --git a/chromium/fuchsia/browser/webrunner_browser_main.cc b/chromium/fuchsia/browser/webrunner_browser_main.cc
new file mode 100644
index 00000000000..aaca77df178
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_browser_main.cc
@@ -0,0 +1,29 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_browser_main.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_main_runner.h"
+
+namespace webrunner {
+
+int WebRunnerBrowserMain(const content::MainFunctionParams& parameters) {
+ std::unique_ptr<content::BrowserMainRunner> main_runner =
+ content::BrowserMainRunner::Create();
+ int exit_code = main_runner->Initialize(parameters);
+ if (exit_code >= 0)
+ return exit_code;
+
+ exit_code = main_runner->Run();
+
+ main_runner->Shutdown();
+
+ return exit_code;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_browser_main.h b/chromium/fuchsia/browser/webrunner_browser_main.h
new file mode 100644
index 00000000000..50a0858fdfc
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_browser_main.h
@@ -0,0 +1,18 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+
+#include <memory>
+
+namespace content {
+struct MainFunctionParams;
+} // namespace content
+
+namespace webrunner {
+int WebRunnerBrowserMain(const content::MainFunctionParams& parameters);
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
diff --git a/chromium/fuchsia/browser/webrunner_browser_main_parts.cc b/chromium/fuchsia/browser/webrunner_browser_main_parts.cc
new file mode 100644
index 00000000000..75581a2e009
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_browser_main_parts.cc
@@ -0,0 +1,84 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_browser_main_parts.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "content/public/browser/render_frame_host.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/browser/webrunner_browser_context.h"
+#include "fuchsia/browser/webrunner_screen.h"
+#include "fuchsia/service/common.h"
+#include "ui/aura/screen_ozone.h"
+#include "ui/ozone/public/ozone_platform.h"
+
+namespace webrunner {
+
+WebRunnerBrowserMainParts::WebRunnerBrowserMainParts(
+ zx::channel context_channel)
+ : context_channel_(std::move(context_channel)) {}
+
+WebRunnerBrowserMainParts::~WebRunnerBrowserMainParts() {
+ display::Screen::SetScreenInstance(nullptr);
+}
+
+void WebRunnerBrowserMainParts::PreMainMessageLoopRun() {
+ DCHECK(!screen_);
+
+ auto platform_screen = ui::OzonePlatform::GetInstance()->CreateScreen();
+ if (platform_screen) {
+ screen_ = std::make_unique<aura::ScreenOzone>(std::move(platform_screen));
+ } else {
+ // Use dummy display::Screen for Ozone platforms that don't provide
+ // PlatformScreen.
+ screen_ = std::make_unique<WebRunnerScreen>();
+ }
+
+ display::Screen::SetScreenInstance(screen_.get());
+
+ DCHECK(!browser_context_);
+ browser_context_ = std::make_unique<WebRunnerBrowserContext>(
+ base::CommandLine::ForCurrentProcess()->HasSwitch(kIncognitoSwitch));
+
+ context_service_ = std::make_unique<ContextImpl>(browser_context_.get());
+
+ context_binding_ = std::make_unique<fidl::Binding<chromium::web::Context>>(
+ context_service_.get(), fidl::InterfaceRequest<chromium::web::Context>(
+ std::move(context_channel_)));
+
+ // Quit the browser main loop when the Context connection is dropped.
+ context_binding_->set_error_handler([this](zx_status_t status) {
+ DLOG(WARNING) << "Client connection to Context service dropped.";
+ context_service_.reset();
+ std::move(quit_closure_).Run();
+ });
+
+ // Disable RenderFrameHost's Javascript injection restrictions so that the
+ // Context and Frames can implement their own JS injection policy at a higher
+ // level.
+ content::RenderFrameHost::AllowInjectingJavaScript();
+}
+
+void WebRunnerBrowserMainParts::PreDefaultMainMessageLoopRun(
+ base::OnceClosure quit_closure) {
+ quit_closure_ = std::move(quit_closure);
+}
+
+void WebRunnerBrowserMainParts::PostMainMessageLoopRun() {
+ // The service and its binding should have already been released by the error
+ // handler.
+ DCHECK(!context_service_);
+ DCHECK(!context_binding_->is_bound());
+
+ // These resources must be freed while a MessageLoop is still available, so
+ // that they may post cleanup tasks during teardown.
+ // NOTE: Please destroy objects in the reverse order of their creation.
+ browser_context_.reset();
+ screen_.reset();
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_browser_main_parts.h b/chromium/fuchsia/browser/webrunner_browser_main_parts.h
new file mode 100644
index 00000000000..7c251093537
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_browser_main_parts.h
@@ -0,0 +1,52 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
+
+#include <lib/fidl/cpp/binding.h>
+#include <memory>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "content/public/browser/browser_main_parts.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+
+namespace display {
+class Screen;
+}
+
+namespace webrunner {
+
+class WebRunnerBrowserContext;
+
+class WebRunnerBrowserMainParts : public content::BrowserMainParts {
+ public:
+ explicit WebRunnerBrowserMainParts(zx::channel context_channel);
+ ~WebRunnerBrowserMainParts() override;
+
+ ContextImpl* context() { return context_service_.get(); }
+
+ // content::BrowserMainParts overrides.
+ void PreMainMessageLoopRun() override;
+ void PreDefaultMainMessageLoopRun(base::OnceClosure quit_closure) override;
+ void PostMainMessageLoopRun() override;
+
+ private:
+ zx::channel context_channel_;
+
+ std::unique_ptr<display::Screen> screen_;
+ std::unique_ptr<WebRunnerBrowserContext> browser_context_;
+ std::unique_ptr<ContextImpl> context_service_;
+ std::unique_ptr<fidl::Binding<chromium::web::Context>> context_binding_;
+
+ base::OnceClosure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerBrowserMainParts);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
diff --git a/chromium/fuchsia/browser/webrunner_content_browser_client.cc b/chromium/fuchsia/browser/webrunner_content_browser_client.cc
new file mode 100644
index 00000000000..d7cb950b6e4
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_content_browser_client.cc
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_content_browser_client.h"
+
+#include <utility>
+
+#include "components/version_info/version_info.h"
+#include "content/public/common/user_agent.h"
+#include "fuchsia/browser/webrunner_browser_main_parts.h"
+
+namespace webrunner {
+
+WebRunnerContentBrowserClient::WebRunnerContentBrowserClient(
+ zx::channel context_channel)
+ : context_channel_(std::move(context_channel)) {}
+
+WebRunnerContentBrowserClient::~WebRunnerContentBrowserClient() = default;
+
+content::BrowserMainParts*
+WebRunnerContentBrowserClient::CreateBrowserMainParts(
+ const content::MainFunctionParams& parameters) {
+ DCHECK(context_channel_);
+ main_parts_ = new WebRunnerBrowserMainParts(std::move(context_channel_));
+ return main_parts_;
+}
+
+std::string WebRunnerContentBrowserClient::GetUserAgent() const {
+ return content::BuildUserAgentFromProduct(
+ version_info::GetProductNameAndVersionForUserAgent());
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_content_browser_client.h b/chromium/fuchsia/browser/webrunner_content_browser_client.h
new file mode 100644
index 00000000000..84a1dddaa4a
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_content_browser_client.h
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
+
+#include <lib/zx/channel.h>
+
+#include "base/macros.h"
+#include "content/public/browser/content_browser_client.h"
+
+namespace webrunner {
+
+class WebRunnerBrowserMainParts;
+
+class WebRunnerContentBrowserClient : public content::ContentBrowserClient {
+ public:
+ explicit WebRunnerContentBrowserClient(zx::channel context_channel);
+ ~WebRunnerContentBrowserClient() override;
+
+ WebRunnerBrowserMainParts* main_parts_for_test() const { return main_parts_; }
+
+ // ContentBrowserClient overrides.
+ content::BrowserMainParts* CreateBrowserMainParts(
+ const content::MainFunctionParams& parameters) override;
+ std::string GetUserAgent() const override;
+
+ private:
+ zx::channel context_channel_;
+ WebRunnerBrowserMainParts* main_parts_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerContentBrowserClient);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
diff --git a/chromium/fuchsia/browser/webrunner_net_log.cc b/chromium/fuchsia/browser/webrunner_net_log.cc
new file mode 100644
index 00000000000..ba60913c18e
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_net_log.cc
@@ -0,0 +1,54 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_net_log.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/values.h"
+#include "net/log/file_net_log_observer.h"
+#include "net/log/net_log_util.h"
+
+namespace webrunner {
+
+namespace {
+
+std::unique_ptr<base::DictionaryValue> GetWebRunnerConstants() {
+ std::unique_ptr<base::DictionaryValue> constants_dict =
+ net::GetNetConstants();
+
+ base::DictionaryValue dict;
+ dict.SetKey("name", base::Value("webrunner"));
+ dict.SetKey(
+ "command_line",
+ base::Value(
+ base::CommandLine::ForCurrentProcess()->GetCommandLineString()));
+
+ constants_dict->SetKey("clientInfo", std::move(dict));
+
+ return constants_dict;
+}
+
+} // namespace
+
+WebRunnerNetLog::WebRunnerNetLog(const base::FilePath& log_path) {
+ if (!log_path.empty()) {
+ net::NetLogCaptureMode capture_mode = net::NetLogCaptureMode::Default();
+ file_net_log_observer_ = net::FileNetLogObserver::CreateUnbounded(
+ log_path, GetWebRunnerConstants());
+ file_net_log_observer_->StartObserving(this, capture_mode);
+ }
+}
+
+WebRunnerNetLog::~WebRunnerNetLog() {
+ if (file_net_log_observer_)
+ file_net_log_observer_->StopObserving(nullptr, base::OnceClosure());
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_net_log.h b/chromium/fuchsia/browser/webrunner_net_log.h
new file mode 100644
index 00000000000..00d736ebfed
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_net_log.h
@@ -0,0 +1,36 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_NET_LOG_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_NET_LOG_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "net/log/net_log.h"
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace net {
+class FileNetLogObserver;
+} // namespace net
+
+namespace webrunner {
+
+class WebRunnerNetLog : public net::NetLog {
+ public:
+ explicit WebRunnerNetLog(const base::FilePath& log_path);
+ ~WebRunnerNetLog() override;
+
+ private:
+ std::unique_ptr<net::FileNetLogObserver> file_net_log_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerNetLog);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_NET_LOG_H_
diff --git a/chromium/fuchsia/browser/webrunner_screen.cc b/chromium/fuchsia/browser/webrunner_screen.cc
new file mode 100644
index 00000000000..18fe9472da4
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_screen.cc
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_screen.h"
+
+#include "ui/display/display.h"
+
+namespace webrunner {
+
+WebRunnerScreen::WebRunnerScreen() {
+ const int64_t kDefaultDisplayId = 1;
+ display::Display display(kDefaultDisplayId);
+ ProcessDisplayChanged(display, /*is_primary=*/true);
+}
+
+WebRunnerScreen::~WebRunnerScreen() = default;
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_screen.h b/chromium/fuchsia/browser/webrunner_screen.h
new file mode 100644
index 00000000000..f44dd9e2a88
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_screen.h
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_SCREEN_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_SCREEN_H_
+
+#include "base/macros.h"
+
+#include "ui/display/screen_base.h"
+
+namespace webrunner {
+
+// display::Screen implementation for WebRunner on Fuchsia.
+class DISPLAY_EXPORT WebRunnerScreen : public display::ScreenBase {
+ public:
+ WebRunnerScreen();
+ ~WebRunnerScreen() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerScreen);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_SCREEN_H_
diff --git a/chromium/fuchsia/browser/webrunner_url_request_context_getter.cc b/chromium/fuchsia/browser/webrunner_url_request_context_getter.cc
new file mode 100644
index 00000000000..32d2b2d697b
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_url_request_context_getter.cc
@@ -0,0 +1,77 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/browser/webrunner_url_request_context_getter.h"
+
+#include <utility>
+
+#include "base/single_thread_task_runner.h"
+#include "content/public/browser/cookie_store_factory.h"
+#include "net/cookies/cookie_store.h"
+#include "net/proxy_resolution/proxy_config_service.h"
+#include "net/ssl/channel_id_service.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+
+namespace webrunner {
+
+WebRunnerURLRequestContextGetter::WebRunnerURLRequestContextGetter(
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+ net::NetLog* net_log,
+ content::ProtocolHandlerMap protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors,
+ base::FilePath data_dir_path)
+ : network_task_runner_(std::move(network_task_runner)),
+ net_log_(net_log),
+ protocol_handlers_(std::move(protocol_handlers)),
+ request_interceptors_(std::move(request_interceptors)),
+ data_dir_path_(data_dir_path) {}
+
+WebRunnerURLRequestContextGetter::~WebRunnerURLRequestContextGetter() = default;
+
+net::URLRequestContext*
+WebRunnerURLRequestContextGetter::GetURLRequestContext() {
+ if (!url_request_context_) {
+ net::URLRequestContextBuilder builder;
+ builder.set_net_log(net_log_);
+ builder.set_data_enabled(true);
+
+ for (auto& protocol_handler : protocol_handlers_) {
+ builder.SetProtocolHandler(protocol_handler.first,
+ std::move(protocol_handler.second));
+ }
+ protocol_handlers_.clear();
+
+ builder.SetInterceptors(std::move(request_interceptors_));
+
+ if (data_dir_path_.empty()) {
+ // Set up an in-memory (ephemeral) CookieStore.
+ builder.SetCookieStore(
+ content::CreateCookieStore(content::CookieStoreConfig(), nullptr));
+ } else {
+ // Set up a persistent CookieStore under |data_dir_path|.
+ content::CookieStoreConfig cookie_config(
+ data_dir_path_.Append(FILE_PATH_LITERAL("Cookies")), false, false,
+ NULL);
+
+ // Platform encryption support is not yet implemented, so store cookies in
+ // plaintext for now.
+ // TODO(crbug.com/884355): Add OSCrypt impl for Fuchsia and encrypt the
+ // cookie store with it.
+ NOTIMPLEMENTED() << "Persistent cookie store is NOT encrypted!";
+ builder.SetCookieStore(
+ content::CreateCookieStore(cookie_config, nullptr));
+ }
+
+ url_request_context_ = builder.Build();
+ }
+ return url_request_context_.get();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+WebRunnerURLRequestContextGetter::GetNetworkTaskRunner() const {
+ return network_task_runner_;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/browser/webrunner_url_request_context_getter.h b/chromium/fuchsia/browser/webrunner_url_request_context_getter.h
new file mode 100644
index 00000000000..d89ec0bdf47
--- /dev/null
+++ b/chromium/fuchsia/browser/webrunner_url_request_context_getter.h
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "content/public/browser/browser_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+class NetLog;
+class ProxyConfigService;
+} // namespace net
+
+namespace webrunner {
+
+class WebRunnerURLRequestContextGetter : public net::URLRequestContextGetter {
+ public:
+ WebRunnerURLRequestContextGetter(
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+ net::NetLog* net_log,
+ content::ProtocolHandlerMap protocol_handlers,
+ content::URLRequestInterceptorScopedVector request_interceptors,
+ base::FilePath data_dir_path);
+
+ // net::URLRequestContextGetter overrides.
+ net::URLRequestContext* GetURLRequestContext() override;
+ scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
+ const override;
+
+ protected:
+ ~WebRunnerURLRequestContextGetter() override;
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+ net::NetLog* net_log_;
+ std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
+ std::unique_ptr<net::URLRequestContext> url_request_context_;
+
+ content::ProtocolHandlerMap protocol_handlers_;
+ content::URLRequestInterceptorScopedVector request_interceptors_;
+ base::FilePath data_dir_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerURLRequestContextGetter);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
diff --git a/chromium/fuchsia/cipd/build_id.template b/chromium/fuchsia/cipd/build_id.template
new file mode 100644
index 00000000000..32a49a4aef8
--- /dev/null
+++ b/chromium/fuchsia/cipd/build_id.template
@@ -0,0 +1 @@
+@MAJOR@.@MINOR@.@BUILD@.@PATCH@ \ No newline at end of file
diff --git a/chromium/fuchsia/cipd/castrunner.yaml b/chromium/fuchsia/cipd/castrunner.yaml
new file mode 100644
index 00000000000..5ddc2cd0b76
--- /dev/null
+++ b/chromium/fuchsia/cipd/castrunner.yaml
@@ -0,0 +1,33 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Creates a package for the Cast application Runner.
+# Note that the web.ContextProvider package is required by the Cast Runner.
+# The ID of the target architecture (amd64, arm64) must be passed in as
+# a pkg-var so that the packages can be archived at the appropriate location
+# in the CIPD package hierarchy.
+#
+# pkg-var arguments:
+# outdir: A fully qualified path to the build output directory.
+# targetarch: The target architecture, either "amd64" or "arm64".
+#
+# To create a CIPD package, run the following command from the build output
+# directory.
+#
+# $ cipd create --pkg-def ../../fuchsia/cipd/castrunner.yaml \
+# -pkg-var targetarch:$TARGET_ARCH \
+# -pkg-var outdir:`pwd` \
+# -ref latest \
+# -tag version:$(cat fuchsia_artifacts/build_id.txt)
+#
+# The most recent package can be discovered by searching for the "latest" ref:
+#
+# $ cipd describe chromium/fuchsia/castrunner-$TARGET_ARCH -version latest
+
+package: chromium/fuchsia/castrunner-${targetarch}
+description: Prebuilt Cast application Runner binaries for Fuchsia.
+root: ${outdir}/fuchsia_artifacts
+data:
+ - file: cast_runner.far
+ - file: LICENSE
diff --git a/chromium/fuchsia/cipd/fidl.yaml b/chromium/fuchsia/cipd/fidl.yaml
new file mode 100644
index 00000000000..5be7aa6ed3b
--- /dev/null
+++ b/chromium/fuchsia/cipd/fidl.yaml
@@ -0,0 +1,22 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Creates a CIPD package for the 'chromium' service FIDL definitions.
+#
+# To create a CIPD package, run the following command from the build output
+# directory.
+#
+# $ cipd create --pkg-def ../../fuchsia/cipd/fidl.yaml \
+# -ref latest \
+# -tag version:$(cat fuchsia_artifacts/build_id.txt)
+#
+# The most recent package can be discovered by searching for the "latest" ref:
+#
+# $ cipd describe chromium/fuchsia/fidl -version latest
+
+package: chromium/fuchsia/fidl
+description: FIDL definitions for the "chromium" service.
+root: ../fidl
+data:
+ - dir: "."
diff --git a/chromium/fuchsia/cipd/http.yaml b/chromium/fuchsia/cipd/http.yaml
new file mode 100644
index 00000000000..702c6fac1be
--- /dev/null
+++ b/chromium/fuchsia/cipd/http.yaml
@@ -0,0 +1,32 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Creates a package for the HTTP service binary.
+# The ID of the target architecture (amd64, arm64) must be passed in as
+# a pkg-var so that the packages can be archived at the appropriate location
+# in the CIPD package hierarchy.
+#
+# pkg-var arguments:
+# outdir: A fully qualified path to the build output directory.
+# targetarch: The target architecture, either "amd64" or "arm64".
+#
+# To create a CIPD package, run the following command from the build output
+# directory.
+#
+# $ cipd create --pkg-def ../../fuchsia/cipd/http.yaml \
+# -pkg-var targetarch:$TARGET_ARCH \
+# -pkg-var outdir:`pwd` \
+# -ref latest \
+# -tag version:$(cat fuchsia_artifacts/build_id.txt)
+#
+# The most recent package can be discovered by searching for the "latest" ref:
+#
+# $ cipd describe chromium/fuchsia/http-$TARGET_ARCH -version latest
+
+package: chromium/fuchsia/http-${targetarch}
+description: Prebuilt HTTP service binary for Fuchsia.
+root: ${outdir}/fuchsia_artifacts
+data:
+ - file: http.far
+ - file: LICENSE
diff --git a/chromium/fuchsia/cipd/webrunner.yaml b/chromium/fuchsia/cipd/webrunner.yaml
new file mode 100644
index 00000000000..9b82992ce84
--- /dev/null
+++ b/chromium/fuchsia/cipd/webrunner.yaml
@@ -0,0 +1,33 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Creates a package for the WebRunner and Chromium service binaries.
+# The ID of the target architecture (amd64, arm64) must be passed in as
+# a pkg-var so that the packages can be archived at the appropriate location
+# in the CIPD package hierarchy.
+#
+# pkg-var arguments:
+# outdir: A fully qualified path to the build output directory.
+# targetarch: The target architecture, either "amd64" or "arm64".
+#
+# To create a CIPD package, run the following command from the build output
+# directory.
+#
+# $ cipd create --pkg-def ../../fuchsia/cipd/webrunner.yaml \
+# -pkg-var targetarch:$TARGET_ARCH \
+# -pkg-var outdir:`pwd` \
+# -ref latest \
+# -tag version:$(cat fuchsia_artifacts/build_id.txt)
+#
+# The most recent package can be discovered by searching for the "latest" ref:
+#
+# $ cipd describe chromium/fuchsia/webrunner-$TARGET_ARCH -version latest
+
+package: chromium/fuchsia/webrunner-${targetarch}
+description: Prebuilt Chrome and Web Runner binaries for Fuchsia.
+root: ${outdir}/fuchsia_artifacts
+data:
+ - file: chromium.far
+ - file: web_runner.far
+ - file: LICENSE
diff --git a/chromium/fuchsia/common/DEPS b/chromium/fuchsia/common/DEPS
new file mode 100644
index 00000000000..5fed38488cb
--- /dev/null
+++ b/chromium/fuchsia/common/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+components/version_info",
+ "+content/public/browser",
+ "+content/public/common",
+ "+content/public/test",
+ "+ui/base",
+ "+ui/ozone/public",
+]
diff --git a/chromium/fuchsia/common/OWNERS b/chromium/fuchsia/common/OWNERS
new file mode 100644
index 00000000000..08850f42120
--- /dev/null
+++ b/chromium/fuchsia/common/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chromium/fuchsia/common/fuchsia_export.h b/chromium/fuchsia/common/fuchsia_export.h
new file mode 100644
index 00000000000..3a7f4761b25
--- /dev/null
+++ b/chromium/fuchsia/common/fuchsia_export.h
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_COMMON_FUCHSIA_EXPORT_H_
+#define FUCHSIA_COMMON_FUCHSIA_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WEBRUNNER_IMPLEMENTATION)
+#define FUCHSIA_EXPORT __attribute__((visibility("default")))
+#else
+#define FUCHSIA_EXPORT
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define FUCHSIA_EXPORT
+#endif
+
+#endif // FUCHSIA_COMMON_FUCHSIA_EXPORT_H_
diff --git a/chromium/fuchsia/common/mem_buffer_util.cc b/chromium/fuchsia/common/mem_buffer_util.cc
new file mode 100644
index 00000000000..837e4c2c22b
--- /dev/null
+++ b/chromium/fuchsia/common/mem_buffer_util.cc
@@ -0,0 +1,88 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/common/mem_buffer_util.h"
+
+#include <lib/fdio/io.h>
+
+#include <lib/zx/vmo.h>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace webrunner {
+
+bool ReadUTF8FromVMOAsUTF16(const fuchsia::mem::Buffer& buffer,
+ base::string16* output) {
+ std::string output_utf8;
+ if (!StringFromMemBuffer(buffer, &output_utf8))
+ return false;
+ return base::UTF8ToUTF16(&output_utf8.front(), output_utf8.size(), output);
+}
+
+fuchsia::mem::Buffer MemBufferFromString(const base::StringPiece& data) {
+ fuchsia::mem::Buffer buffer;
+
+ zx_status_t status =
+ zx::vmo::create(data.size(), ZX_VMO_NON_RESIZABLE, &buffer.vmo);
+ ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
+
+ status = buffer.vmo.write(data.data(), 0, data.size());
+ ZX_CHECK(status == ZX_OK, status) << "zx_vmo_write";
+
+ buffer.size = data.size();
+ return buffer;
+}
+
+fuchsia::mem::Buffer MemBufferFromString16(const base::StringPiece16& data) {
+ return MemBufferFromString(
+ base::StringPiece(reinterpret_cast<const char*>(data.data()),
+ data.size() * sizeof(base::char16)));
+}
+
+bool StringFromMemBuffer(const fuchsia::mem::Buffer& buffer,
+ std::string* output) {
+ output->resize(buffer.size);
+ zx_status_t status = buffer.vmo.read(&output->at(0), 0, buffer.size);
+ if (status != ZX_OK) {
+ ZX_LOG(ERROR, status) << "zx_vmo_read";
+ return false;
+ }
+ return true;
+}
+
+fuchsia::mem::Buffer MemBufferFromFile(base::File file) {
+ if (!file.IsValid())
+ return {};
+
+ zx::vmo vmo;
+ zx_status_t status =
+ fdio_get_vmo_copy(file.GetPlatformFile(), vmo.reset_and_get_address());
+ if (status != ZX_OK) {
+ ZX_LOG(ERROR, status) << "fdio_get_vmo_copy";
+ return {};
+ }
+
+ fuchsia::mem::Buffer output;
+ output.vmo = std::move(vmo);
+ output.size = file.GetLength();
+ return output;
+}
+
+fuchsia::mem::Buffer CloneBuffer(const fuchsia::mem::Buffer& buffer) {
+ fuchsia::mem::Buffer output;
+ output.size = buffer.size;
+ zx_status_t status =
+ buffer.vmo.clone(ZX_VMO_CLONE_COPY_ON_WRITE | ZX_VMO_CLONE_NON_RESIZEABLE,
+ 0, buffer.size, &output.vmo);
+ ZX_CHECK(status == ZX_OK, status) << "zx_vmo_clone";
+ return output;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/common/mem_buffer_util.h b/chromium/fuchsia/common/mem_buffer_util.h
new file mode 100644
index 00000000000..c320fcb8450
--- /dev/null
+++ b/chromium/fuchsia/common/mem_buffer_util.h
@@ -0,0 +1,42 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_COMMON_MEM_BUFFER_UTIL_H_
+#define FUCHSIA_COMMON_MEM_BUFFER_UTIL_H_
+
+#include <fuchsia/mem/cpp/fidl.h>
+#include <lib/zx/channel.h>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace webrunner {
+
+// Reads the contents of |buffer|, encoded in UTF-8, to a UTF-16 string.
+// Returns |false| if |buffer| is not valid UTF-8.
+bool ReadUTF8FromVMOAsUTF16(const fuchsia::mem::Buffer& buffer,
+ base::string16* output);
+
+// Creates a Fuchsia memory buffer from |data|.
+fuchsia::mem::Buffer MemBufferFromString(const base::StringPiece& data);
+
+// Creates a Fuchsia memory buffer from the UTF-16 string |data|.
+fuchsia::mem::Buffer MemBufferFromString16(const base::StringPiece16& data);
+
+// Reads the contents of |buffer| into |output|.
+// Returns true if the read operation succeeded.
+bool StringFromMemBuffer(const fuchsia::mem::Buffer& buffer,
+ std::string* output);
+
+// Creates a memory-mapped, read-only Buffer with the contents of |file|.
+// Will return an empty Buffer if the file could not be opened.
+fuchsia::mem::Buffer MemBufferFromFile(base::File file);
+
+// Creates a non-resizeable, copy-on-write shared memory clone of |buffer|.
+fuchsia::mem::Buffer CloneBuffer(const fuchsia::mem::Buffer& buffer);
+
+} // namespace webrunner
+
+#endif // FUCHSIA_COMMON_MEM_BUFFER_UTIL_H_
diff --git a/chromium/fuchsia/common/named_message_port_connector.cc b/chromium/fuchsia/common/named_message_port_connector.cc
new file mode 100644
index 00000000000..41a0eaf150d
--- /dev/null
+++ b/chromium/fuchsia/common/named_message_port_connector.cc
@@ -0,0 +1,127 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/common/named_message_port_connector.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/threading/thread_restrictions.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+
+namespace webrunner {
+namespace {
+
+const char kBindingsJsPath[] =
+ FILE_PATH_LITERAL("fuchsia/common/named_message_port_connector.js");
+const char kConnectedMessage[] = "connected";
+
+} // namespace
+
+NamedMessagePortConnector::NamedMessagePortConnector() {
+ base::FilePath assets_path;
+ CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
+ bindings_script_ = MemBufferFromFile(
+ base::File(assets_path.AppendASCII(kBindingsJsPath),
+ base::File::FLAG_OPEN | base::File::FLAG_READ));
+}
+
+NamedMessagePortConnector::~NamedMessagePortConnector() = default;
+
+void NamedMessagePortConnector::Register(const std::string& id,
+ PortConnectedCallback handler,
+ chromium::web::Frame* frame) {
+ DCHECK(handler);
+ DCHECK(!id.empty() && id.find(' ') == std::string::npos);
+
+ if (registrations_.find(frame) == registrations_.end())
+ InjectBindings(frame);
+
+ RegistrationEntry entry;
+ entry.id = id;
+ entry.handler = std::move(handler);
+ registrations_.insert(std::make_pair(std::move(frame), std::move(entry)));
+}
+
+void NamedMessagePortConnector::Unregister(chromium::web::Frame* frame,
+ const std::string& id) {
+ auto port_range = registrations_.equal_range(frame);
+ auto it = port_range.first;
+ while (it != port_range.second) {
+ if (it->second.id == id) {
+ it = registrations_.erase(it);
+ continue;
+ }
+ it++;
+ }
+}
+
+void NamedMessagePortConnector::NotifyPageLoad(chromium::web::Frame* frame) {
+ auto registration_range = registrations_.equal_range(frame);
+
+ // Push all bound MessagePorts to the page after every page load.
+ for (auto it = registration_range.first; it != registration_range.second;
+ ++it) {
+ const RegistrationEntry& registration = it->second;
+
+ chromium::web::WebMessage message;
+ message.data = webrunner::MemBufferFromString("connect " + registration.id);
+
+ // Call the handler callback, with the MessagePort client object.
+ message.outgoing_transfer =
+ std::make_unique<chromium::web::OutgoingTransferable>();
+ chromium::web::MessagePortPtr message_port;
+ message.outgoing_transfer->set_message_port(message_port.NewRequest());
+
+ // Send the port to the handler once a "connected" message is received from
+ // the peer, so that the caller has a stronger guarantee that the content is
+ // healthy and capable of processing messages.
+ message_port->ReceiveMessage(
+ [message_port = std::move(message_port),
+ handler =
+ registration.handler](chromium::web::WebMessage message) mutable {
+ std::string message_str;
+ if (!StringFromMemBuffer(message.data, &message_str)) {
+ LOG(ERROR) << "Couldn't read from message VMO.";
+ return;
+ }
+
+ if (message_str != kConnectedMessage) {
+ LOG(ERROR) << "Unexpected message from port: " << message_str;
+ return;
+ }
+
+ handler.Run(std::move(message_port));
+ });
+
+ // Pass the other half of the MessagePort connection to the document.
+ it->first->PostMessage(std::move(message), "*",
+ [](bool result) { CHECK(result); });
+ }
+}
+
+void NamedMessagePortConnector::InjectBindings(chromium::web::Frame* frame) {
+ DCHECK(frame);
+
+ std::vector<std::string> origins = {"*"};
+ frame->ExecuteJavaScript(
+ std::move(origins), CloneBuffer(bindings_script_),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { CHECK(success) << "Couldn't inject bindings."; });
+}
+
+NamedMessagePortConnector::RegistrationEntry::RegistrationEntry() = default;
+
+NamedMessagePortConnector::RegistrationEntry::~RegistrationEntry() = default;
+
+NamedMessagePortConnector::RegistrationEntry::RegistrationEntry(
+ const RegistrationEntry& other) = default;
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/common/named_message_port_connector.h b/chromium/fuchsia/common/named_message_port_connector.h
new file mode 100644
index 00000000000..352903b8682
--- /dev/null
+++ b/chromium/fuchsia/common/named_message_port_connector.h
@@ -0,0 +1,70 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
+#define FUCHSIA_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+
+namespace webrunner {
+
+// The implementation actively creates a MessagePort for each registered Id,
+// passing them all to the web container every time a page-load occurs. It then
+// waits for the page to acknowledge each MessagePort, before invoking its
+// registered |handler| to notify the called that it is ready for use.
+class FUCHSIA_EXPORT NamedMessagePortConnector {
+ public:
+ using PortConnectedCallback =
+ base::RepeatingCallback<void(chromium::web::MessagePortPtr)>;
+
+ NamedMessagePortConnector();
+ ~NamedMessagePortConnector();
+
+ // Registers a |handler| which will be passed a new MessagePort after every
+ // page load.
+ //
+ // The first call to Register() will ensure that the supporting JavaScript
+ // bundle for the NamedMessagePortConnector is injected into |frame|.
+ // Therefore, any JavaScript bundles which depend on the Connector must be
+ // injected after Register is called.
+ void Register(const std::string& id,
+ PortConnectedCallback handler,
+ chromium::web::Frame* frame);
+
+ // Unregisters a handler for |frame|. Should be called before |frame| is
+ // deleted.
+ void Unregister(chromium::web::Frame* frame, const std::string& id);
+
+ // Client should call this every time a page is loaded.
+ void NotifyPageLoad(chromium::web::Frame* frame);
+
+ private:
+ struct RegistrationEntry {
+ RegistrationEntry();
+ ~RegistrationEntry();
+ RegistrationEntry(const RegistrationEntry& other);
+
+ std::string id;
+ PortConnectedCallback handler;
+ };
+
+ void InjectBindings(chromium::web::Frame* frame);
+
+ std::multimap<chromium::web::Frame*, RegistrationEntry> registrations_;
+ fuchsia::mem::Buffer bindings_script_;
+
+ DISALLOW_COPY_AND_ASSIGN(NamedMessagePortConnector);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
diff --git a/chromium/fuchsia/common/named_message_port_connector.js b/chromium/fuchsia/common/named_message_port_connector.js
new file mode 100644
index 00000000000..691c8e32d11
--- /dev/null
+++ b/chromium/fuchsia/common/named_message_port_connector.js
@@ -0,0 +1,117 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+if (!cast)
+ var cast = new Object;
+
+if (!cast.__platform__)
+ cast.__platform__ = new Object;
+
+// Defines a late-bound port for performing bidirectional communication with
+// platform code. Ports are passed in from outside the browser process after
+// every pages load. Constructed ports are not initially connected, but they are
+// immediately available for writing via a buffer. When the Channel is
+// eventually connected, the buffer will be flushed and all subsequent writes
+// will be sent directly to the peer.
+cast.__platform__.Port = class {
+ constructor(id, messageHandler) {
+ this.messageHandler_ = messageHandler;
+ this.id_ = id;
+ }
+
+ // Sends a message and optionally a list of Transferables to the Port,
+ // or buffers the message for later delivery if |this| is unconnected.
+ sendMessage(data, transferables) {
+ if (this.port_) {
+ this.port_.postMessage(data, transferables);
+ } else {
+ this.buffer_.push({'data': data, 'transferables': transferables});
+ }
+ }
+
+ // Receives a MessagePort from cast.__platform__.connector.
+ receivePort(port) {
+ this.port_ = port;
+ this.port_.onmessage = this.onMessage.bind(this);
+ this.port_.onmessageerror = this.onMessageError.bind(this);
+
+ // Signal the peer that the port was received.
+ this.port_.postMessage('connected');
+
+ // Flush and destroy the pre-connect buffer.
+ for (var i = 0; i < this.buffer_.length; ++i) {
+ this.port_.postMessage(this.buffer_[i].data,
+ this.buffer_[i].transferables);
+ }
+ this.buffer = null;
+ }
+
+ onMessage(message) {
+ this.messageHandler_(message.data);
+ }
+
+ onMessageError() {
+ // The underlying MessagePort should be long-lived and reliable. Any
+ // connection drop is likely due to a bug on the other side of the
+ // FIDL IPC connection.
+ console.error('Unexpected connection loss for channel ' + this.id);
+ this.port_ = null;
+ }
+
+ // Called when a message arrives.
+ messageHandler_ = null;
+
+ // Buffer of messages to be sent prior to a port being received.
+ // Once a port is bound, the buffer is flushed and then destroyed.
+ buffer_ = [];
+
+ // Underlying message transport.
+ port_ = null;
+};
+
+// Creates new string-identified Ports and asynchronously binds them to
+// message transport.
+cast.__platform__.connector = new class {
+ // Sets up a Port which will be asynchronously bound to a MessagePort.
+ // The port is identified by a string |id|, which must not have spaces.
+ bind(id, messageHandler) {
+ var binding = new cast.__platform__.Port(id, messageHandler);
+ this.pending_ports_[id] = binding;
+ return binding;
+ }
+
+ // Services incoming port connection events via the window.onmessage handler.
+ onMessageEvent(e) {
+ // Only process events identified by a magic 'connect' prefix.
+ // Let all other events pass through to other handlers.
+ var tokenized = e.data.split(' ');
+ if (tokenized.length != 2 || tokenized[0] != 'connect')
+ return;
+
+ if (e.ports.length != 1) {
+ console.error('Expected only one MessagePort, got ' + e.ports.length +
+ ' instead.');
+ return;
+ }
+
+ var portName = tokenized[1];
+ if (!(portName in this.pending_ports_)) {
+ // Bindings must be registered in advance of MessagePorts.
+ console.error('No handler for port: ' + portName);
+ return;
+ }
+
+ this.pending_ports_[portName].receivePort(e.ports[0]);
+ delete this.pending_ports_[portName];
+ e.stopPropagation();
+ }
+
+ // A map of unconnected ports waiting to be connected with MessagePorts,
+ // keyed string IDs.
+ pending_ports_ = {};
+}();
+
+window.addEventListener('message',
+ cast.__platform__.connector.onMessageEvent.bind(
+ cast.__platform__.connector), true);
diff --git a/chromium/fuchsia/common/named_message_port_connector_browsertest.cc b/chromium/fuchsia/common/named_message_port_connector_browsertest.cc
new file mode 100644
index 00000000000..ff5cf38cba2
--- /dev/null
+++ b/chromium/fuchsia/common/named_message_port_connector_browsertest.cc
@@ -0,0 +1,123 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/barrier_closure.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/test/test_timeouts.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/test/promise.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_constants.h"
+
+namespace webrunner {
+
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
+
+class NamedMessagePortConnectorTest
+ : public webrunner::WebRunnerBrowserTest,
+ public chromium::web::NavigationEventObserver {
+ public:
+ NamedMessagePortConnectorTest()
+ : run_timeout_(TestTimeouts::action_timeout()) {
+ set_test_server_root(base::FilePath("fuchsia/common/test/data"));
+ }
+
+ ~NamedMessagePortConnectorTest() override = default;
+
+ protected:
+ void SetUpOnMainThread() override {
+ webrunner::WebRunnerBrowserTest::SetUpOnMainThread();
+ frame_ = WebRunnerBrowserTest::CreateFrame(this);
+ }
+
+ void OnNavigationStateChanged(
+ chromium::web::NavigationEvent change,
+ OnNavigationStateChangedCallback callback) override {
+ connector_.NotifyPageLoad(frame_.get());
+ if (navigate_run_loop_)
+ navigate_run_loop_->Quit();
+ callback();
+ }
+
+ void CheckLoadUrl(const std::string& url,
+ chromium::web::NavigationController* controller) {
+ navigate_run_loop_ = std::make_unique<base::RunLoop>();
+ controller->LoadUrl(url, nullptr);
+ navigate_run_loop_->Run();
+ navigate_run_loop_.reset();
+ }
+
+ std::unique_ptr<base::RunLoop> navigate_run_loop_;
+ chromium::web::FramePtr frame_;
+ NamedMessagePortConnector connector_;
+
+ private:
+ const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(NamedMessagePortConnectorTest);
+};
+
+IN_PROC_BROWSER_TEST_F(NamedMessagePortConnectorTest,
+ NamedMessagePortConnectorEndToEnd) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL test_url(embedded_test_server()->GetURL("/connector.html"));
+
+ chromium::web::NavigationControllerPtr controller;
+ frame_->GetNavigationController(controller.NewRequest());
+
+ base::RunLoop receive_port_run_loop;
+ Promise<chromium::web::MessagePortPtr> message_port(
+ receive_port_run_loop.QuitClosure());
+ connector_.Register(
+ "hello",
+ base::BindRepeating(&Promise<chromium::web::MessagePortPtr>::ReceiveValue,
+ base::Unretained(&message_port)),
+ frame_.get());
+ CheckLoadUrl(test_url.spec(), controller.get());
+
+ receive_port_run_loop.Run();
+
+ chromium::web::WebMessage msg;
+ msg.data = MemBufferFromString("ping");
+ Promise<bool> post_result;
+ (*message_port)
+ ->PostMessage(std::move(msg),
+ ConvertToFitFunction(post_result.GetReceiveCallback()));
+
+ std::vector<std::string> test_messages = {"early 1", "early 2", "ack ping"};
+ for (std::string expected_msg : test_messages) {
+ base::RunLoop run_loop;
+ Promise<chromium::web::WebMessage> message_receiver(run_loop.QuitClosure());
+ (*message_port)
+ ->ReceiveMessage(
+ ConvertToFitFunction(message_receiver.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ(StringFromMemBufferOrDie(message_receiver->data), expected_msg);
+ }
+
+ // Ensure that the MessagePort is dropped when navigating away.
+ {
+ base::RunLoop run_loop;
+ (*message_port).set_error_handler([&run_loop](zx_status_t) {
+ run_loop.Quit();
+ });
+ controller->LoadUrl("about:blank", nullptr);
+ run_loop.Run();
+ }
+
+ connector_.Unregister(frame_.get(), "hello");
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/common/on_load_script_injector.mojom b/chromium/fuchsia/common/on_load_script_injector.mojom
new file mode 100644
index 00000000000..47ec9f13530
--- /dev/null
+++ b/chromium/fuchsia/common/on_load_script_injector.mojom
@@ -0,0 +1,14 @@
+// Copyright 2018 The Chromium 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 webrunner.mojom;
+
+// Interface associated with RenderFrames for managing on-load JavaScript
+// injection tasks the frame. Does not enforce script injection policies,
+// which must be implemented at a higher level.
+interface OnLoadScriptInjector {
+ AddOnLoadScript(handle<shared_buffer> script);
+
+ ClearOnLoadScripts();
+};
diff --git a/chromium/fuchsia/common/webrunner_content_client.cc b/chromium/fuchsia/common/webrunner_content_client.cc
new file mode 100644
index 00000000000..019d7e00da7
--- /dev/null
+++ b/chromium/fuchsia/common/webrunner_content_client.cc
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/common/webrunner_content_client.h"
+
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace webrunner {
+
+WebRunnerContentClient::WebRunnerContentClient() = default;
+WebRunnerContentClient::~WebRunnerContentClient() = default;
+
+base::string16 WebRunnerContentClient::GetLocalizedString(
+ int message_id) const {
+ return l10n_util::GetStringUTF16(message_id);
+}
+
+base::StringPiece WebRunnerContentClient::GetDataResource(
+ int resource_id,
+ ui::ScaleFactor scale_factor) const {
+ return ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
+ resource_id, scale_factor);
+}
+
+base::RefCountedMemory* WebRunnerContentClient::GetDataResourceBytes(
+ int resource_id) const {
+ return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
+ resource_id);
+}
+
+gfx::Image& WebRunnerContentClient::GetNativeImageNamed(int resource_id) const {
+ return ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
+ resource_id);
+}
+
+blink::OriginTrialPolicy* WebRunnerContentClient::GetOriginTrialPolicy() {
+ NOTIMPLEMENTED();
+ return nullptr;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/common/webrunner_content_client.h b/chromium/fuchsia/common/webrunner_content_client.h
new file mode 100644
index 00000000000..26e36a84153
--- /dev/null
+++ b/chromium/fuchsia/common/webrunner_content_client.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
+#define FUCHSIA_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
+
+#include "base/macros.h"
+#include "content/public/common/content_client.h"
+
+namespace webrunner {
+
+class WebRunnerContentClient : public content::ContentClient {
+ public:
+ WebRunnerContentClient();
+ ~WebRunnerContentClient() override;
+
+ // content::ContentClient implementation.
+ base::string16 GetLocalizedString(int message_id) const override;
+ base::StringPiece GetDataResource(
+ int resource_id,
+ ui::ScaleFactor scale_factor) const override;
+ base::RefCountedMemory* GetDataResourceBytes(int resource_id) const override;
+ gfx::Image& GetNativeImageNamed(int resource_id) const override;
+ blink::OriginTrialPolicy* GetOriginTrialPolicy() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerContentClient);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
diff --git a/chromium/fuchsia/fidl/cast/application_config.fidl b/chromium/fuchsia/fidl/cast/application_config.fidl
new file mode 100644
index 00000000000..a312e26e95e
--- /dev/null
+++ b/chromium/fuchsia/fidl/cast/application_config.fidl
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.cast;
+
+/// Describes the configuration under which a Cast application should run.
+struct ApplicationConfig {
+ /// Cast application Id.
+ string id;
+
+ /// Name to display to the user when referring to this application.
+ string display_name;
+
+ /// Standard web URL from which to load the application.
+ string web_url;
+};
+
+/// Service interface for working with application configurations.
+[Discoverable]
+interface ApplicationConfigManager {
+ /// Returns the ApplicationConfig for the specified application Id.
+ 1: GetConfig(string id) -> (ApplicationConfig? config);
+};
diff --git a/chromium/fuchsia/fidl/cast/cast_channel.fidl b/chromium/fuchsia/fidl/cast/cast_channel.fidl
new file mode 100644
index 00000000000..3ec3374fbf9
--- /dev/null
+++ b/chromium/fuchsia/fidl/cast/cast_channel.fidl
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.cast;
+
+using chromium.web;
+
+[Discoverable]
+interface CastChannel {
+ /// Handles Cast Channel open calls from the webpage.
+ /// The new Cast Channel's message port is returned asynchronously once
+ /// opened by the webpage. The port is disconnected when the peer's Cast
+ /// Channel is closed.
+ 1: Connect() -> (chromium.web.MessagePort channel);
+};
+
diff --git a/chromium/fuchsia/fidl/web/context.fidl b/chromium/fuchsia/fidl/web/context.fidl
new file mode 100644
index 00000000000..923e99c7cdb
--- /dev/null
+++ b/chromium/fuchsia/fidl/web/context.fidl
@@ -0,0 +1,14 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.web;
+
+/// Manages browsing state (e.g. LocalStorage, cookies, etc) associated with
+/// a set of Frames.
+interface Context {
+ /// Creates a new frame under this Context.
+ ///
+ /// |frame|: An interface request that will be bound to the created Frame.
+ CreateFrame(request<Frame> frame);
+};
diff --git a/chromium/fuchsia/fidl/web/context_provider.fidl b/chromium/fuchsia/fidl/web/context_provider.fidl
new file mode 100644
index 00000000000..dc92e474c6b
--- /dev/null
+++ b/chromium/fuchsia/fidl/web/context_provider.fidl
@@ -0,0 +1,29 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.web;
+
+/// The top-level service interface which allows for the creation of
+/// Context resources.
+[Discoverable]
+interface ContextProvider {
+ /// Creates a new browser Context whose state is wholly independent and
+ /// isolated from other Contexts.
+ ///
+ /// context: An interface request which will receive a bound Context
+ /// service.
+ Create(CreateContextParams params, request<Context> context);
+};
+
+struct CreateContextParams {
+ /// Service directory to be used by the context.
+ // TODO(https://crbug.com/870057): Document required and optional services
+ // that Context needs.
+ handle<channel> service_directory;
+
+ /// Handle to the directory that will contain the Context's
+ /// persistent data. If it is left unset, then the created Context will be
+ /// stateless, with all of its data discarded upon Context destruction.
+ handle<channel>? data_directory;
+};
diff --git a/chromium/fuchsia/fidl/web/frame.fidl b/chromium/fuchsia/fidl/web/frame.fidl
new file mode 100644
index 00000000000..9141a7064f1
--- /dev/null
+++ b/chromium/fuchsia/fidl/web/frame.fidl
@@ -0,0 +1,137 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.web;
+
+using fuchsia.mem;
+using fuchsia.sys;
+using fuchsia.ui.viewsv1token;
+
+enum ExecuteMode {
+ /// Evaluate the script immediately.
+ IMMEDIATE_ONCE = 1;
+ /// Evaluate the script on all subsequent page loads.
+ ON_PAGE_LOAD = 2;
+};
+
+enum LogLevel : int32 {
+ /// No logging.
+ NONE = 100;
+ /// Outputs messages from console.debug().
+ DEBUG = -1;
+ /// Outputs messages from console.log().
+ INFO = 0;
+ /// Outputs messages from console.warn().
+ WARN = 1;
+ /// Outputs messages from console.error().
+ ERROR = 2;
+};
+
+interface Frame {
+ /// Creates a new view using the specified |view_token|. Caller should pass
+ /// the other end of the token to CreateViewHolderCmd() to attach the new view
+ /// to a the view tree.
+ ///
+ /// |view_token|: Token for the new view.
+ CreateView2(handle<eventpair> view_token,
+ request<fuchsia.sys.ServiceProvider>? incoming_services,
+ fuchsia.sys.ServiceProvider? outgoing_services);
+
+ // Deprecated.
+ // TODO(crbug.com/899348): Update all code to use CreateView2()
+ // and remove CreateView().
+ CreateView(request<fuchsia.ui.viewsv1token.ViewOwner> view_owner,
+ request<fuchsia.sys.ServiceProvider>? services);
+
+ /// Returns an interface through which the frame may be navigated to
+ /// a desired URL, reloaded, etc.
+ ///
+ /// |view_provider|: An interface request for the Frame's
+ /// NavigationController.
+ GetNavigationController(request<NavigationController> controller);
+
+ /// Executes |script| in the frame if the frame's URL has an origin which
+ /// matches entries in |origins|.
+ /// At least one |origins| entry must be specified.
+ /// If a wildcard "*" is specified in |origins|, then the script will be
+ /// evaluated for all documents.
+ /// If |mode| is NOW, then the script is evaluated immediately.
+ /// If |mode| is ON_PAGE_LOAD, then the script is evaluated on every future
+ /// document load prior to the page's script's execution.
+ ///
+ /// Multiple scripts can be registered by calling ExecuteJavascript()
+ /// repeatedly.
+ ///
+ /// Note that scripts share the same execution context as the document,
+ /// meaning that document may modify variables, classes, or objects set by the
+ /// script in arbitrary or unpredictable ways.
+ // TODO(crbug.com/900391): Investigate if we can run the scripts in isolated
+ // JS worlds.
+ ///
+ /// Returns |true| if the script was executed, |false| if the script was
+ /// rejected due to injection being blocked by the parent Context, or because
+ /// the script's text encoding was invalid.
+ ExecuteJavaScript(
+ vector<string> origins, fuchsia.mem.Buffer script, ExecuteMode mode) ->
+ (bool success);
+
+ /// Posts a message to the frame's onMessage handler.
+ ///
+ /// |targetOrigin| restricts message delivery to the specified origin.
+ /// If |targetOrigin| is "*", then the message will be sent to the document
+ /// regardless of its origin.
+ /// See html.spec.whatwg.org/multipage/web-messaging.html sect. 9.4.3
+ /// for more details on how the target origin policy is applied.
+ ///
+ /// Returns |false| if |message| is invalid or |targetOrigin| is missing.
+ PostMessage(WebMessage message, string targetOrigin) -> (bool success);
+
+ /// Sets the observer for handling page navigation events.
+ ///
+ /// |observer|: The observer to use. Unregisters any existing observers
+ /// if null.
+ SetNavigationEventObserver(NavigationEventObserver? observer);
+
+ /// If set to a value other than NONE, allows web content to log messages
+ /// to the system logger using console.log(), console.info(), console.warn(),
+ /// and console.error().
+ SetJavaScriptLogLevel(LogLevel level);
+};
+
+struct WebMessage {
+ /// The message payload, encoded as an UTF-8 string.
+ fuchsia.mem.Buffer data;
+
+ /// List of objects transferred into the MessagePort from the FIDL client.
+ // TODO(crbug.com/893236): make this a vector when FIDL-354 is fixed.
+ IncomingTransferable? incoming_transfer;
+
+ /// List of objects transferred out of the MessagePort to the FIDL client.
+ OutgoingTransferable? outgoing_transfer;
+};
+
+union OutgoingTransferable {
+ request<MessagePort> message_port;
+};
+
+union IncomingTransferable {
+ MessagePort message_port;
+};
+
+/// Represents one end of an HTML5 MessageChannel. Can be used to send
+/// and exchange Messages with the peered MessagePort in the Frame's script
+/// context. The port is destroyed when either end of the MessagePort channel
+/// is torn down.
+interface MessagePort {
+ /// Sends a WebMessage to the peer.
+ PostMessage(WebMessage message) -> (bool success);
+
+ /// Asynchronously reads the next message from the channel.
+ /// The client should invoke the callback when it is ready to process
+ /// another message.
+ /// Unreceived messages are buffered on the sender's side and bounded
+ /// by its available resources.
+ ReceiveMessage() -> (WebMessage message);
+};
+
diff --git a/chromium/fuchsia/fidl/web/navigation_controller.fidl b/chromium/fuchsia/fidl/web/navigation_controller.fidl
new file mode 100644
index 00000000000..4103cf31014
--- /dev/null
+++ b/chromium/fuchsia/fidl/web/navigation_controller.fidl
@@ -0,0 +1,71 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.web;
+
+/// Provides methods for controlling and querying the navigation state
+/// of a Frame.
+interface NavigationController {
+ /// Tells the Frame to navigate to a |url|.
+ ///
+ /// |url|: The address to navigate to.
+ /// |params|: Additional parameters that affect how the resource will be
+ /// loaded (e.g. cookies, HTTP headers, etc.)
+ LoadUrl(string url, LoadUrlParams? params);
+
+ GoBack();
+ GoForward();
+ Stop();
+ Reload(ReloadType type);
+
+ /// Returns information for the currently visible content regardless of
+ /// loading state, or a null entry if no content is being displayed.
+ GetVisibleEntry() -> (NavigationEntry? entry);
+};
+
+/// Additional parameters for modifying the behavior of LoadUrl().
+struct LoadUrlParams {
+ /// Provides a hint to the browser UI about how LoadUrl was triggered.
+ LoadUrlReason type;
+
+ /// The URL that linked to the resource being requested.
+ string referrer;
+
+ /// Should be set to true to propagate user activation to the frame. User
+ /// activation implies that the user is interacting with the web frame. It
+ /// enables some web features that are not available otherwise. For example
+ /// autoplay will work only when this flag is set to true.
+ bool user_activated = false;
+
+ /// Custom HTTP headers.
+ vector<vector<uint8>> headers;
+};
+
+/// Characterizes the origin of a LoadUrl request.
+enum LoadUrlReason {
+ /// Navigation was initiated by the user following a link.
+ LINK = 0;
+ /// Navigation was initiated by a user-provided URL.
+ TYPED = 1;
+};
+
+/// Contains information about the Frame's navigation state.
+/// The Frame's navigation history can be represented as an aggregation of
+/// NavigationEntries.
+struct NavigationEntry {
+ /// The page's URL.
+ string url;
+ /// The user-visible page title.
+ string title;
+ /// Indicates if an error occurred during this navigation.
+ bool is_error;
+};
+
+enum ReloadType {
+ /// Reloads the current entry, bypassing the cache for the main resource.
+ PARTIAL_CACHE = 0;
+
+ /// Reloads the current entry, bypassing the cache entirely.
+ NO_CACHE = 1;
+};
diff --git a/chromium/fuchsia/fidl/web/navigation_event_observer.fidl b/chromium/fuchsia/fidl/web/navigation_event_observer.fidl
new file mode 100644
index 00000000000..5bb988b0da8
--- /dev/null
+++ b/chromium/fuchsia/fidl/web/navigation_event_observer.fidl
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library chromium.web;
+
+/// Indicates the properties of the NavigationController's visible content
+/// which have changed since the last OnNavigationStateChanged() event.
+/// Any unchanged properties are left unset.
+struct NavigationEvent {
+ string? url;
+ string? title;
+ bool is_error;
+};
+
+/// Interface supplied by the embedder for receiving notifications about
+/// navigation events in a Frame.
+interface NavigationEventObserver {
+ /// Called when user-visible navigation state has changed since Frame
+ /// creation or the last acknowledgement callback, whichever occurred later.
+ /// |change| will contain all the differences in navigation state since the
+ /// last acknowledgement.
+ OnNavigationStateChanged(NavigationEvent change) -> ();
+};
+
diff --git a/chromium/fuchsia/http/BUILD.gn b/chromium/fuchsia/http/BUILD.gn
new file mode 100644
index 00000000000..b62cdcece57
--- /dev/null
+++ b/chromium/fuchsia/http/BUILD.gn
@@ -0,0 +1,63 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(is_fuchsia)
+
+import("//build/config/fuchsia/fidl_library.gni")
+import("//build/config/fuchsia/rules.gni")
+import("//build/util/process_version.gni")
+import("//testing/test.gni")
+import("//tools/grit/repack.gni")
+
+source_set("common") {
+ sources = [
+ "http_service_impl.cc",
+ "http_service_impl.h",
+ "url_loader_impl.cc",
+ "url_loader_impl.h",
+ ]
+ public_deps = [
+ "//base:base",
+ "//net:net",
+ "//third_party/fuchsia-sdk/sdk:net_oldhttp",
+ "//third_party/fuchsia-sdk/sdk:sys",
+ ]
+ visibility = [ ":*" ]
+}
+
+executable("http_exe") {
+ sources = [
+ "http_service_main.cc",
+ ]
+ deps = [
+ ":common",
+ ]
+ visibility = [ ":*" ]
+}
+
+fuchsia_package("http_pkg") {
+ binary = ":http_exe"
+ package_name_override = "http"
+ sandbox_policy = "sandbox_policy"
+}
+
+fuchsia_package_runner("http_pkg_runner") {
+ package = ":http_pkg"
+ package_name_override = "http"
+}
+
+test("http_service_tests") {
+ sources = [
+ "http_service_unittest.cc",
+ ]
+ deps = [
+ ":common",
+ "//base/test:run_all_unittests",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+ data = [
+ "testdata/",
+ ]
+}
diff --git a/chromium/fuchsia/http/http_service_impl.cc b/chromium/fuchsia/http/http_service_impl.cc
new file mode 100644
index 00000000000..9d3e7dcfafe
--- /dev/null
+++ b/chromium/fuchsia/http/http_service_impl.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/http/http_service_impl.h"
+
+#include "fuchsia/http/url_loader_impl.h"
+#include "net/url_request/url_request_context_builder.h"
+
+HttpServiceImpl::HttpServiceImpl() {
+ // TODO: Set the right options in the URLRequestContextBuilder.
+}
+
+HttpServiceImpl::~HttpServiceImpl() = default;
+
+void HttpServiceImpl::CreateURLLoader(
+ fidl::InterfaceRequest<fuchsia::net::oldhttp::URLLoader> request) {
+ // The URLLoaderImpl objects lifespan is tied to their binding, which is set
+ // in their constructor.
+ net::URLRequestContextBuilder builder;
+ new URLLoaderImpl(builder.Build(), std::move(request));
+}
diff --git a/chromium/fuchsia/http/http_service_impl.h b/chromium/fuchsia/http/http_service_impl.h
new file mode 100644
index 00000000000..db2703d7cb2
--- /dev/null
+++ b/chromium/fuchsia/http/http_service_impl.h
@@ -0,0 +1,28 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_HTTP_HTTP_SERVICE_IMPL_H_
+#define FUCHSIA_HTTP_HTTP_SERVICE_IMPL_H_
+
+#include <fuchsia/net/oldhttp/cpp/fidl.h>
+#include <lib/fidl/cpp/interface_request.h>
+
+#include "net/url_request/url_request_context.h"
+
+// Implements the Fuchsia HttpService API, using the //net library.
+class HttpServiceImpl : public ::fuchsia::net::oldhttp::HttpService {
+ public:
+ HttpServiceImpl();
+ ~HttpServiceImpl() override;
+
+ // HttpService methods:
+ void CreateURLLoader(
+ fidl::InterfaceRequest<::fuchsia::net::oldhttp::URLLoader> request)
+ override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpServiceImpl);
+};
+
+#endif // FUCHSIA_HTTP_HTTP_SERVICE_IMPL_H_
diff --git a/chromium/fuchsia/http/http_service_main.cc b/chromium/fuchsia/http/http_service_main.cc
new file mode 100644
index 00000000000..967572ad48d
--- /dev/null
+++ b/chromium/fuchsia/http/http_service_main.cc
@@ -0,0 +1,40 @@
+// Copyright 2018 The Chromium Authors. 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/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/task/task_scheduler/task_scheduler.h"
+#include "fuchsia/http/http_service_impl.h"
+
+int main(int argc, char** argv) {
+ // Instantiate various global structures.
+ base::TaskScheduler::CreateAndStartWithDefaultParams("HTTP Service");
+ base::CommandLine::Init(argc, argv);
+ base::MessageLoopForIO loop;
+ base::AtExitManager exit_manager;
+
+ // Bind the parent-supplied ServiceDirectory-request to a directory and
+ // publish the HTTP service into it.
+ base::fuchsia::ServiceDirectory* directory =
+ base::fuchsia::ServiceDirectory::GetDefault();
+ HttpServiceImpl http_service;
+ base::fuchsia::ScopedServiceBinding<::fuchsia::net::oldhttp::HttpService>
+ binding(directory, &http_service);
+
+ base::RunLoop run_loop;
+
+ // The main thread loop will be terminated when there are no more clients
+ // connected to this service. The system service manager will restart the
+ // service on demand as needed.
+ binding.SetOnLastClientCallback(
+ base::BindOnce(&base::RunLoop::Quit, base::Unretained(&run_loop)));
+ run_loop.Run();
+
+ return 0;
+}
diff --git a/chromium/fuchsia/http/http_service_unittest.cc b/chromium/fuchsia/http/http_service_unittest.cc
new file mode 100644
index 00000000000..56c24994654
--- /dev/null
+++ b/chromium/fuchsia/http/http_service_unittest.cc
@@ -0,0 +1,442 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/net/oldhttp/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "fuchsia/http/http_service_impl.h"
+#include "fuchsia/http/url_loader_impl.h"
+#include "net/base/net_errors.h"
+#include "net/test/embedded_test_server/default_handlers.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace oldhttp = ::fuchsia::net::oldhttp;
+
+namespace {
+
+const base::FilePath::CharType kTestFilePath[] =
+ FILE_PATH_LITERAL("fuchsia/http/testdata");
+
+// Capacity, in bytes, for buffers used to read data off the URLResponse.
+const size_t kBufferCapacity = 1024;
+
+using ResponseHeaders = std::multimap<std::string, std::string>;
+
+class HttpServiceTest : public ::testing::Test {
+ public:
+ HttpServiceTest()
+ : task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME),
+ binding_(&http_service_server_) {
+ // Initialize the test server.
+ test_server_.AddDefaultHandlers(
+ base::FilePath(FILE_PATH_LITERAL(kTestFilePath)));
+ net::test_server::RegisterDefaultHandlers(&test_server_);
+ }
+
+ protected:
+ base::test::ScopedTaskEnvironment task_environment_;
+
+ void SetUp() override {
+ ASSERT_TRUE(test_server_.Start());
+
+ // Bind the service with the client-side interface.
+ binding_.Bind(http_service_interface_.NewRequest());
+ }
+
+ void TearDown() override {
+ // Disconnect the client and wait for the service to shut down.
+ base::RunLoop run_loop;
+ binding_.set_error_handler(
+ [&run_loop](zx_status_t status) { run_loop.Quit(); });
+ http_service_interface_.Unbind();
+ run_loop.Run();
+ binding_.set_error_handler(nullptr);
+
+ // Check there are no pending requests.
+ EXPECT_EQ(URLLoaderImpl::GetNumActiveRequestsForTests(), 0);
+ }
+
+ // Helper method to start |request| on |url_loader|
+ void ExecuteRequest(const oldhttp::URLLoaderPtr& url_loader,
+ oldhttp::URLRequest request) {
+ base::RunLoop run_loop;
+
+ url_loader->Start(std::move(request),
+ [this, &run_loop](oldhttp::URLResponse response) {
+ run_loop.Quit();
+ url_response_ = std::move(response);
+ });
+ run_loop.Run();
+ }
+
+ net::EmbeddedTestServer* http_test_server() { return &test_server_; }
+ oldhttp::HttpServicePtr& http_service() { return http_service_interface_; }
+ oldhttp::URLResponse& url_response() { return url_response_; }
+
+ private:
+ net::EmbeddedTestServer test_server_;
+
+ HttpServiceImpl http_service_server_;
+ oldhttp::HttpServicePtr http_service_interface_;
+ fidl::Binding<oldhttp::HttpService> binding_;
+ oldhttp::URLResponse url_response_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpServiceTest);
+};
+
+class TestZxHandleWatcher : public base::MessagePumpFuchsia::ZxHandleWatcher {
+ public:
+ explicit TestZxHandleWatcher(base::OnceClosure on_signaled)
+ : on_signaled_(std::move(on_signaled)) {}
+ ~TestZxHandleWatcher() override = default;
+
+ // ZxHandleWatcher implementation.
+ void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override {
+ signals_ = signals;
+ std::move(on_signaled_).Run();
+ }
+
+ zx_signals_t signals() { return signals_; }
+
+ protected:
+ base::OnceClosure on_signaled_;
+ zx_signals_t signals_ = 0;
+};
+
+// Runs MessageLoop until one of the specified |signals| is signaled on the
+// |handle|. Return observed signals.
+zx_signals_t RunLoopUntilSignal(zx_handle_t handle, zx_signals_t signals) {
+ base::RunLoop run_loop;
+ TestZxHandleWatcher watcher(run_loop.QuitClosure());
+ base::MessagePumpForIO::ZxHandleWatchController watch_contoller(FROM_HERE);
+
+ base::MessageLoopCurrentForIO::Get()->WatchZxHandle(
+ handle, /*persistent=*/false, signals, &watch_contoller, &watcher);
+ run_loop.Run();
+
+ return watcher.signals();
+}
+
+void CheckResponseStream(const oldhttp::URLResponse& response,
+ const std::string& expected_response) {
+ EXPECT_TRUE(response.body->is_stream());
+
+ zx::socket stream = std::move(response.body->stream());
+ size_t offset = 0;
+
+ while (true) {
+ std::array<char, kBufferCapacity> buffer;
+ size_t size = 0;
+ zx_status_t result = stream.read(0, buffer.data(), kBufferCapacity, &size);
+
+ if (result == ZX_ERR_SHOULD_WAIT) {
+ zx_signals_t signals = RunLoopUntilSignal(
+ stream.get(), ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED);
+
+ if (signals & ZX_SOCKET_READABLE) {
+ // Attempt to read again now that the socket is readable.
+ continue;
+ } else if (signals & ZX_SOCKET_PEER_CLOSED) {
+ // Done reading.
+ break;
+ } else {
+ NOTREACHED();
+ }
+ } else if (result == ZX_ERR_PEER_CLOSED) {
+ // Done reading.
+ break;
+ }
+ EXPECT_EQ(result, ZX_OK);
+
+ EXPECT_TRUE(std::equal(buffer.begin(), buffer.begin() + size,
+ expected_response.begin() + offset));
+ offset += size;
+ }
+
+ EXPECT_EQ(offset, expected_response.length());
+}
+
+void CheckResponseBuffer(const oldhttp::URLResponse& response,
+ const std::string& expected_response) {
+ EXPECT_TRUE(response.body->is_buffer());
+
+ fuchsia::mem::Buffer mem_buffer = std::move(response.body->buffer());
+ size_t response_size = mem_buffer.size;
+ EXPECT_EQ(mem_buffer.size, expected_response.length());
+
+ std::array<char, kBufferCapacity> buffer;
+ size_t offset = 0;
+ while (offset != mem_buffer.size) {
+ size_t length = std::min(response_size - offset, kBufferCapacity);
+ zx_status_t result = mem_buffer.vmo.read(buffer.data(), offset, length);
+ EXPECT_EQ(result, ZX_OK);
+
+ EXPECT_TRUE(std::equal(buffer.begin(), buffer.begin() + response_size,
+ expected_response.begin() + offset));
+ offset += response_size;
+ }
+
+ EXPECT_EQ(offset, expected_response.length());
+}
+
+void CheckResponseHeaders(const oldhttp::URLResponse& response,
+ ResponseHeaders* expected_headers) {
+ for (auto& header : response.headers.get()) {
+ const std::string header_name = header.name.data();
+ const std::string header_value = header.value.data();
+ auto iter = std::find_if(expected_headers->begin(), expected_headers->end(),
+ [&header_name, &header_value](auto& elt) -> bool {
+ return elt.first.compare(header_name) == 0 &&
+ elt.second.compare(header_value) == 0;
+ });
+ EXPECT_NE(iter, expected_headers->end())
+ << "Unexpected header: \"" << header_name << "\" with value: \""
+ << header_value << "\".";
+ if (iter != expected_headers->end()) {
+ expected_headers->erase(iter);
+ }
+ }
+ EXPECT_TRUE(expected_headers->empty());
+}
+
+} // namespace
+
+// Check a basic end-to-end request resolution with the response being streamed
+// is handled properly.
+TEST_F(HttpServiceTest, BasicRequestStream) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/simple.html").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 200u);
+ CheckResponseStream(url_response(), "hello");
+}
+
+// Check a basic end-to-end request resolution with the response being
+// buffered is handled properly.
+TEST_F(HttpServiceTest, BasicRequestBuffer) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/simple.html").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::BUFFER;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 200u);
+ CheckResponseBuffer(url_response(), "hello");
+}
+
+// Check network request headers are received properly.
+TEST_F(HttpServiceTest, RequestWithHeaders) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/with-headers.html").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 200u);
+ CheckResponseStream(
+ url_response(),
+ "This file is boring; all the action's in the .mock-http-headers.\n");
+ ResponseHeaders expected_headers = {
+ {"Cache-Control", "private"},
+ {"Content-Type", "text/html; charset=ISO-8859-1"},
+ {"X-Multiple-Entries", "a"},
+ {"X-Multiple-Entries", "b"},
+ };
+ CheckResponseHeaders(url_response(), &expected_headers);
+}
+
+// Check duplicate network request headers are received properly.
+TEST_F(HttpServiceTest, RequestWithDuplicateHeaders) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url =
+ http_test_server()->GetURL("/with-duplicate-headers.html").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 200u);
+ CheckResponseStream(
+ url_response(),
+ "This file is boring; all the action's in the .mock-http-headers.\n");
+ ResponseHeaders expected_headers = {
+ {"Cache-Control", "private"},
+ {"Content-Type", "text/html; charset=ISO-8859-1"},
+ {"X-Multiple-Entries", "a"},
+ {"X-Multiple-Entries", "a"},
+ {"X-Multiple-Entries", "b"},
+ };
+ CheckResponseHeaders(url_response(), &expected_headers);
+}
+
+// Check a request with automatic redirect resolution is handled properly.
+TEST_F(HttpServiceTest, AutoRedirect) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/redirect-test.html").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+ request.auto_follow_redirects = true;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 200u);
+ EXPECT_EQ(url_response().url,
+ http_test_server()->GetURL("/with-headers.html").spec());
+}
+
+// Check a request with manual redirect resolution is handled properly.
+TEST_F(HttpServiceTest, ManualRedirect) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+ std::string request_url =
+ http_test_server()->GetURL("/redirect-test.html").spec();
+
+ oldhttp::URLRequest request;
+ request.url = request_url;
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+ request.auto_follow_redirects = false;
+
+ ExecuteRequest(url_loader, std::move(request));
+ std::string final_url =
+ http_test_server()->GetURL("/with-headers.html").spec();
+ EXPECT_EQ(url_response().status_code, 302u);
+ EXPECT_EQ(url_response().url, request_url);
+ EXPECT_EQ(url_response().redirect_url, final_url);
+
+ base::RunLoop run_loop;
+ url_loader->FollowRedirect(
+ [&run_loop, &final_url](oldhttp::URLResponse response) {
+ EXPECT_EQ(response.status_code, 200u);
+ EXPECT_EQ(response.url, final_url);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+}
+
+// Check HTTP error codes are properly populated.
+TEST_F(HttpServiceTest, HttpErrorCode) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()
+ ->base_url()
+ .Resolve("/non_existent_cooper.html")
+ .spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 404u);
+}
+
+// Check network error codes are properly populated.
+TEST_F(HttpServiceTest, InvalidURL) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = "ht\\tp://test.test/";
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().error->code, net::ERR_INVALID_URL);
+}
+
+// Ensure the service can handle multiple concurrent requests.
+TEST_F(HttpServiceTest, MultipleRequests) {
+ oldhttp::URLLoaderPtr url_loaders[100];
+ for (int i = 0; i < 100; i++) {
+ http_service()->CreateURLLoader(url_loaders[i].NewRequest());
+ }
+
+ base::RunLoop run_loop;
+ int requests_done = 0;
+ for (int i = 0; i < 100; i++) {
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/simple.html").spec();
+ request.method = "GET";
+ request.response_body_mode = (i % 2) == 0
+ ? oldhttp::ResponseBodyMode::STREAM
+ : oldhttp::ResponseBodyMode::BUFFER;
+ url_loaders[i]->Start(
+ std::move(request),
+ [&requests_done, &run_loop](oldhttp::URLResponse response) {
+ EXPECT_EQ(response.status_code, 200u);
+ if (response.body->is_buffer()) {
+ CheckResponseBuffer(response, "hello");
+ } else {
+ CheckResponseStream(response, "hello");
+ }
+ requests_done++;
+ if (requests_done == 100) {
+ // Last request signals the run_loop to exit.
+ run_loop.Quit();
+ }
+ });
+ }
+ run_loop.Run();
+}
+
+// Check QueryStatus works as expected when a request is loading.
+// Also checks the request is properly deleted after the binding is destroyed.
+TEST_F(HttpServiceTest, QueryStatus) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/hung-after-headers").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ // In socket mode, we should still get the response headers.
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().status_code, 200u);
+
+ base::RunLoop run_loop;
+ url_loader->QueryStatus([&run_loop](oldhttp::URLLoaderStatus status) {
+ EXPECT_TRUE(status.is_loading);
+ run_loop.Quit();
+ });
+ run_loop.Run();
+}
+
+// Check the response error is properly set if the server disconnects early.
+TEST_F(HttpServiceTest, CloseSocket) {
+ oldhttp::URLLoaderPtr url_loader;
+ http_service()->CreateURLLoader(url_loader.NewRequest());
+
+ oldhttp::URLRequest request;
+ request.url = http_test_server()->GetURL("/close-socket").spec();
+ request.method = "GET";
+ request.response_body_mode = oldhttp::ResponseBodyMode::STREAM;
+
+ ExecuteRequest(url_loader, std::move(request));
+ EXPECT_EQ(url_response().error->code, net::ERR_EMPTY_RESPONSE);
+}
diff --git a/chromium/fuchsia/http/sandbox_policy b/chromium/fuchsia/http/sandbox_policy
new file mode 100644
index 00000000000..15be6c0a911
--- /dev/null
+++ b/chromium/fuchsia/http/sandbox_policy
@@ -0,0 +1,7 @@
+{
+ "features": [ "root-ssl-certificates" ],
+ "services": [
+ "fuchsia.net.LegacySocketProvider",
+ "fuchsia.netstack.Netstack"
+ ]
+}
diff --git a/chromium/fuchsia/http/url_loader_impl.cc b/chromium/fuchsia/http/url_loader_impl.cc
new file mode 100644
index 00000000000..78bee866205
--- /dev/null
+++ b/chromium/fuchsia/http/url_loader_impl.cc
@@ -0,0 +1,429 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/http/url_loader_impl.h"
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/task/post_task.h"
+#include "net/base/chunked_upload_data_stream.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/redirect_info.h"
+
+namespace oldhttp = ::fuchsia::net::oldhttp;
+
+namespace {
+// Capacity, in bytes, for buffers used to move data from client requests or
+// server responses.
+const size_t kReadCapacity = 1024;
+
+// The number of active requests. Used for testing.
+int g_active_requests = 0;
+
+// Converts |buffer| into a URLBody with the body set to a buffer. Returns
+// nullptr when an error occurs.
+oldhttp::URLBodyPtr CreateURLBodyFromBuffer(net::GrowableIOBuffer* buffer) {
+ oldhttp::URLBodyPtr body = oldhttp::URLBody::New();
+
+ // The response buffer size is exactly the offset.
+ size_t total_size = buffer->offset();
+
+ ::fuchsia::mem::Buffer mem_buffer;
+ mem_buffer.size = total_size;
+ zx_status_t result =
+ zx::vmo::create(total_size, ZX_VMO_NON_RESIZABLE, &mem_buffer.vmo);
+ if (result != ZX_OK) {
+ ZX_DLOG(WARNING, result) << "zx_vmo_create";
+ return nullptr;
+ }
+
+ result = mem_buffer.vmo.write(buffer->StartOfBuffer(), 0, total_size);
+ if (result != ZX_OK) {
+ ZX_DLOG(WARNING, result) << "zx_vmo_write";
+ return nullptr;
+ }
+ body->set_buffer(std::move(mem_buffer));
+
+ return body;
+}
+
+int NetErrorToHttpError(int net_error) {
+ // TODO(https://crbug.com/875533): Convert the Chromium //net error to their
+ // Fuchsia counterpart.
+ return net_error;
+}
+
+oldhttp::HttpErrorPtr BuildError(int net_error) {
+ if (net_error == net::OK) {
+ return nullptr;
+ }
+
+ oldhttp::HttpErrorPtr error = oldhttp::HttpError::New();
+ error->code = NetErrorToHttpError(net_error);
+ error->description = net::ErrorToString(net_error);
+ return error;
+}
+
+std::unique_ptr<net::UploadDataStream> UploadDataStreamFromZxSocket(
+ zx::socket stream) {
+ // TODO(http://crbug.com/875534): Write a ZxStreamUploadStream class.
+ std::unique_ptr<net::ChunkedUploadDataStream> upload_stream =
+ std::make_unique<net::ChunkedUploadDataStream>(0);
+ char buffer[kReadCapacity];
+ size_t size = 0;
+ zx_status_t result = ZX_OK;
+ while (true) {
+ result = stream.read(0, buffer, kReadCapacity, &size);
+ if (result != ZX_OK) {
+ ZX_DLOG(WARNING, result) << "zx_socket_read";
+ return nullptr;
+ }
+ if (size < kReadCapacity) {
+ upload_stream->AppendData(buffer, size, false);
+ break;
+ }
+ upload_stream->AppendData(buffer, size, true);
+ }
+
+ return upload_stream;
+}
+
+std::unique_ptr<net::UploadDataStream> UploadDataStreamFromMemBuffer(
+ fuchsia::mem::Buffer mem_buffer) {
+ // TODO(http://crbug.com/875534): Write a ZxMemBufferUploadStream class.
+ std::unique_ptr<net::ChunkedUploadDataStream> upload_stream =
+ std::make_unique<net::ChunkedUploadDataStream>(0);
+
+ char buffer[kReadCapacity];
+ size_t size = mem_buffer.size;
+ size_t offset = 0;
+ zx_status_t result = ZX_OK;
+ while (offset != size) {
+ size_t length = std::min(size - offset, kReadCapacity);
+ result = mem_buffer.vmo.read(buffer, offset, length);
+ if (result != ZX_OK) {
+ ZX_DLOG(WARNING, result) << "zx_vmo_read";
+ return nullptr;
+ }
+ upload_stream->AppendData(buffer, length, false);
+ offset += length;
+ }
+
+ return upload_stream;
+}
+
+} // namespace
+
+URLLoaderImpl::URLLoaderImpl(std::unique_ptr<net::URLRequestContext> context,
+ fidl::InterfaceRequest<oldhttp::URLLoader> request)
+ : binding_(this, std::move(request)),
+ context_(std::move(context)),
+ buffer_(new net::GrowableIOBuffer()),
+ write_watch_(FROM_HERE) {
+ binding_.set_error_handler([this](zx_status_t status) { delete this; });
+ g_active_requests++;
+}
+
+URLLoaderImpl::~URLLoaderImpl() {
+ g_active_requests--;
+}
+
+int URLLoaderImpl::GetNumActiveRequestsForTests() {
+ return g_active_requests;
+}
+
+void URLLoaderImpl::Start(oldhttp::URLRequest request, Callback callback) {
+ if (net_request_) {
+ callback(BuildResponse(net::ERR_IO_PENDING));
+ return;
+ }
+
+ done_callback_ = std::move(callback);
+ net_error_ = net::OK;
+
+ // Create the URLRequest and set this object as the delegate.
+ net_request_ = context_->CreateRequest(GURL(request.url),
+ net::RequestPriority::MEDIUM, this);
+ net_request_->set_method(request.method);
+
+ // Set extra headers.
+ if (request.headers) {
+ for (oldhttp::HttpHeader header : *(request.headers)) {
+ net_request_->SetExtraRequestHeaderByName(header.name, header.value,
+ false);
+ }
+ }
+ if (request.cache_mode == oldhttp::CacheMode::BYPASS_CACHE) {
+ net_request_->SetExtraRequestHeaderByName("Cache-Control", "nocache",
+ false);
+ }
+
+ std::unique_ptr<net::UploadDataStream> upload_stream;
+ // Set body.
+ if (request.body) {
+ if (request.body->is_stream()) {
+ upload_stream =
+ UploadDataStreamFromZxSocket(std::move(request.body->stream()));
+ } else {
+ upload_stream =
+ UploadDataStreamFromMemBuffer(std::move(request.body->buffer()));
+ }
+
+ if (!upload_stream) {
+ std::move(done_callback_)(BuildResponse(net::ERR_ACCESS_DENIED));
+ return;
+ }
+ net_request_->set_upload(std::move(upload_stream));
+ }
+
+ auto_follow_redirects_ = request.auto_follow_redirects;
+ response_body_mode_ = request.response_body_mode;
+
+ // Start the request.
+ net_request_->Start();
+}
+
+void URLLoaderImpl::FollowRedirect(Callback callback) {
+ if (!net_request_ || auto_follow_redirects_ ||
+ !net_request_->is_redirecting()) {
+ callback(BuildResponse(net::ERR_INVALID_HANDLE));
+ }
+
+ done_callback_ = std::move(callback);
+ net_request_->FollowDeferredRedirect(base::nullopt /* removed_headers */,
+ base::nullopt /* modified_headers */);
+}
+
+void URLLoaderImpl::QueryStatus(QueryStatusCallback callback) {
+ oldhttp::URLLoaderStatus status;
+
+ if (!net_request_) {
+ status.is_loading = false;
+ } else if (net_request_->is_pending() || net_request_->is_redirecting()) {
+ status.is_loading = true;
+ } else {
+ status.is_loading = false;
+ status.error = BuildError(net_error_);
+ }
+
+ callback(std::move(status));
+}
+
+void URLLoaderImpl::OnReceivedRedirect(net::URLRequest* request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) {
+ DCHECK_EQ(net_request_.get(), request);
+ // Follow redirect depending on policy.
+ *defer_redirect = !auto_follow_redirects_;
+
+ if (!auto_follow_redirects_) {
+ oldhttp::URLResponse response = BuildResponse(net::OK);
+ response.redirect_method = redirect_info.new_method;
+ response.redirect_url = redirect_info.new_url.spec();
+ response.redirect_referrer = redirect_info.new_referrer;
+ std::move(done_callback_)(std::move(response));
+ }
+}
+
+void URLLoaderImpl::OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) {
+ NOTIMPLEMENTED();
+ DCHECK_EQ(net_request_.get(), request);
+ request->CancelAuth();
+}
+
+void URLLoaderImpl::OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) {
+ NOTIMPLEMENTED();
+ DCHECK_EQ(net_request_.get(), request);
+ request->ContinueWithCertificate(nullptr, nullptr);
+}
+
+void URLLoaderImpl::OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ NOTIMPLEMENTED();
+ DCHECK_EQ(net_request_.get(), request);
+ request->Cancel();
+}
+
+void URLLoaderImpl::OnResponseStarted(net::URLRequest* request, int net_error) {
+ DCHECK_EQ(net_request_.get(), request);
+ net_error_ = net_error;
+
+ // Return early if the request failed.
+ if (net_error_ != net::OK) {
+ std::move(done_callback_)(BuildResponse(net_error_));
+ return;
+ }
+
+ // In stream mode, call the callback now and write to the socket.
+ if (response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
+ response_body_mode_ == oldhttp::ResponseBodyMode::BUFFER_OR_STREAM) {
+ zx::socket read_socket;
+ zx_status_t result = zx::socket::create(0, &read_socket, &write_socket_);
+ if (result != ZX_OK) {
+ ZX_DLOG(WARNING, result) << "zx_socket_create";
+ std::move(done_callback_)(BuildResponse(net::ERR_INSUFFICIENT_RESOURCES));
+ return;
+ }
+ oldhttp::URLResponse response = BuildResponse(net::OK);
+ response.body = oldhttp::URLBody::New();
+ response.body->set_stream(std::move(read_socket));
+ std::move(done_callback_)(std::move(response));
+ }
+
+ // In stream mode, the buffer is used as a temporary buffer to write to the
+ // socket. In buffer mode, it is expanded as more of the response is read.
+ buffer_->SetCapacity(kReadCapacity);
+
+ ReadNextBuffer();
+}
+
+void URLLoaderImpl::OnReadCompleted(net::URLRequest* request, int bytes_read) {
+ DCHECK_EQ(net_request_.get(), request);
+ if (WriteResponseBytes(bytes_read)) {
+ ReadNextBuffer();
+ }
+}
+
+void URLLoaderImpl::OnZxHandleSignalled(zx_handle_t handle,
+ zx_signals_t signals) {
+ // We should never have to process signals we didn't ask for.
+ DCHECK((ZX_CHANNEL_WRITABLE | ZX_CHANNEL_PEER_CLOSED) & signals);
+ DCHECK_GT(buffered_bytes_, 0);
+
+ if (signals & ZX_CHANNEL_PEER_CLOSED) {
+ return;
+ }
+
+ if (WriteResponseBytes(buffered_bytes_))
+ ReadNextBuffer();
+ buffered_bytes_ = 0;
+}
+
+void URLLoaderImpl::ReadNextBuffer() {
+ int net_result;
+ do {
+ net_result = net_request_->Read(buffer_.get(), kReadCapacity);
+ if (net_result == net::ERR_IO_PENDING) {
+ return;
+ }
+ } while (WriteResponseBytes(net_result));
+}
+
+bool URLLoaderImpl::WriteResponseBytes(int result) {
+ if (result < 0) {
+ // Signal read error back to the client.
+ if (write_socket_) {
+ DCHECK(response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
+ response_body_mode_ ==
+ oldhttp::ResponseBodyMode::BUFFER_OR_STREAM);
+ // There is no need to check the return value of this call as there is no
+ // way to recover from a failed socket close.
+ write_socket_ = zx::socket();
+ } else {
+ DCHECK_EQ(response_body_mode_, oldhttp::ResponseBodyMode::BUFFER);
+ std::move(done_callback_)(BuildResponse(result));
+ }
+ return false;
+ }
+
+ if (result == 0) {
+ // Read complete.
+ if (write_socket_) {
+ DCHECK(response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
+ response_body_mode_ ==
+ oldhttp::ResponseBodyMode::BUFFER_OR_STREAM);
+ // In socket mode, attempt to shut down the socket and close it.
+ write_socket_.shutdown(ZX_SOCKET_SHUTDOWN_WRITE);
+ write_socket_ = zx::socket();
+ } else {
+ DCHECK_EQ(response_body_mode_, oldhttp::ResponseBodyMode::BUFFER);
+ // In buffer mode, build the response and call the callback.
+ oldhttp::URLBodyPtr body = CreateURLBodyFromBuffer(buffer_.get());
+ if (body) {
+ oldhttp::URLResponse response = BuildResponse(net::OK);
+ response.body = std::move(body);
+ std::move(done_callback_)(std::move(response));
+ } else {
+ std::move(done_callback_)(
+ BuildResponse(net::ERR_INSUFFICIENT_RESOURCES));
+ }
+ }
+ return false;
+ }
+
+ // Write data to the response buffer or socket.
+ if (write_socket_) {
+ DCHECK(response_body_mode_ == oldhttp::ResponseBodyMode::STREAM ||
+ response_body_mode_ == oldhttp::ResponseBodyMode::BUFFER_OR_STREAM);
+ // In socket mode, attempt to write to the socket.
+ zx_status_t status =
+ write_socket_.write(0, buffer_->data(), result, nullptr);
+ if (status == ZX_ERR_SHOULD_WAIT) {
+ // Wait until the socket is writable again.
+ buffered_bytes_ = result;
+ base::MessageLoopCurrentForIO::Get()->WatchZxHandle(
+ write_socket_.get(), false /* persistent */,
+ ZX_SOCKET_WRITABLE | ZX_SOCKET_PEER_CLOSED, &write_watch_, this);
+ return false;
+ }
+ if (status != ZX_OK) {
+ // Something went wrong, attempt to shut down the socket and close it.
+ ZX_DLOG(WARNING, status) << "zx_socket_write";
+ write_socket_ = zx::socket();
+ return false;
+ }
+ } else {
+ DCHECK_EQ(response_body_mode_, oldhttp::ResponseBodyMode::BUFFER);
+ // In buffer mode, expand the buffer.
+ buffer_->SetCapacity(buffer_->capacity() + result);
+ buffer_->set_offset(buffer_->offset() + result);
+ }
+
+ return true;
+}
+
+oldhttp::URLResponse URLLoaderImpl::BuildResponse(int net_error) {
+ oldhttp::URLResponse response;
+
+ response.error = BuildError(net_error);
+ if (response.error) {
+ return response;
+ }
+
+ if (net_request_->url().is_valid()) {
+ response.url = net_request_->url().spec();
+ }
+ response.status_code = net_request_->GetResponseCode();
+
+ net::HttpResponseHeaders* response_headers = net_request_->response_headers();
+ if (response_headers) {
+ response.status_line = response_headers->GetStatusLine();
+ std::string mime_type;
+ if (response_headers->GetMimeType(&mime_type)) {
+ response.mime_type = mime_type;
+ }
+ std::string charset;
+ if (response_headers->GetCharset(&charset)) {
+ response.charset = charset;
+ }
+
+ size_t iter = 0;
+ std::string header_name;
+ std::string header_value;
+ while (response_headers->EnumerateHeaderLines(&iter, &header_name,
+ &header_value)) {
+ oldhttp::HttpHeader header;
+ header.name = header_name;
+ header.value = header_value;
+ response.headers.push_back(header);
+ }
+ }
+
+ return response;
+}
diff --git a/chromium/fuchsia/http/url_loader_impl.h b/chromium/fuchsia/http/url_loader_impl.h
new file mode 100644
index 00000000000..874a8ff54b4
--- /dev/null
+++ b/chromium/fuchsia/http/url_loader_impl.h
@@ -0,0 +1,121 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_HTTP_URL_LOADER_IMPL_H_
+#define FUCHSIA_HTTP_URL_LOADER_IMPL_H_
+
+#include <fuchsia/net/oldhttp/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/interface_request.h>
+
+#include "base/message_loop/message_pump_for_io.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+// URLLoader implementation. This class manages one network request per
+// instance. Internally, this class uses a |net::URLRequest| object to handle
+// the actual request. The lifespan of |URLLoaderImpl| objects is tied to its
+// |binding_|.
+// TODO(https://crbug.com/875532): Implement cache-mode.
+class URLLoaderImpl : public ::fuchsia::net::oldhttp::URLLoader,
+ public net::URLRequest::Delegate,
+ base::MessagePumpForIO::ZxHandleWatcher {
+ public:
+ using Callback = ::fuchsia::net::oldhttp::URLLoader::StartCallback;
+
+ URLLoaderImpl(
+ std::unique_ptr<net::URLRequestContext> context,
+ ::fidl::InterfaceRequest<::fuchsia::net::oldhttp::URLLoader> request);
+ ~URLLoaderImpl() override;
+
+ // Returns the number of active requests. Used for testing.
+ static int GetNumActiveRequestsForTests();
+
+ private:
+ // URLLoader methods:
+ void Start(::fuchsia::net::oldhttp::URLRequest request,
+ Callback callback) override;
+ void FollowRedirect(Callback callback) override;
+ void QueryStatus(::fuchsia::net::oldhttp::URLLoader::QueryStatusCallback
+ callback) override;
+
+ // URLRequest::Delegate methods:
+ void OnReceivedRedirect(net::URLRequest* request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) override;
+ void OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) override;
+ void OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) override;
+ void OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) override;
+ void OnResponseStarted(net::URLRequest* request, int net_error) override;
+ void OnReadCompleted(net::URLRequest* request, int bytes_read) override;
+
+ // MessagePumpForIO::ZxHandleWatcher method:
+ void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override;
+
+ // Reads from the next response buffer available.
+ void ReadNextBuffer();
+
+ // If |response_body_mode_| is set to socket mode, |result| bytes in
+ // |buffer_| are written to |write_socket_|. In buffer mode, |buffer_| is
+ // expanded by |result| bytes.
+ // |result| is 0 when all data from the response has been written to
+ // |buffer_| and is negative when an error occurred.
+ // Returns true if the caller should call |net_request_.Read()| again and
+ // false otherwise.
+ bool WriteResponseBytes(int result);
+
+ // Helper method to build the response.
+ ::fuchsia::net::oldhttp::URLResponse BuildResponse(int net_error);
+
+ // The corresponding binding, this controls the lifetime of the URLLoaderImpl.
+ ::fidl::Binding<::fuchsia::net::oldhttp::URLLoader> binding_;
+
+ // Holds the net::URLRequestContext associated with the |net_request_|
+ std::unique_ptr<net::URLRequestContext> context_;
+
+ // Holds the net::URLRequest used to perform the network operation.
+ std::unique_ptr<net::URLRequest> net_request_;
+
+ // Callback from a Start or FollowRedirect call.
+ Callback done_callback_;
+
+ // Populated from the FIDL URLRequest. Indicates whether to let the client
+ // manually handle redirects.
+ bool auto_follow_redirects_;
+
+ // Populated from the FIDL URLRequest. Indicates how the response body should
+ // be populated.
+ ::fuchsia::net::oldhttp::ResponseBodyMode response_body_mode_;
+
+ // The resulting net error. Set after the request has completed and
+ // |OnResponseStarted| has been called.
+ int net_error_;
+
+ // Used to populate the response body.
+ scoped_refptr<net::GrowableIOBuffer> buffer_;
+
+ // If |response_body_mode_| is set to socket mode, the server side of the
+ // socket. The other end is to be read by the client.
+ zx::socket write_socket_;
+
+ // If |response_body_mode_| is set to socket mode, the watch controller used
+ // to wait for |write_socket_| to be writable. Unused otherwise.
+ base::MessagePumpForIO::ZxHandleWatchController write_watch_;
+
+ // If |response_body_mode_| is set to socket mode and |write_socket_| is not
+ // writable, |buffered_bytes_| is used to store the number of bytes waiting
+ // in |buffer_|.
+ int buffered_bytes_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(URLLoaderImpl);
+};
+
+#endif // FUCHSIA_HTTP_URL_LOADER_IMPL_H_ \ No newline at end of file
diff --git a/chromium/fuchsia/renderer/DEPS b/chromium/fuchsia/renderer/DEPS
new file mode 100644
index 00000000000..66c74a9d9fc
--- /dev/null
+++ b/chromium/fuchsia/renderer/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+content/public/renderer",
+ "+mojo/public/cpp/bindings",
+ "+services/service_manager/public/cpp",
+ "+third_party/blink/public/common/associated_interfaces",
+]
diff --git a/chromium/fuchsia/renderer/on_load_script_injector.cc b/chromium/fuchsia/renderer/on_load_script_injector.cc
new file mode 100644
index 00000000000..904fdae854b
--- /dev/null
+++ b/chromium/fuchsia/renderer/on_load_script_injector.cc
@@ -0,0 +1,66 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/renderer/on_load_script_injector.h"
+
+#include <lib/zx/vmo.h>
+#include <utility>
+#include <vector>
+
+#include "base/memory/shared_memory_handle.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+
+namespace webrunner {
+
+OnLoadScriptInjector::OnLoadScriptInjector(content::RenderFrame* frame)
+ : RenderFrameObserver(frame), weak_ptr_factory_(this) {
+ render_frame()->GetAssociatedInterfaceRegistry()->AddInterface(
+ base::BindRepeating(&OnLoadScriptInjector::BindToRequest,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+OnLoadScriptInjector::~OnLoadScriptInjector() {}
+
+void OnLoadScriptInjector::BindToRequest(
+ mojom::OnLoadScriptInjectorAssociatedRequest request) {
+ bindings_.AddBinding(this, std::move(request));
+}
+
+void OnLoadScriptInjector::DidCommitProvisionalLoad(
+ bool is_same_document_navigation,
+ ui::PageTransition transition) {
+ // Ignore pushState or document fragment navigation.
+ if (is_same_document_navigation)
+ return;
+
+ // Don't inject anything for subframes.
+ if (!render_frame()->IsMainFrame())
+ return;
+
+ for (mojo::ScopedSharedBufferHandle& script : on_load_scripts_) {
+ DCHECK_EQ(script->GetSize() % 2, 0u); // Crude check to see this is UTF-16.
+
+ auto mapping = script->Map(script->GetSize());
+ base::string16 script_converted(static_cast<base::char16*>(mapping.get()),
+ script->GetSize() / sizeof(base::char16));
+ render_frame()->ExecuteJavaScript(script_converted);
+ }
+}
+
+void OnLoadScriptInjector::AddOnLoadScript(
+ mojo::ScopedSharedBufferHandle script) {
+ on_load_scripts_.push_back(std::move(script));
+}
+
+void OnLoadScriptInjector::ClearOnLoadScripts() {
+ on_load_scripts_.clear();
+}
+
+void OnLoadScriptInjector::OnDestruct() {
+ delete this;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/renderer/on_load_script_injector.h b/chromium/fuchsia/renderer/on_load_script_injector.h
new file mode 100644
index 00000000000..f814a2b00b6
--- /dev/null
+++ b/chromium/fuchsia/renderer/on_load_script_injector.h
@@ -0,0 +1,50 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
+#define FUCHSIA_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
+
+#include <lib/zx/vmo.h>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/shared_memory_handle.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "fuchsia/common/on_load_script_injector.mojom.h"
+#include "mojo/public/cpp/bindings/associated_binding_set.h"
+
+namespace webrunner {
+
+// Injects one or more scripts into a RenderFrame at the earliest possible time
+// during the page load process.
+class OnLoadScriptInjector : public content::RenderFrameObserver,
+ public mojom::OnLoadScriptInjector {
+ public:
+ explicit OnLoadScriptInjector(content::RenderFrame* frame);
+
+ void BindToRequest(mojom::OnLoadScriptInjectorAssociatedRequest request);
+
+ void AddOnLoadScript(mojo::ScopedSharedBufferHandle script) override;
+ void ClearOnLoadScripts() override;
+
+ // RenderFrameObserver override:
+ void OnDestruct() override;
+ void DidCommitProvisionalLoad(bool is_same_document_navigation,
+ ui::PageTransition transition) override;
+
+ private:
+ // Called by OnDestruct(), when the RenderFrame is destroyed.
+ ~OnLoadScriptInjector() override;
+
+ std::vector<mojo::ScopedSharedBufferHandle> on_load_scripts_;
+ mojo::AssociatedBindingSet<mojom::OnLoadScriptInjector> bindings_;
+ base::WeakPtrFactory<OnLoadScriptInjector> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(OnLoadScriptInjector);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
diff --git a/chromium/fuchsia/renderer/webrunner_content_renderer_client.cc b/chromium/fuchsia/renderer/webrunner_content_renderer_client.cc
new file mode 100644
index 00000000000..cd05d1397cc
--- /dev/null
+++ b/chromium/fuchsia/renderer/webrunner_content_renderer_client.cc
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/renderer/webrunner_content_renderer_client.h"
+
+#include "base/macros.h"
+#include "content/public/renderer/render_frame.h"
+#include "fuchsia/renderer/on_load_script_injector.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+
+namespace webrunner {
+
+WebRunnerContentRendererClient::WebRunnerContentRendererClient() = default;
+
+WebRunnerContentRendererClient::~WebRunnerContentRendererClient() = default;
+
+void WebRunnerContentRendererClient::RenderFrameCreated(
+ content::RenderFrame* render_frame) {
+ // Add WebRunner services to the new RenderFrame.
+ // The objects' lifetimes are bound to the RenderFrame's lifetime.
+ new OnLoadScriptInjector(render_frame);
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/renderer/webrunner_content_renderer_client.h b/chromium/fuchsia/renderer/webrunner_content_renderer_client.h
new file mode 100644
index 00000000000..fdadb5e597c
--- /dev/null
+++ b/chromium/fuchsia/renderer/webrunner_content_renderer_client.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
+#define FUCHSIA_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
+
+#include "base/macros.h"
+#include "content/public/renderer/content_renderer_client.h"
+
+namespace webrunner {
+
+class WebRunnerContentRendererClient : public content::ContentRendererClient {
+ public:
+ WebRunnerContentRendererClient();
+ ~WebRunnerContentRendererClient() override;
+
+ // content::ContentRendererClient overrides.
+ void RenderFrameCreated(content::RenderFrame* render_frame) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerContentRendererClient);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
diff --git a/chromium/fuchsia/runners/BUILD.gn b/chromium/fuchsia/runners/BUILD.gn
new file mode 100644
index 00000000000..e04a406c850
--- /dev/null
+++ b/chromium/fuchsia/runners/BUILD.gn
@@ -0,0 +1,205 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(is_fuchsia)
+
+import("//build/config/fuchsia/rules.gni")
+import("//testing/test.gni")
+
+# Files common to both cast_runner and web_runner targets.
+source_set("common") {
+ sources = [
+ "common/web_component.cc",
+ "common/web_component.h",
+ "common/web_content_runner.cc",
+ "common/web_content_runner.h",
+ ]
+ deps = [
+ "//base",
+ "//third_party/fuchsia-sdk/sdk:ui_app",
+ "//third_party/fuchsia-sdk/sdk:ui_viewsv1",
+ "//url",
+ ]
+ public_deps = [
+ "//fuchsia:web_fidl",
+ "//third_party/fuchsia-sdk/sdk:sys",
+ ]
+ visibility = [ ":*" ]
+}
+
+source_set("cast_runner_core") {
+ sources = [
+ "cast/bindings/cast_channel.cc",
+ "cast/bindings/cast_channel.h",
+ "cast/cast_runner.cc",
+ "cast/cast_runner.h",
+ ]
+ data = [
+ "cast/bindings/cast_channel.js",
+ ]
+ deps = [
+ "//base",
+ "//fuchsia:mem_buffer_common",
+ "//fuchsia:named_message_port_connector",
+ "//url",
+ ]
+ public_deps = [
+ ":common",
+ "//fuchsia:cast_fidl",
+ ]
+ configs += [ "//fuchsia:webrunner_implementation" ]
+ visibility = [ ":*" ]
+}
+
+executable("cast_runner_exe") {
+ sources = [
+ "cast/main.cc",
+ ]
+ deps = [
+ ":cast_runner_core",
+ ":common",
+ "//base",
+ ]
+ visibility = [ ":*" ]
+}
+
+fuchsia_package("cast_runner_pkg") {
+ binary = ":cast_runner_exe"
+ package_name_override = "cast_runner"
+ sandbox_policy = "cast/sandbox_policy"
+}
+
+fuchsia_package_runner("cast_runner") {
+ package = ":cast_runner_pkg"
+ package_name_override = "cast_runner"
+ install_only = true
+ package_deps = [ [
+ "//fuchsia:service_pkg",
+ "chromium",
+ ] ]
+}
+
+source_set("cast_runner_test_core") {
+ testonly = true
+ sources = [
+ "cast/fake_application_config_manager.cc",
+ "cast/fake_application_config_manager.h",
+ "cast/test_common.cc",
+ "cast/test_common.h",
+ ]
+ public_deps = [
+ "//base",
+ "//fuchsia:cast_fidl",
+ "//net:test_support",
+ "//third_party/fuchsia-sdk/sdk:sys",
+ ]
+ visibility = [ ":*" ]
+}
+
+test("cast_runner_unittests") {
+ sources = [
+ "cast/cast_runner_unittest.cc",
+ ]
+ deps = [
+ ":cast_runner_core",
+ ":cast_runner_test_core",
+ "//base/test:run_all_unittests",
+ "//base/test:test_support",
+ "//fuchsia:test_support",
+ "//testing/gtest",
+ ]
+}
+
+test("cast_runner_integration_tests") {
+ sources = [
+ "cast/cast_runner_integration_test.cc",
+ ]
+ deps = [
+ ":cast_runner_core",
+ ":cast_runner_test_core",
+ "//base/test:run_all_unittests",
+ "//base/test:test_support",
+ "//fuchsia:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+ package_deps = [ [
+ "//fuchsia:service_pkg",
+ "chromium",
+ ] ]
+}
+
+test("cast_runner_browsertests") {
+ sources = [
+ "cast/bindings/cast_channel_browsertest.cc",
+ ]
+ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+ data = [
+ "cast/testdata",
+ ]
+ deps = [
+ ":cast_runner_core",
+ "//base/test:test_support",
+ "//content/public/browser",
+ "//fuchsia:browsertest_common",
+ "//fuchsia:mem_buffer_common",
+ "//fuchsia:named_message_port_connector",
+ "//fuchsia:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//ui/ozone",
+ ]
+}
+
+executable("web_runner_exe") {
+ sources = [
+ "web/main.cc",
+ ]
+ deps = [
+ ":common",
+ "//base",
+ ]
+ visibility = [ ":*" ]
+}
+
+fuchsia_package("web_runner_pkg") {
+ binary = ":web_runner_exe"
+ package_name_override = "web_runner"
+ sandbox_policy = "web/sandbox_policy"
+}
+
+fuchsia_package_runner("web_runner") {
+ package = ":web_runner_pkg"
+ package_name_override = "web_runner"
+ install_only = true
+ package_deps = [ [
+ "//fuchsia:service_pkg",
+ "chromium",
+ ] ]
+}
+
+test("web_runner_integration_tests") {
+ sources = [
+ "web/web_runner_smoke_test.cc",
+ ]
+ deps = [
+ "//base",
+ "//base/test:run_all_unittests",
+ "//base/test:test_support",
+ "//fuchsia:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ "//third_party/fuchsia-sdk/sdk:sys",
+ ]
+ package_deps = [
+ [
+ "//fuchsia:service_pkg",
+ "chromium",
+ ],
+ [
+ ":web_runner_pkg",
+ "web_runner",
+ ],
+ ]
+}
diff --git a/chromium/fuchsia/runners/cast/bindings/cast_channel.cc b/chromium/fuchsia/runners/cast/bindings/cast_channel.cc
new file mode 100644
index 00000000000..5066bd97867
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/bindings/cast_channel.cc
@@ -0,0 +1,117 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/cast/bindings/cast_channel.h"
+
+#include <lib/fit/function.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+
+// Unique identifier of the Cast Channel message port, used by the JavaScript
+// API to connect to the port.
+const char kMessagePortName[] = "cast.__platform__.channel";
+
+CastChannelImpl::CastChannelImpl(
+ chromium::web::Frame* frame,
+ webrunner::NamedMessagePortConnector* connector)
+ : frame_(frame), connector_(connector) {
+ DCHECK(connector_);
+ DCHECK(frame_);
+
+ connector->Register(
+ kMessagePortName,
+ base::BindRepeating(&CastChannelImpl::OnMasterPortReceived,
+ base::Unretained(this)),
+ frame_);
+
+ // Load the cast.__platform__.channel bundle from the package assets, into a
+ // mem::Buffer.
+ base::FilePath assets_path;
+ CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
+ fuchsia::mem::Buffer bindings_buf = webrunner::MemBufferFromFile(base::File(
+ assets_path.AppendASCII("fuchsia/runners/cast/bindings/cast_channel.js"),
+ base::File::FLAG_OPEN | base::File::FLAG_READ));
+ CHECK(bindings_buf.vmo);
+
+ // Inject the cast.__platform__.channel API to all origins.
+ std::vector<std::string> origins = {"*"};
+
+ // Configure the bundle to be re-injected every time the |frame_| content is
+ // loaded.
+ frame_->ExecuteJavaScript(
+ std::move(origins), std::move(bindings_buf),
+ chromium::web::ExecuteMode::ON_PAGE_LOAD,
+ [](bool success) { CHECK(success) << "Couldn't insert bindings."; });
+}
+
+CastChannelImpl::~CastChannelImpl() {
+ connector_->Unregister(frame_, kMessagePortName);
+}
+
+void CastChannelImpl::OnMasterPortError() {
+ master_port_.Unbind();
+}
+
+void CastChannelImpl::Connect(ConnectCallback callback) {
+ // If there is already a bound Cast Channel ready, then return it.
+ if (pending_channel_) {
+ callback(std::move(pending_channel_));
+ return;
+ }
+
+ pending_connect_cb_ = std::move(callback);
+
+ if (master_port_) {
+ master_port_->ReceiveMessage(
+ fit::bind_member(this, &CastChannelImpl::OnCastChannelMessageReceived));
+ }
+
+ // If there is no master port available at this time, then defer invocation of
+ // |pending_connect_cb_| until the master port has been received.
+}
+
+void CastChannelImpl::OnMasterPortReceived(chromium::web::MessagePortPtr port) {
+ DCHECK(port);
+
+ master_port_ = std::move(port);
+ master_port_.set_error_handler([this](zx_status_t status) {
+ ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
+ << "Cast Channel master port disconnected.";
+ OnMasterPortError();
+ });
+
+ if (pending_connect_cb_) {
+ // Resolve the in-flight Connect call.
+ Connect(std::move(pending_connect_cb_));
+ }
+}
+
+void CastChannelImpl::OnCastChannelMessageReceived(
+ chromium::web::WebMessage message) {
+ if (!message.incoming_transfer ||
+ !message.incoming_transfer->is_message_port()) {
+ LOG(WARNING) << "Received a CastChannel without a message port.";
+ OnMasterPortError();
+ return;
+ }
+
+ // Fulfill an outstanding Connect() operation, if there is one.
+ if (pending_connect_cb_) {
+ pending_connect_cb_(std::move(message.incoming_transfer->message_port()));
+ pending_connect_cb_ = {};
+ return;
+ }
+
+ pending_channel_ = std::move(message.incoming_transfer->message_port());
+}
diff --git a/chromium/fuchsia/runners/cast/bindings/cast_channel.h b/chromium/fuchsia/runners/cast/bindings/cast_channel.h
new file mode 100644
index 00000000000..e8d16014a97
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/bindings/cast_channel.h
@@ -0,0 +1,66 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RUNNERS_CAST_BINDINGS_CAST_CHANNEL_H_
+#define FUCHSIA_RUNNERS_CAST_BINDINGS_CAST_CHANNEL_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+
+namespace webrunner {
+class NamedMessagePortConnector;
+}
+
+// Handles the injection of cast.__platform__.channel bindings into pages'
+// scripting context, and establishes a bidirectional message pipe over
+// which the two communicate.
+class FUCHSIA_EXPORT CastChannelImpl : public chromium::cast::CastChannel {
+ public:
+ // Attaches CastChannel bindings and port to a |frame|.
+ // |frame|: The frame to be provided with a CastChannel.
+ // |connector|: The NamedMessagePortConnector to use for establishing
+ // transport.
+ // Both |frame| and |connector| must outlive |this|.
+ CastChannelImpl(chromium::web::Frame* frame,
+ webrunner::NamedMessagePortConnector* connector);
+ ~CastChannelImpl() override;
+
+ // chromium::cast::CastChannel implementation.
+ void Connect(ConnectCallback callback) override;
+
+ private:
+ // Receives a port used for receiving new Cast Channel ports.
+ void OnMasterPortReceived(chromium::web::MessagePortPtr port);
+
+ // Receives a message containing a newly opened Cast Channel from
+ // |master_port_|.
+ void OnCastChannelMessageReceived(chromium::web::WebMessage message);
+
+ // Handles error conditions on |master_port_|.
+ void OnMasterPortError();
+
+ chromium::web::Frame* const frame_;
+ webrunner::NamedMessagePortConnector* const connector_;
+
+ // A long-lived port, used to receive new Cast Channel ports when they are
+ // opened. Should be automatically populated by the
+ // NamedMessagePortConnector whenever |frame| loads a new page.
+ chromium::web::MessagePortPtr master_port_;
+
+ fuchsia::mem::Buffer bindings_script_;
+ ConnectCallback pending_connect_cb_;
+
+ // A Cast Channel received from the webpage, waiting to be handled via
+ // ListenForChannel().
+ fidl::InterfaceHandle<chromium::web::MessagePort> pending_channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelImpl);
+};
+
+#endif // FUCHSIA_RUNNERS_CAST_BINDINGS_CAST_CHANNEL_H_
diff --git a/chromium/fuchsia/runners/cast/bindings/cast_channel.js b/chromium/fuchsia/runners/cast/bindings/cast_channel.js
new file mode 100644
index 00000000000..07ea7451903
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/bindings/cast_channel.js
@@ -0,0 +1,74 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Implementation of the cast.__platform__.channel API which uses MessagePort
+// IPC to communicate with an actual Cast Channel implementation provided by
+// the content embedder. There is at most one channel which may be opened (able
+// to send & receive messages) or closed.
+cast.__platform__.channel = new class {
+ constructor() {
+ this.master_port_ = cast.__platform__.connector.bind(
+ 'cast.__platform__.channel',
+ function(ignored) {
+ // |master_port_| is send-only, so ignore all incoming messages.
+ });
+ }
+
+ // Signals to the peer that the Cast Channel is opened.
+ // |openHandler|: A callback function which is invoked on channel open with
+ // a boolean indicating success.
+ // |messageHandler|: Invoked when a message arrives from the peer.
+ open(openHandler, messageHandler) {
+ if (this.current_port_) {
+ console.error('open() called on an open Cast Channel.');
+ openHandler(false);
+ return;
+ }
+
+ if (!messageHandler) {
+ console.error('Null messageHandler passed to open().');
+ openHandler(false);
+ return;
+ }
+
+ // Create the MessageChannel for Cast Channel and distribute its ports.
+ var channel = new MessageChannel();
+ this.master_port_.sendMessage('', [channel.port1]);
+
+ this.current_port_ = channel.port2;
+ this.current_port_.onmessage = function(message) {
+ messageHandler(message.data);
+ };
+ this.current_port_.onerror = function() {
+ console.error('Cast Channel was closed unexpectedly by peer.');
+ return;
+ };
+
+ openHandler(true);
+ }
+
+ // Closes the Cast Channel.
+ close(closeHandler) {
+ if (this.current_port_) {
+ this.current_port_.close();
+ this.current_port_ = null;
+ }
+ }
+
+ // Sends a message to the Cast Channel's peer.
+ send(message) {
+ if (!this.current_port_) {
+ console.error('send() called on a closed Cast Channel.');
+ return;
+ }
+
+ this.current_port_.postMessage(message);
+ }
+
+ // Used to send newly opened Cast Channel ports to C++.
+ master_port_ = null;
+
+ // The current opened Cast Channel.
+ current_port_ = null;
+};
diff --git a/chromium/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc b/chromium/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc
new file mode 100644
index 00000000000..65779fe08b0
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc
@@ -0,0 +1,218 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/runners/cast/bindings/cast_channel.h"
+#include "fuchsia/test/promise.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_constants.h"
+
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
+
+class CastChannelImplTest : public webrunner::WebRunnerBrowserTest,
+ public chromium::web::NavigationEventObserver {
+ public:
+ CastChannelImplTest() : run_timeout_(TestTimeouts::action_timeout()) {
+ set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
+ }
+
+ ~CastChannelImplTest() override = default;
+
+ protected:
+ void SetUpOnMainThread() override {
+ webrunner::WebRunnerBrowserTest::SetUpOnMainThread();
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ frame_ = WebRunnerBrowserTest::CreateFrame(this);
+ connector_ = std::make_unique<webrunner::NamedMessagePortConnector>();
+ }
+
+ void OnNavigationStateChanged(
+ chromium::web::NavigationEvent change,
+ OnNavigationStateChangedCallback callback) override {
+ connector_->NotifyPageLoad(frame_.get());
+ if (navigate_run_loop_)
+ navigate_run_loop_->Quit();
+ callback();
+ }
+
+ void CheckLoadUrl(const std::string& url,
+ chromium::web::NavigationController* controller) {
+ navigate_run_loop_ = std::make_unique<base::RunLoop>();
+ controller->LoadUrl(url, nullptr);
+ navigate_run_loop_->Run();
+ navigate_run_loop_.reset();
+ }
+
+ std::unique_ptr<base::RunLoop> navigate_run_loop_;
+ chromium::web::FramePtr frame_;
+ std::unique_ptr<webrunner::NamedMessagePortConnector> connector_;
+
+ private:
+ const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastChannelImplTest);
+};
+
+IN_PROC_BROWSER_TEST_F(CastChannelImplTest, CastChannelBuffered) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL test_url(embedded_test_server()->GetURL("/cast_channel.html"));
+
+ frame_->SetJavaScriptLogLevel(chromium::web::LogLevel::INFO);
+ chromium::web::NavigationControllerPtr controller;
+ frame_->GetNavigationController(controller.NewRequest());
+
+ testing::InSequence seq;
+ CastChannelImpl cast_channel_instance(frame_.get(), connector_.get());
+ fidl::Binding<chromium::cast::CastChannel> cast_channel_binding(
+ &cast_channel_instance);
+ chromium::cast::CastChannelPtr cast_channel =
+ cast_channel_binding.NewBinding().Bind();
+
+ // Verify that CastChannelImpl can properly handle message, connect,
+ // disconnect, and MessagePort disconnection events.
+ CheckLoadUrl(test_url.spec(), controller.get());
+
+ chromium::web::MessagePortPtr channel;
+
+ // Expect channel open.
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<fidl::InterfaceHandle<chromium::web::MessagePort>>
+ channel_promise(run_loop.QuitClosure());
+ cast_channel->Connect(
+ webrunner::ConvertToFitFunction(channel_promise.GetReceiveCallback()));
+ run_loop.Run();
+
+ channel = channel_promise->Bind();
+ }
+
+ // Send a message to the channel.
+ auto expected_list = {"this", "is", "a", "test"};
+ for (const std::string& expected : expected_list) {
+ base::RunLoop run_loop;
+ webrunner::Promise<chromium::web::WebMessage> message(
+ run_loop.QuitClosure());
+ channel->ReceiveMessage(
+ webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
+ run_loop.Run();
+
+ EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data), expected);
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(CastChannelImplTest, CastChannelReconnect) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL test_url(embedded_test_server()->GetURL("/cast_channel_reconnect.html"));
+ GURL empty_url(embedded_test_server()->GetURL("/defaultresponse"));
+
+ frame_->SetJavaScriptLogLevel(chromium::web::LogLevel::INFO);
+ chromium::web::NavigationControllerPtr controller;
+ frame_->GetNavigationController(controller.NewRequest());
+
+ testing::InSequence seq;
+ CastChannelImpl cast_channel_instance(frame_.get(), connector_.get());
+ fidl::Binding<chromium::cast::CastChannel> cast_channel_binding(
+ &cast_channel_instance);
+ chromium::cast::CastChannelPtr cast_channel =
+ cast_channel_binding.NewBinding().Bind();
+
+ // Verify that CastChannelImpl can properly handle message, connect,
+ // disconnect, and MessagePort disconnection events.
+ // Also verify that the cast channel is used across inter-page navigations.
+ for (int i = 0; i < 5; ++i) {
+ CheckLoadUrl(test_url.spec(), controller.get());
+
+ chromium::web::MessagePortPtr channel;
+
+ // Expect channel open.
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<fidl::InterfaceHandle<chromium::web::MessagePort>>
+ channel_promise(run_loop.QuitClosure());
+ cast_channel->Connect(webrunner::ConvertToFitFunction(
+ channel_promise.GetReceiveCallback()));
+ run_loop.Run();
+
+ channel = channel_promise->Bind();
+ }
+
+ // Expect channel close.
+ {
+ base::RunLoop run_loop;
+ channel.set_error_handler([&run_loop](zx_status_t) { run_loop.Quit(); });
+ run_loop.Run();
+ }
+
+ // Expect channel re-open.
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<fidl::InterfaceHandle<chromium::web::MessagePort>>
+ channel_promise(run_loop.QuitClosure());
+ cast_channel->Connect(webrunner::ConvertToFitFunction(
+ channel_promise.GetReceiveCallback()));
+ run_loop.Run();
+
+ channel = channel_promise->Bind();
+ }
+
+ // Read "reconnected" from the channel.
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<chromium::web::WebMessage> message(
+ run_loop.QuitClosure());
+ channel->ReceiveMessage(
+ webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
+ run_loop.Run();
+
+ EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data),
+ "reconnected");
+ }
+
+ // Send a message to the channel.
+ {
+ chromium::web::WebMessage message;
+ message.data = webrunner::MemBufferFromString("hello");
+
+ base::RunLoop run_loop;
+ webrunner::Promise<bool> post_result(run_loop.QuitClosure());
+ channel->PostMessage(
+ std::move(message),
+ webrunner::ConvertToFitFunction(post_result.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ(true, *post_result);
+ }
+
+ // Get a message from the channel.
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<chromium::web::WebMessage> message(
+ run_loop.QuitClosure());
+ channel->ReceiveMessage(
+ webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
+ run_loop.Run();
+
+ EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data),
+ "ack hello");
+ }
+
+ // Navigate away.
+ CheckLoadUrl(empty_url.spec(), controller.get());
+ }
+}
diff --git a/chromium/fuchsia/runners/cast/cast_runner.cc b/chromium/fuchsia/runners/cast/cast_runner.cc
new file mode 100644
index 00000000000..10e4b8009db
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/cast_runner.cc
@@ -0,0 +1,75 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/cast/cast_runner.h"
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <utility>
+
+#include "base/logging.h"
+#include "fuchsia/runners/common/web_component.h"
+#include "url/gurl.h"
+
+CastRunner::CastRunner(
+ base::fuchsia::ServiceDirectory* service_directory,
+ chromium::web::ContextPtr context,
+ chromium::cast::ApplicationConfigManagerPtr app_config_manager,
+ base::OnceClosure on_idle_closure)
+ : WebContentRunner(service_directory,
+ std::move(context),
+ std::move(on_idle_closure)),
+ app_config_manager_(std::move(app_config_manager)) {}
+
+CastRunner::~CastRunner() = default;
+
+void CastRunner::StartComponent(
+ fuchsia::sys::Package package,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request) {
+ // Verify that |package| specifies a Cast URI, and pull the app-Id from it.
+ constexpr char kCastPresentationUrlScheme[] = "cast";
+ constexpr char kCastSecurePresentationUrlScheme[] = "casts";
+
+ GURL cast_url(package.resolved_url);
+ if (!cast_url.is_valid() ||
+ (!cast_url.SchemeIs(kCastPresentationUrlScheme) &&
+ !cast_url.SchemeIs(kCastSecurePresentationUrlScheme)) ||
+ cast_url.GetContent().empty()) {
+ LOG(ERROR) << "Rejected invalid URL: " << cast_url;
+ return;
+ }
+
+ // Fetch the Cast application configuration for the specified Id.
+ const std::string cast_app_id(cast_url.GetContent());
+ app_config_manager_->GetConfig(
+ cast_app_id,
+ [this, startup_info = std::move(startup_info),
+ controller_request = std::move(controller_request)](
+ chromium::cast::ApplicationConfigPtr app_config) mutable {
+ GetConfigCallback(std::move(startup_info),
+ std::move(controller_request), std::move(app_config));
+ });
+}
+
+void CastRunner::GetConfigCallback(
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request,
+ chromium::cast::ApplicationConfigPtr app_config) {
+ if (!app_config) {
+ DLOG(WARNING) << "No ApplicationConfig was found.";
+
+ // For test purposes, we need to call RegisterComponent even if there is no
+ // URL to launch.
+ RegisterComponent(std::unique_ptr<WebComponent>(nullptr));
+ return;
+ }
+
+ // If a config was returned then use it to launch a component.
+ GURL cast_app_url(app_config->web_url);
+ RegisterComponent(WebComponent::ForUrlRequest(this, std::move(cast_app_url),
+ std::move(startup_info),
+ std::move(controller_request)));
+}
diff --git a/chromium/fuchsia/runners/cast/cast_runner.h b/chromium/fuchsia/runners/cast/cast_runner.h
new file mode 100644
index 00000000000..da1b86a0af0
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/cast_runner.h
@@ -0,0 +1,42 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
+#define FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/runners/common/web_content_runner.h"
+
+// sys::Runner which instantiates Cast activities specified via cast/casts URIs.
+class CastRunner : public WebContentRunner {
+ public:
+ CastRunner(base::fuchsia::ServiceDirectory* service_directory,
+ chromium::web::ContextPtr context,
+ chromium::cast::ApplicationConfigManagerPtr app_config_manager,
+ base::OnceClosure on_idle_closure);
+
+ ~CastRunner() override;
+
+ // fuchsia::sys::Runner implementation.
+ void StartComponent(fuchsia::sys::Package package,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request) override;
+
+ private:
+ chromium::cast::ApplicationConfigManagerPtr app_config_manager_;
+
+ void GetConfigCallback(
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request,
+ chromium::cast::ApplicationConfigPtr app_config);
+
+ DISALLOW_COPY_AND_ASSIGN(CastRunner);
+};
+
+#endif // FUCHSIA_RUNNERS_CAST_CAST_RUNNER_H_
diff --git a/chromium/fuchsia/runners/cast/cast_runner_integration_test.cc b/chromium/fuchsia/runners/cast/cast_runner_integration_test.cc
new file mode 100644
index 00000000000..3f8b5779c86
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/cast_runner_integration_test.cc
@@ -0,0 +1,153 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/fidl/cpp/binding.h>
+#include <lib/zx/channel.h>
+
+#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_timeouts.h"
+#include "fuchsia/runners/cast/cast_runner.h"
+#include "fuchsia/runners/cast/fake_application_config_manager.h"
+#include "fuchsia/runners/cast/test_common.h"
+#include "fuchsia/runners/common/web_component.h"
+#include "fuchsia/runners/common/web_content_runner.h"
+#include "fuchsia/test/promise.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void ComponentErrorHandler(zx_status_t status) {
+ ZX_LOG(ERROR, status) << "Component launch failed.";
+ ADD_FAILURE();
+}
+
+} // namespace
+
+class CastRunnerIntegrationTest : public testing::Test {
+ public:
+ CastRunnerIntegrationTest() : run_timeout_(TestTimeouts::action_timeout()) {
+ // Create a new test ServiceDirectory, and a test ComponentContext
+ // connected to it, for the test to use to drive the CastRunner.
+ zx::channel service_directory_request, service_directory_client;
+ zx_status_t status = zx::channel::create(0, &service_directory_client,
+ &service_directory_request);
+ ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
+
+ test_service_directory_ = std::make_unique<base::fuchsia::ServiceDirectory>(
+ std::move(service_directory_request));
+ test_component_context_ = std::make_unique<base::fuchsia::ComponentContext>(
+ std::move(service_directory_client));
+
+ // Create the AppConfigManager.
+ app_config_manager_ =
+ std::make_unique<FakeApplicationConfigManager>(&test_server_);
+ app_config_binding_ = std::make_unique<
+ fidl::Binding<chromium::cast::ApplicationConfigManager>>(
+ app_config_manager_.get());
+ chromium::cast::ApplicationConfigManagerPtr app_config_manager_interface;
+ app_config_binding_->Bind(app_config_manager_interface.NewRequest());
+
+ // Create the CastRunner, published into |test_service_directory_|.
+ cast_runner_ = std::make_unique<CastRunner>(
+ test_service_directory_.get(),
+ WebContentRunner::CreateDefaultWebContext(),
+ std::move(app_config_manager_interface),
+ cast_runner_run_loop_.QuitClosure());
+
+ // Connect to the CastRunner's fuchsia.sys.Runner interface.
+ cast_runner_ptr_ =
+ test_component_context_->ConnectToService<fuchsia::sys::Runner>();
+ cast_runner_ptr_.set_error_handler([this](zx_status_t status) {
+ ZX_LOG(ERROR, status) << "CastRunner closed channel.";
+ ADD_FAILURE();
+ cast_runner_run_loop_.Quit();
+ });
+ }
+
+ void SetUp() override { ASSERT_TRUE(test_server_.Start()); }
+
+ void TearDown() override {
+ // Disconnect the CastRunner.
+ cast_runner_ptr_.Unbind();
+ cast_runner_run_loop_.Run();
+ }
+
+ protected:
+ const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
+ base::MessageLoopForIO message_loop_;
+
+ net::EmbeddedTestServer test_server_;
+
+ std::unique_ptr<FakeApplicationConfigManager> app_config_manager_;
+ std::unique_ptr<fidl::Binding<chromium::cast::ApplicationConfigManager>>
+ app_config_binding_;
+
+ // ServiceDirectory into which the CastRunner will publish itself.
+ std::unique_ptr<base::fuchsia::ServiceDirectory> test_service_directory_;
+ std::unique_ptr<base::fuchsia::ComponentContext> test_component_context_;
+
+ std::unique_ptr<CastRunner> cast_runner_;
+ fuchsia::sys::RunnerPtr cast_runner_ptr_;
+ base::RunLoop cast_runner_run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastRunnerIntegrationTest);
+};
+
+// A basic integration test ensuring a basic cast request launches the right
+// URL in the Chromium service.
+TEST_F(CastRunnerIntegrationTest, BasicRequest) {
+ // Launch the test-app component.
+ fuchsia::sys::ComponentControllerPtr component_controller_ptr;
+ base::fuchsia::ComponentContext component_services(StartCastComponent(
+ base::StringPrintf("cast:%s",
+ FakeApplicationConfigManager::kTestCastAppId),
+ &cast_runner_ptr_, component_controller_ptr.NewRequest()));
+ component_controller_ptr.set_error_handler(&ComponentErrorHandler);
+
+ // Access the NavigationController from the WebComponent. The test will hang
+ // here if no WebComponent was created.
+ chromium::web::NavigationControllerPtr nav_controller;
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<WebComponent*> web_component(run_loop.QuitClosure());
+ cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
+ run_loop.Run();
+ ASSERT_NE(*web_component, nullptr);
+ (*web_component)
+ ->frame()
+ ->GetNavigationController(nav_controller.NewRequest());
+ }
+
+ // Ensure the NavigationEntry has the expected URL.
+ {
+ base::RunLoop run_loop;
+ webrunner::Promise<std::unique_ptr<chromium::web::NavigationEntry>>
+ nav_entry(run_loop.QuitClosure());
+ nav_controller->GetVisibleEntry(
+ webrunner::ConvertToFitFunction(nav_entry.GetReceiveCallback()));
+ run_loop.Run();
+ EXPECT_EQ(nav_entry->get()->url, test_server_.base_url().spec());
+ }
+}
+
+TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) {
+ // Launch the test-app component.
+ fuchsia::sys::ComponentControllerPtr component_controller_ptr;
+ base::fuchsia::ComponentContext component_services(
+ StartCastComponent("cast:99999999", &cast_runner_ptr_,
+ component_controller_ptr.NewRequest()));
+ component_controller_ptr.set_error_handler(&ComponentErrorHandler);
+
+ // Ensure no WebComponent was created.
+ base::RunLoop run_loop;
+ webrunner::Promise<WebComponent*> web_component(run_loop.QuitClosure());
+ cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
+ run_loop.Run();
+ EXPECT_EQ(*web_component, nullptr);
+}
diff --git a/chromium/fuchsia/runners/cast/cast_runner_unittest.cc b/chromium/fuchsia/runners/cast/cast_runner_unittest.cc
new file mode 100644
index 00000000000..0c6b4fa9679
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/cast_runner_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/cast/cast_runner.h"
+
+#include <lib/fidl/cpp/binding.h>
+#include <lib/zx/channel.h>
+#include <zircon/status.h>
+
+#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "fuchsia/runners/cast/fake_application_config_manager.h"
+#include "fuchsia/runners/cast/test_common.h"
+#include "fuchsia/test/fake_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class CastRunnerUnitTest : public testing::Test {
+ public:
+ CastRunnerUnitTest()
+ : fake_context_binding_(&fake_context_, fake_context_ptr_.NewRequest()) {
+ // Create a new ServiceDirectory, and a scoped default ComponentContext
+ // connected to it, for the test to use to drive the CastRunner.
+ zx::channel service_directory_request, service_directory_client;
+ zx_status_t status = zx::channel::create(0, &service_directory_client,
+ &service_directory_request);
+ ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
+
+ service_directory_ = std::make_unique<base::fuchsia::ServiceDirectory>(
+ std::move(service_directory_request));
+ scoped_default_component_context_ =
+ std::make_unique<base::fuchsia::ScopedDefaultComponentContext>(
+ std::move(service_directory_client));
+
+ // Create the AppConfigManager.
+ app_config_manager_ =
+ std::make_unique<FakeApplicationConfigManager>(&test_server_);
+ app_config_binding_ = std::make_unique<
+ fidl::Binding<chromium::cast::ApplicationConfigManager>>(
+ app_config_manager_.get());
+ chromium::cast::ApplicationConfigManagerPtr app_config_manager_interface;
+ app_config_binding_->Bind(app_config_manager_interface.NewRequest());
+
+ // Create the CastRunner, published into |service_directory_|.
+ cast_runner_ = std::make_unique<CastRunner>(
+ service_directory_.get(), std::move(fake_context_ptr_),
+ std::move(app_config_manager_interface),
+ until_runner_idle_loop_.QuitClosure());
+
+ // Connect to the CastRunner's fuchsia.sys.Runner interface.
+ cast_runner_ptr_ = base::fuchsia::ComponentContext::GetDefault()
+ ->ConnectToService<fuchsia::sys::Runner>();
+ cast_runner_ptr_.set_error_handler([this](zx_status_t status) {
+ ADD_FAILURE() << "CastRunner closed channel.";
+ until_runner_idle_loop_.Quit();
+ });
+ }
+
+ void SetUp() override { ASSERT_TRUE(test_server_.Start()); }
+
+ void RunUntilCastRunnerIsIdle() { until_runner_idle_loop_.Run(); }
+
+ protected:
+ base::MessageLoopForIO message_loop_;
+ base::RunLoop until_runner_idle_loop_;
+
+ // Test server.
+ net::EmbeddedTestServer test_server_;
+
+ // Test AppConfigManager and its binding.
+ std::unique_ptr<FakeApplicationConfigManager> app_config_manager_;
+ std::unique_ptr<fidl::Binding<chromium::cast::ApplicationConfigManager>>
+ app_config_binding_;
+
+ // Temporarily holds the InterfacePtr to the FakeContext, until it is passed
+ // to the CastRunner.
+ chromium::web::ContextPtr fake_context_ptr_;
+
+ // Fake web::Context, and binding to the client CastRunner.
+ webrunner::FakeContext fake_context_;
+ fidl::Binding<chromium::web::Context> fake_context_binding_;
+
+ // ServiceDirectory into which the CastRunner will publish itself.
+ std::unique_ptr<base::fuchsia::ServiceDirectory> service_directory_;
+ std::unique_ptr<base::fuchsia::ScopedDefaultComponentContext>
+ scoped_default_component_context_;
+
+ std::unique_ptr<CastRunner> cast_runner_;
+ fuchsia::sys::RunnerPtr cast_runner_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastRunnerUnitTest);
+};
+
+TEST_F(CastRunnerUnitTest, TeardownOnClientUnbind) {
+ // Disconnect from the CastRunner and wait for it to terminate.
+ cast_runner_ptr_.Unbind();
+ RunUntilCastRunnerIsIdle();
+}
+
+TEST_F(CastRunnerUnitTest, TeardownOnComponentControllerUnbind) {
+ // Create a ComponentController pointer, to manage the component lifetime.
+ fuchsia::sys::ComponentControllerPtr component_controller_ptr;
+
+ // Launch the test-app component, passing a ComponentController request.
+ base::fuchsia::ComponentContext component_services(StartCastComponent(
+ base::StringPrintf("cast:%s",
+ FakeApplicationConfigManager::kTestCastAppId),
+ &cast_runner_ptr_, component_controller_ptr.NewRequest()));
+
+ // Pump the message-loop to process StartComponent(). If the call is rejected
+ // then the ComponentControllerPtr's error-handler will be invoked at this
+ // point.
+ component_controller_ptr.set_error_handler([](zx_status_t status) {
+ ZX_LOG(ERROR, status) << "Component launch failed";
+ ADD_FAILURE();
+ });
+ base::RunLoop().RunUntilIdle();
+
+ // Disconnect ComponentController and expect the CastRunner will terminate.
+ component_controller_ptr.Unbind();
+ RunUntilCastRunnerIsIdle();
+}
diff --git a/chromium/fuchsia/runners/cast/fake_application_config_manager.cc b/chromium/fuchsia/runners/cast/fake_application_config_manager.cc
new file mode 100644
index 00000000000..185b8eee727
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/fake_application_config_manager.cc
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/cast/fake_application_config_manager.h"
+
+#include "base/logging.h"
+
+const char FakeApplicationConfigManager::kTestCastAppId[] = "00000000";
+
+FakeApplicationConfigManager::FakeApplicationConfigManager(
+ net::EmbeddedTestServer* embedded_test_server)
+ : embedded_test_server_(embedded_test_server) {}
+FakeApplicationConfigManager::~FakeApplicationConfigManager() = default;
+
+void FakeApplicationConfigManager::GetConfig(std::string id,
+ GetConfigCallback callback) {
+ if (id != kTestCastAppId) {
+ LOG(ERROR) << "Unknown Cast app Id: " << id;
+ callback(chromium::cast::ApplicationConfigPtr());
+ return;
+ }
+
+ chromium::cast::ApplicationConfigPtr app_config =
+ chromium::cast::ApplicationConfig::New();
+ app_config->id = id;
+ app_config->display_name = "Dummy test app";
+ app_config->web_url = embedded_test_server_->base_url().spec();
+ callback(std::move(app_config));
+}
diff --git a/chromium/fuchsia/runners/cast/fake_application_config_manager.h b/chromium/fuchsia/runners/cast/fake_application_config_manager.h
new file mode 100644
index 00000000000..eee8008dd5a
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/fake_application_config_manager.h
@@ -0,0 +1,32 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#define FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+
+#include "base/macros.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+// Test cast.ApplicationConfigManager implementation which maps a test Cast
+// AppId to an embedded test server address.
+class FakeApplicationConfigManager
+ : public chromium::cast::ApplicationConfigManager {
+ public:
+ static const char kTestCastAppId[];
+
+ explicit FakeApplicationConfigManager(
+ net::EmbeddedTestServer* embedded_test_server);
+ ~FakeApplicationConfigManager() override;
+
+ // chromium::cast::ApplicationConfigManager interface.
+ void GetConfig(std::string id, GetConfigCallback config_callback) override;
+
+ private:
+ net::EmbeddedTestServer* embedded_test_server_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeApplicationConfigManager);
+};
+
+#endif // FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
diff --git a/chromium/fuchsia/runners/cast/main.cc b/chromium/fuchsia/runners/cast/main.cc
new file mode 100644
index 00000000000..e04adb87841
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/main.cc
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. 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/fuchsia/component_context.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "fuchsia/runners/cast/cast_runner.h"
+
+int main(int argc, char** argv) {
+ base::MessageLoopForIO message_loop;
+ base::RunLoop run_loop;
+
+ CastRunner runner(
+ base::fuchsia::ServiceDirectory::GetDefault(),
+ WebContentRunner::CreateDefaultWebContext(),
+ base::fuchsia::ComponentContext::GetDefault()
+ ->ConnectToService<chromium::cast::ApplicationConfigManager>(),
+ run_loop.QuitClosure());
+
+ // Run until there are no Components, or the last service client channel is
+ // closed.
+ run_loop.Run();
+
+ return 0;
+}
diff --git a/chromium/fuchsia/runners/cast/sandbox_policy b/chromium/fuchsia/runners/cast/sandbox_policy
new file mode 100644
index 00000000000..aa36caacbfc
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/sandbox_policy
@@ -0,0 +1,16 @@
+{
+ "features": [],
+ "services": [
+ "chromium.web.ContextProvider",
+ "fuchsia.fonts.Provider",
+ "fuchsia.media.Audio",
+ "fuchsia.net.LegacySocketProvider",
+ "fuchsia.netstack.Netstack",
+ "fuchsia.process.Launcher",
+ "fuchsia.ui.input.ImeService",
+ "fuchsia.ui.input.ImeVisibilityService",
+ "fuchsia.ui.scenic.Scenic",
+ "fuchsia.ui.viewsv1.ViewManager",
+ "fuchsia.vulkan.loader.Loader"
+ ]
+}
diff --git a/chromium/fuchsia/runners/cast/test_common.cc b/chromium/fuchsia/runners/cast/test_common.cc
new file mode 100644
index 00000000000..2f56a7ea097
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/test_common.cc
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/cast/test_common.h"
+
+#include "base/fuchsia/fuchsia_logging.h"
+
+zx::channel StartCastComponent(
+ const base::StringPiece& cast_url,
+ fuchsia::sys::RunnerPtr* sys_runner,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ component_controller_request) {
+ fuchsia::sys::LaunchInfo launch_info;
+ launch_info.url = cast_url.as_string();
+
+ // Create a channel to pass to the Runner, through which to expose the new
+ // component's ServiceDirectory.
+ zx::channel service_directory_client;
+ zx_status_t status = zx::channel::create(0, &service_directory_client,
+ &launch_info.directory_request);
+ ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
+
+ fuchsia::sys::StartupInfo startup_info;
+ startup_info.launch_info = std::move(launch_info);
+
+ // The FlatNamespace vectors must be non-null, but may be empty.
+ startup_info.flat_namespace.paths.resize(0);
+ startup_info.flat_namespace.directories.resize(0);
+
+ fuchsia::sys::Package package;
+ package.resolved_url = cast_url.as_string();
+
+ sys_runner->get()->StartComponent(std::move(package), std::move(startup_info),
+ std::move(component_controller_request));
+ return service_directory_client;
+}
diff --git a/chromium/fuchsia/runners/cast/test_common.h b/chromium/fuchsia/runners/cast/test_common.h
new file mode 100644
index 00000000000..a8112104658
--- /dev/null
+++ b/chromium/fuchsia/runners/cast/test_common.h
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RUNNERS_CAST_TEST_COMMON_H_
+#define FUCHSIA_RUNNERS_CAST_TEST_COMMON_H_
+
+#include <fuchsia/sys/cpp/fidl.h>
+
+#include "base/strings/string_piece.h"
+
+// Starts a cast component from the runner |sys_runner| with the URL |cast_url|
+// and returns the service directory client channel.
+zx::channel StartCastComponent(
+ const base::StringPiece& cast_url,
+ fuchsia::sys::RunnerPtr* sys_runner,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ component_controller_request);
+
+#endif // FUCHSIA_RUNNERS_CAST_TEST_COMMON_H_ \ No newline at end of file
diff --git a/chromium/fuchsia/runners/common/web_component.cc b/chromium/fuchsia/runners/common/web_component.cc
new file mode 100644
index 00000000000..e410f79fd0f
--- /dev/null
+++ b/chromium/fuchsia/runners/common/web_component.cc
@@ -0,0 +1,121 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/common/web_component.h"
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/fit/function.h>
+#include <utility>
+
+#include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/logging.h"
+#include "fuchsia/runners/common/web_content_runner.h"
+
+WebComponent::~WebComponent() {
+ // Send process termination details to the client.
+ controller_binding_.events().OnTerminated(termination_exit_code_,
+ termination_reason_);
+}
+
+// static
+std::unique_ptr<WebComponent> WebComponent::ForUrlRequest(
+ WebContentRunner* runner,
+ const GURL& url,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request) {
+ DCHECK(url.is_valid());
+ std::unique_ptr<WebComponent> component(new WebComponent(
+ runner, std::move(startup_info), std::move(controller_request)));
+ chromium::web::NavigationControllerPtr navigation_controller;
+ component->frame()->GetNavigationController(
+ navigation_controller.NewRequest());
+
+ // Set the page activation flag on the initial load, so that features like
+ // autoplay work as expected when a WebComponent first loads the specified
+ // content.
+ auto params = std::make_unique<chromium::web::LoadUrlParams>();
+ params->user_activated = true;
+
+ navigation_controller->LoadUrl(url.spec(), std::move(params));
+
+ return component;
+}
+
+WebComponent::WebComponent(
+ WebContentRunner* runner,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request)
+ : runner_(runner), controller_binding_(this) {
+ DCHECK(runner);
+
+ // If the ComponentController request is valid then bind it, and configure it
+ // to destroy this component on error.
+ if (controller_request.is_valid()) {
+ controller_binding_.Bind(std::move(controller_request));
+ controller_binding_.set_error_handler([this](zx_status_t status) {
+ // Signal graceful process termination.
+ DestroyComponent(0, fuchsia::sys::TerminationReason::EXITED);
+ });
+ }
+
+ // Create the underlying Frame and get its NavigationController.
+ runner_->context()->CreateFrame(frame_.NewRequest());
+
+ // Create a ServiceDirectory for this component, and publish a ViewProvider
+ // into it, for the caller to use to create a View for this component.
+ // Note that we must publish ViewProvider before returning control to the
+ // message-loop, to ensure that it is available before the ServiceDirectory
+ // starts processing requests.
+ service_directory_ = std::make_unique<base::fuchsia::ServiceDirectory>(
+ std::move(startup_info.launch_info.directory_request));
+ view_provider_binding_ = std::make_unique<
+ base::fuchsia::ScopedServiceBinding<fuchsia::ui::app::ViewProvider>>(
+ service_directory_.get(), this);
+ legacy_view_provider_binding_ = std::make_unique<
+ base::fuchsia::ScopedServiceBinding<fuchsia::ui::viewsv1::ViewProvider>>(
+ service_directory_.get(), this);
+}
+
+void WebComponent::Kill() {
+ // Signal abnormal process termination.
+ DestroyComponent(1, fuchsia::sys::TerminationReason::RUNNER_TERMINATED);
+}
+
+void WebComponent::Detach() {
+ controller_binding_.set_error_handler(nullptr);
+}
+
+void WebComponent::CreateView(
+ zx::eventpair view_token,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
+ fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) {
+ DCHECK(frame_);
+ DCHECK(!view_is_bound_);
+
+ frame_->CreateView2(std::move(view_token), std::move(incoming_services),
+ std::move(outgoing_services));
+
+ view_is_bound_ = true;
+}
+
+void WebComponent::CreateView(
+ fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) {
+ // Cast the ViewOwner request to view_token. This is temporary hack for
+ // ViewsV2 transition. This version of CreateView() will be removed in the
+ // future.
+ CreateView(zx::eventpair(view_owner.TakeChannel().release()),
+ std::move(services), nullptr);
+}
+
+void WebComponent::DestroyComponent(int termination_exit_code,
+ fuchsia::sys::TerminationReason reason) {
+ termination_reason_ = reason;
+ termination_exit_code_ = termination_exit_code;
+ runner_->DestroyComponent(this);
+}
diff --git a/chromium/fuchsia/runners/common/web_component.h b/chromium/fuchsia/runners/common/web_component.h
new file mode 100644
index 00000000000..3bd43349cd3
--- /dev/null
+++ b/chromium/fuchsia/runners/common/web_component.h
@@ -0,0 +1,110 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RUNNERS_COMMON_WEB_COMPONENT_H_
+#define FUCHSIA_RUNNERS_COMMON_WEB_COMPONENT_H_
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/ui/app/cpp/fidl.h>
+#include <fuchsia/ui/viewsv1/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/logging.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "url/gurl.h"
+
+class WebContentRunner;
+
+// Base component implementation for web-based content Runners. Each instance
+// manages the lifetime of its own chromium::web::Frame, including associated
+// resources and service bindings. Runners for specialized web-based content
+// (e.g. Cast applications) can extend this class to configure the Frame to
+// their needs, publish additional APIs, etc.
+// TODO(crbug.com/899348): Remove fuchsia::ui::viewsv1::ViewProvider.
+class WebComponent : public fuchsia::sys::ComponentController,
+ public fuchsia::ui::app::ViewProvider,
+ public fuchsia::ui::viewsv1::ViewProvider {
+ public:
+ ~WebComponent() override;
+
+ // Creates a WebComponent and navigates its Frame to |url|.
+ static std::unique_ptr<WebComponent> ForUrlRequest(
+ WebContentRunner* runner,
+ const GURL& url,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request);
+
+ chromium::web::Frame* frame() { return frame_.get(); }
+
+ protected:
+ // Creates a WebComponent encapsulating a web.Frame. A ViewProvider service
+ // will be published to the service-directory specified in |startup_info|, and
+ // if |controller_request| is valid then it will be bound to this component,
+ // and the component configured to teardown if that channel closes.
+ // |runner| must outlive this component.
+ WebComponent(WebContentRunner* runner,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request);
+
+ // fuchsia::sys::ComponentController implementation.
+ void Kill() override;
+ void Detach() override;
+
+ // fuchsia::ui::app::ViewProvider implementation.
+ void CreateView(
+ zx::eventpair view_token,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
+ fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)
+ override;
+
+ // fuchsia::ui::viewsv1::ViewProvider implementation.
+ void CreateView(
+ fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
+ fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) override;
+
+ // Reports the supplied exit-code and reason to the |controller_binding_| and
+ // requests that the |runner_| delete this component.
+ void DestroyComponent(int termination_exit_code,
+ fuchsia::sys::TerminationReason reason);
+
+ base::fuchsia::ServiceDirectory* service_directory() {
+ return service_directory_.get();
+ }
+
+ private:
+ WebContentRunner* runner_ = nullptr;
+
+ chromium::web::FramePtr frame_;
+
+ fidl::Binding<fuchsia::sys::ComponentController> controller_binding_;
+
+ // Objects used for binding and exporting the ViewProvider service.
+ std::unique_ptr<base::fuchsia::ServiceDirectory> service_directory_;
+ std::unique_ptr<
+ base::fuchsia::ScopedServiceBinding<fuchsia::ui::app::ViewProvider>>
+ view_provider_binding_;
+ std::unique_ptr<
+ base::fuchsia::ScopedServiceBinding<fuchsia::ui::viewsv1::ViewProvider>>
+ legacy_view_provider_binding_;
+
+ // Termination reason and exit-code to be reported via the
+ // sys::ComponentController::OnTerminated event.
+ fuchsia::sys::TerminationReason termination_reason_ =
+ fuchsia::sys::TerminationReason::UNKNOWN;
+ int termination_exit_code_ = 0;
+
+ bool view_is_bound_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(WebComponent);
+};
+
+#endif // FUCHSIA_RUNNERS_COMMON_WEB_COMPONENT_H_
diff --git a/chromium/fuchsia/runners/common/web_content_runner.cc b/chromium/fuchsia/runners/common/web_content_runner.cc
new file mode 100644
index 00000000000..01e6717b929
--- /dev/null
+++ b/chromium/fuchsia/runners/common/web_content_runner.cc
@@ -0,0 +1,109 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/common/web_content_runner.h"
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <utility>
+
+#include "base/files/file.h"
+#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/logging.h"
+#include "fuchsia/runners/common/web_component.h"
+#include "url/gurl.h"
+
+// static
+chromium::web::ContextPtr WebContentRunner::CreateDefaultWebContext() {
+ auto web_context_provider =
+ base::fuchsia::ComponentContext::GetDefault()
+ ->ConnectToService<chromium::web::ContextProvider>();
+
+ chromium::web::CreateContextParams create_params;
+
+ // Clone /svc to the context.
+ create_params.service_directory =
+ zx::channel(base::fuchsia::GetHandleFromFile(
+ base::File(base::FilePath("/svc"),
+ base::File::FLAG_OPEN | base::File::FLAG_READ)));
+
+ chromium::web::ContextPtr web_context;
+ web_context_provider->Create(std::move(create_params),
+ web_context.NewRequest());
+ web_context.set_error_handler([](zx_status_t status) {
+ // If the browser instance died, then exit everything and do not attempt
+ // to recover. appmgr will relaunch the runner when it is needed again.
+ ZX_LOG(ERROR, status) << "Connection to Context lost.";
+ exit(1);
+ });
+ return web_context;
+}
+
+WebContentRunner::WebContentRunner(
+ base::fuchsia::ServiceDirectory* service_directory,
+ chromium::web::ContextPtr context,
+ base::OnceClosure on_idle_closure)
+ : context_(std::move(context)),
+ service_binding_(service_directory, this),
+ on_idle_closure_(std::move(on_idle_closure)) {
+ DCHECK(context_);
+ DCHECK(on_idle_closure_);
+
+ // Signal that we're idle if the service manager connection is dropped.
+ service_binding_.SetOnLastClientCallback(base::BindOnce(
+ &WebContentRunner::RunOnIdleClosureIfValid, base::Unretained(this)));
+}
+
+WebContentRunner::~WebContentRunner() = default;
+
+void WebContentRunner::StartComponent(
+ fuchsia::sys::Package package,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request) {
+ GURL url(package.resolved_url);
+ if (!url.is_valid()) {
+ LOG(ERROR) << "Rejected invalid URL: " << url;
+ return;
+ }
+
+ RegisterComponent(WebComponent::ForUrlRequest(this, std::move(url),
+ std::move(startup_info),
+ std::move(controller_request)));
+}
+
+void WebContentRunner::GetWebComponentForTest(
+ base::OnceCallback<void(WebComponent*)> callback) {
+ if (!components_.empty()) {
+ std::move(callback).Run(components_.begin()->get());
+ return;
+ }
+ web_component_test_callback_ = std::move(callback);
+}
+
+void WebContentRunner::DestroyComponent(WebComponent* component) {
+ components_.erase(components_.find(component));
+
+ if (components_.empty())
+ RunOnIdleClosureIfValid();
+}
+
+void WebContentRunner::RegisterComponent(
+ std::unique_ptr<WebComponent> component) {
+ if (web_component_test_callback_) {
+ std::move(web_component_test_callback_).Run(component.get());
+ }
+ if (component) {
+ components_.insert(std::move(component));
+ }
+}
+
+void WebContentRunner::RunOnIdleClosureIfValid() {
+ if (on_idle_closure_)
+ std::move(on_idle_closure_).Run();
+}
diff --git a/chromium/fuchsia/runners/common/web_content_runner.h b/chromium/fuchsia/runners/common/web_content_runner.h
new file mode 100644
index 00000000000..c65f30ed5ea
--- /dev/null
+++ b/chromium/fuchsia/runners/common/web_content_runner.h
@@ -0,0 +1,81 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
+#define FUCHSIA_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <memory>
+#include <set>
+
+#include "base/callback.h"
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/macros.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+
+class WebComponent;
+
+// sys::Runner that instantiates components hosting standard web content.
+class WebContentRunner : public fuchsia::sys::Runner {
+ public:
+ // Creates and returns a web.Context with a default path and parameters,
+ // and with access to the same services as this Runner. The returned binding
+ // is configured to exit this process on error.
+ static chromium::web::ContextPtr CreateDefaultWebContext();
+
+ // |service_directory|: ServiceDirectory into which this Runner will be
+ // published. |on_idle_closure| will be invoked when the final client of the
+ // published service disconnects, even if one or more Components are still
+ // active.
+ // |content|: Context (e.g. persisted profile storage) under which all web
+ // content launched through this Runner instance will be run.
+ // |on_idle_closure|: A callback which is invoked when the WebContentRunner
+ // has entered an idle state and may be safely torn down.
+ WebContentRunner(base::fuchsia::ServiceDirectory* service_directory,
+ chromium::web::ContextPtr context,
+ base::OnceClosure on_idle_closure);
+ ~WebContentRunner() override;
+
+ chromium::web::Context* context() { return context_.get(); }
+
+ // Used by WebComponent instances to signal that the ComponentController
+ // channel was dropped, and therefore the component should be destroyed.
+ void DestroyComponent(WebComponent* component);
+
+ // fuchsia::sys::Runner implementation.
+ void StartComponent(fuchsia::sys::Package package,
+ fuchsia::sys::StartupInfo startup_info,
+ fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+ controller_request) override;
+
+ // Used by tests to asynchronously access the first WebComponent.
+ void GetWebComponentForTest(base::OnceCallback<void(WebComponent*)> callback);
+
+ protected:
+ // Registers a WebComponent, or specialization, with this Runner.
+ void RegisterComponent(std::unique_ptr<WebComponent> component);
+
+ private:
+ void RunOnIdleClosureIfValid();
+
+ chromium::web::ContextPtr context_;
+ std::set<std::unique_ptr<WebComponent>, base::UniquePtrComparator>
+ components_;
+
+ // Publishes this Runner into the service directory specified at construction.
+ base::fuchsia::ScopedServiceBinding<fuchsia::sys::Runner> service_binding_;
+
+ // Run when no components remain, or the last |service_binding_| client
+ // disconnects, to quit the Runner.
+ base::OnceClosure on_idle_closure_;
+
+ // Test-only callback for GetWebComponentForTest.
+ base::OnceCallback<void(WebComponent*)> web_component_test_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebContentRunner);
+};
+
+#endif // FUCHSIA_RUNNERS_COMMON_WEB_CONTENT_RUNNER_H_
diff --git a/chromium/fuchsia/runners/web/main.cc b/chromium/fuchsia/runners/web/main.cc
new file mode 100644
index 00000000000..b4f88964bfd
--- /dev/null
+++ b/chromium/fuchsia/runners/web/main.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Chromium Authors. 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/fuchsia/service_directory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "fuchsia/runners/common/web_content_runner.h"
+
+int main(int argc, char** argv) {
+ base::MessageLoopForIO message_loop;
+ base::RunLoop run_loop;
+
+ WebContentRunner runner(base::fuchsia::ServiceDirectory::GetDefault(),
+ WebContentRunner::CreateDefaultWebContext(),
+ run_loop.QuitClosure());
+
+ // Run until there are no Components, or the last service client channel is
+ // closed.
+ run_loop.Run();
+
+ return 0;
+}
diff --git a/chromium/fuchsia/runners/web/sandbox_policy b/chromium/fuchsia/runners/web/sandbox_policy
new file mode 100644
index 00000000000..9f8cb6375e4
--- /dev/null
+++ b/chromium/fuchsia/runners/web/sandbox_policy
@@ -0,0 +1,17 @@
+{
+ "features": [],
+ "services": [
+ "chromium.web.ContextProvider",
+ "fuchsia.fonts.Provider",
+ "fuchsia.media.Audio",
+ "fuchsia.mediacodec.CodecFactory",
+ "fuchsia.net.LegacySocketProvider",
+ "fuchsia.netstack.Netstack",
+ "fuchsia.process.Launcher",
+ "fuchsia.ui.input.ImeService",
+ "fuchsia.ui.input.ImeVisibilityService",
+ "fuchsia.ui.scenic.Scenic",
+ "fuchsia.ui.viewsv1.ViewManager",
+ "fuchsia.vulkan.loader.Loader"
+ ]
+}
diff --git a/chromium/fuchsia/runners/web/web_runner_smoke_test.cc b/chromium/fuchsia/runners/web/web_runner_smoke_test.cc
new file mode 100644
index 00000000000..d4182e5351b
--- /dev/null
+++ b/chromium/fuchsia/runners/web/web_runner_smoke_test.cc
@@ -0,0 +1,78 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/sys/cpp/fidl.h>
+
+#include "base/fuchsia/component_context.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::test_server::HttpRequest;
+using net::test_server::HttpResponse;
+
+namespace {
+
+class WebRunnerSmokeTest : public testing::Test {
+ public:
+ WebRunnerSmokeTest() : run_timeout_(TestTimeouts::action_timeout()) {}
+ void SetUp() final {
+ test_server_.RegisterRequestHandler(base::BindRepeating(
+ &WebRunnerSmokeTest::HandleRequest, base::Unretained(this)));
+ ASSERT_TRUE(test_server_.Start());
+ }
+
+ std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
+ GURL absolute_url = test_server_.GetURL(request.relative_url);
+ if (absolute_url.path() == "/test.html") {
+ EXPECT_FALSE(test_html_requested_);
+ test_html_requested_ = true;
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("<!doctype html><img src=\"/img.png\">");
+ http_response->set_content_type("text/html");
+ return http_response;
+ } else if (absolute_url.path() == "/img.png") {
+ EXPECT_FALSE(test_image_requested_);
+ test_image_requested_ = true;
+ // All done!
+ run_loop_.Quit();
+ }
+ return nullptr;
+ }
+
+ protected:
+ const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
+ bool test_html_requested_ = false;
+ bool test_image_requested_ = false;
+
+ base::MessageLoopForIO message_loop_;
+
+ net::EmbeddedTestServer test_server_;
+
+ base::RunLoop run_loop_;
+};
+
+TEST_F(WebRunnerSmokeTest, RequestHtmlAndImage) {
+ fuchsia::sys::LaunchInfo launch_info;
+ launch_info.url = test_server_.GetURL("/test.html").spec();
+
+ auto launcher = base::fuchsia::ComponentContext::GetDefault()
+ ->ConnectToServiceSync<fuchsia::sys::Launcher>();
+
+ fuchsia::sys::ComponentControllerSyncPtr controller;
+ launcher->CreateComponent(std::move(launch_info), controller.NewRequest());
+
+ run_loop_.Run();
+
+ EXPECT_TRUE(test_html_requested_);
+ EXPECT_TRUE(test_image_requested_);
+}
+
+} // anonymous namespace
diff --git a/chromium/fuchsia/service/DEPS b/chromium/fuchsia/service/DEPS
new file mode 100644
index 00000000000..510cf285dde
--- /dev/null
+++ b/chromium/fuchsia/service/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+chromium/web/cpp", # FIDL generated headers
+ "+content/public/app",
+ "+services/service_manager",
+ "+ui/base",
+]
diff --git a/chromium/fuchsia/service/common.cc b/chromium/fuchsia/service/common.cc
new file mode 100644
index 00000000000..cdccacebb61
--- /dev/null
+++ b/chromium/fuchsia/service/common.cc
@@ -0,0 +1,11 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/service/common.h"
+
+namespace webrunner {
+
+constexpr char kIncognitoSwitch[] = "incognito";
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/service/common.h b/chromium/fuchsia/service/common.h
new file mode 100644
index 00000000000..e48ec868c63
--- /dev/null
+++ b/chromium/fuchsia/service/common.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SERVICE_COMMON_H_
+#define FUCHSIA_SERVICE_COMMON_H_
+
+#include <zircon/processargs.h>
+
+#include "fuchsia/common/fuchsia_export.h"
+
+namespace webrunner {
+
+// Switch passed to content process when running in incognito mode, i.e. when
+// there is no kWebContextDataPath.
+FUCHSIA_EXPORT extern const char kIncognitoSwitch[];
+
+// This file contains constants and functions shared between Context and
+// ContextProvider processes.
+
+// Handle ID for the Context interface request passed from ContextProvider to
+// Context process.
+constexpr uint32_t kContextRequestHandleId = PA_HND(PA_USER0, 0);
+
+} // namespace webrunner
+
+#endif // FUCHSIA_SERVICE_COMMON_H_
diff --git a/chromium/fuchsia/service/context_provider_impl.cc b/chromium/fuchsia/service/context_provider_impl.cc
new file mode 100644
index 00000000000..4c64a1b3852
--- /dev/null
+++ b/chromium/fuchsia/service/context_provider_impl.cc
@@ -0,0 +1,140 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/service/context_provider_impl.h"
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/async/default.h>
+#include <lib/fdio/io.h>
+#include <lib/fdio/util.h>
+#include <lib/zx/job.h>
+#include <stdio.h>
+#include <zircon/processargs.h>
+
+#include <utility>
+
+#include "base/base_paths_fuchsia.h"
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/fuchsia/default_job.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "fuchsia/service/common.h"
+#include "services/service_manager/sandbox/fuchsia/sandbox_policy_fuchsia.h"
+
+namespace webrunner {
+namespace {
+
+// Relaunches the current executable as a Context process.
+base::Process LaunchContextProcess(const base::CommandLine& launch_command,
+ const base::LaunchOptions& launch_options) {
+ return base::LaunchProcess(launch_command, launch_options);
+}
+
+// Returns true if |handle| is connected to a directory.
+bool IsValidDirectory(zx_handle_t handle) {
+ base::File directory =
+ base::fuchsia::GetFileFromHandle(zx::handle(fdio_service_clone(handle)));
+ if (!directory.IsValid())
+ return false;
+
+ base::File::Info info;
+ if (!directory.GetInfo(&info)) {
+ LOG(ERROR) << "Could not query FileInfo for handle.";
+ directory.Close();
+ return false;
+ }
+
+ if (!info.is_directory) {
+ LOG(ERROR) << "Handle is not a directory.";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+ContextProviderImpl::ContextProviderImpl() : ContextProviderImpl(false) {}
+
+ContextProviderImpl::ContextProviderImpl(bool use_shared_tmp)
+ : launch_(base::BindRepeating(&LaunchContextProcess)),
+ use_shared_tmp_(use_shared_tmp) {}
+
+ContextProviderImpl::~ContextProviderImpl() = default;
+
+// static
+std::unique_ptr<ContextProviderImpl> ContextProviderImpl::CreateForTest() {
+ // Bind the unique_ptr in a two step process.
+ // std::make_unique<> doesn't work well with private constructors,
+ // and the unique_ptr(raw_ptr*) constructor format isn't permitted as per
+ // PRESUBMIT.py policy.
+ std::unique_ptr<ContextProviderImpl> provider;
+ provider.reset(new ContextProviderImpl(true));
+ return provider;
+}
+
+void ContextProviderImpl::SetLaunchCallbackForTests(
+ const LaunchContextProcessCallback& launch) {
+ launch_ = launch;
+}
+
+void ContextProviderImpl::Create(
+ chromium::web::CreateContextParams params,
+ ::fidl::InterfaceRequest<chromium::web::Context> context_request) {
+ DCHECK(context_request.is_valid());
+
+ base::CommandLine launch_command = *base::CommandLine::ForCurrentProcess();
+
+ base::LaunchOptions launch_options;
+
+ service_manager::SandboxPolicyFuchsia sandbox_policy;
+ sandbox_policy.Initialize(service_manager::SANDBOX_TYPE_WEB_CONTEXT);
+ sandbox_policy.SetServiceDirectory(std::move(params.service_directory));
+ sandbox_policy.UpdateLaunchOptionsForSandbox(&launch_options);
+
+ if (use_shared_tmp_)
+ launch_options.paths_to_clone.push_back(base::FilePath("/tmp"));
+
+ // Transfer the ContextRequest handle to a well-known location in the child
+ // process' handle table.
+ zx::channel context_handle(context_request.TakeChannel());
+ launch_options.handles_to_transfer.push_back(
+ {kContextRequestHandleId, context_handle.get()});
+
+ // Bind |data_directory| to /data directory, if provided.
+ if (params.data_directory) {
+ if (!IsValidDirectory(params.data_directory.get()))
+ return;
+
+ base::FilePath data_path;
+ CHECK(base::PathService::Get(base::DIR_APP_DATA, &data_path));
+ launch_options.paths_to_transfer.push_back(
+ base::PathToTransfer{data_path, params.data_directory.release()});
+ }
+
+ // Isolate the child Context processes by containing them within their own
+ // respective jobs.
+ zx::job job;
+ zx_status_t status = zx::job::create(*base::GetDefaultJob(), 0, &job);
+ ZX_CHECK(status == ZX_OK, status) << "zx_job_create";
+ launch_options.job_handle = job.get();
+
+ ignore_result(launch_.Run(std::move(launch_command), launch_options));
+
+ ignore_result(context_handle.release());
+ ignore_result(job.release());
+}
+
+void ContextProviderImpl::Bind(
+ fidl::InterfaceRequest<chromium::web::ContextProvider> request) {
+ bindings_.AddBinding(this, std::move(request));
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/service/context_provider_impl.h b/chromium/fuchsia/service/context_provider_impl.h
new file mode 100644
index 00000000000..84239c808bf
--- /dev/null
+++ b/chromium/fuchsia/service/context_provider_impl.h
@@ -0,0 +1,71 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SERVICE_CONTEXT_PROVIDER_IMPL_H_
+#define FUCHSIA_SERVICE_CONTEXT_PROVIDER_IMPL_H_
+
+#include <lib/fidl/cpp/binding_set.h>
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "chromium/web/cpp/fidl.h"
+#include "fuchsia/common/fuchsia_export.h"
+
+namespace base {
+class CommandLine;
+struct LaunchOptions;
+class Process;
+} // namespace base
+
+namespace webrunner {
+
+class FUCHSIA_EXPORT ContextProviderImpl
+ : public chromium::web::ContextProvider {
+ public:
+ ContextProviderImpl();
+ ~ContextProviderImpl() override;
+
+ // Creates a ContextProviderImpl that shares its /tmp directory with its child
+ // processes. This is useful for GTest processes, which depend on a shared
+ // tmpdir for storing startup flags and retrieving test result files.
+ static std::unique_ptr<ContextProviderImpl> CreateForTest();
+
+ // Binds |this| object instance to |request|.
+ // The service will persist and continue to serve other channels in the event
+ // that a bound channel is dropped.
+ void Bind(fidl::InterfaceRequest<chromium::web::ContextProvider> request);
+
+ // chromium::web::ContextProvider implementation.
+ void Create(chromium::web::CreateContextParams params,
+ ::fidl::InterfaceRequest<chromium::web::Context> context_request)
+ override;
+
+ private:
+ using LaunchContextProcessCallback = base::RepeatingCallback<base::Process(
+ const base::CommandLine& command,
+ const base::LaunchOptions& options)>;
+
+ friend class ContextProviderImplTest;
+
+ explicit ContextProviderImpl(bool use_shared_tmp);
+
+ // Overrides the default child process launching logic to call |launch|
+ // instead.
+ void SetLaunchCallbackForTests(const LaunchContextProcessCallback& launch);
+
+ // Spawns a Context child process.
+ LaunchContextProcessCallback launch_;
+
+ // If set, then the ContextProvider will share /tmp with its child processes.
+ bool use_shared_tmp_ = true;
+
+ fidl::BindingSet<chromium::web::ContextProvider> bindings_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextProviderImpl);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_SERVICE_CONTEXT_PROVIDER_IMPL_H_
diff --git a/chromium/fuchsia/service/context_provider_impl_unittest.cc b/chromium/fuchsia/service/context_provider_impl_unittest.cc
new file mode 100644
index 00000000000..ac744c1fef4
--- /dev/null
+++ b/chromium/fuchsia/service/context_provider_impl_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/service/context_provider_impl.h"
+
+#include <lib/fdio/util.h>
+#include <lib/fidl/cpp/binding.h>
+#include <zircon/processargs.h>
+
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/base_paths_fuchsia.h"
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/test/multiprocess_test.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl_test_base.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/test/fake_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace webrunner {
+namespace {
+
+constexpr char kTestDataFileIn[] = "DataFileIn";
+constexpr char kTestDataFileOut[] = "DataFileOut";
+constexpr char kUrl[] = "chrome://:emorhc";
+constexpr char kTitle[] = "Palindrome";
+
+MULTIPROCESS_TEST_MAIN(SpawnContextServer) {
+ base::MessageLoopForIO message_loop;
+
+ base::FilePath data_dir;
+ CHECK(base::PathService::Get(base::DIR_APP_DATA, &data_dir));
+ if (!data_dir.empty()) {
+ if (base::PathExists(data_dir.AppendASCII(kTestDataFileIn))) {
+ auto out_file = data_dir.AppendASCII(kTestDataFileOut);
+ EXPECT_EQ(base::WriteFile(out_file, nullptr, 0), 0);
+ }
+ }
+
+ zx::channel context_handle(zx_take_startup_handle(kContextRequestHandleId));
+ CHECK(context_handle);
+
+ FakeContext context;
+ fidl::Binding<chromium::web::Context> context_binding(
+ &context, fidl::InterfaceRequest<chromium::web::Context>(
+ std::move(context_handle)));
+
+ // When a Frame's NavigationEventObserver is bound, immediately broadcast a
+ // navigation event to its listeners.
+ context.set_on_create_frame_callback(
+ base::BindRepeating([](FakeFrame* frame) {
+ frame->set_on_set_observer_callback(base::BindOnce(
+ [](FakeFrame* frame) {
+ chromium::web::NavigationEvent event;
+ event.url = kUrl;
+ event.title = kTitle;
+ frame->observer()->OnNavigationStateChanged(std::move(event),
+ []() {});
+ },
+ frame));
+ }));
+
+ // Quit the process when the context is destroyed.
+ base::RunLoop run_loop;
+ context_binding.set_error_handler(
+ [&run_loop](zx_status_t status) { run_loop.Quit(); });
+ run_loop.Run();
+
+ return 0;
+}
+
+} // namespace
+
+class ContextProviderImplTest : public base::MultiProcessTest {
+ public:
+ ContextProviderImplTest() : provider_(ContextProviderImpl::CreateForTest()) {
+ provider_->SetLaunchCallbackForTests(base::BindRepeating(
+ &base::SpawnMultiProcessTestChild, "SpawnContextServer"));
+ provider_->Bind(provider_ptr_.NewRequest());
+ }
+
+ ~ContextProviderImplTest() override {
+ provider_ptr_.Unbind();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Check if a Context is responsive by creating a Frame from it and then
+ // listening for an event.
+ void CheckContextResponsive(
+ fidl::InterfacePtr<chromium::web::Context>* context) {
+ // Call a Context method and wait for it to invoke an observer call.
+ base::RunLoop run_loop;
+ context->set_error_handler([&run_loop](zx_status_t status) {
+ ADD_FAILURE();
+ run_loop.Quit();
+ });
+
+ chromium::web::FramePtr frame_ptr;
+ frame_ptr.set_error_handler([&run_loop](zx_status_t status) {
+ ADD_FAILURE();
+ run_loop.Quit();
+ });
+ (*context)->CreateFrame(frame_ptr.NewRequest());
+
+ // Create a Frame and expect to see a navigation event.
+ CapturingNavigationEventObserver change_observer(run_loop.QuitClosure());
+ fidl::Binding<chromium::web::NavigationEventObserver>
+ change_observer_binding(&change_observer);
+ frame_ptr->SetNavigationEventObserver(change_observer_binding.NewBinding());
+ run_loop.Run();
+
+ EXPECT_EQ(change_observer.captured_event().url, kUrl);
+ EXPECT_EQ(change_observer.captured_event().title, kTitle);
+ }
+
+ chromium::web::CreateContextParams BuildCreateContextParams() {
+ zx::channel client_channel;
+ zx::channel server_channel;
+ zx_status_t result =
+ zx::channel::create(0, &client_channel, &server_channel);
+ ZX_CHECK(result == ZX_OK, result) << "zx_channel_create()";
+ result = fdio_service_connect("/svc/.", server_channel.release());
+ ZX_CHECK(result == ZX_OK, result) << "Failed to open /svc";
+
+ chromium::web::CreateContextParams output;
+ output.service_directory = std::move(client_channel);
+ return output;
+ }
+
+ // Checks that the Context channel was dropped.
+ void CheckContextUnresponsive(
+ fidl::InterfacePtr<chromium::web::Context>* context) {
+ base::RunLoop run_loop;
+ context->set_error_handler(
+ [&run_loop](zx_status_t status) { run_loop.Quit(); });
+
+ chromium::web::FramePtr frame;
+ (*context)->CreateFrame(frame.NewRequest());
+
+ // The error handler should be called here.
+ run_loop.Run();
+ }
+
+ protected:
+ base::MessageLoopForIO message_loop_;
+ std::unique_ptr<ContextProviderImpl> provider_;
+ chromium::web::ContextProviderPtr provider_ptr_;
+
+ private:
+ struct CapturingNavigationEventObserver
+ : public chromium::web::NavigationEventObserver {
+ public:
+ explicit CapturingNavigationEventObserver(base::OnceClosure on_change_cb)
+ : on_change_cb_(std::move(on_change_cb)) {}
+ ~CapturingNavigationEventObserver() override = default;
+
+ void OnNavigationStateChanged(
+ chromium::web::NavigationEvent change,
+ OnNavigationStateChangedCallback callback) override {
+ captured_event_ = std::move(change);
+ std::move(on_change_cb_).Run();
+ }
+
+ chromium::web::NavigationEvent captured_event() { return captured_event_; }
+
+ private:
+ base::OnceClosure on_change_cb_;
+ chromium::web::NavigationEvent captured_event_;
+ };
+
+ DISALLOW_COPY_AND_ASSIGN(ContextProviderImplTest);
+};
+
+TEST_F(ContextProviderImplTest, LaunchContext) {
+ // Connect to a new context process.
+ fidl::InterfacePtr<chromium::web::Context> context;
+ chromium::web::CreateContextParams create_params = BuildCreateContextParams();
+ provider_ptr_->Create(std::move(create_params), context.NewRequest());
+ CheckContextResponsive(&context);
+}
+
+TEST_F(ContextProviderImplTest, MultipleConcurrentClients) {
+ // Bind a Provider connection, and create a Context from it.
+ chromium::web::ContextProviderPtr provider_1_ptr;
+ provider_->Bind(provider_1_ptr.NewRequest());
+ chromium::web::ContextPtr context_1;
+ provider_1_ptr->Create(BuildCreateContextParams(), context_1.NewRequest());
+
+ // Do the same on another Provider connection.
+ chromium::web::ContextProviderPtr provider_2_ptr;
+ provider_->Bind(provider_2_ptr.NewRequest());
+ chromium::web::ContextPtr context_2;
+ provider_2_ptr->Create(BuildCreateContextParams(), context_2.NewRequest());
+
+ CheckContextResponsive(&context_1);
+ CheckContextResponsive(&context_2);
+
+ // Ensure that the initial ContextProvider connection is still usable, by
+ // creating and verifying another Context from it.
+ chromium::web::ContextPtr context_3;
+ provider_2_ptr->Create(BuildCreateContextParams(), context_3.NewRequest());
+ CheckContextResponsive(&context_3);
+}
+
+TEST_F(ContextProviderImplTest, WithProfileDir) {
+ base::ScopedTempDir profile_temp_dir;
+
+ // Connect to a new context process.
+ fidl::InterfacePtr<chromium::web::Context> context;
+ chromium::web::CreateContextParams create_params = BuildCreateContextParams();
+
+ // Setup data dir.
+ EXPECT_TRUE(profile_temp_dir.CreateUniqueTempDir());
+ ASSERT_EQ(
+ base::WriteFile(profile_temp_dir.GetPath().AppendASCII(kTestDataFileIn),
+ nullptr, 0),
+ 0);
+
+ // Pass a handle data dir to the context.
+ create_params.data_directory.reset(
+ base::fuchsia::GetHandleFromFile(
+ base::File(profile_temp_dir.GetPath(),
+ base::File::FLAG_OPEN | base::File::FLAG_READ))
+ .release());
+
+ provider_ptr_->Create(std::move(create_params), context.NewRequest());
+
+ CheckContextResponsive(&context);
+
+ // Verify that the context process can write to the data dir.
+ EXPECT_TRUE(base::PathExists(
+ profile_temp_dir.GetPath().AppendASCII(kTestDataFileOut)));
+}
+
+TEST_F(ContextProviderImplTest, FailsDataDirectoryIsFile) {
+ base::FilePath temp_file_path;
+
+ // Connect to a new context process.
+ fidl::InterfacePtr<chromium::web::Context> context;
+ chromium::web::CreateContextParams create_params = BuildCreateContextParams();
+
+ // Pass in a handle to a file instead of a directory.
+ CHECK(base::CreateTemporaryFile(&temp_file_path));
+ base::File temp_file(temp_file_path,
+ base::File::FLAG_OPEN | base::File::FLAG_READ);
+ create_params.data_directory.reset(
+ base::fuchsia::GetHandleFromFile(std::move(temp_file)).release());
+
+ provider_ptr_->Create(std::move(create_params), context.NewRequest());
+
+ CheckContextUnresponsive(&context);
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/service/context_provider_main.cc b/chromium/fuchsia/service/context_provider_main.cc
new file mode 100644
index 00000000000..dfa17496728
--- /dev/null
+++ b/chromium/fuchsia/service/context_provider_main.cc
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/service/context_provider_main.h"
+
+#include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "fuchsia/service/context_provider_impl.h"
+
+namespace webrunner {
+
+int ContextProviderMain() {
+ base::MessageLoopForUI message_loop;
+ base::fuchsia::ServiceDirectory* directory =
+ base::fuchsia::ServiceDirectory::GetDefault();
+ ContextProviderImpl context_provider;
+ base::fuchsia::ScopedServiceBinding<chromium::web::ContextProvider> binding(
+ directory, &context_provider);
+
+ // TODO(crbug.com/852145): Currently the process will run until it's killed.
+ base::RunLoop().Run();
+
+ return 0;
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/service/context_provider_main.h b/chromium/fuchsia/service/context_provider_main.h
new file mode 100644
index 00000000000..4ae7b8c6936
--- /dev/null
+++ b/chromium/fuchsia/service/context_provider_main.h
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SERVICE_CONTEXT_PROVIDER_MAIN_H_
+#define FUCHSIA_SERVICE_CONTEXT_PROVIDER_MAIN_H_
+
+#include "fuchsia/common/fuchsia_export.h"
+
+namespace webrunner {
+
+// Main function for the process that implements web::ContextProvider interface.
+// Called by WebRunnerMainDelegate when the process is started without --type
+// argument.
+FUCHSIA_EXPORT int ContextProviderMain();
+
+} // namespace webrunner
+
+#endif // FUCHSIA_SERVICE_CONTEXT_PROVIDER_MAIN_H_ \ No newline at end of file
diff --git a/chromium/fuchsia/service/sandbox_policy b/chromium/fuchsia/service/sandbox_policy
new file mode 100644
index 00000000000..f6de1d1298a
--- /dev/null
+++ b/chromium/fuchsia/service/sandbox_policy
@@ -0,0 +1,6 @@
+{
+ "features": [ "root-ssl-certificates", "vulkan" ],
+ "services": [
+ "fuchsia.process.Launcher"
+ ]
+}
diff --git a/chromium/fuchsia/service/web_content_service_main.cc b/chromium/fuchsia/service/web_content_service_main.cc
new file mode 100644
index 00000000000..6fb4d72825c
--- /dev/null
+++ b/chromium/fuchsia/service/web_content_service_main.cc
@@ -0,0 +1,45 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <zircon/process.h>
+
+#include "base/command_line.h"
+#include "content/public/app/content_main.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/service/context_provider_main.h"
+#include "fuchsia/service/webrunner_main_delegate.h"
+#include "services/service_manager/embedder/switches.h"
+
+int main(int argc, const char** argv) {
+ base::CommandLine::Init(argc, argv);
+
+ std::string process_type =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ service_manager::switches::kProcessType);
+ zx::channel context_channel;
+
+ if (process_type.empty()) {
+ // zx_take_startup_handle() is called only when process_type is empty (i.e.
+ // for Browser and ContextProvider processes). Renderer and other child
+ // processes may use the same handle id for other handles.
+ context_channel.reset(
+ zx_take_startup_handle(webrunner::kContextRequestHandleId));
+
+ // If |process_type| is empty then this may be a Browser process, or the
+ // main ContextProvider process. Browser processes will have a
+ // |context_channel| set
+ if (!context_channel)
+ return webrunner::ContextProviderMain();
+ }
+
+ webrunner::WebRunnerMainDelegate delegate(std::move(context_channel));
+ content::ContentMainParams params(&delegate);
+
+ // Repeated base::CommandLine::Init() is ignored, so it's safe to pass null
+ // args here.
+ params.argc = 0;
+ params.argv = nullptr;
+
+ return content::ContentMain(params);
+}
diff --git a/chromium/fuchsia/service/webrunner_main_delegate.cc b/chromium/fuchsia/service/webrunner_main_delegate.cc
new file mode 100644
index 00000000000..361711460b1
--- /dev/null
+++ b/chromium/fuchsia/service/webrunner_main_delegate.cc
@@ -0,0 +1,101 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/service/webrunner_main_delegate.h"
+
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "content/public/common/content_switches.h"
+#include "fuchsia/browser/webrunner_browser_main.h"
+#include "fuchsia/browser/webrunner_content_browser_client.h"
+#include "fuchsia/common/webrunner_content_client.h"
+#include "fuchsia/renderer/webrunner_content_renderer_client.h"
+#include "fuchsia/service/common.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace webrunner {
+namespace {
+
+WebRunnerMainDelegate* g_current_webrunner_main_delegate = nullptr;
+
+void InitLoggingFromCommandLine(const base::CommandLine& command_line) {
+ base::FilePath log_filename;
+ std::string filename = command_line.GetSwitchValueASCII(switches::kLogFile);
+ if (filename.empty()) {
+ base::PathService::Get(base::DIR_EXE, &log_filename);
+ log_filename = log_filename.AppendASCII("webrunner.log");
+ } else {
+ log_filename = base::FilePath::FromUTF8Unsafe(filename);
+ }
+
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = log_filename.value().c_str();
+ settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+ logging::InitLogging(settings);
+ logging::SetLogItems(true /* Process ID */, true /* Thread ID */,
+ true /* Timestamp */, false /* Tick count */);
+}
+
+void InitializeResourceBundle() {
+ base::FilePath pak_file;
+ bool result = base::PathService::Get(base::DIR_ASSETS, &pak_file);
+ DCHECK(result);
+ pak_file = pak_file.Append(FILE_PATH_LITERAL("webrunner.pak"));
+ ui::ResourceBundle::InitSharedInstanceWithPakPath(pak_file);
+}
+
+} // namespace
+
+// static
+WebRunnerMainDelegate* WebRunnerMainDelegate::GetInstanceForTest() {
+ return g_current_webrunner_main_delegate;
+}
+
+WebRunnerMainDelegate::WebRunnerMainDelegate(zx::channel context_channel)
+ : context_channel_(std::move(context_channel)) {
+ g_current_webrunner_main_delegate = this;
+}
+
+WebRunnerMainDelegate::~WebRunnerMainDelegate() = default;
+
+bool WebRunnerMainDelegate::BasicStartupComplete(int* exit_code) {
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ InitLoggingFromCommandLine(*command_line);
+ content_client_ = std::make_unique<WebRunnerContentClient>();
+ SetContentClient(content_client_.get());
+ return false;
+}
+
+void WebRunnerMainDelegate::PreSandboxStartup() {
+ InitializeResourceBundle();
+}
+
+int WebRunnerMainDelegate::RunProcess(
+ const std::string& process_type,
+ const content::MainFunctionParams& main_function_params) {
+ if (!process_type.empty())
+ return -1;
+
+ return WebRunnerBrowserMain(main_function_params);
+}
+
+content::ContentBrowserClient*
+WebRunnerMainDelegate::CreateContentBrowserClient() {
+ DCHECK(!browser_client_);
+ browser_client_ = std::make_unique<WebRunnerContentBrowserClient>(
+ std::move(context_channel_));
+ return browser_client_.get();
+}
+
+content::ContentRendererClient*
+WebRunnerMainDelegate::CreateContentRendererClient() {
+ renderer_client_ = std::make_unique<WebRunnerContentRendererClient>();
+ return renderer_client_.get();
+}
+
+} // namespace webrunner
diff --git a/chromium/fuchsia/service/webrunner_main_delegate.h b/chromium/fuchsia/service/webrunner_main_delegate.h
new file mode 100644
index 00000000000..c430930ca40
--- /dev/null
+++ b/chromium/fuchsia/service/webrunner_main_delegate.h
@@ -0,0 +1,58 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
+#define FUCHSIA_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
+
+#include <lib/zx/channel.h>
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "content/public/app/content_main_delegate.h"
+#include "fuchsia/common/fuchsia_export.h"
+
+namespace content {
+class ContentClient;
+} // namespace content
+
+namespace webrunner {
+
+class WebRunnerContentBrowserClient;
+class WebRunnerContentRendererClient;
+
+class FUCHSIA_EXPORT WebRunnerMainDelegate
+ : public content::ContentMainDelegate {
+ public:
+ explicit WebRunnerMainDelegate(zx::channel context_channel);
+ ~WebRunnerMainDelegate() override;
+
+ static WebRunnerMainDelegate* GetInstanceForTest();
+
+ WebRunnerContentBrowserClient* browser_client() {
+ return browser_client_.get();
+ }
+
+ // ContentMainDelegate implementation.
+ bool BasicStartupComplete(int* exit_code) override;
+ void PreSandboxStartup() override;
+ int RunProcess(
+ const std::string& process_type,
+ const content::MainFunctionParams& main_function_params) override;
+ content::ContentBrowserClient* CreateContentBrowserClient() override;
+ content::ContentRendererClient* CreateContentRendererClient() override;
+
+ private:
+ std::unique_ptr<content::ContentClient> content_client_;
+ std::unique_ptr<WebRunnerContentBrowserClient> browser_client_;
+ std::unique_ptr<WebRunnerContentRendererClient> renderer_client_;
+
+ zx::channel context_channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebRunnerMainDelegate);
+};
+
+} // namespace webrunner
+
+#endif // FUCHSIA_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_