summaryrefslogtreecommitdiffstats
path: root/chrome/browser/debugger
diff options
context:
space:
mode:
authorsdoyon@chromium.org <sdoyon@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-26 19:30:15 +0000
committersdoyon@chromium.org <sdoyon@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-26 19:30:15 +0000
commita1ec6d1f82699b1e06cca1bca9efdb4ed212c2c3 (patch)
tree75e77bb933a9ce8149df5053fa82fe619c42c044 /chrome/browser/debugger
parent51a0f7d1e4a47ff6dbc739687237f17a62bc8333 (diff)
downloadchromium_src-a1ec6d1f82699b1e06cca1bca9efdb4ed212c2c3.zip
chromium_src-a1ec6d1f82699b1e06cca1bca9efdb4ed212c2c3.tar.gz
chromium_src-a1ec6d1f82699b1e06cca1bca9efdb4ed212c2c3.tar.bz2
Extension ports devtools remote service.
Wires message ports to extensions through the devtools remote socket. BUG=none TEST=none Review URL: http://codereview.chromium.org/174226 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24499 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/debugger')
-rw-r--r--chrome/browser/debugger/debugger_wrapper.cc4
-rw-r--r--chrome/browser/debugger/extension_ports_remote_service.cc383
-rw-r--r--chrome/browser/debugger/extension_ports_remote_service.h105
3 files changed, 492 insertions, 0 deletions
diff --git a/chrome/browser/debugger/debugger_wrapper.cc b/chrome/browser/debugger/debugger_wrapper.cc
index fa2fac6..c551846 100644
--- a/chrome/browser/debugger/debugger_wrapper.cc
+++ b/chrome/browser/debugger/debugger_wrapper.cc
@@ -7,6 +7,7 @@
#include "chrome/browser/debugger/debugger_remote_service.h"
#include "chrome/browser/debugger/devtools_protocol_handler.h"
#include "chrome/browser/debugger/devtools_remote_service.h"
+#include "chrome/browser/debugger/extension_ports_remote_service.h"
DebuggerWrapper::DebuggerWrapper(int port) {
if (port > 0) {
@@ -17,6 +18,9 @@ DebuggerWrapper::DebuggerWrapper(int port) {
proto_handler_->RegisterDestination(
new DebuggerRemoteService(proto_handler_),
DebuggerRemoteService::kToolName);
+ proto_handler_->RegisterDestination(
+ new ExtensionPortsRemoteService(proto_handler_),
+ ExtensionPortsRemoteService::kToolName);
proto_handler_->Start();
}
}
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..a6bfa22
--- /dev/null
+++ b/chrome/browser/debugger/extension_ports_remote_service.cc
@@ -0,0 +1,383 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// 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_reader.h"
+#include "base/json_writer.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/debugger/devtools_manager.h"
+#include "chrome/browser/debugger/devtools_protocol_handler.h"
+#include "chrome/browser/debugger/devtools_remote_message.h"
+#include "chrome/browser/debugger/inspectable_tab_proxy.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/profile_manager.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/devtools_messages.h"
+#include "chrome/common/render_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:
+static const std::string kConnect = "connect";
+static const std::string kDisconnect = "disconnect";
+static const std::string kPostMessage = "postMessage";
+// Events:
+static const std::string kOnMessage = "onMessage";
+static const std::string 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.
+static const std::wstring kCommandWide = L"command";
+
+// Always present in messages sent to the external client.
+static const std::wstring kResultWide = L"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.
+static const std::wstring kDataWide = L"data";
+
+// Fields within the "data" dictionary:
+
+// Required for "connect":
+static const std::wstring kExtensionIdWide = L"extensionId";
+// Optional in "connect":
+static const std::wstring kChannelNameWide = L"channelName";
+static const std::wstring kTabIdWide = L"tabId";
+
+// Present under "data" in replies to a successful "connect" .
+static const std::wstring kPortIdWide = L"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;
+ }
+ for (ProfileManager::ProfileVector::const_iterator it
+ = profile_manager->begin();
+ it != profile_manager->end();
+ ++it) {
+ if (!(*it)->IsOffTheRecord()) {
+ service_ = (*it)->GetExtensionMessageService();
+ break;
+ }
+ }
+ if (!service_)
+ LOG(WARNING) << "No usable profile for 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(JSONReader::Read(message.content(), true));
+ if (request.get() == NULL) {
+ // Bad JSON
+ NOTREACHED();
+ return;
+ }
+ DictionaryValue* content;
+ if (!request->IsType(Value::TYPE_DICTIONARY)) {
+ NOTREACHED(); // Broken protocol :(
+ return;
+ }
+ content = static_cast<DictionaryValue*>(request.get());
+ if (!content->HasKey(kCommandWide)) {
+ NOTREACHED(); // Broken protocol :(
+ return;
+ }
+ std::string command;
+ DictionaryValue response;
+
+ content->GetString(kCommandWide, &command);
+ response.SetString(kCommandWide, command);
+
+ if (!service_) {
+ // This happens if we failed to obtain an ExtensionMessageService
+ // during initialization.
+ NOTREACHED();
+ response.SetInteger(kResultWide, RESULT_NO_SERVICE);
+ SendResponse(response, message.tool(), message.destination());
+ return;
+ }
+
+ int destination = -1;
+ if (destinationString.size() != 0)
+ StringToInt(destinationString, &destination);
+
+ if (command == kConnect) {
+ if (destination != -1) // destination should be empty for this command.
+ response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND);
+ else
+ ConnectCommand(content, &response);
+ } else if (command == kDisconnect) {
+ if (destination == -1) // Destination required for this command.
+ response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND);
+ else
+ DisconnectCommand(destination, &response);
+ } else if (command == kPostMessage) {
+ if (destination == -1) // Destination required for this command.
+ response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND);
+ else
+ PostMessageCommand(destination, content, &response);
+ } else {
+ // Unknown command
+ NOTREACHED();
+ response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND);
+ }
+ SendResponse(response, message.tool(), message.destination());
+}
+
+void ExtensionPortsRemoteService::OnConnectionLost() {
+ LOG(INFO) << "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;
+ 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(ViewMsg_ExtensionMessageInvoke,
+ OnExtensionMessageInvoke)
+ IPC_MESSAGE_UNHANDLED_ERROR()
+ IPC_END_MESSAGE_MAP()
+
+ delete message;
+ return true;
+}
+
+void ExtensionPortsRemoteService::OnExtensionMessageInvoke(
+ const std::string& function_name, const ListValue& args) {
+ 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) {
+ LOG(INFO) << "Message event: from port " << port_id
+ << ", < " << message << ">";
+ // Transpose the information into a JSON message for the external client.
+ DictionaryValue content;
+ content.SetString(kCommandWide, kOnMessage);
+ content.SetInteger(kResultWide, RESULT_OK);
+ // Turn the stringified message body back into JSON.
+ Value* data = JSONReader::Read(message, false);
+ if (!data) {
+ NOTREACHED();
+ return;
+ }
+ content.Set(kDataWide, data);
+ SendResponse(content, kToolName, IntToString(port_id));
+}
+
+void ExtensionPortsRemoteService::OnExtensionPortDisconnected(int port_id) {
+ LOG(INFO) << "Disconnect event for port " << port_id;
+ openPortIds_.erase(port_id);
+ DictionaryValue content;
+ content.SetString(kCommandWide, kOnDisconnect);
+ content.SetInteger(kResultWide, RESULT_OK);
+ SendResponse(content, kToolName, IntToString(port_id));
+}
+
+void ExtensionPortsRemoteService::ConnectCommand(
+ DictionaryValue* content, DictionaryValue* response) {
+ // Parse out the parameters.
+ DictionaryValue* data;
+ if (!content->GetDictionary(kDataWide, &data)) {
+ response->SetInteger(kResultWide, RESULT_PARAMETER_ERROR);
+ return;
+ }
+ std::string extension_id;
+ if (!data->GetString(kExtensionIdWide, &extension_id)) {
+ response->SetInteger(kResultWide, RESULT_PARAMETER_ERROR);
+ return;
+ }
+ std::string channel_name = "";
+ data->GetString(kChannelNameWide, &channel_name); // optional.
+ int tab_id = -1;
+ data->GetInteger(kTabIdWide, &tab_id); // optional.
+ int port_id;
+ if (tab_id != -1) { // Resolve the tab ID.
+ const InspectableTabProxy::ControllersMap& navcon_map =
+ delegate_->inspectable_tab_proxy()->controllers_map();
+ InspectableTabProxy::ControllersMap::const_iterator it =
+ navcon_map.find(tab_id);
+ TabContents* tab_contents = NULL;
+ if (it != navcon_map.end())
+ tab_contents = it->second->tab_contents();
+ if (!tab_contents) {
+ LOG(INFO) << "tab not found: " << tab_id;
+ response->SetInteger(kResultWide, RESULT_TAB_NOT_FOUND);
+ return;
+ }
+ // Ask the ExtensionMessageService to open the channel.
+ LOG(INFO) << "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.
+ LOG(INFO) << "Connect: extension_id <" << extension_id
+ << ">, channel_name <" << channel_name << ">";
+ DCHECK(service_);
+ port_id = service_->OpenSpecialChannelToExtension(
+ extension_id, channel_name, this);
+ }
+ if (port_id == -1) {
+ // Failure: probably the extension ID doesn't exist.
+ LOG(INFO) << "Connect failed";
+ response->SetInteger(kResultWide, RESULT_CONNECT_FAILED);
+ return;
+ }
+ LOG(INFO) << "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(kPortIdWide, port_id);
+ response->Set(kDataWide, reply_data);
+ response->SetInteger(kResultWide, RESULT_OK);
+}
+
+void ExtensionPortsRemoteService::DisconnectCommand(
+ int port_id, DictionaryValue* response) {
+ LOG(INFO) << "Disconnect port " << port_id;
+ PortIdSet::iterator portEntry = openPortIds_.find(port_id);
+ if (portEntry == openPortIds_.end()) { // unknown port ID.
+ LOG(INFO) << "unknown port: " << port_id;
+ response->SetInteger(kResultWide, RESULT_UNKNOWN_PORT);
+ return;
+ }
+ DCHECK(service_);
+ service_->CloseChannel(port_id);
+ openPortIds_.erase(portEntry);
+ response->SetInteger(kResultWide, RESULT_OK);
+}
+
+void ExtensionPortsRemoteService::PostMessageCommand(
+ int port_id, DictionaryValue* content, DictionaryValue* response) {
+ Value* data;
+ if (!content->Get(kDataWide, &data)) {
+ response->SetInteger(kResultWide, RESULT_PARAMETER_ERROR);
+ return;
+ }
+ std::string message;
+ // Stringified the JSON message body.
+ JSONWriter::Write(data, false, &message);
+ LOG(INFO) << "postMessage: port " << port_id
+ << ", message: <" << message << ">";
+ PortIdSet::iterator portEntry = openPortIds_.find(port_id);
+ if (portEntry == openPortIds_.end()) { // Unknown port ID.
+ LOG(INFO) << "unknown port: " << port_id;
+ response->SetInteger(kResultWide, 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(kResultWide, 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..1bf9a7a
--- /dev/null
+++ b/chrome/browser/debugger/extension_ports_remote_service.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// 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_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/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 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);
+ virtual ~ExtensionPortsRemoteService() {}
+
+ // 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;
+
+ // Sends a JSON message with the |response| to the external client.
+ // |tool| and |destination| are used as the respective header values.
+ void SendResponse(const Value& response,
+ const std::string& tool,
+ const std::string& destination);
+
+ // Handles a message from the ExtensionMessageService.
+ void OnExtensionMessageInvoke(
+ const std::string& function_name, const ListValue& args);
+ // 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(DictionaryValue* content, DictionaryValue* response);
+ // Disconnects a message port.
+ void DisconnectCommand(int port_id, DictionaryValue* response);
+ // Sends a message to an extension through an established message port.
+ void PostMessageCommand(int port_id, DictionaryValue* content,
+ 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_