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
27namespace {
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:
75const char kConnect[] = "connect";
76const char kDisconnect[] = "disconnect";
77const char kPostMessage[] = "postMessage";
78// Events:
79const char kOnMessage[] = "onMessage";
80const 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.
87const char kCommandKey[] = "command";
88
89// Always present in messages sent to the external client.
90const 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.
95const char kDataKey[] = "data";
96
97// Fields within the "data" dictionary:
98
99// Required for "connect":
100const char kExtensionIdKey[] = "extensionId";
101// Optional in "connect":
102const char kChannelNameKey[] = "channelName";
103const char kTabIdKey[] = "tabId";
104
105// Present under "data" in replies to a successful "connect" .
106const char kPortIdKey[] = "portId";
107
108}  // namespace
109
110const std::string ExtensionPortsRemoteService::kToolName = "ExtensionPorts";
111
112ExtensionPortsRemoteService::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
136ExtensionPortsRemoteService::~ExtensionPortsRemoteService() {
137}
138
139void 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
201void 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
212void 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
223bool 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
235void 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
260void 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
277void 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
286void 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
347void 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
362void 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