diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-13 16:23:34 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-14 10:37:21 +0000 |
commit | 38a9a29f4f9436cace7f0e7abf9c586057df8a4e (patch) | |
tree | c4e8c458dc595bc0ddb435708fa2229edfd00bd4 /chromium/fuchsia | |
parent | e684a3455bcc29a6e3e66a004e352dea4e1141e7 (diff) | |
download | qtwebengine-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')
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_ |