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
24namespace {
25
26// Constants for the "data", "result", and "command" JSON message fields.
27const char kDataKey[] = "data";
28const char kResultKey[] = "result";
29const char kCommandKey[] = "command";
30
31}  // namespace
32
33const std::string DebuggerRemoteServiceCommand::kAttach = "attach";
34const std::string DebuggerRemoteServiceCommand::kDetach = "detach";
35const std::string DebuggerRemoteServiceCommand::kDebuggerCommand =
36    "debugger_command";
37const std::string DebuggerRemoteServiceCommand::kEvaluateJavascript =
38    "evaluate_javascript";
39const std::string DebuggerRemoteServiceCommand::kFrameNavigate =
40    "navigated";
41const std::string DebuggerRemoteServiceCommand::kTabClosed =
42    "closed";
43
44const std::string DebuggerRemoteService::kToolName = "V8Debugger";
45
46DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate)
47    : delegate_(delegate) {}
48
49DebuggerRemoteService::~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.
56void 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
113void 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.
119void 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.
133TabContents* 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.
154void 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.
173void 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.
185void 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.
195void 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.
239void 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.
269bool 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.
309bool 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