| // Copyright 2014 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 "chromecast/browser/devtools/remote_debugging_server.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/macros.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| #include "chromecast/base/cast_paths.h" |
| #include "chromecast/browser/cast_browser_process.h" |
| #include "chromecast/browser/devtools/cast_devtools_manager_delegate.h" |
| #include "chromecast/common/cast_content_client.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/devtools_socket_factory.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/user_agent.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/log/net_log_source.h" |
| #include "net/socket/tcp_server_socket.h" |
| |
| #if defined(OS_ANDROID) |
| #include "content/public/browser/android/devtools_auth.h" |
| #include "net/socket/unix_domain_server_socket_posix.h" |
| #endif // defined(OS_ANDROID) |
| |
| namespace chromecast { |
| namespace shell { |
| |
| namespace { |
| |
| const uint16_t kDefaultRemoteDebuggingPort = 9222; |
| |
| const int kBackLog = 10; |
| |
| #if defined(OS_ANDROID) |
| class UnixDomainServerSocketFactory : public content::DevToolsSocketFactory { |
| public: |
| explicit UnixDomainServerSocketFactory(const std::string& socket_name) |
| : socket_name_(socket_name) {} |
| |
| private: |
| // content::DevToolsSocketFactory. |
| std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { |
| std::unique_ptr<net::UnixDomainServerSocket> socket( |
| new net::UnixDomainServerSocket( |
| base::Bind(&content::CanUserConnectToDevTools), |
| true /* use_abstract_namespace */)); |
| if (socket->BindAndListen(socket_name_, kBackLog) != net::OK) |
| return std::unique_ptr<net::ServerSocket>(); |
| |
| return std::move(socket); |
| } |
| |
| std::unique_ptr<net::ServerSocket> CreateForTethering( |
| std::string* name) override { |
| return nullptr; |
| } |
| |
| std::string socket_name_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory); |
| }; |
| #else |
| class TCPServerSocketFactory : public content::DevToolsSocketFactory { |
| public: |
| explicit TCPServerSocketFactory(const net::IPEndPoint& endpoint) |
| : endpoint_(endpoint) {} |
| |
| private: |
| // content::DevToolsSocketFactory. |
| std::unique_ptr<net::ServerSocket> CreateForHttpServer() override { |
| std::unique_ptr<net::ServerSocket> socket( |
| new net::TCPServerSocket(nullptr, net::NetLogSource())); |
| |
| if (socket->Listen(endpoint_, kBackLog) != net::OK) |
| return std::unique_ptr<net::ServerSocket>(); |
| |
| return socket; |
| } |
| |
| std::unique_ptr<net::ServerSocket> CreateForTethering( |
| std::string* name) override { |
| return nullptr; |
| } |
| |
| const net::IPEndPoint endpoint_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory); |
| }; |
| #endif |
| |
| std::unique_ptr<content::DevToolsSocketFactory> CreateSocketFactory( |
| uint16_t port) { |
| #if defined(OS_ANDROID) |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| std::string socket_name = "cast_shell_devtools_remote"; |
| if (command_line->HasSwitch(switches::kRemoteDebuggingSocketName)) { |
| socket_name = command_line->GetSwitchValueASCII( |
| switches::kRemoteDebuggingSocketName); |
| } |
| return std::unique_ptr<content::DevToolsSocketFactory>( |
| new UnixDomainServerSocketFactory(socket_name)); |
| #else |
| net::IPEndPoint endpoint(net::IPAddress::IPv6AllZeros(), port); |
| return std::unique_ptr<content::DevToolsSocketFactory>( |
| new TCPServerSocketFactory(endpoint)); |
| #endif |
| } |
| |
| uint16_t GetPort() { |
| std::string port_str = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| switches::kRemoteDebuggingPort); |
| |
| if (port_str.empty()) |
| return kDefaultRemoteDebuggingPort; |
| |
| int port; |
| if (base::StringToInt(port_str, &port)) |
| return port; |
| |
| return kDefaultRemoteDebuggingPort; |
| } |
| |
| } // namespace |
| |
| class RemoteDebuggingServer::WebContentsObserver |
| : public content::WebContentsObserver { |
| public: |
| WebContentsObserver(content::WebContents* contents, |
| RemoteDebuggingServer* server) |
| : server_(server) { |
| Observe(contents); |
| } |
| |
| ~WebContentsObserver() override {} |
| |
| // content::WebContentsObserver implementation: |
| void WebContentsDestroyed() override { |
| // Alert the server that |web_contents_| will be destroyed. Do not call |
| // anything after this line; |this| will be destroyed. |
| server_->DisableWebContentsForDebugging(web_contents()); |
| } |
| |
| private: |
| RemoteDebuggingServer* const server_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WebContentsObserver); |
| }; |
| |
| RemoteDebuggingServer::RemoteDebuggingServer(bool start_immediately) |
| : port_(GetPort()), is_started_(false) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| } |
| |
| RemoteDebuggingServer::~RemoteDebuggingServer() { |
| StopIfNeeded(); |
| for (const auto& observer : observers_) |
| DisableWebContentsForDebugging(observer.first); |
| } |
| |
| CastDevToolsManagerDelegate* RemoteDebuggingServer::GetDevtoolsDelegate() { |
| // TODO(slan): This class uses a broken pattern of ownership and access which |
| // requires careful handling when obtaining a reference. DevToolsManager |
| // "owns" the single instance of this class, and could theoretically destroy |
| // it without warning. |
| auto* dev_tools_delegate = |
| chromecast::shell::CastDevToolsManagerDelegate::GetInstance(); |
| DCHECK(dev_tools_delegate); |
| return dev_tools_delegate; |
| } |
| |
| void RemoteDebuggingServer::StartIfNeeded() { |
| if (is_started_) |
| return; |
| |
| base::FilePath output_dir; |
| if (!port_) { |
| // Providing a defined output_dir causes DevTools to write the port |
| // number chosen to a file named DevToolsActivePort. This file is |
| // used by test automation tools like ChromeDriver. ChromeDriver passes |
| // in --remote-debugging-port=0 so that cast_shell picks its own port to |
| // binds to so that there is no race condition. |
| bool result = |
| base::PathService::Get(chromecast::DIR_CAST_HOME, &output_dir); |
| DCHECK(result); |
| } |
| |
| content::DevToolsAgentHost::StartRemoteDebuggingServer( |
| CreateSocketFactory(port_), output_dir, base::FilePath()); |
| LOG(INFO) << "Devtools started"; |
| is_started_ = true; |
| } |
| |
| void RemoteDebuggingServer::StopIfNeeded() { |
| // TODO(seantopping): The debugging server goes down temporarily when there |
| // are no active apps. This can sometimes break in-progress traces. Find a |
| // fix for this. |
| if (!is_started_ || GetDevtoolsDelegate()->HasEnabledWebContents()) |
| return; |
| |
| LOG(INFO) << "Stopping Devtools server."; |
| content::DevToolsAgentHost::StopRemoteDebuggingServer(); |
| is_started_ = false; |
| } |
| |
| void RemoteDebuggingServer::EnableWebContentsForDebugging( |
| content::WebContents* web_contents) { |
| DCHECK(web_contents); |
| |
| StartIfNeeded(); |
| |
| GetDevtoolsDelegate()->EnableWebContentsForDebugging(web_contents); |
| observers_.insert(std::make_pair( |
| web_contents, std::make_unique<WebContentsObserver>(web_contents, this))); |
| } |
| |
| void RemoteDebuggingServer::DisableWebContentsForDebugging( |
| content::WebContents* web_contents) { |
| DCHECK(web_contents); |
| |
| observers_.erase(web_contents); |
| GetDevtoolsDelegate()->DisableWebContentsForDebugging(web_contents); |
| |
| StopIfNeeded(); |
| } |
| |
| } // namespace shell |
| } // namespace chromecast |