// 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; }