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.
9 #include "base/compiler_specific.h"
10 #include "base/files/file_util.h"
11 #include "base/json/json_writer.h"
12 #include "base/logging.h"
13 #include "base/message_loop/message_loop_proxy.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/threading/thread.h"
17 #include "base/values.h"
18 #include "content/browser/devtools/devtools_manager.h"
19 #include "content/browser/devtools/protocol/devtools_protocol_handler.h"
20 #include "content/browser/devtools/protocol/system_info_handler.h"
21 #include "content/browser/devtools/protocol/tethering_handler.h"
22 #include "content/browser/devtools/protocol/tracing_handler.h"
23 #include "content/common/devtools_messages.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/devtools_agent_host.h"
26 #include "content/public/browser/devtools_http_handler.h"
27 #include "content/public/browser/devtools_http_handler_delegate.h"
28 #include "content/public/browser/devtools_target.h"
29 #include "content/public/common/content_client.h"
30 #include "content/public/common/url_constants.h"
31 #include "content/public/common/user_agent.h"
32 #include "grit/devtools_resources_map.h"
33 #include "net/base/escape.h"
34 #include "net/base/io_buffer.h"
35 #include "net/base/ip_endpoint.h"
36 #include "net/base/net_errors.h"
37 #include "net/server/http_server.h"
38 #include "net/server/http_server_request_info.h"
39 #include "net/server/http_server_response_info.h"
40 #include "net/socket/server_socket.h"
42 #if defined(OS_ANDROID)
43 #include "base/android/build_info.h"
50 const base::FilePath::CharType kDevToolsActivePortFileName
[] =
51 FILE_PATH_LITERAL("DevToolsActivePort");
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 kTargetParentIdField
[] = "parentId";
60 const char kTargetTypeField
[] = "type";
61 const char kTargetTitleField
[] = "title";
62 const char kTargetDescriptionField
[] = "description";
63 const char kTargetUrlField
[] = "url";
64 const char kTargetThumbnailUrlField
[] = "thumbnailUrl";
65 const char kTargetFaviconUrlField
[] = "faviconUrl";
66 const char kTargetWebSocketDebuggerUrlField
[] = "webSocketDebuggerUrl";
67 const char kTargetDevtoolsFrontendUrlField
[] = "devtoolsFrontendUrl";
69 // Maximum write buffer size of devtools http/websocket connectinos.
70 const int32 kSendBufferSizeForDevTools
= 100 * 1024 * 1024; // 100Mb
73 class DevToolsAgentHostClientImpl
;
76 // DevToolsHttpHandlerImpl ---------------------------------------------------
78 class DevToolsHttpHandlerImpl
: public DevToolsHttpHandler
{
80 DevToolsHttpHandlerImpl(scoped_ptr
<ServerSocketFactory
> server_socket_factory
,
81 const std::string
& frontend_url
,
82 DevToolsHttpHandlerDelegate
* delegate
,
83 const base::FilePath
& output_directory
);
84 ~DevToolsHttpHandlerImpl() override
;
86 void OnJsonRequest(int connection_id
,
87 const net::HttpServerRequestInfo
& info
);
88 void OnThumbnailRequest(int connection_id
, const std::string
& target_id
);
89 void OnDiscoveryPageRequest(int connection_id
);
91 void OnWebSocketRequest(int connection_id
,
92 const net::HttpServerRequestInfo
& info
);
93 void OnWebSocketMessage(int connection_id
, const std::string
& data
);
94 void OnClose(int connection_id
);
96 void ServerStarted(base::Thread
* thread
,
97 ServerWrapper
* server_wrapper
,
98 scoped_ptr
<net::IPEndPoint
> ip_address
);
101 // DevToolsHttpHandler implementation.
102 GURL
GetFrontendURL(const std::string
& path
) override
;
104 static void OnTargetListReceivedWeak(
105 base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
107 const std::string
& host
,
108 const DevToolsManagerDelegate::TargetList
& targets
);
109 void OnTargetListReceived(
111 const std::string
& host
,
112 const DevToolsManagerDelegate::TargetList
& targets
);
114 DevToolsTarget
* GetTarget(const std::string
& id
);
116 void SendJson(int connection_id
,
117 net::HttpStatusCode status_code
,
119 const std::string
& message
);
120 void Send200(int connection_id
,
121 const std::string
& data
,
122 const std::string
& mime_type
);
123 void Send404(int connection_id
);
124 void Send500(int connection_id
,
125 const std::string
& message
);
126 void AcceptWebSocket(int connection_id
,
127 const net::HttpServerRequestInfo
& request
);
129 // Returns the front end url without the host at the beginning.
130 std::string
GetFrontendURLInternal(const std::string target_id
,
131 const std::string
& host
);
133 base::DictionaryValue
* SerializeTarget(const DevToolsTarget
& target
,
134 const std::string
& host
);
136 // The thread used by the devtools handler to run server socket.
137 base::Thread
* thread_
;
139 std::string frontend_url_
;
140 ServerWrapper
* server_wrapper_
;
141 scoped_ptr
<net::IPEndPoint
> server_ip_address_
;
142 typedef std::map
<int, DevToolsAgentHostClientImpl
*> ConnectionToClientMap
;
143 ConnectionToClientMap connection_to_client_
;
144 const scoped_ptr
<DevToolsHttpHandlerDelegate
> delegate_
;
145 typedef std::map
<std::string
, DevToolsTarget
*> TargetMap
;
146 TargetMap target_map_
;
147 typedef std::map
<int, BrowserTarget
*> BrowserTargets
;
148 BrowserTargets browser_targets_
;
149 base::WeakPtrFactory
<DevToolsHttpHandlerImpl
> weak_factory_
;
151 DISALLOW_COPY_AND_ASSIGN(DevToolsHttpHandlerImpl
);
154 // ServerWrapper -------------------------------------------------------------
155 // All methods in this class are only called on handler thread.
156 class ServerWrapper
: net::HttpServer::Delegate
{
158 ServerWrapper(base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
159 scoped_ptr
<net::ServerSocket
> socket
,
160 const base::FilePath
& frontend_dir
,
161 bool bundles_resources
);
163 int GetLocalAddress(net::IPEndPoint
* address
);
165 void AcceptWebSocket(int connection_id
,
166 const net::HttpServerRequestInfo
& request
);
167 void SendOverWebSocket(int connection_id
, const std::string
& message
);
168 void SendResponse(int connection_id
,
169 const net::HttpServerResponseInfo
& response
);
170 void Send200(int connection_id
,
171 const std::string
& data
,
172 const std::string
& mime_type
);
173 void Send404(int connection_id
);
174 void Send500(int connection_id
, const std::string
& message
);
175 void Close(int connection_id
);
177 void WriteActivePortToUserProfile(const base::FilePath
& output_directory
);
179 virtual ~ServerWrapper() {}
182 // net::HttpServer::Delegate implementation.
183 void OnConnect(int connection_id
) override
{}
184 void OnHttpRequest(int connection_id
,
185 const net::HttpServerRequestInfo
& info
) override
;
186 void OnWebSocketRequest(int connection_id
,
187 const net::HttpServerRequestInfo
& info
) override
;
188 void OnWebSocketMessage(int connection_id
,
189 const std::string
& data
) override
;
190 void OnClose(int connection_id
) override
;
192 base::WeakPtr
<DevToolsHttpHandlerImpl
> handler_
;
193 scoped_ptr
<net::HttpServer
> server_
;
194 base::FilePath frontend_dir_
;
195 bool bundles_resources_
;
198 ServerWrapper::ServerWrapper(base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
199 scoped_ptr
<net::ServerSocket
> socket
,
200 const base::FilePath
& frontend_dir
,
201 bool bundles_resources
)
203 server_(new net::HttpServer(socket
.Pass(), this)),
204 frontend_dir_(frontend_dir
),
205 bundles_resources_(bundles_resources
) {
208 int ServerWrapper::GetLocalAddress(net::IPEndPoint
* address
) {
209 return server_
->GetLocalAddress(address
);
212 void ServerWrapper::AcceptWebSocket(int connection_id
,
213 const net::HttpServerRequestInfo
& request
) {
214 server_
->SetSendBufferSize(connection_id
, kSendBufferSizeForDevTools
);
215 server_
->AcceptWebSocket(connection_id
, request
);
218 void ServerWrapper::SendOverWebSocket(int connection_id
,
219 const std::string
& message
) {
220 server_
->SendOverWebSocket(connection_id
, message
);
223 void ServerWrapper::SendResponse(int connection_id
,
224 const net::HttpServerResponseInfo
& response
) {
225 server_
->SendResponse(connection_id
, response
);
228 void ServerWrapper::Send200(int connection_id
,
229 const std::string
& data
,
230 const std::string
& mime_type
) {
231 server_
->Send200(connection_id
, data
, mime_type
);
234 void ServerWrapper::Send404(int connection_id
) {
235 server_
->Send404(connection_id
);
238 void ServerWrapper::Send500(int connection_id
,
239 const std::string
& message
) {
240 server_
->Send500(connection_id
, message
);
243 void ServerWrapper::Close(int connection_id
) {
244 server_
->Close(connection_id
);
247 // Thread and ServerWrapper lifetime management ------------------------------
249 void TerminateOnUI(base::Thread
* thread
, ServerWrapper
* server_wrapper
) {
250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
251 if (server_wrapper
) {
253 thread
->message_loop()->DeleteSoon(FROM_HERE
, server_wrapper
);
256 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE
, thread
);
260 void ServerStartedOnUI(
261 base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
262 base::Thread
* thread
,
263 ServerWrapper
* server_wrapper
,
264 scoped_ptr
<net::IPEndPoint
> ip_address
) {
265 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
266 if (handler
&& thread
&& server_wrapper
) {
267 handler
->ServerStarted(thread
, server_wrapper
, ip_address
.Pass());
269 TerminateOnUI(thread
, server_wrapper
);
273 void StartServerOnHandlerThread(
274 base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
275 base::Thread
* thread
,
276 scoped_ptr
<DevToolsHttpHandler::ServerSocketFactory
> server_socket_factory
,
277 const base::FilePath
& output_directory
,
278 const base::FilePath
& frontend_dir
,
279 bool bundles_resources
) {
280 DCHECK_EQ(thread
->message_loop(), base::MessageLoop::current());
281 ServerWrapper
* server_wrapper
= nullptr;
282 scoped_ptr
<net::ServerSocket
> server_socket
=
283 server_socket_factory
->CreateAndListen();
284 scoped_ptr
<net::IPEndPoint
> ip_address(new net::IPEndPoint
);
286 server_wrapper
= new ServerWrapper(handler
, server_socket
.Pass(),
287 frontend_dir
, bundles_resources
);
288 if (!output_directory
.empty())
289 server_wrapper
->WriteActivePortToUserProfile(output_directory
);
291 if (server_wrapper
->GetLocalAddress(ip_address
.get()) != net::OK
)
295 LOG(ERROR
) << "Cannot start http server for devtools. Stop devtools.";
297 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
298 base::Bind(&ServerStartedOnUI
,
299 handler
, thread
, server_wrapper
, base::Passed(&ip_address
)));
302 void StartServerOnFile(
303 base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
304 scoped_ptr
<DevToolsHttpHandler::ServerSocketFactory
> server_socket_factory
,
305 const base::FilePath
& output_directory
,
306 const base::FilePath
& frontend_dir
,
307 bool bundles_resources
) {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
309 scoped_ptr
<base::Thread
> thread(new base::Thread(kDevToolsHandlerThreadName
));
310 base::Thread::Options options
;
311 options
.message_loop_type
= base::MessageLoop::TYPE_IO
;
312 if (thread
->StartWithOptions(options
)) {
313 base::MessageLoop
* message_loop
= thread
->message_loop();
314 message_loop
->PostTask(FROM_HERE
,
315 base::Bind(&StartServerOnHandlerThread
,
317 base::Unretained(thread
.release()),
318 base::Passed(&server_socket_factory
),
325 // DevToolsAgentHostClientImpl -----------------------------------------------
326 // An internal implementation of DevToolsAgentHostClient that delegates
327 // messages sent to a DebuggerShell instance.
328 class DevToolsAgentHostClientImpl
: public DevToolsAgentHostClient
{
330 DevToolsAgentHostClientImpl(base::MessageLoop
* message_loop
,
331 ServerWrapper
* server_wrapper
,
333 DevToolsAgentHost
* agent_host
)
334 : message_loop_(message_loop
),
335 server_wrapper_(server_wrapper
),
336 connection_id_(connection_id
),
337 agent_host_(agent_host
) {
338 agent_host_
->AttachClient(this);
341 ~DevToolsAgentHostClientImpl() override
{
342 if (agent_host_
.get())
343 agent_host_
->DetachClient();
346 void AgentHostClosed(DevToolsAgentHost
* agent_host
,
347 bool replaced_with_another_client
) override
{
348 DCHECK(agent_host
== agent_host_
.get());
350 base::Callback
<void(const std::string
&)> raw_message_callback(
351 base::Bind(&DevToolsAgentHostClientImpl::DispatchProtocolMessage
,
352 base::Unretained(this), base::Unretained(agent_host
)));
353 devtools::inspector::Client
inspector(raw_message_callback
);
354 inspector
.Detached(devtools::inspector::DetachedParams::Create()
355 ->set_reason(replaced_with_another_client
?
356 "replaced_with_devtools" : "target_closed"));
358 agent_host_
= nullptr;
359 message_loop_
->PostTask(
361 base::Bind(&ServerWrapper::Close
,
362 base::Unretained(server_wrapper_
),
366 void DispatchProtocolMessage(DevToolsAgentHost
* agent_host
,
367 const std::string
& message
) override
{
368 DCHECK(agent_host
== agent_host_
.get());
369 message_loop_
->PostTask(
371 base::Bind(&ServerWrapper::SendOverWebSocket
,
372 base::Unretained(server_wrapper_
),
377 void OnMessage(const std::string
& message
) {
378 if (agent_host_
.get())
379 agent_host_
->DispatchProtocolMessage(message
);
383 base::MessageLoop
* const message_loop_
;
384 ServerWrapper
* const server_wrapper_
;
385 const int connection_id_
;
386 scoped_refptr
<DevToolsAgentHost
> agent_host_
;
389 static bool TimeComparator(const DevToolsTarget
* target1
,
390 const DevToolsTarget
* target2
) {
391 return target1
->GetLastActivityTime() > target2
->GetLastActivityTime();
394 // BrowserTarget -------------------------------------------------------------
396 class BrowserTarget
{
398 BrowserTarget(base::MessageLoop
* message_loop
,
399 ServerWrapper
* server_wrapper
,
400 DevToolsHttpHandlerDelegate
* delegate
,
402 : message_loop_(message_loop
),
403 server_wrapper_(server_wrapper
),
404 connection_id_(connection_id
),
405 system_info_handler_(new devtools::system_info::SystemInfoHandler()),
406 tethering_handler_(new devtools::tethering::TetheringHandler(
407 delegate
, message_loop
->message_loop_proxy())),
408 tracing_handler_(new devtools::tracing::TracingHandler(
409 devtools::tracing::TracingHandler::Browser
)),
410 protocol_handler_(new DevToolsProtocolHandler(
411 true /* handle_generic_errors */,
412 base::Bind(&BrowserTarget::Respond
, base::Unretained(this)))) {
413 DevToolsProtocolDispatcher
* dispatcher
= protocol_handler_
->dispatcher();
414 dispatcher
->SetSystemInfoHandler(system_info_handler_
.get());
415 dispatcher
->SetTetheringHandler(tethering_handler_
.get());
416 dispatcher
->SetTracingHandler(tracing_handler_
.get());
419 void HandleMessage(const std::string
& message
) {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
421 std::string error_response
;
422 scoped_ptr
<base::DictionaryValue
> command
=
423 protocol_handler_
->ParseCommand(message
);
425 bool result
= protocol_handler_
->HandleCommand(command
.Pass());
430 void Respond(const std::string
& message
) {
431 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
432 message_loop_
->PostTask(
434 base::Bind(&ServerWrapper::SendOverWebSocket
,
435 base::Unretained(server_wrapper_
),
441 base::MessageLoop
* const message_loop_
;
442 ServerWrapper
* const server_wrapper_
;
443 const int connection_id_
;
444 scoped_ptr
<devtools::system_info::SystemInfoHandler
> system_info_handler_
;
445 scoped_ptr
<devtools::tethering::TetheringHandler
> tethering_handler_
;
446 scoped_ptr
<devtools::tracing::TracingHandler
> tracing_handler_
;
447 scoped_ptr
<DevToolsProtocolHandler
> protocol_handler_
;
452 // DevToolsHttpHandler -------------------------------------------------------
455 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
456 const std::string
& version
) {
457 return devtools::IsSupportedProtocolVersion(version
);
461 int DevToolsHttpHandler::GetFrontendResourceId(const std::string
& name
) {
462 for (size_t i
= 0; i
< kDevtoolsResourcesSize
; ++i
) {
463 if (name
== kDevtoolsResources
[i
].name
)
464 return kDevtoolsResources
[i
].value
;
470 DevToolsHttpHandler
* DevToolsHttpHandler::Start(
471 scoped_ptr
<ServerSocketFactory
> server_socket_factory
,
472 const std::string
& frontend_url
,
473 DevToolsHttpHandlerDelegate
* delegate
,
474 const base::FilePath
& active_port_output_directory
) {
475 DevToolsHttpHandlerImpl
* http_handler
=
476 new DevToolsHttpHandlerImpl(server_socket_factory
.Pass(),
479 active_port_output_directory
);
483 // DevToolsHttpHandler::ServerSocketFactory ----------------------------------
485 DevToolsHttpHandler::ServerSocketFactory::ServerSocketFactory(
486 const std::string
& address
,
494 DevToolsHttpHandler::ServerSocketFactory::~ServerSocketFactory() {
497 scoped_ptr
<net::ServerSocket
>
498 DevToolsHttpHandler::ServerSocketFactory::CreateAndListen() const {
499 scoped_ptr
<net::ServerSocket
> socket
= Create();
501 socket
->ListenWithAddressAndPort(address_
, port_
, backlog_
) == net::OK
) {
502 return socket
.Pass();
504 return scoped_ptr
<net::ServerSocket
>();
507 // DevToolsHttpHandlerImpl ---------------------------------------------------
509 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
510 TerminateOnUI(thread_
, server_wrapper_
);
511 STLDeleteValues(&target_map_
);
512 STLDeleteValues(&browser_targets_
);
513 STLDeleteValues(&connection_to_client_
);
516 GURL
DevToolsHttpHandlerImpl::GetFrontendURL(const std::string
& path
) {
517 if (!server_ip_address_
)
519 return GURL(std::string("http://") + server_ip_address_
->ToString() +
520 (path
.empty() ? frontend_url_
: path
));
523 static std::string
PathWithoutParams(const std::string
& path
) {
524 size_t query_position
= path
.find("?");
525 if (query_position
!= std::string::npos
)
526 return path
.substr(0, query_position
);
530 static std::string
GetMimeType(const std::string
& filename
) {
531 if (EndsWith(filename
, ".html", false)) {
533 } else if (EndsWith(filename
, ".css", false)) {
535 } else if (EndsWith(filename
, ".js", false)) {
536 return "application/javascript";
537 } else if (EndsWith(filename
, ".png", false)) {
539 } else if (EndsWith(filename
, ".gif", false)) {
541 } else if (EndsWith(filename
, ".json", false)) {
542 return "application/json";
544 LOG(ERROR
) << "GetMimeType doesn't know mime type for: "
546 << " text/plain will be returned";
551 void ServerWrapper::OnHttpRequest(int connection_id
,
552 const net::HttpServerRequestInfo
& info
) {
553 server_
->SetSendBufferSize(connection_id
, kSendBufferSizeForDevTools
);
555 if (info
.path
.find("/json") == 0) {
556 BrowserThread::PostTask(
559 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequest
,
566 if (info
.path
.find(kThumbUrlPrefix
) == 0) {
567 // Thumbnail request.
568 const std::string target_id
= info
.path
.substr(strlen(kThumbUrlPrefix
));
569 BrowserThread::PostTask(
572 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequest
,
579 if (info
.path
== "" || info
.path
== "/") {
580 // Discovery page request.
581 BrowserThread::PostTask(
584 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequest
,
590 if (info
.path
.find("/devtools/") != 0) {
591 server_
->Send404(connection_id
);
595 std::string filename
= PathWithoutParams(info
.path
.substr(10));
596 std::string mime_type
= GetMimeType(filename
);
598 if (!frontend_dir_
.empty()) {
599 base::FilePath path
= frontend_dir_
.AppendASCII(filename
);
601 base::ReadFileToString(path
, &data
);
602 server_
->Send200(connection_id
, data
, mime_type
);
605 if (bundles_resources_
) {
606 int resource_id
= DevToolsHttpHandler::GetFrontendResourceId(filename
);
607 if (resource_id
!= -1) {
608 base::StringPiece data
= GetContentClient()->GetDataResource(
609 resource_id
, ui::SCALE_FACTOR_NONE
);
610 server_
->Send200(connection_id
, data
.as_string(), mime_type
);
614 server_
->Send404(connection_id
);
617 void ServerWrapper::OnWebSocketRequest(
619 const net::HttpServerRequestInfo
& request
) {
620 BrowserThread::PostTask(
624 &DevToolsHttpHandlerImpl::OnWebSocketRequest
,
630 void ServerWrapper::OnWebSocketMessage(int connection_id
,
631 const std::string
& data
) {
632 BrowserThread::PostTask(
636 &DevToolsHttpHandlerImpl::OnWebSocketMessage
,
642 void ServerWrapper::OnClose(int connection_id
) {
643 BrowserThread::PostTask(
647 &DevToolsHttpHandlerImpl::OnClose
,
652 std::string
DevToolsHttpHandlerImpl::GetFrontendURLInternal(
653 const std::string id
,
654 const std::string
& host
) {
655 return base::StringPrintf(
657 frontend_url_
.c_str(),
658 frontend_url_
.find("?") == std::string::npos
? "?" : "&",
664 static bool ParseJsonPath(
665 const std::string
& path
,
666 std::string
* command
,
667 std::string
* target_id
) {
669 // Fall back to list in case of empty query.
675 if (path
.find("/") != 0) {
676 // Malformed command.
679 *command
= path
.substr(1);
681 size_t separator_pos
= command
->find("/");
682 if (separator_pos
!= std::string::npos
) {
683 *target_id
= command
->substr(separator_pos
+ 1);
684 *command
= command
->substr(0, separator_pos
);
689 void DevToolsHttpHandlerImpl::OnJsonRequest(
691 const net::HttpServerRequestInfo
& info
) {
693 std::string path
= info
.path
.substr(5);
695 // Trim fragment and query
697 size_t query_pos
= path
.find("?");
698 if (query_pos
!= std::string::npos
) {
699 query
= path
.substr(query_pos
+ 1);
700 path
= path
.substr(0, query_pos
);
703 size_t fragment_pos
= path
.find("#");
704 if (fragment_pos
!= std::string::npos
)
705 path
= path
.substr(0, fragment_pos
);
708 std::string target_id
;
709 if (!ParseJsonPath(path
, &command
, &target_id
)) {
710 SendJson(connection_id
,
713 "Malformed query: " + info
.path
);
717 if (command
== "version") {
718 base::DictionaryValue version
;
719 version
.SetString("Protocol-Version", devtools::kProtocolVersion
);
720 version
.SetString("WebKit-Version", GetWebKitVersion());
721 version
.SetString("Browser", GetContentClient()->GetProduct());
722 version
.SetString("User-Agent", GetContentClient()->GetUserAgent());
723 #if defined(OS_ANDROID)
724 version
.SetString("Android-Package",
725 base::android::BuildInfo::GetInstance()->package_name());
727 SendJson(connection_id
, net::HTTP_OK
, &version
, std::string());
731 if (command
== "list") {
732 std::string host
= info
.headers
["host"];
733 DevToolsManagerDelegate
* manager_delegate
=
734 DevToolsManager::GetInstance()->delegate();
735 if (manager_delegate
) {
736 manager_delegate
->EnumerateTargets(
737 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceivedWeak
,
738 weak_factory_
.GetWeakPtr(), connection_id
, host
));
740 DevToolsManagerDelegate::TargetList empty_list
;
741 OnTargetListReceived(connection_id
, host
, empty_list
);
746 if (command
== "new") {
747 GURL
url(net::UnescapeURLComponent(
748 query
, net::UnescapeRule::URL_SPECIAL_CHARS
));
750 url
= GURL(url::kAboutBlankURL
);
751 scoped_ptr
<DevToolsTarget
> target
;
752 DevToolsManagerDelegate
* manager_delegate
=
753 DevToolsManager::GetInstance()->delegate();
754 if (manager_delegate
)
755 target
= manager_delegate
->CreateNewTarget(url
);
757 SendJson(connection_id
,
758 net::HTTP_INTERNAL_SERVER_ERROR
,
760 "Could not create new page");
763 std::string host
= info
.headers
["host"];
764 scoped_ptr
<base::DictionaryValue
> dictionary(
765 SerializeTarget(*target
.get(), host
));
766 SendJson(connection_id
, net::HTTP_OK
, dictionary
.get(), std::string());
767 const std::string target_id
= target
->GetId();
768 target_map_
[target_id
] = target
.release();
772 if (command
== "activate" || command
== "close") {
773 DevToolsTarget
* target
= GetTarget(target_id
);
775 SendJson(connection_id
,
778 "No such target id: " + target_id
);
782 if (command
== "activate") {
783 if (target
->Activate()) {
784 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target activated");
786 SendJson(connection_id
,
787 net::HTTP_INTERNAL_SERVER_ERROR
,
789 "Could not activate target id: " + target_id
);
794 if (command
== "close") {
795 if (target
->Close()) {
796 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target is closing");
798 SendJson(connection_id
,
799 net::HTTP_INTERNAL_SERVER_ERROR
,
801 "Could not close target id: " + target_id
);
806 SendJson(connection_id
,
809 "Unknown command: " + command
);
814 void DevToolsHttpHandlerImpl::OnTargetListReceivedWeak(
815 base::WeakPtr
<DevToolsHttpHandlerImpl
> handler
,
817 const std::string
& host
,
818 const DevToolsManagerDelegate::TargetList
& targets
) {
820 handler
->OnTargetListReceived(connection_id
, host
, targets
);
822 STLDeleteContainerPointers(targets
.begin(), targets
.end());
826 void DevToolsHttpHandlerImpl::OnTargetListReceived(
828 const std::string
& host
,
829 const DevToolsManagerDelegate::TargetList
& targets
) {
830 DevToolsManagerDelegate::TargetList sorted_targets
= targets
;
831 std::sort(sorted_targets
.begin(), sorted_targets
.end(), TimeComparator
);
833 STLDeleteValues(&target_map_
);
834 base::ListValue list_value
;
835 for (DevToolsManagerDelegate::TargetList::const_iterator it
=
836 sorted_targets
.begin(); it
!= sorted_targets
.end(); ++it
) {
837 DevToolsTarget
* target
= *it
;
838 target_map_
[target
->GetId()] = target
;
839 list_value
.Append(SerializeTarget(*target
, host
));
841 SendJson(connection_id
, net::HTTP_OK
, &list_value
, std::string());
844 DevToolsTarget
* DevToolsHttpHandlerImpl::GetTarget(const std::string
& id
) {
845 TargetMap::const_iterator it
= target_map_
.find(id
);
846 if (it
== target_map_
.end())
851 void DevToolsHttpHandlerImpl::OnThumbnailRequest(
852 int connection_id
, const std::string
& target_id
) {
853 DevToolsTarget
* target
= GetTarget(target_id
);
856 page_url
= target
->GetURL();
857 DevToolsManagerDelegate
* manager_delegate
=
858 DevToolsManager::GetInstance()->delegate();
860 manager_delegate
? manager_delegate
->GetPageThumbnailData(page_url
) : "";
862 Send200(connection_id
, data
, "image/png");
864 Send404(connection_id
);
867 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequest(int connection_id
) {
868 std::string response
= delegate_
->GetDiscoveryPageHTML();
869 Send200(connection_id
, response
, "text/html; charset=UTF-8");
872 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
874 const net::HttpServerRequestInfo
& request
) {
878 std::string browser_prefix
= "/devtools/browser";
879 size_t browser_pos
= request
.path
.find(browser_prefix
);
880 if (browser_pos
== 0) {
881 browser_targets_
[connection_id
] = new BrowserTarget(thread_
->message_loop(),
885 AcceptWebSocket(connection_id
, request
);
889 size_t pos
= request
.path
.find(kPageUrlPrefix
);
891 Send404(connection_id
);
895 std::string page_id
= request
.path
.substr(strlen(kPageUrlPrefix
));
896 DevToolsTarget
* target
= GetTarget(page_id
);
897 scoped_refptr
<DevToolsAgentHost
> agent
=
898 target
? target
->GetAgentHost() : NULL
;
900 Send500(connection_id
, "No such target id: " + page_id
);
904 if (agent
->IsAttached()) {
905 Send500(connection_id
,
906 "Target with given id is being inspected: " + page_id
);
910 DevToolsAgentHostClientImpl
* client_host
= new DevToolsAgentHostClientImpl(
911 thread_
->message_loop(), server_wrapper_
, connection_id
, agent
.get());
912 connection_to_client_
[connection_id
] = client_host
;
914 AcceptWebSocket(connection_id
, request
);
917 void DevToolsHttpHandlerImpl::OnWebSocketMessage(
919 const std::string
& data
) {
920 BrowserTargets::iterator browser_it
= browser_targets_
.find(connection_id
);
921 if (browser_it
!= browser_targets_
.end()) {
922 browser_it
->second
->HandleMessage(data
);
926 ConnectionToClientMap::iterator it
=
927 connection_to_client_
.find(connection_id
);
928 if (it
!= connection_to_client_
.end())
929 it
->second
->OnMessage(data
);
932 void DevToolsHttpHandlerImpl::OnClose(int connection_id
) {
933 BrowserTargets::iterator browser_it
= browser_targets_
.find(connection_id
);
934 if (browser_it
!= browser_targets_
.end()) {
935 delete browser_it
->second
;
936 browser_targets_
.erase(connection_id
);
940 ConnectionToClientMap::iterator it
=
941 connection_to_client_
.find(connection_id
);
942 if (it
!= connection_to_client_
.end()) {
944 connection_to_client_
.erase(connection_id
);
948 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
949 scoped_ptr
<ServerSocketFactory
> server_socket_factory
,
950 const std::string
& frontend_url
,
951 DevToolsHttpHandlerDelegate
* delegate
,
952 const base::FilePath
& output_directory
)
954 frontend_url_(frontend_url
),
955 server_wrapper_(nullptr),
957 weak_factory_(this) {
958 if (frontend_url_
.empty())
959 frontend_url_
= "/devtools/inspector.html";
961 BrowserThread::PostTask(
962 BrowserThread::FILE, FROM_HERE
,
963 base::Bind(&StartServerOnFile
,
964 weak_factory_
.GetWeakPtr(),
965 base::Passed(&server_socket_factory
),
967 delegate_
->GetDebugFrontendDir(),
968 delegate_
->BundlesFrontendResources()));
971 void DevToolsHttpHandlerImpl::ServerStarted(
972 base::Thread
* thread
,
973 ServerWrapper
* server_wrapper
,
974 scoped_ptr
<net::IPEndPoint
> ip_address
) {
976 server_wrapper_
= server_wrapper
;
977 server_ip_address_
.swap(ip_address
);
980 void ServerWrapper::WriteActivePortToUserProfile(
981 const base::FilePath
& output_directory
) {
982 DCHECK(!output_directory
.empty());
983 net::IPEndPoint endpoint
;
985 if ((err
= server_
->GetLocalAddress(&endpoint
)) != net::OK
) {
986 LOG(ERROR
) << "Error " << err
<< " getting local address";
990 // Write this port to a well-known file in the profile directory
991 // so Telemetry can pick it up.
992 base::FilePath path
= output_directory
.Append(kDevToolsActivePortFileName
);
993 std::string port_string
= base::IntToString(endpoint
.port());
994 if (base::WriteFile(path
, port_string
.c_str(), port_string
.length()) < 0) {
995 LOG(ERROR
) << "Error writing DevTools active port to file";
999 void DevToolsHttpHandlerImpl::SendJson(int connection_id
,
1000 net::HttpStatusCode status_code
,
1002 const std::string
& message
) {
1006 // Serialize value and message.
1007 std::string json_value
;
1009 base::JSONWriter::WriteWithOptions(value
,
1010 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
1013 std::string json_message
;
1014 scoped_ptr
<base::Value
> message_object(new base::StringValue(message
));
1015 base::JSONWriter::Write(message_object
.get(), &json_message
);
1017 net::HttpServerResponseInfo
response(status_code
);
1018 response
.SetBody(json_value
+ message
, "application/json; charset=UTF-8");
1020 thread_
->message_loop()->PostTask(
1022 base::Bind(&ServerWrapper::SendResponse
,
1023 base::Unretained(server_wrapper_
),
1028 void DevToolsHttpHandlerImpl::Send200(int connection_id
,
1029 const std::string
& data
,
1030 const std::string
& mime_type
) {
1033 thread_
->message_loop()->PostTask(
1035 base::Bind(&ServerWrapper::Send200
,
1036 base::Unretained(server_wrapper_
),
1042 void DevToolsHttpHandlerImpl::Send404(int connection_id
) {
1045 thread_
->message_loop()->PostTask(
1047 base::Bind(&ServerWrapper::Send404
,
1048 base::Unretained(server_wrapper_
),
1052 void DevToolsHttpHandlerImpl::Send500(int connection_id
,
1053 const std::string
& message
) {
1056 thread_
->message_loop()->PostTask(
1058 base::Bind(&ServerWrapper::Send500
,
1059 base::Unretained(server_wrapper_
),
1064 void DevToolsHttpHandlerImpl::AcceptWebSocket(
1066 const net::HttpServerRequestInfo
& request
) {
1069 thread_
->message_loop()->PostTask(
1071 base::Bind(&ServerWrapper::AcceptWebSocket
,
1072 base::Unretained(server_wrapper_
),
1077 base::DictionaryValue
* DevToolsHttpHandlerImpl::SerializeTarget(
1078 const DevToolsTarget
& target
,
1079 const std::string
& host
) {
1080 base::DictionaryValue
* dictionary
= new base::DictionaryValue
;
1082 std::string id
= target
.GetId();
1083 dictionary
->SetString(kTargetIdField
, id
);
1084 std::string parent_id
= target
.GetParentId();
1085 if (!parent_id
.empty())
1086 dictionary
->SetString(kTargetParentIdField
, parent_id
);
1087 dictionary
->SetString(kTargetTypeField
, target
.GetType());
1088 dictionary
->SetString(kTargetTitleField
,
1089 net::EscapeForHTML(target
.GetTitle()));
1090 dictionary
->SetString(kTargetDescriptionField
, target
.GetDescription());
1092 GURL url
= target
.GetURL();
1093 dictionary
->SetString(kTargetUrlField
, url
.spec());
1095 GURL favicon_url
= target
.GetFaviconURL();
1096 if (favicon_url
.is_valid())
1097 dictionary
->SetString(kTargetFaviconUrlField
, favicon_url
.spec());
1099 DevToolsManagerDelegate
* manager_delegate
=
1100 DevToolsManager::GetInstance()->delegate();
1101 if (manager_delegate
&&
1102 !manager_delegate
->GetPageThumbnailData(url
).empty()) {
1103 dictionary
->SetString(kTargetThumbnailUrlField
,
1104 std::string(kThumbUrlPrefix
) + id
);
1107 if (!target
.IsAttached()) {
1108 dictionary
->SetString(kTargetWebSocketDebuggerUrlField
,
1109 base::StringPrintf("ws://%s%s%s",
1113 std::string devtools_frontend_url
= GetFrontendURLInternal(
1116 dictionary
->SetString(
1117 kTargetDevtoolsFrontendUrlField
, devtools_frontend_url
);
1123 } // namespace content