debugger_remote_service.cc revision dc0f95d653279beabeb9817299e2902918ba123e
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 "chrome/common/render_messages_params.h" 22#include "content/browser/renderer_host/render_view_host.h" 23#include "content/browser/tab_contents/tab_contents.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.empty()) { 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