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_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/profiles/profile_manager.h" 23 #include "chrome/common/devtools_messages.h" 24 #include "chrome/common/extensions/extension_messages.h" 25 #include "content/browser/tab_contents/tab_contents.h" 26 27 namespace { 28 29 // Protocol is as follows: 30 // 31 // From external client: 32 // {"command": "connect", 33 // "data": { 34 // "extensionId": "<extension_id string>", 35 // "channelName": "<port name string>", (optional) 36 // "tabId": <numerical tab ID> (optional) 37 // } 38 // } 39 // To connect to a background page or tool strip, the tabId should be omitted. 40 // Tab IDs can be enumerated with the list_tabs DevToolsService command. 41 // 42 // Response: 43 // {"command": "connect", 44 // "result": 0, (assuming success) 45 // "data": { 46 // "portId": <numerical port ID> 47 // } 48 // } 49 // 50 // Posting a message from external client: 51 // Put the target message port ID in the devtools destination field. 52 // {"command": "postMessage", 53 // "data": <message body - arbitrary JSON> 54 // } 55 // Response: 56 // {"command": "postMessage", 57 // "result": 0 (Assuming success) 58 // } 59 // Note this is a confirmation from the devtools protocol layer, not 60 // a response from the extension. 61 // 62 // Message from an extension to the external client: 63 // The message port ID is in the devtools destination field. 64 // {"command": "onMessage", 65 // "result": 0, (Always 0) 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 const char kConnect[] = "connect"; 76 const char kDisconnect[] = "disconnect"; 77 const char kPostMessage[] = "postMessage"; 78 // Events: 79 const char kOnMessage[] = "onMessage"; 80 const char 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 const char kCommandKey[] = "command"; 88 89 // Always present in messages sent to the external client. 90 const char kResultKey[] = "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 const char kDataKey[] = "data"; 96 97 // Fields within the "data" dictionary: 98 99 // Required for "connect": 100 const char kExtensionIdKey[] = "extensionId"; 101 // Optional in "connect": 102 const char kChannelNameKey[] = "channelName"; 103 const char kTabIdKey[] = "tabId"; 104 105 // Present under "data" in replies to a successful "connect" . 106 const char kPortIdKey[] = "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 125 std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles()); 126 for (size_t i = 0; i < profiles.size(); ++i) { 127 if (!profiles[i]->IsOffTheRecord()) { 128 service_ = profiles[i]->GetExtensionMessageService(); 129 break; 130 } 131 } 132 if (!service_) 133 LOG(WARNING) << "No usable profile for ExtensionPortsRemoteService"; 134 } 135 136 ExtensionPortsRemoteService::~ExtensionPortsRemoteService() { 137 } 138 139 void ExtensionPortsRemoteService::HandleMessage( 140 const DevToolsRemoteMessage& message) { 141 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); 142 const std::string destinationString = message.destination(); 143 scoped_ptr<Value> request(base::JSONReader::Read(message.content(), true)); 144 if (request.get() == NULL) { 145 // Bad JSON 146 NOTREACHED(); 147 return; 148 } 149 DictionaryValue* content; 150 if (!request->IsType(Value::TYPE_DICTIONARY)) { 151 NOTREACHED(); // Broken protocol :( 152 return; 153 } 154 content = static_cast<DictionaryValue*>(request.get()); 155 if (!content->HasKey(kCommandKey)) { 156 NOTREACHED(); // Broken protocol :( 157 return; 158 } 159 std::string command; 160 DictionaryValue response; 161 162 content->GetString(kCommandKey, &command); 163 response.SetString(kCommandKey, command); 164 165 if (!service_) { 166 // This happens if we failed to obtain an ExtensionMessageService 167 // during initialization. 168 NOTREACHED(); 169 response.SetInteger(kResultKey, RESULT_NO_SERVICE); 170 SendResponse(response, message.tool(), message.destination()); 171 return; 172 } 173 174 int destination = -1; 175 if (!destinationString.empty()) 176 base::StringToInt(destinationString, &destination); 177 178 if (command == kConnect) { 179 if (destination != -1) // destination should be empty for this command. 180 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); 181 else 182 ConnectCommand(content, &response); 183 } else if (command == kDisconnect) { 184 if (destination == -1) // Destination required for this command. 185 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); 186 else 187 DisconnectCommand(destination, &response); 188 } else if (command == kPostMessage) { 189 if (destination == -1) // Destination required for this command. 190 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); 191 else 192 PostMessageCommand(destination, content, &response); 193 } else { 194 // Unknown command 195 NOTREACHED(); 196 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); 197 } 198 SendResponse(response, message.tool(), message.destination()); 199 } 200 201 void ExtensionPortsRemoteService::OnConnectionLost() { 202 VLOG(1) << "OnConnectionLost"; 203 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); 204 DCHECK(service_); 205 for (PortIdSet::iterator it = openPortIds_.begin(); 206 it != openPortIds_.end(); 207 ++it) 208 service_->CloseChannel(*it); 209 openPortIds_.clear(); 210 } 211 212 void ExtensionPortsRemoteService::SendResponse( 213 const Value& response, const std::string& tool, 214 const std::string& destination) { 215 std::string response_content; 216 base::JSONWriter::Write(&response, false, &response_content); 217 scoped_ptr<DevToolsRemoteMessage> response_message( 218 DevToolsRemoteMessageBuilder::instance().Create( 219 tool, destination, response_content)); 220 delegate_->Send(*response_message.get()); 221 } 222 223 bool ExtensionPortsRemoteService::Send(IPC::Message *message) { 224 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); 225 226 IPC_BEGIN_MESSAGE_MAP(ExtensionPortsRemoteService, *message) 227 IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnExtensionMessageInvoke) 228 IPC_MESSAGE_UNHANDLED_ERROR() 229 IPC_END_MESSAGE_MAP() 230 231 delete message; 232 return true; 233 } 234 235 void ExtensionPortsRemoteService::OnExtensionMessageInvoke( 236 const std::string& extension_id, 237 const std::string& function_name, 238 const ListValue& args, 239 const GURL& event_url) { 240 if (function_name == ExtensionMessageService::kDispatchOnMessage) { 241 DCHECK_EQ(args.GetSize(), 2u); 242 std::string message; 243 int port_id; 244 if (args.GetString(0, &message) && args.GetInteger(1, &port_id)) 245 OnExtensionMessage(message, port_id); 246 } else if (function_name == ExtensionMessageService::kDispatchOnDisconnect) { 247 DCHECK_EQ(args.GetSize(), 1u); 248 int port_id; 249 if (args.GetInteger(0, &port_id)) 250 OnExtensionPortDisconnected(port_id); 251 } else if (function_name == ExtensionMessageService::kDispatchOnConnect) { 252 // There is no way for this service to be addressed and receive 253 // connections. 254 NOTREACHED() << function_name << " shouldn't be called."; 255 } else { 256 NOTREACHED() << function_name << " shouldn't be called."; 257 } 258 } 259 260 void ExtensionPortsRemoteService::OnExtensionMessage( 261 const std::string& message, int port_id) { 262 VLOG(1) << "Message event: from port " << port_id << ", < " << message << ">"; 263 // Transpose the information into a JSON message for the external client. 264 DictionaryValue content; 265 content.SetString(kCommandKey, kOnMessage); 266 content.SetInteger(kResultKey, RESULT_OK); 267 // Turn the stringified message body back into JSON. 268 Value* data = base::JSONReader::Read(message, false); 269 if (!data) { 270 NOTREACHED(); 271 return; 272 } 273 content.Set(kDataKey, data); 274 SendResponse(content, kToolName, base::IntToString(port_id)); 275 } 276 277 void ExtensionPortsRemoteService::OnExtensionPortDisconnected(int port_id) { 278 VLOG(1) << "Disconnect event for port " << port_id; 279 openPortIds_.erase(port_id); 280 DictionaryValue content; 281 content.SetString(kCommandKey, kOnDisconnect); 282 content.SetInteger(kResultKey, RESULT_OK); 283 SendResponse(content, kToolName, base::IntToString(port_id)); 284 } 285 286 void ExtensionPortsRemoteService::ConnectCommand( 287 DictionaryValue* content, DictionaryValue* response) { 288 // Parse out the parameters. 289 DictionaryValue* data; 290 if (!content->GetDictionary(kDataKey, &data)) { 291 response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR); 292 return; 293 } 294 std::string extension_id; 295 if (!data->GetString(kExtensionIdKey, &extension_id)) { 296 response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR); 297 return; 298 } 299 std::string channel_name = ""; 300 data->GetString(kChannelNameKey, &channel_name); // optional. 301 int tab_id = -1; 302 data->GetInteger(kTabIdKey, &tab_id); // optional. 303 int port_id; 304 if (tab_id != -1) { // Resolve the tab ID. 305 const InspectableTabProxy::ControllersMap& navcon_map = 306 delegate_->inspectable_tab_proxy()->controllers_map(); 307 InspectableTabProxy::ControllersMap::const_iterator it = 308 navcon_map.find(tab_id); 309 TabContents* tab_contents = NULL; 310 if (it != navcon_map.end()) 311 tab_contents = it->second->tab_contents(); 312 if (!tab_contents) { 313 VLOG(1) << "tab not found: " << tab_id; 314 response->SetInteger(kResultKey, RESULT_TAB_NOT_FOUND); 315 return; 316 } 317 // Ask the ExtensionMessageService to open the channel. 318 VLOG(1) << "Connect: extension_id <" << extension_id 319 << ">, channel_name <" << channel_name 320 << ">, tab " << tab_id; 321 DCHECK(service_); 322 port_id = service_->OpenSpecialChannelToTab( 323 extension_id, channel_name, tab_contents, this); 324 } else { // no tab: channel to an extension' background page / toolstrip. 325 // Ask the ExtensionMessageService to open the channel. 326 VLOG(1) << "Connect: extension_id <" << extension_id 327 << ">, channel_name <" << channel_name << ">"; 328 DCHECK(service_); 329 port_id = service_->OpenSpecialChannelToExtension( 330 extension_id, channel_name, "null", this); 331 } 332 if (port_id == -1) { 333 // Failure: probably the extension ID doesn't exist. 334 VLOG(1) << "Connect failed"; 335 response->SetInteger(kResultKey, RESULT_CONNECT_FAILED); 336 return; 337 } 338 VLOG(1) << "Connected: port " << port_id; 339 openPortIds_.insert(port_id); 340 // Reply to external client with the port ID assigned to the new channel. 341 DictionaryValue* reply_data = new DictionaryValue(); 342 reply_data->SetInteger(kPortIdKey, port_id); 343 response->Set(kDataKey, reply_data); 344 response->SetInteger(kResultKey, RESULT_OK); 345 } 346 347 void ExtensionPortsRemoteService::DisconnectCommand( 348 int port_id, DictionaryValue* response) { 349 VLOG(1) << "Disconnect port " << port_id; 350 PortIdSet::iterator portEntry = openPortIds_.find(port_id); 351 if (portEntry == openPortIds_.end()) { // unknown port ID. 352 VLOG(1) << "unknown port: " << port_id; 353 response->SetInteger(kResultKey, RESULT_UNKNOWN_PORT); 354 return; 355 } 356 DCHECK(service_); 357 service_->CloseChannel(port_id); 358 openPortIds_.erase(portEntry); 359 response->SetInteger(kResultKey, RESULT_OK); 360 } 361 362 void ExtensionPortsRemoteService::PostMessageCommand( 363 int port_id, DictionaryValue* content, DictionaryValue* response) { 364 Value* data; 365 if (!content->Get(kDataKey, &data)) { 366 response->SetInteger(kResultKey, RESULT_PARAMETER_ERROR); 367 return; 368 } 369 std::string message; 370 // Stringified the JSON message body. 371 base::JSONWriter::Write(data, false, &message); 372 VLOG(1) << "postMessage: port " << port_id 373 << ", message: <" << message << ">"; 374 PortIdSet::iterator portEntry = openPortIds_.find(port_id); 375 if (portEntry == openPortIds_.end()) { // Unknown port ID. 376 VLOG(1) << "unknown port: " << port_id; 377 response->SetInteger(kResultKey, RESULT_UNKNOWN_PORT); 378 return; 379 } 380 // Post the message through the ExtensionMessageService. 381 DCHECK(service_); 382 service_->PostMessageFromRenderer(port_id, message); 383 // Confirm to the external client that we sent its message. 384 response->SetInteger(kResultKey, RESULT_OK); 385 } 386