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