summaryrefslogtreecommitdiffstats
path: root/chrome/browser/debugger
diff options
context:
space:
mode:
authorpfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-20 11:38:03 +0000
committerpfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-07-20 11:38:03 +0000
commit0639cfd4ed40aa9450c5781c86d6b47af8396220 (patch)
tree486f82e04cd53623ffb67ae7932523bde70b118f /chrome/browser/debugger
parentd641b6e4c2ebf556cb42818e7b3470329baf170c (diff)
downloadchromium_src-0639cfd4ed40aa9450c5781c86d6b47af8396220.zip
chromium_src-0639cfd4ed40aa9450c5781c86d6b47af8396220.tar.gz
chromium_src-0639cfd4ed40aa9450c5781c86d6b47af8396220.tar.bz2
DevTools: bulk move of legacy protocol back to chrome/.
Review URL: http://codereview.chromium.org/7458015 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@93182 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/debugger')
-rw-r--r--chrome/browser/debugger/browser_list_tabcontents_provider.cc20
-rw-r--r--chrome/browser/debugger/browser_list_tabcontents_provider.h21
-rw-r--r--chrome/browser/debugger/debugger_remote_service.cc337
-rw-r--r--chrome/browser/debugger/debugger_remote_service.h127
-rw-r--r--chrome/browser/debugger/devtools_protocol_handler.cc126
-rw-r--r--chrome/browser/debugger/devtools_protocol_handler.h76
-rw-r--r--chrome/browser/debugger/devtools_remote.h47
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket.cc258
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket.h72
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc383
-rw-r--r--chrome/browser/debugger/devtools_remote_listen_socket_unittest.h129
-rw-r--r--chrome/browser/debugger/devtools_remote_message.cc65
-rw-r--r--chrome/browser/debugger/devtools_remote_message.h99
-rw-r--r--chrome/browser/debugger/devtools_remote_message_unittest.cc70
-rw-r--r--chrome/browser/debugger/devtools_remote_service.cc110
-rw-r--r--chrome/browser/debugger/devtools_remote_service.h50
-rw-r--r--chrome/browser/debugger/devtools_sanity_unittest.cc443
-rw-r--r--chrome/browser/debugger/devtools_window.cc10
-rw-r--r--chrome/browser/debugger/devtools_window.h10
-rw-r--r--chrome/browser/debugger/extension_ports_remote_service.cc385
-rw-r--r--chrome/browser/debugger/extension_ports_remote_service.h115
-rw-r--r--chrome/browser/debugger/inspectable_tab_proxy.cc117
-rw-r--r--chrome/browser/debugger/inspectable_tab_proxy.h91
23 files changed, 3151 insertions, 10 deletions
diff --git a/chrome/browser/debugger/browser_list_tabcontents_provider.cc b/chrome/browser/debugger/browser_list_tabcontents_provider.cc
new file mode 100644
index 0000000..65addd8
--- /dev/null
+++ b/chrome/browser/debugger/browser_list_tabcontents_provider.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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/browser_list_tabcontents_provider.h"
+
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/browser_list.h"
+
+DevToolsHttpProtocolHandler::InspectableTabs
+BrowserListTabContentsProvider::GetInspectableTabs() {
+ DevToolsHttpProtocolHandler::InspectableTabs tabs;
+ 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)
+ tabs.push_back(model->GetTabContentsAt(i));
+ }
+ return tabs;
+}
diff --git a/chrome/browser/debugger/browser_list_tabcontents_provider.h b/chrome/browser/debugger/browser_list_tabcontents_provider.h
new file mode 100644
index 0000000..d8adb9b
--- /dev/null
+++ b/chrome/browser/debugger/browser_list_tabcontents_provider.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2011 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_BROWSER_LIST_TABCONTENTS_PROVIDER_H_
+#define CHROME_BROWSER_DEBUGGER_BROWSER_LIST_TABCONTENTS_PROVIDER_H_
+
+#include "content/browser/debugger/devtools_http_protocol_handler.h"
+
+class BrowserListTabContentsProvider
+ : public DevToolsHttpProtocolHandler::TabContentsProvider {
+ public:
+ BrowserListTabContentsProvider() {}
+ virtual ~BrowserListTabContentsProvider() {}
+
+ virtual DevToolsHttpProtocolHandler::InspectableTabs GetInspectableTabs();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BrowserListTabContentsProvider);
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_BROWSER_LIST_TABCONTENTS_PROVIDER_H_
diff --git a/chrome/browser/debugger/debugger_remote_service.cc b/chrome/browser/debugger/debugger_remote_service.cc
new file mode 100644
index 0000000..a93084f
--- /dev/null
+++ b/chrome/browser/debugger/debugger_remote_service.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2011 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.
+
+// This file contains implementations of the DebuggerRemoteService methods,
+// defines DebuggerRemoteService and DebuggerRemoteServiceCommand constants.
+
+#include "chrome/browser/debugger/debugger_remote_service.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/stringprintf.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.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/ui/tab_contents/tab_contents_wrapper.h"
+#include "chrome/common/render_messages.h"
+#include "content/browser/debugger/devtools_manager.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/common/devtools_messages.h"
+
+namespace {
+
+// Constants for the "data", "result", and "command" JSON message fields.
+const char kDataKey[] = "data";
+const char kResultKey[] = "result";
+const char kCommandKey[] = "command";
+
+} // namespace
+
+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 DebuggerRemoteServiceCommand::kFrameNavigate =
+ "navigated";
+const std::string DebuggerRemoteServiceCommand::kTabClosed =
+ "closed";
+
+const std::string DebuggerRemoteService::kToolName = "V8Debugger";
+
+DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate)
+ : delegate_(delegate) {}
+
+DebuggerRemoteService::~DebuggerRemoteService() {}
+
+// This method handles the V8Debugger tool commands which are
+// retrieved from the request "command" field. If an operation result
+// is ready off-hand (synchronously), it is sent back to the remote debugger.
+// Otherwise the corresponding response is received through IPC from the
+// V8 debugger via DevToolsClientHost.
+void DebuggerRemoteService::HandleMessage(
+ const DevToolsRemoteMessage& message) {
+ const std::string destination = message.destination();
+ scoped_ptr<Value> request(base::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(kCommandKey)) {
+ NOTREACHED(); // Broken protocol :(
+ return;
+ }
+ std::string command;
+ DictionaryValue response;
+
+ content->GetString(kCommandKey, &command);
+ response.SetString(kCommandKey, command);
+ bool send_response = true;
+ if (destination.empty()) {
+ // Unknown command (bad format?)
+ NOTREACHED();
+ response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
+ SendResponse(response, message.tool(), message.destination());
+ return;
+ }
+ int32 tab_uid = -1;
+ base::StringToInt(destination, &tab_uid);
+
+ if (command == DebuggerRemoteServiceCommand::kAttach) {
+ // TODO(apavlov): handle 0 for a new tab
+ response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kAttach);
+ AttachToTab(destination, &response);
+ } else if (command == DebuggerRemoteServiceCommand::kDetach) {
+ response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kDetach);
+ DetachFromTab(destination, &response);
+ } else if (command == DebuggerRemoteServiceCommand::kDebuggerCommand) {
+ send_response = DispatchDebuggerCommand(tab_uid, content, &response);
+ } else if (command == DebuggerRemoteServiceCommand::kEvaluateJavascript) {
+ send_response = DispatchEvaluateJavascript(tab_uid, content, &response);
+ } else {
+ // Unknown command
+ NOTREACHED();
+ response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
+ }
+
+ if (send_response) {
+ SendResponse(response, message.tool(), message.destination());
+ }
+}
+
+void DebuggerRemoteService::OnConnectionLost() {
+ delegate_->inspectable_tab_proxy()->OnRemoteDebuggerDetached();
+}
+
+// Sends a JSON response to the remote debugger using |response| as content,
+// |tool| and |destination| as the respective header values.
+void DebuggerRemoteService::SendResponse(const Value& response,
+ const std::string& tool,
+ const std::string& destination) {
+ std::string response_content;
+ base::JSONWriter::Write(&response, false, &response_content);
+ scoped_ptr<DevToolsRemoteMessage> response_message(
+ DevToolsRemoteMessageBuilder::instance().Create(tool,
+ destination,
+ response_content));
+ delegate_->Send(*response_message.get());
+}
+
+// Gets a TabContents instance corresponding to the |tab_uid| using the
+// InspectableTabProxy controllers map, or NULL if none found.
+TabContents* DebuggerRemoteService::ToTabContents(int32 tab_uid) {
+ const InspectableTabProxy::TabMap& tab_map =
+ delegate_->inspectable_tab_proxy()->tab_map();
+ InspectableTabProxy::TabMap::const_iterator it = tab_map.find(tab_uid);
+ if (it != tab_map.end()) {
+ TabContents* tab_contents = it->second->tab_contents();
+ if (tab_contents == NULL) {
+ return NULL;
+ } else {
+ return tab_contents;
+ }
+ } else {
+ return NULL;
+ }
+}
+
+// Gets invoked from a DevToolsClientHost callback whenever
+// a message from the V8 VM debugger corresponding to |tab_id| is received.
+// Composes a Chrome Developer Tools Protocol JSON response and sends it
+// to the remote debugger.
+void DebuggerRemoteService::DebuggerOutput(int32 tab_uid,
+ const std::string& message) {
+ std::string content = StringPrintf(
+ "{\"command\":\"%s\",\"result\":%s,\"data\":%s}",
+ DebuggerRemoteServiceCommand::kDebuggerCommand.c_str(),
+ base::IntToString(RESULT_OK).c_str(),
+ message.c_str());
+ scoped_ptr<DevToolsRemoteMessage> response_message(
+ DevToolsRemoteMessageBuilder::instance().Create(
+ kToolName,
+ base::IntToString(tab_uid),
+ content));
+ delegate_->Send(*(response_message.get()));
+}
+
+// Gets invoked from a DevToolsClientHost callback whenever
+// a tab corresponding to |tab_id| changes its URL. |url| is the new
+// URL of the tab (may be the same as the previous one if the tab is reloaded).
+// Sends the corresponding message to the remote debugger.
+void DebuggerRemoteService::FrameNavigate(int32 tab_uid,
+ const std::string& url) {
+ DictionaryValue value;
+ value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kFrameNavigate);
+ value.SetInteger(kResultKey, RESULT_OK);
+ value.SetString(kDataKey, url);
+ SendResponse(value, kToolName, base::IntToString(tab_uid));
+}
+
+// Gets invoked from a DevToolsClientHost callback whenever
+// a tab corresponding to |tab_id| gets closed.
+// Sends the corresponding message to the remote debugger.
+void DebuggerRemoteService::TabClosed(int32 tab_id) {
+ DictionaryValue value;
+ value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kTabClosed);
+ value.SetInteger(kResultKey, RESULT_OK);
+ SendResponse(value, kToolName, base::IntToString(tab_id));
+}
+
+// Attaches a remote debugger to the target tab specified by |destination|
+// by posting the DevToolsAgentMsg_Attach message and sends a response
+// to the remote debugger immediately.
+void DebuggerRemoteService::AttachToTab(const std::string& destination,
+ DictionaryValue* response) {
+ int32 tab_uid = -1;
+ base::StringToInt(destination, &tab_uid);
+ if (tab_uid < 0) {
+ // Bad tab_uid received from remote debugger (perhaps NaN)
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ 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(kResultKey, RESULT_UNKNOWN_TAB);
+ return;
+ }
+ TabContents* tab_contents = ToTabContents(tab_uid);
+ if (tab_contents == NULL) {
+ // No active tab contents with tab_uid
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ return;
+ }
+ RenderViewHost* target_host = tab_contents->render_view_host();
+ DevToolsClientHost* client_host =
+ delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid);
+ if (client_host == NULL) {
+ client_host =
+ delegate_->inspectable_tab_proxy()->NewClientHost(tab_uid, this);
+ DevToolsManager* manager = DevToolsManager::GetInstance();
+ if (manager != NULL) {
+ manager->RegisterDevToolsClientHostFor(target_host, client_host);
+ response->SetInteger(kResultKey, RESULT_OK);
+ } else {
+ response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR);
+ }
+ } else {
+ // DevToolsClientHost for this tab is already registered
+ response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE);
+ }
+}
+
+// Detaches a remote debugger from the target tab specified by |destination|
+// by posting the DevToolsAgentMsg_Detach message and sends a response
+// to the remote debugger immediately.
+void DebuggerRemoteService::DetachFromTab(const std::string& destination,
+ DictionaryValue* response) {
+ int32 tab_uid = -1;
+ base::StringToInt(destination, &tab_uid);
+ if (tab_uid == -1) {
+ // Bad tab_uid received from remote debugger (NaN)
+ if (response != NULL) {
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ }
+ return;
+ }
+ int result_code;
+ DevToolsClientHostImpl* client_host =
+ delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid);
+ if (client_host != NULL) {
+ client_host->CloseImpl();
+ result_code = RESULT_OK;
+ } else {
+ // No client host registered for |tab_uid|.
+ result_code = RESULT_UNKNOWN_TAB;
+ }
+ if (response != NULL) {
+ response->SetInteger(kResultKey, result_code);
+ }
+}
+
+// Sends a V8 debugger command to the target tab V8 debugger.
+// Does not send back a response (which is received asynchronously
+// through IPC) unless an error occurs before the command has actually
+// been sent.
+bool DebuggerRemoteService::DispatchDebuggerCommand(int tab_uid,
+ DictionaryValue* content,
+ DictionaryValue* response) {
+ if (tab_uid == -1) {
+ // Invalid tab_uid from remote debugger (perhaps NaN)
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ return true;
+ }
+ DevToolsManager* manager = DevToolsManager::GetInstance();
+ if (manager == NULL) {
+ response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR);
+ return true;
+ }
+ TabContents* tab_contents = ToTabContents(tab_uid);
+ if (tab_contents == NULL) {
+ // Unknown tab_uid from remote debugger
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ return true;
+ }
+ DevToolsClientHost* client_host =
+ manager->GetDevToolsClientHostFor(tab_contents->render_view_host());
+ if (client_host == NULL) {
+ // tab_uid is not being debugged (Attach has not been invoked)
+ response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE);
+ return true;
+ }
+ std::string v8_command;
+ DictionaryValue* v8_command_value;
+ content->GetDictionary(kDataKey, &v8_command_value);
+ base::JSONWriter::Write(v8_command_value, false, &v8_command);
+ manager->ForwardToDevToolsAgent(
+ client_host, DevToolsAgentMsg_DebuggerCommand(MSG_ROUTING_NONE,
+ v8_command));
+ // Do not send the response right now, as the JSON will be received from
+ // the V8 debugger asynchronously.
+ return false;
+}
+
+// Sends the immediate "evaluate Javascript" command to the V8 debugger.
+// The evaluation result is not sent back to the client as this command
+// is in fact needed to invoke processing of queued debugger commands.
+bool DebuggerRemoteService::DispatchEvaluateJavascript(
+ int tab_uid,
+ DictionaryValue* content,
+ DictionaryValue* response) {
+ if (tab_uid == -1) {
+ // Invalid tab_uid from remote debugger (perhaps NaN)
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ return true;
+ }
+ TabContents* tab_contents = ToTabContents(tab_uid);
+ if (tab_contents == NULL) {
+ // Unknown tab_uid from remote debugger
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ return true;
+ }
+ RenderViewHost* render_view_host = tab_contents->render_view_host();
+ if (render_view_host == NULL) {
+ // No RenderViewHost
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
+ return true;
+ }
+ std::string javascript;
+ content->GetString(kDataKey, &javascript);
+ render_view_host->ExecuteJavascriptInWebFrame(string16(),
+ UTF8ToUTF16(javascript));
+ return false;
+}
diff --git a/chrome/browser/debugger/debugger_remote_service.h b/chrome/browser/debugger/debugger_remote_service.h
new file mode 100644
index 0000000..113fc8f
--- /dev/null
+++ b/chrome/browser/debugger/debugger_remote_service.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2011 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.
+
+// This file declares the DebuggerRemoteServiceCommand struct and the
+// DebuggerRemoteService class which handles commands directed to the
+// "V8Debugger" tool.
+#ifndef CHROME_BROWSER_DEBUGGER_DEBUGGER_REMOTE_SERVICE_H_
+#define CHROME_BROWSER_DEBUGGER_DEBUGGER_REMOTE_SERVICE_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/debugger/devtools_remote.h"
+
+class DevToolsProtocolHandler;
+class DevToolsRemoteMessage;
+class TabContents;
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+// Contains constants for DebuggerRemoteService tool protocol commands
+// (V8-related only).
+struct DebuggerRemoteServiceCommand {
+ static const std::string kAttach;
+ static const std::string kDetach;
+ static const std::string kDebuggerCommand;
+ static const std::string kEvaluateJavascript;
+ static const std::string kFrameNavigate; // navigation event
+ static const std::string kTabClosed; // tab closing event
+};
+
+// 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:
+ // |delegate| (never NULL) is the protocol handler instance
+ // which dispatches messages to this service. The responses from the
+ // V8 VM debugger are routed back to |delegate|.
+ // The ownership of |delegate| is NOT transferred to this class.
+ explicit DebuggerRemoteService(DevToolsProtocolHandler* delegate);
+
+ // Handles a JSON message from the tab_uid-associated V8 debugger.
+ void DebuggerOutput(int32 tab_uid, const std::string& message);
+
+ // Handles a frame navigation event.
+ void FrameNavigate(int32 tab_uid, const std::string& url);
+
+ // Handles a tab closing event.
+ void TabClosed(int32 tab_uid);
+
+ // Detaches the remote debugger from the tab specified by |destination|.
+ // It is public so that we can detach from the tab on the remote debugger
+ // connection loss.
+ // If |response| is not NULL, the operation result will be written
+ // as the "result" field in |response|, otherwise the result
+ // will not be propagated back to the caller.
+ void DetachFromTab(const std::string& destination,
+ base::DictionaryValue* response);
+
+ // DevToolsRemoteListener interface.
+
+ // Processes |message| from the remote debugger, where the tool is
+ // "V8Debugger". Either sends the reply immediately or waits for an
+ // asynchronous response from the V8 debugger.
+ virtual void HandleMessage(const DevToolsRemoteMessage& message);
+
+ // Gets invoked on the remote debugger [socket] connection loss.
+ // Notifies the InspectableTabProxy of the remote debugger detachment.
+ virtual void OnConnectionLost();
+
+ // Specifies a tool name ("V8Debugger") handled by this class.
+ static const std::string kToolName;
+
+ private:
+ // Operation result returned in the "result" field.
+ typedef enum {
+ RESULT_OK = 0,
+ RESULT_ILLEGAL_TAB_STATE,
+ RESULT_UNKNOWN_TAB,
+ RESULT_DEBUGGER_ERROR,
+ RESULT_UNKNOWN_COMMAND
+ } Result;
+
+ virtual ~DebuggerRemoteService();
+
+ // Attaches a remote debugger to the tab specified by |destination|.
+ // Writes the attachment result (one of Result enum values) into |response|.
+ void AttachToTab(const std::string& destination,
+ base::DictionaryValue* response);
+
+ // Retrieves a WebContents instance for the specified |tab_uid|
+ // or NULL if no such tab is found or no WebContents instance
+ // corresponds to that tab.
+ TabContents* ToTabContents(int32 tab_uid);
+
+ // Sends a JSON message with the |response| to the remote debugger.
+ // |tool| and |destination| are used as the respective header values.
+ void SendResponse(const base::Value& response,
+ const std::string& tool,
+ const std::string& destination);
+
+ // Redirects a V8 debugger command from |content| to a V8 debugger associated
+ // with the |tab_uid| and writes the result into |response| if it becomes
+ // known immediately.
+ bool DispatchDebuggerCommand(int tab_uid,
+ base::DictionaryValue* content,
+ base::DictionaryValue* response);
+
+ // Redirects a Javascript evaluation command from |content| to
+ // a V8 debugger associated with the |tab_uid| and writes the result
+ // into |response| if it becomes known immediately.
+ bool DispatchEvaluateJavascript(int tab_uid,
+ base::DictionaryValue* content,
+ base::DictionaryValue* response);
+
+ // The delegate is used to get an InspectableTabProxy instance.
+ DevToolsProtocolHandler* delegate_;
+ DISALLOW_COPY_AND_ASSIGN(DebuggerRemoteService);
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_DEBUGGER_REMOTE_SERVICE_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..2245a96
--- /dev/null
+++ b/chrome/browser/debugger/devtools_protocol_handler.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2011 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 "chrome/browser/debugger/inspectable_tab_proxy.h"
+#include "chrome/browser/debugger/debugger_remote_service.h"
+#include "chrome/browser/debugger/devtools_remote_message.h"
+#include "chrome/browser/debugger/devtools_remote_listen_socket.h"
+#include "chrome/browser/debugger/devtools_remote_service.h"
+#include "chrome/browser/debugger/extension_ports_remote_service.h"
+#include "content/browser/browser_thread.h"
+
+// static
+scoped_refptr<DevToolsProtocolHandler> DevToolsProtocolHandler::Start(
+ int port) {
+ scoped_refptr<DevToolsProtocolHandler> 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->RegisterDestination(
+ new ExtensionPortsRemoteService(proto_handler),
+ ExtensionPortsRemoteService::kToolName);
+ proto_handler->Start();
+ return proto_handler;
+}
+
+DevToolsProtocolHandler::DevToolsProtocolHandler(int port)
+ : port_(port),
+ connection_(NULL),
+ server_(NULL) {
+ 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() {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ NewRunnableMethod(this, &DevToolsProtocolHandler::Init));
+}
+
+void DevToolsProtocolHandler::Init() {
+ server_ = DevToolsRemoteListenSocket::Listen(
+ "127.0.0.1", port_, this);
+}
+
+void DevToolsProtocolHandler::Stop() {
+ BrowserThread::PostTask(
+ BrowserThread::IO, 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(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(
+ it->second.get(), &DevToolsRemoteListener::HandleMessage, message));
+}
+
+void DevToolsProtocolHandler::Send(const DevToolsRemoteMessage& message) {
+ if (connection_ != NULL) {
+ connection_->Send(message.ToString());
+ }
+}
+
+void DevToolsProtocolHandler::OnAcceptConnection(
+ net::ListenSocket *connection) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ connection_ = connection;
+}
+
+void DevToolsProtocolHandler::OnConnectionLost() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ connection_ = NULL;
+ for (ToolToListenerMap::const_iterator it = tool_to_listener_map_.begin(),
+ end = tool_to_listener_map_.end();
+ it != end;
+ ++it) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ NewRunnableMethod(
+ it->second.get(), &DevToolsRemoteListener::OnConnectionLost));
+ }
+}
diff --git a/chrome/browser/debugger/devtools_protocol_handler.h b/chrome/browser/debugger/devtools_protocol_handler.h
new file mode 100644
index 0000000..b1b9158d
--- /dev/null
+++ b/chrome/browser/debugger/devtools_protocol_handler.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#include <string>
+
+#include "base/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/debugger/devtools_remote.h"
+#include "net/base/listen_socket.h"
+
+class InspectableTabProxy;
+class DevToolsRemoteListenSocket;
+class DevToolsRemoteMessage;
+
+// Dispatches DevToolsRemoteMessages to their appropriate handlers (Tools)
+// based on the "Tool" message header value.
+class DevToolsProtocolHandler
+ : public DevToolsRemoteListener,
+ public OutboundSocketDelegate {
+ public:
+ typedef base::hash_map< std::string, scoped_refptr<DevToolsRemoteListener> >
+ ToolToListenerMap;
+
+ static scoped_refptr<DevToolsProtocolHandler> Start(int port);
+
+ // Called from the main thread in order to stop protocol handler.
+ // Will schedule tear down task on IO thread.
+ 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);
+ virtual void OnAcceptConnection(net::ListenSocket *connection);
+ virtual void OnConnectionLost();
+
+ // OutboundSocketDelegate interface
+ virtual void Send(const DevToolsRemoteMessage& message);
+
+ private:
+ explicit DevToolsProtocolHandler(int port);
+ virtual ~DevToolsProtocolHandler();
+ void Start();
+
+ void Init();
+ void Teardown();
+ int port_;
+ ToolToListenerMap tool_to_listener_map_;
+ scoped_refptr<net::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..78b1d13
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+
+class DevToolsRemoteMessage;
+
+namespace net {
+class ListenSocket;
+}
+
+// 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 void HandleMessage(const DevToolsRemoteMessage& message) = 0;
+ // This method is invoked on the UI thread whenever the debugger connection
+ // has been lost.
+ virtual void OnConnectionLost() = 0;
+ virtual void OnAcceptConnection(net::ListenSocket* connection) {}
+
+ protected:
+ friend class base::RefCountedThreadSafe<DevToolsRemoteListener>;
+
+ virtual ~DevToolsRemoteListener() {}
+
+ 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..0ec9e32
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_listen_socket.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2011 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>
+#endif
+
+#include "base/compiler_specific.h"
+#include "base/eintr_wrapper.h"
+#include "base/string_number_conversions.h"
+#include "base/threading/platform_thread.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;
+#endif
+
+const int kReadBufSize = 200;
+
+DevToolsRemoteListenSocket::DevToolsRemoteListenSocket(
+ SOCKET s,
+ DevToolsRemoteListener* message_listener)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(net::ListenSocket(s, this)),
+ state_(HANDSHAKE),
+ remaining_payload_length_(0),
+ message_listener_(message_listener),
+ cr_received_(false) {}
+
+void DevToolsRemoteListenSocket::StartNextField() {
+ switch (state_) {
+ case INVALID:
+ state_ = HANDSHAKE;
+ break;
+ case HANDSHAKE:
+ state_ = HEADERS;
+ break;
+ case HEADERS:
+ if (protocol_field_.empty()) { // empty line - end of headers
+ const std::string& payload_length_string = GetHeader(
+ DevToolsRemoteMessageHeaders::kContentLength, "0");
+ base::StringToInt(payload_length_string, &remaining_payload_length_);
+ 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,
+ DevToolsRemoteListener* listener) {
+ SOCKET s = net::ListenSocket::Listen(ip, port);
+ if (s == INVALID_SOCKET) {
+ // TODO(apavlov): error handling
+ } else {
+ DevToolsRemoteListenSocket* sock =
+ new DevToolsRemoteListenSocket(s, listener);
+ sock->Listen();
+ return sock;
+ }
+ return NULL;
+}
+
+void DevToolsRemoteListenSocket::DidAccept(net::ListenSocket *server,
+ net::ListenSocket *connection) {
+ connection->AddRef();
+ message_listener_->OnAcceptConnection(connection);
+}
+
+// Dispatches data from socket to socket_delegate_, extracting messages
+// delimited by newlines.
+void DevToolsRemoteListenSocket::DidRead(net::ListenSocket* connection,
+ const char* pBuf,
+ int len) {
+ while (len > 0) {
+ if (state_ != PAYLOAD) {
+ if (cr_received_ && *pBuf == '\n') {
+ cr_received_ = false;
+ CONSUME_BUFFER_CHAR;
+ } else {
+ while (*pBuf != '\r' && len > 0) {
+ protocol_field_.push_back(*pBuf);
+ CONSUME_BUFFER_CHAR;
+ }
+ if (*pBuf == '\r') {
+ cr_received_ = true;
+ CONSUME_BUFFER_CHAR;
+ }
+ continue;
+ }
+ 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::DidClose(net::ListenSocket *connection) {
+ message_listener_->OnConnectionLost();
+ connection->Release();
+}
+
+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_.empty()) { // 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::Listen() {
+ net::ListenSocket::Listen();
+}
+
+void DevToolsRemoteListenSocket::Accept() {
+ SOCKET conn = net::ListenSocket::Accept(socket_);
+ if (conn != INVALID_SOCKET) {
+ scoped_refptr<DevToolsRemoteListenSocket> sock(
+ new DevToolsRemoteListenSocket(conn,
+ 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::SendInternal(const char* bytes, int len) {
+ char* send_buf = const_cast<char *>(bytes);
+ int len_left = len;
+ while (true) {
+ int sent = HANDLE_EINTR(send(socket_, send_buf, len_left, 0));
+ if (sent == len_left) { // A shortcut to avoid extraneous checks.
+ break;
+ }
+ if (sent == kSocketError) {
+#if defined(OS_WIN)
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ LOG(ERROR) << "send failed: WSAGetLastError()==" << WSAGetLastError();
+#elif defined(OS_POSIX)
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ LOG(ERROR) << "send failed: errno==" << errno;
+#endif
+ break;
+ }
+ // Otherwise we would block, and now we have to wait for a retry.
+ // Fall through to PlatformThread::YieldCurrentThread()
+ } else {
+ // sent != len_left according to the shortcut above.
+ // Shift the buffer start and send the remainder after a short while.
+ send_buf += sent;
+ len_left -= sent;
+ }
+ base::PlatformThread::YieldCurrentThread();
+ }
+}
+
+void DevToolsRemoteListenSocket::Close() {
+ net::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..40212f4
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_listen_socket.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#include <string>
+
+#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 net::ListenSocket,
+ public net::ListenSocket::ListenSocketDelegate {
+ 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,
+ DevToolsRemoteListener* message_listener);
+
+ protected:
+ virtual void Listen();
+ virtual void Accept();
+ virtual void Close();
+ virtual void SendInternal(const char* bytes, int len);
+
+ private:
+ virtual ~DevToolsRemoteListenSocket();
+
+ // net::ListenSocket::ListenSocketDelegate interface
+ virtual void DidAccept(net::ListenSocket *server,
+ net::ListenSocket *connection);
+ virtual void DidRead(net::ListenSocket *connection,
+ const char* data, int len);
+ virtual void DidClose(net::ListenSocket *connection);
+
+ // 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,
+ DevToolsRemoteListener *listener);
+ void StartNextField();
+ void HandleMessage();
+ 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_;
+ bool cr_received_;
+
+ 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..9821b59
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc
@@ -0,0 +1,383 @@
+// Copyright (c) 2011 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>
+#if defined(OS_POSIX)
+#include <netinet/in.h>
+#endif
+
+#include "base/eintr_wrapper.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.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* kSimpleMessagePart1 =
+ "Tool:V8Debugger\r\n"
+ "Destination:2\r";
+static const char* kSimpleMessagePart2 =
+ "\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";
+#if defined(OS_POSIX)
+static const char* kSemaphoreName = "chromium.listen_socket";
+#endif
+
+
+ListenSocketTestAction::ListenSocketTestAction() : action_(ACTION_NONE) {}
+
+ListenSocketTestAction::ListenSocketTestAction(ActionType action)
+ : action_(action) {}
+
+ListenSocketTestAction::ListenSocketTestAction(ActionType action,
+ std::string data)
+ : action_(action),
+ data_(data) {}
+
+ListenSocketTestAction::ListenSocketTestAction(
+ ActionType action,
+ const DevToolsRemoteMessage& message)
+ : action_(action),
+ message_(message) {}
+
+ListenSocketTestAction::~ListenSocketTestAction() {}
+
+net::ListenSocket* DevToolsRemoteListenSocketTester::DoListen() {
+ return DevToolsRemoteListenSocket::Listen(kLoopback, kTestPort, this);
+}
+
+DevToolsRemoteListenSocketTester::DevToolsRemoteListenSocketTester()
+ : semaphore_(NULL),
+ thread_(NULL),
+ loop_(NULL),
+ server_(NULL),
+ connection_(NULL),
+ test_socket_(INVALID_SOCKET) {
+ memset(&lock_, 0, sizeof(lock_));
+}
+
+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(TestTimeouts::action_timeout_ms()));
+ 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);
+ ASSERT_NE(INVALID_SOCKET, test_socket_);
+ struct sockaddr_in client;
+ client.sin_family = AF_INET;
+ client.sin_addr.s_addr = inet_addr(kLoopback);
+ client.sin_port = htons(kTestPort);
+ int ret = HANDLE_EINTR(connect(test_socket_,
+ reinterpret_cast<sockaddr*>(&client),
+ sizeof(client)));
+ ASSERT_NE(ret, SOCKET_ERROR);
+
+ net::SetNonBlocking(test_socket_);
+ ASSERT_TRUE(NextAction(TestTimeouts::action_timeout_ms()));
+ ASSERT_EQ(ACTION_ACCEPT, last_action_.type());
+}
+
+void DevToolsRemoteListenSocketTester::TearDown() {
+ // verify close
+#if defined(OS_WIN)
+ closesocket(test_socket_);
+#elif defined(OS_POSIX)
+ int ret = HANDLE_EINTR(close(test_socket_));
+ ASSERT_EQ(ret, 0);
+#endif
+ ASSERT_TRUE(NextAction(TestTimeouts::action_timeout_ms()));
+ ASSERT_EQ(ACTION_CLOSE, last_action_.type());
+
+ loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &DevToolsRemoteListenSocketTester::Shutdown));
+ ASSERT_TRUE(NextAction(TestTimeouts::action_timeout_ms()));
+ 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_.empty()) {
+ 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_);
+ base::PlatformThread::Sleep(1); // 1MS sleep
+ timeout--;
+ if (timeout <= 0)
+ return false;
+ if (result == 0)
+ break;
+ }
+ pthread_mutex_lock(&lock_);
+ if (queue_.empty()) {
+ 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 = HANDLE_EINTR(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
+ base::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() {
+ server_->Release();
+ server_ = NULL;
+ ReportAction(ListenSocketTestAction(ACTION_SHUTDOWN));
+}
+
+void DevToolsRemoteListenSocketTester::Listen() {
+ server_ = DoListen();
+ server_->AddRef();
+ ReportAction(ListenSocketTestAction(ACTION_LISTEN));
+}
+
+void DevToolsRemoteListenSocketTester::SendFromTester() {
+ connection_->Send(kChromeDevToolsHandshake);
+ ReportAction(ListenSocketTestAction(ACTION_SEND));
+}
+
+void DevToolsRemoteListenSocketTester::OnAcceptConnection(
+ net::ListenSocket* connection) {
+ connection_ = connection;
+ ReportAction(ListenSocketTestAction(ACTION_ACCEPT));
+}
+
+void DevToolsRemoteListenSocketTester::OnConnectionLost() {
+ connection_ = NULL;
+ 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 = HANDLE_EINTR(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_, kSimpleMessagePart1));
+ // sleep for 10ms to test message split between \r and \n
+ base::PlatformThread::Sleep(10);
+ ASSERT_TRUE(Send(test_socket_, kSimpleMessagePart2));
+ ASSERT_TRUE(NextAction(TestTimeouts::action_timeout_ms()));
+ 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(TestTimeouts::action_timeout_ms()));
+ 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(TestTimeouts::action_timeout_ms()));
+ 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(TestTimeouts::action_timeout_ms()));
+ 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.
+ base::PlatformThread::Sleep(10); // sleep for 10ms
+ const int buf_len = 200;
+ char buf[buf_len+1];
+ int recv_len = HANDLE_EINTR(recv(test_socket_, buf, buf_len, 0));
+ ASSERT_NE(recv_len, SOCKET_ERROR);
+ buf[recv_len] = 0;
+ ASSERT_STREQ(buf, kChromeDevToolsHandshake);
+}
+
+DevToolsRemoteListenSocketTester::~DevToolsRemoteListenSocketTester() {}
+
+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_;
+};
+
+// This test is flaky; see comment in ::TestServerSend.
+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..0b9b36b
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#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/threading/thread.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/threading/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();
+ explicit ListenSocketTestAction(ActionType action);
+ ListenSocketTestAction(ActionType action, std::string data);
+ ListenSocketTestAction(ActionType action,
+ const DevToolsRemoteMessage& message);
+ ~ListenSocketTestAction();
+
+ const std::string data() const { return data_; }
+ const DevToolsRemoteMessage message() { return message_; }
+ 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 DevToolsRemoteListener {
+ public:
+ DevToolsRemoteListenSocketTester();
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ void ReportAction(const ListenSocketTestAction& action);
+ bool NextAction(int timeout);
+
+ // DevToolsRemoteMessageHandler interface
+ virtual void HandleMessage(const DevToolsRemoteMessage& message);
+ virtual void OnAcceptConnection(net::ListenSocket* connection);
+ virtual void OnConnectionLost();
+
+ // read all pending data from the test socket
+ int ClearTestSocket();
+ // Release the connection and server sockets
+ void Shutdown();
+ void Listen();
+ void SendFromTester();
+ 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_;
+ net::ListenSocket* server_;
+ net::ListenSocket* connection_;
+ ListenSocketTestAction last_action_;
+ std::deque<ListenSocketTestAction> queue_;
+ SOCKET test_socket_;
+ static const int kTestPort;
+
+ protected:
+ virtual net::ListenSocket* DoListen();
+
+ private:
+ virtual ~DevToolsRemoteListenSocketTester();
+};
+
+#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..c64a027
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_message.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 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_message.h"
+
+#include "base/string_number_conversions.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_;
+ return instance_;
+}
+
+DevToolsRemoteMessage::DevToolsRemoteMessage() {}
+
+DevToolsRemoteMessage::DevToolsRemoteMessage(const HeaderMap& headers,
+ const std::string& content)
+ : header_map_(headers),
+ content_(content) {
+}
+
+DevToolsRemoteMessage::~DevToolsRemoteMessage() {}
+
+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] =
+ base::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..5e3197c
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_message.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/hash_tables.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);
+ virtual ~DevToolsRemoteMessage();
+
+ const HeaderMap& headers() const {
+ return header_map_;
+ }
+
+ const std::string& content() const {
+ return content_;
+ }
+
+ 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:
+ // 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);
+
+ private:
+ DevToolsRemoteMessageBuilder() {}
+ virtual ~DevToolsRemoteMessageBuilder() {}
+ 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..670cd79
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_message_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 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/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+#include "chrome/browser/debugger/devtools_remote.h"
+#include "chrome/browser/debugger/devtools_remote_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+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] =
+ base::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) {
+ std::string content = "Responsecontent";
+ 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..329e1a8
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_service.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2011 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_service.h"
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.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/sessions/restore_tab_helper.h"
+#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
+#include "content/browser/debugger/devtools_manager.h"
+#include "content/browser/tab_contents/navigation_controller.h"
+#include "content/browser/tab_contents/navigation_entry.h"
+#include "content/common/devtools_messages.h"
+
+const char DevToolsRemoteServiceCommand::kPing[] = "ping";
+const char DevToolsRemoteServiceCommand::kVersion[] = "version";
+const char DevToolsRemoteServiceCommand::kListTabs[] = "list_tabs";
+
+const char DevToolsRemoteService::kToolName[] = "DevToolsService";
+
+namespace {
+const char kCommandKey[] = "command";
+const char kDataKey[] = "data";
+const char kResultKey[] = "result";
+} // namespace
+
+DevToolsRemoteService::DevToolsRemoteService(DevToolsProtocolHandler* delegate)
+ : delegate_(delegate) {}
+
+DevToolsRemoteService::~DevToolsRemoteService() {}
+
+void DevToolsRemoteService::HandleMessage(
+ const DevToolsRemoteMessage& message) {
+ scoped_ptr<Value> request(base::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(kCommandKey)) {
+ 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(kCommandKey, &command);
+ response.SetString(kCommandKey, command);
+
+ if (command == DevToolsRemoteServiceCommand::kPing) {
+ response.SetInteger(kResultKey, Result::kOk);
+ response.SetString(kDataKey, kOkResponse);
+ } else if (command == DevToolsRemoteServiceCommand::kVersion) {
+ response.SetInteger(kResultKey, Result::kOk);
+ response.SetString(kDataKey, kVersion);
+ } else if (command == DevToolsRemoteServiceCommand::kListTabs) {
+ ListValue* data = new ListValue();
+ const InspectableTabProxy::TabMap& tab_map =
+ delegate_->inspectable_tab_proxy()->tab_map();
+ for (InspectableTabProxy::TabMap::const_iterator it =
+ tab_map.begin(), end = tab_map.end(); it != end; ++it) {
+ NavigationEntry* entry = it->second->controller().GetActiveEntry();
+ if (entry == NULL) {
+ continue;
+ }
+ if (entry->url().is_valid()) {
+ ListValue* tab = new ListValue();
+ tab->Append(Value::CreateIntegerValue(
+ it->second->restore_tab_helper()->session_id().id()));
+ tab->Append(Value::CreateStringValue(entry->url().spec()));
+ data->Append(tab);
+ }
+ }
+ response.SetInteger(kResultKey, Result::kOk);
+ response.Set(kDataKey, data);
+ } else {
+ // Unknown protocol command.
+ NOTREACHED();
+ response.SetInteger(kResultKey, Result::kUnknownCommand);
+ }
+ std::string response_json;
+ base::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..c06ccf5
--- /dev/null
+++ b/chrome/browser/debugger/devtools_remote_service.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#include "base/basictypes.h"
+#include "chrome/browser/debugger/devtools_remote.h"
+
+class DevToolsRemoteMessage;
+class DevToolsProtocolHandler;
+
+namespace base {
+class DictionaryValue;
+}
+
+// Contains constants for DevToolsRemoteService tool protocol commands.
+struct DevToolsRemoteServiceCommand {
+ static const char kPing[];
+ static const char kVersion[];
+ static const char kListTabs[];
+};
+
+// Handles Chrome remote debugger protocol service commands.
+class DevToolsRemoteService : public DevToolsRemoteListener {
+ public:
+ explicit DevToolsRemoteService(DevToolsProtocolHandler* delegate);
+
+ // DevToolsRemoteListener interface
+ virtual void HandleMessage(const DevToolsRemoteMessage& message);
+ virtual void OnConnectionLost() {}
+
+ static const char kToolName[];
+
+ private:
+ // Operation result returned in the "result" field.
+ struct Result {
+ static const int kOk = 0;
+ static const int kUnknownCommand = 1;
+ };
+ virtual ~DevToolsRemoteService();
+ void ProcessJson(base::DictionaryValue* json,
+ const DevToolsRemoteMessage& message);
+ DevToolsProtocolHandler* delegate_;
+ DISALLOW_COPY_AND_ASSIGN(DevToolsRemoteService);
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_DEVTOOLS_REMOTE_SERVICE_H_
diff --git a/chrome/browser/debugger/devtools_sanity_unittest.cc b/chrome/browser/debugger/devtools_sanity_unittest.cc
new file mode 100644
index 0000000..37bd337
--- /dev/null
+++ b/chrome/browser/debugger/devtools_sanity_unittest.cc
@@ -0,0 +1,443 @@
+// Copyright (c) 2011 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/command_line.h"
+#include "base/path_service.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/debugger/devtools_window.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/in_process_browser_test.h"
+#include "chrome/test/ui_test_utils.h"
+#include "content/browser/content_browser_client.h"
+#include "content/browser/debugger/devtools_client_host.h"
+#include "content/browser/debugger/devtools_manager.h"
+#include "content/browser/debugger/worker_devtools_manager_io.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/browser/worker_host/worker_process_host.h"
+#include "content/common/notification_registrar.h"
+#include "content/common/notification_service.h"
+#include "net/test/test_server.h"
+
+namespace {
+
+// Used to block until a dev tools client window's browser is closed.
+class BrowserClosedObserver : public NotificationObserver {
+ public:
+ explicit BrowserClosedObserver(Browser* browser) {
+ registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
+ Source<Browser>(browser));
+ ui_test_utils::RunMessageLoop();
+ }
+
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ MessageLoopForUI::current()->Quit();
+ }
+
+ private:
+ NotificationRegistrar registrar_;
+ DISALLOW_COPY_AND_ASSIGN(BrowserClosedObserver);
+};
+
+// The delay waited in some cases where we don't have a notifications for an
+// action we take.
+const int kActionDelayMs = 500;
+
+const char kDebuggerTestPage[] = "files/devtools/debugger_test_page.html";
+const char kPauseWhenLoadingDevTools[] =
+ "files/devtools/pause_when_loading_devtools.html";
+const char kPauseWhenScriptIsRunning[] =
+ "files/devtools/pause_when_script_is_running.html";
+const char kPageWithContentScript[] =
+ "files/devtools/page_with_content_script.html";
+const char kChunkedTestPage[] = "chunked";
+const char kSlowTestPage[] =
+ "chunked?waitBeforeHeaders=100&waitBetweenChunks=100&chunksNumber=2";
+const char kSharedWorkerTestPage[] =
+ "files/workers/workers_ui_shared_worker.html";
+
+void RunTestFuntion(DevToolsWindow* window, const char* test_name) {
+ std::string result;
+
+ // At first check that JavaScript part of the front-end is loaded by
+ // checking that global variable uiTests exists(it's created after all js
+ // files have been loaded) and has runTest method.
+ ASSERT_TRUE(
+ ui_test_utils::ExecuteJavaScriptAndExtractString(
+ window->GetRenderViewHost(),
+ L"",
+ L"window.domAutomationController.send("
+ L"'' + (window.uiTests && (typeof uiTests.runTest)));",
+ &result));
+
+ if (result == "function") {
+ ASSERT_TRUE(
+ ui_test_utils::ExecuteJavaScriptAndExtractString(
+ window->GetRenderViewHost(),
+ L"",
+ UTF8ToWide(base::StringPrintf("uiTests.runTest('%s')",
+ test_name)),
+ &result));
+ EXPECT_EQ("[OK]", result);
+ } else {
+ FAIL() << "DevTools front-end is broken.";
+ }
+}
+
+class DevToolsSanityTest : public InProcessBrowserTest {
+ public:
+ DevToolsSanityTest()
+ : window_(NULL),
+ inspected_rvh_(NULL) {
+ set_show_window(true);
+ EnableDOMAutomation();
+ }
+
+ protected:
+ void RunTest(const std::string& test_name, const std::string& test_page) {
+ OpenDevToolsWindow(test_page);
+ RunTestFuntion(window_, test_name.c_str());
+ CloseDevToolsWindow();
+ }
+
+ void OpenDevToolsWindow(const std::string& test_page) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL url = test_server()->GetURL(test_page);
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ inspected_rvh_ = GetInspectedTab()->render_view_host();
+ window_ = DevToolsWindow::OpenDevToolsWindow(inspected_rvh_);
+ RenderViewHost* client_rvh = window_->GetRenderViewHost();
+ TabContents* client_contents = client_rvh->delegate()->GetAsTabContents();
+ ui_test_utils::WaitForNavigation(&client_contents->controller());
+ }
+
+ TabContents* GetInspectedTab() {
+ return browser()->GetTabContentsAt(0);
+ }
+
+ void CloseDevToolsWindow() {
+ DevToolsManager* devtools_manager = DevToolsManager::GetInstance();
+ // UnregisterDevToolsClientHostFor may destroy window_ so store the browser
+ // first.
+ Browser* browser = window_->browser();
+ devtools_manager->UnregisterDevToolsClientHostFor(inspected_rvh_);
+
+ // Wait only when DevToolsWindow has a browser. For docked DevTools, this
+ // is NULL and we skip the wait.
+ if (browser)
+ BrowserClosedObserver close_observer(browser);
+ }
+
+ DevToolsWindow* window_;
+ RenderViewHost* inspected_rvh_;
+};
+
+
+class CancelableQuitTask : public Task {
+ public:
+ explicit CancelableQuitTask(const std::string& timeout_message)
+ : timeout_message_(timeout_message),
+ cancelled_(false) {
+ }
+
+ void cancel() {
+ cancelled_ = true;
+ }
+
+ virtual void Run() {
+ if (cancelled_) {
+ return;
+ }
+ FAIL() << timeout_message_;
+ MessageLoop::current()->Quit();
+ }
+
+ private:
+ std::string timeout_message_;
+ bool cancelled_;
+};
+
+
+// Base class for DevTools tests that test devtools functionality for
+// extensions and content scripts.
+class DevToolsExtensionDebugTest : public DevToolsSanityTest,
+ public NotificationObserver {
+ public:
+ DevToolsExtensionDebugTest() : DevToolsSanityTest() {
+ PathService::Get(chrome::DIR_TEST_DATA, &test_extensions_dir_);
+ test_extensions_dir_ = test_extensions_dir_.AppendASCII("devtools");
+ test_extensions_dir_ = test_extensions_dir_.AppendASCII("extensions");
+ }
+
+ protected:
+ // Load an extention from test\data\devtools\extensions\<extension_name>
+ void LoadExtension(const char* extension_name) {
+ FilePath path = test_extensions_dir_.AppendASCII(extension_name);
+ ASSERT_TRUE(LoadExtensionFromPath(path)) << "Failed to load extension.";
+ }
+
+ private:
+ bool LoadExtensionFromPath(const FilePath& path) {
+ ExtensionService* service = browser()->profile()->GetExtensionService();
+ size_t num_before = service->extensions()->size();
+ {
+ NotificationRegistrar registrar;
+ registrar.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
+ NotificationService::AllSources());
+ CancelableQuitTask* delayed_quit =
+ new CancelableQuitTask("Extension load timed out.");
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, delayed_quit,
+ 4*1000);
+ service->LoadExtension(path);
+ ui_test_utils::RunMessageLoop();
+ delayed_quit->cancel();
+ }
+ size_t num_after = service->extensions()->size();
+ if (num_after != (num_before + 1))
+ return false;
+
+ return WaitForExtensionHostsToLoad();
+ }
+
+ bool WaitForExtensionHostsToLoad() {
+ // Wait for all the extension hosts that exist to finish loading.
+ // NOTE: This assumes that the extension host list is not changing while
+ // this method is running.
+
+ NotificationRegistrar registrar;
+ registrar.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
+ NotificationService::AllSources());
+ CancelableQuitTask* delayed_quit =
+ new CancelableQuitTask("Extension host load timed out.");
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, delayed_quit,
+ 4*1000);
+
+ ExtensionProcessManager* manager =
+ browser()->profile()->GetExtensionProcessManager();
+ for (ExtensionProcessManager::const_iterator iter = manager->begin();
+ iter != manager->end();) {
+ if ((*iter)->did_stop_loading())
+ ++iter;
+ else
+ ui_test_utils::RunMessageLoop();
+ }
+
+ delayed_quit->cancel();
+ return true;
+ }
+
+ void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_LOADED:
+ case chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING:
+ MessageLoopForUI::current()->Quit();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ FilePath test_extensions_dir_;
+};
+
+
+
+// Used to block until a navigation completes.
+class LoadStopObserver : public NotificationObserver {
+ public:
+ explicit LoadStopObserver(const NotificationSource& source) : done_(false) {
+ registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, source);
+ ui_test_utils::RunMessageLoop();
+ }
+
+ private:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == content::NOTIFICATION_LOAD_STOP) {
+ if (done_)
+ return;
+ done_ = true;
+ MessageLoopForUI::current()->Quit();
+ }
+ }
+
+ NotificationRegistrar registrar_;
+ bool done_;
+ DISALLOW_COPY_AND_ASSIGN(LoadStopObserver);
+};
+
+
+class WorkerDevToolsSanityTest : public InProcessBrowserTest {
+ public:
+ WorkerDevToolsSanityTest() : window_(NULL) {
+ set_show_window(true);
+ EnableDOMAutomation();
+ }
+
+ protected:
+ void RunTest(const char* test_name, const char* test_page) {
+ ASSERT_TRUE(test_server()->Start());
+ GURL url = test_server()->GetURL(test_page);
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ OpenDevToolsWindowForFirstSharedWorker();
+ RunTestFuntion(window_, test_name);
+ CloseDevToolsWindow();
+ }
+
+ static void OpenDevToolsWindowForFirstSharedWorkerOnIOThread(int attempt) {
+ BrowserChildProcessHost::Iterator iter(ChildProcessInfo::WORKER_PROCESS);
+ bool found = false;
+ for (; !iter.Done(); ++iter) {
+ WorkerProcessHost* worker = static_cast<WorkerProcessHost*>(*iter);
+ const WorkerProcessHost::Instances& instances = worker->instances();
+ for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
+ i != instances.end(); ++i) {
+ if (!i->shared())
+ continue;
+ WorkerDevToolsManagerIO::GetInstance()->OpenDevToolsForWorker(
+ worker->id(), i->worker_route_id());
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ new MessageLoop::QuitTask);
+ } else if (attempt < 30) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableFunction(
+ &OpenDevToolsWindowForFirstSharedWorkerOnIOThread, attempt + 1),
+ 100);
+ } else {
+ FAIL() << "Shared worker not found.";
+ }
+
+ }
+
+ void OpenDevToolsWindowForFirstSharedWorker() {
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, NewRunnableFunction(
+ &OpenDevToolsWindowForFirstSharedWorkerOnIOThread, 1));
+ ui_test_utils::RunMessageLoop();
+ window_ = static_cast<DevToolsWindow*>(
+ DevToolsClientHost::GetDevToolsClientHostForTest());
+ ASSERT_TRUE(window_ != NULL);
+
+ RenderViewHost* client_rvh = window_->GetRenderViewHost();
+ TabContents* client_contents = client_rvh->delegate()->GetAsTabContents();
+ if (client_contents->is_loading()) {
+ LoadStopObserver(
+ Source<NavigationController>(&client_contents->controller()));
+ }
+ }
+
+ void CloseDevToolsWindow() {
+ Browser* browser = window_->browser();
+ browser->CloseAllTabs();
+ BrowserClosedObserver close_observer(browser);
+ }
+
+ DevToolsWindow* window_;
+};
+
+
+// Tests scripts panel showing.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestShowScriptsTab) {
+ RunTest("testShowScriptsTab", kDebuggerTestPage);
+}
+
+// Tests that scripts tab is populated with inspected scripts even if it
+// hadn't been shown by the moment inspected paged refreshed.
+// @see http://crbug.com/26312
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest,
+ TestScriptsTabIsPopulatedOnInspectedPageRefresh) {
+ // Clear inspector settings to ensure that Elements will be
+ // current panel when DevTools window is open.
+ content::GetContentClient()->browser()->ClearInspectorSettings(
+ GetInspectedTab()->render_view_host());
+ RunTest("testScriptsTabIsPopulatedOnInspectedPageRefresh",
+ kDebuggerTestPage);
+}
+
+// Tests that a content script is in the scripts list.
+// This test is disabled, see bug 28961.
+IN_PROC_BROWSER_TEST_F(DevToolsExtensionDebugTest,
+ TestContentScriptIsPresent) {
+ LoadExtension("simple_content_script");
+ RunTest("testContentScriptIsPresent", kPageWithContentScript);
+}
+
+// Tests that scripts are not duplicated after Scripts Panel switch.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest,
+ TestNoScriptDuplicatesOnPanelSwitch) {
+ RunTest("testNoScriptDuplicatesOnPanelSwitch", kDebuggerTestPage);
+}
+
+// Tests that debugger works correctly if pause event occurs when DevTools
+// frontend is being loaded.
+// Flaky - http://crbug.com/69719.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, FLAKY_TestPauseWhenLoadingDevTools) {
+ RunTest("testPauseWhenLoadingDevTools", kPauseWhenLoadingDevTools);
+}
+
+// Tests that pressing 'Pause' will pause script execution if the script
+// is already running.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, FLAKY_TestPauseWhenScriptIsRunning) {
+ RunTest("testPauseWhenScriptIsRunning", kPauseWhenScriptIsRunning);
+}
+
+// Tests network timing.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkTiming) {
+ RunTest("testNetworkTiming", kSlowTestPage);
+}
+
+// Tests network size.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkSize) {
+ RunTest("testNetworkSize", kChunkedTestPage);
+}
+
+// Tests raw headers text.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkSyncSize) {
+ RunTest("testNetworkSyncSize", kChunkedTestPage);
+}
+
+// Tests raw headers text.
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestNetworkRawHeadersText) {
+ RunTest("testNetworkRawHeadersText", kChunkedTestPage);
+}
+
+IN_PROC_BROWSER_TEST_F(DevToolsSanityTest, TestPageWithNoJavaScript) {
+ OpenDevToolsWindow("about:blank");
+ std::string result;
+ ASSERT_TRUE(
+ ui_test_utils::ExecuteJavaScriptAndExtractString(
+ window_->GetRenderViewHost(),
+ L"",
+ L"window.domAutomationController.send("
+ L"'' + (window.uiTests && (typeof uiTests.runTest)));",
+ &result));
+ ASSERT_EQ("function", result) << "DevTools front-end is broken.";
+ CloseDevToolsWindow();
+}
+
+// http://crbug.com/89845
+IN_PROC_BROWSER_TEST_F(WorkerDevToolsSanityTest, DISABLED_InspectSharedWorker) {
+ RunTest("testSharedWorker", kSharedWorkerTestPage);
+}
+
+} // namespace
diff --git a/chrome/browser/debugger/devtools_window.cc b/chrome/browser/debugger/devtools_window.cc
index b5161da..aa697cd 100644
--- a/chrome/browser/debugger/devtools_window.cc
+++ b/chrome/browser/debugger/devtools_window.cc
@@ -237,7 +237,7 @@ void DevToolsWindow::Show(DevToolsToggleAction action) {
ScheduleAction(action);
}
-void DevToolsWindow::Activate() {
+void DevToolsWindow::RequestActivate() {
if (!docked_) {
if (!browser_->window()->IsActive()) {
browser_->window()->Activate();
@@ -249,7 +249,7 @@ void DevToolsWindow::Activate() {
}
}
-void DevToolsWindow::SetDocked(bool docked) {
+void DevToolsWindow::RequestSetDocked(bool docked) {
if (docked_ == docked)
return;
@@ -283,14 +283,14 @@ void DevToolsWindow::SetDocked(bool docked) {
Show(DEVTOOLS_TOGGLE_ACTION_NONE);
}
-void DevToolsWindow::Close() {
+void DevToolsWindow::RequestClose() {
DCHECK(docked_);
NotifyCloseListener();
InspectedTabClosing();
}
-void DevToolsWindow::SaveAs(const std::string& suggested_file_name,
- const std::string& content) {
+void DevToolsWindow::RequestSaveAs(const std::string& suggested_file_name,
+ const std::string& content) {
DevToolsFileUtil::SaveAs(tab_contents_->profile(),
suggested_file_name,
content);
diff --git a/chrome/browser/debugger/devtools_window.h b/chrome/browser/debugger/devtools_window.h
index 0692e85..3d1e706 100644
--- a/chrome/browser/debugger/devtools_window.h
+++ b/chrome/browser/debugger/devtools_window.h
@@ -54,11 +54,11 @@ class DevToolsWindow
virtual void InspectedTabClosing();
virtual void TabReplaced(TabContents* new_tab);
virtual RenderViewHost* GetClientRenderViewHost();
- virtual void Activate();
- virtual void SetDocked(bool docked);
- virtual void Close();
- virtual void SaveAs(const std::string& suggested_file_name,
- const std::string& content);
+ virtual void RequestActivate();
+ virtual void RequestSetDocked(bool docked);
+ virtual void RequestClose();
+ virtual void RequestSaveAs(const std::string& suggested_file_name,
+ const std::string& content);
RenderViewHost* GetRenderViewHost();
void Show(DevToolsToggleAction action);
diff --git a/chrome/browser/debugger/extension_ports_remote_service.cc b/chrome/browser/debugger/extension_ports_remote_service.cc
new file mode 100644
index 0000000..3c0275d
--- /dev/null
+++ b/chrome/browser/debugger/extension_ports_remote_service.cc
@@ -0,0 +1,385 @@
+// Copyright (c) 2011 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.
+
+// Implementation of the ExtensionPortsRemoteService.
+
+// Inspired significantly from debugger_remote_service
+// and ../automation/extension_port_container.
+
+#include "chrome/browser/debugger/extension_ports_remote_service.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/message_loop.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.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/profiles/profile_manager.h"
+#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
+#include "chrome/common/extensions/extension_messages.h"
+#include "content/browser/debugger/devtools_manager.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/common/devtools_messages.h"
+
+namespace {
+
+// Protocol is as follows:
+//
+// From external client:
+// {"command": "connect",
+// "data": {
+// "extensionId": "<extension_id string>",
+// "channelName": "<port name string>", (optional)
+// "tabId": <numerical tab ID> (optional)
+// }
+// }
+// To connect to a background page or tool strip, the tabId should be omitted.
+// Tab IDs can be enumerated with the list_tabs DevToolsService command.
+//
+// Response:
+// {"command": "connect",
+// "result": 0, (assuming success)
+// "data": {
+// "portId": <numerical port ID>
+// }
+// }
+//
+// Posting a message from external client:
+// Put the target message port ID in the devtools destination field.
+// {"command": "postMessage",
+// "data": <message body - arbitrary JSON>
+// }
+// Response:
+// {"command": "postMessage",
+// "result": 0 (Assuming success)
+// }
+// Note this is a confirmation from the devtools protocol layer, not
+// a response from the extension.
+//
+// Message from an extension to the external client:
+// The message port ID is in the devtools destination field.
+// {"command": "onMessage",
+// "result": 0, (Always 0)
+// "data": <message body - arbitrary JSON>
+// }
+//
+// The "disconnect" command from the external client, and
+// "onDisconnect" notification from the ExtensionMessageService, are
+// similar: with the message port ID in the destination field, but no
+// "data" field in this case.
+
+// Commands:
+const char kConnect[] = "connect";
+const char kDisconnect[] = "disconnect";
+const char kPostMessage[] = "postMessage";
+// Events:
+const char kOnMessage[] = "onMessage";
+const char kOnDisconnect[] = "onDisconnect";
+
+// Constants for the JSON message fields.
+// The type is wstring because the constant is used to get a
+// DictionaryValue field (which requires a wide string).
+
+// Mandatory.
+const char kCommandKey[] = "command";
+
+// Always present in messages sent to the external client.
+const char kResultKey[] = "result";
+
+// Field for command-specific parameters. Not strictly necessary, but
+// makes it more similar to the remote debugger protocol, which should
+// allow easier reuse of client code.
+const char kDataKey[] = "data";
+
+// Fields within the "data" dictionary:
+
+// Required for "connect":
+const char kExtensionIdKey[] = "extensionId";
+// Optional in "connect":
+const char kChannelNameKey[] = "channelName";
+const char kTabIdKey[] = "tabId";
+
+// Present under "data" in replies to a successful "connect" .
+const char kPortIdKey[] = "portId";
+
+} // namespace
+
+const std::string ExtensionPortsRemoteService::kToolName = "ExtensionPorts";
+
+ExtensionPortsRemoteService::ExtensionPortsRemoteService(
+ DevToolsProtocolHandler* delegate)
+ : delegate_(delegate), service_(NULL) {
+ // We need an ExtensionMessageService instance. It hangs off of
+ // |profile|. But we do not have a particular tab or RenderViewHost
+ // as context. I'll just use the first active profile not in
+ // incognito mode. But this is probably not the right way.
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ if (!profile_manager) {
+ LOG(WARNING) << "No profile manager for ExtensionPortsRemoteService";
+ return;
+ }
+
+ std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
+ for (size_t i = 0; i < profiles.size(); ++i) {
+ if (!profiles[i]->IsOffTheRecord()) {
+ service_ = profiles[i]->GetExtensionMessageService();
+ break;
+ }
+ }
+ if (!service_)
+ LOG(WARNING) << "No usable profile for ExtensionPortsRemoteService";
+}
+
+ExtensionPortsRemoteService::~ExtensionPortsRemoteService() {
+}
+
+void ExtensionPortsRemoteService::HandleMessage(
+ const DevToolsRemoteMessage& message) {
+ DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI);
+ const std::string destinationString = message.destination();
+ scoped_ptr<Value> request(base::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(kCommandKey)) {
+ NOTREACHED(); // Broken protocol :(
+ return;
+ }
+ std::string command;
+ DictionaryValue response;
+
+ content->GetString(kCommandKey, &command);
+ response.SetString(kCommandKey, command);
+
+ if (!service_) {
+ // This happens if we failed to obtain an ExtensionMessageService
+ // during initialization.
+ NOTREACHED();
+ response.SetInteger(kResultKey, RESULT_NO_SERVICE);
+ SendResponse(response, message.tool(), message.destination());
+ return;
+ }
+
+ int destination = -1;
+ if (!destinationString.empty())
+ base::StringToInt(destinationString, &destination);
+
+ if (command == kConnect) {
+ if (destination != -1) // destination should be empty for this command.
+ response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
+ else
+ ConnectCommand(content, &response);
+ } else if (command == kDisconnect) {
+ if (destination == -1) // Destination required for this command.
+ response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
+ else
+ DisconnectCommand(destination, &response);
+ } else if (command == kPostMessage) {
+ if (destination == -1) // Destination required for this command.
+ response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
+ else
+ PostMessageCommand(destination, content, &response);
+ } else {
+ // Unknown command
+ NOTREACHED();
+ response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
+ }
+ SendResponse(response, message.tool(), message.destination());
+}
+
+void ExtensionPortsRemoteService::OnConnectionLost() {
+ VLOG(1) << "OnConnectionLost";
+ DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI);
+ DCHECK(service_);
+ for (PortIdSet::iterator it = openPortIds_.begin();
+ it != openPortIds_.end();
+ ++it)
+ service_->CloseChannel(*it);
+ openPortIds_.clear();
+}
+
+void ExtensionPortsRemoteService::SendResponse(
+ const Value& response, const std::string& tool,
+ const std::string& destination) {
+ std::string response_content;
+ base::JSONWriter::Write(&response, false, &response_content);
+ scoped_ptr<DevToolsRemoteMessage> response_message(
+ DevToolsRemoteMessageBuilder::instance().Create(
+ tool, destination, response_content));
+ delegate_->Send(*response_message.get());
+}
+
+bool ExtensionPortsRemoteService::Send(IPC::Message *message) {
+ DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI);
+
+ IPC_BEGIN_MESSAGE_MAP(ExtensionPortsRemoteService, *message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnExtensionMessageInvoke)
+ IPC_MESSAGE_UNHANDLED_ERROR()
+ IPC_END_MESSAGE_MAP()
+
+ delete message;
+ return true;
+}
+
+void ExtensionPortsRemoteService::OnExtensionMessageInvoke(
+ const std::string& extension_id,
+ const std::string& function_name,
+ const ListValue& args,
+ const GURL& event_url) {
+ if (function_name == ExtensionMessageService::kDispatchOnMessage) {
+ DCHECK_EQ(args.GetSize(), 2u);
+ std::string message;
+ int port_id;
+ if (args.GetString(0, &message) && args.GetInteger(1, &port_id))
+ OnExtensionMessage(message, port_id);
+ } else if (function_name == ExtensionMessageService::kDispatchOnDisconnect) {
+ DCHECK_EQ(args.GetSize(), 1u);
+ int port_id;
+ if (args.GetInteger(0, &port_id))
+ OnExtensionPortDisconnected(port_id);
+ } else if (function_name == ExtensionMessageService::kDispatchOnConnect) {
+ // There is no way for this service to be addressed and receive
+ // connections.
+ NOTREACHED() << function_name << " shouldn't be called.";
+ } else {
+ NOTREACHED() << function_name << " shouldn't be called.";
+ }
+}
+
+void ExtensionPortsRemoteService::OnExtensionMessage(
+ const std::string& message, int port_id) {
+ VLOG(1) << "Message event: from port " << port_id << ", < " << message << ">";
+ // Transpose the information into a JSON message for the external client.
+ DictionaryValue content;
+ content.SetString(kCommandKey, kOnMessage);
+ content.SetInteger(kResultKey, RESULT_OK);
+ // Turn the stringified message body back into JSON.
+ Value* data = base::JSONReader::Read(message, false);
+ if (!data) {
+ NOTREACHED();
+ return;
+ }
+ content.Set(kDataKey, data);
+ SendResponse(content, kToolName, base::IntToString(port_id));
+}
+
+void ExtensionPortsRemoteService::OnExtensionPortDisconnected(int port_id) {
+ VLOG(1) << "Disconnect event for port " << port_id;
+ openPortIds_.erase(port_id);
+ DictionaryValue content;
+ content.SetString(kCommandKey, kOnDisconnect);
+ content.SetInteger(kResultKey, RESULT_OK);
+ SendResponse(content, kToolName, base::IntToString(port_id));
+}
+
+void ExtensionPortsRemoteService::ConnectCommand(
+ DictionaryValue* content, DictionaryValue* response) {
+ // Parse out the parameters.
+ DictionaryValue* data;
+ if (!content->GetDictionary(kDataKey, &data)) {
+ response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR);
+ return;
+ }
+ std::string extension_id;
+ if (!data->GetString(kExtensionIdKey, &extension_id)) {
+ response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR);
+ return;
+ }
+ std::string channel_name = "";
+ data->GetString(kChannelNameKey, &channel_name); // optional.
+ int tab_id = -1;
+ data->GetInteger(kTabIdKey, &tab_id); // optional.
+ int port_id;
+ if (tab_id != -1) { // Resolve the tab ID.
+ const InspectableTabProxy::TabMap& tab_map =
+ delegate_->inspectable_tab_proxy()->tab_map();
+ InspectableTabProxy::TabMap::const_iterator it = tab_map.find(tab_id);
+ TabContents* tab_contents = NULL;
+ if (it != tab_map.end())
+ tab_contents = it->second->tab_contents();
+ if (!tab_contents) {
+ VLOG(1) << "tab not found: " << tab_id;
+ response->SetInteger(kResultKey, RESULT_TAB_NOT_FOUND);
+ return;
+ }
+ // Ask the ExtensionMessageService to open the channel.
+ VLOG(1) << "Connect: extension_id <" << extension_id
+ << ">, channel_name <" << channel_name
+ << ">, tab " << tab_id;
+ DCHECK(service_);
+ port_id = service_->OpenSpecialChannelToTab(
+ extension_id, channel_name, tab_contents, this);
+ } else { // no tab: channel to an extension' background page / toolstrip.
+ // Ask the ExtensionMessageService to open the channel.
+ VLOG(1) << "Connect: extension_id <" << extension_id
+ << ">, channel_name <" << channel_name << ">";
+ DCHECK(service_);
+ port_id = service_->OpenSpecialChannelToExtension(
+ extension_id, channel_name, "null", this);
+ }
+ if (port_id == -1) {
+ // Failure: probably the extension ID doesn't exist.
+ VLOG(1) << "Connect failed";
+ response->SetInteger(kResultKey, RESULT_CONNECT_FAILED);
+ return;
+ }
+ VLOG(1) << "Connected: port " << port_id;
+ openPortIds_.insert(port_id);
+ // Reply to external client with the port ID assigned to the new channel.
+ DictionaryValue* reply_data = new DictionaryValue();
+ reply_data->SetInteger(kPortIdKey, port_id);
+ response->Set(kDataKey, reply_data);
+ response->SetInteger(kResultKey, RESULT_OK);
+}
+
+void ExtensionPortsRemoteService::DisconnectCommand(
+ int port_id, DictionaryValue* response) {
+ VLOG(1) << "Disconnect port " << port_id;
+ PortIdSet::iterator portEntry = openPortIds_.find(port_id);
+ if (portEntry == openPortIds_.end()) { // unknown port ID.
+ VLOG(1) << "unknown port: " << port_id;
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_PORT);
+ return;
+ }
+ DCHECK(service_);
+ service_->CloseChannel(port_id);
+ openPortIds_.erase(portEntry);
+ response->SetInteger(kResultKey, RESULT_OK);
+}
+
+void ExtensionPortsRemoteService::PostMessageCommand(
+ int port_id, DictionaryValue* content, DictionaryValue* response) {
+ Value* data;
+ if (!content->Get(kDataKey, &data)) {
+ response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR);
+ return;
+ }
+ std::string message;
+ // Stringified the JSON message body.
+ base::JSONWriter::Write(data, false, &message);
+ VLOG(1) << "postMessage: port " << port_id
+ << ", message: <" << message << ">";
+ PortIdSet::iterator portEntry = openPortIds_.find(port_id);
+ if (portEntry == openPortIds_.end()) { // Unknown port ID.
+ VLOG(1) << "unknown port: " << port_id;
+ response->SetInteger(kResultKey, RESULT_UNKNOWN_PORT);
+ return;
+ }
+ // Post the message through the ExtensionMessageService.
+ DCHECK(service_);
+ service_->PostMessageFromRenderer(port_id, message);
+ // Confirm to the external client that we sent its message.
+ response->SetInteger(kResultKey, RESULT_OK);
+}
diff --git a/chrome/browser/debugger/extension_ports_remote_service.h b/chrome/browser/debugger/extension_ports_remote_service.h
new file mode 100644
index 0000000..0a5eea6
--- /dev/null
+++ b/chrome/browser/debugger/extension_ports_remote_service.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 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.
+
+// ExtensionsPorts service: wires extension message ports through the
+// devtools remote protocol, allowing an external client program to
+// exchange messages with Chrome extensions.
+
+#ifndef CHROME_BROWSER_DEBUGGER_EXTENSION_PORTS_REMOTE_SERVICE_H_
+#define CHROME_BROWSER_DEBUGGER_EXTENSION_PORTS_REMOTE_SERVICE_H_
+#pragma once
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "chrome/browser/debugger/devtools_remote.h"
+#include "chrome/browser/extensions/extension_message_service.h"
+#include "ipc/ipc_message.h"
+
+class DevToolsProtocolHandler;
+class DevToolsRemoteMessage;
+class GURL;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+class Value;
+}
+
+class ExtensionPortsRemoteService : public DevToolsRemoteListener,
+ public IPC::Message::Sender {
+ public:
+ // Specifies a tool name ("ExtensionPorts") handled by this class.
+ static const std::string kToolName;
+
+ // |delegate| (never NULL) is the protocol handler instance which
+ // dispatches messages to this service.
+ // The ownership of |delegate| is NOT transferred to this class.
+ explicit ExtensionPortsRemoteService(DevToolsProtocolHandler* delegate);
+
+ // DevToolsRemoteListener methods:
+
+ // Processes |message| from the external client (where the tool is
+ // "ExtensionPorts").
+ virtual void HandleMessage(const DevToolsRemoteMessage& message);
+
+ // Gets invoked on the external client socket connection loss.
+ // Closes open message ports.
+ virtual void OnConnectionLost();
+
+ // IPC::Message::Sender methods:
+
+ // This is the callback through which the ExtensionMessageService
+ // passes us messages from extensions as well as disconnect events.
+ virtual bool Send(IPC::Message* msg);
+
+ private:
+ // Operation result returned in the "result" field in messages sent
+ // to the external client.
+ typedef enum {
+ RESULT_OK = 0,
+ RESULT_UNKNOWN_COMMAND,
+ RESULT_NO_SERVICE,
+ RESULT_PARAMETER_ERROR,
+ RESULT_UNKNOWN_PORT,
+ RESULT_TAB_NOT_FOUND,
+ RESULT_CONNECT_FAILED, // probably extension ID not found.
+ } Result;
+
+ virtual ~ExtensionPortsRemoteService();
+
+ // Sends a JSON message with the |response| to the external client.
+ // |tool| and |destination| are used as the respective header values.
+ void SendResponse(const base::Value& response,
+ const std::string& tool,
+ const std::string& destination);
+
+ // Handles a message from the ExtensionMessageService.
+ void OnExtensionMessageInvoke(const std::string& extension_id,
+ const std::string& function_name,
+ const base::ListValue& args,
+ const GURL& event_url);
+ // Handles a message sent from an extension through the
+ // ExtensionMessageService, to be passed to the external client.
+ void OnExtensionMessage(const std::string& message, int port_id);
+ // Handles a disconnect event sent from the ExtensionMessageService.
+ void OnExtensionPortDisconnected(int port_id);
+
+ // Implementation for the commands we can receive from the external client.
+ // Opens a channel to an extension.
+ void ConnectCommand(base::DictionaryValue* content,
+ base::DictionaryValue* response);
+ // Disconnects a message port.
+ void DisconnectCommand(int port_id, base::DictionaryValue* response);
+ // Sends a message to an extension through an established message port.
+ void PostMessageCommand(int port_id,
+ base::DictionaryValue* content,
+ base::DictionaryValue* response);
+
+ // The delegate is used to send responses and events back to the
+ // external client, and to resolve tab IDs.
+ DevToolsProtocolHandler* delegate_;
+
+ // Set of message port IDs we successfully opened.
+ typedef std::set<int> PortIdSet;
+ PortIdSet openPortIds_;
+
+ scoped_refptr<ExtensionMessageService> service_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionPortsRemoteService);
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_EXTENSION_PORTS_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..7008179
--- /dev/null
+++ b/chrome/browser/debugger/inspectable_tab_proxy.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 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/string_number_conversions.h"
+#include "base/string_util.h"
+#include "chrome/browser/debugger/debugger_remote_service.h"
+#include "chrome/browser/sessions/restore_tab_helper.h"
+#include "chrome/browser/sessions/session_id.h"
+#include "chrome/browser/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
+#include "content/browser/debugger/devtools_client_host.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "content/common/devtools_messages.h"
+
+DevToolsClientHostImpl::DevToolsClientHostImpl(
+ int32 id,
+ DebuggerRemoteService* service,
+ InspectableTabProxy::IdToClientHostMap* map)
+ : id_(id),
+ service_(service),
+ map_(map) {}
+
+DevToolsClientHostImpl::~DevToolsClientHostImpl() {
+ map_->erase(this->id_);
+}
+
+// The debugged tab has closed.
+void DevToolsClientHostImpl::InspectedTabClosing() {
+ TabClosed();
+ delete this;
+}
+
+// The remote debugger has detached.
+void DevToolsClientHostImpl::CloseImpl() {
+ NotifyCloseListener();
+ delete this;
+}
+
+void DevToolsClientHostImpl::SendMessageToClient(
+ const IPC::Message& msg) {
+ // TODO(prybin): Restore FrameNavigate.
+ IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg)
+ IPC_MESSAGE_HANDLER(DevToolsClientMsg_DebuggerOutput, OnDebuggerOutput);
+ IPC_MESSAGE_UNHANDLED_ERROR()
+ IPC_END_MESSAGE_MAP()
+}
+
+void DevToolsClientHostImpl::TabReplaced(TabContents* new_tab) {
+ map_->erase(id_);
+ TabContentsWrapper* new_tab_wrapper =
+ TabContentsWrapper::GetCurrentWrapperForContents(new_tab);
+ DCHECK(new_tab_wrapper);
+ if (!new_tab_wrapper)
+ return;
+ id_ = new_tab_wrapper->restore_tab_helper()->session_id().id();
+ (*map_)[id_] = this;
+}
+
+void DevToolsClientHostImpl::OnDebuggerOutput(const std::string& data) {
+ service_->DebuggerOutput(id_, data);
+}
+
+void DevToolsClientHostImpl::FrameNavigating(const std::string& url) {
+ service_->FrameNavigate(id_, url);
+}
+
+void DevToolsClientHostImpl::TabClosed() {
+ service_->TabClosed(id_);
+}
+
+InspectableTabProxy::InspectableTabProxy() {}
+
+InspectableTabProxy::~InspectableTabProxy() {}
+
+const InspectableTabProxy::TabMap& InspectableTabProxy::tab_map() {
+ tab_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) {
+ TabContentsWrapper* tab = model->GetTabContentsAt(i);
+ tab_map_[tab->restore_tab_helper()->session_id().id()] = tab;
+ }
+ }
+ return tab_map_;
+}
+
+DevToolsClientHostImpl* InspectableTabProxy::ClientHostForTabId(
+ int32 id) {
+ InspectableTabProxy::IdToClientHostMap::const_iterator it =
+ id_to_client_host_map_.find(id);
+ if (it == id_to_client_host_map_.end()) {
+ return NULL;
+ }
+ return it->second;
+}
+
+DevToolsClientHost* InspectableTabProxy::NewClientHost(
+ int32 id,
+ DebuggerRemoteService* service) {
+ DevToolsClientHostImpl* client_host =
+ new DevToolsClientHostImpl(id, service, &id_to_client_host_map_);
+ id_to_client_host_map_[id] = client_host;
+ return client_host;
+}
+
+void InspectableTabProxy::OnRemoteDebuggerDetached() {
+ while (!id_to_client_host_map_.empty()) {
+ IdToClientHostMap::iterator it = id_to_client_host_map_.begin();
+ it->second->debugger_remote_service()->DetachFromTab(
+ base::IntToString(it->first), NULL);
+ }
+}
diff --git a/chrome/browser/debugger/inspectable_tab_proxy.h b/chrome/browser/debugger/inspectable_tab_proxy.h
new file mode 100644
index 0000000..0ccaf76
--- /dev/null
+++ b/chrome/browser/debugger/inspectable_tab_proxy.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2011 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_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/hash_tables.h"
+#include "content/browser/debugger/devtools_client_host.h"
+
+class DebuggerRemoteService;
+class DevToolsClientHost;
+class DevToolsClientHostImpl;
+struct DevToolsMessageData;
+class TabContentsWrapper;
+
+// Proxies debugged tabs' TabContentsWrapper using their UIDs.
+// Keeps track of tabs being debugged so that we can detach from
+// them on remote debugger connection loss.
+class InspectableTabProxy {
+ public:
+ typedef base::hash_map<int32, TabContentsWrapper*> TabMap;
+ typedef base::hash_map<int32, DevToolsClientHostImpl*> IdToClientHostMap;
+
+ InspectableTabProxy();
+ virtual ~InspectableTabProxy();
+
+ // Returns a map of SessionID to TabContentsWrapper for all Browser
+ // instances. Clients should not keep the result around for extended periods
+ // of time as tabs might get closed thus invalidating the map.
+ const TabMap& tab_map();
+
+ // Returns a DevToolsClientHostImpl for the given tab |id|.
+ DevToolsClientHostImpl* ClientHostForTabId(int32 id);
+
+ // 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.
+ DevToolsClientHost* NewClientHost(int32 id,
+ DebuggerRemoteService* service);
+
+ // Gets invoked when a remote debugger is detached. In this case we should
+ // send the corresponding message to the V8 debugger for each of the tabs
+ // the debugger is attached to, and invoke InspectedTabClosing().
+ void OnRemoteDebuggerDetached();
+
+ private:
+ TabMap tab_map_;
+ IdToClientHostMap id_to_client_host_map_;
+ DISALLOW_COPY_AND_ASSIGN(InspectableTabProxy);
+};
+
+
+// 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,
+ InspectableTabProxy::IdToClientHostMap* map);
+ virtual ~DevToolsClientHostImpl();
+
+ DebuggerRemoteService* debugger_remote_service() {
+ return service_;
+ }
+
+ void CloseImpl();
+
+ // DevToolsClientHost interface
+ virtual void InspectedTabClosing();
+ virtual void SendMessageToClient(const IPC::Message& msg);
+ virtual void TabReplaced(TabContents* new_tab);
+
+ private:
+ // Message handling routines
+ void OnDebuggerOutput(const std::string& msg);
+ virtual void FrameNavigating(const std::string& url);
+ void TabClosed();
+
+ int32 id_;
+ DebuggerRemoteService* service_;
+ InspectableTabProxy::IdToClientHostMap* map_;
+};
+
+#endif // CHROME_BROWSER_DEBUGGER_INSPECTABLE_TAB_PROXY_H_