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