summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/DEPS1
-rw-r--r--chrome/browser/debugger/debugger.vcproj52
-rw-r--r--chrome/browser/debugger/debugger_remote_service.cc262
-rw-r--r--chrome/browser/debugger/debugger_remote_service.h69
-rw-r--r--chrome/browser/debugger/debugger_wrapper.cc28
-rw-r--r--chrome/browser/debugger/debugger_wrapper.h12
-rw-r--r--chrome/browser/debugger/devtools_protocol_handler.cc107
-rw-r--r--chrome/browser/debugger/devtools_protocol_handler.h81
-rw-r--r--chrome/browser/debugger/devtools_remote.h34
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket.cc246
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket.h65
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc356
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket_unittest.h139
-rw-r--r--chrome/browser/debugger/devtools_remote_message.cc54
-rw-r--r--chrome/browser/debugger/devtools_remote_message.h134
-rw-r--r--chrome/browser/debugger/devtools_remote_message_unittest.cc80
-rw-r--r--chrome/browser/debugger/devtools_remote_service.cc103
-rw-r--r--chrome/browser/debugger/devtools_remote_service.h50
-rw-r--r--chrome/browser/debugger/inspectable_tab_proxy.cc103
-rw-r--r--chrome/browser/debugger/inspectable_tab_proxy.h42
-rw-r--r--chrome/chrome.gyp22
-rw-r--r--chrome/test/unit/unittests.vcproj12
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>