Home | History | Annotate | Download | only in debugger
      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