// Copyright (c) 2012 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.
#include "content/browser/devtools/devtools_browser_target.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "content/public/browser/browser_thread.h"
#include "net/server/http_server.h"
namespace content {
DevToolsBrowserTarget::DevToolsBrowserTarget(
net::HttpServer* http_server,
int connection_id)
: message_loop_proxy_(base::MessageLoopProxy::current()),
http_server_(http_server),
connection_id_(connection_id),
weak_factory_(this) {
}
void DevToolsBrowserTarget::RegisterDomainHandler(
const std::string& domain,
DevToolsProtocol::Handler* handler,
bool handle_on_ui_thread) {
DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
DCHECK(handlers_.find(domain) == handlers_.end());
handlers_[domain] = handler;
if (handle_on_ui_thread) {
handle_on_ui_thread_.insert(domain);
handler->SetNotifier(base::Bind(&DevToolsBrowserTarget::RespondFromUIThread,
weak_factory_.GetWeakPtr()));
} else {
handler->SetNotifier(base::Bind(&DevToolsBrowserTarget::Respond,
base::Unretained(this)));
}
}
typedef std::map<std::string, DevToolsBrowserTarget*> DomainMap;
base::LazyInstance<DomainMap>::Leaky g_used_domains = LAZY_INSTANCE_INITIALIZER;
void DevToolsBrowserTarget::HandleMessage(const std::string& data) {
DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
std::string error_response;
scoped_refptr<DevToolsProtocol::Command> command =
DevToolsProtocol::ParseCommand(data, &error_response);
if (!command) {
Respond(error_response);
return;
}
DomainHandlerMap::iterator it = handlers_.find(command->domain());
if (it == handlers_.end()) {
Respond(command->NoSuchMethodErrorResponse()->Serialize());
return;
}
DomainMap& used_domains(g_used_domains.Get());
std::string domain = command->domain();
DomainMap::iterator jt = used_domains.find(domain);
if (jt == used_domains.end()) {
used_domains[domain] = this;
} else if (jt->second != this) {
std::string message =
base::StringPrintf("'%s' is held by another connection",
domain.c_str());
Respond(command->ServerErrorResponse(message)->Serialize());
return;
}
DevToolsProtocol::Handler* handler = it->second;
bool handle_directly = handle_on_ui_thread_.find(domain) ==
handle_on_ui_thread_.end();
if (handle_directly) {
scoped_refptr<DevToolsProtocol::Response> response =
handler->HandleCommand(command);
if (response && response->is_async_promise())
return;
if (response)
Respond(response->Serialize());
else
Respond(command->NoSuchMethodErrorResponse()->Serialize());
return;
}
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&DevToolsBrowserTarget::HandleCommandOnUIThread,
this,
handler,
command));
}
void DevToolsBrowserTarget::Detach() {
DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
DCHECK(http_server_);
http_server_ = NULL;
DomainMap& used_domains(g_used_domains.Get());
for (DomainMap::iterator it = used_domains.begin();
it != used_domains.end();) {
if (it->second == this) {
DomainMap::iterator to_erase = it;
++it;
used_domains.erase(to_erase);
} else {
++it;
}
}
std::vector<DevToolsProtocol::Handler*> ui_handlers;
for (std::set<std::string>::iterator domain_it = handle_on_ui_thread_.begin();
domain_it != handle_on_ui_thread_.end();
++domain_it) {
DomainHandlerMap::iterator handler_it = handlers_.find(*domain_it);
CHECK(handler_it != handlers_.end());
ui_handlers.push_back(handler_it->second);
handlers_.erase(handler_it);
}
STLDeleteValues(&handlers_);
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&DevToolsBrowserTarget::DeleteHandlersOnUIThread,
this,
ui_handlers));
}
DevToolsBrowserTarget::~DevToolsBrowserTarget() {
// DCHECK that Detach has been called or no handler has ever been registered.
DCHECK(handlers_.empty());
}
void DevToolsBrowserTarget::HandleCommandOnUIThread(
DevToolsProtocol::Handler* handler,
scoped_refptr<DevToolsProtocol::Command> command) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
scoped_refptr<DevToolsProtocol::Response> response =
handler->HandleCommand(command);
if (response && response->is_async_promise())
return;
if (response)
RespondFromUIThread(response->Serialize());
else
RespondFromUIThread(command->NoSuchMethodErrorResponse()->Serialize());
}
void DevToolsBrowserTarget::DeleteHandlersOnUIThread(
std::vector<DevToolsProtocol::Handler*> handlers) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
STLDeleteElements(&handlers);
}
void DevToolsBrowserTarget::Respond(const std::string& message) {
DCHECK_EQ(message_loop_proxy_, base::MessageLoopProxy::current());
if (!http_server_)
return;
http_server_->SendOverWebSocket(connection_id_, message);
}
void DevToolsBrowserTarget::RespondFromUIThread(const std::string& message) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
message_loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&DevToolsBrowserTarget::Respond, this, message));
}
} // namespace content