1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/devtools/devtools_http_handler_impl.h"
10 #include "base/bind.h"
11 #include "base/compiler_specific.h"
12 #include "base/file_util.h"
13 #include "base/json/json_writer.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_loop_proxy.h"
16 #include "base/stl_util.h"
17 #include "base/threading/thread.h"
18 #include "base/values.h"
19 #include "content/browser/devtools/devtools_browser_target.h"
20 #include "content/browser/devtools/devtools_protocol.h"
21 #include "content/browser/devtools/devtools_protocol_constants.h"
22 #include "content/browser/devtools/devtools_system_info_handler.h"
23 #include "content/browser/devtools/devtools_tracing_handler.h"
24 #include "content/browser/devtools/tethering_handler.h"
25 #include "content/common/devtools_messages.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/browser/devtools_agent_host.h"
28 #include "content/public/browser/devtools_client_host.h"
29 #include "content/public/browser/devtools_http_handler_delegate.h"
30 #include "content/public/browser/devtools_manager.h"
31 #include "content/public/browser/devtools_target.h"
32 #include "content/public/common/content_client.h"
33 #include "content/public/common/url_constants.h"
34 #include "grit/devtools_resources_map.h"
35 #include "net/base/escape.h"
36 #include "net/base/io_buffer.h"
37 #include "net/base/ip_endpoint.h"
38 #include "net/server/http_server_request_info.h"
39 #include "net/server/http_server_response_info.h"
40 #include "webkit/common/user_agent/user_agent.h"
41 #include "webkit/common/user_agent/user_agent_util.h"
47 const char kProtocolVersion[] = "1.0";
49 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread";
51 const char kThumbUrlPrefix[] = "/thumb/";
52 const char kPageUrlPrefix[] = "/devtools/page/";
54 const char kTargetIdField[] = "id";
55 const char kTargetTypeField[] = "type";
56 const char kTargetTitleField[] = "title";
57 const char kTargetDescriptionField[] = "description";
58 const char kTargetUrlField[] = "url";
59 const char kTargetThumbnailUrlField[] = "thumbnailUrl";
60 const char kTargetFaviconUrlField[] = "faviconUrl";
61 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
62 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
64 // An internal implementation of DevToolsClientHost that delegates
65 // messages sent for DevToolsClient to a DebuggerShell instance.
66 class DevToolsClientHostImpl : public DevToolsClientHost {
68 DevToolsClientHostImpl(base::MessageLoop* message_loop,
69 net::HttpServer* server,
71 : message_loop_(message_loop),
73 connection_id_(connection_id),
75 detach_reason_("target_closed") {}
77 virtual ~DevToolsClientHostImpl() {}
79 // DevToolsClientHost interface
80 virtual void InspectedContentsClosing() OVERRIDE {
85 base::DictionaryValue notification;
86 notification.SetString(
87 devtools::Inspector::detached::kParamReason, detach_reason_);
88 std::string response = DevToolsProtocol::CreateNotification(
89 devtools::Inspector::detached::kName,
90 notification.DeepCopy())->Serialize();
91 message_loop_->PostTask(
93 base::Bind(&net::HttpServer::SendOverWebSocket,
98 message_loop_->PostTask(
100 base::Bind(&net::HttpServer::Close, server_, connection_id_));
103 virtual void DispatchOnInspectorFrontend(const std::string& data) OVERRIDE {
104 message_loop_->PostTask(
106 base::Bind(&net::HttpServer::SendOverWebSocket,
112 virtual void ReplacedWithAnotherClient() OVERRIDE {
113 detach_reason_ = "replaced_with_devtools";
117 base::MessageLoop* message_loop_;
118 net::HttpServer* server_;
121 std::string detach_reason_;
124 static bool TimeComparator(const DevToolsTarget* target1,
125 const DevToolsTarget* target2) {
126 return target1->GetLastActivityTime() > target2->GetLastActivityTime();
132 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
133 const std::string& version) {
134 return version == kProtocolVersion;
138 int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) {
139 for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) {
140 if (name == kDevtoolsResources[i].name)
141 return kDevtoolsResources[i].value;
147 DevToolsHttpHandler* DevToolsHttpHandler::Start(
148 const net::StreamListenSocketFactory* socket_factory,
149 const std::string& frontend_url,
150 DevToolsHttpHandlerDelegate* delegate) {
151 DevToolsHttpHandlerImpl* http_handler =
152 new DevToolsHttpHandlerImpl(socket_factory,
155 http_handler->Start();
159 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161 // Stop() must be called prior to destruction.
162 DCHECK(server_.get() == NULL);
163 DCHECK(thread_.get() == NULL);
164 STLDeleteValues(&target_map_);
167 void DevToolsHttpHandlerImpl::Start() {
170 thread_.reset(new base::Thread(kDevToolsHandlerThreadName));
171 BrowserThread::PostTask(
172 BrowserThread::FILE, FROM_HERE,
173 base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread, this));
176 // Runs on FILE thread.
177 void DevToolsHttpHandlerImpl::StartHandlerThread() {
178 base::Thread::Options options;
179 options.message_loop_type = base::MessageLoop::TYPE_IO;
180 if (!thread_->StartWithOptions(options)) {
181 BrowserThread::PostTask(
182 BrowserThread::UI, FROM_HERE,
183 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this));
187 thread_->message_loop()->PostTask(
189 base::Bind(&DevToolsHttpHandlerImpl::Init, this));
192 void DevToolsHttpHandlerImpl::ResetHandlerThread() {
196 void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
197 ResetHandlerThread();
201 void DevToolsHttpHandlerImpl::Stop() {
204 BrowserThread::PostTaskAndReply(
205 BrowserThread::FILE, FROM_HERE,
206 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this),
207 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease, this));
210 GURL DevToolsHttpHandlerImpl::GetFrontendURL() {
211 net::IPEndPoint ip_address;
212 if (server_->GetLocalAddress(&ip_address))
214 return GURL(std::string("http://") + ip_address.ToString() +
215 overridden_frontend_url_);
218 static std::string PathWithoutParams(const std::string& path) {
219 size_t query_position = path.find("?");
220 if (query_position != std::string::npos)
221 return path.substr(0, query_position);
225 static std::string GetMimeType(const std::string& filename) {
226 if (EndsWith(filename, ".html", false)) {
228 } else if (EndsWith(filename, ".css", false)) {
230 } else if (EndsWith(filename, ".js", false)) {
231 return "application/javascript";
232 } else if (EndsWith(filename, ".png", false)) {
234 } else if (EndsWith(filename, ".gif", false)) {
241 void DevToolsHttpHandlerImpl::OnHttpRequest(
243 const net::HttpServerRequestInfo& info) {
244 if (info.path.find("/json") == 0) {
245 BrowserThread::PostTask(
248 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI,
255 if (info.path.find(kThumbUrlPrefix) == 0) {
256 // Thumbnail request.
257 const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
258 DevToolsTarget* target = GetTarget(target_id);
261 page_url = target->GetUrl();
262 BrowserThread::PostTask(
265 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI,
272 if (info.path == "" || info.path == "/") {
273 // Discovery page request.
274 BrowserThread::PostTask(
277 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI,
283 if (info.path.find("/devtools/") != 0) {
284 server_->Send404(connection_id);
288 std::string filename = PathWithoutParams(info.path.substr(10));
289 std::string mime_type = GetMimeType(filename);
291 base::FilePath frontend_dir = delegate_->GetDebugFrontendDir();
292 if (!frontend_dir.empty()) {
293 base::FilePath path = frontend_dir.AppendASCII(filename);
295 base::ReadFileToString(path, &data);
296 server_->Send200(connection_id, data, mime_type);
299 if (delegate_->BundlesFrontendResources()) {
300 int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename);
301 if (resource_id != -1) {
302 base::StringPiece data = GetContentClient()->GetDataResource(
303 resource_id, ui::SCALE_FACTOR_NONE);
304 server_->Send200(connection_id, data.as_string(), mime_type);
308 server_->Send404(connection_id);
311 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
313 const net::HttpServerRequestInfo& request) {
314 std::string browser_prefix = "/devtools/browser";
315 size_t browser_pos = request.path.find(browser_prefix);
316 if (browser_pos == 0) {
317 if (browser_target_) {
318 server_->Send500(connection_id, "Another client already attached");
321 browser_target_ = new DevToolsBrowserTarget(
322 thread_->message_loop_proxy().get(), server_.get(), connection_id);
323 browser_target_->RegisterDomainHandler(
324 devtools::Tracing::kName,
325 new DevToolsTracingHandler(),
326 true /* handle on UI thread */);
327 browser_target_->RegisterDomainHandler(
328 TetheringHandler::kDomain,
329 new TetheringHandler(delegate_.get()),
330 false /* handle on this thread */);
331 browser_target_->RegisterDomainHandler(
332 devtools::SystemInfo::kName,
333 new DevToolsSystemInfoHandler(),
334 true /* handle on UI thread */);
336 server_->AcceptWebSocket(connection_id, request);
340 BrowserThread::PostTask(
344 &DevToolsHttpHandlerImpl::OnWebSocketRequestUI,
350 void DevToolsHttpHandlerImpl::OnWebSocketMessage(
352 const std::string& data) {
353 if (browser_target_ && connection_id == browser_target_->connection_id()) {
354 browser_target_->HandleMessage(data);
358 BrowserThread::PostTask(
362 &DevToolsHttpHandlerImpl::OnWebSocketMessageUI,
368 void DevToolsHttpHandlerImpl::OnClose(int connection_id) {
369 if (browser_target_ && browser_target_->connection_id() == connection_id) {
370 browser_target_->Detach();
371 browser_target_ = NULL;
375 BrowserThread::PostTask(
379 &DevToolsHttpHandlerImpl::OnCloseUI,
384 std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal(
385 const std::string id,
386 const std::string& host) {
387 return base::StringPrintf(
389 overridden_frontend_url_.c_str(),
390 overridden_frontend_url_.find("?") == std::string::npos ? "?" : "&",
396 static bool ParseJsonPath(
397 const std::string& path,
398 std::string* command,
399 std::string* target_id) {
401 // Fall back to list in case of empty query.
407 if (path.find("/") != 0) {
408 // Malformed command.
411 *command = path.substr(1);
413 size_t separator_pos = command->find("/");
414 if (separator_pos != std::string::npos) {
415 *target_id = command->substr(separator_pos + 1);
416 *command = command->substr(0, separator_pos);
421 void DevToolsHttpHandlerImpl::OnJsonRequestUI(
423 const net::HttpServerRequestInfo& info) {
425 std::string path = info.path.substr(5);
427 // Trim fragment and query
429 size_t query_pos = path.find("?");
430 if (query_pos != std::string::npos) {
431 query = path.substr(query_pos + 1);
432 path = path.substr(0, query_pos);
435 size_t fragment_pos = path.find("#");
436 if (fragment_pos != std::string::npos)
437 path = path.substr(0, fragment_pos);
440 std::string target_id;
441 if (!ParseJsonPath(path, &command, &target_id)) {
442 SendJson(connection_id,
445 "Malformed query: " + info.path);
449 if (command == "version") {
450 base::DictionaryValue version;
451 version.SetString("Protocol-Version", kProtocolVersion);
452 version.SetString("WebKit-Version", webkit_glue::GetWebKitVersion());
453 version.SetString("Browser", content::GetContentClient()->GetProduct());
454 version.SetString("User-Agent",
455 webkit_glue::GetUserAgent(GURL(kAboutBlankURL)));
456 SendJson(connection_id, net::HTTP_OK, &version, std::string());
460 if (command == "list") {
461 std::string host = info.headers["host"];
462 AddRef(); // Balanced in OnTargetListReceived.
463 delegate_->EnumerateTargets(
464 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceived,
465 this, connection_id, host));
469 if (command == "new") {
470 GURL url(net::UnescapeURLComponent(
471 query, net::UnescapeRule::URL_SPECIAL_CHARS));
473 url = GURL(kAboutBlankURL);
474 scoped_ptr<DevToolsTarget> target(delegate_->CreateNewTarget(url));
476 SendJson(connection_id,
477 net::HTTP_INTERNAL_SERVER_ERROR,
479 "Could not create new page");
482 std::string host = info.headers["host"];
483 scoped_ptr<base::DictionaryValue> dictionary(
484 SerializeTarget(*target.get(), host));
485 SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
486 const std::string target_id = target->GetId();
487 target_map_[target_id] = target.release();
491 if (command == "activate" || command == "close") {
492 DevToolsTarget* target = GetTarget(target_id);
494 SendJson(connection_id,
497 "No such target id: " + target_id);
501 if (command == "activate") {
502 if (target->Activate()) {
503 SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
505 SendJson(connection_id,
506 net::HTTP_INTERNAL_SERVER_ERROR,
508 "Could not activate target id: " + target_id);
513 if (command == "close") {
514 if (target->Close()) {
515 SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
517 SendJson(connection_id,
518 net::HTTP_INTERNAL_SERVER_ERROR,
520 "Could not close target id: " + target_id);
525 SendJson(connection_id,
528 "Unknown command: " + command);
532 void DevToolsHttpHandlerImpl::OnTargetListReceived(
534 const std::string& host,
535 const DevToolsHttpHandlerDelegate::TargetList& targets) {
536 DevToolsHttpHandlerDelegate::TargetList sorted_targets = targets;
537 std::sort(sorted_targets.begin(), sorted_targets.end(), TimeComparator);
539 STLDeleteValues(&target_map_);
540 base::ListValue list_value;
541 for (DevToolsHttpHandlerDelegate::TargetList::const_iterator it =
542 sorted_targets.begin(); it != sorted_targets.end(); ++it) {
543 DevToolsTarget* target = *it;
544 target_map_[target->GetId()] = target;
545 list_value.Append(SerializeTarget(*target, host));
547 SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
548 Release(); // Balanced in OnJsonRequestUI.
551 DevToolsTarget* DevToolsHttpHandlerImpl::GetTarget(const std::string& id) {
552 TargetMap::const_iterator it = target_map_.find(id);
553 if (it == target_map_.end())
558 void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
559 int connection_id, const GURL& page_url) {
560 std::string data = delegate_->GetPageThumbnailData(page_url);
562 Send200(connection_id, data, "image/png");
564 Send404(connection_id);
567 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id) {
568 std::string response = delegate_->GetDiscoveryPageHTML();
569 Send200(connection_id, response, "text/html; charset=UTF-8");
572 void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
574 const net::HttpServerRequestInfo& request) {
578 size_t pos = request.path.find(kPageUrlPrefix);
580 Send404(connection_id);
584 std::string page_id = request.path.substr(strlen(kPageUrlPrefix));
585 DevToolsTarget* target = GetTarget(page_id);
586 scoped_refptr<DevToolsAgentHost> agent =
587 target ? target->GetAgentHost() : NULL;
589 Send500(connection_id, "No such target id: " + page_id);
593 if (agent->IsAttached()) {
594 Send500(connection_id,
595 "Target with given id is being inspected: " + page_id);
599 DevToolsClientHostImpl* client_host = new DevToolsClientHostImpl(
600 thread_->message_loop(), server_.get(), connection_id);
601 connection_to_client_host_ui_[connection_id] = client_host;
603 DevToolsManager::GetInstance()->
604 RegisterDevToolsClientHostFor(agent, client_host);
606 AcceptWebSocket(connection_id, request);
609 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
611 const std::string& data) {
612 ConnectionToClientHostMap::iterator it =
613 connection_to_client_host_ui_.find(connection_id);
614 if (it == connection_to_client_host_ui_.end())
617 DevToolsManager* manager = DevToolsManager::GetInstance();
618 manager->DispatchOnInspectorBackend(it->second, data);
621 void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) {
622 ConnectionToClientHostMap::iterator it =
623 connection_to_client_host_ui_.find(connection_id);
624 if (it != connection_to_client_host_ui_.end()) {
625 DevToolsClientHostImpl* client_host =
626 static_cast<DevToolsClientHostImpl*>(it->second);
627 DevToolsManager::GetInstance()->ClientHostClosing(client_host);
629 connection_to_client_host_ui_.erase(connection_id);
633 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
634 const net::StreamListenSocketFactory* socket_factory,
635 const std::string& frontend_url,
636 DevToolsHttpHandlerDelegate* delegate)
637 : overridden_frontend_url_(frontend_url),
638 socket_factory_(socket_factory),
639 delegate_(delegate) {
640 if (overridden_frontend_url_.empty())
641 overridden_frontend_url_ = "/devtools/devtools.html";
643 // Balanced in ResetHandlerThreadAndRelease().
647 // Runs on the handler thread
648 void DevToolsHttpHandlerImpl::Init() {
649 server_ = new net::HttpServer(*socket_factory_.get(), this);
652 // Runs on the handler thread
653 void DevToolsHttpHandlerImpl::Teardown() {
657 // Runs on FILE thread to make sure that it is serialized against
658 // {Start|Stop}HandlerThread and to allow calling pthread_join.
659 void DevToolsHttpHandlerImpl::StopHandlerThread() {
660 if (!thread_->message_loop())
662 thread_->message_loop()->PostTask(
664 base::Bind(&DevToolsHttpHandlerImpl::Teardown, this));
665 // Thread::Stop joins the thread.
669 void DevToolsHttpHandlerImpl::SendJson(int connection_id,
670 net::HttpStatusCode status_code,
672 const std::string& message) {
676 // Serialize value and message.
677 std::string json_value;
679 base::JSONWriter::WriteWithOptions(value,
680 base::JSONWriter::OPTIONS_PRETTY_PRINT,
683 std::string json_message;
684 scoped_ptr<base::Value> message_object(new base::StringValue(message));
685 base::JSONWriter::Write(message_object.get(), &json_message);
687 net::HttpServerResponseInfo response(status_code);
688 response.SetBody(json_value + message, "application/json; charset=UTF-8");
690 thread_->message_loop()->PostTask(
692 base::Bind(&net::HttpServer::SendResponse,
698 void DevToolsHttpHandlerImpl::Send200(int connection_id,
699 const std::string& data,
700 const std::string& mime_type) {
703 thread_->message_loop()->PostTask(
705 base::Bind(&net::HttpServer::Send200,
712 void DevToolsHttpHandlerImpl::Send404(int connection_id) {
715 thread_->message_loop()->PostTask(
717 base::Bind(&net::HttpServer::Send404, server_.get(), connection_id));
720 void DevToolsHttpHandlerImpl::Send500(int connection_id,
721 const std::string& message) {
724 thread_->message_loop()->PostTask(
726 base::Bind(&net::HttpServer::Send500, server_.get(), connection_id,
730 void DevToolsHttpHandlerImpl::AcceptWebSocket(
732 const net::HttpServerRequestInfo& request) {
735 thread_->message_loop()->PostTask(
737 base::Bind(&net::HttpServer::AcceptWebSocket, server_.get(),
738 connection_id, request));
741 base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeTarget(
742 const DevToolsTarget& target,
743 const std::string& host) {
744 base::DictionaryValue* dictionary = new base::DictionaryValue;
746 std::string id = target.GetId();
747 dictionary->SetString(kTargetIdField, id);
748 dictionary->SetString(kTargetTypeField, target.GetType());
749 dictionary->SetString(kTargetTitleField,
750 net::EscapeForHTML(target.GetTitle()));
751 dictionary->SetString(kTargetDescriptionField, target.GetDescription());
753 GURL url = target.GetUrl();
754 dictionary->SetString(kTargetUrlField, url.spec());
756 GURL favicon_url = target.GetFaviconUrl();
757 if (favicon_url.is_valid())
758 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
760 if (!delegate_->GetPageThumbnailData(url).empty()) {
761 dictionary->SetString(kTargetThumbnailUrlField,
762 std::string(kThumbUrlPrefix) + id);
765 if (!target.IsAttached()) {
766 dictionary->SetString(kTargetWebSocketDebuggerUrlField,
767 base::StringPrintf("ws://%s%s%s",
771 std::string devtools_frontend_url = GetFrontendURLInternal(
774 dictionary->SetString(
775 kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
781 } // namespace content