| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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/stringprintf.h" | |
| 13 #include "base/string_number_conversions.h" | |
| 14 #include "base/utf_string_conversions.h" | |
| 15 #include "base/values.h" | |
| 16 #include "chrome/browser/debugger/devtools_manager.h" | |
| 17 #include "chrome/browser/debugger/devtools_protocol_handler.h" | |
| 18 #include "chrome/browser/debugger/devtools_remote_message.h" | |
| 19 #include "chrome/browser/debugger/inspectable_tab_proxy.h" | |
| 20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 21 #include "chrome/common/render_messages.h" | |
| 22 #include "content/browser/renderer_host/render_view_host.h" | |
| 23 #include "content/browser/tab_contents/tab_contents.h" | |
| 24 #include "content/common/devtools_messages.h" | |
| 25 | |
| 26 namespace { | |
| 27 | |
| 28 // Constants for the "data", "result", and "command" JSON message fields. | |
| 29 const char kDataKey[] = "data"; | |
| 30 const char kResultKey[] = "result"; | |
| 31 const char kCommandKey[] = "command"; | |
| 32 | |
| 33 } // namespace | |
| 34 | |
| 35 const std::string DebuggerRemoteServiceCommand::kAttach = "attach"; | |
| 36 const std::string DebuggerRemoteServiceCommand::kDetach = "detach"; | |
| 37 const std::string DebuggerRemoteServiceCommand::kDebuggerCommand = | |
| 38 "debugger_command"; | |
| 39 const std::string DebuggerRemoteServiceCommand::kEvaluateJavascript = | |
| 40 "evaluate_javascript"; | |
| 41 const std::string DebuggerRemoteServiceCommand::kFrameNavigate = | |
| 42 "navigated"; | |
| 43 const std::string DebuggerRemoteServiceCommand::kTabClosed = | |
| 44 "closed"; | |
| 45 | |
| 46 const std::string DebuggerRemoteService::kToolName = "V8Debugger"; | |
| 47 | |
| 48 DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate) | |
| 49 : delegate_(delegate) {} | |
| 50 | |
| 51 DebuggerRemoteService::~DebuggerRemoteService() {} | |
| 52 | |
| 53 // This method handles the V8Debugger tool commands which are | |
| 54 // retrieved from the request "command" field. If an operation result | |
| 55 // is ready off-hand (synchronously), it is sent back to the remote debugger. | |
| 56 // Otherwise the corresponding response is received through IPC from the | |
| 57 // V8 debugger via DevToolsClientHost. | |
| 58 void DebuggerRemoteService::HandleMessage( | |
| 59 const DevToolsRemoteMessage& message) { | |
| 60 const std::string destination = message.destination(); | |
| 61 scoped_ptr<Value> request(base::JSONReader::Read(message.content(), true)); | |
| 62 if (request.get() == NULL) { | |
| 63 // Bad JSON | |
| 64 NOTREACHED(); | |
| 65 return; | |
| 66 } | |
| 67 DictionaryValue* content; | |
| 68 if (!request->IsType(Value::TYPE_DICTIONARY)) { | |
| 69 NOTREACHED(); // Broken protocol :( | |
| 70 return; | |
| 71 } | |
| 72 content = static_cast<DictionaryValue*>(request.get()); | |
| 73 if (!content->HasKey(kCommandKey)) { | |
| 74 NOTREACHED(); // Broken protocol :( | |
| 75 return; | |
| 76 } | |
| 77 std::string command; | |
| 78 DictionaryValue response; | |
| 79 | |
| 80 content->GetString(kCommandKey, &command); | |
| 81 response.SetString(kCommandKey, command); | |
| 82 bool send_response = true; | |
| 83 if (destination.empty()) { | |
| 84 // Unknown command (bad format?) | |
| 85 NOTREACHED(); | |
| 86 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); | |
| 87 SendResponse(response, message.tool(), message.destination()); | |
| 88 return; | |
| 89 } | |
| 90 int32 tab_uid = -1; | |
| 91 base::StringToInt(destination, &tab_uid); | |
| 92 | |
| 93 if (command == DebuggerRemoteServiceCommand::kAttach) { | |
| 94 // TODO(apavlov): handle 0 for a new tab | |
| 95 response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kAttach); | |
| 96 AttachToTab(destination, &response); | |
| 97 } else if (command == DebuggerRemoteServiceCommand::kDetach) { | |
| 98 response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kDetach); | |
| 99 DetachFromTab(destination, &response); | |
| 100 } else if (command == DebuggerRemoteServiceCommand::kDebuggerCommand) { | |
| 101 send_response = DispatchDebuggerCommand(tab_uid, content, &response); | |
| 102 } else if (command == DebuggerRemoteServiceCommand::kEvaluateJavascript) { | |
| 103 send_response = DispatchEvaluateJavascript(tab_uid, content, &response); | |
| 104 } else { | |
| 105 // Unknown command | |
| 106 NOTREACHED(); | |
| 107 response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); | |
| 108 } | |
| 109 | |
| 110 if (send_response) { | |
| 111 SendResponse(response, message.tool(), message.destination()); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 void DebuggerRemoteService::OnConnectionLost() { | |
| 116 delegate_->inspectable_tab_proxy()->OnRemoteDebuggerDetached(); | |
| 117 } | |
| 118 | |
| 119 // Sends a JSON response to the remote debugger using |response| as content, | |
| 120 // |tool| and |destination| as the respective header values. | |
| 121 void DebuggerRemoteService::SendResponse(const Value& response, | |
| 122 const std::string& tool, | |
| 123 const std::string& destination) { | |
| 124 std::string response_content; | |
| 125 base::JSONWriter::Write(&response, false, &response_content); | |
| 126 scoped_ptr<DevToolsRemoteMessage> response_message( | |
| 127 DevToolsRemoteMessageBuilder::instance().Create(tool, | |
| 128 destination, | |
| 129 response_content)); | |
| 130 delegate_->Send(*response_message.get()); | |
| 131 } | |
| 132 | |
| 133 // Gets a TabContents instance corresponding to the |tab_uid| using the | |
| 134 // InspectableTabProxy controllers map, or NULL if none found. | |
| 135 TabContents* DebuggerRemoteService::ToTabContents(int32 tab_uid) { | |
| 136 const InspectableTabProxy::TabMap& tab_map = | |
| 137 delegate_->inspectable_tab_proxy()->tab_map(); | |
| 138 InspectableTabProxy::TabMap::const_iterator it = tab_map.find(tab_uid); | |
| 139 if (it != tab_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. | |
| 155 void 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. | |
| 174 void 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. | |
| 186 void 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. | |
| 196 void 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. | |
| 240 void 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. | |
| 270 bool 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. | |
| 310 bool 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 } | |
| OLD | NEW |