| // 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/camera3/cpp/fidl.h> |
| #include <fuchsia/legacymetrics/cpp/fidl.h> |
| #include <fuchsia/legacymetrics/cpp/fidl_test_base.h> |
| #include <fuchsia/media/cpp/fidl.h> |
| #include <fuchsia/modular/cpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| #include <lib/zx/channel.h> |
| #include <zircon/processargs.h> |
| |
| #include "base/base_paths_fuchsia.h" |
| #include "base/callback_helpers.h" |
| #include "base/files/file_util.h" |
| #include "base/fuchsia/file_utils.h" |
| #include "base/fuchsia/filtered_service_directory.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/fuchsia/scoped_service_binding.h" |
| #include "base/fuchsia/test_component_controller.h" |
| #include "base/path_service.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_piece.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_run_loop_timeout.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "fuchsia/base/agent_impl.h" |
| #include "fuchsia/base/context_provider_test_connector.h" |
| #include "fuchsia/base/fake_component_context.h" |
| #include "fuchsia/base/fit_adapter.h" |
| #include "fuchsia/base/frame_test_util.h" |
| #include "fuchsia/base/mem_buffer_util.h" |
| #include "fuchsia/base/result_receiver.h" |
| #include "fuchsia/base/string_util.h" |
| #include "fuchsia/base/test_devtools_list_fetcher.h" |
| #include "fuchsia/base/url_request_rewrite_test_util.h" |
| #include "fuchsia/runners/cast/cast_runner.h" |
| #include "fuchsia/runners/cast/cast_runner_switches.h" |
| #include "fuchsia/runners/cast/fake_api_bindings.h" |
| #include "fuchsia/runners/cast/fake_application_config_manager.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| constexpr char kTestAppId[] = "00000000"; |
| constexpr char kSecondTestAppId[] = "FFFFFFFF"; |
| |
| constexpr char kBlankAppUrl[] = "/defaultresponse"; |
| constexpr char kEchoHeaderPath[] = "/echoheader?Test"; |
| |
| constexpr char kTestServerRoot[] = "fuchsia/runners/cast/testdata"; |
| |
| constexpr char kDummyAgentUrl[] = |
| "fuchsia-pkg://fuchsia.com/dummy_agent#meta/dummy_agent.cmx"; |
| |
| constexpr char kEnableFrameHostComponent[] = "enable-frame-host-component"; |
| |
| class FakeCorsExemptHeaderProvider |
| : public chromium::cast::CorsExemptHeaderProvider { |
| public: |
| FakeCorsExemptHeaderProvider() = default; |
| ~FakeCorsExemptHeaderProvider() final = default; |
| |
| FakeCorsExemptHeaderProvider(const FakeCorsExemptHeaderProvider&) = delete; |
| FakeCorsExemptHeaderProvider& operator=(const FakeCorsExemptHeaderProvider&) = |
| delete; |
| |
| private: |
| void GetCorsExemptHeaderNames( |
| GetCorsExemptHeaderNamesCallback callback) final { |
| callback({cr_fuchsia::StringToBytes("Test")}); |
| } |
| }; |
| |
| class FakeUrlRequestRewriteRulesProvider |
| : public chromium::cast::UrlRequestRewriteRulesProvider { |
| public: |
| FakeUrlRequestRewriteRulesProvider() = default; |
| ~FakeUrlRequestRewriteRulesProvider() final = default; |
| |
| FakeUrlRequestRewriteRulesProvider( |
| const FakeUrlRequestRewriteRulesProvider&) = delete; |
| FakeUrlRequestRewriteRulesProvider& operator=( |
| const FakeUrlRequestRewriteRulesProvider&) = delete; |
| |
| private: |
| void GetUrlRequestRewriteRules( |
| GetUrlRequestRewriteRulesCallback callback) final { |
| // Only send the rules once. They do not expire |
| if (rules_sent_) |
| return; |
| rules_sent_ = true; |
| |
| std::vector<fuchsia::web::UrlRequestRewrite> rewrites; |
| rewrites.push_back( |
| cr_fuchsia::CreateRewriteAddHeaders("Test", "TestHeaderValue")); |
| fuchsia::web::UrlRequestRewriteRule rule; |
| rule.set_rewrites(std::move(rewrites)); |
| std::vector<fuchsia::web::UrlRequestRewriteRule> rules; |
| rules.push_back(std::move(rule)); |
| callback(std::move(rules)); |
| } |
| |
| bool rules_sent_ = false; |
| }; |
| |
| class FakeApplicationContext : public chromium::cast::ApplicationContext { |
| public: |
| FakeApplicationContext() = default; |
| ~FakeApplicationContext() final = default; |
| |
| FakeApplicationContext(const FakeApplicationContext&) = delete; |
| FakeApplicationContext& operator=(const FakeApplicationContext&) = delete; |
| |
| chromium::cast::ApplicationController* controller() { |
| if (!controller_) |
| return nullptr; |
| |
| return controller_.get(); |
| } |
| |
| base::Optional<int64_t> WaitForApplicationTerminated() { |
| base::RunLoop loop; |
| on_application_terminated_ = loop.QuitClosure(); |
| loop.Run(); |
| return application_exit_code_; |
| } |
| |
| private: |
| // chromium::cast::ApplicationContext implementation. |
| void GetMediaSessionId(GetMediaSessionIdCallback callback) final { |
| callback(0); |
| } |
| void SetApplicationController( |
| fidl::InterfaceHandle<chromium::cast::ApplicationController> controller) |
| final { |
| controller_ = controller.Bind(); |
| } |
| void OnApplicationExit(int64_t exit_code) final { |
| application_exit_code_ = exit_code; |
| if (on_application_terminated_) |
| std::move(on_application_terminated_).Run(); |
| } |
| |
| chromium::cast::ApplicationControllerPtr controller_; |
| |
| base::Optional<int64_t> application_exit_code_; |
| base::OnceClosure on_application_terminated_; |
| }; |
| |
| class FakeComponentState : public cr_fuchsia::AgentImpl::ComponentStateBase { |
| public: |
| FakeComponentState( |
| base::StringPiece component_url, |
| chromium::cast::ApplicationConfigManager* app_config_manager, |
| chromium::cast::ApiBindings* bindings_manager, |
| chromium::cast::UrlRequestRewriteRulesProvider* |
| url_request_rules_provider) |
| : ComponentStateBase(component_url), |
| app_config_binding_(outgoing_directory(), app_config_manager), |
| bindings_manager_binding_(outgoing_directory(), bindings_manager), |
| context_binding_(outgoing_directory(), &application_context_) { |
| if (url_request_rules_provider) { |
| url_request_rules_provider_binding_.emplace(outgoing_directory(), |
| url_request_rules_provider); |
| } |
| } |
| |
| ~FakeComponentState() override { |
| if (on_delete_) |
| std::move(on_delete_).Run(); |
| } |
| FakeComponentState(const FakeComponentState&) = delete; |
| FakeComponentState& operator=(const FakeComponentState&) = delete; |
| |
| // Make outgoing_directory() public. |
| using ComponentStateBase::outgoing_directory; |
| |
| FakeApplicationContext* application_context() { |
| return &application_context_; |
| } |
| |
| void set_on_delete(base::OnceClosure on_delete) { |
| on_delete_ = std::move(on_delete); |
| } |
| |
| void Disconnect() { DisconnectClientsAndTeardown(); } |
| |
| bool api_bindings_has_clients() { |
| return bindings_manager_binding_.has_clients(); |
| } |
| |
| bool url_request_rules_provider_has_clients() { |
| if (url_request_rules_provider_binding_) { |
| return url_request_rules_provider_binding_->has_clients(); |
| } |
| return false; |
| } |
| |
| protected: |
| const base::ScopedServiceBinding<chromium::cast::ApplicationConfigManager> |
| app_config_binding_; |
| const base::ScopedServiceBinding<chromium::cast::ApiBindings> |
| bindings_manager_binding_; |
| base::Optional<base::ScopedServiceBinding< |
| chromium::cast::UrlRequestRewriteRulesProvider>> |
| url_request_rules_provider_binding_; |
| |
| FakeApplicationContext application_context_; |
| const base::ScopedServiceBinding<chromium::cast::ApplicationContext> |
| context_binding_; |
| base::OnceClosure on_delete_; |
| }; |
| |
| class TestCastComponent { |
| public: |
| TestCastComponent(fuchsia::sys::Runner* cast_runner) |
| : app_config_manager_binding_(&component_services_, &app_config_manager_), |
| cast_runner_(cast_runner) { |
| DCHECK(cast_runner_); |
| } |
| |
| ~TestCastComponent() { |
| if (component_controller_) |
| ShutdownComponent(); |
| } |
| |
| TestCastComponent(const TestCastComponent&) = delete; |
| TestCastComponent& operator=(const FakeComponentState&) = delete; |
| |
| void CreateComponentContextAndStartComponent( |
| base::StringPiece app_id = kTestAppId) { |
| auto component_url = base::StrCat({"cast:", app_id}); |
| InjectQueryApi(); |
| CreateComponentContext(component_url); |
| StartCastComponent(component_url); |
| WaitComponentStateCreated(); |
| WaitQueryApiConnected(); |
| } |
| |
| void CreateComponentContext(const base::StringPiece& component_url) { |
| url_request_rewrite_rules_provider_ = |
| std::make_unique<FakeUrlRequestRewriteRulesProvider>(); |
| component_context_ = std::make_unique<cr_fuchsia::FakeComponentContext>( |
| &component_services_, component_url); |
| component_context_->RegisterCreateComponentStateCallback( |
| FakeApplicationConfigManager::kFakeAgentUrl, |
| base::BindRepeating(&TestCastComponent::OnComponentConnect, |
| base::Unretained(this))); |
| } |
| |
| void StartCastComponent(base::StringPiece component_url) { |
| // Configure the Runner, including a service directory channel to publish |
| // services to. |
| fuchsia::sys::StartupInfo startup_info; |
| startup_info.launch_info.url = std::string(component_url); |
| |
| fidl::InterfaceHandle<fuchsia::io::Directory> outgoing_directory; |
| startup_info.launch_info.directory_request = |
| outgoing_directory.NewRequest().TakeChannel(); |
| |
| fidl::InterfaceHandle<fuchsia::io::Directory> svc_directory; |
| CHECK_EQ(fdio_service_connect_at( |
| outgoing_directory.channel().get(), "svc", |
| svc_directory.NewRequest().TakeChannel().release()), |
| ZX_OK); |
| |
| component_services_client_ = |
| std::make_unique<sys::ServiceDirectory>(std::move(svc_directory)); |
| |
| // Populate |component_services_| with services for the component to use. |
| fidl::InterfaceHandle<fuchsia::io::Directory> directory; |
| component_services_.GetOrCreateDirectory("svc")->Serve( |
| fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE, |
| directory.NewRequest().TakeChannel()); |
| component_services_.AddPublicService( |
| cors_exempt_header_provider_binding_.GetHandler( |
| &cors_exempt_header_provider_)); |
| |
| // Provide the directory of services in the |flat_namespace|. |
| startup_info.flat_namespace.paths.emplace_back(base::kServiceDirectoryPath); |
| startup_info.flat_namespace.directories.emplace_back( |
| directory.TakeChannel()); |
| |
| fuchsia::sys::Package package; |
| package.resolved_url = std::string(component_url); |
| |
| cast_runner_->StartComponent(std::move(package), std::move(startup_info), |
| component_controller_.ptr().NewRequest()); |
| component_controller_.ptr().set_error_handler([](zx_status_t status) { |
| ZX_LOG(ERROR, status) << "Component launch failed"; |
| ADD_FAILURE(); |
| }); |
| } |
| |
| void RegisterAppWithTestData(GURL url) { |
| fuchsia::web::ContentDirectoryProvider provider; |
| provider.set_name("testdata"); |
| base::FilePath pkg_path; |
| CHECK(base::PathService::Get(base::DIR_ASSETS, &pkg_path)); |
| provider.set_directory(base::OpenDirectoryHandle( |
| pkg_path.AppendASCII("fuchsia/runners/cast/testdata"))); |
| std::vector<fuchsia::web::ContentDirectoryProvider> providers; |
| providers.emplace_back(std::move(provider)); |
| |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, url); |
| app_config.set_content_directories_for_isolated_application( |
| std::move(providers)); |
| app_config_manager_.AddAppConfig(std::move(app_config)); |
| } |
| |
| // Executes |code| in the context of the test application and then returns |
| // the result serialized as string. If the code evaluates to a promise then |
| // execution is blocked until the promise is complete and the result of the |
| // promise is returned. |
| std::string ExecuteJavaScript(const std::string& code) { |
| fuchsia::web::WebMessage message; |
| message.set_data(cr_fuchsia::MemBufferFromString(code, "test-msg")); |
| test_port_->PostMessage( |
| std::move(message), |
| [](fuchsia::web::MessagePort_PostMessage_Result result) { |
| EXPECT_TRUE(result.is_response()); |
| }); |
| |
| base::RunLoop response_loop; |
| cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> response( |
| response_loop.QuitClosure()); |
| test_port_->ReceiveMessage( |
| cr_fuchsia::CallbackToFitFunction(response.GetReceiveCallback())); |
| response_loop.Run(); |
| |
| std::string response_string; |
| EXPECT_TRUE( |
| cr_fuchsia::StringFromMemBuffer(response->data(), &response_string)); |
| |
| return response_string; |
| } |
| |
| void CheckAppUrl(const GURL& app_url) { |
| EXPECT_EQ(ExecuteJavaScript("window.location.href"), app_url.spec()); |
| } |
| |
| void ShutdownComponent() { |
| DCHECK(component_controller_); |
| |
| if (component_state_) { |
| base::RunLoop run_loop; |
| component_state_->set_on_delete(run_loop.QuitClosure()); |
| component_controller_.ptr().Unbind(); |
| run_loop.Run(); |
| } |
| } |
| |
| void ExpectControllerDisconnectWithStatus(zx_status_t expected_status) { |
| DCHECK(component_controller_); |
| |
| base::RunLoop loop; |
| component_controller_.ptr().set_error_handler( |
| [&loop, expected_status](zx_status_t status) { |
| loop.Quit(); |
| EXPECT_EQ(expected_status, status); |
| }); |
| |
| loop.Run(); |
| } |
| |
| void WaitForComponentDestroyed() { |
| ASSERT_TRUE(component_state_); |
| base::RunLoop state_loop; |
| component_state_->set_on_delete(state_loop.QuitClosure()); |
| |
| if (component_controller_) |
| ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED); |
| |
| state_loop.Run(); |
| |
| ResetComponentState(); |
| } |
| |
| void ResetComponentState() { |
| component_context_ = nullptr; |
| component_services_client_ = nullptr; |
| component_state_ = nullptr; |
| test_port_ = nullptr; |
| } |
| |
| FakeApplicationConfigManager* app_config_manager() { |
| return &app_config_manager_; |
| } |
| FakeApiBindingsImpl* api_bindings() { return &api_bindings_; } |
| cr_fuchsia::FakeComponentContext* component_context() { |
| return component_context_.get(); |
| } |
| base::TestComponentController* component_controller() { |
| return &component_controller_; |
| } |
| sys::OutgoingDirectory* component_services() { return &component_services_; } |
| sys::ServiceDirectory* component_services_client() { |
| return component_services_client_.get(); |
| } |
| FakeComponentState* component_state() { return component_state_; } |
| |
| private: |
| void InjectQueryApi() { |
| // Inject an API which can be used to evaluate arbitrary Javascript and |
| // return the results over a MessagePort. |
| std::vector<chromium::cast::ApiBinding> binding_list; |
| chromium::cast::ApiBinding eval_js_binding; |
| eval_js_binding.set_before_load_script(cr_fuchsia::MemBufferFromString( |
| "function valueOrUndefinedString(value) {" |
| " return (typeof(value) == 'undefined') ? 'undefined' : value;" |
| "}" |
| "window.addEventListener('DOMContentLoaded', (event) => {" |
| " var port = cast.__platform__.PortConnector.bind('testport');" |
| " port.onmessage = (e) => {" |
| " var result = eval(e.data);" |
| " if (result && typeof(result.then) == 'function') {" |
| " result" |
| " .then(result =>" |
| " port.postMessage(valueOrUndefinedString(result)))" |
| " .catch(e => port.postMessage(JSON.stringify(e)));" |
| " } else {" |
| " port.postMessage(valueOrUndefinedString(result));" |
| " }" |
| " };" |
| "});", |
| "test")); |
| binding_list.emplace_back(std::move(eval_js_binding)); |
| api_bindings_.set_bindings(std::move(binding_list)); |
| } |
| |
| void WaitQueryApiConnected() { |
| CHECK(!test_port_); |
| test_port_ = api_bindings_.RunAndReturnConnectedPort("testport").Bind(); |
| } |
| |
| void WaitComponentStateCreated() { |
| base::RunLoop run_loop; |
| component_state_created_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> OnComponentConnect( |
| base::StringPiece component_url) { |
| auto component_state = std::make_unique<FakeComponentState>( |
| component_url, &app_config_manager_, &api_bindings_, |
| url_request_rewrite_rules_provider_.get()); |
| component_state_ = component_state.get(); |
| |
| if (component_state_created_callback_) |
| std::move(component_state_created_callback_).Run(); |
| |
| return component_state; |
| } |
| |
| FakeApplicationConfigManager app_config_manager_; |
| FakeApiBindingsImpl api_bindings_; |
| std::unique_ptr<FakeUrlRequestRewriteRulesProvider> |
| url_request_rewrite_rules_provider_; |
| |
| FakeCorsExemptHeaderProvider cors_exempt_header_provider_; |
| fidl::BindingSet<chromium::cast::CorsExemptHeaderProvider> |
| cors_exempt_header_provider_binding_; |
| |
| // Incoming service directory, ComponentContext and per-component state. |
| sys::OutgoingDirectory component_services_; |
| base::ScopedServiceBinding<chromium::cast::ApplicationConfigManager> |
| app_config_manager_binding_; |
| std::unique_ptr<cr_fuchsia::FakeComponentContext> component_context_; |
| base::TestComponentController component_controller_; |
| std::unique_ptr<sys::ServiceDirectory> component_services_client_; |
| FakeComponentState* component_state_ = nullptr; |
| fuchsia::web::MessagePortPtr test_port_; |
| |
| base::OnceClosure component_state_created_callback_; |
| fuchsia::sys::Runner* cast_runner_; |
| }; |
| |
| enum CastRunnerFeatures { |
| kCastRunnerFeaturesNone = 0, |
| kCastRunnerFeaturesHeadless = 1, |
| kCastRunnerFeaturesVulkan = 1 << 1, |
| kCastRunnerFeaturesFrameHost = 1 << 2 |
| }; |
| |
| sys::ServiceDirectory StartCastRunner( |
| fidl::InterfaceHandle<fuchsia::io::Directory> web_engine_host_directory, |
| CastRunnerFeatures runner_features, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> |
| component_controller_request) { |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = |
| "fuchsia-pkg://fuchsia.com/cast_runner#meta/cast_runner.cmx"; |
| |
| // Clone stderr from the current process to CastRunner and ask it to |
| // redirect all logs to stderr. |
| launch_info.err = fuchsia::sys::FileDescriptor::New(); |
| launch_info.err->type0 = PA_FD; |
| zx_status_t status = fdio_fd_clone( |
| STDERR_FILENO, launch_info.err->handle0.reset_and_get_address()); |
| ZX_CHECK(status == ZX_OK, status); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitchASCII("enable-logging", "stderr"); |
| |
| if (runner_features & kCastRunnerFeaturesHeadless) |
| command_line.AppendSwitch(kForceHeadlessForTestsSwitch); |
| if (!(runner_features & kCastRunnerFeaturesVulkan)) |
| command_line.AppendSwitch(kDisableVulkanForTestsSwitch); |
| if (runner_features & kCastRunnerFeaturesFrameHost) |
| command_line.AppendSwitch(kEnableFrameHostComponent); |
| |
| // Add all switches and arguments, skipping the program. |
| launch_info.arguments.emplace(std::vector<std::string>( |
| command_line.argv().begin() + 1, command_line.argv().end())); |
| |
| fidl::InterfaceHandle<fuchsia::io::Directory> cast_runner_services_dir; |
| launch_info.directory_request = |
| cast_runner_services_dir.NewRequest().TakeChannel(); |
| |
| // Redirect ContextProvider to |web_engine_host_directory|. |
| launch_info.additional_services = |
| std::make_unique<fuchsia::sys::ServiceList>(); |
| launch_info.additional_services->host_directory = |
| web_engine_host_directory.TakeChannel(); |
| launch_info.additional_services->names.push_back( |
| fuchsia::web::ContextProvider::Name_); |
| |
| fuchsia::sys::LauncherPtr launcher; |
| base::ComponentContextForProcess()->svc()->Connect(launcher.NewRequest()); |
| launcher->CreateComponent(std::move(launch_info), |
| std::move(component_controller_request)); |
| return sys::ServiceDirectory(std::move(cast_runner_services_dir)); |
| } |
| |
| } // namespace |
| |
| class CastRunnerIntegrationTest : public testing::Test { |
| public: |
| CastRunnerIntegrationTest() |
| : CastRunnerIntegrationTest(kCastRunnerFeaturesNone) {} |
| |
| CastRunnerIntegrationTest(const CastRunnerIntegrationTest&) = delete; |
| CastRunnerIntegrationTest& operator=(const CastRunnerIntegrationTest&) = |
| delete; |
| |
| void TearDown() override { |
| // Unbind the Runner channel, to prevent it from triggering an error when |
| // the CastRunner and WebEngine are torn down. |
| cast_runner_.Unbind(); |
| } |
| |
| protected: |
| explicit CastRunnerIntegrationTest(CastRunnerFeatures runner_features) { |
| StartAndPublishWebEngine(); |
| |
| // Start CastRunner. |
| fidl::InterfaceHandle<::fuchsia::io::Directory> incoming_services; |
| services_for_cast_runner_.GetOrCreateDirectory("svc")->Serve( |
| ::fuchsia::io::OPEN_RIGHT_READABLE | ::fuchsia::io::OPEN_RIGHT_WRITABLE, |
| incoming_services.NewRequest().TakeChannel()); |
| sys::ServiceDirectory cast_runner_services = |
| StartCastRunner(std::move(incoming_services), runner_features, |
| cast_runner_controller_.ptr().NewRequest()); |
| |
| // Connect to the CastRunner's fuchsia.sys.Runner interface. |
| cast_runner_ = cast_runner_services.Connect<fuchsia::sys::Runner>(); |
| cast_runner_.set_error_handler([](zx_status_t status) { |
| ZX_LOG(ERROR, status) << "CastRunner closed channel."; |
| ADD_FAILURE(); |
| }); |
| |
| test_server_.ServeFilesFromSourceDirectory(kTestServerRoot); |
| net::test_server::RegisterDefaultHandlers(&test_server_); |
| EXPECT_TRUE(test_server_.Start()); |
| } |
| |
| void StartAndPublishWebEngine() { |
| fidl::InterfaceHandle<fuchsia::io::Directory> web_engine_outgoing_dir = |
| cr_fuchsia::StartWebEngineForTests( |
| web_engine_controller_.ptr().NewRequest()); |
| sys::ServiceDirectory web_engine_outgoing_services( |
| std::move(web_engine_outgoing_dir)); |
| |
| services_for_cast_runner_ |
| .RemovePublicService<fuchsia::web::ContextProvider>(); |
| services_for_cast_runner_.AddPublicService( |
| std::make_unique<vfs::Service>( |
| [web_engine_outgoing_services = |
| std::move(web_engine_outgoing_services)]( |
| zx::channel channel, async_dispatcher_t* dispatcher) { |
| web_engine_outgoing_services.Connect( |
| fuchsia::web::ContextProvider::Name_, std::move(channel)); |
| }), |
| fuchsia::web::ContextProvider::Name_); |
| } |
| |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; |
| net::EmbeddedTestServer test_server_; |
| |
| // TODO(https://crbug.com/1168538): Override the RunLoop timeout set by |
| // |task_environment_| to allow for the very high variability in web.Context |
| // launch times. |
| const base::test::ScopedRunLoopTimeout scoped_timeout_{ |
| FROM_HERE, TestTimeouts::action_max_timeout()}; |
| |
| base::TestComponentController web_engine_controller_; |
| base::TestComponentController cast_runner_controller_; |
| |
| // Directory used to publish test ContextProvider to CastRunner. Some tests |
| // restart ContextProvider, so we can't pass the services directory from |
| // ContextProvider to CastRunner directly. |
| sys::OutgoingDirectory services_for_cast_runner_; |
| |
| fuchsia::sys::RunnerPtr cast_runner_; |
| }; |
| |
| // A basic integration test ensuring a basic cast request launches the right |
| // URL in the Chromium service. |
| TEST_F(CastRunnerIntegrationTest, BasicRequest) { |
| TestCastComponent component(cast_runner_.get()); |
| |
| GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, app_url); |
| component.CreateComponentContextAndStartComponent(); |
| |
| component.CheckAppUrl(app_url); |
| } |
| |
| // Verify that the Runner can continue to be used even after its Context has |
| // crashed. Regression test for https://crbug.com/1066826). |
| // TODO(https://crbug.com/1066833): Make this a WebRunner test. |
| TEST_F(CastRunnerIntegrationTest, CanRecreateContext) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, app_url); |
| |
| // Create a Cast component and verify that it has loaded. |
| component.CreateComponentContextAndStartComponent(); |
| component.CheckAppUrl(app_url); |
| |
| // Terminate the component that provides the ContextProvider service and |
| // wait for the Cast component to terminate, without allowing the message- |
| // loop to spin in-between. |
| web_engine_controller_.ptr()->Kill(); |
| component.WaitForComponentDestroyed(); |
| |
| // Create a second Cast component and verify that it has loaded. |
| // There is no guarantee that the CastRunner has detected the old web.Context |
| // disconnecting yet, so attempts to launch Cast components could fail. |
| // WebContentRunner::CreateFrameWithParams() will synchronously verify that |
| // the web.Context is not-yet-closed, to work-around that. |
| StartAndPublishWebEngine(); |
| component.app_config_manager()->AddApp(kTestAppId, app_url); |
| component.CreateComponentContextAndStartComponent(); |
| component.CheckAppUrl(app_url); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, ApiBindings) { |
| TestCastComponent component(cast_runner_.get()); |
| component.app_config_manager()->AddApp(kTestAppId, |
| test_server_.GetURL(kBlankAppUrl)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Verify that we can communicate with the binding added in |
| // CastRunnerIntegrationTest(). |
| EXPECT_EQ(component.ExecuteJavaScript("1+2+\"\""), "3"); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) { |
| TestCastComponent component(cast_runner_.get()); |
| const char kIncorrectComponentUrl[] = "cast:99999999"; |
| |
| component.CreateComponentContext(kIncorrectComponentUrl); |
| component.StartCastComponent(kIncorrectComponentUrl); |
| |
| // Run the loop until the ComponentController is dropped. |
| component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED); |
| |
| EXPECT_TRUE(!component.component_state()); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, UrlRequestRewriteRulesProvider) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL echo_app_url = test_server_.GetURL(kEchoHeaderPath); |
| component.app_config_manager()->AddApp(kTestAppId, echo_app_url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| component.CheckAppUrl(echo_app_url); |
| |
| EXPECT_EQ(component.ExecuteJavaScript("document.body.innerText"), |
| "TestHeaderValue"); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, ApplicationControllerBound) { |
| TestCastComponent component(cast_runner_.get()); |
| component.app_config_manager()->AddApp(kTestAppId, |
| test_server_.GetURL(kBlankAppUrl)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Spin the message loop to handle creation of the component state. |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(component.component_state()); |
| EXPECT_TRUE(component.component_state()->application_context()->controller()); |
| } |
| |
| // Verify an App launched with remote debugging enabled is properly reachable. |
| TEST_F(CastRunnerIntegrationTest, RemoteDebugging) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| app_config.set_enable_remote_debugging(true); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Connect to the debug service and ensure we get the proper response. |
| base::Value devtools_list = |
| cr_fuchsia::GetDevToolsListFromPort(CastRunner::kRemoteDebuggingPort); |
| ASSERT_TRUE(devtools_list.is_list()); |
| EXPECT_EQ(devtools_list.GetList().size(), 1u); |
| |
| base::Value* devtools_url = devtools_list.GetList()[0].FindPath("url"); |
| ASSERT_TRUE(devtools_url->is_string()); |
| EXPECT_EQ(devtools_url->GetString(), app_url.spec()); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, IsolatedContext) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL kContentDirectoryUrl("fuchsia-dir://testdata/empty.html"); |
| |
| component.RegisterAppWithTestData(kContentDirectoryUrl); |
| component.CreateComponentContextAndStartComponent(); |
| component.CheckAppUrl(kContentDirectoryUrl); |
| } |
| |
| // Test the lack of CastAgent service does not cause a CastRunner crash. |
| TEST_F(CastRunnerIntegrationTest, NoCastAgent) { |
| TestCastComponent component(cast_runner_.get()); |
| component.app_config_manager()->AddApp(kTestAppId, |
| test_server_.GetURL(kEchoHeaderPath)); |
| |
| component.StartCastComponent(base::StrCat({"cast:", kTestAppId})); |
| |
| component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED); |
| } |
| |
| // Test the CastAgent disconnecting does not cause a CastRunner crash. |
| TEST_F(CastRunnerIntegrationTest, DisconnectedCastAgent) { |
| TestCastComponent component(cast_runner_.get()); |
| component.app_config_manager()->AddApp(kTestAppId, |
| test_server_.GetURL(kEchoHeaderPath)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Tear down the ComponentState, this should close the Agent connection and |
| // shut down the CastComponent. |
| component.component_state()->Disconnect(); |
| |
| component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED); |
| } |
| |
| // Test that the ApiBindings and RewriteRules are received from the secondary |
| // DummyAgent. This validates that the |agent_url| retrieved from |
| // AppConfigManager is the one used to retrieve the bindings and the rewrite |
| // rules. |
| TEST_F(CastRunnerIntegrationTest, ApplicationConfigAgentUrl) { |
| TestCastComponent component(cast_runner_.get()); |
| |
| // These are part of the secondary agent, and CastRunner will contact |
| // the secondary agent for both of them. |
| FakeUrlRequestRewriteRulesProvider dummy_url_request_rewrite_rules_provider; |
| FakeApiBindingsImpl dummy_agent_api_bindings; |
| |
| // Indicate that this app is to get bindings from a secondary agent. |
| auto app_config = FakeApplicationConfigManager::CreateConfig( |
| kTestAppId, test_server_.GetURL(kBlankAppUrl)); |
| app_config.set_agent_url(kDummyAgentUrl); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| // Instantiate the bindings that are returned in the multi-agent scenario. The |
| // bindings returned for the single-agent scenario are not initialized. |
| std::vector<chromium::cast::ApiBinding> binding_list; |
| chromium::cast::ApiBinding echo_binding; |
| echo_binding.set_before_load_script(cr_fuchsia::MemBufferFromString( |
| "window.echo = cast.__platform__.PortConnector.bind('dummyService');", |
| "test")); |
| binding_list.emplace_back(std::move(echo_binding)); |
| // Assign the bindings to the multi-agent binding. |
| dummy_agent_api_bindings.set_bindings(std::move(binding_list)); |
| |
| auto component_url = base::StrCat({"cast:", kTestAppId}); |
| component.CreateComponentContext(component_url); |
| EXPECT_TRUE(component.component_context()); |
| |
| base::RunLoop run_loop; |
| FakeComponentState* dummy_component_state = nullptr; |
| component.component_context()->RegisterCreateComponentStateCallback( |
| kDummyAgentUrl, |
| base::BindLambdaForTesting( |
| [&](base::StringPiece component_url) |
| -> std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> { |
| run_loop.Quit(); |
| auto result = std::make_unique<FakeComponentState>( |
| component_url, component.app_config_manager(), |
| &dummy_agent_api_bindings, |
| &dummy_url_request_rewrite_rules_provider); |
| dummy_component_state = result.get(); |
| return result; |
| })); |
| |
| component.StartCastComponent(component_url); |
| |
| // Wait for the component state to be created. |
| run_loop.Run(); |
| |
| // Validate that the component state in the default agent wasn't crated. |
| EXPECT_FALSE(component.component_state()); |
| |
| // Shutdown component before destroying dummy_agent_api_bindings. |
| base::RunLoop shutdown_run_loop; |
| dummy_component_state->set_on_delete(shutdown_run_loop.QuitClosure()); |
| component.component_controller()->ptr().Unbind(); |
| shutdown_run_loop.Run(); |
| } |
| |
| // Test that when RewriteRules are not provided, a WebComponent is still |
| // created. Further validate that the primary agent does not provide ApiBindings |
| // or RewriteRules. |
| TEST_F(CastRunnerIntegrationTest, ApplicationConfigAgentUrlRewriteOptional) { |
| TestCastComponent component(cast_runner_.get()); |
| FakeApiBindingsImpl dummy_agent_api_bindings; |
| |
| // Indicate that this app is to get bindings from a secondary agent. |
| auto app_config = FakeApplicationConfigManager::CreateConfig( |
| kTestAppId, test_server_.GetURL(kBlankAppUrl)); |
| app_config.set_agent_url(kDummyAgentUrl); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| // Instantiate the bindings that are returned in the multi-agent scenario. The |
| // bindings returned for the single-agent scenario are not initialized. |
| std::vector<chromium::cast::ApiBinding> binding_list; |
| chromium::cast::ApiBinding echo_binding; |
| echo_binding.set_before_load_script(cr_fuchsia::MemBufferFromString( |
| "window.echo = cast.__platform__.PortConnector.bind('dummyService');", |
| "test")); |
| binding_list.emplace_back(std::move(echo_binding)); |
| // Assign the bindings to the multi-agent binding. |
| dummy_agent_api_bindings.set_bindings(std::move(binding_list)); |
| |
| auto component_url = base::StrCat({"cast:", kTestAppId}); |
| component.CreateComponentContext(component_url); |
| base::RunLoop run_loop; |
| FakeComponentState* dummy_component_state = nullptr; |
| component.component_context()->RegisterCreateComponentStateCallback( |
| kDummyAgentUrl, |
| base::BindLambdaForTesting( |
| [&](base::StringPiece component_url) |
| -> std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> { |
| run_loop.Quit(); |
| auto result = std::make_unique<FakeComponentState>( |
| component_url, component.app_config_manager(), |
| &dummy_agent_api_bindings, nullptr); |
| dummy_component_state = result.get(); |
| return result; |
| })); |
| |
| component.StartCastComponent(component_url); |
| |
| // Wait for the component state to be created. |
| run_loop.Run(); |
| |
| // Validate that the component state in the default agent wasn't crated. |
| EXPECT_FALSE(component.component_state()); |
| |
| // Shutdown component before destroying dummy_agent_api_bindings. |
| base::RunLoop shutdown_run_loop; |
| dummy_component_state->set_on_delete(shutdown_run_loop.QuitClosure()); |
| component.component_controller()->ptr().Unbind(); |
| shutdown_run_loop.Run(); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, MicrophoneRedirect) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL("/microphone.html"); |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| |
| fuchsia::web::PermissionDescriptor mic_permission; |
| mic_permission.set_type(fuchsia::web::PermissionType::MICROPHONE); |
| app_config.mutable_permissions()->push_back(std::move(mic_permission)); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Expect fuchsia.media.Audio connection to be redirected to the agent. |
| base::RunLoop run_loop; |
| component.component_state()->outgoing_directory()->AddPublicService( |
| std::make_unique<vfs::Service>( |
| [quit_closure = run_loop.QuitClosure()]( |
| zx::channel channel, async_dispatcher_t* dispatcher) mutable { |
| std::move(quit_closure).Run(); |
| }), |
| fuchsia::media::Audio::Name_); |
| |
| component.ExecuteJavaScript("connectMicrophone();"); |
| |
| // Will quit once AudioCapturer is connected. |
| run_loop.Run(); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, CameraRedirect) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL("/camera.html"); |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| |
| fuchsia::web::PermissionDescriptor camera_permission; |
| camera_permission.set_type(fuchsia::web::PermissionType::CAMERA); |
| app_config.mutable_permissions()->push_back(std::move(camera_permission)); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Expect fuchsia.camera3.DeviceWatcher connection to be redirected to the |
| // agent. |
| bool received_device_watcher_request = false; |
| component.component_state()->outgoing_directory()->AddPublicService( |
| std::make_unique<vfs::Service>( |
| [&received_device_watcher_request]( |
| zx::channel channel, async_dispatcher_t* dispatcher) mutable { |
| received_device_watcher_request = true; |
| }), |
| fuchsia::camera3::DeviceWatcher::Name_); |
| |
| component.ExecuteJavaScript("connectCamera();"); |
| EXPECT_TRUE(received_device_watcher_request); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, CameraAccessAfterComponentShutdown) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL("/camera.html"); |
| |
| // First app with camera permission. |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| fuchsia::web::PermissionDescriptor camera_permission; |
| camera_permission.set_type(fuchsia::web::PermissionType::CAMERA); |
| app_config.mutable_permissions()->push_back(std::move(camera_permission)); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| // Second app without camera permission (but it will still try to access |
| // fuchsia.camera3.DeviceWatcher service to enumerate devices). |
| TestCastComponent second_component(cast_runner_.get()); |
| auto app_config_2 = |
| FakeApplicationConfigManager::CreateConfig(kSecondTestAppId, app_url); |
| second_component.app_config_manager()->AddAppConfig(std::move(app_config_2)); |
| |
| // Start and then shutdown the first app. |
| component.CreateComponentContextAndStartComponent(kTestAppId); |
| component.ShutdownComponent(); |
| component.ResetComponentState(); |
| |
| // Start the second app and try to connect the camera. It's expected to fail |
| // to initialize the camera without crashing CastRunner. |
| second_component.CreateComponentContextAndStartComponent(kSecondTestAppId); |
| EXPECT_EQ(second_component.ExecuteJavaScript("connectCamera();"), |
| "getUserMediaFailed"); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, MultipleComponentsUsingCamera) { |
| TestCastComponent first_component(cast_runner_.get()); |
| TestCastComponent second_component(cast_runner_.get()); |
| |
| GURL app_url = test_server_.GetURL("/camera.html"); |
| |
| // Start two apps, both with camera permission. |
| auto app_config1 = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| fuchsia::web::PermissionDescriptor camera_permission1; |
| camera_permission1.set_type(fuchsia::web::PermissionType::CAMERA); |
| app_config1.mutable_permissions()->push_back(std::move(camera_permission1)); |
| first_component.app_config_manager()->AddAppConfig(std::move(app_config1)); |
| first_component.CreateComponentContextAndStartComponent(kTestAppId); |
| |
| auto app_config2 = |
| FakeApplicationConfigManager::CreateConfig(kSecondTestAppId, app_url); |
| fuchsia::web::PermissionDescriptor camera_permission2; |
| camera_permission2.set_type(fuchsia::web::PermissionType::CAMERA); |
| app_config2.mutable_permissions()->push_back(std::move(camera_permission2)); |
| second_component.app_config_manager()->AddAppConfig(std::move(app_config2)); |
| second_component.CreateComponentContextAndStartComponent(kSecondTestAppId); |
| |
| // Shut down the first component. |
| first_component.ShutdownComponent(); |
| first_component.ResetComponentState(); |
| |
| // Expect fuchsia.camera3.DeviceWatcher connection to be redirected to the |
| // agent. |
| bool received_device_watcher_request = false; |
| second_component.component_state()->outgoing_directory()->AddPublicService( |
| std::make_unique<vfs::Service>( |
| [&received_device_watcher_request]( |
| zx::channel channel, async_dispatcher_t* dispatcher) mutable { |
| received_device_watcher_request = true; |
| }), |
| fuchsia::camera3::DeviceWatcher::Name_); |
| |
| second_component.ExecuteJavaScript("connectCamera();"); |
| EXPECT_TRUE(received_device_watcher_request); |
| } |
| |
| class HeadlessCastRunnerIntegrationTest : public CastRunnerIntegrationTest { |
| public: |
| HeadlessCastRunnerIntegrationTest() |
| : CastRunnerIntegrationTest(kCastRunnerFeaturesHeadless) {} |
| }; |
| |
| // A basic integration test ensuring a basic cast request launches the right |
| // URL in the Chromium service. |
| TEST_F(HeadlessCastRunnerIntegrationTest, Headless) { |
| TestCastComponent component(cast_runner_.get()); |
| |
| const char kAnimationPath[] = "/css_animation.html"; |
| const GURL animation_url = test_server_.GetURL(kAnimationPath); |
| component.app_config_manager()->AddApp(kTestAppId, animation_url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| auto tokens = scenic::ViewTokenPair::New(); |
| |
| // Create a view. |
| auto view_provider = component.component_services_client() |
| ->Connect<fuchsia::ui::app::ViewProvider>(); |
| view_provider->CreateView(std::move(tokens.view_holder_token.value), {}, {}); |
| |
| component.api_bindings()->RunAndReturnConnectedPort("animation_finished"); |
| |
| // Verify that dropped "view" EventPair is handled properly. |
| tokens.view_token.value.reset(); |
| component.api_bindings()->RunAndReturnConnectedPort("view_hidden"); |
| } |
| |
| // Isolated *and* headless? Doesn't sound like much fun! |
| TEST_F(HeadlessCastRunnerIntegrationTest, IsolatedAndHeadless) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL kContentDirectoryUrl("fuchsia-dir://testdata/empty.html"); |
| |
| component.RegisterAppWithTestData(kContentDirectoryUrl); |
| component.CreateComponentContextAndStartComponent(); |
| component.CheckAppUrl(kContentDirectoryUrl); |
| } |
| |
| // Verifies that the Context can establish a connection to the Agent's |
| // MetricsRecorder service. |
| TEST_F(CastRunnerIntegrationTest, LegacyMetricsRedirect) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, app_url); |
| |
| auto component_url = base::StrCat({"cast:", kTestAppId}); |
| component.CreateComponentContext(component_url); |
| EXPECT_TRUE(component.component_context()); |
| |
| base::RunLoop run_loop; |
| |
| // Add MetricsRecorder the the component's incoming_services. |
| component.component_services()->AddPublicService( |
| std::make_unique<vfs::Service>( |
| [&run_loop](zx::channel request, async_dispatcher_t* dispatcher) { |
| run_loop.Quit(); |
| }), |
| fuchsia::legacymetrics::MetricsRecorder::Name_); |
| |
| component.StartCastComponent(component_url); |
| |
| // Wait until we see the CastRunner connect to the MetricsRecorder service. |
| run_loop.Run(); |
| } |
| |
| // Verifies that the ApplicationContext::OnApplicationTerminated() is notified |
| // with the component exit code if the web content closes itself. |
| TEST_F(CastRunnerIntegrationTest, OnApplicationTerminated_WindowClose) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // It is possible to observe the ComponentController close before |
| // OnApplicationTerminated() is received, so ignore that. |
| component.component_controller()->ptr().set_error_handler([](zx_status_t) {}); |
| |
| // Have the web content close itself, and wait for OnApplicationTerminated(). |
| EXPECT_EQ(component.ExecuteJavaScript("window.close()"), "undefined"); |
| base::Optional<zx_status_t> exit_code = component.component_state() |
| ->application_context() |
| ->WaitForApplicationTerminated(); |
| ASSERT_TRUE(exit_code); |
| EXPECT_EQ(exit_code.value(), ZX_OK); |
| } |
| |
| // Verifies that the ComponentController reports TerminationReason::EXITED and |
| // exit code ZX_OK if the web content terminates itself. |
| // TODO(https://crbug.com/1066833): Make this a WebRunner test. |
| TEST_F(CastRunnerIntegrationTest, OnTerminated_WindowClose) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Register an handler on the ComponentController channel, for the |
| // OnTerminated event. |
| base::RunLoop exit_code_loop; |
| component.component_controller()->ptr().set_error_handler( |
| [quit_loop = exit_code_loop.QuitClosure()](zx_status_t) { |
| quit_loop.Run(); |
| ADD_FAILURE(); |
| }); |
| component.component_controller()->ptr().events().OnTerminated = |
| [quit_loop = exit_code_loop.QuitClosure()]( |
| int64_t exit_code, fuchsia::sys::TerminationReason reason) { |
| quit_loop.Run(); |
| EXPECT_EQ(reason, fuchsia::sys::TerminationReason::EXITED); |
| EXPECT_EQ(exit_code, ZX_OK); |
| }; |
| |
| // Have the web content close itself, and wait for OnTerminated(). |
| EXPECT_EQ(component.ExecuteJavaScript("window.close()"), "undefined"); |
| exit_code_loop.Run(); |
| |
| component.component_controller()->ptr().Unbind(); |
| } |
| |
| // Verifies that the ComponentController reports TerminationReason::EXITED and |
| // exit code ZX_OK if Kill() is used. |
| // TODO(https://crbug.com/1066833): Make this a WebRunner test. |
| TEST_F(CastRunnerIntegrationTest, OnTerminated_ComponentKill) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| // Register an handler on the ComponentController channel, for the |
| // OnTerminated event. |
| base::RunLoop exit_code_loop; |
| component.component_controller()->ptr().set_error_handler( |
| [quit_loop = exit_code_loop.QuitClosure()](zx_status_t) { |
| quit_loop.Run(); |
| ADD_FAILURE(); |
| }); |
| component.component_controller()->ptr().events().OnTerminated = |
| [quit_loop = exit_code_loop.QuitClosure()]( |
| int64_t exit_code, fuchsia::sys::TerminationReason reason) { |
| quit_loop.Run(); |
| EXPECT_EQ(reason, fuchsia::sys::TerminationReason::EXITED); |
| EXPECT_EQ(exit_code, ZX_OK); |
| }; |
| |
| // Kill() the component and wait for OnTerminated(). |
| component.component_controller()->ptr()->Kill(); |
| exit_code_loop.Run(); |
| |
| component.component_controller()->ptr().Unbind(); |
| } |
| |
| // Ensures that CastRunner handles the value not being specified. |
| // TODO(https://crrev.com/c/2516246): Check for no logging. |
| TEST_F(CastRunnerIntegrationTest, InitialMinConsoleLogSeverity_NotSet) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| |
| EXPECT_FALSE(app_config.has_initial_min_console_log_severity()); |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| component.CheckAppUrl(app_url); |
| } |
| |
| // TODO(https://crrev.com/c/2516246): Check for logging. |
| TEST_F(CastRunnerIntegrationTest, InitialMinConsoleLogSeverity_DEBUG) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| auto app_config = |
| FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url); |
| |
| *app_config.mutable_initial_min_console_log_severity() = |
| fuchsia::diagnostics::Severity::DEBUG; |
| component.app_config_manager()->AddAppConfig(std::move(app_config)); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| component.CheckAppUrl(app_url); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, WebGLContextAbsentWithoutVulkanFeature) { |
| TestCastComponent component(cast_runner_.get()); |
| const char kTestPath[] = "/webgl_presence.html"; |
| const GURL test_url = test_server_.GetURL(kTestPath); |
| component.app_config_manager()->AddApp(kTestAppId, test_url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| EXPECT_EQ(component.ExecuteJavaScript("document.title"), "absent"); |
| } |
| |
| TEST_F(CastRunnerIntegrationTest, |
| WebGLContextAbsentWithoutVulkanFeature_IsolatedRunner) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL kContentDirectoryUrl("fuchsia-dir://testdata/webgl_presence.html"); |
| |
| component.RegisterAppWithTestData(kContentDirectoryUrl); |
| component.CreateComponentContextAndStartComponent(); |
| component.CheckAppUrl(kContentDirectoryUrl); |
| |
| EXPECT_EQ(component.ExecuteJavaScript("document.title"), "absent"); |
| } |
| |
| // Verifies that starting a component fails if CORS exempt headers cannot be |
| // fetched. |
| TEST_F(CastRunnerIntegrationTest, MissingCorsExemptHeaderProvider) { |
| TestCastComponent component(cast_runner_.get()); |
| GURL app_url = test_server_.GetURL(kBlankAppUrl); |
| component.app_config_manager()->AddApp(kTestAppId, app_url); |
| |
| // Start the Cast component, and wait for the controller to disconnect. |
| component.StartCastComponent(base::StrCat({"cast:", kTestAppId})); |
| |
| component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED); |
| |
| EXPECT_FALSE(component.component_state()); |
| } |
| |
| // Verifies that CastRunner offers a chromium.cast.DataReset service. |
| // TODO(crbug.com/1146474): Expand the test to verify that the persisted data is |
| // correctly cleared (e.g. using a custom test HTML app that uses persisted |
| // data). |
| TEST_F(CastRunnerIntegrationTest, DataReset) { |
| TestCastComponent component(cast_runner_.get()); |
| constexpr char kDataResetComponentName[] = "cast:chromium.cast.DataReset"; |
| component.StartCastComponent(kDataResetComponentName); |
| |
| base::RunLoop loop; |
| auto data_reset = component.component_services_client() |
| ->Connect<chromium::cast::DataReset>(); |
| data_reset.set_error_handler([quit_loop = loop.QuitClosure()](zx_status_t) { |
| quit_loop.Run(); |
| ADD_FAILURE(); |
| }); |
| bool succeeded = false; |
| data_reset->DeletePersistentData([&succeeded, &loop](bool result) { |
| succeeded = result; |
| loop.Quit(); |
| }); |
| loop.Run(); |
| |
| EXPECT_TRUE(succeeded); |
| } |
| |
| class CastRunnerFrameHostIntegrationTest : public CastRunnerIntegrationTest { |
| public: |
| CastRunnerFrameHostIntegrationTest() |
| : CastRunnerIntegrationTest(kCastRunnerFeaturesFrameHost) {} |
| }; |
| |
| // Verifies that the CastRunner offers a fuchsia.web.FrameHost service. |
| // TODO(crbug.com/1144102): Clean up config-data vs command-line flags handling |
| // and add a not-enabled test here. |
| TEST_F(CastRunnerFrameHostIntegrationTest, FrameHostComponent) { |
| TestCastComponent component(cast_runner_.get()); |
| constexpr char kFrameHostComponentName[] = "cast:fuchsia.web.FrameHost"; |
| component.StartCastComponent(kFrameHostComponentName); |
| |
| // Connect to the fuchsia.web.FrameHost service and create a Frame. |
| auto frame_host = |
| component.component_services_client()->Connect<fuchsia::web::FrameHost>(); |
| fuchsia::web::FramePtr frame; |
| frame_host->CreateFrameWithParams(fuchsia::web::CreateFrameParams(), |
| frame.NewRequest()); |
| |
| // Verify that a response is received for a LoadUrl() request to the frame. |
| fuchsia::web::NavigationControllerPtr controller; |
| frame->GetNavigationController(controller.NewRequest()); |
| const GURL url = test_server_.GetURL(kBlankAppUrl); |
| EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse( |
| controller.get(), fuchsia::web::LoadUrlParams(), url.spec())); |
| } |
| |
| #if defined(ARCH_CPU_ARM_FAMILY) |
| // TODO(crbug.com/1058247): Support Vulkan in tests on ARM64. |
| #define MAYBE_VulkanCastRunnerIntegrationTest \ |
| DISABLED_VulkanCastRunnerIntegrationTest |
| #else |
| #define MAYBE_VulkanCastRunnerIntegrationTest VulkanCastRunnerIntegrationTest |
| #endif |
| |
| class MAYBE_VulkanCastRunnerIntegrationTest : public CastRunnerIntegrationTest { |
| public: |
| MAYBE_VulkanCastRunnerIntegrationTest() |
| : CastRunnerIntegrationTest(kCastRunnerFeaturesVulkan) {} |
| }; |
| |
| TEST_F(MAYBE_VulkanCastRunnerIntegrationTest, |
| WebGLContextPresentWithVulkanFeature) { |
| TestCastComponent component(cast_runner_.get()); |
| const char kTestPath[] = "/webgl_presence.html"; |
| const GURL test_url = test_server_.GetURL(kTestPath); |
| component.app_config_manager()->AddApp(kTestAppId, test_url); |
| |
| component.CreateComponentContextAndStartComponent(); |
| |
| EXPECT_EQ(component.ExecuteJavaScript("document.title"), "present"); |
| } |
| |
| TEST_F(MAYBE_VulkanCastRunnerIntegrationTest, |
| WebGLContextPresentWithVulkanFeature_IsolatedRunner) { |
| TestCastComponent component(cast_runner_.get()); |
| const GURL kContentDirectoryUrl("fuchsia-dir://testdata/webgl_presence.html"); |
| |
| component.RegisterAppWithTestData(kContentDirectoryUrl); |
| component.CreateComponentContextAndStartComponent(); |
| component.CheckAppUrl(kContentDirectoryUrl); |
| |
| EXPECT_EQ(component.ExecuteJavaScript("document.title"), "present"); |
| } |