web_engine_devtools_controller.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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_web/webengine/browser/web_engine_devtools_controller.h"
  5. #include <fuchsia/web/cpp/fidl.h>
  6. #include <lib/fidl/cpp/interface_ptr_set.h>
  7. #include <lib/sys/cpp/component_context.h>
  8. #include <vector>
  9. #include "base/command_line.h"
  10. #include "base/containers/flat_set.h"
  11. #include "base/fuchsia/process_context.h"
  12. #include "base/fuchsia/scoped_service_binding.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/string_tokenizer.h"
  15. #include "content/public/browser/devtools_agent_host.h"
  16. #include "content/public/browser/devtools_socket_factory.h"
  17. #include "content/public/common/content_switches.h"
  18. #include "fuchsia_web/webengine/switches.h"
  19. #include "net/base/net_errors.h"
  20. #include "net/base/port_util.h"
  21. #include "net/socket/tcp_server_socket.h"
  22. #include "third_party/abseil-cpp/absl/types/optional.h"
  23. namespace {
  24. using OnDevToolsPortChanged = base::OnceCallback<void(uint16_t)>;
  25. class DevToolsSocketFactory : public content::DevToolsSocketFactory {
  26. public:
  27. DevToolsSocketFactory(OnDevToolsPortChanged on_devtools_port,
  28. net::IPEndPoint ip_end_point)
  29. : on_devtools_port_(std::move(on_devtools_port)),
  30. ip_end_point_(std::move(ip_end_point)) {}
  31. ~DevToolsSocketFactory() override = default;
  32. DevToolsSocketFactory(const DevToolsSocketFactory&) = delete;
  33. DevToolsSocketFactory& operator=(const DevToolsSocketFactory&) = delete;
  34. // content::DevToolsSocketFactory implementation.
  35. std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
  36. const int kTcpListenBackLog = 5;
  37. auto socket =
  38. std::make_unique<net::TCPServerSocket>(nullptr, net::NetLogSource());
  39. int error = socket->Listen(ip_end_point_, kTcpListenBackLog);
  40. if (error != net::OK) {
  41. LOG(WARNING) << "Failed to start the HTTP debugger service. "
  42. << net::ErrorToString(error);
  43. std::move(on_devtools_port_).Run(0);
  44. return nullptr;
  45. }
  46. net::IPEndPoint end_point;
  47. socket->GetLocalAddress(&end_point);
  48. std::move(on_devtools_port_).Run(end_point.port());
  49. return socket;
  50. }
  51. std::unique_ptr<net::ServerSocket> CreateForTethering(
  52. std::string* out_name) override {
  53. return nullptr;
  54. }
  55. private:
  56. OnDevToolsPortChanged on_devtools_port_;
  57. net::IPEndPoint ip_end_point_;
  58. };
  59. void StartRemoteDebuggingServer(OnDevToolsPortChanged on_devtools_port,
  60. net::IPEndPoint ip_end_point) {
  61. const base::FilePath kDisableActivePortOutputDirectory{};
  62. const base::FilePath kDisableDebugOutput{};
  63. content::DevToolsAgentHost::StartRemoteDebuggingServer(
  64. std::make_unique<DevToolsSocketFactory>(std::move(on_devtools_port),
  65. ip_end_point),
  66. kDisableActivePortOutputDirectory, kDisableDebugOutput);
  67. }
  68. class NoopController : public WebEngineDevToolsController {
  69. public:
  70. NoopController() = default;
  71. ~NoopController() override = default;
  72. NoopController(const NoopController&) = delete;
  73. NoopController& operator=(const NoopController&) = delete;
  74. // WebEngineDevToolsController implementation:
  75. bool OnFrameCreated(content::WebContents* contents,
  76. bool user_debugging) override {
  77. return !user_debugging;
  78. }
  79. void OnFrameLoaded(content::WebContents* contents) override {}
  80. void OnFrameDestroyed(content::WebContents* contents) override {}
  81. content::DevToolsAgentHost::List RemoteDebuggingTargets() override {
  82. return {};
  83. }
  84. void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
  85. std::move(callback).Run(0);
  86. }
  87. };
  88. // "User-mode" makes DevTools accessible to remote devices for Frames specified
  89. // by the web_instance owner. The controller, which starts DevTools when the
  90. // first Frame is created, and shuts it down when no debuggable Frames remain.
  91. class UserModeController : public WebEngineDevToolsController {
  92. public:
  93. explicit UserModeController(uint16_t server_port)
  94. : ip_endpoint_(net::IPAddress::IPv6AllZeros(), server_port) {}
  95. ~UserModeController() override {
  96. if (is_remote_debugging_started_) {
  97. content::DevToolsAgentHost::StopRemoteDebuggingServer();
  98. }
  99. }
  100. UserModeController(const UserModeController&) = delete;
  101. UserModeController& operator=(const UserModeController&) = delete;
  102. // WebEngineDevToolsController implementation:
  103. bool OnFrameCreated(content::WebContents* contents,
  104. bool user_debugging) override {
  105. if (user_debugging) {
  106. debuggable_contents_.insert(contents);
  107. UpdateRemoteDebuggingServer();
  108. }
  109. return true;
  110. }
  111. void OnFrameLoaded(content::WebContents* contents) override {}
  112. void OnFrameDestroyed(content::WebContents* contents) override {
  113. debuggable_contents_.erase(contents);
  114. UpdateRemoteDebuggingServer();
  115. }
  116. content::DevToolsAgentHost::List RemoteDebuggingTargets() override {
  117. DCHECK(is_remote_debugging_started_);
  118. content::DevToolsAgentHost::List enabled_hosts;
  119. for (content::WebContents* contents : debuggable_contents_) {
  120. enabled_hosts.push_back(
  121. content::DevToolsAgentHost::GetOrCreateFor(contents));
  122. }
  123. return enabled_hosts;
  124. }
  125. void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
  126. get_port_callbacks_.emplace_back(std::move(callback));
  127. MaybeNotifyGetPortCallbacks();
  128. }
  129. private:
  130. // Starts or stops the remote debugging server, if necessary
  131. void UpdateRemoteDebuggingServer() {
  132. bool need_remote_debugging = !debuggable_contents_.empty();
  133. if (need_remote_debugging == is_remote_debugging_started_)
  134. return;
  135. is_remote_debugging_started_ = need_remote_debugging;
  136. if (need_remote_debugging) {
  137. StartRemoteDebuggingServer(
  138. base::BindOnce(&UserModeController::OnDevToolsPortChanged,
  139. base::Unretained(this)),
  140. ip_endpoint_);
  141. } else {
  142. content::DevToolsAgentHost::StopRemoteDebuggingServer();
  143. devtools_port_.reset();
  144. }
  145. }
  146. void OnDevToolsPortChanged(uint16_t port) {
  147. devtools_port_ = port;
  148. MaybeNotifyGetPortCallbacks();
  149. }
  150. void MaybeNotifyGetPortCallbacks() {
  151. if (!devtools_port_.has_value())
  152. return;
  153. for (auto& callback : get_port_callbacks_)
  154. std::move(callback).Run(devtools_port_.value());
  155. get_port_callbacks_.clear();
  156. }
  157. const net::IPEndPoint ip_endpoint_;
  158. // True if the remote debugging server is started.
  159. bool is_remote_debugging_started_ = false;
  160. // Currently active DevTools port. Set to 0 on service startup error.
  161. absl::optional<uint16_t> devtools_port_;
  162. // Set of Frames' content::WebContents which are remotely debuggable.
  163. base::flat_set<content::WebContents*> debuggable_contents_;
  164. std::vector<base::OnceCallback<void(uint16_t)>> get_port_callbacks_;
  165. };
  166. // "Debug-mode" is used for on-device testing, and makes all Frames available
  167. // for debugging by clients on the same device. DevTools is only reported when
  168. // the first Frame finishes loading its main document, so that the
  169. // DevToolsPerContextListeners can start interacting with it immediately.
  170. class DebugModeController : public WebEngineDevToolsController,
  171. public fuchsia::web::Debug {
  172. public:
  173. DebugModeController()
  174. : DebugModeController(
  175. net::IPEndPoint(net::IPAddress::IPv4Localhost(), 0)) {}
  176. ~DebugModeController() override {
  177. content::DevToolsAgentHost::StopRemoteDebuggingServer();
  178. }
  179. DebugModeController(const DebugModeController&) = delete;
  180. DebugModeController& operator=(const DebugModeController&) = delete;
  181. // DevToolsController implementation:
  182. bool OnFrameCreated(content::WebContents* contents,
  183. bool user_debugging) override {
  184. return !user_debugging;
  185. }
  186. void OnFrameLoaded(content::WebContents* contents) override {
  187. frame_loaded_ = true;
  188. MaybeSendRemoteDebuggingCallbacks();
  189. }
  190. void OnFrameDestroyed(content::WebContents* contents) override {}
  191. content::DevToolsAgentHost::List RemoteDebuggingTargets() override {
  192. return content::DevToolsAgentHost::GetOrCreateAll();
  193. }
  194. void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
  195. std::move(callback).Run(0);
  196. }
  197. protected:
  198. explicit DebugModeController(net::IPEndPoint ip_endpoint)
  199. : ip_endpoint_(std::move(ip_endpoint)),
  200. binding_(base::ComponentContextForProcess()->outgoing().get(), this) {
  201. // Immediately start the service.
  202. StartRemoteDebuggingServer(
  203. base::BindOnce(&DebugModeController::OnDevToolsPortChanged,
  204. base::Unretained(this)),
  205. ip_endpoint_);
  206. }
  207. virtual void OnDevToolsPortChanged(uint16_t port) {
  208. devtools_port_ = port;
  209. MaybeSendRemoteDebuggingCallbacks();
  210. }
  211. // Currently active DevTools port. Set to 0 on service startup error.
  212. absl::optional<uint16_t> devtools_port_;
  213. private:
  214. // fuchsia::web::Debug implementation.
  215. void EnableDevTools(
  216. fidl::InterfaceHandle<fuchsia::web::DevToolsListener> listener_handle,
  217. EnableDevToolsCallback callback) override {
  218. // Each web-instance has a single DevTools "context", so create a new
  219. // per-context channel, and pass it to |listener| immediately.
  220. fuchsia::web::DevToolsPerContextListenerPtr context_listener;
  221. auto listener = listener_handle.Bind();
  222. listener->OnContextDevToolsAvailable(context_listener.NewRequest());
  223. // Notify the per-context listener immediately, if the port is ready.
  224. if (frame_loaded_ && devtools_port_)
  225. context_listener->OnHttpPortOpen(devtools_port_.value());
  226. devtools_listeners_.AddInterfacePtr(std::move(context_listener));
  227. }
  228. void MaybeSendRemoteDebuggingCallbacks() {
  229. if (!frame_loaded_ || !devtools_port_)
  230. return;
  231. // If |devtools_port_| is zero then DevTools failed to initialize, and
  232. // all listener connections should be closed to indicate failure.
  233. if (devtools_port_.value() == 0) {
  234. devtools_listeners_.CloseAll();
  235. return;
  236. }
  237. for (const auto& listener : devtools_listeners_.ptrs()) {
  238. listener->get()->OnHttpPortOpen(devtools_port_.value());
  239. }
  240. }
  241. const net::IPEndPoint ip_endpoint_;
  242. bool frame_loaded_ = false;
  243. fidl::InterfacePtrSet<fuchsia::web::DevToolsPerContextListener>
  244. devtools_listeners_;
  245. const base::ScopedServiceBinding<fuchsia::web::Debug> binding_;
  246. };
  247. // "Mixed-mode" is used when both user and debug remote debugging are active at
  248. // the same time. The service is enabled for the lifespan of the web_instance.
  249. class MixedModeController : public DebugModeController {
  250. public:
  251. explicit MixedModeController(uint16_t server_port)
  252. : DebugModeController(
  253. net::IPEndPoint(net::IPAddress::IPv6AllZeros(), server_port)) {}
  254. ~MixedModeController() override = default;
  255. // WebEngineDevToolsController overrides:
  256. bool OnFrameCreated(content::WebContents* contents,
  257. bool user_debugging) override {
  258. return true;
  259. }
  260. void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
  261. get_port_callbacks_.emplace_back(std::move(callback));
  262. MaybeNotifyGetPortCallbacks();
  263. }
  264. // DebugModeController overrides:
  265. void OnDevToolsPortChanged(uint16_t port) override {
  266. DebugModeController::OnDevToolsPortChanged(port);
  267. MaybeNotifyGetPortCallbacks();
  268. }
  269. void MaybeNotifyGetPortCallbacks() {
  270. if (!devtools_port_)
  271. return;
  272. for (auto& callback : get_port_callbacks_)
  273. std::move(callback).Run(devtools_port_.value());
  274. get_port_callbacks_.clear();
  275. }
  276. std::vector<base::OnceCallback<void(uint16_t)>> get_port_callbacks_;
  277. };
  278. } // namespace
  279. // static
  280. std::unique_ptr<WebEngineDevToolsController>
  281. WebEngineDevToolsController::CreateFromCommandLine(
  282. const base::CommandLine& command_line) {
  283. absl::optional<uint16_t> devtools_port;
  284. if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
  285. // Set up DevTools to listen on all network routes on the command-line
  286. // provided port.
  287. std::string command_line_port_value =
  288. command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort);
  289. int parsed_port = 0;
  290. // The command-line option can only be provided by the ContextProvider
  291. // process, it should not fail to parse to an int.
  292. bool parsed = base::StringToInt(command_line_port_value, &parsed_port);
  293. DCHECK(parsed);
  294. if (parsed_port != 0 &&
  295. (!net::IsPortValid(parsed_port) || net::IsWellKnownPort(parsed_port))) {
  296. LOG(WARNING) << "Invalid HTTP debugger service port number "
  297. << command_line_port_value;
  298. } else {
  299. devtools_port = parsed_port;
  300. }
  301. }
  302. bool enable_debug_mode =
  303. command_line.HasSwitch(switches::kEnableRemoteDebugMode);
  304. if (devtools_port) {
  305. if (enable_debug_mode) {
  306. return std::make_unique<MixedModeController>(devtools_port.value());
  307. } else {
  308. return std::make_unique<UserModeController>(devtools_port.value());
  309. }
  310. } else if (enable_debug_mode) {
  311. return std::make_unique<DebugModeController>();
  312. } else {
  313. return std::make_unique<NoopController>();
  314. }
  315. }