web_engine_debug_integration_test.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <fuchsia/sys/cpp/fidl.h>
  5. #include <fuchsia/web/cpp/fidl.h>
  6. #include <lib/fidl/cpp/binding.h>
  7. #include <lib/fidl/cpp/binding_set.h>
  8. #include <lib/sys/cpp/component_context.h>
  9. #include "base/files/file_enumerator.h"
  10. #include "base/files/file_util.h"
  11. #include "base/fuchsia/file_utils.h"
  12. #include "base/test/task_environment.h"
  13. #include "base/test/test_future.h"
  14. #include "fuchsia_web/common/test/fit_adapter.h"
  15. #include "fuchsia_web/common/test/frame_test_util.h"
  16. #include "fuchsia_web/common/test/test_devtools_list_fetcher.h"
  17. #include "fuchsia_web/common/test/test_navigation_listener.h"
  18. #include "fuchsia_web/webengine/test/context_provider_test_connector.h"
  19. #include "fuchsia_web/webengine/test_debug_listener.h"
  20. #include "net/test/embedded_test_server/embedded_test_server.h"
  21. #include "testing/gtest/include/gtest/gtest.h"
  22. namespace {
  23. const char kTestServerRoot[] = "fuchsia_web/webengine/test/data";
  24. } // namespace
  25. class WebEngineDebugIntegrationTest : public testing::Test {
  26. public:
  27. WebEngineDebugIntegrationTest()
  28. : dev_tools_listener_binding_(&dev_tools_listener_) {}
  29. WebEngineDebugIntegrationTest(const WebEngineDebugIntegrationTest&) = delete;
  30. WebEngineDebugIntegrationTest& operator=(
  31. const WebEngineDebugIntegrationTest&) = delete;
  32. ~WebEngineDebugIntegrationTest() override = default;
  33. void SetUp() override {
  34. // Add a switch to the WebEngine instance to distinguish it from other
  35. // instances that may be started by other tests.
  36. std::string test_switch =
  37. std::string("--test-name=") +
  38. testing::UnitTest::GetInstance()->current_test_info()->name();
  39. base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  40. command_line.AppendSwitch(test_switch);
  41. web_context_provider_ = ConnectContextProvider(
  42. web_engine_controller_.NewRequest(), command_line);
  43. web_context_provider_.set_error_handler(
  44. [](zx_status_t status) { ADD_FAILURE(); });
  45. // Wait for the OnDirectoryReady event, which indicates that the component's
  46. // outgoing directory is available, including the "/debug" contents accessed
  47. // via the Hub.
  48. base::RunLoop directory_loop;
  49. web_engine_controller_.events().OnDirectoryReady =
  50. [quit_loop = directory_loop.QuitClosure()]() { quit_loop.Run(); };
  51. directory_loop.Run();
  52. // Enumerate all entries in /hub/c/context_provider.cmx to find WebEngine
  53. // instance with |test_switch|.
  54. base::FileEnumerator file_enum(
  55. base::FilePath("/hub/c/context_provider.cmx"), false,
  56. base::FileEnumerator::DIRECTORIES);
  57. base::FilePath web_engine_path;
  58. for (auto dir = file_enum.Next(); !dir.empty(); dir = file_enum.Next()) {
  59. std::string args;
  60. if (!base::ReadFileToString(dir.Append("args"), &args)) {
  61. // WebEngine may shutdown while we are enumerating the directory, so
  62. // it's safe to ignore this error.
  63. continue;
  64. }
  65. if (args.find(test_switch) != std::string::npos) {
  66. // There should only one instance of WebEngine with |test_switch|.
  67. EXPECT_TRUE(web_engine_path.empty());
  68. web_engine_path = dir;
  69. // Keep iterating to check that there are no other matching instances.
  70. }
  71. }
  72. // Check that we've found the WebEngine instance with |test_switch|.
  73. ASSERT_FALSE(web_engine_path.empty());
  74. debug_dir_ = std::make_unique<sys::ServiceDirectory>(
  75. base::OpenDirectoryHandle(web_engine_path.Append("out/debug")));
  76. debug_dir_->Connect(debug_.NewRequest());
  77. // Attach the DevToolsListener. EnableDevTools has an acknowledgement
  78. // callback so the listener will have been added after this call returns.
  79. debug_->EnableDevTools(dev_tools_listener_binding_.NewBinding());
  80. test_server_.ServeFilesFromSourceDirectory(kTestServerRoot);
  81. ASSERT_TRUE(test_server_.Start());
  82. }
  83. protected:
  84. base::test::SingleThreadTaskEnvironment task_environment_{
  85. base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
  86. TestDebugListener dev_tools_listener_;
  87. fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding_;
  88. std::unique_ptr<sys::ServiceDirectory> debug_dir_;
  89. fuchsia::web::ContextProviderPtr web_context_provider_;
  90. fuchsia::sys::ComponentControllerPtr web_engine_controller_;
  91. fuchsia::web::DebugSyncPtr debug_;
  92. base::OnceClosure on_url_fetch_complete_ack_;
  93. net::EmbeddedTestServer test_server_;
  94. };
  95. enum class UserModeDebugging { kEnabled = 0, kDisabled = 1 };
  96. // Helper struct to intiialize all data necessary for a Context to create a
  97. // Frame and navigate it to a specific URL.
  98. struct TestContextAndFrame {
  99. explicit TestContextAndFrame(fuchsia::web::ContextProvider* context_provider,
  100. UserModeDebugging user_mode_debugging,
  101. std::string url) {
  102. // Create a Context, a Frame and navigate it to |url|.
  103. auto directory =
  104. base::OpenDirectoryHandle(base::FilePath(base::kServiceDirectoryPath));
  105. if (!directory.is_valid())
  106. return;
  107. fuchsia::web::CreateContextParams create_params;
  108. create_params.set_features(fuchsia::web::ContextFeatureFlags::NETWORK);
  109. create_params.set_service_directory(std::move(directory));
  110. if (user_mode_debugging == UserModeDebugging::kEnabled)
  111. create_params.set_remote_debugging_port(0);
  112. context_provider->Create(std::move(create_params), context.NewRequest());
  113. context->CreateFrame(frame.NewRequest());
  114. frame->GetNavigationController(controller.NewRequest());
  115. if (!LoadUrlAndExpectResponse(controller.get(),
  116. fuchsia::web::LoadUrlParams(), url)) {
  117. ADD_FAILURE();
  118. context.Unbind();
  119. frame.Unbind();
  120. controller.Unbind();
  121. return;
  122. }
  123. }
  124. TestContextAndFrame(const TestContextAndFrame&) = delete;
  125. TestContextAndFrame& operator=(const TestContextAndFrame&) = delete;
  126. ~TestContextAndFrame() = default;
  127. fuchsia::web::ContextPtr context;
  128. fuchsia::web::FramePtr frame;
  129. fuchsia::web::NavigationControllerPtr controller;
  130. };
  131. // Test the Debug service is properly started and accessible.
  132. TEST_F(WebEngineDebugIntegrationTest, DebugService) {
  133. std::string url = test_server_.GetURL("/title1.html").spec();
  134. TestContextAndFrame frame_data(web_context_provider_.get(),
  135. UserModeDebugging::kDisabled, url);
  136. ASSERT_TRUE(frame_data.context);
  137. // Test the debug information is correct.
  138. dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
  139. base::Value devtools_list =
  140. GetDevToolsListFromPort(*dev_tools_listener_.debug_ports().begin());
  141. ASSERT_TRUE(devtools_list.is_list());
  142. EXPECT_EQ(devtools_list.GetListDeprecated().size(), 1u);
  143. base::Value* devtools_url =
  144. devtools_list.GetListDeprecated()[0].FindPath("url");
  145. ASSERT_TRUE(devtools_url->is_string());
  146. EXPECT_EQ(devtools_url->GetString(), url);
  147. base::Value* devtools_title =
  148. devtools_list.GetListDeprecated()[0].FindPath("title");
  149. ASSERT_TRUE(devtools_title->is_string());
  150. EXPECT_EQ(devtools_title->GetString(), "title 1");
  151. // Unbind the context and wait for the listener to no longer have any active
  152. // DevTools port.
  153. frame_data.context.Unbind();
  154. dev_tools_listener_.RunUntilNumberOfPortsIs(0);
  155. }
  156. TEST_F(WebEngineDebugIntegrationTest, MultipleDebugClients) {
  157. std::string url1 = test_server_.GetURL("/title1.html").spec();
  158. TestContextAndFrame frame_data1(web_context_provider_.get(),
  159. UserModeDebugging::kDisabled, url1);
  160. ASSERT_TRUE(frame_data1.context);
  161. // Test the debug information is correct.
  162. dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
  163. uint16_t port1 = *dev_tools_listener_.debug_ports().begin();
  164. base::Value devtools_list1 = GetDevToolsListFromPort(port1);
  165. ASSERT_TRUE(devtools_list1.is_list());
  166. EXPECT_EQ(devtools_list1.GetListDeprecated().size(), 1u);
  167. base::Value* devtools_url1 =
  168. devtools_list1.GetListDeprecated()[0].FindPath("url");
  169. ASSERT_TRUE(devtools_url1->is_string());
  170. EXPECT_EQ(devtools_url1->GetString(), url1);
  171. base::Value* devtools_title1 =
  172. devtools_list1.GetListDeprecated()[0].FindPath("title");
  173. ASSERT_TRUE(devtools_title1->is_string());
  174. EXPECT_EQ(devtools_title1->GetString(), "title 1");
  175. // Connect a second Debug interface.
  176. fuchsia::web::DebugSyncPtr debug2;
  177. debug_dir_->Connect(debug2.NewRequest());
  178. TestDebugListener dev_tools_listener2;
  179. fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding2(
  180. &dev_tools_listener2);
  181. debug2->EnableDevTools(dev_tools_listener_binding2.NewBinding());
  182. // Create a second Context, a second Frame and navigate it to title2.html.
  183. std::string url2 = test_server_.GetURL("/title2.html").spec();
  184. TestContextAndFrame frame_data2(web_context_provider_.get(),
  185. UserModeDebugging::kDisabled, url2);
  186. ASSERT_TRUE(frame_data2.context);
  187. // Ensure each DevTools listener has the right information.
  188. dev_tools_listener_.RunUntilNumberOfPortsIs(2u);
  189. dev_tools_listener2.RunUntilNumberOfPortsIs(1u);
  190. uint16_t port2 = *dev_tools_listener2.debug_ports().begin();
  191. ASSERT_NE(port1, port2);
  192. ASSERT_NE(dev_tools_listener_.debug_ports().find(port2),
  193. dev_tools_listener_.debug_ports().end());
  194. base::Value devtools_list2 = GetDevToolsListFromPort(port2);
  195. ASSERT_TRUE(devtools_list2.is_list());
  196. EXPECT_EQ(devtools_list2.GetListDeprecated().size(), 1u);
  197. base::Value* devtools_url2 =
  198. devtools_list2.GetListDeprecated()[0].FindPath("url");
  199. ASSERT_TRUE(devtools_url2->is_string());
  200. EXPECT_EQ(devtools_url2->GetString(), url2);
  201. base::Value* devtools_title2 =
  202. devtools_list2.GetListDeprecated()[0].FindPath("title");
  203. ASSERT_TRUE(devtools_title2->is_string());
  204. EXPECT_EQ(devtools_title2->GetString(), "title 2");
  205. // Unbind the first Context, each listener should still have one open port.
  206. frame_data1.context.Unbind();
  207. dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
  208. dev_tools_listener2.RunUntilNumberOfPortsIs(1u);
  209. // Unbind the second Context, no listener should have any open port.
  210. frame_data2.context.Unbind();
  211. dev_tools_listener_.RunUntilNumberOfPortsIs(0);
  212. dev_tools_listener2.RunUntilNumberOfPortsIs(0);
  213. }
  214. // Test the Debug service is accessible when the User service is requested.
  215. TEST_F(WebEngineDebugIntegrationTest, DebugAndUserService) {
  216. std::string url = test_server_.GetURL("/title1.html").spec();
  217. TestContextAndFrame frame_data(web_context_provider_.get(),
  218. UserModeDebugging::kEnabled, url);
  219. ASSERT_TRUE(frame_data.context);
  220. dev_tools_listener_.RunUntilNumberOfPortsIs(1u);
  221. // Check we are getting the same port on both the debug and user APIs.
  222. base::test::TestFuture<fuchsia::web::Context_GetRemoteDebuggingPort_Result>
  223. port_receiver;
  224. frame_data.context->GetRemoteDebuggingPort(
  225. CallbackToFitFunction(port_receiver.GetCallback()));
  226. ASSERT_TRUE(port_receiver.Wait());
  227. ASSERT_TRUE(port_receiver.Get().is_response());
  228. uint16_t remote_debugging_port = port_receiver.Get().response().port;
  229. ASSERT_EQ(remote_debugging_port, *dev_tools_listener_.debug_ports().begin());
  230. // Test the debug information is correct.
  231. base::Value devtools_list = GetDevToolsListFromPort(remote_debugging_port);
  232. ASSERT_TRUE(devtools_list.is_list());
  233. EXPECT_EQ(devtools_list.GetListDeprecated().size(), 1u);
  234. base::Value* devtools_url =
  235. devtools_list.GetListDeprecated()[0].FindPath("url");
  236. ASSERT_TRUE(devtools_url->is_string());
  237. EXPECT_EQ(devtools_url->GetString(), url);
  238. base::Value* devtools_title =
  239. devtools_list.GetListDeprecated()[0].FindPath("title");
  240. ASSERT_TRUE(devtools_title->is_string());
  241. EXPECT_EQ(devtools_title->GetString(), "title 1");
  242. // Unbind the context and wait for the listener to no longer have any active
  243. // DevTools port.
  244. frame_data.context.Unbind();
  245. dev_tools_listener_.RunUntilNumberOfPortsIs(0);
  246. }