// Copyright (c) 2012 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 #include #include "base/bind.h" #include "base/compiler_specific.h" #include "base/files/file_util.h" #include "base/json/json_writer.h" #include "base/location.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/thread.h" #include "base/values.h" #include "components/devtools_discovery/devtools_discovery_manager.h" #include "components/devtools_http_handler/devtools_http_handler.h" #include "components/devtools_http_handler/devtools_http_handler_delegate.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/common/url_constants.h" #include "content/public/common/user_agent.h" #include "net/base/escape.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/server/http_server.h" #include "net/server/http_server_request_info.h" #include "net/server/http_server_response_info.h" #include "net/socket/server_socket.h" #if defined(OS_ANDROID) #include "base/android/build_info.h" #endif using content::BrowserThread; using content::DevToolsAgentHost; using content::DevToolsAgentHostClient; using devtools_discovery::DevToolsTargetDescriptor; namespace devtools_http_handler { namespace { const base::FilePath::CharType kDevToolsActivePortFileName[] = FILE_PATH_LITERAL("DevToolsActivePort"); const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread"; const char kThumbUrlPrefix[] = "/thumb/"; const char kPageUrlPrefix[] = "/devtools/page/"; const char kTargetIdField[] = "id"; const char kTargetParentIdField[] = "parentId"; const char kTargetTypeField[] = "type"; const char kTargetTitleField[] = "title"; const char kTargetDescriptionField[] = "description"; const char kTargetUrlField[] = "url"; const char kTargetThumbnailUrlField[] = "thumbnailUrl"; const char kTargetFaviconUrlField[] = "faviconUrl"; const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl"; const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl"; // Maximum write buffer size of devtools http/websocket connections. // TODO(rmcilroy/pfieldman): Reduce this back to 100Mb when we have // added back pressure on the TraceComplete message protocol - crbug.com/456845. const int32 kSendBufferSizeForDevTools = 256 * 1024 * 1024; // 256Mb } // namespace // ServerWrapper ------------------------------------------------------------- // All methods in this class are only called on handler thread. class ServerWrapper : net::HttpServer::Delegate { public: ServerWrapper(base::WeakPtr handler, scoped_ptr socket, const base::FilePath& frontend_dir, bool bundles_resources); int GetLocalAddress(net::IPEndPoint* address); void AcceptWebSocket(int connection_id, const net::HttpServerRequestInfo& request); void SendOverWebSocket(int connection_id, const std::string& message); void SendResponse(int connection_id, const net::HttpServerResponseInfo& response); void Send200(int connection_id, const std::string& data, const std::string& mime_type); void Send404(int connection_id); void Send500(int connection_id, const std::string& message); void Close(int connection_id); void WriteActivePortToUserProfile(const base::FilePath& output_directory); ~ServerWrapper() override {} private: // net::HttpServer::Delegate implementation. void OnConnect(int connection_id) override {} void OnHttpRequest(int connection_id, const net::HttpServerRequestInfo& info) override; void OnWebSocketRequest(int connection_id, const net::HttpServerRequestInfo& info) override; void OnWebSocketMessage(int connection_id, const std::string& data) override; void OnClose(int connection_id) override; base::WeakPtr handler_; scoped_ptr server_; base::FilePath frontend_dir_; bool bundles_resources_; }; ServerWrapper::ServerWrapper(base::WeakPtr handler, scoped_ptr socket, const base::FilePath& frontend_dir, bool bundles_resources) : handler_(handler), server_(new net::HttpServer(socket.Pass(), this)), frontend_dir_(frontend_dir), bundles_resources_(bundles_resources) { } int ServerWrapper::GetLocalAddress(net::IPEndPoint* address) { return server_->GetLocalAddress(address); } void ServerWrapper::AcceptWebSocket(int connection_id, const net::HttpServerRequestInfo& request) { server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools); server_->AcceptWebSocket(connection_id, request); } void ServerWrapper::SendOverWebSocket(int connection_id, const std::string& message) { server_->SendOverWebSocket(connection_id, message); } void ServerWrapper::SendResponse(int connection_id, const net::HttpServerResponseInfo& response) { server_->SendResponse(connection_id, response); } void ServerWrapper::Send200(int connection_id, const std::string& data, const std::string& mime_type) { server_->Send200(connection_id, data, mime_type); } void ServerWrapper::Send404(int connection_id) { server_->Send404(connection_id); } void ServerWrapper::Send500(int connection_id, const std::string& message) { server_->Send500(connection_id, message); } void ServerWrapper::Close(int connection_id) { server_->Close(connection_id); } // Thread and ServerWrapper lifetime management ------------------------------ void TerminateOnUI(base::Thread* thread, ServerWrapper* server_wrapper, DevToolsHttpHandler::ServerSocketFactory* socket_factory) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (server_wrapper) { DCHECK(thread); thread->message_loop()->DeleteSoon(FROM_HERE, server_wrapper); } if (socket_factory) { DCHECK(thread); thread->message_loop()->DeleteSoon(FROM_HERE, socket_factory); } if (thread) { BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, thread); } } void ServerStartedOnUI( base::WeakPtr handler, base::Thread* thread, ServerWrapper* server_wrapper, DevToolsHttpHandler::ServerSocketFactory* socket_factory, scoped_ptr ip_address) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (handler && thread && server_wrapper) { handler->ServerStarted(thread, server_wrapper, socket_factory, ip_address.Pass()); } else { TerminateOnUI(thread, server_wrapper, socket_factory); } } void StartServerOnHandlerThread( base::WeakPtr handler, base::Thread* thread, DevToolsHttpHandler::ServerSocketFactory* server_socket_factory, const base::FilePath& output_directory, const base::FilePath& frontend_dir, bool bundles_resources) { DCHECK_EQ(thread->message_loop(), base::MessageLoop::current()); ServerWrapper* server_wrapper = nullptr; scoped_ptr server_socket = server_socket_factory->CreateForHttpServer(); scoped_ptr ip_address(new net::IPEndPoint); if (server_socket) { server_wrapper = new ServerWrapper(handler, server_socket.Pass(), frontend_dir, bundles_resources); if (!output_directory.empty()) server_wrapper->WriteActivePortToUserProfile(output_directory); if (server_wrapper->GetLocalAddress(ip_address.get()) != net::OK) ip_address.reset(); } else { ip_address.reset(); LOG(ERROR) << "Cannot start http server for devtools. Stop devtools."; } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&ServerStartedOnUI, handler, thread, server_wrapper, server_socket_factory, base::Passed(&ip_address))); } void StartServerOnFile( base::WeakPtr handler, DevToolsHttpHandler::ServerSocketFactory* server_socket_factory, const base::FilePath& output_directory, const base::FilePath& frontend_dir, bool bundles_resources) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); scoped_ptr thread(new base::Thread(kDevToolsHandlerThreadName)); base::Thread::Options options; options.message_loop_type = base::MessageLoop::TYPE_IO; if (thread->StartWithOptions(options)) { base::MessageLoop* message_loop = thread->message_loop(); message_loop->task_runner()->PostTask( FROM_HERE, base::Bind(&StartServerOnHandlerThread, handler, base::Unretained(thread.release()), server_socket_factory, output_directory, frontend_dir, bundles_resources)); } } // DevToolsAgentHostClientImpl ----------------------------------------------- // An internal implementation of DevToolsAgentHostClient that delegates // messages sent to a DebuggerShell instance. class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient { public: DevToolsAgentHostClientImpl(base::MessageLoop* message_loop, ServerWrapper* server_wrapper, int connection_id, scoped_refptr agent_host) : message_loop_(message_loop), server_wrapper_(server_wrapper), connection_id_(connection_id), agent_host_(agent_host) { DCHECK_CURRENTLY_ON(BrowserThread::UI); agent_host_->AttachClient(this); } ~DevToolsAgentHostClientImpl() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (agent_host_.get()) agent_host_->DetachClient(); } void AgentHostClosed(DevToolsAgentHost* agent_host, bool replaced_with_another_client) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(agent_host == agent_host_.get()); std::string message = base::StringPrintf( "{ \"method\": \"Inspector.detached\", " "\"params\": { \"reason\": \"%s\"} }", replaced_with_another_client ? "replaced_with_devtools" : "target_closed"); DispatchProtocolMessage(agent_host, message); agent_host_ = nullptr; message_loop_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::Close, base::Unretained(server_wrapper_), connection_id_)); } void DispatchProtocolMessage(DevToolsAgentHost* agent_host, const std::string& message) override { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(agent_host == agent_host_.get()); message_loop_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::SendOverWebSocket, base::Unretained(server_wrapper_), connection_id_, message)); } void OnMessage(const std::string& message) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (agent_host_.get()) agent_host_->DispatchProtocolMessage(message); } private: base::MessageLoop* const message_loop_; ServerWrapper* const server_wrapper_; const int connection_id_; scoped_refptr agent_host_; }; static bool TimeComparator(const DevToolsTargetDescriptor* desc1, const DevToolsTargetDescriptor* desc2) { return desc1->GetLastActivityTime() > desc2->GetLastActivityTime(); } // DevToolsHttpHandler::ServerSocketFactory ---------------------------------- scoped_ptr DevToolsHttpHandler::ServerSocketFactory::CreateForHttpServer() { return scoped_ptr(); } scoped_ptr DevToolsHttpHandler::ServerSocketFactory::CreateForTethering( std::string* name) { return scoped_ptr(); } // DevToolsHttpHandler ------------------------------------------------------- DevToolsHttpHandler::~DevToolsHttpHandler() { TerminateOnUI(thread_, server_wrapper_, socket_factory_); STLDeleteValues(&descriptor_map_); STLDeleteValues(&connection_to_client_); } GURL DevToolsHttpHandler::GetFrontendURL(const std::string& path) { if (!server_ip_address_) return GURL(); return GURL(std::string("http://") + server_ip_address_->ToString() + (path.empty() ? frontend_url_ : path)); } static std::string PathWithoutParams(const std::string& path) { size_t query_position = path.find("?"); if (query_position != std::string::npos) return path.substr(0, query_position); return path; } static std::string GetMimeType(const std::string& filename) { if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) { return "text/html"; } else if (base::EndsWith(filename, ".css", base::CompareCase::INSENSITIVE_ASCII)) { return "text/css"; } else if (base::EndsWith(filename, ".js", base::CompareCase::INSENSITIVE_ASCII)) { return "application/javascript"; } else if (base::EndsWith(filename, ".png", base::CompareCase::INSENSITIVE_ASCII)) { return "image/png"; } else if (base::EndsWith(filename, ".gif", base::CompareCase::INSENSITIVE_ASCII)) { return "image/gif"; } else if (base::EndsWith(filename, ".json", base::CompareCase::INSENSITIVE_ASCII)) { return "application/json"; } LOG(ERROR) << "GetMimeType doesn't know mime type for: " << filename << " text/plain will be returned"; NOTREACHED(); return "text/plain"; } void ServerWrapper::OnHttpRequest(int connection_id, const net::HttpServerRequestInfo& info) { server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools); if (info.path.find("/json") == 0) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DevToolsHttpHandler::OnJsonRequest, handler_, connection_id, info)); return; } if (info.path.find(kThumbUrlPrefix) == 0) { // Thumbnail request. const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix)); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DevToolsHttpHandler::OnThumbnailRequest, handler_, connection_id, target_id)); return; } if (info.path.empty() || info.path == "/") { // Discovery page request. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DevToolsHttpHandler::OnDiscoveryPageRequest, handler_, connection_id)); return; } if (info.path.find("/devtools/") != 0) { server_->Send404(connection_id); return; } std::string filename = PathWithoutParams(info.path.substr(10)); std::string mime_type = GetMimeType(filename); if (!frontend_dir_.empty()) { base::FilePath path = frontend_dir_.AppendASCII(filename); std::string data; base::ReadFileToString(path, &data); server_->Send200(connection_id, data, mime_type); return; } if (bundles_resources_) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DevToolsHttpHandler::OnFrontendResourceRequest, handler_, connection_id, filename)); return; } server_->Send404(connection_id); } void ServerWrapper::OnWebSocketRequest( int connection_id, const net::HttpServerRequestInfo& request) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( &DevToolsHttpHandler::OnWebSocketRequest, handler_, connection_id, request)); } void ServerWrapper::OnWebSocketMessage(int connection_id, const std::string& data) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( &DevToolsHttpHandler::OnWebSocketMessage, handler_, connection_id, data)); } void ServerWrapper::OnClose(int connection_id) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( &DevToolsHttpHandler::OnClose, handler_, connection_id)); } std::string DevToolsHttpHandler::GetFrontendURLInternal( const std::string id, const std::string& host) { return base::StringPrintf( "%s%sws=%s%s%s", frontend_url_.c_str(), frontend_url_.find("?") == std::string::npos ? "?" : "&", host.c_str(), kPageUrlPrefix, id.c_str()); } static bool ParseJsonPath( const std::string& path, std::string* command, std::string* target_id) { // Fall back to list in case of empty query. if (path.empty()) { *command = "list"; return true; } if (path.find("/") != 0) { // Malformed command. return false; } *command = path.substr(1); size_t separator_pos = command->find("/"); if (separator_pos != std::string::npos) { *target_id = command->substr(separator_pos + 1); *command = command->substr(0, separator_pos); } return true; } void DevToolsHttpHandler::OnJsonRequest( int connection_id, const net::HttpServerRequestInfo& info) { // Trim /json std::string path = info.path.substr(5); // Trim fragment and query std::string query; size_t query_pos = path.find("?"); if (query_pos != std::string::npos) { query = path.substr(query_pos + 1); path = path.substr(0, query_pos); } size_t fragment_pos = path.find("#"); if (fragment_pos != std::string::npos) path = path.substr(0, fragment_pos); std::string command; std::string target_id; if (!ParseJsonPath(path, &command, &target_id)) { SendJson(connection_id, net::HTTP_NOT_FOUND, NULL, "Malformed query: " + info.path); return; } if (command == "version") { base::DictionaryValue version; version.SetString("Protocol-Version", DevToolsAgentHost::GetProtocolVersion().c_str()); version.SetString("WebKit-Version", content::GetWebKitVersion()); version.SetString("Browser", product_name_); version.SetString("User-Agent", user_agent_); #if defined(OS_ANDROID) version.SetString("Android-Package", base::android::BuildInfo::GetInstance()->package_name()); #endif SendJson(connection_id, net::HTTP_OK, &version, std::string()); return; } if (command == "list") { std::string host = info.headers["host"]; DevToolsTargetDescriptor::List descriptors = devtools_discovery::DevToolsDiscoveryManager::GetInstance()-> GetDescriptors(); std::sort(descriptors.begin(), descriptors.end(), TimeComparator); STLDeleteValues(&descriptor_map_); base::ListValue list_value; for (DevToolsTargetDescriptor* descriptor : descriptors) { descriptor_map_[descriptor->GetId()] = descriptor; list_value.Append(SerializeDescriptor(*descriptor, host)); } SendJson(connection_id, net::HTTP_OK, &list_value, std::string()); return; } if (command == "new") { GURL url(net::UnescapeURLComponent( query, net::UnescapeRule::URL_SPECIAL_CHARS)); if (!url.is_valid()) url = GURL(url::kAboutBlankURL); scoped_ptr descriptor = devtools_discovery::DevToolsDiscoveryManager::GetInstance()-> CreateNew(url); if (!descriptor) { SendJson(connection_id, net::HTTP_INTERNAL_SERVER_ERROR, NULL, "Could not create new page"); return; } std::string host = info.headers["host"]; scoped_ptr dictionary( SerializeDescriptor(*descriptor.get(), host)); SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string()); const std::string target_id = descriptor->GetId(); descriptor_map_[target_id] = descriptor.release(); return; } if (command == "activate" || command == "close") { DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id); if (!descriptor) { SendJson(connection_id, net::HTTP_NOT_FOUND, NULL, "No such target id: " + target_id); return; } if (command == "activate") { if (descriptor->Activate()) { SendJson(connection_id, net::HTTP_OK, NULL, "Target activated"); } else { SendJson(connection_id, net::HTTP_INTERNAL_SERVER_ERROR, NULL, "Could not activate target id: " + target_id); } return; } if (command == "close") { if (descriptor->Close()) { SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing"); } else { SendJson(connection_id, net::HTTP_INTERNAL_SERVER_ERROR, NULL, "Could not close target id: " + target_id); } return; } } SendJson(connection_id, net::HTTP_NOT_FOUND, NULL, "Unknown command: " + command); return; } DevToolsTargetDescriptor* DevToolsHttpHandler::GetDescriptor( const std::string& target_id) { DescriptorMap::const_iterator it = descriptor_map_.find(target_id); if (it == descriptor_map_.end()) return nullptr; return it->second; } void DevToolsHttpHandler::OnThumbnailRequest( int connection_id, const std::string& target_id) { DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id); GURL page_url; if (descriptor) page_url = descriptor->GetURL(); std::string data = delegate_->GetPageThumbnailData(page_url); if (!data.empty()) Send200(connection_id, data, "image/png"); else Send404(connection_id); } void DevToolsHttpHandler::OnDiscoveryPageRequest(int connection_id) { std::string response = delegate_->GetDiscoveryPageHTML(); Send200(connection_id, response, "text/html; charset=UTF-8"); } void DevToolsHttpHandler::OnFrontendResourceRequest( int connection_id, const std::string& path) { Send200(connection_id, delegate_->GetFrontendResource(path), GetMimeType(path)); } void DevToolsHttpHandler::OnWebSocketRequest( int connection_id, const net::HttpServerRequestInfo& request) { if (!thread_) return; std::string browser_prefix = "/devtools/browser"; size_t browser_pos = request.path.find(browser_prefix); if (browser_pos == 0) { scoped_refptr browser_agent = DevToolsAgentHost::CreateForBrowser( thread_->task_runner(), base::Bind(&ServerSocketFactory::CreateForTethering, base::Unretained(socket_factory_))); connection_to_client_[connection_id] = new DevToolsAgentHostClientImpl( thread_->message_loop(), server_wrapper_, connection_id, browser_agent); AcceptWebSocket(connection_id, request); return; } size_t pos = request.path.find(kPageUrlPrefix); if (pos != 0) { Send404(connection_id); return; } std::string target_id = request.path.substr(strlen(kPageUrlPrefix)); DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id); scoped_refptr agent = descriptor ? descriptor->GetAgentHost() : nullptr; if (!agent.get()) { Send500(connection_id, "No such target id: " + target_id); return; } if (agent->IsAttached()) { Send500(connection_id, "Target with given id is being inspected: " + target_id); return; } DevToolsAgentHostClientImpl* client_host = new DevToolsAgentHostClientImpl( thread_->message_loop(), server_wrapper_, connection_id, agent); connection_to_client_[connection_id] = client_host; AcceptWebSocket(connection_id, request); } void DevToolsHttpHandler::OnWebSocketMessage( int connection_id, const std::string& data) { ConnectionToClientMap::iterator it = connection_to_client_.find(connection_id); if (it != connection_to_client_.end()) it->second->OnMessage(data); } void DevToolsHttpHandler::OnClose(int connection_id) { ConnectionToClientMap::iterator it = connection_to_client_.find(connection_id); if (it != connection_to_client_.end()) { delete it->second; connection_to_client_.erase(connection_id); } } DevToolsHttpHandler::DevToolsHttpHandler( scoped_ptr server_socket_factory, const std::string& frontend_url, DevToolsHttpHandlerDelegate* delegate, const base::FilePath& output_directory, const base::FilePath& debug_frontend_dir, const std::string& product_name, const std::string& user_agent) : thread_(nullptr), frontend_url_(frontend_url), product_name_(product_name), user_agent_(user_agent), server_wrapper_(nullptr), delegate_(delegate), socket_factory_(nullptr), weak_factory_(this) { bool bundles_resources = frontend_url_.empty(); if (frontend_url_.empty()) frontend_url_ = "/devtools/inspector.html"; BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&StartServerOnFile, weak_factory_.GetWeakPtr(), server_socket_factory.release(), output_directory, debug_frontend_dir, bundles_resources)); } void DevToolsHttpHandler::ServerStarted( base::Thread* thread, ServerWrapper* server_wrapper, ServerSocketFactory* socket_factory, scoped_ptr ip_address) { thread_ = thread; server_wrapper_ = server_wrapper; socket_factory_ = socket_factory; server_ip_address_.swap(ip_address); } void ServerWrapper::WriteActivePortToUserProfile( const base::FilePath& output_directory) { DCHECK(!output_directory.empty()); net::IPEndPoint endpoint; int err; if ((err = server_->GetLocalAddress(&endpoint)) != net::OK) { LOG(ERROR) << "Error " << err << " getting local address"; return; } // Write this port to a well-known file in the profile directory // so Telemetry can pick it up. base::FilePath path = output_directory.Append(kDevToolsActivePortFileName); std::string port_string = base::IntToString(endpoint.port()); if (base::WriteFile(path, port_string.c_str(), static_cast(port_string.length())) < 0) { LOG(ERROR) << "Error writing DevTools active port to file"; } } void DevToolsHttpHandler::SendJson(int connection_id, net::HttpStatusCode status_code, base::Value* value, const std::string& message) { if (!thread_) return; // Serialize value and message. std::string json_value; if (value) { base::JSONWriter::WriteWithOptions( *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_value); } std::string json_message; base::JSONWriter::Write(base::StringValue(message), &json_message); net::HttpServerResponseInfo response(status_code); response.SetBody(json_value + message, "application/json; charset=UTF-8"); thread_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::SendResponse, base::Unretained(server_wrapper_), connection_id, response)); } void DevToolsHttpHandler::Send200(int connection_id, const std::string& data, const std::string& mime_type) { if (!thread_) return; thread_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::Send200, base::Unretained(server_wrapper_), connection_id, data, mime_type)); } void DevToolsHttpHandler::Send404(int connection_id) { if (!thread_) return; thread_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::Send404, base::Unretained(server_wrapper_), connection_id)); } void DevToolsHttpHandler::Send500(int connection_id, const std::string& message) { if (!thread_) return; thread_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::Send500, base::Unretained(server_wrapper_), connection_id, message)); } void DevToolsHttpHandler::AcceptWebSocket( int connection_id, const net::HttpServerRequestInfo& request) { if (!thread_) return; thread_->task_runner()->PostTask( FROM_HERE, base::Bind(&ServerWrapper::AcceptWebSocket, base::Unretained(server_wrapper_), connection_id, request)); } base::DictionaryValue* DevToolsHttpHandler::SerializeDescriptor( const DevToolsTargetDescriptor& descriptor, const std::string& host) { base::DictionaryValue* dictionary = new base::DictionaryValue; std::string id = descriptor.GetId(); dictionary->SetString(kTargetIdField, id); std::string parent_id = descriptor.GetParentId(); if (!parent_id.empty()) dictionary->SetString(kTargetParentIdField, parent_id); dictionary->SetString(kTargetTypeField, descriptor.GetType()); dictionary->SetString(kTargetTitleField, net::EscapeForHTML(descriptor.GetTitle())); dictionary->SetString(kTargetDescriptionField, descriptor.GetDescription()); GURL url = descriptor.GetURL(); dictionary->SetString(kTargetUrlField, url.spec()); GURL favicon_url = descriptor.GetFaviconURL(); if (favicon_url.is_valid()) dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec()); if (!delegate_->GetPageThumbnailData(url).empty()) { dictionary->SetString(kTargetThumbnailUrlField, std::string(kThumbUrlPrefix) + id); } if (!descriptor.IsAttached()) { dictionary->SetString(kTargetWebSocketDebuggerUrlField, base::StringPrintf("ws://%s%s%s", host.c_str(), kPageUrlPrefix, id.c_str())); std::string devtools_frontend_url = GetFrontendURLInternal( id.c_str(), host); dictionary->SetString( kTargetDevtoolsFrontendUrlField, devtools_frontend_url); } return dictionary; } } // namespace devtools_http_handler