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"
43 #if defined(OS_ANDROID)
44 #include "base/android/build_info.h"
51 const char kProtocolVersion
[] = "1.0";
53 const char kDevToolsHandlerThreadName
[] = "Chrome_DevToolsHandlerThread";
55 const char kThumbUrlPrefix
[] = "/thumb/";
56 const char kPageUrlPrefix
[] = "/devtools/page/";
58 const char kTargetIdField
[] = "id";
59 const char kTargetTypeField
[] = "type";
60 const char kTargetTitleField
[] = "title";
61 const char kTargetDescriptionField
[] = "description";
62 const char kTargetUrlField
[] = "url";
63 const char kTargetThumbnailUrlField
[] = "thumbnailUrl";
64 const char kTargetFaviconUrlField
[] = "faviconUrl";
65 const char kTargetWebSocketDebuggerUrlField
[] = "webSocketDebuggerUrl";
66 const char kTargetDevtoolsFrontendUrlField
[] = "devtoolsFrontendUrl";
68 // An internal implementation of DevToolsClientHost that delegates
69 // messages sent for DevToolsClient to a DebuggerShell instance.
70 class DevToolsClientHostImpl
: public DevToolsClientHost
{
72 DevToolsClientHostImpl(base::MessageLoop
* message_loop
,
73 net::HttpServer
* server
,
75 : message_loop_(message_loop
),
77 connection_id_(connection_id
),
79 detach_reason_("target_closed") {}
81 virtual ~DevToolsClientHostImpl() {}
83 // DevToolsClientHost interface
84 virtual void InspectedContentsClosing() OVERRIDE
{
89 base::DictionaryValue notification
;
90 notification
.SetString(
91 devtools::Inspector::detached::kParamReason
, detach_reason_
);
92 std::string response
= DevToolsProtocol::CreateNotification(
93 devtools::Inspector::detached::kName
,
94 notification
.DeepCopy())->Serialize();
95 message_loop_
->PostTask(
97 base::Bind(&net::HttpServer::SendOverWebSocket
,
102 message_loop_
->PostTask(
104 base::Bind(&net::HttpServer::Close
, server_
, connection_id_
));
107 virtual void DispatchOnInspectorFrontend(const std::string
& data
) OVERRIDE
{
108 message_loop_
->PostTask(
110 base::Bind(&net::HttpServer::SendOverWebSocket
,
116 virtual void ReplacedWithAnotherClient() OVERRIDE
{
117 detach_reason_
= "replaced_with_devtools";
121 base::MessageLoop
* message_loop_
;
122 net::HttpServer
* server_
;
125 std::string detach_reason_
;
128 static bool TimeComparator(const DevToolsTarget
* target1
,
129 const DevToolsTarget
* target2
) {
130 return target1
->GetLastActivityTime() > target2
->GetLastActivityTime();
136 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
137 const std::string
& version
) {
138 return version
== kProtocolVersion
;
142 int DevToolsHttpHandler::GetFrontendResourceId(const std::string
& name
) {
143 for (size_t i
= 0; i
< kDevtoolsResourcesSize
; ++i
) {
144 if (name
== kDevtoolsResources
[i
].name
)
145 return kDevtoolsResources
[i
].value
;
151 DevToolsHttpHandler
* DevToolsHttpHandler::Start(
152 const net::StreamListenSocketFactory
* socket_factory
,
153 const std::string
& frontend_url
,
154 DevToolsHttpHandlerDelegate
* delegate
) {
155 DevToolsHttpHandlerImpl
* http_handler
=
156 new DevToolsHttpHandlerImpl(socket_factory
,
159 http_handler
->Start();
163 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
165 // Stop() must be called prior to destruction.
166 DCHECK(server_
.get() == NULL
);
167 DCHECK(thread_
.get() == NULL
);
168 STLDeleteValues(&target_map_
);
171 void DevToolsHttpHandlerImpl::Start() {
174 thread_
.reset(new base::Thread(kDevToolsHandlerThreadName
));
175 BrowserThread::PostTask(
176 BrowserThread::FILE, FROM_HERE
,
177 base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread
, this));
180 // Runs on FILE thread.
181 void DevToolsHttpHandlerImpl::StartHandlerThread() {
182 base::Thread::Options options
;
183 options
.message_loop_type
= base::MessageLoop::TYPE_IO
;
184 if (!thread_
->StartWithOptions(options
)) {
185 BrowserThread::PostTask(
186 BrowserThread::UI
, FROM_HERE
,
187 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread
, this));
191 thread_
->message_loop()->PostTask(
193 base::Bind(&DevToolsHttpHandlerImpl::Init
, this));
196 void DevToolsHttpHandlerImpl::ResetHandlerThread() {
200 void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
201 ResetHandlerThread();
205 void DevToolsHttpHandlerImpl::Stop() {
208 BrowserThread::PostTaskAndReply(
209 BrowserThread::FILE, FROM_HERE
,
210 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread
, this),
211 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease
, this));
214 GURL
DevToolsHttpHandlerImpl::GetFrontendURL() {
215 net::IPEndPoint ip_address
;
216 if (server_
->GetLocalAddress(&ip_address
))
218 return GURL(std::string("http://") + ip_address
.ToString() +
219 overridden_frontend_url_
);
222 static std::string
PathWithoutParams(const std::string
& path
) {
223 size_t query_position
= path
.find("?");
224 if (query_position
!= std::string::npos
)
225 return path
.substr(0, query_position
);
229 static std::string
GetMimeType(const std::string
& filename
) {
230 if (EndsWith(filename
, ".html", false)) {
232 } else if (EndsWith(filename
, ".css", false)) {
234 } else if (EndsWith(filename
, ".js", false)) {
235 return "application/javascript";
236 } else if (EndsWith(filename
, ".png", false)) {
238 } else if (EndsWith(filename
, ".gif", false)) {
245 void DevToolsHttpHandlerImpl::OnHttpRequest(
247 const net::HttpServerRequestInfo
& info
) {
248 if (info
.path
.find("/json") == 0) {
249 BrowserThread::PostTask(
252 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI
,
259 if (info
.path
.find(kThumbUrlPrefix
) == 0) {
260 // Thumbnail request.
261 const std::string target_id
= info
.path
.substr(strlen(kThumbUrlPrefix
));
262 DevToolsTarget
* target
= GetTarget(target_id
);
265 page_url
= target
->GetUrl();
266 BrowserThread::PostTask(
269 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI
,
276 if (info
.path
== "" || info
.path
== "/") {
277 // Discovery page request.
278 BrowserThread::PostTask(
281 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI
,
287 if (info
.path
.find("/devtools/") != 0) {
288 server_
->Send404(connection_id
);
292 std::string filename
= PathWithoutParams(info
.path
.substr(10));
293 std::string mime_type
= GetMimeType(filename
);
295 base::FilePath frontend_dir
= delegate_
->GetDebugFrontendDir();
296 if (!frontend_dir
.empty()) {
297 base::FilePath path
= frontend_dir
.AppendASCII(filename
);
299 base::ReadFileToString(path
, &data
);
300 server_
->Send200(connection_id
, data
, mime_type
);
303 if (delegate_
->BundlesFrontendResources()) {
304 int resource_id
= DevToolsHttpHandler::GetFrontendResourceId(filename
);
305 if (resource_id
!= -1) {
306 base::StringPiece data
= GetContentClient()->GetDataResource(
307 resource_id
, ui::SCALE_FACTOR_NONE
);
308 server_
->Send200(connection_id
, data
.as_string(), mime_type
);
312 server_
->Send404(connection_id
);
315 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
317 const net::HttpServerRequestInfo
& request
) {
318 std::string browser_prefix
= "/devtools/browser";
319 size_t browser_pos
= request
.path
.find(browser_prefix
);
320 if (browser_pos
== 0) {
321 if (browser_target_
) {
322 server_
->Send500(connection_id
, "Another client already attached");
325 browser_target_
= new DevToolsBrowserTarget(
326 thread_
->message_loop_proxy().get(), server_
.get(), connection_id
);
327 browser_target_
->RegisterDomainHandler(
328 devtools::Tracing::kName
,
329 new DevToolsTracingHandler(),
330 true /* handle on UI thread */);
331 browser_target_
->RegisterDomainHandler(
332 TetheringHandler::kDomain
,
333 new TetheringHandler(delegate_
.get()),
334 false /* handle on this thread */);
335 browser_target_
->RegisterDomainHandler(
336 devtools::SystemInfo::kName
,
337 new DevToolsSystemInfoHandler(),
338 true /* handle on UI thread */);
340 server_
->AcceptWebSocket(connection_id
, request
);
344 BrowserThread::PostTask(
348 &DevToolsHttpHandlerImpl::OnWebSocketRequestUI
,
354 void DevToolsHttpHandlerImpl::OnWebSocketMessage(
356 const std::string
& data
) {
357 if (browser_target_
&& connection_id
== browser_target_
->connection_id()) {
358 browser_target_
->HandleMessage(data
);
362 BrowserThread::PostTask(
366 &DevToolsHttpHandlerImpl::OnWebSocketMessageUI
,
372 void DevToolsHttpHandlerImpl::OnClose(int connection_id
) {
373 if (browser_target_
&& browser_target_
->connection_id() == connection_id
) {
374 browser_target_
->Detach();
375 browser_target_
= NULL
;
379 BrowserThread::PostTask(
383 &DevToolsHttpHandlerImpl::OnCloseUI
,
388 std::string
DevToolsHttpHandlerImpl::GetFrontendURLInternal(
389 const std::string id
,
390 const std::string
& host
) {
391 return base::StringPrintf(
393 overridden_frontend_url_
.c_str(),
394 overridden_frontend_url_
.find("?") == std::string::npos
? "?" : "&",
400 static bool ParseJsonPath(
401 const std::string
& path
,
402 std::string
* command
,
403 std::string
* target_id
) {
405 // Fall back to list in case of empty query.
411 if (path
.find("/") != 0) {
412 // Malformed command.
415 *command
= path
.substr(1);
417 size_t separator_pos
= command
->find("/");
418 if (separator_pos
!= std::string::npos
) {
419 *target_id
= command
->substr(separator_pos
+ 1);
420 *command
= command
->substr(0, separator_pos
);
425 void DevToolsHttpHandlerImpl::OnJsonRequestUI(
427 const net::HttpServerRequestInfo
& info
) {
429 std::string path
= info
.path
.substr(5);
431 // Trim fragment and query
433 size_t query_pos
= path
.find("?");
434 if (query_pos
!= std::string::npos
) {
435 query
= path
.substr(query_pos
+ 1);
436 path
= path
.substr(0, query_pos
);
439 size_t fragment_pos
= path
.find("#");
440 if (fragment_pos
!= std::string::npos
)
441 path
= path
.substr(0, fragment_pos
);
444 std::string target_id
;
445 if (!ParseJsonPath(path
, &command
, &target_id
)) {
446 SendJson(connection_id
,
449 "Malformed query: " + info
.path
);
453 if (command
== "version") {
454 base::DictionaryValue version
;
455 version
.SetString("Protocol-Version", kProtocolVersion
);
456 version
.SetString("WebKit-Version", webkit_glue::GetWebKitVersion());
457 version
.SetString("Browser", content::GetContentClient()->GetProduct());
458 version
.SetString("User-Agent",
459 webkit_glue::GetUserAgent(GURL(kAboutBlankURL
)));
460 #if defined(OS_ANDROID)
461 version
.SetString("Android-Package",
462 base::android::BuildInfo::GetInstance()->package_name());
464 SendJson(connection_id
, net::HTTP_OK
, &version
, std::string());
468 if (command
== "list") {
469 std::string host
= info
.headers
["host"];
470 AddRef(); // Balanced in OnTargetListReceived.
471 delegate_
->EnumerateTargets(
472 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceived
,
473 this, connection_id
, host
));
477 if (command
== "new") {
478 GURL
url(net::UnescapeURLComponent(
479 query
, net::UnescapeRule::URL_SPECIAL_CHARS
));
481 url
= GURL(kAboutBlankURL
);
482 scoped_ptr
<DevToolsTarget
> target(delegate_
->CreateNewTarget(url
));
484 SendJson(connection_id
,
485 net::HTTP_INTERNAL_SERVER_ERROR
,
487 "Could not create new page");
490 std::string host
= info
.headers
["host"];
491 scoped_ptr
<base::DictionaryValue
> dictionary(
492 SerializeTarget(*target
.get(), host
));
493 SendJson(connection_id
, net::HTTP_OK
, dictionary
.get(), std::string());
494 const std::string target_id
= target
->GetId();
495 target_map_
[target_id
] = target
.release();
499 if (command
== "activate" || command
== "close") {
500 DevToolsTarget
* target
= GetTarget(target_id
);
502 SendJson(connection_id
,
505 "No such target id: " + target_id
);
509 if (command
== "activate") {
510 if (target
->Activate()) {
511 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target activated");
513 SendJson(connection_id
,
514 net::HTTP_INTERNAL_SERVER_ERROR
,
516 "Could not activate target id: " + target_id
);
521 if (command
== "close") {
522 if (target
->Close()) {
523 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target is closing");
525 SendJson(connection_id
,
526 net::HTTP_INTERNAL_SERVER_ERROR
,
528 "Could not close target id: " + target_id
);
533 SendJson(connection_id
,
536 "Unknown command: " + command
);
540 void DevToolsHttpHandlerImpl::OnTargetListReceived(
542 const std::string
& host
,
543 const DevToolsHttpHandlerDelegate::TargetList
& targets
) {
544 DevToolsHttpHandlerDelegate::TargetList sorted_targets
= targets
;
545 std::sort(sorted_targets
.begin(), sorted_targets
.end(), TimeComparator
);
547 STLDeleteValues(&target_map_
);
548 base::ListValue list_value
;
549 for (DevToolsHttpHandlerDelegate::TargetList::const_iterator it
=
550 sorted_targets
.begin(); it
!= sorted_targets
.end(); ++it
) {
551 DevToolsTarget
* target
= *it
;
552 target_map_
[target
->GetId()] = target
;
553 list_value
.Append(SerializeTarget(*target
, host
));
555 SendJson(connection_id
, net::HTTP_OK
, &list_value
, std::string());
556 Release(); // Balanced in OnJsonRequestUI.
559 DevToolsTarget
* DevToolsHttpHandlerImpl::GetTarget(const std::string
& id
) {
560 TargetMap::const_iterator it
= target_map_
.find(id
);
561 if (it
== target_map_
.end())
566 void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
567 int connection_id
, const GURL
& page_url
) {
568 std::string data
= delegate_
->GetPageThumbnailData(page_url
);
570 Send200(connection_id
, data
, "image/png");
572 Send404(connection_id
);
575 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id
) {
576 std::string response
= delegate_
->GetDiscoveryPageHTML();
577 Send200(connection_id
, response
, "text/html; charset=UTF-8");
580 void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
582 const net::HttpServerRequestInfo
& request
) {
586 size_t pos
= request
.path
.find(kPageUrlPrefix
);
588 Send404(connection_id
);
592 std::string page_id
= request
.path
.substr(strlen(kPageUrlPrefix
));
593 DevToolsTarget
* target
= GetTarget(page_id
);
594 scoped_refptr
<DevToolsAgentHost
> agent
=
595 target
? target
->GetAgentHost() : NULL
;
597 Send500(connection_id
, "No such target id: " + page_id
);
601 if (agent
->IsAttached()) {
602 Send500(connection_id
,
603 "Target with given id is being inspected: " + page_id
);
607 DevToolsClientHostImpl
* client_host
= new DevToolsClientHostImpl(
608 thread_
->message_loop(), server_
.get(), connection_id
);
609 connection_to_client_host_ui_
[connection_id
] = client_host
;
611 DevToolsManager::GetInstance()->
612 RegisterDevToolsClientHostFor(agent
, client_host
);
614 AcceptWebSocket(connection_id
, request
);
617 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
619 const std::string
& data
) {
620 ConnectionToClientHostMap::iterator it
=
621 connection_to_client_host_ui_
.find(connection_id
);
622 if (it
== connection_to_client_host_ui_
.end())
625 DevToolsManager
* manager
= DevToolsManager::GetInstance();
626 manager
->DispatchOnInspectorBackend(it
->second
, data
);
629 void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id
) {
630 ConnectionToClientHostMap::iterator it
=
631 connection_to_client_host_ui_
.find(connection_id
);
632 if (it
!= connection_to_client_host_ui_
.end()) {
633 DevToolsClientHostImpl
* client_host
=
634 static_cast<DevToolsClientHostImpl
*>(it
->second
);
635 DevToolsManager::GetInstance()->ClientHostClosing(client_host
);
637 connection_to_client_host_ui_
.erase(connection_id
);
641 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
642 const net::StreamListenSocketFactory
* socket_factory
,
643 const std::string
& frontend_url
,
644 DevToolsHttpHandlerDelegate
* delegate
)
645 : overridden_frontend_url_(frontend_url
),
646 socket_factory_(socket_factory
),
647 delegate_(delegate
) {
648 if (overridden_frontend_url_
.empty())
649 overridden_frontend_url_
= "/devtools/devtools.html";
651 // Balanced in ResetHandlerThreadAndRelease().
655 // Runs on the handler thread
656 void DevToolsHttpHandlerImpl::Init() {
657 server_
= new net::HttpServer(*socket_factory_
.get(), this);
660 // Runs on the handler thread
661 void DevToolsHttpHandlerImpl::Teardown() {
665 // Runs on FILE thread to make sure that it is serialized against
666 // {Start|Stop}HandlerThread and to allow calling pthread_join.
667 void DevToolsHttpHandlerImpl::StopHandlerThread() {
668 if (!thread_
->message_loop())
670 thread_
->message_loop()->PostTask(
672 base::Bind(&DevToolsHttpHandlerImpl::Teardown
, this));
673 // Thread::Stop joins the thread.
677 void DevToolsHttpHandlerImpl::SendJson(int connection_id
,
678 net::HttpStatusCode status_code
,
680 const std::string
& message
) {
684 // Serialize value and message.
685 std::string json_value
;
687 base::JSONWriter::WriteWithOptions(value
,
688 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
691 std::string json_message
;
692 scoped_ptr
<base::Value
> message_object(new base::StringValue(message
));
693 base::JSONWriter::Write(message_object
.get(), &json_message
);
695 net::HttpServerResponseInfo
response(status_code
);
696 response
.SetBody(json_value
+ message
, "application/json; charset=UTF-8");
698 thread_
->message_loop()->PostTask(
700 base::Bind(&net::HttpServer::SendResponse
,
706 void DevToolsHttpHandlerImpl::Send200(int connection_id
,
707 const std::string
& data
,
708 const std::string
& mime_type
) {
711 thread_
->message_loop()->PostTask(
713 base::Bind(&net::HttpServer::Send200
,
720 void DevToolsHttpHandlerImpl::Send404(int connection_id
) {
723 thread_
->message_loop()->PostTask(
725 base::Bind(&net::HttpServer::Send404
, server_
.get(), connection_id
));
728 void DevToolsHttpHandlerImpl::Send500(int connection_id
,
729 const std::string
& message
) {
732 thread_
->message_loop()->PostTask(
734 base::Bind(&net::HttpServer::Send500
, server_
.get(), connection_id
,
738 void DevToolsHttpHandlerImpl::AcceptWebSocket(
740 const net::HttpServerRequestInfo
& request
) {
743 thread_
->message_loop()->PostTask(
745 base::Bind(&net::HttpServer::AcceptWebSocket
, server_
.get(),
746 connection_id
, request
));
749 base::DictionaryValue
* DevToolsHttpHandlerImpl::SerializeTarget(
750 const DevToolsTarget
& target
,
751 const std::string
& host
) {
752 base::DictionaryValue
* dictionary
= new base::DictionaryValue
;
754 std::string id
= target
.GetId();
755 dictionary
->SetString(kTargetIdField
, id
);
756 dictionary
->SetString(kTargetTypeField
, target
.GetType());
757 dictionary
->SetString(kTargetTitleField
,
758 net::EscapeForHTML(target
.GetTitle()));
759 dictionary
->SetString(kTargetDescriptionField
, target
.GetDescription());
761 GURL url
= target
.GetUrl();
762 dictionary
->SetString(kTargetUrlField
, url
.spec());
764 GURL favicon_url
= target
.GetFaviconUrl();
765 if (favicon_url
.is_valid())
766 dictionary
->SetString(kTargetFaviconUrlField
, favicon_url
.spec());
768 if (!delegate_
->GetPageThumbnailData(url
).empty()) {
769 dictionary
->SetString(kTargetThumbnailUrlField
,
770 std::string(kThumbUrlPrefix
) + id
);
773 if (!target
.IsAttached()) {
774 dictionary
->SetString(kTargetWebSocketDebuggerUrlField
,
775 base::StringPrintf("ws://%s%s%s",
779 std::string devtools_frontend_url
= GetFrontendURLInternal(
782 dictionary
->SetString(
783 kTargetDevtoolsFrontendUrlField
, devtools_frontend_url
);
789 } // namespace content