OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // Implementation of the ExtensionPortsRemoteService. |
| 6 |
| 7 // Inspired significantly from debugger_remote_service |
| 8 // and ../automation/extension_port_container. |
| 9 |
| 10 #include "chrome/browser/debugger/extension_ports_remote_service.h" |
| 11 |
| 12 #include "base/json_reader.h" |
| 13 #include "base/json_writer.h" |
| 14 #include "base/message_loop.h" |
| 15 #include "base/string_util.h" |
| 16 #include "base/values.h" |
| 17 #include "chrome/browser/browser_process.h" |
| 18 #include "chrome/browser/debugger/devtools_manager.h" |
| 19 #include "chrome/browser/debugger/devtools_protocol_handler.h" |
| 20 #include "chrome/browser/debugger/devtools_remote_message.h" |
| 21 #include "chrome/browser/debugger/inspectable_tab_proxy.h" |
| 22 #include "chrome/browser/profile.h" |
| 23 #include "chrome/browser/profile_manager.h" |
| 24 #include "chrome/browser/tab_contents/tab_contents.h" |
| 25 #include "chrome/common/devtools_messages.h" |
| 26 #include "chrome/common/render_messages.h" |
| 27 |
| 28 namespace { |
| 29 |
| 30 // Protocol is as follows: |
| 31 // |
| 32 // From external client: |
| 33 // {"command": "connect", |
| 34 // "data": { |
| 35 // "extensionId": "<extension_id string>", |
| 36 // "channelName": "<port name string>", (optional) |
| 37 // "tabId": <numerical tab ID> (optional) |
| 38 // } |
| 39 // } |
| 40 // To connect to a background page or tool strip, the tabId should be omitted. |
| 41 // Tab IDs can be enumerated with the list_tabs DevToolsService command. |
| 42 // |
| 43 // Response: |
| 44 // {"command": "connect", |
| 45 // "result": 0, |
| 46 // "data": { |
| 47 // "portId": <numerical port ID> |
| 48 // } |
| 49 // } |
| 50 // |
| 51 // Posting a message from external client: |
| 52 // Put the target message port ID in the devtools destination field. |
| 53 // {"command": "postMessage", |
| 54 // "data": <message body - arbitrary JSON> |
| 55 // } |
| 56 // Response: |
| 57 // {"command": "postMessage", |
| 58 // "result": 0 |
| 59 // } |
| 60 // Note this is a confirmation from the devtools protocol layer, not |
| 61 // a response from the extension. |
| 62 // |
| 63 // Message from an extension to the external client: |
| 64 // The message port ID is in the devtools destination field. |
| 65 // {"command": "onMessage", |
| 66 // "data": <message body - arbitrary JSON> |
| 67 // } |
| 68 // |
| 69 // The "disconnect" command from the external client, and |
| 70 // "onDisconnect" notification from the ExtensionMessageService, are |
| 71 // similar: with the message port ID in the destination field, but no |
| 72 // "data" field in this case. |
| 73 |
| 74 // Commands: |
| 75 static const std::string kConnect = "connect"; |
| 76 static const std::string kDisconnect = "disconnect"; |
| 77 static const std::string kPostMessage = "postMessage"; |
| 78 // Events: |
| 79 static const std::string kOnMessage = "onMessage"; |
| 80 static const std::string kOnDisconnect = "onDisconnect"; |
| 81 |
| 82 // Constants for the JSON message fields. |
| 83 // The type is wstring because the constant is used to get a |
| 84 // DictionaryValue field (which requires a wide string). |
| 85 |
| 86 // Mandatory. |
| 87 static const std::wstring kCommandWide = L"command"; |
| 88 |
| 89 // Always present in messages sent to the external client. |
| 90 static const std::wstring kResultWide = L"result"; |
| 91 |
| 92 // Field for command-specific parameters. Not strictly necessary, but |
| 93 // makes it more similar to the remote debugger protocol, which should |
| 94 // allow easier reuse of client code. |
| 95 static const std::wstring kDataWide = L"data"; |
| 96 |
| 97 // Fields within the "data" dictionary: |
| 98 |
| 99 // Required for "connect": |
| 100 static const std::wstring kExtensionIdWide = L"extensionId"; |
| 101 // Optional in "connect": |
| 102 static const std::wstring kChannelNameWide = L"channelName"; |
| 103 static const std::wstring kTabIdWide = L"tabId"; |
| 104 |
| 105 // Present under "data" in replies to a successful "connect" . |
| 106 static const std::wstring kPortIdWide = L"portId"; |
| 107 |
| 108 } // namespace |
| 109 |
| 110 const std::string ExtensionPortsRemoteService::kToolName = "ExtensionPorts"; |
| 111 |
| 112 ExtensionPortsRemoteService::ExtensionPortsRemoteService( |
| 113 DevToolsProtocolHandler* delegate) |
| 114 : delegate_(delegate), service_(NULL) { |
| 115 // We need an ExtensionMessageService instance. It hangs off of |
| 116 // |profile|. But we do not have a particular tab or RenderViewHost |
| 117 // as context. I'll just use the first active profile not in |
| 118 // incognito mode. But this is probably not the right way. |
| 119 ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| 120 if (!profile_manager) { |
| 121 LOG(WARNING) << "No profile manager for ExtensionPortsRemoteService"; |
| 122 return; |
| 123 } |
| 124 for (ProfileManager::ProfileVector::const_iterator it |
| 125 = profile_manager->begin(); |
| 126 it != profile_manager->end(); |
| 127 ++it) { |
| 128 if (!(*it)->IsOffTheRecord()) { |
| 129 service_ = (*it)->GetExtensionMessageService(); |
| 130 break; |
| 131 } |
| 132 } |
| 133 if (!service_) |
| 134 LOG(WARNING) << "No usable profile for ExtensionPortsRemoteService"; |
| 135 } |
| 136 |
| 137 void ExtensionPortsRemoteService::HandleMessage( |
| 138 const DevToolsRemoteMessage& message) { |
| 139 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); |
| 140 const std::string destinationString = message.destination(); |
| 141 scoped_ptr<Value> request(JSONReader::Read(message.content(), true)); |
| 142 if (request.get() == NULL) { |
| 143 // Bad JSON |
| 144 NOTREACHED(); |
| 145 return; |
| 146 } |
| 147 DictionaryValue* content; |
| 148 if (!request->IsType(Value::TYPE_DICTIONARY)) { |
| 149 NOTREACHED(); // Broken protocol :( |
| 150 return; |
| 151 } |
| 152 content = static_cast<DictionaryValue*>(request.get()); |
| 153 if (!content->HasKey(kCommandWide)) { |
| 154 NOTREACHED(); // Broken protocol :( |
| 155 return; |
| 156 } |
| 157 std::string command; |
| 158 DictionaryValue response; |
| 159 |
| 160 content->GetString(kCommandWide, &command); |
| 161 response.SetString(kCommandWide, command); |
| 162 |
| 163 if (!service_) { |
| 164 // This happens if we failed to obtain an ExtensionMessageService |
| 165 // during initialization. |
| 166 NOTREACHED(); |
| 167 response.SetInteger(kResultWide, RESULT_NO_SERVICE); |
| 168 SendResponse(response, message.tool(), message.destination()); |
| 169 return; |
| 170 } |
| 171 |
| 172 int destination = -1; |
| 173 if (destinationString.size() != 0) |
| 174 StringToInt(destinationString, &destination); |
| 175 |
| 176 if (command == kConnect) { |
| 177 if (destination != -1) // destination should be empty for this command. |
| 178 response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND); |
| 179 else |
| 180 ConnectCommand(content, &response); |
| 181 } else if (command == kDisconnect) { |
| 182 if (destination == -1) // Destination required for this command. |
| 183 response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND); |
| 184 else |
| 185 DisconnectCommand(destination, &response); |
| 186 } else if (command == kPostMessage) { |
| 187 if (destination == -1) // Destination required for this command. |
| 188 response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND); |
| 189 else |
| 190 PostMessageCommand(destination, content, &response); |
| 191 } else { |
| 192 // Unknown command |
| 193 NOTREACHED(); |
| 194 response.SetInteger(kResultWide, RESULT_UNKNOWN_COMMAND); |
| 195 } |
| 196 SendResponse(response, message.tool(), message.destination()); |
| 197 } |
| 198 |
| 199 void ExtensionPortsRemoteService::OnConnectionLost() { |
| 200 LOG(INFO) << "OnConnectionLost"; |
| 201 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); |
| 202 DCHECK(service_); |
| 203 for (PortIdSet::iterator it = openPortIds_.begin(); |
| 204 it != openPortIds_.end(); |
| 205 ++it) |
| 206 service_->CloseChannel(*it); |
| 207 openPortIds_.clear(); |
| 208 } |
| 209 |
| 210 void ExtensionPortsRemoteService::SendResponse( |
| 211 const Value& response, const std::string& tool, |
| 212 const std::string& destination) { |
| 213 std::string response_content; |
| 214 JSONWriter::Write(&response, false, &response_content); |
| 215 scoped_ptr<DevToolsRemoteMessage> response_message( |
| 216 DevToolsRemoteMessageBuilder::instance().Create( |
| 217 tool, destination, response_content)); |
| 218 delegate_->Send(*response_message.get()); |
| 219 } |
| 220 |
| 221 bool ExtensionPortsRemoteService::Send(IPC::Message *message) { |
| 222 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); |
| 223 |
| 224 IPC_BEGIN_MESSAGE_MAP(ExtensionPortsRemoteService, *message) |
| 225 IPC_MESSAGE_HANDLER(ViewMsg_ExtensionMessageInvoke, |
| 226 OnExtensionMessageInvoke) |
| 227 IPC_MESSAGE_UNHANDLED_ERROR() |
| 228 IPC_END_MESSAGE_MAP() |
| 229 |
| 230 delete message; |
| 231 return true; |
| 232 } |
| 233 |
| 234 void ExtensionPortsRemoteService::OnExtensionMessageInvoke( |
| 235 const std::string& function_name, const ListValue& args) { |
| 236 if (function_name == ExtensionMessageService::kDispatchOnMessage) { |
| 237 DCHECK_EQ(args.GetSize(), 2u); |
| 238 std::string message; |
| 239 int port_id; |
| 240 if (args.GetString(0, &message) && args.GetInteger(1, &port_id)) |
| 241 OnExtensionMessage(message, port_id); |
| 242 } else if (function_name == ExtensionMessageService::kDispatchOnDisconnect) { |
| 243 DCHECK_EQ(args.GetSize(), 1u); |
| 244 int port_id; |
| 245 if (args.GetInteger(0, &port_id)) |
| 246 OnExtensionPortDisconnected(port_id); |
| 247 } else if (function_name == ExtensionMessageService::kDispatchOnConnect) { |
| 248 // There is no way for this service to be addressed and receive |
| 249 // connections. |
| 250 NOTREACHED() << function_name << " shouldn't be called."; |
| 251 } else { |
| 252 NOTREACHED() << function_name << " shouldn't be called."; |
| 253 } |
| 254 } |
| 255 |
| 256 void ExtensionPortsRemoteService::OnExtensionMessage( |
| 257 const std::string& message, int port_id) { |
| 258 LOG(INFO) << "Message event: from port " << port_id |
| 259 << ", < " << message << ">"; |
| 260 // Transpose the information into a JSON message for the external client. |
| 261 DictionaryValue content; |
| 262 content.SetString(kCommandWide, kOnMessage); |
| 263 content.SetInteger(kResultWide, RESULT_OK); |
| 264 // Turn the stringified message body back into JSON. |
| 265 Value* data = JSONReader::Read(message, false); |
| 266 if (!data) { |
| 267 NOTREACHED(); |
| 268 return; |
| 269 } |
| 270 content.Set(kDataWide, data); |
| 271 SendResponse(content, kToolName, IntToString(port_id)); |
| 272 } |
| 273 |
| 274 void ExtensionPortsRemoteService::OnExtensionPortDisconnected(int port_id) { |
| 275 LOG(INFO) << "Disconnect event for port " << port_id; |
| 276 openPortIds_.erase(port_id); |
| 277 DictionaryValue content; |
| 278 content.SetString(kCommandWide, kOnDisconnect); |
| 279 content.SetInteger(kResultWide, RESULT_OK); |
| 280 SendResponse(content, kToolName, IntToString(port_id)); |
| 281 } |
| 282 |
| 283 void ExtensionPortsRemoteService::ConnectCommand( |
| 284 DictionaryValue* content, DictionaryValue* response) { |
| 285 // Parse out the parameters. |
| 286 DictionaryValue* data; |
| 287 if (!content->GetDictionary(kDataWide, &data)) { |
| 288 response->SetInteger(kResultWide, RESULT_PARAMETER_ERROR); |
| 289 return; |
| 290 } |
| 291 std::string extension_id; |
| 292 if (!data->GetString(kExtensionIdWide, &extension_id)) { |
| 293 response->SetInteger(kResultWide, RESULT_PARAMETER_ERROR); |
| 294 return; |
| 295 } |
| 296 std::string channel_name = ""; |
| 297 data->GetString(kChannelNameWide, &channel_name); // optional. |
| 298 int tab_id = -1; |
| 299 data->GetInteger(kTabIdWide, &tab_id); // optional. |
| 300 int port_id; |
| 301 if (tab_id != -1) { // Resolve the tab ID. |
| 302 const InspectableTabProxy::ControllersMap& navcon_map = |
| 303 delegate_->inspectable_tab_proxy()->controllers_map(); |
| 304 InspectableTabProxy::ControllersMap::const_iterator it = |
| 305 navcon_map.find(tab_id); |
| 306 TabContents* tab_contents = NULL; |
| 307 if (it != navcon_map.end()) |
| 308 tab_contents = it->second->tab_contents(); |
| 309 if (!tab_contents) { |
| 310 LOG(INFO) << "tab not found: " << tab_id; |
| 311 response->SetInteger(kResultWide, RESULT_TAB_NOT_FOUND); |
| 312 return; |
| 313 } |
| 314 // Ask the ExtensionMessageService to open the channel. |
| 315 LOG(INFO) << "Connect: extension_id <" << extension_id |
| 316 << ">, channel_name <" << channel_name << ">" |
| 317 << ", tab " << tab_id; |
| 318 DCHECK(service_); |
| 319 port_id = service_->OpenSpecialChannelToTab( |
| 320 extension_id, channel_name, tab_contents, this); |
| 321 } else { // no tab: channel to an extension' background page / toolstrip. |
| 322 // Ask the ExtensionMessageService to open the channel. |
| 323 LOG(INFO) << "Connect: extension_id <" << extension_id |
| 324 << ">, channel_name <" << channel_name << ">"; |
| 325 DCHECK(service_); |
| 326 port_id = service_->OpenSpecialChannelToExtension( |
| 327 extension_id, channel_name, this); |
| 328 } |
| 329 if (port_id == -1) { |
| 330 // Failure: probably the extension ID doesn't exist. |
| 331 LOG(INFO) << "Connect failed"; |
| 332 response->SetInteger(kResultWide, RESULT_CONNECT_FAILED); |
| 333 return; |
| 334 } |
| 335 LOG(INFO) << "Connected: port " << port_id; |
| 336 openPortIds_.insert(port_id); |
| 337 // Reply to external client with the port ID assigned to the new channel. |
| 338 DictionaryValue* reply_data = new DictionaryValue(); |
| 339 reply_data->SetInteger(kPortIdWide, port_id); |
| 340 response->Set(kDataWide, reply_data); |
| 341 response->SetInteger(kResultWide, RESULT_OK); |
| 342 } |
| 343 |
| 344 void ExtensionPortsRemoteService::DisconnectCommand( |
| 345 int port_id, DictionaryValue* response) { |
| 346 LOG(INFO) << "Disconnect port " << port_id; |
| 347 PortIdSet::iterator portEntry = openPortIds_.find(port_id); |
| 348 if (portEntry == openPortIds_.end()) { // unknown port ID. |
| 349 LOG(INFO) << "unknown port: " << port_id; |
| 350 response->SetInteger(kResultWide, RESULT_UNKNOWN_PORT); |
| 351 return; |
| 352 } |
| 353 DCHECK(service_); |
| 354 service_->CloseChannel(port_id); |
| 355 openPortIds_.erase(portEntry); |
| 356 response->SetInteger(kResultWide, RESULT_OK); |
| 357 } |
| 358 |
| 359 void ExtensionPortsRemoteService::PostMessageCommand( |
| 360 int port_id, DictionaryValue* content, DictionaryValue* response) { |
| 361 Value* data; |
| 362 if (!content->Get(kDataWide, &data)) { |
| 363 response->SetInteger(kResultWide, RESULT_PARAMETER_ERROR); |
| 364 return; |
| 365 } |
| 366 std::string message; |
| 367 // Stringified the JSON message body. |
| 368 JSONWriter::Write(data, false, &message); |
| 369 LOG(INFO) << "postMessage: port " << port_id |
| 370 << ", message: <" << message << ">"; |
| 371 PortIdSet::iterator portEntry = openPortIds_.find(port_id); |
| 372 if (portEntry == openPortIds_.end()) { // Unknown port ID. |
| 373 LOG(INFO) << "unknown port: " << port_id; |
| 374 response->SetInteger(kResultWide, RESULT_UNKNOWN_PORT); |
| 375 return; |
| 376 } |
| 377 // Post the message through the ExtensionMessageService. |
| 378 DCHECK(service_); |
| 379 service_->PostMessageFromRenderer(port_id, message); |
| 380 // Confirm to the external client that we sent its message. |
| 381 response->SetInteger(kResultWide, RESULT_OK); |
| 382 } |
OLD | NEW |