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 "content/public/common/user_agent.h"
35 #include "content/public/common/user_agent.h"
36 #include "grit/devtools_resources_map.h"
37 #include "net/base/escape.h"
38 #include "net/base/io_buffer.h"
39 #include "net/base/ip_endpoint.h"
40 #include "net/server/http_server_request_info.h"
41 #include "net/server/http_server_response_info.h"
43 #if defined(OS_ANDROID)
44 #include "base/android/build_info.h"
51 const char kDevToolsHandlerThreadName
[] = "Chrome_DevToolsHandlerThread";
53 const char kThumbUrlPrefix
[] = "/thumb/";
54 const char kPageUrlPrefix
[] = "/devtools/page/";
56 const char kTargetIdField
[] = "id";
57 const char kTargetTypeField
[] = "type";
58 const char kTargetTitleField
[] = "title";
59 const char kTargetDescriptionField
[] = "description";
60 const char kTargetUrlField
[] = "url";
61 const char kTargetThumbnailUrlField
[] = "thumbnailUrl";
62 const char kTargetFaviconUrlField
[] = "faviconUrl";
63 const char kTargetWebSocketDebuggerUrlField
[] = "webSocketDebuggerUrl";
64 const char kTargetDevtoolsFrontendUrlField
[] = "devtoolsFrontendUrl";
66 // An internal implementation of DevToolsClientHost that delegates
67 // messages sent for DevToolsClient to a DebuggerShell instance.
68 class DevToolsClientHostImpl
: public DevToolsClientHost
{
70 DevToolsClientHostImpl(base::MessageLoop
* message_loop
,
71 net::HttpServer
* server
,
73 : message_loop_(message_loop
),
75 connection_id_(connection_id
),
77 detach_reason_("target_closed") {}
79 virtual ~DevToolsClientHostImpl() {}
81 // DevToolsClientHost interface
82 virtual void InspectedContentsClosing() OVERRIDE
{
87 base::DictionaryValue notification
;
88 notification
.SetString(
89 devtools::Inspector::detached::kParamReason
, detach_reason_
);
90 std::string response
= DevToolsProtocol::CreateNotification(
91 devtools::Inspector::detached::kName
,
92 notification
.DeepCopy())->Serialize();
93 message_loop_
->PostTask(
95 base::Bind(&net::HttpServer::SendOverWebSocket
,
100 message_loop_
->PostTask(
102 base::Bind(&net::HttpServer::Close
, server_
, connection_id_
));
105 virtual void DispatchOnInspectorFrontend(const std::string
& data
) OVERRIDE
{
106 message_loop_
->PostTask(
108 base::Bind(&net::HttpServer::SendOverWebSocket
,
114 virtual void ReplacedWithAnotherClient() OVERRIDE
{
115 detach_reason_
= "replaced_with_devtools";
119 base::MessageLoop
* message_loop_
;
120 net::HttpServer
* server_
;
123 std::string detach_reason_
;
126 static bool TimeComparator(const DevToolsTarget
* target1
,
127 const DevToolsTarget
* target2
) {
128 return target1
->GetLastActivityTime() > target2
->GetLastActivityTime();
134 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
135 const std::string
& version
) {
136 return devtools::IsSupportedProtocolVersion(version
);
140 int DevToolsHttpHandler::GetFrontendResourceId(const std::string
& name
) {
141 for (size_t i
= 0; i
< kDevtoolsResourcesSize
; ++i
) {
142 if (name
== kDevtoolsResources
[i
].name
)
143 return kDevtoolsResources
[i
].value
;
149 DevToolsHttpHandler
* DevToolsHttpHandler::Start(
150 const net::StreamListenSocketFactory
* socket_factory
,
151 const std::string
& frontend_url
,
152 DevToolsHttpHandlerDelegate
* delegate
) {
153 DevToolsHttpHandlerImpl
* http_handler
=
154 new DevToolsHttpHandlerImpl(socket_factory
,
157 http_handler
->Start();
161 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
163 // Stop() must be called prior to destruction.
164 DCHECK(server_
.get() == NULL
);
165 DCHECK(thread_
.get() == NULL
);
166 STLDeleteValues(&target_map_
);
169 void DevToolsHttpHandlerImpl::Start() {
172 thread_
.reset(new base::Thread(kDevToolsHandlerThreadName
));
173 BrowserThread::PostTask(
174 BrowserThread::FILE, FROM_HERE
,
175 base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread
, this));
178 // Runs on FILE thread.
179 void DevToolsHttpHandlerImpl::StartHandlerThread() {
180 base::Thread::Options options
;
181 options
.message_loop_type
= base::MessageLoop::TYPE_IO
;
182 if (!thread_
->StartWithOptions(options
)) {
183 BrowserThread::PostTask(
184 BrowserThread::UI
, FROM_HERE
,
185 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread
, this));
189 thread_
->message_loop()->PostTask(
191 base::Bind(&DevToolsHttpHandlerImpl::Init
, this));
194 void DevToolsHttpHandlerImpl::ResetHandlerThread() {
198 void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
199 ResetHandlerThread();
203 void DevToolsHttpHandlerImpl::Stop() {
206 BrowserThread::PostTaskAndReply(
207 BrowserThread::FILE, FROM_HERE
,
208 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread
, this),
209 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease
, this));
212 GURL
DevToolsHttpHandlerImpl::GetFrontendURL() {
213 net::IPEndPoint ip_address
;
214 if (server_
->GetLocalAddress(&ip_address
))
216 return GURL(std::string("http://") + ip_address
.ToString() + frontend_url_
);
219 static std::string
PathWithoutParams(const std::string
& path
) {
220 size_t query_position
= path
.find("?");
221 if (query_position
!= std::string::npos
)
222 return path
.substr(0, query_position
);
226 static std::string
GetMimeType(const std::string
& filename
) {
227 if (EndsWith(filename
, ".html", false)) {
229 } else if (EndsWith(filename
, ".css", false)) {
231 } else if (EndsWith(filename
, ".js", false)) {
232 return "application/javascript";
233 } else if (EndsWith(filename
, ".png", false)) {
235 } else if (EndsWith(filename
, ".gif", false)) {
242 void DevToolsHttpHandlerImpl::OnHttpRequest(
244 const net::HttpServerRequestInfo
& info
) {
245 if (info
.path
.find("/json") == 0) {
246 BrowserThread::PostTask(
249 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI
,
256 if (info
.path
.find(kThumbUrlPrefix
) == 0) {
257 // Thumbnail request.
258 const std::string target_id
= info
.path
.substr(strlen(kThumbUrlPrefix
));
259 DevToolsTarget
* target
= GetTarget(target_id
);
262 page_url
= target
->GetUrl();
263 BrowserThread::PostTask(
266 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI
,
273 if (info
.path
== "" || info
.path
== "/") {
274 // Discovery page request.
275 BrowserThread::PostTask(
278 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI
,
284 if (info
.path
.find("/devtools/") != 0) {
285 server_
->Send404(connection_id
);
289 std::string filename
= PathWithoutParams(info
.path
.substr(10));
290 std::string mime_type
= GetMimeType(filename
);
292 base::FilePath frontend_dir
= delegate_
->GetDebugFrontendDir();
293 if (!frontend_dir
.empty()) {
294 base::FilePath path
= frontend_dir
.AppendASCII(filename
);
296 base::ReadFileToString(path
, &data
);
297 server_
->Send200(connection_id
, data
, mime_type
);
300 if (delegate_
->BundlesFrontendResources()) {
301 int resource_id
= DevToolsHttpHandler::GetFrontendResourceId(filename
);
302 if (resource_id
!= -1) {
303 base::StringPiece data
= GetContentClient()->GetDataResource(
304 resource_id
, ui::SCALE_FACTOR_NONE
);
305 server_
->Send200(connection_id
, data
.as_string(), mime_type
);
309 server_
->Send404(connection_id
);
312 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
314 const net::HttpServerRequestInfo
& request
) {
315 std::string browser_prefix
= "/devtools/browser";
316 size_t browser_pos
= request
.path
.find(browser_prefix
);
317 if (browser_pos
== 0) {
318 if (browser_target_
) {
319 server_
->Send500(connection_id
, "Another client already attached");
322 browser_target_
= new DevToolsBrowserTarget(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 frontend_url_
.c_str(),
390 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", devtools::kProtocolVersion
);
452 version
.SetString("WebKit-Version", GetWebKitVersion());
453 version
.SetString("Browser", GetContentClient()->GetProduct());
454 version
.SetString("User-Agent", GetContentClient()->GetUserAgent());
455 #if defined(OS_ANDROID)
456 version
.SetString("Android-Package",
457 base::android::BuildInfo::GetInstance()->package_name());
459 SendJson(connection_id
, net::HTTP_OK
, &version
, std::string());
463 if (command
== "list") {
464 std::string host
= info
.headers
["host"];
465 AddRef(); // Balanced in OnTargetListReceived.
466 delegate_
->EnumerateTargets(
467 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceived
,
468 this, connection_id
, host
));
472 if (command
== "new") {
473 GURL
url(net::UnescapeURLComponent(
474 query
, net::UnescapeRule::URL_SPECIAL_CHARS
));
476 url
= GURL(kAboutBlankURL
);
477 scoped_ptr
<DevToolsTarget
> target(delegate_
->CreateNewTarget(url
));
479 SendJson(connection_id
,
480 net::HTTP_INTERNAL_SERVER_ERROR
,
482 "Could not create new page");
485 std::string host
= info
.headers
["host"];
486 scoped_ptr
<base::DictionaryValue
> dictionary(
487 SerializeTarget(*target
.get(), host
));
488 SendJson(connection_id
, net::HTTP_OK
, dictionary
.get(), std::string());
489 const std::string target_id
= target
->GetId();
490 target_map_
[target_id
] = target
.release();
494 if (command
== "activate" || command
== "close") {
495 DevToolsTarget
* target
= GetTarget(target_id
);
497 SendJson(connection_id
,
500 "No such target id: " + target_id
);
504 if (command
== "activate") {
505 if (target
->Activate()) {
506 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target activated");
508 SendJson(connection_id
,
509 net::HTTP_INTERNAL_SERVER_ERROR
,
511 "Could not activate target id: " + target_id
);
516 if (command
== "close") {
517 if (target
->Close()) {
518 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target is closing");
520 SendJson(connection_id
,
521 net::HTTP_INTERNAL_SERVER_ERROR
,
523 "Could not close target id: " + target_id
);
528 SendJson(connection_id
,
531 "Unknown command: " + command
);
535 void DevToolsHttpHandlerImpl::OnTargetListReceived(
537 const std::string
& host
,
538 const DevToolsHttpHandlerDelegate::TargetList
& targets
) {
539 DevToolsHttpHandlerDelegate::TargetList sorted_targets
= targets
;
540 std::sort(sorted_targets
.begin(), sorted_targets
.end(), TimeComparator
);
542 STLDeleteValues(&target_map_
);
543 base::ListValue list_value
;
544 for (DevToolsHttpHandlerDelegate::TargetList::const_iterator it
=
545 sorted_targets
.begin(); it
!= sorted_targets
.end(); ++it
) {
546 DevToolsTarget
* target
= *it
;
547 target_map_
[target
->GetId()] = target
;
548 list_value
.Append(SerializeTarget(*target
, host
));
550 SendJson(connection_id
, net::HTTP_OK
, &list_value
, std::string());
551 Release(); // Balanced in OnJsonRequestUI.
554 DevToolsTarget
* DevToolsHttpHandlerImpl::GetTarget(const std::string
& id
) {
555 TargetMap::const_iterator it
= target_map_
.find(id
);
556 if (it
== target_map_
.end())
561 void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
562 int connection_id
, const GURL
& page_url
) {
563 std::string data
= delegate_
->GetPageThumbnailData(page_url
);
565 Send200(connection_id
, data
, "image/png");
567 Send404(connection_id
);
570 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id
) {
571 std::string response
= delegate_
->GetDiscoveryPageHTML();
572 Send200(connection_id
, response
, "text/html; charset=UTF-8");
575 void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
577 const net::HttpServerRequestInfo
& request
) {
581 size_t pos
= request
.path
.find(kPageUrlPrefix
);
583 Send404(connection_id
);
587 std::string page_id
= request
.path
.substr(strlen(kPageUrlPrefix
));
588 DevToolsTarget
* target
= GetTarget(page_id
);
589 scoped_refptr
<DevToolsAgentHost
> agent
=
590 target
? target
->GetAgentHost() : NULL
;
592 Send500(connection_id
, "No such target id: " + page_id
);
596 if (agent
->IsAttached()) {
597 Send500(connection_id
,
598 "Target with given id is being inspected: " + page_id
);
602 DevToolsClientHostImpl
* client_host
= new DevToolsClientHostImpl(
603 thread_
->message_loop(), server_
.get(), connection_id
);
604 connection_to_client_host_ui_
[connection_id
] = client_host
;
606 DevToolsManager::GetInstance()->
607 RegisterDevToolsClientHostFor(agent
, client_host
);
609 AcceptWebSocket(connection_id
, request
);
612 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
614 const std::string
& data
) {
615 ConnectionToClientHostMap::iterator it
=
616 connection_to_client_host_ui_
.find(connection_id
);
617 if (it
== connection_to_client_host_ui_
.end())
620 DevToolsManager
* manager
= DevToolsManager::GetInstance();
621 manager
->DispatchOnInspectorBackend(it
->second
, data
);
624 void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id
) {
625 ConnectionToClientHostMap::iterator it
=
626 connection_to_client_host_ui_
.find(connection_id
);
627 if (it
!= connection_to_client_host_ui_
.end()) {
628 DevToolsClientHostImpl
* client_host
=
629 static_cast<DevToolsClientHostImpl
*>(it
->second
);
630 DevToolsManager::GetInstance()->ClientHostClosing(client_host
);
632 connection_to_client_host_ui_
.erase(connection_id
);
636 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
637 const net::StreamListenSocketFactory
* socket_factory
,
638 const std::string
& frontend_url
,
639 DevToolsHttpHandlerDelegate
* delegate
)
640 : frontend_url_(frontend_url
),
641 socket_factory_(socket_factory
),
642 delegate_(delegate
) {
643 if (frontend_url_
.empty())
644 frontend_url_
= "/devtools/devtools.html";
646 // Balanced in ResetHandlerThreadAndRelease().
650 // Runs on the handler thread
651 void DevToolsHttpHandlerImpl::Init() {
652 server_
= new net::HttpServer(*socket_factory_
.get(), this);
655 // Runs on the handler thread
656 void DevToolsHttpHandlerImpl::Teardown() {
660 // Runs on FILE thread to make sure that it is serialized against
661 // {Start|Stop}HandlerThread and to allow calling pthread_join.
662 void DevToolsHttpHandlerImpl::StopHandlerThread() {
663 if (!thread_
->message_loop())
665 thread_
->message_loop()->PostTask(
667 base::Bind(&DevToolsHttpHandlerImpl::Teardown
, this));
668 // Thread::Stop joins the thread.
672 void DevToolsHttpHandlerImpl::SendJson(int connection_id
,
673 net::HttpStatusCode status_code
,
675 const std::string
& message
) {
679 // Serialize value and message.
680 std::string json_value
;
682 base::JSONWriter::WriteWithOptions(value
,
683 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
686 std::string json_message
;
687 scoped_ptr
<base::Value
> message_object(new base::StringValue(message
));
688 base::JSONWriter::Write(message_object
.get(), &json_message
);
690 net::HttpServerResponseInfo
response(status_code
);
691 response
.SetBody(json_value
+ message
, "application/json; charset=UTF-8");
693 thread_
->message_loop()->PostTask(
695 base::Bind(&net::HttpServer::SendResponse
,
701 void DevToolsHttpHandlerImpl::Send200(int connection_id
,
702 const std::string
& data
,
703 const std::string
& mime_type
) {
706 thread_
->message_loop()->PostTask(
708 base::Bind(&net::HttpServer::Send200
,
715 void DevToolsHttpHandlerImpl::Send404(int connection_id
) {
718 thread_
->message_loop()->PostTask(
720 base::Bind(&net::HttpServer::Send404
, server_
.get(), connection_id
));
723 void DevToolsHttpHandlerImpl::Send500(int connection_id
,
724 const std::string
& message
) {
727 thread_
->message_loop()->PostTask(
729 base::Bind(&net::HttpServer::Send500
, server_
.get(), connection_id
,
733 void DevToolsHttpHandlerImpl::AcceptWebSocket(
735 const net::HttpServerRequestInfo
& request
) {
738 thread_
->message_loop()->PostTask(
740 base::Bind(&net::HttpServer::AcceptWebSocket
, server_
.get(),
741 connection_id
, request
));
744 base::DictionaryValue
* DevToolsHttpHandlerImpl::SerializeTarget(
745 const DevToolsTarget
& target
,
746 const std::string
& host
) {
747 base::DictionaryValue
* dictionary
= new base::DictionaryValue
;
749 std::string id
= target
.GetId();
750 dictionary
->SetString(kTargetIdField
, id
);
751 dictionary
->SetString(kTargetTypeField
, target
.GetType());
752 dictionary
->SetString(kTargetTitleField
,
753 net::EscapeForHTML(target
.GetTitle()));
754 dictionary
->SetString(kTargetDescriptionField
, target
.GetDescription());
756 GURL url
= target
.GetUrl();
757 dictionary
->SetString(kTargetUrlField
, url
.spec());
759 GURL favicon_url
= target
.GetFaviconUrl();
760 if (favicon_url
.is_valid())
761 dictionary
->SetString(kTargetFaviconUrlField
, favicon_url
.spec());
763 if (!delegate_
->GetPageThumbnailData(url
).empty()) {
764 dictionary
->SetString(kTargetThumbnailUrlField
,
765 std::string(kThumbUrlPrefix
) + id
);
768 if (!target
.IsAttached()) {
769 dictionary
->SetString(kTargetWebSocketDebuggerUrlField
,
770 base::StringPrintf("ws://%s%s%s",
774 std::string devtools_frontend_url
= GetFrontendURLInternal(
777 dictionary
->SetString(
778 kTargetDevtoolsFrontendUrlField
, devtools_frontend_url
);
784 } // namespace content