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