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/strings/string_number_conversions.h"
18 #include "base/threading/thread.h"
19 #include "base/values.h"
20 #include "content/browser/devtools/devtools_browser_target.h"
21 #include "content/browser/devtools/devtools_protocol.h"
22 #include "content/browser/devtools/devtools_protocol_constants.h"
23 #include "content/browser/devtools/devtools_system_info_handler.h"
24 #include "content/browser/devtools/devtools_tracing_handler.h"
25 #include "content/browser/devtools/tethering_handler.h"
26 #include "content/common/devtools_messages.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/devtools_agent_host.h"
29 #include "content/public/browser/devtools_client_host.h"
30 #include "content/public/browser/devtools_http_handler_delegate.h"
31 #include "content/public/browser/devtools_manager.h"
32 #include "content/public/browser/devtools_target.h"
33 #include "content/public/common/content_client.h"
34 #include "content/public/common/url_constants.h"
35 #include "content/public/common/user_agent.h"
36 #include "content/public/common/user_agent.h"
37 #include "grit/devtools_resources_map.h"
38 #include "net/base/escape.h"
39 #include "net/base/io_buffer.h"
40 #include "net/base/ip_endpoint.h"
41 #include "net/base/net_errors.h"
42 #include "net/server/http_server_request_info.h"
43 #include "net/server/http_server_response_info.h"
45 #if defined(OS_ANDROID)
46 #include "base/android/build_info.h"
53 const base::FilePath::CharType kDevToolsActivePortFileName
[] =
54 FILE_PATH_LITERAL("DevToolsActivePort");
56 const char kDevToolsHandlerThreadName
[] = "Chrome_DevToolsHandlerThread";
58 const char kThumbUrlPrefix
[] = "/thumb/";
59 const char kPageUrlPrefix
[] = "/devtools/page/";
61 const char kTargetIdField
[] = "id";
62 const char kTargetParentIdField
[] = "parentId";
63 const char kTargetTypeField
[] = "type";
64 const char kTargetTitleField
[] = "title";
65 const char kTargetDescriptionField
[] = "description";
66 const char kTargetUrlField
[] = "url";
67 const char kTargetThumbnailUrlField
[] = "thumbnailUrl";
68 const char kTargetFaviconUrlField
[] = "faviconUrl";
69 const char kTargetWebSocketDebuggerUrlField
[] = "webSocketDebuggerUrl";
70 const char kTargetDevtoolsFrontendUrlField
[] = "devtoolsFrontendUrl";
72 // An internal implementation of DevToolsClientHost that delegates
73 // messages sent for DevToolsClient to a DebuggerShell instance.
74 class DevToolsClientHostImpl
: public DevToolsClientHost
{
76 DevToolsClientHostImpl(base::MessageLoop
* message_loop
,
77 net::HttpServer
* server
,
79 : message_loop_(message_loop
),
81 connection_id_(connection_id
),
83 detach_reason_("target_closed") {}
85 virtual ~DevToolsClientHostImpl() {}
87 // DevToolsClientHost interface
88 virtual void InspectedContentsClosing() OVERRIDE
{
93 base::DictionaryValue notification
;
94 notification
.SetString(
95 devtools::Inspector::detached::kParamReason
, detach_reason_
);
96 std::string response
= DevToolsProtocol::CreateNotification(
97 devtools::Inspector::detached::kName
,
98 notification
.DeepCopy())->Serialize();
99 message_loop_
->PostTask(
101 base::Bind(&net::HttpServer::SendOverWebSocket
,
106 message_loop_
->PostTask(
108 base::Bind(&net::HttpServer::Close
, server_
, connection_id_
));
111 virtual void DispatchOnInspectorFrontend(const std::string
& data
) OVERRIDE
{
112 message_loop_
->PostTask(
114 base::Bind(&net::HttpServer::SendOverWebSocket
,
120 virtual void ReplacedWithAnotherClient() OVERRIDE
{
121 detach_reason_
= "replaced_with_devtools";
125 base::MessageLoop
* message_loop_
;
126 net::HttpServer
* server_
;
129 std::string detach_reason_
;
132 static bool TimeComparator(const DevToolsTarget
* target1
,
133 const DevToolsTarget
* target2
) {
134 return target1
->GetLastActivityTime() > target2
->GetLastActivityTime();
140 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
141 const std::string
& version
) {
142 return devtools::IsSupportedProtocolVersion(version
);
146 int DevToolsHttpHandler::GetFrontendResourceId(const std::string
& name
) {
147 for (size_t i
= 0; i
< kDevtoolsResourcesSize
; ++i
) {
148 if (name
== kDevtoolsResources
[i
].name
)
149 return kDevtoolsResources
[i
].value
;
155 DevToolsHttpHandler
* DevToolsHttpHandler::Start(
156 const net::StreamListenSocketFactory
* socket_factory
,
157 const std::string
& frontend_url
,
158 DevToolsHttpHandlerDelegate
* delegate
,
159 const base::FilePath
& active_port_output_directory
) {
160 DevToolsHttpHandlerImpl
* http_handler
=
161 new DevToolsHttpHandlerImpl(socket_factory
,
164 active_port_output_directory
);
165 http_handler
->Start();
169 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
171 // Stop() must be called prior to destruction.
172 DCHECK(server_
.get() == NULL
);
173 DCHECK(thread_
.get() == NULL
);
174 STLDeleteValues(&target_map_
);
177 void DevToolsHttpHandlerImpl::Start() {
180 thread_
.reset(new base::Thread(kDevToolsHandlerThreadName
));
181 BrowserThread::PostTask(
182 BrowserThread::FILE, FROM_HERE
,
183 base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread
, this));
186 // Runs on FILE thread.
187 void DevToolsHttpHandlerImpl::StartHandlerThread() {
188 base::Thread::Options options
;
189 options
.message_loop_type
= base::MessageLoop::TYPE_IO
;
190 if (!thread_
->StartWithOptions(options
)) {
191 BrowserThread::PostTask(
192 BrowserThread::UI
, FROM_HERE
,
193 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread
, this));
197 thread_
->message_loop()->PostTask(
199 base::Bind(&DevToolsHttpHandlerImpl::Init
, this));
202 void DevToolsHttpHandlerImpl::ResetHandlerThread() {
206 void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
207 ResetHandlerThread();
211 void DevToolsHttpHandlerImpl::Stop() {
214 BrowserThread::PostTaskAndReply(
215 BrowserThread::FILE, FROM_HERE
,
216 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread
, this),
217 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease
, this));
220 GURL
DevToolsHttpHandlerImpl::GetFrontendURL() {
221 net::IPEndPoint ip_address
;
222 if (server_
->GetLocalAddress(&ip_address
))
224 return GURL(std::string("http://") + ip_address
.ToString() + frontend_url_
);
227 static std::string
PathWithoutParams(const std::string
& path
) {
228 size_t query_position
= path
.find("?");
229 if (query_position
!= std::string::npos
)
230 return path
.substr(0, query_position
);
234 static std::string
GetMimeType(const std::string
& filename
) {
235 if (EndsWith(filename
, ".html", false)) {
237 } else if (EndsWith(filename
, ".css", false)) {
239 } else if (EndsWith(filename
, ".js", false)) {
240 return "application/javascript";
241 } else if (EndsWith(filename
, ".png", false)) {
243 } else if (EndsWith(filename
, ".gif", false)) {
245 } else if (EndsWith(filename
, ".json", false)) {
246 return "application/json";
248 LOG(ERROR
) << "GetMimeType doesn't know mime type for: "
250 << " text/plain will be returned";
255 void DevToolsHttpHandlerImpl::OnHttpRequest(
257 const net::HttpServerRequestInfo
& info
) {
258 if (info
.path
.find("/json") == 0) {
259 BrowserThread::PostTask(
262 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI
,
269 if (info
.path
.find(kThumbUrlPrefix
) == 0) {
270 // Thumbnail request.
271 const std::string target_id
= info
.path
.substr(strlen(kThumbUrlPrefix
));
272 DevToolsTarget
* target
= GetTarget(target_id
);
275 page_url
= target
->GetURL();
276 BrowserThread::PostTask(
279 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI
,
286 if (info
.path
== "" || info
.path
== "/") {
287 // Discovery page request.
288 BrowserThread::PostTask(
291 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI
,
297 if (info
.path
.find("/devtools/") != 0) {
298 server_
->Send404(connection_id
);
302 std::string filename
= PathWithoutParams(info
.path
.substr(10));
303 std::string mime_type
= GetMimeType(filename
);
305 base::FilePath frontend_dir
= delegate_
->GetDebugFrontendDir();
306 if (!frontend_dir
.empty()) {
307 base::FilePath path
= frontend_dir
.AppendASCII(filename
);
309 base::ReadFileToString(path
, &data
);
310 server_
->Send200(connection_id
, data
, mime_type
);
313 if (delegate_
->BundlesFrontendResources()) {
314 int resource_id
= DevToolsHttpHandler::GetFrontendResourceId(filename
);
315 if (resource_id
!= -1) {
316 base::StringPiece data
= GetContentClient()->GetDataResource(
317 resource_id
, ui::SCALE_FACTOR_NONE
);
318 server_
->Send200(connection_id
, data
.as_string(), mime_type
);
322 server_
->Send404(connection_id
);
325 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
327 const net::HttpServerRequestInfo
& request
) {
328 std::string browser_prefix
= "/devtools/browser";
329 size_t browser_pos
= request
.path
.find(browser_prefix
);
330 if (browser_pos
== 0) {
331 scoped_refptr
<DevToolsBrowserTarget
> browser_target
=
332 new DevToolsBrowserTarget(server_
.get(), connection_id
);
333 browser_target
->RegisterDomainHandler(
334 devtools::Tracing::kName
,
335 new DevToolsTracingHandler(DevToolsTracingHandler::Browser
),
336 true /* handle on UI thread */);
337 browser_target
->RegisterDomainHandler(
338 TetheringHandler::kDomain
,
339 new TetheringHandler(delegate_
.get()),
340 false /* handle on this thread */);
341 browser_target
->RegisterDomainHandler(
342 devtools::SystemInfo::kName
,
343 new DevToolsSystemInfoHandler(),
344 true /* handle on UI thread */);
345 browser_targets_
[connection_id
] = browser_target
;
347 server_
->AcceptWebSocket(connection_id
, request
);
351 BrowserThread::PostTask(
355 &DevToolsHttpHandlerImpl::OnWebSocketRequestUI
,
361 void DevToolsHttpHandlerImpl::OnWebSocketMessage(
363 const std::string
& data
) {
364 BrowserTargets::iterator it
= browser_targets_
.find(connection_id
);
365 if (it
!= browser_targets_
.end()) {
366 it
->second
->HandleMessage(data
);
370 BrowserThread::PostTask(
374 &DevToolsHttpHandlerImpl::OnWebSocketMessageUI
,
380 void DevToolsHttpHandlerImpl::OnClose(int connection_id
) {
381 BrowserTargets::iterator it
= browser_targets_
.find(connection_id
);
382 if (it
!= browser_targets_
.end()) {
383 it
->second
->Detach();
384 browser_targets_
.erase(it
);
388 BrowserThread::PostTask(
392 &DevToolsHttpHandlerImpl::OnCloseUI
,
397 std::string
DevToolsHttpHandlerImpl::GetFrontendURLInternal(
398 const std::string id
,
399 const std::string
& host
) {
400 return base::StringPrintf(
402 frontend_url_
.c_str(),
403 frontend_url_
.find("?") == std::string::npos
? "?" : "&",
409 static bool ParseJsonPath(
410 const std::string
& path
,
411 std::string
* command
,
412 std::string
* target_id
) {
414 // Fall back to list in case of empty query.
420 if (path
.find("/") != 0) {
421 // Malformed command.
424 *command
= path
.substr(1);
426 size_t separator_pos
= command
->find("/");
427 if (separator_pos
!= std::string::npos
) {
428 *target_id
= command
->substr(separator_pos
+ 1);
429 *command
= command
->substr(0, separator_pos
);
434 void DevToolsHttpHandlerImpl::OnJsonRequestUI(
436 const net::HttpServerRequestInfo
& info
) {
438 std::string path
= info
.path
.substr(5);
440 // Trim fragment and query
442 size_t query_pos
= path
.find("?");
443 if (query_pos
!= std::string::npos
) {
444 query
= path
.substr(query_pos
+ 1);
445 path
= path
.substr(0, query_pos
);
448 size_t fragment_pos
= path
.find("#");
449 if (fragment_pos
!= std::string::npos
)
450 path
= path
.substr(0, fragment_pos
);
453 std::string target_id
;
454 if (!ParseJsonPath(path
, &command
, &target_id
)) {
455 SendJson(connection_id
,
458 "Malformed query: " + info
.path
);
462 if (command
== "version") {
463 base::DictionaryValue version
;
464 version
.SetString("Protocol-Version", devtools::kProtocolVersion
);
465 version
.SetString("WebKit-Version", GetWebKitVersion());
466 version
.SetString("Browser", GetContentClient()->GetProduct());
467 version
.SetString("User-Agent", GetContentClient()->GetUserAgent());
468 #if defined(OS_ANDROID)
469 version
.SetString("Android-Package",
470 base::android::BuildInfo::GetInstance()->package_name());
472 SendJson(connection_id
, net::HTTP_OK
, &version
, std::string());
476 if (command
== "list") {
477 std::string host
= info
.headers
["host"];
478 AddRef(); // Balanced in OnTargetListReceived.
479 delegate_
->EnumerateTargets(
480 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceived
,
481 this, connection_id
, host
));
485 if (command
== "new") {
486 GURL
url(net::UnescapeURLComponent(
487 query
, net::UnescapeRule::URL_SPECIAL_CHARS
));
489 url
= GURL(url::kAboutBlankURL
);
490 scoped_ptr
<DevToolsTarget
> target(delegate_
->CreateNewTarget(url
));
492 SendJson(connection_id
,
493 net::HTTP_INTERNAL_SERVER_ERROR
,
495 "Could not create new page");
498 std::string host
= info
.headers
["host"];
499 scoped_ptr
<base::DictionaryValue
> dictionary(
500 SerializeTarget(*target
.get(), host
));
501 SendJson(connection_id
, net::HTTP_OK
, dictionary
.get(), std::string());
502 const std::string target_id
= target
->GetId();
503 target_map_
[target_id
] = target
.release();
507 if (command
== "activate" || command
== "close") {
508 DevToolsTarget
* target
= GetTarget(target_id
);
510 SendJson(connection_id
,
513 "No such target id: " + target_id
);
517 if (command
== "activate") {
518 if (target
->Activate()) {
519 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target activated");
521 SendJson(connection_id
,
522 net::HTTP_INTERNAL_SERVER_ERROR
,
524 "Could not activate target id: " + target_id
);
529 if (command
== "close") {
530 if (target
->Close()) {
531 SendJson(connection_id
, net::HTTP_OK
, NULL
, "Target is closing");
533 SendJson(connection_id
,
534 net::HTTP_INTERNAL_SERVER_ERROR
,
536 "Could not close target id: " + target_id
);
541 SendJson(connection_id
,
544 "Unknown command: " + command
);
548 void DevToolsHttpHandlerImpl::OnTargetListReceived(
550 const std::string
& host
,
551 const DevToolsHttpHandlerDelegate::TargetList
& targets
) {
552 DevToolsHttpHandlerDelegate::TargetList sorted_targets
= targets
;
553 std::sort(sorted_targets
.begin(), sorted_targets
.end(), TimeComparator
);
555 STLDeleteValues(&target_map_
);
556 base::ListValue list_value
;
557 for (DevToolsHttpHandlerDelegate::TargetList::const_iterator it
=
558 sorted_targets
.begin(); it
!= sorted_targets
.end(); ++it
) {
559 DevToolsTarget
* target
= *it
;
560 target_map_
[target
->GetId()] = target
;
561 list_value
.Append(SerializeTarget(*target
, host
));
563 SendJson(connection_id
, net::HTTP_OK
, &list_value
, std::string());
564 Release(); // Balanced in OnJsonRequestUI.
567 DevToolsTarget
* DevToolsHttpHandlerImpl::GetTarget(const std::string
& id
) {
568 TargetMap::const_iterator it
= target_map_
.find(id
);
569 if (it
== target_map_
.end())
574 void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
575 int connection_id
, const GURL
& page_url
) {
576 std::string data
= delegate_
->GetPageThumbnailData(page_url
);
578 Send200(connection_id
, data
, "image/png");
580 Send404(connection_id
);
583 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id
) {
584 std::string response
= delegate_
->GetDiscoveryPageHTML();
585 Send200(connection_id
, response
, "text/html; charset=UTF-8");
588 void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
590 const net::HttpServerRequestInfo
& request
) {
594 size_t pos
= request
.path
.find(kPageUrlPrefix
);
596 Send404(connection_id
);
600 std::string page_id
= request
.path
.substr(strlen(kPageUrlPrefix
));
601 DevToolsTarget
* target
= GetTarget(page_id
);
602 scoped_refptr
<DevToolsAgentHost
> agent
=
603 target
? target
->GetAgentHost() : NULL
;
605 Send500(connection_id
, "No such target id: " + page_id
);
609 if (agent
->IsAttached()) {
610 Send500(connection_id
,
611 "Target with given id is being inspected: " + page_id
);
615 DevToolsClientHostImpl
* client_host
= new DevToolsClientHostImpl(
616 thread_
->message_loop(), server_
.get(), connection_id
);
617 connection_to_client_host_ui_
[connection_id
] = client_host
;
619 DevToolsManager::GetInstance()->
620 RegisterDevToolsClientHostFor(agent
, client_host
);
622 AcceptWebSocket(connection_id
, request
);
625 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
627 const std::string
& data
) {
628 ConnectionToClientHostMap::iterator it
=
629 connection_to_client_host_ui_
.find(connection_id
);
630 if (it
== connection_to_client_host_ui_
.end())
633 DevToolsManager
* manager
= DevToolsManager::GetInstance();
634 manager
->DispatchOnInspectorBackend(it
->second
, data
);
637 void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id
) {
638 ConnectionToClientHostMap::iterator it
=
639 connection_to_client_host_ui_
.find(connection_id
);
640 if (it
!= connection_to_client_host_ui_
.end()) {
641 DevToolsClientHostImpl
* client_host
=
642 static_cast<DevToolsClientHostImpl
*>(it
->second
);
643 DevToolsManager::GetInstance()->ClientHostClosing(client_host
);
645 connection_to_client_host_ui_
.erase(connection_id
);
649 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
650 const net::StreamListenSocketFactory
* socket_factory
,
651 const std::string
& frontend_url
,
652 DevToolsHttpHandlerDelegate
* delegate
,
653 const base::FilePath
& active_port_output_directory
)
654 : frontend_url_(frontend_url
),
655 socket_factory_(socket_factory
),
657 active_port_output_directory_(active_port_output_directory
) {
658 if (frontend_url_
.empty())
659 frontend_url_
= "/devtools/devtools.html";
661 // Balanced in ResetHandlerThreadAndRelease().
665 // Runs on the handler thread
666 void DevToolsHttpHandlerImpl::Init() {
667 server_
= new net::HttpServer(*socket_factory_
.get(), this);
668 if (!active_port_output_directory_
.empty())
669 WriteActivePortToUserProfile();
672 // Runs on the handler thread
673 void DevToolsHttpHandlerImpl::Teardown() {
677 // Runs on FILE thread to make sure that it is serialized against
678 // {Start|Stop}HandlerThread and to allow calling pthread_join.
679 void DevToolsHttpHandlerImpl::StopHandlerThread() {
680 if (!thread_
->message_loop())
682 thread_
->message_loop()->PostTask(
684 base::Bind(&DevToolsHttpHandlerImpl::Teardown
, this));
685 // Thread::Stop joins the thread.
689 void DevToolsHttpHandlerImpl::WriteActivePortToUserProfile() {
690 DCHECK(!active_port_output_directory_
.empty());
691 net::IPEndPoint endpoint
;
693 if ((err
= server_
->GetLocalAddress(&endpoint
)) != net::OK
) {
694 LOG(ERROR
) << "Error " << err
<< " getting local address";
698 // Write this port to a well-known file in the profile directory
699 // so Telemetry can pick it up.
700 base::FilePath path
= active_port_output_directory_
.Append(
701 kDevToolsActivePortFileName
);
702 std::string port_string
= base::IntToString(endpoint
.port());
703 if (base::WriteFile(path
, port_string
.c_str(), port_string
.length()) < 0) {
704 LOG(ERROR
) << "Error writing DevTools active port to file";
708 void DevToolsHttpHandlerImpl::SendJson(int connection_id
,
709 net::HttpStatusCode status_code
,
711 const std::string
& message
) {
715 // Serialize value and message.
716 std::string json_value
;
718 base::JSONWriter::WriteWithOptions(value
,
719 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
722 std::string json_message
;
723 scoped_ptr
<base::Value
> message_object(new base::StringValue(message
));
724 base::JSONWriter::Write(message_object
.get(), &json_message
);
726 net::HttpServerResponseInfo
response(status_code
);
727 response
.SetBody(json_value
+ message
, "application/json; charset=UTF-8");
729 thread_
->message_loop()->PostTask(
731 base::Bind(&net::HttpServer::SendResponse
,
737 void DevToolsHttpHandlerImpl::Send200(int connection_id
,
738 const std::string
& data
,
739 const std::string
& mime_type
) {
742 thread_
->message_loop()->PostTask(
744 base::Bind(&net::HttpServer::Send200
,
751 void DevToolsHttpHandlerImpl::Send404(int connection_id
) {
754 thread_
->message_loop()->PostTask(
756 base::Bind(&net::HttpServer::Send404
, server_
.get(), connection_id
));
759 void DevToolsHttpHandlerImpl::Send500(int connection_id
,
760 const std::string
& message
) {
763 thread_
->message_loop()->PostTask(
765 base::Bind(&net::HttpServer::Send500
, server_
.get(), connection_id
,
769 void DevToolsHttpHandlerImpl::AcceptWebSocket(
771 const net::HttpServerRequestInfo
& request
) {
774 thread_
->message_loop()->PostTask(
776 base::Bind(&net::HttpServer::AcceptWebSocket
, server_
.get(),
777 connection_id
, request
));
780 base::DictionaryValue
* DevToolsHttpHandlerImpl::SerializeTarget(
781 const DevToolsTarget
& target
,
782 const std::string
& host
) {
783 base::DictionaryValue
* dictionary
= new base::DictionaryValue
;
785 std::string id
= target
.GetId();
786 dictionary
->SetString(kTargetIdField
, id
);
787 std::string parent_id
= target
.GetParentId();
788 if (!parent_id
.empty())
789 dictionary
->SetString(kTargetParentIdField
, parent_id
);
790 dictionary
->SetString(kTargetTypeField
, target
.GetType());
791 dictionary
->SetString(kTargetTitleField
,
792 net::EscapeForHTML(target
.GetTitle()));
793 dictionary
->SetString(kTargetDescriptionField
, target
.GetDescription());
795 GURL url
= target
.GetURL();
796 dictionary
->SetString(kTargetUrlField
, url
.spec());
798 GURL favicon_url
= target
.GetFaviconURL();
799 if (favicon_url
.is_valid())
800 dictionary
->SetString(kTargetFaviconUrlField
, favicon_url
.spec());
802 if (!delegate_
->GetPageThumbnailData(url
).empty()) {
803 dictionary
->SetString(kTargetThumbnailUrlField
,
804 std::string(kThumbUrlPrefix
) + id
);
807 if (!target
.IsAttached()) {
808 dictionary
->SetString(kTargetWebSocketDebuggerUrlField
,
809 base::StringPrintf("ws://%s%s%s",
813 std::string devtools_frontend_url
= GetFrontendURLInternal(
816 dictionary
->SetString(
817 kTargetDevtoolsFrontendUrlField
, devtools_frontend_url
);
823 } // namespace content