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 |