diff options
22 files changed, 2042 insertions, 10 deletions
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS index e324a90..4e0fb74 100644 --- a/chrome/browser/DEPS +++ b/chrome/browser/DEPS @@ -18,6 +18,7 @@ include_rules = [ "+libxml", # For search engine definition parsing. "+media/audio", # Chrome's lightweight audio library. "+third_party/sqlite", + "+third_party/libevent", # For the remote V8 debugging server "+v8/include", # Browser uses V8 to get the version and run the debugger. # FIXME: this should probably not be here, we need to find a better diff --git a/chrome/browser/debugger/debugger.vcproj b/chrome/browser/debugger/debugger.vcproj index e40fb81..878f8aa 100644 --- a/chrome/browser/debugger/debugger.vcproj +++ b/chrome/browser/debugger/debugger.vcproj @@ -174,6 +174,14 @@ > </File> <File + RelativePath=".\debugger_remote_service.cc" + > + </File> + <File + RelativePath=".\debugger_remote_service.h" + > + </File> + <File RelativePath=".\debugger_shell.cc" > </File> @@ -218,6 +226,42 @@ > </File> <File + RelativePath=".\devtools_protocol_handler.cc" + > + </File> + <File + RelativePath=".\devtools_protocol_handler.h" + > + </File> + <File + RelativePath=".\devtools_remote.h" + > + </File> + <File + RelativePath=".\devtools_remote_listen_socket.cc" + > + </File> + <File + RelativePath=".\devtools_remote_listen_socket.h" + > + </File> + <File + RelativePath=".\devtools_remote_message.cc" + > + </File> + <File + RelativePath=".\devtools_remote_message.h" + > + </File> + <File + RelativePath=".\devtools_remote_service.cc" + > + </File> + <File + RelativePath=".\devtools_remote_service.h" + > + </File> + <File RelativePath=".\devtools_view.cc" > </File> @@ -237,6 +281,14 @@ RelativePath=".\devtools_window_win.h" > </File> + <File + RelativePath=".\inspectable_tab_proxy.cc" + > + </File> + <File + RelativePath=".\inspectable_tab_proxy.h" + > + </File> </Files> <Globals> </Globals> diff --git a/chrome/browser/debugger/debugger_remote_service.cc b/chrome/browser/debugger/debugger_remote_service.cc new file mode 100644 index 0000000..de7e364 --- /dev/null +++ b/chrome/browser/debugger/debugger_remote_service.cc @@ -0,0 +1,262 @@ +// Copyright (c) 2009 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 "chrome/browser/debugger/debugger_remote_service.h" + +#include "base/json_reader.h" +#include "base/json_writer.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/debugger/devtools_protocol_handler.h" +#include "chrome/browser/debugger/devtools_remote_message.h" +#include "chrome/browser/debugger/inspectable_tab_proxy.h" +#include "chrome/browser/tab_contents/web_contents.h" +#include "chrome/common/devtools_messages.h" +#include "chrome/common/render_messages.h" + +const std::string DebuggerRemoteServiceCommand::kAttach = "attach"; +const std::string DebuggerRemoteServiceCommand::kDetach = "detach"; +const std::string DebuggerRemoteServiceCommand::kDebuggerCommand = + "debugger_command"; +const std::string DebuggerRemoteServiceCommand::kEvaluateJavascript = + "evaluate_javascript"; + +const std::string DebuggerRemoteService::kToolName = "V8Debugger"; +const std::wstring DebuggerRemoteService::kDataWide = L"data"; +const std::wstring DebuggerRemoteService::kResultWide = L"result"; + +DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate) + : delegate_(delegate) {} + +DebuggerRemoteService::~DebuggerRemoteService() {} + +// message from remote debugger +void DebuggerRemoteService::HandleMessage( + const DevToolsRemoteMessage& message) { + static const std::wstring kCommandWide = L"command"; + const std::string destination = message.destination(); + scoped_ptr<Value> request(JSONReader::Read(message.content(), true)); + if (request.get() == NULL) { + // Bad JSON + NOTREACHED(); + return; + } + DictionaryValue* content; + if (!request->IsType(Value::TYPE_DICTIONARY)) { + NOTREACHED(); // Broken protocol :( + return; + } + content = static_cast<DictionaryValue*>(request.get()); + if (!content->HasKey(kCommandWide)) { + NOTREACHED(); // Broken protocol :( + return; + } + std::string command; + DictionaryValue response; + + content->GetString(kCommandWide, &command); + response.SetString(kCommandWide, command); + bool send_response = true; + if (destination.size() == 0) { + // Unknown command (bad format?) + NOTREACHED(); + response.SetInteger(kResultWide, Result::kUnknownCommand); + SendResponse(response, message.tool(), message.destination()); + return; + } + int32 tab_uid = -1; + StringToInt(destination, &tab_uid); + + if (command == DebuggerRemoteServiceCommand::kAttach) { + // TODO(apavlov): handle 0 for a new tab + response.SetString(kCommandWide, DebuggerRemoteServiceCommand::kAttach); + AttachTab(destination, &response); + } else if (command == DebuggerRemoteServiceCommand::kDetach) { + response.SetString(kCommandWide, DebuggerRemoteServiceCommand::kDetach); + DetachTab(destination, &response); + } else if (command == DebuggerRemoteServiceCommand::kDebuggerCommand) { + if (tab_uid != -1) { + DevToolsManager* manager = g_browser_process->devtools_manager(); + if (manager == NULL) { + response.SetInteger(kResultWide, Result::kDebuggerError); + } + WebContents* web_contents = ToWebContents(tab_uid); + if (web_contents != NULL) { + DevToolsClientHost* client_host = + manager->GetDevToolsClientHostFor(*web_contents); + if (client_host != NULL) { + std::string v8_command; + DictionaryValue* v8_command_value; + content->GetDictionary(kDataWide, &v8_command_value); + JSONWriter::Write(v8_command_value, false, &v8_command); + g_browser_process->devtools_manager()->ForwardToDevToolsAgent( + *client_host, DevToolsAgentMsg_DebuggerCommand(v8_command)); + send_response = false; + // Do not send response right now as the JSON will be received from + // the V8 debugger asynchronously + } else { + // tab_uid is not being debugged (Attach has not been invoked) + response.SetInteger(kResultWide, Result::kIllegalTabState); + } + } else { + // Unknown tab_uid from remote debugger + response.SetInteger(kResultWide, Result::kUnknownTab); + } + } else { + // Invalid tab_uid from remote debugger (perhaps NaN) + response.SetInteger(kResultWide, Result::kUnknownTab); + } + } else if (command == DebuggerRemoteServiceCommand::kEvaluateJavascript) { + if (tab_uid != -1) { + WebContents* web_contents = ToWebContents(tab_uid); + if (web_contents != NULL) { + RenderViewHost* rvh = web_contents->render_view_host(); + if (rvh != NULL) { + std::wstring javascript; + content->GetString(kDataWide, &javascript); + rvh->Send(new ViewMsg_ScriptEvalRequest( + rvh->routing_id(), L"", javascript)); + send_response = false; + } else { + // No RenderViewHost + response.SetInteger(kResultWide, Result::kDebuggerError); + } + } else { + // Unknown tab_uid from remote debugger + response.SetInteger(kResultWide, Result::kUnknownTab); + } + } + } else { + // Unknown command + NOTREACHED(); + response.SetInteger(kResultWide, Result::kUnknownCommand); + } + + if (send_response) { + SendResponse(response, message.tool(), message.destination()); + } +} + +void DebuggerRemoteService::SendResponse(const Value& response, + const std::string& tool, + const std::string& destination) { + std::string response_content; + JSONWriter::Write(&response, false, &response_content); + scoped_ptr<DevToolsRemoteMessage> response_message( + DevToolsRemoteMessageBuilder::instance().Create(tool, + destination, + response_content)); + delegate_->Send(*response_message.get()); +} + +WebContents* DebuggerRemoteService::ToWebContents(int32 tab_uid) { + const InspectableTabProxy::ControllersMap& navcon_map = + delegate_->inspectable_tab_proxy()->controllers_map(false); + InspectableTabProxy::ControllersMap::const_iterator it = + navcon_map.find(tab_uid); + if (it != navcon_map.end()) { + TabContents* tab_contents = it->second->active_contents(); + if (tab_contents == NULL) { + return NULL; + } else { + return tab_contents->AsWebContents(); + } + } else { + return NULL; + } +} + +void DebuggerRemoteService::DebuggerOutput(int32 tab_id, + const std::string& message) { + std::string content; + content.append("{\"command\":\"") + .append(DebuggerRemoteServiceCommand::kDebuggerCommand) + .append("\",\"result\":") + .append(IntToString(Result::kOk)) + .append(",\"data\":") + .append(message) + .append("}"); + scoped_ptr<DevToolsRemoteMessage> response_message( + DevToolsRemoteMessageBuilder::instance().Create( + kToolName, + IntToString(tab_id), + content)); + delegate_->Send(*(response_message.get())); +} + +void DebuggerRemoteService::AttachTab(const std::string& destination, + DictionaryValue* response) { + int32 tab_uid = -1; + StringToInt(destination, &tab_uid); + if (tab_uid < 0) { + // Bad tab_uid received from remote debugger (perhaps NaN) + response->SetInteger(kDataWide, Result::kUnknownTab); + return; + } + if (tab_uid == 0) { // single tab_uid + // We've been asked to open a new tab with URL. + // TODO(apavlov): implement + NOTIMPLEMENTED(); + response->SetInteger(kDataWide, Result::kUnknownTab); + return; + } + WebContents* web_contents = ToWebContents(tab_uid); + if (web_contents == NULL) { + // No active web contents with tab_uid + response->SetInteger(kDataWide, Result::kUnknownTab); + return; + } + if (g_browser_process->devtools_manager()->GetDevToolsClientHostFor( + *web_contents) == NULL) { + DevToolsClientHost* client_host = + InspectableTabProxy::NewClientHost(tab_uid, this); + DevToolsManager* manager = g_browser_process->devtools_manager(); + if (manager != NULL) { + manager->RegisterDevToolsClientHostFor(*web_contents, client_host); + manager->ForwardToDevToolsAgent(*client_host, DevToolsAgentMsg_Attach()); + response->SetInteger(kDataWide, Result::kOk); + } else { + response->SetInteger(kDataWide, Result::kDebuggerError); + } + } else { + // DevToolsClientHost for this tab already registered + response->SetInteger(kDataWide, Result::kIllegalTabState); + } +} + +void DebuggerRemoteService::DetachTab(const std::string& destination, + DictionaryValue* response) { + int32 tab_uid = -1; + StringToInt(destination, &tab_uid); + if (tab_uid == -1) { + // Bad tab_uid received from remote debugger (NaN) + response->SetInteger(kDataWide, Result::kUnknownTab); + return; + } + WebContents* web_contents = ToWebContents(tab_uid); + if (web_contents == NULL) { + // Unknown tab + response->SetInteger(kDataWide, Result::kUnknownTab); + } else { + DevToolsManager* manager = g_browser_process->devtools_manager(); + if (manager != NULL) { + DevToolsClientHost* client_host = + manager->GetDevToolsClientHostFor(*web_contents); + if (client_host != NULL) { + manager->ForwardToDevToolsAgent( + *client_host, DevToolsAgentMsg_Detach()); + client_host->InspectedTabClosing(); + response->SetInteger(kDataWide, Result::kOk); + } else { + // No client host registered + response->SetInteger(kDataWide, Result::kUnknownTab); + } + } else { + // No DevToolsManager + response->SetInteger(kResultWide, Result::kDebuggerError); + } + } +} diff --git a/chrome/browser/debugger/debugger_remote_service.h b/chrome/browser/debugger/debugger_remote_service.h new file mode 100644 index 0000000..da46863 --- /dev/null +++ b/chrome/browser/debugger/debugger_remote_service.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEBUGGER_REMOTE_SERVICE_H_ +#define CHROME_BROWSER_DEBUGGER_DEBUGGER_REMOTE_SERVICE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/debugger/devtools_remote.h" + +class DevToolsProtocolHandler; +class DevToolsRemoteMessage; +class DictionaryValue; +class Value; +class WebContents; + +// Contains constants for DebuggerRemoteService tool protocol commands +// (only V8-related). +struct DebuggerRemoteServiceCommand { + static const std::string kAttach; + static const std::string kDetach; + static const std::string kDebuggerCommand; + static const std::string kEvaluateJavascript; +}; + +// Handles V8 debugger-related messages from the remote debugger (like +// attach to V8 debugger, detach from V8 debugger, send command to V8 debugger) +// and proxies JSON messages from V8 debugger to the remote debugger. +class DebuggerRemoteService : public DevToolsRemoteListener { + public: + explicit DebuggerRemoteService(DevToolsProtocolHandler* delegate); + virtual ~DebuggerRemoteService(); + + // Handles a JSON message from the tab_id-associated V8 debugger. + void DebuggerOutput(int32 tab_id, const std::string& message); + + // DevToolsRemoteListener interface + virtual void HandleMessage(const DevToolsRemoteMessage& message); + + static const std::string kToolName; + + private: + // Operation result returned in the "result" field. + struct Result { + static const int kOk = 0; + static const int kIllegalTabState = 1; + static const int kUnknownTab = 2; + static const int kDebuggerError = 3; + static const int kUnknownCommand = 4; + }; + + void AttachTab(const std::string& destination, + DictionaryValue* response); + void DetachTab(const std::string& destination, + DictionaryValue* response); + WebContents* ToWebContents(int32 tab_uid); + void SendResponse(const Value& response, + const std::string& tool, + const std::string& destination); + static const std::wstring kDataWide; + static const std::wstring kResultWide; + DevToolsProtocolHandler* delegate_; + DISALLOW_COPY_AND_ASSIGN(DebuggerRemoteService); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_REMOTE_SERVICE_H_ diff --git a/chrome/browser/debugger/debugger_wrapper.cc b/chrome/browser/debugger/debugger_wrapper.cc index 674b092..8033a83 100644 --- a/chrome/browser/debugger/debugger_wrapper.cc +++ b/chrome/browser/debugger/debugger_wrapper.cc @@ -3,21 +3,43 @@ // found in the LICENSE file. #include "chrome/browser/debugger/debugger_wrapper.h" + +#include "base/command_line.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/debugger/debugger_shell.h" #include "chrome/browser/debugger/debugger_io_socket.h" #include "chrome/browser/debugger/debugger_host.h" +#include "chrome/browser/debugger/debugger_remote_service.h" +#include "chrome/browser/debugger/devtools_protocol_handler.h" +#include "chrome/browser/debugger/devtools_remote_service.h" +#include "chrome/common/chrome_switches.h" DebuggerWrapper::DebuggerWrapper(int port) { #ifndef CHROME_DEBUGGER_DISABLED if (port > 0) { - DebuggerInputOutputSocket *io = new DebuggerInputOutputSocket(port); - debugger_ = new DebuggerShell(io); - debugger_->Start(); + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableOutOfProcessDevTools)) { + DebuggerInputOutputSocket *io = new DebuggerInputOutputSocket(port); + debugger_ = new DebuggerShell(io); + debugger_->Start(); + } else { + proto_handler_ = new DevToolsProtocolHandler(port); + proto_handler_->RegisterDestination( + new DevToolsRemoteService(proto_handler_), + DevToolsRemoteService::kToolName); + proto_handler_->RegisterDestination( + new DebuggerRemoteService(proto_handler_), + DebuggerRemoteService::kToolName); + proto_handler_->Start(); + } } #endif } DebuggerWrapper::~DebuggerWrapper() { + if (proto_handler_.get() != NULL) { + proto_handler_->Stop(); + } } void DebuggerWrapper::SetDebugger(DebuggerHost* debugger) { diff --git a/chrome/browser/debugger/debugger_wrapper.h b/chrome/browser/debugger/debugger_wrapper.h index f1e68cc..59184df 100644 --- a/chrome/browser/debugger/debugger_wrapper.h +++ b/chrome/browser/debugger/debugger_wrapper.h @@ -15,19 +15,22 @@ // of the debugger files without CHROME_DEBUGGER_DISABLED so the full // functionality is enabled. -#ifndef CHROME_BROWSER_DEBUGGER_DEBUGGER_INTERFACE_H_ -#define CHROME_BROWSER_DEBUGGER_DEBUGGER_INTERFACE_H_ +#ifndef CHROME_BROWSER_DEBUGGER_DEBUGGER_WRAPPER_H_ +#define CHROME_BROWSER_DEBUGGER_DEBUGGER_WRAPPER_H_ #include <string> #include "base/basictypes.h" #include "base/ref_counted.h" +#include "base/scoped_ptr.h" class DebuggerHost; +class DevToolsProtocolHandler; +class DevToolsRemoteListenSocket; class DebuggerWrapper : public base::RefCountedThreadSafe<DebuggerWrapper> { public: - DebuggerWrapper(int port); + explicit DebuggerWrapper(int port); virtual ~DebuggerWrapper(); @@ -41,6 +44,7 @@ class DebuggerWrapper : public base::RefCountedThreadSafe<DebuggerWrapper> { private: scoped_refptr<DebuggerHost> debugger_; + scoped_refptr<DevToolsProtocolHandler> proto_handler_; }; -#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_INTERFACE_H_ +#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_WRAPPER_H_ diff --git a/chrome/browser/debugger/devtools_protocol_handler.cc b/chrome/browser/debugger/devtools_protocol_handler.cc new file mode 100644 index 0000000..5350298 --- /dev/null +++ b/chrome/browser/debugger/devtools_protocol_handler.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 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 "chrome/browser/debugger/devtools_protocol_handler.h" + +#include "base/logging.h" +#include "base/thread.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/debugger/inspectable_tab_proxy.h" +#include "chrome/browser/debugger/devtools_remote_message.h" +#include "chrome/browser/debugger/devtools_remote_listen_socket.h" +#include "chrome/browser/tab_contents/tab_contents.h" + +DevToolsProtocolHandler::DevToolsProtocolHandler(int port) + : port_(port), + connection_(NULL), + server_(NULL) { + ui_loop_ = MessageLoop::current(); + io_loop_ = g_browser_process->io_thread()->message_loop(); + inspectable_tab_proxy_.reset(new InspectableTabProxy); +} + +DevToolsProtocolHandler::~DevToolsProtocolHandler() { + // Stop() must be called prior to this being called + DCHECK(server_.get() == NULL); + DCHECK(connection_.get() == NULL); +} + +void DevToolsProtocolHandler::Start() { + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &DevToolsProtocolHandler::Init)); +} + +void DevToolsProtocolHandler::Init() { + server_ = DevToolsRemoteListenSocket::Listen( + "127.0.0.1", port_, this, this); +} + +void DevToolsProtocolHandler::Stop() { + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &DevToolsProtocolHandler::Teardown)); + tool_to_listener_map_.clear(); // Releases all scoped_refptr's to listeners +} + +// Run in I/O thread +void DevToolsProtocolHandler::Teardown() { + connection_ = NULL; + server_ = NULL; +} + +void DevToolsProtocolHandler::RegisterDestination( + DevToolsRemoteListener* listener, + const std::string& tool_name) { + DCHECK(tool_to_listener_map_.find(tool_name) == tool_to_listener_map_.end()); + tool_to_listener_map_.insert(std::make_pair(tool_name, listener)); +} + +void DevToolsProtocolHandler::UnregisterDestination( + DevToolsRemoteListener* listener, + const std::string& tool_name) { + DCHECK(tool_to_listener_map_.find(tool_name) != tool_to_listener_map_.end()); + DCHECK(tool_to_listener_map_.find(tool_name)->second == listener); + tool_to_listener_map_.erase(tool_name); +} + +void DevToolsProtocolHandler::HandleMessage( + const DevToolsRemoteMessage& message) { + std::string tool = message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kTool); + ToolToListenerMap::const_iterator it = tool_to_listener_map_.find(tool); + if (it == tool_to_listener_map_.end()) { + NOTREACHED(); // an unsupported tool, bail out + return; + } + DCHECK(MessageLoop::current() == io_loop_); + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( + it->second.get(), &DevToolsRemoteListener::HandleMessage, message)); +} + +void DevToolsProtocolHandler::Send(const DevToolsRemoteMessage& message) { + if (connection_ != NULL) { + connection_->Send(message.ToString()); + } +} + +void DevToolsProtocolHandler::DidAccept(ListenSocket *server, + ListenSocket *connection) { + DCHECK(MessageLoop::current() == io_loop_); + if (connection_ == NULL) { + connection_ = connection; + connection_->AddRef(); + } + // else the connection will get deleted itself with scoped_refptr +} + +void DevToolsProtocolHandler::DidRead(ListenSocket *connection, + const std::string& data) { + // This method is not used. +} + +void DevToolsProtocolHandler::DidClose(ListenSocket *sock) { + DCHECK(MessageLoop::current() == io_loop_); + DCHECK(connection_ == sock); + connection_ = NULL; + sock->Release(); +} diff --git a/chrome/browser/debugger/devtools_protocol_handler.h b/chrome/browser/debugger/devtools_protocol_handler.h new file mode 100644 index 0000000..4af8ebb --- /dev/null +++ b/chrome/browser/debugger/devtools_protocol_handler.h @@ -0,0 +1,81 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEVTOOLS_PROTOCOL_HANDLER_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_PROTOCOL_HANDLER_H_ + +#include <string> + +#include "base/hash_tables.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/debugger/devtools_remote.h" +#include "net/base/listen_socket.h" + +class InspectableTabProxy; +class DevToolsRemoteListenSocket; +class DevToolsRemoteMessage; +class WebContents; + +// Dispatches DevToolsRemoteMessages to their appropriate handlers (Tools) +// based on the value of the Tool message header. +class DevToolsProtocolHandler + : public DevToolsRemoteListener, + public OutboundSocketDelegate, + public ListenSocket::ListenSocketDelegate { + public: + typedef base::hash_map< std::string, scoped_refptr<DevToolsRemoteListener> > + ToolToListenerMap; + + explicit DevToolsProtocolHandler(int port); + virtual ~DevToolsProtocolHandler(); + + // This method should be called after the object construction. + void Start(); + + // This method should be called before the object destruction. + void Stop(); + + // Registers a |listener| to handle messages for a certain |tool_name| Tool. + // |listener| is the new message handler to register. + // As DevToolsRemoteListener inherits base::RefCountedThreadSafe, + // you should have no problems with ownership and destruction. + // |tool_name| is the name of the Tool to associate the listener with. + void RegisterDestination(DevToolsRemoteListener* listener, + const std::string& tool_name); + + // Unregisters a |listener| so that it will no longer handle messages + // directed to the specified |tool_name| tool. + void UnregisterDestination(DevToolsRemoteListener* listener, + const std::string& tool_name); + + InspectableTabProxy* inspectable_tab_proxy() { + return inspectable_tab_proxy_.get(); + } + + // DevToolsRemoteListener interface + virtual void HandleMessage(const DevToolsRemoteMessage& message); + + // OutboundSocketDelegate interface + virtual void Send(const DevToolsRemoteMessage& message); + + // ListenSocket::ListenSocketDelegate interface + virtual void DidAccept(ListenSocket *server, ListenSocket *connection); + virtual void DidRead(ListenSocket *connection, const std::string& data); + virtual void DidClose(ListenSocket *sock); + + private: + void Init(); + void Teardown(); + int port_; + MessageLoop* ui_loop_; + MessageLoop* io_loop_; + ToolToListenerMap tool_to_listener_map_; + scoped_refptr<ListenSocket> connection_; + scoped_refptr<DevToolsRemoteListenSocket> server_; + scoped_ptr<InspectableTabProxy> inspectable_tab_proxy_; + DISALLOW_COPY_AND_ASSIGN(DevToolsProtocolHandler); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_PROTOCOL_HANDLER_H_ diff --git a/chrome/browser/debugger/devtools_remote.h b/chrome/browser/debugger/devtools_remote.h new file mode 100644 index 0000000..c475030 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_H_ + +#include "base/basictypes.h" +#include "base/ref_counted.h" + +class DevToolsRemoteMessage; + +// This interface should be implemented by a class that wants to handle +// DevToolsRemoteMessages dispatched by some entity. It must extend +class DevToolsRemoteListener + : public base::RefCountedThreadSafe<DevToolsRemoteListener> { + public: + DevToolsRemoteListener() {} + virtual ~DevToolsRemoteListener() {} + virtual void HandleMessage(const DevToolsRemoteMessage& message) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(DevToolsRemoteListener); +}; + +// Interface exposed by DevToolsProtocolHandler to receive reply messages +// from registered tools. +class OutboundSocketDelegate { + public: + virtual ~OutboundSocketDelegate() {} + virtual void Send(const DevToolsRemoteMessage& message) = 0; +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_H_ diff --git a/chrome/browser/debugger/devtools_remote_listen_socket.cc b/chrome/browser/debugger/devtools_remote_listen_socket.cc new file mode 100644 index 0000000..f064c0c --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_listen_socket.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2009 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 "chrome/browser/debugger/devtools_remote_listen_socket.h" + +#include "build/build_config.h" + +#include <stdlib.h> + +#if defined(OS_WIN) +// winsock2.h must be included first in order to ensure it is included before +// windows.h. +#include <winsock2.h> +#elif defined(OS_POSIX) +#include <errno.h> +#include <sys/socket.h> +#include "base/message_loop.h" +#include "base/message_pump_libevent.h" +#include "net/base/net_errors.h" +#include "third_party/libevent/event.h" +#endif + +#include "base/string_util.h" +#include "chrome/browser/debugger/devtools_remote.h" +#include "chrome/browser/debugger/devtools_remote_message.h" + +#define CONSUME_BUFFER_CHAR \ + pBuf++;\ + len-- + +#if defined(OS_POSIX) +// Used same name as in Windows to avoid #ifdef where refrenced +#define SOCKET int +const int INVALID_SOCKET = -1; +const int SOCKET_ERROR = -1; +struct event; // From libevent +#endif + +const int kReadBufSize = 200; + +DevToolsRemoteListenSocket::DevToolsRemoteListenSocket( + SOCKET s, + ListenSocketDelegate* del, + DevToolsRemoteListener* message_listener) + : ListenSocket(s, del), + state_(HANDSHAKE), + message_listener_(message_listener) {} + +void DevToolsRemoteListenSocket::StartNextField() { + switch (state_) { + case INVALID: + state_ = HANDSHAKE; + break; + case HANDSHAKE: + state_ = HEADERS; + break; + case HEADERS: + if (protocol_field_.size() == 0) { // empty line - end of headers + const std::string& payload_length_string = GetHeader( + DevToolsRemoteMessageHeaders::kContentLength, "0"); + remaining_payload_length_ = StringToInt(payload_length_string); + state_ = PAYLOAD; + if (remaining_payload_length_ == 0) { // no payload + DispatchField(); + return; + } + } + break; + case PAYLOAD: + header_map_.clear(); + payload_.clear(); + state_ = HEADERS; + break; + default: + NOTREACHED(); + break; + } + protocol_field_.clear(); +} + +DevToolsRemoteListenSocket::~DevToolsRemoteListenSocket() {} + +DevToolsRemoteListenSocket* + DevToolsRemoteListenSocket::Listen(const std::string& ip, + int port, + ListenSocketDelegate* del, + DevToolsRemoteListener* listener) { + SOCKET s = ListenSocket::Listen(ip, port); + if (s == INVALID_SOCKET) { + // TODO(apavlov): error handling + } else { + DevToolsRemoteListenSocket* sock = + new DevToolsRemoteListenSocket(s, del, listener); + sock->Listen(); + return sock; + } + return NULL; +} + +void DevToolsRemoteListenSocket::Read() { + char buf[kReadBufSize]; + int len; + do { + len = recv(socket_, buf, kReadBufSize, 0); + if (len == SOCKET_ERROR) { +#if defined(OS_WIN) + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { +#elif defined(OS_POSIX) + if (errno == EWOULDBLOCK || errno == EAGAIN) { +#endif + break; + } else { + // TODO(apavlov): some error handling required here + break; + } + } else if (len == 0) { + // In Windows, Close() is called by OnObjectSignaled. In POSIX, we need + // to call it here. +#if defined(OS_POSIX) + Close(); +#endif + } else { + // TODO(apavlov): maybe change DidRead to take a length instead + DCHECK(len > 0 && len <= kReadBufSize); + this->DispatchRead(buf, len); + } + } while (len == kReadBufSize); +} + +// Dispatches data from socket to socket_delegate_, extracting messages +// delimited by newlines. +void DevToolsRemoteListenSocket::DispatchRead(char* buf, int len) { + char* pBuf = buf; + while (len > 0) { + if (state_ != PAYLOAD) { + while (*pBuf != '\r' && len > 0) { + protocol_field_.push_back(*pBuf); + CONSUME_BUFFER_CHAR; + } + if (*pBuf != '\r') { + continue; + } else { + CONSUME_BUFFER_CHAR; + if (*pBuf != '\n') { + continue; + } else { + CONSUME_BUFFER_CHAR; // handle the \r\n series + } + } + switch (state_) { + case HANDSHAKE: + case HEADERS: + DispatchField(); + break; + default: + NOTREACHED(); + break; + } + } else { // PAYLOAD + while (remaining_payload_length_ > 0 && len > 0) { + protocol_field_.push_back(*pBuf); + CONSUME_BUFFER_CHAR; + remaining_payload_length_--; + } + if (remaining_payload_length_ == 0) { + DispatchField(); + } + } + } +} + +void DevToolsRemoteListenSocket::DispatchField() { + static const std::string kHandshakeString = "ChromeDevToolsHandshake"; + switch (state_) { + case HANDSHAKE: + if (protocol_field_.compare(kHandshakeString)) { + state_ = INVALID; + } else { + Send(kHandshakeString, true); + } + break; + case HEADERS: { + if (protocol_field_.size() > 0) { // not end-of-headers + std::string::size_type colon_pos = protocol_field_.find_first_of(":"); + if (colon_pos == std::string::npos) { + // TODO(apavlov): handle the error (malformed header) + } else { + const std::string header_name = protocol_field_.substr(0, colon_pos); + std::string header_val = protocol_field_.substr(colon_pos + 1); + header_map_[header_name] = header_val; + } + } + break; + } + case PAYLOAD: + payload_ = protocol_field_; + HandleMessage(); + break; + default: + NOTREACHED(); + break; + } + StartNextField(); +} + +const std::string& DevToolsRemoteListenSocket::GetHeader( + const std::string& header_name, + const std::string& default_value) const { + DevToolsRemoteMessage::HeaderMap::const_iterator it = + header_map_.find(header_name); + if (it == header_map_.end()) { + return default_value; + } + return it->second; +} + +// Handle header_map_ and payload_ +void DevToolsRemoteListenSocket::HandleMessage() { + if (message_listener_ != NULL) { + DevToolsRemoteMessage message(header_map_, payload_); + message_listener_->HandleMessage(message); + } +} + +void DevToolsRemoteListenSocket::Accept() { + SOCKET conn = ListenSocket::Accept(socket_); + if (conn != INVALID_SOCKET) { + scoped_refptr<DevToolsRemoteListenSocket> sock = + new DevToolsRemoteListenSocket(conn, + socket_delegate_, + message_listener_); + // it's up to the delegate to AddRef if it wants to keep it around +#if defined(OS_POSIX) + sock->WatchSocket(WAITING_READ); +#endif + socket_delegate_->DidAccept(this, sock); + } else { + // TODO(apavlov): some error handling required here + } +} + +void DevToolsRemoteListenSocket::Close() { + ListenSocket::Close(); +} diff --git a/chrome/browser/debugger/devtools_remote_listen_socket.h b/chrome/browser/debugger/devtools_remote_listen_socket.h new file mode 100644 index 0000000..d671bf2 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_listen_socket.h @@ -0,0 +1,65 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_LISTEN_SOCKET_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_LISTEN_SOCKET_H_ + +#include <string> + +#include "base/hash_tables.h" +#include "chrome/browser/debugger/devtools_remote_message.h" +#include "net/base/listen_socket.h" + +class DevToolsRemoteListener; + +// Listens to remote debugger incoming connections, handles the V8ARDP protocol +// socket input and invokes the message handler when appropriate. +class DevToolsRemoteListenSocket : public ListenSocket { + public: + // Listen on port for the specified IP address. Use 127.0.0.1 to only + // accept local connections. + static DevToolsRemoteListenSocket* Listen( + const std::string& ip, + int port, + ListenSocketDelegate* del, + DevToolsRemoteListener* message_listener); + virtual ~DevToolsRemoteListenSocket(); + + protected: + virtual void Listen() { ListenSocket::Listen(); } + virtual void Accept(); + virtual void Read(); + virtual void Close(); + + private: + + // The protocol states while reading socket input + enum State { + INVALID = 0, // Bad handshake message received, retry + HANDSHAKE = 1, // Receiving handshake message + HEADERS = 2, // Receiving protocol headers + PAYLOAD = 3 // Receiving payload + }; + + DevToolsRemoteListenSocket(SOCKET s, + ListenSocketDelegate *del, + DevToolsRemoteListener *listener); + void StartNextField(); + void HandleMessage(); + void DispatchRead(char* buf, int len); + void DispatchField(); + const std::string& GetHeader(const std::string& header_name, + const std::string& default_value) const; + + State state_; + DevToolsRemoteMessage::HeaderMap header_map_; + std::string protocol_field_; + std::string payload_; + int32 remaining_payload_length_; + DevToolsRemoteListener* message_listener_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsRemoteListenSocket); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_LISTEN_SOCKET_H_ diff --git a/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc new file mode 100644 index 0000000..a59c992 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc @@ -0,0 +1,356 @@ +// Copyright (c) 2006-2008 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 "chrome/browser/debugger/devtools_remote_listen_socket_unittest.h" + +#include <fcntl.h> + +#include "net/base/net_util.h" +#include "testing/platform_test.h" + +const int DevToolsRemoteListenSocketTester::kTestPort = 9999; + +static const int kReadBufSize = 1024; +static const char* kChromeDevToolsHandshake = "ChromeDevToolsHandshake\r\n"; +static const char* kSimpleMessage = + "Tool:V8Debugger\r\n" + "Destination:2\r\n" + "Content-Length:0\r\n" + "\r\n"; +static const char* kTwoMessages = + "Tool:DevToolsService\r\n" + "Content-Length:300\r\n" + "\r\n" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000" + "Tool:V8Debugger\r\n" + "Destination:1\r\n" + "Content-Length:0\r\n" + "\r\n"; + +static const int kMaxQueueSize = 20; +static const char* kLoopback = "127.0.0.1"; +static const int kDefaultTimeoutMs = 5000; +#if defined(OS_POSIX) +static const char* kSemaphoreName = "chromium.listen_socket"; +#endif + + +ListenSocket* DevToolsRemoteListenSocketTester::DoListen() { + return DevToolsRemoteListenSocket::Listen(kLoopback, kTestPort, this, this); +} + +void DevToolsRemoteListenSocketTester::SetUp() { +#if defined(OS_WIN) + InitializeCriticalSection(&lock_); + semaphore_ = CreateSemaphore(NULL, 0, kMaxQueueSize, NULL); + server_ = NULL; + net::EnsureWinsockInit(); +#elif defined(OS_POSIX) + ASSERT_EQ(0, pthread_mutex_init(&lock_, NULL)); + sem_unlink(kSemaphoreName); + semaphore_ = sem_open(kSemaphoreName, O_CREAT, 0, 0); + ASSERT_NE(SEM_FAILED, semaphore_); +#endif + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + thread_.reset(new base::Thread("socketio_test")); + thread_->StartWithOptions(options); + loop_ = static_cast<MessageLoopForIO*>(thread_->message_loop()); + + loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &DevToolsRemoteListenSocketTester::Listen)); + + // verify Listen succeeded + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_FALSE(server_ == NULL); + ASSERT_EQ(ACTION_LISTEN, last_action_.type()); + + // verify the connect/accept and setup test_socket_ + test_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + struct sockaddr_in client; + client.sin_family = AF_INET; + client.sin_addr.s_addr = inet_addr(kLoopback); + client.sin_port = htons(kTestPort); + int ret = connect(test_socket_, + reinterpret_cast<sockaddr*>(&client), sizeof(client)); + ASSERT_NE(ret, SOCKET_ERROR); + + net::SetNonBlocking(test_socket_); + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_ACCEPT, last_action_.type()); +} + +void DevToolsRemoteListenSocketTester::TearDown() { + // verify close +#if defined(OS_WIN) + closesocket(test_socket_); +#elif defined(OS_POSIX) + close(test_socket_); +#endif + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_CLOSE, last_action_.type()); + + loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &DevToolsRemoteListenSocketTester::Shutdown)); + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_SHUTDOWN, last_action_.type()); + +#if defined(OS_WIN) + CloseHandle(semaphore_); + semaphore_ = 0; + DeleteCriticalSection(&lock_); +#elif defined(OS_POSIX) + ASSERT_EQ(0, pthread_mutex_lock(&lock_)); + semaphore_ = NULL; + ASSERT_EQ(0, pthread_mutex_unlock(&lock_)); + ASSERT_EQ(0, sem_unlink(kSemaphoreName)); + ASSERT_EQ(0, pthread_mutex_destroy(&lock_)); +#endif + + thread_.reset(); + loop_ = NULL; +} + +void DevToolsRemoteListenSocketTester::ReportAction( + const ListenSocketTestAction& action) { +#if defined(OS_WIN) + EnterCriticalSection(&lock_); + queue_.push_back(action); + LeaveCriticalSection(&lock_); + ReleaseSemaphore(semaphore_, 1, NULL); +#elif defined(OS_POSIX) + ASSERT_EQ(0, pthread_mutex_lock(&lock_)); + queue_.push_back(action); + ASSERT_EQ(0, pthread_mutex_unlock(&lock_)); + ASSERT_EQ(0, sem_post(semaphore_)); +#endif +} + +bool DevToolsRemoteListenSocketTester::NextAction(int timeout) { +#if defined(OS_WIN) + DWORD ret = ::WaitForSingleObject(semaphore_, timeout); + if (ret != WAIT_OBJECT_0) + return false; + EnterCriticalSection(&lock_); + if (queue_.size() == 0) { + LeaveCriticalSection(&lock_); + return false; + } + last_action_ = queue_.front(); + queue_.pop_front(); + LeaveCriticalSection(&lock_); + return true; +#elif defined(OS_POSIX) + if (semaphore_ == SEM_FAILED) + return false; + while (true) { + int result = sem_trywait(semaphore_); + PlatformThread::Sleep(1); // 1MS sleep + timeout--; + if (timeout <= 0) + return false; + if (result == 0) + break; + } + pthread_mutex_lock(&lock_); + if (queue_.size() == 0) { + pthread_mutex_unlock(&lock_); + return false; + } + last_action_ = queue_.front(); + queue_.pop_front(); + pthread_mutex_unlock(&lock_); + return true; +#endif +} + +int DevToolsRemoteListenSocketTester::ClearTestSocket() { + char buf[kReadBufSize]; + int len_ret = 0; + int time_out = 0; + do { + int len = recv(test_socket_, buf, kReadBufSize, 0); +#if defined(OS_WIN) + if (len == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { +#elif defined(OS_POSIX) + if (len == SOCKET_ERROR) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { +#endif + PlatformThread::Sleep(1); + time_out++; + if (time_out > 10) + break; + continue; // still trying + } + } else if (len == 0) { + // socket closed + break; + } else { + time_out = 0; + len_ret += len; + } + } while (true); + return len_ret; +} + +void DevToolsRemoteListenSocketTester::Shutdown() { + connection_->Release(); + connection_ = NULL; + server_->Release(); + server_ = NULL; + ReportAction(ListenSocketTestAction(ACTION_SHUTDOWN)); +} + +void DevToolsRemoteListenSocketTester::Listen() { + server_ = DoListen(); + if (server_) { + server_->AddRef(); + ReportAction(ListenSocketTestAction(ACTION_LISTEN)); + } +} + +void DevToolsRemoteListenSocketTester::SendFromTester() { + connection_->Send(kChromeDevToolsHandshake); + ReportAction(ListenSocketTestAction(ACTION_SEND)); +} + +void DevToolsRemoteListenSocketTester::DidAccept(ListenSocket *server, + ListenSocket *connection) { + connection_ = connection; + connection_->AddRef(); + ReportAction(ListenSocketTestAction(ACTION_ACCEPT)); +} + +void DevToolsRemoteListenSocketTester::DidRead(ListenSocket *connection, + const std::string& data) { + ReportAction(ListenSocketTestAction(ACTION_READ, data)); +} + +void DevToolsRemoteListenSocketTester::DidClose(ListenSocket *sock) { + ReportAction(ListenSocketTestAction(ACTION_CLOSE)); +} + +void DevToolsRemoteListenSocketTester::HandleMessage( + const DevToolsRemoteMessage& message) { + ReportAction(ListenSocketTestAction(ACTION_READ_MESSAGE, message)); +} + +bool DevToolsRemoteListenSocketTester::Send(SOCKET sock, + const std::string& str) { + int len = static_cast<int>(str.length()); + int send_len = send(sock, str.data(), len, 0); + if (send_len == SOCKET_ERROR) { + LOG(ERROR) << "send failed: " << errno; + return false; + } else if (send_len != len) { + return false; + } + return true; +} + +void DevToolsRemoteListenSocketTester::TestClientSend() { + ASSERT_TRUE(Send(test_socket_, kChromeDevToolsHandshake)); + { + ASSERT_TRUE(Send(test_socket_, kSimpleMessage)); + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_READ_MESSAGE, last_action_.type()); + const DevToolsRemoteMessage& message = last_action_.message(); + ASSERT_STREQ("V8Debugger", message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kTool).c_str()); + ASSERT_STREQ("2", message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kDestination).c_str()); + ASSERT_STREQ("0", message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kContentLength).c_str()); + ASSERT_EQ(0, static_cast<int>(message.content().size())); + } + ASSERT_TRUE(Send(test_socket_, kTwoMessages)); + { + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_READ_MESSAGE, last_action_.type()); + const DevToolsRemoteMessage& message = last_action_.message(); + ASSERT_STREQ("DevToolsService", message.tool().c_str()); + ASSERT_STREQ("", message.destination().c_str()); + ASSERT_EQ(300, message.content_length()); + const std::string& content = message.content(); + ASSERT_EQ(300, static_cast<int>(content.size())); + for (int i = 0; i < 300; ++i) { + ASSERT_EQ('0', content[i]); + } + } + { + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_READ_MESSAGE, last_action_.type()); + const DevToolsRemoteMessage& message = last_action_.message(); + ASSERT_STREQ("V8Debugger", message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kTool).c_str()); + ASSERT_STREQ("1", message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kDestination).c_str()); + ASSERT_STREQ("0", message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kContentLength).c_str()); + const std::string& content = message.content(); + ASSERT_EQ(0, static_cast<int>(content.size())); + } +} + +void DevToolsRemoteListenSocketTester::TestServerSend() { + loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &DevToolsRemoteListenSocketTester::SendFromTester)); + ASSERT_TRUE(NextAction(kDefaultTimeoutMs)); + ASSERT_EQ(ACTION_SEND, last_action_.type()); + // TODO(erikkay): Without this sleep, the recv seems to fail a small amount + // of the time. I could fix this by making the socket blocking, but then + // this test might hang in the case of errors. It would be nice to do + // something that felt more reliable here. + PlatformThread::Sleep(10); // sleep for 10ms + const int buf_len = 200; + char buf[buf_len+1]; + int recv_len; + do { + recv_len = recv(test_socket_, buf, buf_len, 0); +#if defined(OS_POSIX) + } while (recv_len == SOCKET_ERROR && errno == EINTR); +#else + } while (false); +#endif + ASSERT_NE(recv_len, SOCKET_ERROR); + buf[recv_len] = 0; + ASSERT_STREQ(buf, kChromeDevToolsHandshake); +} + + +class DevToolsRemoteListenSocketTest: public PlatformTest { + public: + DevToolsRemoteListenSocketTest() { + tester_ = NULL; + } + + virtual void SetUp() { + PlatformTest::SetUp(); + tester_ = new DevToolsRemoteListenSocketTester(); + tester_->SetUp(); + } + + virtual void TearDown() { + PlatformTest::TearDown(); + tester_->TearDown(); + tester_ = NULL; + } + + scoped_refptr<DevToolsRemoteListenSocketTester> tester_; +}; + +TEST_F(DevToolsRemoteListenSocketTest, ServerSend) { + tester_->TestServerSend(); +} + +TEST_F(DevToolsRemoteListenSocketTest, ClientSend) { + tester_->TestClientSend(); +} diff --git a/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h new file mode 100644 index 0000000..73d8714 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h @@ -0,0 +1,139 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_LISTEN_SOCKET_UNITTEST_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_LISTEN_SOCKET_UNITTEST_H_ + +#include "build/build_config.h" + +#include <deque> +#include <string> + +#if defined(OS_WIN) +#include <winsock2.h> +#elif defined(OS_POSIX) +#include <sys/socket.h> +#include <errno.h> +#include <semaphore.h> +#include <arpa/inet.h> +#endif + +#include "base/thread.h" +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "chrome/browser/debugger/devtools_remote.h" +#include "chrome/browser/debugger/devtools_remote_listen_socket.h" +#include "chrome/browser/debugger/devtools_remote_message.h" +#include "net/base/net_util.h" +#include "net/base/listen_socket.h" +#include "net/base/winsock_init.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +// Used same name as in Windows to avoid #ifdef where refrenced +#define SOCKET int +const int INVALID_SOCKET = -1; +const int SOCKET_ERROR = -1; +#endif + +enum ActionType { + ACTION_NONE = 0, + ACTION_LISTEN = 1, + ACTION_ACCEPT = 2, + ACTION_READ = 3, + ACTION_READ_MESSAGE = 4, + ACTION_SEND = 5, + ACTION_CLOSE = 6, + ACTION_SHUTDOWN = 7 +}; + +class ListenSocketTestAction { + public: + ListenSocketTestAction() : action_(ACTION_NONE) {} + explicit ListenSocketTestAction(ActionType action) + : action_(action) {} + ListenSocketTestAction(ActionType action, std::string data) + : action_(action), + data_(data) {} + ListenSocketTestAction(ActionType action, + const DevToolsRemoteMessage& message) + : action_(action), + message_(message) {} + + const std::string data() const { return data_; } + const DevToolsRemoteMessage message() { return message_; } + const ActionType type() const { return action_; } + + private: + ActionType action_; + std::string data_; + DevToolsRemoteMessage message_; +}; + + +// This had to be split out into a separate class because I couldn't +// make a the testing::Test class refcounted. +class DevToolsRemoteListenSocketTester : + public ListenSocket::ListenSocketDelegate, + public DevToolsRemoteListener { + public: + DevToolsRemoteListenSocketTester() + : thread_(NULL), + loop_(NULL), + server_(NULL), + connection_(NULL) { + } + + virtual ~DevToolsRemoteListenSocketTester() { + } + + virtual void SetUp(); + virtual void TearDown(); + + void ReportAction(const ListenSocketTestAction& action); + bool NextAction(int timeout); + + // DevToolsRemoteMessageHandler interface + virtual void HandleMessage(const DevToolsRemoteMessage& message); + + // read all pending data from the test socket + int ClearTestSocket(); + // Release the connection and server sockets + void Shutdown(); + void Listen(); + void SendFromTester(); + virtual void DidAccept(ListenSocket *server, ListenSocket *connection); + virtual void DidRead(ListenSocket *connection, const std::string& data); + virtual void DidClose(ListenSocket *sock); + virtual bool Send(SOCKET sock, const std::string& str); + // verify the send/read from client to server + void TestClientSend(); + // verify a send/read from server to client + void TestServerSend(); + +#if defined(OS_WIN) + CRITICAL_SECTION lock_; + HANDLE semaphore_; +#elif defined(OS_POSIX) + pthread_mutex_t lock_; + sem_t* semaphore_; +#endif + + scoped_ptr<base::Thread> thread_; + MessageLoopForIO* loop_; + ListenSocket* server_; + ListenSocket* connection_; + ListenSocketTestAction last_action_; + std::deque<ListenSocketTestAction> queue_; + SOCKET test_socket_; + static const int kTestPort; + + protected: + virtual ListenSocket* DoListen(); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_LISTEN_SOCKET_UNITTEST_H_ diff --git a/chrome/browser/debugger/devtools_remote_message.cc b/chrome/browser/debugger/devtools_remote_message.cc new file mode 100644 index 0000000..09e62d5 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_message.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2009 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 "base/string_util.h" +#include "chrome/browser/debugger/devtools_remote_message.h" + +const char DevToolsRemoteMessageHeaders::kContentLength[] = "Content-Length"; +const char DevToolsRemoteMessageHeaders::kTool[] = "Tool"; +const char DevToolsRemoteMessageHeaders::kDestination[] = "Destination"; + +const char DevToolsRemoteMessage::kEmptyValue[] = ""; + +DevToolsRemoteMessageBuilder& DevToolsRemoteMessageBuilder::instance() { + static DevToolsRemoteMessageBuilder instance_(new IdGeneratorImpl); + return instance_; +} + +const std::string DevToolsRemoteMessage::GetHeader( + const std::string& header_name, + const std::string& default_value) const { + HeaderMap::const_iterator it = header_map_.find(header_name); + if (it == header_map_.end()) { + return default_value; + } + return it->second; +} + +const std::string DevToolsRemoteMessage::GetHeaderWithEmptyDefault( + const std::string& header_name) const { + return GetHeader(header_name, DevToolsRemoteMessage::kEmptyValue); +} + +const std::string DevToolsRemoteMessage::ToString() const { + std::string result; + for (HeaderMap::const_iterator it = header_map_.begin(), + end = header_map_.end(); it != end; ++it) { + result.append(it->first).append(":").append(it->second).append("\r\n"); + } + result.append("\r\n").append(content_); + return result; +} + +DevToolsRemoteMessage* DevToolsRemoteMessageBuilder::Create( + const std::string& tool, + const std::string& destination, + const std::string& content) { + DevToolsRemoteMessage::HeaderMap headers; + headers[DevToolsRemoteMessageHeaders::kContentLength] = + IntToString(content.size()); + headers[DevToolsRemoteMessageHeaders::kTool] = tool; + headers[DevToolsRemoteMessageHeaders::kDestination] = destination; + return new DevToolsRemoteMessage(headers, content); +} diff --git a/chrome/browser/debugger/devtools_remote_message.h b/chrome/browser/debugger/devtools_remote_message.h new file mode 100644 index 0000000..7665269 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_message.h @@ -0,0 +1,134 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_MESSAGE_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_MESSAGE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/logging.h" + +// Contains DevTools protocol message header names +// and the Flags header bit field constants. +struct DevToolsRemoteMessageHeaders { + // The content length in decimal. + static const char kContentLength[]; + // The tool that should handle the message. + static const char kTool[]; + // The destination (inspected) object identifier (if any), like a TabID. + static const char kDestination[]; +}; + +// Represents a Chrome remote debugging protocol message transferred +// over the wire between the remote debugger and a Chrome instance. +// Consider using DevToolsRemoteMessageBuilder (see end of this file) for easy +// construction of outbound (Chrome -> remote debugger) messages. +class DevToolsRemoteMessage { + public: + typedef base::hash_map<std::string, std::string> HeaderMap; + + // Use this as the second parameter in a |GetHeader| call to use + // an empty string as the default value. + static const char kEmptyValue[]; + + // Constructs an empty message with no content or headers. + DevToolsRemoteMessage() {} + DevToolsRemoteMessage(const HeaderMap& headers, const std::string& content) + : header_map_(headers), + content_(content) {} + virtual ~DevToolsRemoteMessage() {} + + const HeaderMap& headers() const { + return header_map_; + } + + const std::string& content() const { + return content_; + } + + const int content_length() const { + return content_.size(); + } + + const std::string tool() const { + return GetHeaderWithEmptyDefault(DevToolsRemoteMessageHeaders::kTool); + } + + const std::string destination() const { + return GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kDestination); + } + + // Returns the header value providing default_value if the header is absent. + const std::string GetHeader(const std::string& header_name, + const std::string& default_value) const; + + // Returns the header value providing an empty string if the header is absent. + const std::string GetHeaderWithEmptyDefault( + const std::string& header_name) const; + + // Returns a string representation of the message useful for the transfer to + // the remote debugger. + const std::string ToString() const; + + private: + HeaderMap header_map_; + std::string content_; + // Cannot DISALLOW_COPY_AND_ASSIGN(DevToolsRemoteMessage) since it is passed + // as an IPC message argument and needs to be copied. +}; + +// Facilitates easy construction of outbound (Chrome -> remote debugger) +// DevToolsRemote messages. +class DevToolsRemoteMessageBuilder { + public: + class IdGenerator { + public: + virtual ~IdGenerator() {} + virtual int32 NextId() = 0; + }; + // A singleton instance getter. + static DevToolsRemoteMessageBuilder& instance(); + // Creates a message given the certain header values and a payload. + DevToolsRemoteMessage* Create(const std::string& tool, + const std::string& destination, + const std::string& payload); + // Sets a message ID generator instance. The builder then owns this instance + // and deletes it upon termination. + void set_id_generator(IdGenerator* id_generator) { + if (id_generator_ != NULL) { + delete id_generator_; + id_generator_ = id_generator; + } else { + NOTREACHED(); + } + } + + private: + class IdGeneratorImpl : public IdGenerator { + public: + IdGeneratorImpl() : id_(1) {} + virtual int32 NextId() { + return id_++; + } + private: + int32 id_; + }; + + explicit DevToolsRemoteMessageBuilder(IdGenerator* id_generator) + : id_generator_(id_generator) {} + ~DevToolsRemoteMessageBuilder() { + delete id_generator_; + } + int32 NextMessageId() { + return id_generator_->NextId(); + } + + IdGenerator* id_generator_; + DISALLOW_COPY_AND_ASSIGN(DevToolsRemoteMessageBuilder); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_MESSAGE_H_ diff --git a/chrome/browser/debugger/devtools_remote_message_unittest.cc b/chrome/browser/debugger/devtools_remote_message_unittest.cc new file mode 100644 index 0000000..ad0c3a5 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_message_unittest.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2009 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 <string> + +#include "base/string_util.h" +#include "chrome/browser/debugger/devtools_remote.h" +#include "chrome/browser/debugger/devtools_remote_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +class TestIdGenerator : public DevToolsRemoteMessageBuilder::IdGenerator { + virtual int32 NextId() { + return 123; + } +}; +} + +class DevToolsRemoteMessageTest : public testing::Test { + public: + DevToolsRemoteMessageTest() : testing::Test() { + } + + protected: + virtual void SetUp() { + testing::Test::SetUp(); + } +}; + +TEST_F(DevToolsRemoteMessageTest, ConstructInstanceManually) { + DevToolsRemoteMessage::HeaderMap headers; + std::string content = "{\"command\":\"ping\"}"; + headers[DevToolsRemoteMessageHeaders::kTool] = "DevToolsService"; + headers[DevToolsRemoteMessageHeaders::kContentLength] = + IntToString(content.size()); + + DevToolsRemoteMessage message(headers, content); + ASSERT_STREQ("DevToolsService", + message.GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kTool).c_str()); + ASSERT_STREQ("DevToolsService", message.tool().c_str()); + ASSERT_STREQ(content.c_str(), message.content().c_str()); + ASSERT_EQ(content.size(), + static_cast<std::string::size_type>(message.content_length())); + ASSERT_EQ(static_cast<DevToolsRemoteMessage::HeaderMap::size_type>(2), + message.headers().size()); +} + +TEST_F(DevToolsRemoteMessageTest, ConstructWithBuilder) { + DevToolsRemoteMessageBuilder::instance().set_id_generator( + new TestIdGenerator); + std::string content = "Responsecontent"; + testing::internal::scoped_ptr<DevToolsRemoteMessage> message( + DevToolsRemoteMessageBuilder::instance().Create( + "V8Debugger", // tool + "2", // destination + content)); // content + + ASSERT_EQ(static_cast<DevToolsRemoteMessage::HeaderMap::size_type>(3), + message->headers().size()); + ASSERT_STREQ( + "V8Debugger", + message->GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kTool).c_str()); + ASSERT_STREQ( + "V8Debugger", + message->tool().c_str()); + ASSERT_STREQ( + "2", + message->GetHeaderWithEmptyDefault( + DevToolsRemoteMessageHeaders::kDestination).c_str()); + ASSERT_STREQ( + "2", + message->destination().c_str()); + ASSERT_EQ(content.size(), + static_cast<DevToolsRemoteMessage::HeaderMap::size_type>( + message->content_length())); + ASSERT_STREQ(content.c_str(), message->content().c_str()); +} diff --git a/chrome/browser/debugger/devtools_remote_service.cc b/chrome/browser/debugger/devtools_remote_service.cc new file mode 100644 index 0000000..4913f8b --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_service.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2009 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 "base/json_reader.h" +#include "base/json_writer.h" +#include "base/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/debugger/devtools_protocol_handler.h" +#include "chrome/browser/debugger/devtools_remote_message.h" +#include "chrome/browser/debugger/devtools_remote_service.h" +#include "chrome/browser/debugger/inspectable_tab_proxy.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/common/devtools_messages.h" + +const std::string DevToolsRemoteServiceCommand::kPing = "ping"; +const std::string DevToolsRemoteServiceCommand::kVersion = "version"; +const std::string DevToolsRemoteServiceCommand::kListTabs = "list_tabs"; + +const std::wstring DevToolsRemoteService::kCommandWide = L"command"; +const std::wstring DevToolsRemoteService::kDataWide = L"data"; +const std::wstring DevToolsRemoteService::kResultWide = L"result"; +const std::string DevToolsRemoteService::kToolName = "DevToolsService"; + +DevToolsRemoteService::DevToolsRemoteService(DevToolsProtocolHandler* delegate) + : delegate_(delegate) {} + +DevToolsRemoteService::~DevToolsRemoteService() {} + +void DevToolsRemoteService::HandleMessage( + const DevToolsRemoteMessage& message) { + scoped_ptr<Value> request(JSONReader::Read(message.content(), false)); + if (request.get() == NULL) { + // Bad JSON + NOTREACHED(); + return; + } + DictionaryValue* json; + if (request->IsType(Value::TYPE_DICTIONARY)) { + json = static_cast<DictionaryValue*>(request.get()); + if (!json->HasKey(kCommandWide)) { + NOTREACHED(); // Broken protocol - no "command" specified + return; + } + } else { + NOTREACHED(); // Broken protocol - not a JS object + return; + } + ProcessJson(json, message); +} + +void DevToolsRemoteService::ProcessJson(DictionaryValue* json, + const DevToolsRemoteMessage& message) { + static const std::string kOkResponse = "ok"; // "Ping" response + static const std::string kVersion = "0.1"; // Current protocol version + std::string command; + DictionaryValue response; + + json->GetString(kCommandWide, &command); + response.SetString(kCommandWide, command); + + if (command == DevToolsRemoteServiceCommand::kPing) { + response.SetInteger(kResultWide, Result::kOk); + response.SetString(kDataWide, kOkResponse); + } else if (command == DevToolsRemoteServiceCommand::kVersion) { + response.SetInteger(kResultWide, Result::kOk); + response.SetString(kDataWide, kVersion); + } else if (command == DevToolsRemoteServiceCommand::kListTabs) { + ListValue* data = new ListValue(); + const InspectableTabProxy::ControllersMap& navcon_map = + delegate_->inspectable_tab_proxy()->controllers_map(true); + for (InspectableTabProxy::ControllersMap::const_iterator it = + navcon_map.begin(), end = navcon_map.end(); it != end; ++it) { + NavigationEntry* entry = it->second->GetActiveEntry(); + if (entry == NULL) { + continue; + } + if (entry->url().is_valid()) { + ListValue* tab = new ListValue(); + tab->Append(Value::CreateIntegerValue( + static_cast<int32>(it->second->session_id().id()))); + tab->Append(Value::CreateStringValue(entry->url().spec())); + data->Append(tab); + } + } + response.SetInteger(kResultWide, Result::kOk); + response.Set(kDataWide, data); + } else { + // Unknown protocol command. + NOTREACHED(); + response.SetInteger(kResultWide, Result::kUnknownCommand); + } + std::string response_json; + JSONWriter::Write(&response, false, &response_json); + scoped_ptr<DevToolsRemoteMessage> response_message( + DevToolsRemoteMessageBuilder::instance().Create(message.tool(), + message.destination(), + response_json)); + delegate_->Send(*response_message.get()); +} diff --git a/chrome/browser/debugger/devtools_remote_service.h b/chrome/browser/debugger/devtools_remote_service.h new file mode 100644 index 0000000..96bdab4 --- /dev/null +++ b/chrome/browser/debugger/devtools_remote_service.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_SERVICE_H_ +#define CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_SERVICE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "chrome/browser/debugger/devtools_remote.h" + +class DevToolsRemoteMessage; +class DevToolsProtocolHandler; +class DictionaryValue; +class Value; + +// Contains constants for DevToolsRemoteService tool protocol commands. +struct DevToolsRemoteServiceCommand { + static const std::string kPing; + static const std::string kVersion; + static const std::string kListTabs; +}; + +// Handles Chrome remote debugger protocol service commands. +class DevToolsRemoteService : public DevToolsRemoteListener { + public: + explicit DevToolsRemoteService(DevToolsProtocolHandler* delegate); + virtual ~DevToolsRemoteService(); + + // DevToolsRemoteListener interface + virtual void HandleMessage(const DevToolsRemoteMessage& message); + + static const std::string kToolName; + + private: + // Operation result returned in the "result" field. + struct Result { + static const int kOk = 0; + static const int kUnknownCommand = 1; + }; + void ProcessJson(DictionaryValue* json, const DevToolsRemoteMessage& message); + static const std::wstring kCommandWide; + static const std::wstring kDataWide; + static const std::wstring kResultWide; + DevToolsProtocolHandler* delegate_; + DISALLOW_COPY_AND_ASSIGN(DevToolsRemoteService); +}; + +#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_SERVICE_H_ diff --git a/chrome/browser/debugger/inspectable_tab_proxy.cc b/chrome/browser/debugger/inspectable_tab_proxy.cc new file mode 100644 index 0000000..e141bca --- /dev/null +++ b/chrome/browser/debugger/inspectable_tab_proxy.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2009 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 "chrome/browser/debugger/inspectable_tab_proxy.h" + +#include "base/json_reader.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/debugger/debugger_remote_service.h" +#include "chrome/browser/debugger/devtools_client_host.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/common/devtools_messages.h" + +namespace { + +// An internal implementation of DevToolsClientHost that delegates +// messages sent for DevToolsClient to a DebuggerShell instance. +class DevToolsClientHostImpl : public DevToolsClientHost { + public: + DevToolsClientHostImpl(int32 id, DebuggerRemoteService* service) + : id_(id), + service_(service) {} + + // DevToolsClientHost interface + virtual void InspectedTabClosing(); + virtual void SendMessageToClient(const IPC::Message& msg); + + private: + // Message handling routines + void OnRpcMessage(const std::string& msg); + void DebuggerOutput(const std::string& msg); + + int32 id_; + DebuggerRemoteService* service_; +}; + +void DevToolsClientHostImpl::InspectedTabClosing() { + NotifyCloseListener(); + delete this; +} + +void DevToolsClientHostImpl::SendMessageToClient( + const IPC::Message& msg) { + IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg) + IPC_MESSAGE_HANDLER(DevToolsClientMsg_RpcMessage, OnRpcMessage); + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP() +} + +void DevToolsClientHostImpl::OnRpcMessage(const std::string& msg) { + static const std::string kDebuggerAgentDelegate = "DebuggerAgentDelegate"; + static const std::string kDebuggerOutput = "DebuggerOutput"; + scoped_ptr<Value> message(JSONReader::Read(msg, false)); + if (!message->IsType(Value::TYPE_LIST)) { + NOTREACHED(); // The protocol has changed :( + return; + } + ListValue* list_msg = static_cast<ListValue*>(message.get()); + std::string class_name; + list_msg->GetString(0, &class_name); + std::string message_name; + list_msg->GetString(1, &message_name); + if (class_name == kDebuggerAgentDelegate && message_name == kDebuggerOutput) { + std::string str; + list_msg->GetString(2, &str); + DebuggerOutput(str); + } +} + +void DevToolsClientHostImpl::DebuggerOutput(const std::string& msg) { + service_->DebuggerOutput(id_, msg); +} + +} // namespace + +const InspectableTabProxy::ControllersMap& + InspectableTabProxy::controllers_map(bool refresh) { + if (refresh || controllers_map_.empty() /* on initial call */) { + controllers_map_.clear(); + for (BrowserList::const_iterator it = BrowserList::begin(), + end = BrowserList::end(); it != end; ++it) { + TabStripModel* model = (*it)->tabstrip_model(); + for (int i = 0, size = model->count(); i < size; ++i) { + NavigationController* controller = + model->GetTabContentsAt(i)->controller(); + controllers_map_[controller->session_id().id()] = controller; + } + } + } + return controllers_map_; +} + +// static +DevToolsClientHost* InspectableTabProxy::NewClientHost( + int32 id, + DebuggerRemoteService* service) { + return new DevToolsClientHostImpl(id, service); +} diff --git a/chrome/browser/debugger/inspectable_tab_proxy.h b/chrome/browser/debugger/inspectable_tab_proxy.h new file mode 100644 index 0000000..7888a7c --- /dev/null +++ b/chrome/browser/debugger/inspectable_tab_proxy.h @@ -0,0 +1,42 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_DEBUGGER_INSPECTABLE_TAB_PROXY_H_ +#define CHROME_BROWSER_DEBUGGER_INSPECTABLE_TAB_PROXY_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/hash_tables.h" + +class DebuggerRemoteService; +class DevToolsClientHost; +class NavigationController; + +// Proxies debugged tabs' NavigationControllers using their UIDs. +class InspectableTabProxy { + public: + typedef base::hash_map<int32, NavigationController*> ControllersMap; + + InspectableTabProxy() {} + virtual ~InspectableTabProxy() {} + + // Returns a map of NavigationControllerKeys to NavigationControllers + // for all Browser instances. If |refresh| == true, the cached map will be + // dropped and retrieved anew. + const ControllersMap& controllers_map(bool refresh); + + // Creates a new DevToolsClientHost implementor instance. + // |id| is the UID of the tab to debug. + // |service| is the DebuggerRemoteService instance the DevToolsClient + // messages shall be dispatched to. + static DevToolsClientHost* NewClientHost(int32 id, + DebuggerRemoteService* service); + + private: + ControllersMap controllers_map_; + DISALLOW_COPY_AND_ASSIGN(InspectableTabProxy); +}; + +#endif // CHROME_BROWSER_DEBUGGER_INSPECTABLE_TAB_PROXY_H_ diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 75bc37a..b38c506 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -566,6 +566,8 @@ 'browser/debugger/debugger_io_socket.h', 'browser/debugger/debugger_node.cc', 'browser/debugger/debugger_node.h', + 'browser/debugger/debugger_remote_service.cc', + 'browser/debugger/debugger_remote_service.h', 'browser/debugger/debugger_shell.cc', 'browser/debugger/debugger_shell.h', 'browser/debugger/debugger_shell_stubs.cc', @@ -576,14 +578,25 @@ 'browser/debugger/debugger_wrapper.cc', 'browser/debugger/debugger_wrapper.h', 'browser/debugger/devtools_client_host.h', - 'browser/debugger/devtools_manager.h', 'browser/debugger/devtools_manager.cc', + 'browser/debugger/devtools_manager.h', + 'browser/debugger/devtools_protocol_handler.cc', + 'browser/debugger/devtools_protocol_handler.h', + 'browser/debugger/devtools_remote.h', + 'browser/debugger/devtools_remote_listen_socket.cc', + 'browser/debugger/devtools_remote_listen_socket.h', + 'browser/debugger/devtools_remote_message.cc', + 'browser/debugger/devtools_remote_message.h', + 'browser/debugger/devtools_remote_service.cc', + 'browser/debugger/devtools_remote_service.h', + 'browser/debugger/devtools_view.cc', + 'browser/debugger/devtools_view.h', 'browser/debugger/devtools_window.h', 'browser/debugger/devtools_window_gtk.cc', 'browser/debugger/devtools_window_mac.cc', 'browser/debugger/devtools_window_win.cc', - 'browser/debugger/devtools_view.cc', - 'browser/debugger/devtools_view.h', + 'browser/debugger/inspectable_tab_proxy.cc', + 'browser/debugger/inspectable_tab_proxy.h', 'browser/dock_info.cc', 'browser/dock_info.h', 'browser/dom_operation_notification_details.h', @@ -2092,6 +2105,9 @@ 'browser/bookmarks/bookmark_table_model_unittest.cc', 'browser/bookmarks/bookmark_utils_unittest.cc', 'browser/browser_commands_unittest.cc', + 'browser/debugger/devtools_remote_message_unittest.cc', + 'browser/debugger/devtools_remote_listen_socket_unittest.cc', + 'browser/debugger/devtools_remote_listen_socket_unittest.h', 'browser/chrome_thread_unittest.cc', # It is safe to list */cocoa/* files in the "common" file list # without an explicit exclusion since gyp is smart enough to diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index 2bf265e..4711923f 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -448,6 +448,18 @@ > </File> <File + RelativePath="..\..\browser\debugger\devtools_remote_listen_socket_unittest.cc" + > + </File> + <File + RelativePath="..\..\browser\debugger\devtools_remote_listen_socket_unittest.h" + > + </File> + <File + RelativePath="..\..\browser\debugger\devtools_remote_message_unittest.cc" + > + </File> + <File RelativePath="..\..\browser\net\dns_host_info_unittest.cc" > </File> |