| // Copyright (c) 2011 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. |
| |
| // Implements the Chrome Extensions Debugger API. |
| |
| #include "chrome/browser/extensions/extension_debugger_api.h" |
| |
| #include <map> |
| #include <set> |
| |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/singleton.h" |
| #include "base/string_number_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/debugger/devtools_client_host.h" |
| #include "chrome/browser/debugger/devtools_manager.h" |
| #include "chrome/browser/extensions/extension_debugger_api_constants.h" |
| #include "chrome/browser/extensions/extension_event_router.h" |
| #include "chrome/browser/extensions/extension_tabs_module.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
| #include "chrome/common/devtools_messages.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_error_utils.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| #include "content/common/notification_service.h" |
| |
| namespace keys = extension_debugger_api_constants; |
| |
| class ExtensionDevToolsClientHost : public DevToolsClientHost, |
| public NotificationObserver { |
| public: |
| ExtensionDevToolsClientHost(TabContents* tab_contents, |
| const std::string& extension_id, |
| int tab_id); |
| |
| ~ExtensionDevToolsClientHost(); |
| |
| bool MatchesContentsAndExtensionId(TabContents* tab_contents, |
| const std::string& extension_id); |
| void Close(); |
| void SendMessageToBackend(SendRequestDebuggerFunction* function, |
| const std::string& method, |
| Value* params); |
| |
| // DevToolsClientHost interface |
| virtual void InspectedTabClosing(); |
| virtual void SendMessageToClient(const IPC::Message& msg); |
| virtual void TabReplaced(TabContentsWrapper* tab_contents); |
| virtual void FrameNavigating(const std::string& url) {} |
| |
| private: |
| // NotificationObserver implementation. |
| virtual void Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details); |
| void OnDispatchOnInspectorFrontend(const std::string& data); |
| |
| TabContents* tab_contents_; |
| std::string extension_id_; |
| int tab_id_; |
| NotificationRegistrar registrar_; |
| int last_request_id_; |
| typedef std::map<int, scoped_refptr<SendRequestDebuggerFunction> > |
| PendingRequests; |
| PendingRequests pending_requests_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost); |
| }; |
| |
| namespace { |
| |
| class AttachedClientHosts { |
| public: |
| AttachedClientHosts() {} |
| |
| // Returns the singleton instance of this class |
| static AttachedClientHosts* GetInstance() { |
| return Singleton<AttachedClientHosts>::get(); |
| } |
| |
| void Add(ExtensionDevToolsClientHost* client_host) { |
| client_hosts_.insert(client_host); |
| } |
| |
| void Remove(ExtensionDevToolsClientHost* client_host) { |
| client_hosts_.erase(client_host); |
| } |
| |
| ExtensionDevToolsClientHost* Lookup(RenderViewHost* rvh) { |
| DevToolsClientHost* client_host = |
| DevToolsManager::GetInstance()->GetDevToolsClientHostFor(rvh); |
| std::set<DevToolsClientHost*>::iterator it = |
| client_hosts_.find(client_host); |
| if (it == client_hosts_.end()) |
| return NULL; |
| return static_cast<ExtensionDevToolsClientHost*>(client_host); |
| } |
| |
| private: |
| std::set<DevToolsClientHost*> client_hosts_; |
| }; |
| |
| } // namespace |
| |
| ExtensionDevToolsClientHost::ExtensionDevToolsClientHost( |
| TabContents* tab_contents, |
| const std::string& extension_id, |
| int tab_id) |
| : tab_contents_(tab_contents), |
| extension_id_(extension_id), |
| tab_id_(tab_id), |
| last_request_id_(0) { |
| AttachedClientHosts::GetInstance()->Add(this); |
| |
| // Detach from debugger when extension unloads. |
| registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, |
| Source<Profile>(tab_contents_->profile())); |
| |
| // Attach to debugger and tell it we are ready. |
| DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( |
| tab_contents_->render_view_host(), |
| this); |
| DevToolsManager::GetInstance()->ForwardToDevToolsAgent( |
| this, |
| DevToolsAgentMsg_FrontendLoaded()); |
| } |
| |
| ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() { |
| AttachedClientHosts::GetInstance()->Remove(this); |
| } |
| |
| bool ExtensionDevToolsClientHost::MatchesContentsAndExtensionId( |
| TabContents* tab_contents, |
| const std::string& extension_id) { |
| return tab_contents == tab_contents_ && extension_id_ == extension_id; |
| } |
| |
| // DevToolsClientHost interface |
| void ExtensionDevToolsClientHost::InspectedTabClosing() { |
| // Tell extension that this client host has been detached. |
| Profile* profile = tab_contents_->profile(); |
| if (profile != NULL && profile->GetExtensionEventRouter()) { |
| ListValue args; |
| args.Append(Value::CreateIntegerValue(tab_id_)); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| |
| profile->GetExtensionEventRouter()->DispatchEventToExtension( |
| extension_id_, keys::kOnDetach, json_args, profile, GURL()); |
| } |
| delete this; |
| } |
| |
| void ExtensionDevToolsClientHost::SendMessageToClient( |
| const IPC::Message& msg) { |
| IPC_BEGIN_MESSAGE_MAP(ExtensionDevToolsClientHost, msg) |
| IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend, |
| OnDispatchOnInspectorFrontend); |
| IPC_MESSAGE_UNHANDLED_ERROR() |
| IPC_END_MESSAGE_MAP() |
| } |
| |
| void ExtensionDevToolsClientHost::TabReplaced( |
| TabContentsWrapper* tab_contents) { |
| tab_contents_ = tab_contents->tab_contents(); |
| } |
| |
| void ExtensionDevToolsClientHost::Close() { |
| DevToolsClientHost::NotifyCloseListener(); |
| delete this; |
| } |
| |
| void ExtensionDevToolsClientHost::SendMessageToBackend( |
| SendRequestDebuggerFunction* function, |
| const std::string& method, |
| Value* params) { |
| DictionaryValue protocol_request; |
| int request_id = ++last_request_id_; |
| pending_requests_[request_id] = function; |
| protocol_request.SetInteger("id", request_id); |
| protocol_request.SetString("method", method); |
| if (params) |
| protocol_request.Set("params", params->DeepCopy()); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&protocol_request, false, &json_args); |
| DevToolsManager::GetInstance()->ForwardToDevToolsAgent( |
| this, |
| DevToolsAgentMsg_DispatchOnInspectorBackend(json_args)); |
| } |
| |
| void ExtensionDevToolsClientHost::Observe( |
| NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| DCHECK(type == NotificationType::EXTENSION_UNLOADED); |
| Close(); |
| } |
| |
| void ExtensionDevToolsClientHost::OnDispatchOnInspectorFrontend( |
| const std::string& data) { |
| Profile* profile = tab_contents_->profile(); |
| if (profile == NULL || !profile->GetExtensionEventRouter()) |
| return; |
| |
| scoped_ptr<Value> result(base::JSONReader::Read(data, false)); |
| if (!result->IsType(Value::TYPE_DICTIONARY)) |
| return; |
| DictionaryValue* dictionary = static_cast<DictionaryValue*>(result.get()); |
| |
| int id; |
| if (!dictionary->GetInteger("id", &id)) { |
| std::string method_name; |
| if (!dictionary->GetString("method", &method_name)) |
| return; |
| |
| ListValue args; |
| args.Append(Value::CreateIntegerValue(tab_id_)); |
| args.Append(Value::CreateStringValue(method_name)); |
| Value* params_value; |
| if (dictionary->Get("params", ¶ms_value)) |
| args.Append(params_value->DeepCopy()); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| |
| profile->GetExtensionEventRouter()->DispatchEventToExtension( |
| extension_id_, keys::kOnEvent, json_args, profile, GURL()); |
| } else { |
| SendRequestDebuggerFunction* function = pending_requests_[id]; |
| if (!function) |
| return; |
| |
| function->SendResponseBody(dictionary); |
| pending_requests_.erase(id); |
| } |
| } |
| |
| DebuggerFunction::DebuggerFunction() |
| : contents_(0), |
| client_host_(0) { |
| } |
| |
| bool DebuggerFunction::InitTabContents(int tab_id) { |
| // Find the TabContents that contains this tab id. |
| contents_ = NULL; |
| TabContentsWrapper* wrapper = NULL; |
| bool result = ExtensionTabUtil::GetTabById( |
| tab_id, profile(), include_incognito(), NULL, NULL, &wrapper, NULL); |
| if (!result || !wrapper) { |
| error_ = error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kNoTabError, |
| base::IntToString(tab_id)); |
| return false; |
| } |
| contents_ = wrapper->tab_contents(); |
| return true; |
| } |
| |
| bool DebuggerFunction::InitClientHost(int tab_id) { |
| if (!InitTabContents(tab_id)) |
| return false; |
| |
| RenderViewHost* rvh = contents_->render_view_host(); |
| client_host_ = AttachedClientHosts::GetInstance()->Lookup(rvh); |
| |
| if (!client_host_ || |
| !client_host_->MatchesContentsAndExtensionId(contents_, |
| GetExtension()->id())) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kNotAttachedError, |
| base::IntToString(tab_id)); |
| return false; |
| } |
| return true; |
| } |
| |
| AttachDebuggerFunction::AttachDebuggerFunction() {} |
| |
| AttachDebuggerFunction::~AttachDebuggerFunction() {} |
| |
| bool AttachDebuggerFunction::RunImpl() { |
| int tab_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); |
| |
| if (!InitTabContents(tab_id)) |
| return false; |
| |
| DevToolsClientHost* client_host = DevToolsManager::GetInstance()-> |
| GetDevToolsClientHostFor(contents_->render_view_host()); |
| |
| if (client_host != NULL) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kAlreadyAttachedError, |
| base::IntToString(tab_id)); |
| return false; |
| } |
| |
| new ExtensionDevToolsClientHost(contents_, GetExtension()->id(), tab_id); |
| SendResponse(true); |
| return true; |
| } |
| |
| DetachDebuggerFunction::DetachDebuggerFunction() {} |
| |
| DetachDebuggerFunction::~DetachDebuggerFunction() {} |
| |
| bool DetachDebuggerFunction::RunImpl() { |
| int tab_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); |
| |
| if (!InitClientHost(tab_id)) |
| return false; |
| |
| client_host_->Close(); |
| SendResponse(true); |
| return true; |
| } |
| |
| SendRequestDebuggerFunction::SendRequestDebuggerFunction() {} |
| |
| SendRequestDebuggerFunction::~SendRequestDebuggerFunction() {} |
| |
| bool SendRequestDebuggerFunction::RunImpl() { |
| int tab_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); |
| |
| if (!InitClientHost(tab_id)) |
| return false; |
| |
| std::string method; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &method)); |
| |
| Value *params; |
| if (!args_->Get(2, ¶ms)) |
| params = NULL; |
| |
| client_host_->SendMessageToBackend(this, method, params); |
| return true; |
| } |
| |
| void SendRequestDebuggerFunction::SendResponseBody( |
| DictionaryValue* dictionary) { |
| Value* error_body; |
| if (dictionary->Get("error", &error_body)) { |
| base::JSONWriter::Write(error_body, false, &error_); |
| SendResponse(false); |
| return; |
| } |
| |
| Value* result_body; |
| if (dictionary->Get("result", &result_body)) |
| result_.reset(result_body->DeepCopy()); |
| else |
| result_.reset(new DictionaryValue()); |
| SendResponse(true); |
| } |