123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h"
- #include <fuchsia/web/cpp/fidl.h>
- #include <lib/fidl/cpp/interface_ptr_set.h>
- #include <lib/sys/cpp/component_context.h>
- #include <vector>
- #include "base/command_line.h"
- #include "base/containers/flat_set.h"
- #include "base/fuchsia/process_context.h"
- #include "base/fuchsia/scoped_service_binding.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_tokenizer.h"
- #include "content/public/browser/devtools_agent_host.h"
- #include "content/public/browser/devtools_socket_factory.h"
- #include "content/public/common/content_switches.h"
- #include "fuchsia_web/webengine/switches.h"
- #include "net/base/net_errors.h"
- #include "net/base/port_util.h"
- #include "net/socket/tcp_server_socket.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace {
- using OnDevToolsPortChanged = base::OnceCallback<void(uint16_t)>;
- class DevToolsSocketFactory : public content::DevToolsSocketFactory {
- public:
- DevToolsSocketFactory(OnDevToolsPortChanged on_devtools_port,
- net::IPEndPoint ip_end_point)
- : on_devtools_port_(std::move(on_devtools_port)),
- ip_end_point_(std::move(ip_end_point)) {}
- ~DevToolsSocketFactory() override = default;
- DevToolsSocketFactory(const DevToolsSocketFactory&) = delete;
- DevToolsSocketFactory& operator=(const DevToolsSocketFactory&) = delete;
- // content::DevToolsSocketFactory implementation.
- std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
- const int kTcpListenBackLog = 5;
- auto socket =
- std::make_unique<net::TCPServerSocket>(nullptr, net::NetLogSource());
- int error = socket->Listen(ip_end_point_, kTcpListenBackLog);
- if (error != net::OK) {
- LOG(WARNING) << "Failed to start the HTTP debugger service. "
- << net::ErrorToString(error);
- std::move(on_devtools_port_).Run(0);
- return nullptr;
- }
- net::IPEndPoint end_point;
- socket->GetLocalAddress(&end_point);
- std::move(on_devtools_port_).Run(end_point.port());
- return socket;
- }
- std::unique_ptr<net::ServerSocket> CreateForTethering(
- std::string* out_name) override {
- return nullptr;
- }
- private:
- OnDevToolsPortChanged on_devtools_port_;
- net::IPEndPoint ip_end_point_;
- };
- void StartRemoteDebuggingServer(OnDevToolsPortChanged on_devtools_port,
- net::IPEndPoint ip_end_point) {
- const base::FilePath kDisableActivePortOutputDirectory{};
- const base::FilePath kDisableDebugOutput{};
- content::DevToolsAgentHost::StartRemoteDebuggingServer(
- std::make_unique<DevToolsSocketFactory>(std::move(on_devtools_port),
- ip_end_point),
- kDisableActivePortOutputDirectory, kDisableDebugOutput);
- }
- class NoopController : public WebEngineDevToolsController {
- public:
- NoopController() = default;
- ~NoopController() override = default;
- NoopController(const NoopController&) = delete;
- NoopController& operator=(const NoopController&) = delete;
- // WebEngineDevToolsController implementation:
- bool OnFrameCreated(content::WebContents* contents,
- bool user_debugging) override {
- return !user_debugging;
- }
- void OnFrameLoaded(content::WebContents* contents) override {}
- void OnFrameDestroyed(content::WebContents* contents) override {}
- content::DevToolsAgentHost::List RemoteDebuggingTargets() override {
- return {};
- }
- void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
- std::move(callback).Run(0);
- }
- };
- // "User-mode" makes DevTools accessible to remote devices for Frames specified
- // by the web_instance owner. The controller, which starts DevTools when the
- // first Frame is created, and shuts it down when no debuggable Frames remain.
- class UserModeController : public WebEngineDevToolsController {
- public:
- explicit UserModeController(uint16_t server_port)
- : ip_endpoint_(net::IPAddress::IPv6AllZeros(), server_port) {}
- ~UserModeController() override {
- if (is_remote_debugging_started_) {
- content::DevToolsAgentHost::StopRemoteDebuggingServer();
- }
- }
- UserModeController(const UserModeController&) = delete;
- UserModeController& operator=(const UserModeController&) = delete;
- // WebEngineDevToolsController implementation:
- bool OnFrameCreated(content::WebContents* contents,
- bool user_debugging) override {
- if (user_debugging) {
- debuggable_contents_.insert(contents);
- UpdateRemoteDebuggingServer();
- }
- return true;
- }
- void OnFrameLoaded(content::WebContents* contents) override {}
- void OnFrameDestroyed(content::WebContents* contents) override {
- debuggable_contents_.erase(contents);
- UpdateRemoteDebuggingServer();
- }
- content::DevToolsAgentHost::List RemoteDebuggingTargets() override {
- DCHECK(is_remote_debugging_started_);
- content::DevToolsAgentHost::List enabled_hosts;
- for (content::WebContents* contents : debuggable_contents_) {
- enabled_hosts.push_back(
- content::DevToolsAgentHost::GetOrCreateFor(contents));
- }
- return enabled_hosts;
- }
- void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
- get_port_callbacks_.emplace_back(std::move(callback));
- MaybeNotifyGetPortCallbacks();
- }
- private:
- // Starts or stops the remote debugging server, if necessary
- void UpdateRemoteDebuggingServer() {
- bool need_remote_debugging = !debuggable_contents_.empty();
- if (need_remote_debugging == is_remote_debugging_started_)
- return;
- is_remote_debugging_started_ = need_remote_debugging;
- if (need_remote_debugging) {
- StartRemoteDebuggingServer(
- base::BindOnce(&UserModeController::OnDevToolsPortChanged,
- base::Unretained(this)),
- ip_endpoint_);
- } else {
- content::DevToolsAgentHost::StopRemoteDebuggingServer();
- devtools_port_.reset();
- }
- }
- void OnDevToolsPortChanged(uint16_t port) {
- devtools_port_ = port;
- MaybeNotifyGetPortCallbacks();
- }
- void MaybeNotifyGetPortCallbacks() {
- if (!devtools_port_.has_value())
- return;
- for (auto& callback : get_port_callbacks_)
- std::move(callback).Run(devtools_port_.value());
- get_port_callbacks_.clear();
- }
- const net::IPEndPoint ip_endpoint_;
- // True if the remote debugging server is started.
- bool is_remote_debugging_started_ = false;
- // Currently active DevTools port. Set to 0 on service startup error.
- absl::optional<uint16_t> devtools_port_;
- // Set of Frames' content::WebContents which are remotely debuggable.
- base::flat_set<content::WebContents*> debuggable_contents_;
- std::vector<base::OnceCallback<void(uint16_t)>> get_port_callbacks_;
- };
- // "Debug-mode" is used for on-device testing, and makes all Frames available
- // for debugging by clients on the same device. DevTools is only reported when
- // the first Frame finishes loading its main document, so that the
- // DevToolsPerContextListeners can start interacting with it immediately.
- class DebugModeController : public WebEngineDevToolsController,
- public fuchsia::web::Debug {
- public:
- DebugModeController()
- : DebugModeController(
- net::IPEndPoint(net::IPAddress::IPv4Localhost(), 0)) {}
- ~DebugModeController() override {
- content::DevToolsAgentHost::StopRemoteDebuggingServer();
- }
- DebugModeController(const DebugModeController&) = delete;
- DebugModeController& operator=(const DebugModeController&) = delete;
- // DevToolsController implementation:
- bool OnFrameCreated(content::WebContents* contents,
- bool user_debugging) override {
- return !user_debugging;
- }
- void OnFrameLoaded(content::WebContents* contents) override {
- frame_loaded_ = true;
- MaybeSendRemoteDebuggingCallbacks();
- }
- void OnFrameDestroyed(content::WebContents* contents) override {}
- content::DevToolsAgentHost::List RemoteDebuggingTargets() override {
- return content::DevToolsAgentHost::GetOrCreateAll();
- }
- void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
- std::move(callback).Run(0);
- }
- protected:
- explicit DebugModeController(net::IPEndPoint ip_endpoint)
- : ip_endpoint_(std::move(ip_endpoint)),
- binding_(base::ComponentContextForProcess()->outgoing().get(), this) {
- // Immediately start the service.
- StartRemoteDebuggingServer(
- base::BindOnce(&DebugModeController::OnDevToolsPortChanged,
- base::Unretained(this)),
- ip_endpoint_);
- }
- virtual void OnDevToolsPortChanged(uint16_t port) {
- devtools_port_ = port;
- MaybeSendRemoteDebuggingCallbacks();
- }
- // Currently active DevTools port. Set to 0 on service startup error.
- absl::optional<uint16_t> devtools_port_;
- private:
- // fuchsia::web::Debug implementation.
- void EnableDevTools(
- fidl::InterfaceHandle<fuchsia::web::DevToolsListener> listener_handle,
- EnableDevToolsCallback callback) override {
- // Each web-instance has a single DevTools "context", so create a new
- // per-context channel, and pass it to |listener| immediately.
- fuchsia::web::DevToolsPerContextListenerPtr context_listener;
- auto listener = listener_handle.Bind();
- listener->OnContextDevToolsAvailable(context_listener.NewRequest());
- // Notify the per-context listener immediately, if the port is ready.
- if (frame_loaded_ && devtools_port_)
- context_listener->OnHttpPortOpen(devtools_port_.value());
- devtools_listeners_.AddInterfacePtr(std::move(context_listener));
- }
- void MaybeSendRemoteDebuggingCallbacks() {
- if (!frame_loaded_ || !devtools_port_)
- return;
- // If |devtools_port_| is zero then DevTools failed to initialize, and
- // all listener connections should be closed to indicate failure.
- if (devtools_port_.value() == 0) {
- devtools_listeners_.CloseAll();
- return;
- }
- for (const auto& listener : devtools_listeners_.ptrs()) {
- listener->get()->OnHttpPortOpen(devtools_port_.value());
- }
- }
- const net::IPEndPoint ip_endpoint_;
- bool frame_loaded_ = false;
- fidl::InterfacePtrSet<fuchsia::web::DevToolsPerContextListener>
- devtools_listeners_;
- const base::ScopedServiceBinding<fuchsia::web::Debug> binding_;
- };
- // "Mixed-mode" is used when both user and debug remote debugging are active at
- // the same time. The service is enabled for the lifespan of the web_instance.
- class MixedModeController : public DebugModeController {
- public:
- explicit MixedModeController(uint16_t server_port)
- : DebugModeController(
- net::IPEndPoint(net::IPAddress::IPv6AllZeros(), server_port)) {}
- ~MixedModeController() override = default;
- // WebEngineDevToolsController overrides:
- bool OnFrameCreated(content::WebContents* contents,
- bool user_debugging) override {
- return true;
- }
- void GetDevToolsPort(base::OnceCallback<void(uint16_t)> callback) override {
- get_port_callbacks_.emplace_back(std::move(callback));
- MaybeNotifyGetPortCallbacks();
- }
- // DebugModeController overrides:
- void OnDevToolsPortChanged(uint16_t port) override {
- DebugModeController::OnDevToolsPortChanged(port);
- MaybeNotifyGetPortCallbacks();
- }
- void MaybeNotifyGetPortCallbacks() {
- if (!devtools_port_)
- return;
- for (auto& callback : get_port_callbacks_)
- std::move(callback).Run(devtools_port_.value());
- get_port_callbacks_.clear();
- }
- std::vector<base::OnceCallback<void(uint16_t)>> get_port_callbacks_;
- };
- } // namespace
- // static
- std::unique_ptr<WebEngineDevToolsController>
- WebEngineDevToolsController::CreateFromCommandLine(
- const base::CommandLine& command_line) {
- absl::optional<uint16_t> devtools_port;
- if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
- // Set up DevTools to listen on all network routes on the command-line
- // provided port.
- std::string command_line_port_value =
- command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort);
- int parsed_port = 0;
- // The command-line option can only be provided by the ContextProvider
- // process, it should not fail to parse to an int.
- bool parsed = base::StringToInt(command_line_port_value, &parsed_port);
- DCHECK(parsed);
- if (parsed_port != 0 &&
- (!net::IsPortValid(parsed_port) || net::IsWellKnownPort(parsed_port))) {
- LOG(WARNING) << "Invalid HTTP debugger service port number "
- << command_line_port_value;
- } else {
- devtools_port = parsed_port;
- }
- }
- bool enable_debug_mode =
- command_line.HasSwitch(switches::kEnableRemoteDebugMode);
- if (devtools_port) {
- if (enable_debug_mode) {
- return std::make_unique<MixedModeController>(devtools_port.value());
- } else {
- return std::make_unique<UserModeController>(devtools_port.value());
- }
- } else if (enable_debug_mode) {
- return std::make_unique<DebugModeController>();
- } else {
- return std::make_unique<NoopController>();
- }
- }
|