• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ExtensionPortsRemoteService(DevToolsProtocolHandler * delegate)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 
~ExtensionPortsRemoteService()136 ExtensionPortsRemoteService::~ExtensionPortsRemoteService() {
137 }
138 
HandleMessage(const DevToolsRemoteMessage & message)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 
OnConnectionLost()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 
SendResponse(const Value & response,const std::string & tool,const std::string & destination)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 
Send(IPC::Message * message)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 
OnExtensionMessageInvoke(const std::string & extension_id,const std::string & function_name,const ListValue & args,const GURL & event_url)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 
OnExtensionMessage(const std::string & message,int port_id)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 
OnExtensionPortDisconnected(int port_id)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 
ConnectCommand(DictionaryValue * content,DictionaryValue * response)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 
DisconnectCommand(int port_id,DictionaryValue * response)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 
PostMessageCommand(int port_id,DictionaryValue * content,DictionaryValue * response)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