| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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/json_reader.h" | |
| 13 #include "base/json/json_writer.h" | |
| 14 #include "base/message_loop.h" | |
| 15 #include "base/string_number_conversions.h" | |
| 16 #include "base/values.h" | |
| 17 #include "chrome/browser/browser_process.h" | |
| 18 #include "chrome/browser/debugger/devtools_protocol_handler.h" | |
| 19 #include "chrome/browser/debugger/devtools_remote_message.h" | |
| 20 #include "chrome/browser/debugger/inspectable_tab_proxy.h" | |
| 21 #include "chrome/browser/profiles/profile_manager.h" | |
| 22 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 23 #include "chrome/common/extensions/extension_messages.h" | |
| 24 #include "content/browser/debugger/devtools_manager.h" | |
| 25 #include "content/browser/tab_contents/tab_contents.h" | |
| 26 #include "content/common/devtools_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, (assuming success) | |
| 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 (Assuming success) | |
| 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 // "result": 0, (Always 0) | |
| 67 // "data": <message body - arbitrary JSON> | |
| 68 // } | |
| 69 // | |
| 70 // The "disconnect" command from the external client, and | |
| 71 // "onDisconnect" notification from the ExtensionMessageService, are | |
| 72 // similar: with the message port ID in the destination field, but no | |
| 73 // "data" field in this case. | |
| 74 | |
| 75 // Commands: | |
| 76 const char kConnect[] = "connect"; | |
| 77 const char kDisconnect[] = "disconnect"; | |
| 78 const char kPostMessage[] = "postMessage"; | |
| 79 // Events: | |
| 80 const char kOnMessage[] = "onMessage"; | |
| 81 const char kOnDisconnect[] = "onDisconnect"; | |
| 82 | |
| 83 // Constants for the JSON message fields. | |
| 84 // The type is wstring because the constant is used to get a | |
| 85 // DictionaryValue field (which requires a wide string). | |
| 86 | |
| 87 // Mandatory. | |
| 88 const char kCommandKey[] = "command"; | |
| 89 | |
| 90 // Always present in messages sent to the external client. | |
| 91 const char kResultKey[] = "result"; | |
| 92 | |
| 93 // Field for command-specific parameters. Not strictly necessary, but | |
| 94 // makes it more similar to the remote debugger protocol, which should | |
| 95 // allow easier reuse of client code. | |
| 96 const char kDataKey[] = "data"; | |
| 97 | |
| 98 // Fields within the "data" dictionary: | |
| 99 | |
| 100 // Required for "connect": | |
| 101 const char kExtensionIdKey[] = "extensionId"; | |
| 102 // Optional in "connect": | |
| 103 const char kChannelNameKey[] = "channelName"; | |
| 104 const char kTabIdKey[] = "tabId"; | |
| 105 | |
| 106 // Present under "data" in replies to a successful "connect" . | |
| 107 const char kPortIdKey[] = "portId"; | |
| 108 | |
| 109 } // namespace | |
| 110 | |
| 111 const char ExtensionPortsRemoteService::kToolName[] = "ExtensionPorts"; | |
| 112 | |
| 113 ExtensionPortsRemoteService::ExtensionPortsRemoteService( | |
| 114 DevToolsProtocolHandler* delegate) | |
| 115 : delegate_(delegate), service_(NULL) { | |
| 116 // We need an ExtensionMessageService instance. It hangs off of | |
| 117 // |profile|. But we do not have a particular tab or RenderViewHost | |
| 118 // as context. I'll just use the first active profile not in | |
| 119 // incognito mode. But this is probably not the right way. | |
| 120 ProfileManager* profile_manager = g_browser_process->profile_manager(); | |
| 121 if (!profile_manager) { | |
| 122 LOG(WARNING) << "No profile manager for ExtensionPortsRemoteService"; | |
| 123 return; | |
| 124 } | |
| 125 | |
| 126 std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles()); | |
| 127 for (size_t i = 0; i < profiles.size(); ++i) { | |
| 128 if (!profiles[i]->IsOffTheRecord()) { | |
| 129 service_ = profiles[i]->GetExtensionMessageService(); | |
| 130 break; | |
| 131 } | |
| 132 } | |
| 133 if (!service_) | |
| 134 LOG(WARNING) << "No usable profile for ExtensionPortsRemoteService"; | |
| 135 } | |
| 136 | |
| 137 ExtensionPortsRemoteService::~ExtensionPortsRemoteService() { | |
| 138 } | |
| 139 | |
| 140 void ExtensionPortsRemoteService::HandleMessage( | |
| 141 const DevToolsRemoteMessage& message) { | |
| 142 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); | |
| 143 const std::string destinationString = message.destination(); | |
| 144 scoped_ptr<Value> request(base::JSONReader::Read(message.content(), true)); | |
| 145 if (request.get() == NULL) { | |
| 146 // Bad JSON | |
| 147 NOTREACHED(); | |
| 148 return; | |
| 149 } | |
| 150 DictionaryValue* content; | |
| 151 if (!request->IsType(Value::TYPE_DICTIONARY)) { | |
| 152 NOTREACHED(); // Broken protocol :( | |
| 153 return; | |
| 154 } | |
| 155 content = static_cast<DictionaryValue*>(request.get()); | |
| 156 if (!content->HasKey(kCommandKey)) { | |
| 157 NOTREACHED(); // Broken protocol :( | |
| 158 return; | |
| 159 } | |
| 160 std::string command; | |
| 161 DictionaryValue response; | |
| 162 | |
| 163 content->GetString(kCommandKey, &command); | |
| 164 response.SetString(kCommandKey, command); | |
| 165 | |
| 166 if (!service_) { | |
| 167 // This happens if we failed to obtain an ExtensionMessageService | |
| 168 // during initialization. | |
| 169 NOTREACHED(); | |
| 170 response.SetInteger(kResultKey, RESULT_NO_SERVICE); | |
| 171 SendResponse(response, message.tool(), message.destination()); | |
| 172 return; | |
| 173 } | |
| 174 | |
| 175 int destination = -1; | |
| 176 if (!destinationString.empty()) | |
| 177 base::StringToInt(destinationString, &destination); | |
| 178 | |
| 179 if (command == kConnect) { | |
| 180 if (destination != -1) // destination should be empty for this command. | |
| 181 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); | |
| 182 else | |
| 183 ConnectCommand(content, &response); | |
| 184 } else if (command == kDisconnect) { | |
| 185 if (destination == -1) // Destination required for this command. | |
| 186 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); | |
| 187 else | |
| 188 DisconnectCommand(destination, &response); | |
| 189 } else if (command == kPostMessage) { | |
| 190 if (destination == -1) // Destination required for this command. | |
| 191 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); | |
| 192 else | |
| 193 PostMessageCommand(destination, content, &response); | |
| 194 } else { | |
| 195 // Unknown command | |
| 196 NOTREACHED(); | |
| 197 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); | |
| 198 } | |
| 199 SendResponse(response, message.tool(), message.destination()); | |
| 200 } | |
| 201 | |
| 202 void ExtensionPortsRemoteService::OnConnectionLost() { | |
| 203 VLOG(1) << "OnConnectionLost"; | |
| 204 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); | |
| 205 DCHECK(service_); | |
| 206 for (PortIdSet::iterator it = openPortIds_.begin(); | |
| 207 it != openPortIds_.end(); | |
| 208 ++it) | |
| 209 service_->CloseChannel(*it); | |
| 210 openPortIds_.clear(); | |
| 211 } | |
| 212 | |
| 213 void ExtensionPortsRemoteService::SendResponse( | |
| 214 const Value& response, const std::string& tool, | |
| 215 const std::string& destination) { | |
| 216 std::string response_content; | |
| 217 base::JSONWriter::Write(&response, false, &response_content); | |
| 218 scoped_ptr<DevToolsRemoteMessage> response_message( | |
| 219 DevToolsRemoteMessageBuilder::instance().Create( | |
| 220 tool, destination, response_content)); | |
| 221 delegate_->Send(*response_message.get()); | |
| 222 } | |
| 223 | |
| 224 bool ExtensionPortsRemoteService::Send(IPC::Message *message) { | |
| 225 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); | |
| 226 | |
| 227 IPC_BEGIN_MESSAGE_MAP(ExtensionPortsRemoteService, *message) | |
| 228 IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnExtensionMessageInvoke) | |
| 229 IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage) | |
| 230 IPC_MESSAGE_UNHANDLED_ERROR() | |
| 231 IPC_END_MESSAGE_MAP() | |
| 232 | |
| 233 delete message; | |
| 234 return true; | |
| 235 } | |
| 236 | |
| 237 void ExtensionPortsRemoteService::OnExtensionMessageInvoke( | |
| 238 const std::string& extension_id, | |
| 239 const std::string& function_name, | |
| 240 const ListValue& args, | |
| 241 const GURL& event_url) { | |
| 242 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::OnDeliverMessage( | |
| 257 int port_id, const std::string& message) { | |
| 258 VLOG(1) << "Message event: from port " << port_id << ", < " << message << ">"; | |
| 259 // Transpose the information into a JSON message for the external client. | |
| 260 DictionaryValue content; | |
| 261 content.SetString(kCommandKey, kOnMessage); | |
| 262 content.SetInteger(kResultKey, RESULT_OK); | |
| 263 // Turn the stringified message body back into JSON. | |
| 264 Value* data = base::JSONReader::Read(message, false); | |
| 265 if (!data) { | |
| 266 NOTREACHED(); | |
| 267 return; | |
| 268 } | |
| 269 content.Set(kDataKey, data); | |
| 270 SendResponse(content, kToolName, base::IntToString(port_id)); | |
| 271 } | |
| 272 | |
| 273 void ExtensionPortsRemoteService::OnExtensionPortDisconnected(int port_id) { | |
| 274 VLOG(1) << "Disconnect event for port " << port_id; | |
| 275 openPortIds_.erase(port_id); | |
| 276 DictionaryValue content; | |
| 277 content.SetString(kCommandKey, kOnDisconnect); | |
| 278 content.SetInteger(kResultKey, RESULT_OK); | |
| 279 SendResponse(content, kToolName, base::IntToString(port_id)); | |
| 280 } | |
| 281 | |
| 282 void ExtensionPortsRemoteService::ConnectCommand( | |
| 283 DictionaryValue* content, DictionaryValue* response) { | |
| 284 // Parse out the parameters. | |
| 285 DictionaryValue* data; | |
| 286 if (!content->GetDictionary(kDataKey, &data)) { | |
| 287 response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR); | |
| 288 return; | |
| 289 } | |
| 290 std::string extension_id; | |
| 291 if (!data->GetString(kExtensionIdKey, &extension_id)) { | |
| 292 response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR); | |
| 293 return; | |
| 294 } | |
| 295 std::string channel_name = ""; | |
| 296 data->GetString(kChannelNameKey, &channel_name); // optional. | |
| 297 int tab_id = -1; | |
| 298 data->GetInteger(kTabIdKey, &tab_id); // optional. | |
| 299 int port_id; | |
| 300 if (tab_id != -1) { // Resolve the tab ID. | |
| 301 const InspectableTabProxy::TabMap& tab_map = | |
| 302 delegate_->inspectable_tab_proxy()->tab_map(); | |
| 303 InspectableTabProxy::TabMap::const_iterator it = tab_map.find(tab_id); | |
| 304 TabContents* tab_contents = NULL; | |
| 305 if (it != tab_map.end()) | |
| 306 tab_contents = it->second->tab_contents(); | |
| 307 if (!tab_contents) { | |
| 308 VLOG(1) << "tab not found: " << tab_id; | |
| 309 response->SetInteger(kResultKey, RESULT_TAB_NOT_FOUND); | |
| 310 return; | |
| 311 } | |
| 312 // Ask the ExtensionMessageService to open the channel. | |
| 313 VLOG(1) << "Connect: extension_id <" << extension_id | |
| 314 << ">, channel_name <" << channel_name | |
| 315 << ">, tab " << tab_id; | |
| 316 DCHECK(service_); | |
| 317 port_id = service_->OpenSpecialChannelToTab( | |
| 318 extension_id, channel_name, tab_contents, this); | |
| 319 } else { // no tab: channel to an extension page. | |
| 320 // Ask the ExtensionMessageService to open the channel. | |
| 321 VLOG(1) << "Connect: extension_id <" << extension_id | |
| 322 << ">, channel_name <" << channel_name << ">"; | |
| 323 DCHECK(service_); | |
| 324 port_id = service_->OpenSpecialChannelToExtension( | |
| 325 extension_id, channel_name, "null", this); | |
| 326 } | |
| 327 if (port_id == -1) { | |
| 328 // Failure: probably the extension ID doesn't exist. | |
| 329 VLOG(1) << "Connect failed"; | |
| 330 response->SetInteger(kResultKey, RESULT_CONNECT_FAILED); | |
| 331 return; | |
| 332 } | |
| 333 VLOG(1) << "Connected: port " << port_id; | |
| 334 openPortIds_.insert(port_id); | |
| 335 // Reply to external client with the port ID assigned to the new channel. | |
| 336 DictionaryValue* reply_data = new DictionaryValue(); | |
| 337 reply_data->SetInteger(kPortIdKey, port_id); | |
| 338 response->Set(kDataKey, reply_data); | |
| 339 response->SetInteger(kResultKey, RESULT_OK); | |
| 340 } | |
| 341 | |
| 342 void ExtensionPortsRemoteService::DisconnectCommand( | |
| 343 int port_id, DictionaryValue* response) { | |
| 344 VLOG(1) << "Disconnect port " << port_id; | |
| 345 PortIdSet::iterator portEntry = openPortIds_.find(port_id); | |
| 346 if (portEntry == openPortIds_.end()) { // unknown port ID. | |
| 347 VLOG(1) << "unknown port: " << port_id; | |
| 348 response->SetInteger(kResultKey, RESULT_UNKNOWN_PORT); | |
| 349 return; | |
| 350 } | |
| 351 DCHECK(service_); | |
| 352 service_->CloseChannel(port_id); | |
| 353 openPortIds_.erase(portEntry); | |
| 354 response->SetInteger(kResultKey, RESULT_OK); | |
| 355 } | |
| 356 | |
| 357 void ExtensionPortsRemoteService::PostMessageCommand( | |
| 358 int port_id, DictionaryValue* content, DictionaryValue* response) { | |
| 359 Value* data; | |
| 360 if (!content->Get(kDataKey, &data)) { | |
| 361 response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR); | |
| 362 return; | |
| 363 } | |
| 364 std::string message; | |
| 365 // Stringified the JSON message body. | |
| 366 base::JSONWriter::Write(data, false, &message); | |
| 367 VLOG(1) << "postMessage: port " << port_id | |
| 368 << ", message: <" << message << ">"; | |
| 369 PortIdSet::iterator portEntry = openPortIds_.find(port_id); | |
| 370 if (portEntry == openPortIds_.end()) { // Unknown port ID. | |
| 371 VLOG(1) << "unknown port: " << port_id; | |
| 372 response->SetInteger(kResultKey, RESULT_UNKNOWN_PORT); | |
| 373 return; | |
| 374 } | |
| 375 // Post the message through the ExtensionMessageService. | |
| 376 DCHECK(service_); | |
| 377 service_->PostMessageFromRenderer(port_id, message); | |
| 378 // Confirm to the external client that we sent its message. | |
| 379 response->SetInteger(kResultKey, RESULT_OK); | |
| 380 } | |
| OLD | NEW |