blob: 7147ce6f00e39c3a208104e139d679c9f0751b0e [file] [log] [blame]
// 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/debugger/devtools_http_handler_impl.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "base/string_number_conversions.h"
#include "base/stringprintf.h"
#include "base/threading/thread.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/devtools_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host_registry.h"
#include "content/public/browser/devtools_client_host.h"
#include "content/public/browser/devtools_http_handler_delegate.h"
#include "content/public/browser/devtools_manager.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/common/content_client.h"
#include "googleurl/src/gurl.h"
#include "grit/devtools_resources_map.h"
#include "net/base/escape.h"
#include "net/base/io_buffer.h"
#include "net/server/http_server_request_info.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "ui/base/layout.h"
namespace content {
const int kBufferSize = 16 * 1024;
namespace {
class DevToolsDefaultBindingHandler
: public DevToolsHttpHandler::RenderViewHostBinding {
public:
DevToolsDefaultBindingHandler() {
}
virtual std::string GetIdentifier(RenderViewHost* rvh) OVERRIDE {
int process_id = rvh->GetProcess()->GetID();
int routing_id = rvh->GetRoutingID();
return base::StringPrintf("%d_%d", process_id, routing_id);
}
virtual RenderViewHost* ForIdentifier(
const std::string& identifier) OVERRIDE {
size_t pos = identifier.find("_");
if (pos == std::string::npos)
return NULL;
int process_id;
if (!base::StringToInt(identifier.substr(0, pos), &process_id))
return NULL;
int routing_id;
if (!base::StringToInt(identifier.substr(pos+1), &routing_id))
return NULL;
return RenderViewHost::FromID(process_id, routing_id);
}
};
// An internal implementation of DevToolsClientHost that delegates
// messages sent for DevToolsClient to a DebuggerShell instance.
class DevToolsClientHostImpl : public DevToolsClientHost {
public:
DevToolsClientHostImpl(
net::HttpServer* server,
int connection_id)
: server_(server),
connection_id_(connection_id) {
}
~DevToolsClientHostImpl() {}
// DevToolsClientHost interface
virtual void InspectedContentsClosing() {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&net::HttpServer::Close, server_, connection_id_));
}
virtual void DispatchOnInspectorFrontend(const std::string& data) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&net::HttpServer::SendOverWebSocket,
server_,
connection_id_,
data));
}
virtual void ContentsReplaced(WebContents* new_contents) {
}
private:
virtual void FrameNavigating(const std::string& url) {}
net::HttpServer* server_;
int connection_id_;
};
} // namespace
// static
int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) {
for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) {
if (name == kDevtoolsResources[i].name)
return kDevtoolsResources[i].value;
}
return -1;
}
// static
DevToolsHttpHandler* DevToolsHttpHandler::Start(
const net::StreamListenSocketFactory* socket_factory,
const std::string& frontend_url,
net::URLRequestContextGetter* request_context_getter,
DevToolsHttpHandlerDelegate* delegate) {
DevToolsHttpHandlerImpl* http_handler =
new DevToolsHttpHandlerImpl(socket_factory,
frontend_url,
request_context_getter,
delegate);
http_handler->Start();
return http_handler;
}
DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
// Stop() must be called prior to this being called
DCHECK(server_.get() == NULL);
}
void DevToolsHttpHandlerImpl::Start() {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&DevToolsHttpHandlerImpl::Init, this));
}
void DevToolsHttpHandlerImpl::Stop() {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&DevToolsHttpHandlerImpl::TeardownAndRelease, this));
}
void DevToolsHttpHandlerImpl::SetRenderViewHostBinding(
RenderViewHostBinding* binding) {
if (binding)
binding_ = binding;
else
binding_ = default_binding_.get();
}
GURL DevToolsHttpHandlerImpl::GetFrontendURL(RenderViewHost* render_view_host) {
net::IPEndPoint ip_address;
if (server_->GetLocalAddress(&ip_address))
return GURL();
std::string host = ip_address.ToString();
std::string id = binding_->GetIdentifier(render_view_host);
return GURL(std::string("http://") +
ip_address.ToString() +
GetFrontendURLInternal(id, host));
}
static std::string PathWithoutParams(const std::string& path) {
size_t query_position = path.find("?");
if (query_position != std::string::npos)
return path.substr(0, query_position);
return path;
}
static std::string GetMimeType(const std::string& filename) {
if (EndsWith(filename, ".html", false)) {
return "text/html";
} else if (EndsWith(filename, ".css", false)) {
return "text/css";
} else if (EndsWith(filename, ".js", false)) {
return "application/javascript";
} else if (EndsWith(filename, ".png", false)) {
return "image/png";
} else if (EndsWith(filename, ".gif", false)) {
return "image/gif";
}
NOTREACHED();
return "text/plain";
}
void DevToolsHttpHandlerImpl::OnHttpRequest(
int connection_id,
const net::HttpServerRequestInfo& info) {
if (info.path.find("/json") == 0) {
// Pages discovery json request.
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI,
this,
connection_id,
info));
return;
}
if (info.path == "" || info.path == "/") {
std::string response = delegate_->GetDiscoveryPageHTML();
server_->Send200(connection_id, response, "text/html; charset=UTF-8");
return;
}
// Proxy static files from chrome-devtools://devtools/*.
net::URLRequestContext* request_context =
request_context_getter_->GetURLRequestContext();
if (!request_context) {
server_->Send404(connection_id);
return;
}
net::URLRequest* request;
if (info.path.find("/devtools/") == 0) {
// Serve front-end files from resource bundle.
std::string filename = PathWithoutParams(info.path.substr(10));
if (delegate_->BundlesFrontendResources()) {
int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename);
if (resource_id != -1) {
base::StringPiece data =
content::GetContentClient()->GetDataResource(
resource_id, ui::SCALE_FACTOR_NONE);
server_->Send200(connection_id,
data.as_string(),
GetMimeType(filename));
}
return;
}
std::string base_url = delegate_->GetFrontendResourcesBaseURL();
request = request_context->CreateRequest(GURL(base_url + filename), this);
} else if (info.path.find("/thumb/") == 0) {
request = request_context->CreateRequest(
GURL("chrome:/" + info.path), this);
} else {
server_->Send404(connection_id);
return;
}
Bind(request, connection_id);
request->Start();
}
void DevToolsHttpHandlerImpl::OnWebSocketRequest(
int connection_id,
const net::HttpServerRequestInfo& request) {
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(
&DevToolsHttpHandlerImpl::OnWebSocketRequestUI,
this,
connection_id,
request));
}
void DevToolsHttpHandlerImpl::OnWebSocketMessage(
int connection_id,
const std::string& data) {
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(
&DevToolsHttpHandlerImpl::OnWebSocketMessageUI,
this,
connection_id,
data));
}
void DevToolsHttpHandlerImpl::OnClose(int connection_id) {
ConnectionToRequestsMap::iterator it =
connection_to_requests_io_.find(connection_id);
if (it != connection_to_requests_io_.end()) {
// Dispose delegating socket.
for (std::set<net::URLRequest*>::iterator it2 = it->second.begin();
it2 != it->second.end(); ++it2) {
net::URLRequest* request = *it2;
request->Cancel();
request_to_connection_io_.erase(request);
request_to_buffer_io_.erase(request);
delete request;
}
connection_to_requests_io_.erase(connection_id);
}
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(
&DevToolsHttpHandlerImpl::OnCloseUI,
this,
connection_id));
}
struct DevToolsHttpHandlerImpl::PageInfo {
PageInfo()
: attached(false) {
}
std::string id;
std::string url;
bool attached;
std::string title;
std::string thumbnail_url;
std::string favicon_url;
base::TimeTicks last_selected_time;
};
// static
bool DevToolsHttpHandlerImpl::SortPageListByTime(const PageInfo& info1,
const PageInfo& info2) {
return info1.last_selected_time > info2.last_selected_time;
}
DevToolsHttpHandlerImpl::PageList DevToolsHttpHandlerImpl::GeneratePageList() {
PageList page_list;
for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
!it.IsAtEnd(); it.Advance()) {
RenderProcessHost* render_process_host = it.GetCurrentValue();
DCHECK(render_process_host);
// Ignore processes that don't have a connection, such as crashed contents.
if (!render_process_host->HasConnection())
continue;
RenderProcessHost::RenderWidgetHostsIterator rwit(
render_process_host->GetRenderWidgetHostsIterator());
for (; !rwit.IsAtEnd(); rwit.Advance()) {
const RenderWidgetHost* widget = rwit.GetCurrentValue();
DCHECK(widget);
if (!widget || !widget->IsRenderView())
continue;
RenderViewHost* host =
RenderViewHost::From(const_cast<RenderWidgetHost*>(widget));
content::RenderViewHostDelegate* host_delegate = host->GetDelegate();
DevToolsAgentHost* agent =
DevToolsAgentHostRegistry::GetDevToolsAgentHost(host);
DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
GetDevToolsClientHostFor(agent);
PageInfo page_info;
page_info.id = binding_->GetIdentifier(host);
page_info.attached = client_host != NULL;
page_info.url = host_delegate->GetURL().spec();
WebContents* web_contents = host_delegate->GetAsWebContents();
if (web_contents) {
page_info.title = UTF16ToUTF8(
net::EscapeForHTML(web_contents->GetTitle()));
page_info.last_selected_time = web_contents->GetLastSelectedTime();
NavigationController& controller = web_contents->GetController();
NavigationEntry* entry = controller.GetActiveEntry();
if (entry != NULL && entry->GetURL().is_valid()) {
page_info.thumbnail_url = "/thumb/" + entry->GetURL().spec();
page_info.favicon_url = entry->GetFavicon().url.spec();
}
}
page_list.push_back(page_info);
}
}
std::sort(page_list.begin(), page_list.end(), SortPageListByTime);
return page_list;
}
std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal(
const std::string rvh_id,
const std::string& host) {
return base::StringPrintf(
"%s%sws=%s/devtools/page/%s",
overridden_frontend_url_.c_str(),
overridden_frontend_url_.find("?") == std::string::npos ? "?" : "&",
host.c_str(),
rvh_id.c_str());
}
void DevToolsHttpHandlerImpl::OnJsonRequestUI(
int connection_id,
const net::HttpServerRequestInfo& info) {
PageList page_list = GeneratePageList();
ListValue json_pages_list;
std::string host = info.headers["Host"];
for (PageList::iterator i = page_list.begin();
i != page_list.end(); ++i) {
DictionaryValue* page_info = new DictionaryValue;
json_pages_list.Append(page_info);
page_info->SetString("title", i->title);
page_info->SetString("url", i->url);
page_info->SetString("thumbnailUrl", i->thumbnail_url);
page_info->SetString("faviconUrl", i->favicon_url);
if (!i->attached) {
page_info->SetString("webSocketDebuggerUrl",
base::StringPrintf("ws://%s/devtools/page/%s",
host.c_str(),
i->id.c_str()));
std::string devtools_frontend_url = GetFrontendURLInternal(i->id.c_str(),
host);
page_info->SetString("devtoolsFrontendUrl", devtools_frontend_url);
}
}
std::string response;
base::JSONWriter::WriteWithOptions(&json_pages_list,
base::JSONWriter::OPTIONS_PRETTY_PRINT,
&response);
size_t jsonp_pos = info.path.find("?jsonp=");
if (jsonp_pos == std::string::npos) {
Send200(connection_id, response, "application/json; charset=UTF-8");
return;
}
std::string jsonp = info.path.substr(jsonp_pos + 7);
response = StringPrintf("%s(%s);", jsonp.c_str(), response.c_str());
Send200(connection_id, response, "text/javascript; charset=UTF-8");
}
void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
int connection_id,
const net::HttpServerRequestInfo& request) {
std::string prefix = "/devtools/page/";
size_t pos = request.path.find(prefix);
if (pos != 0) {
Send404(connection_id);
return;
}
std::string page_id = request.path.substr(prefix.length());
RenderViewHost* rvh = binding_->ForIdentifier(page_id);
if (!rvh) {
Send500(connection_id, "No such target id: " + page_id);
return;
}
DevToolsManager* manager = DevToolsManager::GetInstance();
DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost(
rvh);
if (manager->GetDevToolsClientHostFor(agent)) {
Send500(connection_id, "Target with given id is being inspected: " +
page_id);
return;
}
DevToolsClientHostImpl* client_host =
new DevToolsClientHostImpl(server_, connection_id);
connection_to_client_host_ui_[connection_id] = client_host;
manager->RegisterDevToolsClientHostFor(agent, client_host);
AcceptWebSocket(connection_id, request);
}
void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
int connection_id,
const std::string& data) {
ConnectionToClientHostMap::iterator it =
connection_to_client_host_ui_.find(connection_id);
if (it == connection_to_client_host_ui_.end())
return;
DevToolsManager* manager = DevToolsManager::GetInstance();
manager->DispatchOnInspectorBackend(it->second, data);
}
void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) {
ConnectionToClientHostMap::iterator it =
connection_to_client_host_ui_.find(connection_id);
if (it != connection_to_client_host_ui_.end()) {
DevToolsClientHostImpl* client_host =
static_cast<DevToolsClientHostImpl*>(it->second);
DevToolsManager::GetInstance()->ClientHostClosing(client_host);
delete client_host;
connection_to_client_host_ui_.erase(connection_id);
}
}
void DevToolsHttpHandlerImpl::OnResponseStarted(net::URLRequest* request) {
RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
if (it == request_to_connection_io_.end())
return;
int connection_id = it->second;
std::string content_type;
request->GetMimeType(&content_type);
if (request->status().is_success()) {
server_->Send(connection_id,
base::StringPrintf("HTTP/1.1 200 OK\r\n"
"Content-Type:%s\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n",
content_type.c_str()));
} else {
server_->Send404(connection_id);
}
int bytes_read = 0;
// Some servers may treat HEAD requests as GET requests. To free up the
// network connection as soon as possible, signal that the request has
// completed immediately, without trying to read any data back (all we care
// about is the response code and headers, which we already have).
net::IOBuffer* buffer = request_to_buffer_io_[request].get();
if (request->status().is_success())
request->Read(buffer, kBufferSize, &bytes_read);
OnReadCompleted(request, bytes_read);
}
void DevToolsHttpHandlerImpl::OnReadCompleted(net::URLRequest* request,
int bytes_read) {
RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
if (it == request_to_connection_io_.end())
return;
int connection_id = it->second;
net::IOBuffer* buffer = request_to_buffer_io_[request].get();
do {
if (!request->status().is_success() || bytes_read <= 0)
break;
std::string chunk_size = base::StringPrintf("%X\r\n", bytes_read);
server_->Send(connection_id, chunk_size);
server_->Send(connection_id, buffer->data(), bytes_read);
server_->Send(connection_id, "\r\n");
} while (request->Read(buffer, kBufferSize, &bytes_read));
// See comments re: HEAD requests in OnResponseStarted().
if (!request->status().is_io_pending()) {
server_->Send(connection_id, "0\r\n\r\n");
RequestCompleted(request);
}
}
DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
const net::StreamListenSocketFactory* socket_factory,
const std::string& frontend_url,
net::URLRequestContextGetter* request_context_getter,
DevToolsHttpHandlerDelegate* delegate)
: overridden_frontend_url_(frontend_url),
socket_factory_(socket_factory),
request_context_getter_(request_context_getter),
delegate_(delegate) {
if (overridden_frontend_url_.empty())
overridden_frontend_url_ = "/devtools/devtools.html";
default_binding_.reset(new DevToolsDefaultBindingHandler);
binding_ = default_binding_.get();
AddRef();
}
void DevToolsHttpHandlerImpl::Init() {
server_ = new net::HttpServer(*socket_factory_.get(), this);
}
// Run on I/O thread
void DevToolsHttpHandlerImpl::TeardownAndRelease() {
server_ = NULL;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DevToolsHttpHandlerImpl::Release, this));
}
void DevToolsHttpHandlerImpl::Bind(net::URLRequest* request,
int connection_id) {
request_to_connection_io_[request] = connection_id;
ConnectionToRequestsMap::iterator it =
connection_to_requests_io_.find(connection_id);
if (it == connection_to_requests_io_.end()) {
std::pair<int, std::set<net::URLRequest*> > value(
connection_id,
std::set<net::URLRequest*>());
it = connection_to_requests_io_.insert(value).first;
}
it->second.insert(request);
request_to_buffer_io_[request] = new net::IOBuffer(kBufferSize);
}
void DevToolsHttpHandlerImpl::RequestCompleted(net::URLRequest* request) {
RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
if (it == request_to_connection_io_.end())
return;
int connection_id = it->second;
request_to_connection_io_.erase(request);
ConnectionToRequestsMap::iterator it2 =
connection_to_requests_io_.find(connection_id);
it2->second.erase(request);
request_to_buffer_io_.erase(request);
delete request;
}
void DevToolsHttpHandlerImpl::Send200(int connection_id,
const std::string& data,
const std::string& mime_type) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&net::HttpServer::Send200,
server_.get(),
connection_id,
data,
mime_type));
}
void DevToolsHttpHandlerImpl::Send404(int connection_id) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&net::HttpServer::Send404, server_.get(), connection_id));
}
void DevToolsHttpHandlerImpl::Send500(int connection_id,
const std::string& message) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&net::HttpServer::Send500, server_.get(), connection_id,
message));
}
void DevToolsHttpHandlerImpl::AcceptWebSocket(
int connection_id,
const net::HttpServerRequestInfo& request) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&net::HttpServer::AcceptWebSocket, server_.get(),
connection_id, request));
}
} // namespace content