| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/web/cpp/fidl.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| |
| #include "base/containers/contains.h" |
| #include "base/fuchsia/file_utils.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "fuchsia_web/common/test/fit_adapter.h" |
| #include "fuchsia_web/common/test/frame_test_util.h" |
| #include "fuchsia_web/common/test/test_debug_listener.h" |
| #include "fuchsia_web/common/test/test_devtools_list_fetcher.h" |
| #include "fuchsia_web/common/test/test_navigation_listener.h" |
| #include "fuchsia_web/webengine/test/context_provider_for_test.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| const char kTestServerRoot[] = "fuchsia_web/webengine/test/data"; |
| |
| } // namespace |
| |
| class WebEngineDebugIntegrationTest : public testing::Test { |
| public: |
| WebEngineDebugIntegrationTest() |
| : web_context_provider_(ContextProviderForDebugTest::Create( |
| base::CommandLine(base::CommandLine::NO_PROGRAM))), |
| dev_tools_listener_binding_(&dev_tools_listener_) { |
| web_context_provider_.ptr().set_error_handler( |
| [](zx_status_t status) { FAIL() << zx_status_get_string(status); }); |
| } |
| |
| WebEngineDebugIntegrationTest(const WebEngineDebugIntegrationTest&) = delete; |
| WebEngineDebugIntegrationTest& operator=( |
| const WebEngineDebugIntegrationTest&) = delete; |
| |
| ~WebEngineDebugIntegrationTest() override = default; |
| |
| void SetUp() override { |
| ASSERT_NO_FATAL_FAILURE( |
| web_context_provider_.ConnectToDebug(debug_.NewRequest())); |
| |
| // Attach the DevToolsListener. EnableDevTools has an acknowledgement |
| // callback so the listener will have been added after this call returns. |
| debug_->EnableDevTools(dev_tools_listener_binding_.NewBinding()); |
| |
| test_server_.ServeFilesFromSourceDirectory(kTestServerRoot); |
| ASSERT_TRUE(test_server_.Start()); |
| } |
| |
| protected: |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; |
| |
| ContextProviderForDebugTest web_context_provider_; |
| TestDebugListener dev_tools_listener_; |
| fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding_; |
| fuchsia::web::DebugSyncPtr debug_; |
| |
| base::OnceClosure on_url_fetch_complete_ack_; |
| |
| net::EmbeddedTestServer test_server_; |
| }; |
| |
| enum class UserModeDebugging { kEnabled = 0, kDisabled = 1 }; |
| |
| // Helper struct to intiialize all data necessary for a Context to create a |
| // Frame and navigate it to a specific URL. |
| struct TestContextAndFrame { |
| explicit TestContextAndFrame(fuchsia::web::ContextProvider* context_provider, |
| UserModeDebugging user_mode_debugging, |
| std::string url) { |
| // Create a Context, a Frame and navigate it to |url|. |
| auto directory = |
| base::OpenDirectoryHandle(base::FilePath(base::kServiceDirectoryPath)); |
| if (!directory.is_valid()) |
| return; |
| |
| fuchsia::web::CreateContextParams create_params; |
| create_params.set_features(fuchsia::web::ContextFeatureFlags::NETWORK); |
| create_params.set_service_directory(std::move(directory)); |
| if (user_mode_debugging == UserModeDebugging::kEnabled) |
| create_params.set_remote_debugging_port(0); |
| context_provider->Create(std::move(create_params), context.NewRequest()); |
| context->CreateFrame(frame.NewRequest()); |
| frame->GetNavigationController(controller.NewRequest()); |
| if (!LoadUrlAndExpectResponse(controller.get(), |
| fuchsia::web::LoadUrlParams(), url)) { |
| ADD_FAILURE(); |
| context.Unbind(); |
| frame.Unbind(); |
| controller.Unbind(); |
| return; |
| } |
| } |
| |
| TestContextAndFrame(const TestContextAndFrame&) = delete; |
| TestContextAndFrame& operator=(const TestContextAndFrame&) = delete; |
| |
| ~TestContextAndFrame() = default; |
| |
| fuchsia::web::ContextPtr context; |
| fuchsia::web::FramePtr frame; |
| fuchsia::web::NavigationControllerPtr controller; |
| }; |
| |
| // Test the Debug service is properly started and accessible. |
| TEST_F(WebEngineDebugIntegrationTest, DebugService) { |
| std::string url = test_server_.GetURL("/title1.html").spec(); |
| TestContextAndFrame frame_data(web_context_provider_.get(), |
| UserModeDebugging::kDisabled, url); |
| ASSERT_TRUE(frame_data.context); |
| |
| // Test the debug information is correct. |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(1u)); |
| |
| base::Value::List devtools_list = |
| GetDevToolsListFromPort(*dev_tools_listener_.debug_ports().begin()); |
| EXPECT_EQ(devtools_list.size(), 1u); |
| |
| const auto& devtools_dict = devtools_list[0].GetDict(); |
| const auto* devtools_url = devtools_dict.FindString("url"); |
| ASSERT_TRUE(devtools_url); |
| EXPECT_EQ(*devtools_url, url); |
| |
| const auto* devtools_title = devtools_dict.FindString("title"); |
| ASSERT_TRUE(devtools_title); |
| EXPECT_EQ(*devtools_title, "title 1"); |
| |
| // Unbind the context and wait for the listener to no longer have any active |
| // DevTools port. |
| frame_data.context.Unbind(); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(0)); |
| } |
| |
| TEST_F(WebEngineDebugIntegrationTest, MultipleDebugClients) { |
| std::string url1 = test_server_.GetURL("/title1.html").spec(); |
| TestContextAndFrame frame_data1(web_context_provider_.get(), |
| UserModeDebugging::kDisabled, url1); |
| ASSERT_TRUE(frame_data1.context); |
| |
| // Test the debug information is correct. |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(1u)); |
| uint16_t port1 = *dev_tools_listener_.debug_ports().begin(); |
| |
| base::Value::List devtools_list1 = GetDevToolsListFromPort(port1); |
| EXPECT_EQ(devtools_list1.size(), 1u); |
| |
| const auto& devtools_dict1 = devtools_list1[0].GetDict(); |
| const auto* devtools_url1 = devtools_dict1.FindString("url"); |
| ASSERT_TRUE(devtools_url1); |
| EXPECT_EQ(*devtools_url1, url1); |
| |
| const auto* devtools_title1 = devtools_dict1.FindString("title"); |
| ASSERT_TRUE(devtools_title1); |
| EXPECT_EQ(*devtools_title1, "title 1"); |
| |
| // Connect a second Debug interface. |
| fuchsia::web::DebugSyncPtr debug2; |
| ASSERT_NO_FATAL_FAILURE( |
| web_context_provider_.ConnectToDebug(debug2.NewRequest())); |
| TestDebugListener dev_tools_listener2; |
| fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding2( |
| &dev_tools_listener2); |
| debug2->EnableDevTools(dev_tools_listener_binding2.NewBinding()); |
| |
| // Create a second Context, a second Frame and navigate it to title2.html. |
| std::string url2 = test_server_.GetURL("/title2.html").spec(); |
| TestContextAndFrame frame_data2(web_context_provider_.get(), |
| UserModeDebugging::kDisabled, url2); |
| ASSERT_TRUE(frame_data2.context); |
| |
| // Ensure each DevTools listener has the right information. |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(2u)); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener2.RunUntilNumberOfPortsIs(1u)); |
| |
| uint16_t port2 = *dev_tools_listener2.debug_ports().begin(); |
| ASSERT_NE(port1, port2); |
| ASSERT_TRUE(base::Contains(dev_tools_listener_.debug_ports(), port2)); |
| |
| base::Value::List devtools_list2 = GetDevToolsListFromPort(port2); |
| EXPECT_EQ(devtools_list2.size(), 1u); |
| |
| const auto& devtools_dict2 = devtools_list2[0].GetDict(); |
| const auto* devtools_url2 = devtools_dict2.FindString("url"); |
| ASSERT_TRUE(devtools_url2); |
| EXPECT_EQ(*devtools_url2, url2); |
| |
| const auto* devtools_title2 = devtools_dict2.FindString("title"); |
| ASSERT_TRUE(devtools_title2); |
| EXPECT_EQ(*devtools_title2, "title 2"); |
| |
| // Unbind the first Context, each listener should still have one open port. |
| frame_data1.context.Unbind(); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(1u)); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener2.RunUntilNumberOfPortsIs(1u)); |
| |
| // Unbind the second Context, no listener should have any open port. |
| frame_data2.context.Unbind(); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(0)); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener2.RunUntilNumberOfPortsIs(0)); |
| } |
| |
| // Test the Debug service is accessible when the User service is requested. |
| TEST_F(WebEngineDebugIntegrationTest, DebugAndUserService) { |
| std::string url = test_server_.GetURL("/title1.html").spec(); |
| TestContextAndFrame frame_data(web_context_provider_.get(), |
| UserModeDebugging::kEnabled, url); |
| ASSERT_TRUE(frame_data.context); |
| |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(1u)); |
| |
| // Check we are getting the same port on both the debug and user APIs. |
| base::test::TestFuture<fuchsia::web::Context_GetRemoteDebuggingPort_Result> |
| port_receiver; |
| frame_data.context->GetRemoteDebuggingPort( |
| CallbackToFitFunction(port_receiver.GetCallback())); |
| ASSERT_TRUE(port_receiver.Wait()); |
| |
| ASSERT_TRUE(port_receiver.Get().is_response()); |
| uint16_t remote_debugging_port = port_receiver.Get().response().port; |
| ASSERT_EQ(remote_debugging_port, *dev_tools_listener_.debug_ports().begin()); |
| |
| // Test the debug information is correct. |
| base::Value::List devtools_list = |
| GetDevToolsListFromPort(remote_debugging_port); |
| EXPECT_EQ(devtools_list.size(), 1u); |
| |
| const auto& devtools_dict = devtools_list[0].GetDict(); |
| const auto* devtools_url = devtools_dict.FindString("url"); |
| ASSERT_TRUE(devtools_url); |
| EXPECT_EQ(*devtools_url, url); |
| |
| const auto* devtools_title = devtools_dict.FindString("title"); |
| ASSERT_TRUE(devtools_title); |
| EXPECT_EQ(*devtools_title, "title 1"); |
| |
| // Unbind the context and wait for the listener to no longer have any active |
| // DevTools port. |
| frame_data.context.Unbind(); |
| ASSERT_NO_FATAL_FAILURE(dev_tools_listener_.RunUntilNumberOfPortsIs(0)); |
| } |