Home | History | Annotate | Download | only in debugger
      1 // Copyright (c) 2010 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 // This file contains implementations of the DebuggerRemoteService methods,
      6 // defines DebuggerRemoteService and DebuggerRemoteServiceCommand constants.
      7 
      8 #include "chrome/browser/debugger/debugger_remote_service.h"
      9 
     10 #include "base/json/json_reader.h"
     11 #include "base/json/json_writer.h"
     12 #include "base/string_number_conversions.h"
     13 #include "base/utf_string_conversions.h"
     14 #include "base/values.h"
     15 #include "chrome/browser/debugger/devtools_manager.h"
     16 #include "chrome/browser/debugger/devtools_protocol_handler.h"
     17 #include "chrome/browser/debugger/devtools_remote_message.h"
     18 #include "chrome/browser/debugger/inspectable_tab_proxy.h"
     19 #include "chrome/common/devtools_messages.h"
     20 #include "chrome/common/render_messages.h"
     21 #include "content/browser/renderer_host/render_view_host.h"
     22 #include "content/browser/tab_contents/tab_contents.h"
     23 
     24 namespace {
     25 
     26 // Constants for the "data", "result", and "command" JSON message fields.
     27 const char kDataKey[] = "data";
     28 const char kResultKey[] = "result";
     29 const char kCommandKey[] = "command";
     30 
     31 }  // namespace
     32 
     33 const std::string DebuggerRemoteServiceCommand::kAttach = "attach";
     34 const std::string DebuggerRemoteServiceCommand::kDetach = "detach";
     35 const std::string DebuggerRemoteServiceCommand::kDebuggerCommand =
     36     "debugger_command";
     37 const std::string DebuggerRemoteServiceCommand::kEvaluateJavascript =
     38     "evaluate_javascript";
     39 const std::string DebuggerRemoteServiceCommand::kFrameNavigate =
     40     "navigated";
     41 const std::string DebuggerRemoteServiceCommand::kTabClosed =
     42     "closed";
     43 
     44 const std::string DebuggerRemoteService::kToolName = "V8Debugger";
     45 
     46 DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate)
     47     : delegate_(delegate) {}
     48 
     49 DebuggerRemoteService::~DebuggerRemoteService() {}
     50 
     51 // This method handles the V8Debugger tool commands which are
     52 // retrieved from the request "command" field. If an operation result
     53 // is ready off-hand (synchronously), it is sent back to the remote debugger.
     54 // Otherwise the corresponding response is received through IPC from the
     55 // V8 debugger via DevToolsClientHost.
     56 void DebuggerRemoteService::HandleMessage(
     57     const DevToolsRemoteMessage& message) {
     58   const std::string destination = message.destination();
     59   scoped_ptr<Value> request(base::JSONReader::Read(message.content(), true));
     60   if (request.get() == NULL) {
     61     // Bad JSON
     62     NOTREACHED();
     63     return;
     64   }
     65   DictionaryValue* content;
     66   if (!request->IsType(Value::TYPE_DICTIONARY)) {
     67     NOTREACHED();  // Broken protocol :(
     68     return;
     69   }
     70   content = static_cast<DictionaryValue*>(request.get());
     71   if (!content->HasKey(kCommandKey)) {
     72     NOTREACHED();  // Broken protocol :(
     73     return;
     74   }
     75   std::string command;
     76   DictionaryValue response;
     77 
     78   content->GetString(kCommandKey, &command);
     79   response.SetString(kCommandKey, command);
     80   bool send_response = true;
     81   if (destination.empty()) {
     82     // Unknown command (bad format?)
     83     NOTREACHED();
     84     response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
     85     SendResponse(response, message.tool(), message.destination());
     86     return;
     87   }
     88   int32 tab_uid = -1;
     89   base::StringToInt(destination, &tab_uid);
     90 
     91   if (command == DebuggerRemoteServiceCommand::kAttach) {
     92     // TODO(apavlov): handle 0 for a new tab
     93     response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kAttach);
     94     AttachToTab(destination, &response);
     95   } else if (command == DebuggerRemoteServiceCommand::kDetach) {
     96     response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kDetach);
     97     DetachFromTab(destination, &response);
     98   } else if (command == DebuggerRemoteServiceCommand::kDebuggerCommand) {
     99     send_response = DispatchDebuggerCommand(tab_uid, content, &response);
    100   } else if (command == DebuggerRemoteServiceCommand::kEvaluateJavascript) {
    101     send_response = DispatchEvaluateJavascript(tab_uid, content, &response);
    102   } else {
    103     // Unknown command
    104     NOTREACHED();
    105     response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND);
    106   }
    107 
    108   if (send_response) {
    109     SendResponse(response, message.tool(), message.destination());
    110   }
    111 }
    112 
    113 void DebuggerRemoteService::OnConnectionLost() {
    114   delegate_->inspectable_tab_proxy()->OnRemoteDebuggerDetached();
    115 }
    116 
    117 // Sends a JSON response to the remote debugger using |response| as content,
    118 // |tool| and |destination| as the respective header values.
    119 void DebuggerRemoteService::SendResponse(const Value& response,
    120                                          const std::string& tool,
    121                                          const std::string& destination) {
    122   std::string response_content;
    123   base::JSONWriter::Write(&response, false, &response_content);
    124   scoped_ptr<DevToolsRemoteMessage> response_message(
    125       DevToolsRemoteMessageBuilder::instance().Create(tool,
    126                                                       destination,
    127                                                       response_content));
    128   delegate_->Send(*response_message.get());
    129 }
    130 
    131 // Gets a TabContents instance corresponding to the |tab_uid| using the
    132 // InspectableTabProxy controllers map, or NULL if none found.
    133 TabContents* DebuggerRemoteService::ToTabContents(int32 tab_uid) {
    134   const InspectableTabProxy::ControllersMap& navcon_map =
    135       delegate_->inspectable_tab_proxy()->controllers_map();
    136   InspectableTabProxy::ControllersMap::const_iterator it =
    137       navcon_map.find(tab_uid);
    138   if (it != navcon_map.end()) {
    139     TabContents* tab_contents = it->second->tab_contents();
    140     if (tab_contents == NULL) {
    141       return NULL;
    142     } else {
    143       return tab_contents;
    144     }
    145   } else {
    146     return NULL;
    147   }
    148 }
    149 
    150 // Gets invoked from a DevToolsClientHost callback whenever
    151 // a message from the V8 VM debugger corresponding to |tab_id| is received.
    152 // Composes a Chrome Developer Tools Protocol JSON response and sends it
    153 // to the remote debugger.
    154 void DebuggerRemoteService::DebuggerOutput(int32 tab_uid,
    155                                            const std::string& message) {
    156   std::string content = StringPrintf(
    157       "{\"command\":\"%s\",\"result\":%s,\"data\":%s}",
    158       DebuggerRemoteServiceCommand::kDebuggerCommand.c_str(),
    159       base::IntToString(RESULT_OK).c_str(),
    160       message.c_str());
    161   scoped_ptr<DevToolsRemoteMessage> response_message(
    162       DevToolsRemoteMessageBuilder::instance().Create(
    163           kToolName,
    164           base::IntToString(tab_uid),
    165           content));
    166   delegate_->Send(*(response_message.get()));
    167 }
    168 
    169 // Gets invoked from a DevToolsClientHost callback whenever
    170 // a tab corresponding to |tab_id| changes its URL. |url| is the new
    171 // URL of the tab (may be the same as the previous one if the tab is reloaded).
    172 // Sends the corresponding message to the remote debugger.
    173 void DebuggerRemoteService::FrameNavigate(int32 tab_uid,
    174                                           const std::string& url) {
    175   DictionaryValue value;
    176   value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kFrameNavigate);
    177   value.SetInteger(kResultKey, RESULT_OK);
    178   value.SetString(kDataKey, url);
    179   SendResponse(value, kToolName, base::IntToString(tab_uid));
    180 }
    181 
    182 // Gets invoked from a DevToolsClientHost callback whenever
    183 // a tab corresponding to |tab_id| gets closed.
    184 // Sends the corresponding message to the remote debugger.
    185 void DebuggerRemoteService::TabClosed(int32 tab_id) {
    186   DictionaryValue value;
    187   value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kTabClosed);
    188   value.SetInteger(kResultKey, RESULT_OK);
    189   SendResponse(value, kToolName, base::IntToString(tab_id));
    190 }
    191 
    192 // Attaches a remote debugger to the target tab specified by |destination|
    193 // by posting the DevToolsAgentMsg_Attach message and sends a response
    194 // to the remote debugger immediately.
    195 void DebuggerRemoteService::AttachToTab(const std::string& destination,
    196                                         DictionaryValue* response) {
    197   int32 tab_uid = -1;
    198   base::StringToInt(destination, &tab_uid);
    199   if (tab_uid < 0) {
    200     // Bad tab_uid received from remote debugger (perhaps NaN)
    201     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    202     return;
    203   }
    204   if (tab_uid == 0) {  // single tab_uid
    205     // We've been asked to open a new tab with URL
    206     // TODO(apavlov): implement
    207     NOTIMPLEMENTED();
    208     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    209     return;
    210   }
    211   TabContents* tab_contents = ToTabContents(tab_uid);
    212   if (tab_contents == NULL) {
    213     // No active tab contents with tab_uid
    214     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    215     return;
    216   }
    217   RenderViewHost* target_host = tab_contents->render_view_host();
    218   DevToolsClientHost* client_host =
    219       delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid);
    220   if (client_host == NULL) {
    221     client_host =
    222         delegate_->inspectable_tab_proxy()->NewClientHost(tab_uid, this);
    223     DevToolsManager* manager = DevToolsManager::GetInstance();
    224     if (manager != NULL) {
    225       manager->RegisterDevToolsClientHostFor(target_host, client_host);
    226       response->SetInteger(kResultKey, RESULT_OK);
    227     } else {
    228       response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR);
    229     }
    230   } else {
    231     // DevToolsClientHost for this tab is already registered
    232     response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE);
    233   }
    234 }
    235 
    236 // Detaches a remote debugger from the target tab specified by |destination|
    237 // by posting the DevToolsAgentMsg_Detach message and sends a response
    238 // to the remote debugger immediately.
    239 void DebuggerRemoteService::DetachFromTab(const std::string& destination,
    240                                           DictionaryValue* response) {
    241   int32 tab_uid = -1;
    242   base::StringToInt(destination, &tab_uid);
    243   if (tab_uid == -1) {
    244     // Bad tab_uid received from remote debugger (NaN)
    245     if (response != NULL) {
    246       response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    247     }
    248     return;
    249   }
    250   int result_code;
    251   DevToolsClientHostImpl* client_host =
    252       delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid);
    253   if (client_host != NULL) {
    254     client_host->Close();
    255     result_code = RESULT_OK;
    256   } else {
    257     // No client host registered for |tab_uid|.
    258     result_code = RESULT_UNKNOWN_TAB;
    259   }
    260   if (response != NULL) {
    261     response->SetInteger(kResultKey, result_code);
    262   }
    263 }
    264 
    265 // Sends a V8 debugger command to the target tab V8 debugger.
    266 // Does not send back a response (which is received asynchronously
    267 // through IPC) unless an error occurs before the command has actually
    268 // been sent.
    269 bool DebuggerRemoteService::DispatchDebuggerCommand(int tab_uid,
    270                                                     DictionaryValue* content,
    271                                                     DictionaryValue* response) {
    272   if (tab_uid == -1) {
    273     // Invalid tab_uid from remote debugger (perhaps NaN)
    274     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    275     return true;
    276   }
    277   DevToolsManager* manager = DevToolsManager::GetInstance();
    278   if (manager == NULL) {
    279     response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR);
    280     return true;
    281   }
    282   TabContents* tab_contents = ToTabContents(tab_uid);
    283   if (tab_contents == NULL) {
    284     // Unknown tab_uid from remote debugger
    285     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    286     return true;
    287   }
    288   DevToolsClientHost* client_host =
    289       manager->GetDevToolsClientHostFor(tab_contents->render_view_host());
    290   if (client_host == NULL) {
    291     // tab_uid is not being debugged (Attach has not been invoked)
    292     response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE);
    293     return true;
    294   }
    295   std::string v8_command;
    296   DictionaryValue* v8_command_value;
    297   content->GetDictionary(kDataKey, &v8_command_value);
    298   base::JSONWriter::Write(v8_command_value, false, &v8_command);
    299   manager->ForwardToDevToolsAgent(
    300       client_host, DevToolsAgentMsg_DebuggerCommand(v8_command));
    301   // Do not send the response right now, as the JSON will be received from
    302   // the V8 debugger asynchronously.
    303   return false;
    304 }
    305 
    306 // Sends the immediate "evaluate Javascript" command to the V8 debugger.
    307 // The evaluation result is not sent back to the client as this command
    308 // is in fact needed to invoke processing of queued debugger commands.
    309 bool DebuggerRemoteService::DispatchEvaluateJavascript(
    310     int tab_uid,
    311     DictionaryValue* content,
    312     DictionaryValue* response) {
    313   if (tab_uid == -1) {
    314     // Invalid tab_uid from remote debugger (perhaps NaN)
    315     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    316     return true;
    317   }
    318   TabContents* tab_contents = ToTabContents(tab_uid);
    319   if (tab_contents == NULL) {
    320     // Unknown tab_uid from remote debugger
    321     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    322     return true;
    323   }
    324   RenderViewHost* render_view_host = tab_contents->render_view_host();
    325   if (render_view_host == NULL) {
    326     // No RenderViewHost
    327     response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB);
    328     return true;
    329   }
    330   std::string javascript;
    331   content->GetString(kDataKey, &javascript);
    332   render_view_host->ExecuteJavascriptInWebFrame(string16(),
    333                                                 UTF8ToUTF16(javascript));
    334   return false;
    335 }
    336