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
DebuggerRemoteService(DevToolsProtocolHandler * delegate)46 DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate)
47 : delegate_(delegate) {}
48
~DebuggerRemoteService()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.
HandleMessage(const DevToolsRemoteMessage & message)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
OnConnectionLost()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.
SendResponse(const Value & response,const std::string & tool,const std::string & destination)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.
ToTabContents(int32 tab_uid)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.
DebuggerOutput(int32 tab_uid,const std::string & message)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.
FrameNavigate(int32 tab_uid,const std::string & url)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.
TabClosed(int32 tab_id)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.
AttachToTab(const std::string & destination,DictionaryValue * response)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.
DetachFromTab(const std::string & destination,DictionaryValue * response)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.
DispatchDebuggerCommand(int tab_uid,DictionaryValue * content,DictionaryValue * response)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.
DispatchEvaluateJavascript(int tab_uid,DictionaryValue * content,DictionaryValue * response)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