| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file contains implementations of the DebuggerRemoteService methods, |
| // defines DebuggerRemoteService and DebuggerRemoteServiceCommand constants. |
| |
| #include "chrome/browser/debugger/debugger_remote_service.h" |
| |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/debugger/devtools_manager.h" |
| #include "chrome/browser/debugger/devtools_protocol_handler.h" |
| #include "chrome/browser/debugger/devtools_remote_message.h" |
| #include "chrome/browser/debugger/inspectable_tab_proxy.h" |
| #include "chrome/common/devtools_messages.h" |
| #include "chrome/common/render_messages.h" |
| #include "content/browser/renderer_host/render_view_host.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| |
| namespace { |
| |
| // Constants for the "data", "result", and "command" JSON message fields. |
| const char kDataKey[] = "data"; |
| const char kResultKey[] = "result"; |
| const char kCommandKey[] = "command"; |
| |
| } // namespace |
| |
| const std::string DebuggerRemoteServiceCommand::kAttach = "attach"; |
| const std::string DebuggerRemoteServiceCommand::kDetach = "detach"; |
| const std::string DebuggerRemoteServiceCommand::kDebuggerCommand = |
| "debugger_command"; |
| const std::string DebuggerRemoteServiceCommand::kEvaluateJavascript = |
| "evaluate_javascript"; |
| const std::string DebuggerRemoteServiceCommand::kFrameNavigate = |
| "navigated"; |
| const std::string DebuggerRemoteServiceCommand::kTabClosed = |
| "closed"; |
| |
| const std::string DebuggerRemoteService::kToolName = "V8Debugger"; |
| |
| DebuggerRemoteService::DebuggerRemoteService(DevToolsProtocolHandler* delegate) |
| : delegate_(delegate) {} |
| |
| DebuggerRemoteService::~DebuggerRemoteService() {} |
| |
| // This method handles the V8Debugger tool commands which are |
| // retrieved from the request "command" field. If an operation result |
| // is ready off-hand (synchronously), it is sent back to the remote debugger. |
| // Otherwise the corresponding response is received through IPC from the |
| // V8 debugger via DevToolsClientHost. |
| void DebuggerRemoteService::HandleMessage( |
| const DevToolsRemoteMessage& message) { |
| const std::string destination = message.destination(); |
| scoped_ptr<Value> request(base::JSONReader::Read(message.content(), true)); |
| if (request.get() == NULL) { |
| // Bad JSON |
| NOTREACHED(); |
| return; |
| } |
| DictionaryValue* content; |
| if (!request->IsType(Value::TYPE_DICTIONARY)) { |
| NOTREACHED(); // Broken protocol :( |
| return; |
| } |
| content = static_cast<DictionaryValue*>(request.get()); |
| if (!content->HasKey(kCommandKey)) { |
| NOTREACHED(); // Broken protocol :( |
| return; |
| } |
| std::string command; |
| DictionaryValue response; |
| |
| content->GetString(kCommandKey, &command); |
| response.SetString(kCommandKey, command); |
| bool send_response = true; |
| if (destination.empty()) { |
| // Unknown command (bad format?) |
| NOTREACHED(); |
| response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); |
| SendResponse(response, message.tool(), message.destination()); |
| return; |
| } |
| int32 tab_uid = -1; |
| base::StringToInt(destination, &tab_uid); |
| |
| if (command == DebuggerRemoteServiceCommand::kAttach) { |
| // TODO(apavlov): handle 0 for a new tab |
| response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kAttach); |
| AttachToTab(destination, &response); |
| } else if (command == DebuggerRemoteServiceCommand::kDetach) { |
| response.SetString(kCommandKey, DebuggerRemoteServiceCommand::kDetach); |
| DetachFromTab(destination, &response); |
| } else if (command == DebuggerRemoteServiceCommand::kDebuggerCommand) { |
| send_response = DispatchDebuggerCommand(tab_uid, content, &response); |
| } else if (command == DebuggerRemoteServiceCommand::kEvaluateJavascript) { |
| send_response = DispatchEvaluateJavascript(tab_uid, content, &response); |
| } else { |
| // Unknown command |
| NOTREACHED(); |
| response.SetInteger(kResultKey, RESULT_UNKNOWN_COMMAND); |
| } |
| |
| if (send_response) { |
| SendResponse(response, message.tool(), message.destination()); |
| } |
| } |
| |
| void DebuggerRemoteService::OnConnectionLost() { |
| delegate_->inspectable_tab_proxy()->OnRemoteDebuggerDetached(); |
| } |
| |
| // Sends a JSON response to the remote debugger using |response| as content, |
| // |tool| and |destination| as the respective header values. |
| void DebuggerRemoteService::SendResponse(const Value& response, |
| const std::string& tool, |
| const std::string& destination) { |
| std::string response_content; |
| base::JSONWriter::Write(&response, false, &response_content); |
| scoped_ptr<DevToolsRemoteMessage> response_message( |
| DevToolsRemoteMessageBuilder::instance().Create(tool, |
| destination, |
| response_content)); |
| delegate_->Send(*response_message.get()); |
| } |
| |
| // Gets a TabContents instance corresponding to the |tab_uid| using the |
| // InspectableTabProxy controllers map, or NULL if none found. |
| TabContents* DebuggerRemoteService::ToTabContents(int32 tab_uid) { |
| const InspectableTabProxy::ControllersMap& navcon_map = |
| delegate_->inspectable_tab_proxy()->controllers_map(); |
| InspectableTabProxy::ControllersMap::const_iterator it = |
| navcon_map.find(tab_uid); |
| if (it != navcon_map.end()) { |
| TabContents* tab_contents = it->second->tab_contents(); |
| if (tab_contents == NULL) { |
| return NULL; |
| } else { |
| return tab_contents; |
| } |
| } else { |
| return NULL; |
| } |
| } |
| |
| // Gets invoked from a DevToolsClientHost callback whenever |
| // a message from the V8 VM debugger corresponding to |tab_id| is received. |
| // Composes a Chrome Developer Tools Protocol JSON response and sends it |
| // to the remote debugger. |
| void DebuggerRemoteService::DebuggerOutput(int32 tab_uid, |
| const std::string& message) { |
| std::string content = StringPrintf( |
| "{\"command\":\"%s\",\"result\":%s,\"data\":%s}", |
| DebuggerRemoteServiceCommand::kDebuggerCommand.c_str(), |
| base::IntToString(RESULT_OK).c_str(), |
| message.c_str()); |
| scoped_ptr<DevToolsRemoteMessage> response_message( |
| DevToolsRemoteMessageBuilder::instance().Create( |
| kToolName, |
| base::IntToString(tab_uid), |
| content)); |
| delegate_->Send(*(response_message.get())); |
| } |
| |
| // Gets invoked from a DevToolsClientHost callback whenever |
| // a tab corresponding to |tab_id| changes its URL. |url| is the new |
| // URL of the tab (may be the same as the previous one if the tab is reloaded). |
| // Sends the corresponding message to the remote debugger. |
| void DebuggerRemoteService::FrameNavigate(int32 tab_uid, |
| const std::string& url) { |
| DictionaryValue value; |
| value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kFrameNavigate); |
| value.SetInteger(kResultKey, RESULT_OK); |
| value.SetString(kDataKey, url); |
| SendResponse(value, kToolName, base::IntToString(tab_uid)); |
| } |
| |
| // Gets invoked from a DevToolsClientHost callback whenever |
| // a tab corresponding to |tab_id| gets closed. |
| // Sends the corresponding message to the remote debugger. |
| void DebuggerRemoteService::TabClosed(int32 tab_id) { |
| DictionaryValue value; |
| value.SetString(kCommandKey, DebuggerRemoteServiceCommand::kTabClosed); |
| value.SetInteger(kResultKey, RESULT_OK); |
| SendResponse(value, kToolName, base::IntToString(tab_id)); |
| } |
| |
| // Attaches a remote debugger to the target tab specified by |destination| |
| // by posting the DevToolsAgentMsg_Attach message and sends a response |
| // to the remote debugger immediately. |
| void DebuggerRemoteService::AttachToTab(const std::string& destination, |
| DictionaryValue* response) { |
| int32 tab_uid = -1; |
| base::StringToInt(destination, &tab_uid); |
| if (tab_uid < 0) { |
| // Bad tab_uid received from remote debugger (perhaps NaN) |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return; |
| } |
| if (tab_uid == 0) { // single tab_uid |
| // We've been asked to open a new tab with URL |
| // TODO(apavlov): implement |
| NOTIMPLEMENTED(); |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return; |
| } |
| TabContents* tab_contents = ToTabContents(tab_uid); |
| if (tab_contents == NULL) { |
| // No active tab contents with tab_uid |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return; |
| } |
| RenderViewHost* target_host = tab_contents->render_view_host(); |
| DevToolsClientHost* client_host = |
| delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid); |
| if (client_host == NULL) { |
| client_host = |
| delegate_->inspectable_tab_proxy()->NewClientHost(tab_uid, this); |
| DevToolsManager* manager = DevToolsManager::GetInstance(); |
| if (manager != NULL) { |
| manager->RegisterDevToolsClientHostFor(target_host, client_host); |
| response->SetInteger(kResultKey, RESULT_OK); |
| } else { |
| response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR); |
| } |
| } else { |
| // DevToolsClientHost for this tab is already registered |
| response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE); |
| } |
| } |
| |
| // Detaches a remote debugger from the target tab specified by |destination| |
| // by posting the DevToolsAgentMsg_Detach message and sends a response |
| // to the remote debugger immediately. |
| void DebuggerRemoteService::DetachFromTab(const std::string& destination, |
| DictionaryValue* response) { |
| int32 tab_uid = -1; |
| base::StringToInt(destination, &tab_uid); |
| if (tab_uid == -1) { |
| // Bad tab_uid received from remote debugger (NaN) |
| if (response != NULL) { |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| } |
| return; |
| } |
| int result_code; |
| DevToolsClientHostImpl* client_host = |
| delegate_->inspectable_tab_proxy()->ClientHostForTabId(tab_uid); |
| if (client_host != NULL) { |
| client_host->Close(); |
| result_code = RESULT_OK; |
| } else { |
| // No client host registered for |tab_uid|. |
| result_code = RESULT_UNKNOWN_TAB; |
| } |
| if (response != NULL) { |
| response->SetInteger(kResultKey, result_code); |
| } |
| } |
| |
| // Sends a V8 debugger command to the target tab V8 debugger. |
| // Does not send back a response (which is received asynchronously |
| // through IPC) unless an error occurs before the command has actually |
| // been sent. |
| bool DebuggerRemoteService::DispatchDebuggerCommand(int tab_uid, |
| DictionaryValue* content, |
| DictionaryValue* response) { |
| if (tab_uid == -1) { |
| // Invalid tab_uid from remote debugger (perhaps NaN) |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return true; |
| } |
| DevToolsManager* manager = DevToolsManager::GetInstance(); |
| if (manager == NULL) { |
| response->SetInteger(kResultKey, RESULT_DEBUGGER_ERROR); |
| return true; |
| } |
| TabContents* tab_contents = ToTabContents(tab_uid); |
| if (tab_contents == NULL) { |
| // Unknown tab_uid from remote debugger |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return true; |
| } |
| DevToolsClientHost* client_host = |
| manager->GetDevToolsClientHostFor(tab_contents->render_view_host()); |
| if (client_host == NULL) { |
| // tab_uid is not being debugged (Attach has not been invoked) |
| response->SetInteger(kResultKey, RESULT_ILLEGAL_TAB_STATE); |
| return true; |
| } |
| std::string v8_command; |
| DictionaryValue* v8_command_value; |
| content->GetDictionary(kDataKey, &v8_command_value); |
| base::JSONWriter::Write(v8_command_value, false, &v8_command); |
| manager->ForwardToDevToolsAgent( |
| client_host, DevToolsAgentMsg_DebuggerCommand(v8_command)); |
| // Do not send the response right now, as the JSON will be received from |
| // the V8 debugger asynchronously. |
| return false; |
| } |
| |
| // Sends the immediate "evaluate Javascript" command to the V8 debugger. |
| // The evaluation result is not sent back to the client as this command |
| // is in fact needed to invoke processing of queued debugger commands. |
| bool DebuggerRemoteService::DispatchEvaluateJavascript( |
| int tab_uid, |
| DictionaryValue* content, |
| DictionaryValue* response) { |
| if (tab_uid == -1) { |
| // Invalid tab_uid from remote debugger (perhaps NaN) |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return true; |
| } |
| TabContents* tab_contents = ToTabContents(tab_uid); |
| if (tab_contents == NULL) { |
| // Unknown tab_uid from remote debugger |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return true; |
| } |
| RenderViewHost* render_view_host = tab_contents->render_view_host(); |
| if (render_view_host == NULL) { |
| // No RenderViewHost |
| response->SetInteger(kResultKey, RESULT_UNKNOWN_TAB); |
| return true; |
| } |
| std::string javascript; |
| content->GetString(kDataKey, &javascript); |
| render_view_host->ExecuteJavascriptInWebFrame(string16(), |
| UTF8ToUTF16(javascript)); |
| return false; |
| } |