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.
4
5 #include "content/browser/devtools/devtools_http_handler_impl.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/compiler_specific.h"
12 #include "base/files/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_manager.h"
22 #include "content/browser/devtools/devtools_protocol.h"
23 #include "content/browser/devtools/devtools_protocol_constants.h"
24 #include "content/browser/devtools/devtools_system_info_handler.h"
25 #include "content/browser/devtools/devtools_tracing_handler.h"
26 #include "content/browser/devtools/tethering_handler.h"
27 #include "content/common/devtools_messages.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/devtools_agent_host.h"
30 #include "content/public/browser/devtools_http_handler_delegate.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 "grit/devtools_resources_map.h"
36 #include "net/base/escape.h"
37 #include "net/base/io_buffer.h"
38 #include "net/base/ip_endpoint.h"
39 #include "net/base/net_errors.h"
40 #include "net/server/http_server_request_info.h"
41 #include "net/server/http_server_response_info.h"
42 #include "net/socket/server_socket.h"
43
44 #if defined(OS_ANDROID)
45 #include "base/android/build_info.h"
46 #endif
47
48 namespace content {
49
50 namespace {
51
52 const base::FilePath::CharType kDevToolsActivePortFileName[] =
53 FILE_PATH_LITERAL("DevToolsActivePort");
54
55 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread";
56
57 const char kThumbUrlPrefix[] = "/thumb/";
58 const char kPageUrlPrefix[] = "/devtools/page/";
59
60 const char kTargetIdField[] = "id";
61 const char kTargetParentIdField[] = "parentId";
62 const char kTargetTypeField[] = "type";
63 const char kTargetTitleField[] = "title";
64 const char kTargetDescriptionField[] = "description";
65 const char kTargetUrlField[] = "url";
66 const char kTargetThumbnailUrlField[] = "thumbnailUrl";
67 const char kTargetFaviconUrlField[] = "faviconUrl";
68 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
69 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
70
71 // Maximum write buffer size of devtools http/websocket connectinos.
72 const int32 kSendBufferSizeForDevTools = 100 * 1024 * 1024; // 100Mb
73
74 // An internal implementation of DevToolsAgentHostClient that delegates
75 // messages sent to a DebuggerShell instance.
76 class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient {
77 public:
DevToolsAgentHostClientImpl(base::MessageLoop * message_loop,net::HttpServer * server,int connection_id,DevToolsAgentHost * agent_host)78 DevToolsAgentHostClientImpl(base::MessageLoop* message_loop,
79 net::HttpServer* server,
80 int connection_id,
81 DevToolsAgentHost* agent_host)
82 : message_loop_(message_loop),
83 server_(server),
84 connection_id_(connection_id),
85 agent_host_(agent_host) {
86 agent_host_->AttachClient(this);
87 }
88
~DevToolsAgentHostClientImpl()89 virtual ~DevToolsAgentHostClientImpl() {
90 if (agent_host_.get())
91 agent_host_->DetachClient();
92 }
93
AgentHostClosed(DevToolsAgentHost * agent_host,bool replaced_with_another_client)94 virtual void AgentHostClosed(
95 DevToolsAgentHost* agent_host,
96 bool replaced_with_another_client) OVERRIDE {
97 DCHECK(agent_host == agent_host_.get());
98 agent_host_ = NULL;
99
100 base::DictionaryValue notification;
101 notification.SetString(
102 devtools::Inspector::detached::kParamReason,
103 replaced_with_another_client ?
104 "replaced_with_devtools" : "target_closed");
105 std::string response = DevToolsProtocol::CreateNotification(
106 devtools::Inspector::detached::kName,
107 notification.DeepCopy())->Serialize();
108 message_loop_->PostTask(
109 FROM_HERE,
110 base::Bind(&net::HttpServer::SendOverWebSocket,
111 base::Unretained(server_),
112 connection_id_,
113 response));
114
115 message_loop_->PostTask(
116 FROM_HERE,
117 base::Bind(&net::HttpServer::Close,
118 base::Unretained(server_),
119 connection_id_));
120 }
121
DispatchProtocolMessage(DevToolsAgentHost * agent_host,const std::string & message)122 virtual void DispatchProtocolMessage(
123 DevToolsAgentHost* agent_host, const std::string& message) OVERRIDE {
124 DCHECK(agent_host == agent_host_.get());
125 message_loop_->PostTask(
126 FROM_HERE,
127 base::Bind(&net::HttpServer::SendOverWebSocket,
128 base::Unretained(server_),
129 connection_id_,
130 message));
131 }
132
OnMessage(const std::string & message)133 void OnMessage(const std::string& message) {
134 if (agent_host_.get())
135 agent_host_->DispatchProtocolMessage(message);
136 }
137
138 private:
139 base::MessageLoop* const message_loop_;
140 net::HttpServer* const server_;
141 const int connection_id_;
142 scoped_refptr<DevToolsAgentHost> agent_host_;
143 };
144
TimeComparator(const DevToolsTarget * target1,const DevToolsTarget * target2)145 static bool TimeComparator(const DevToolsTarget* target1,
146 const DevToolsTarget* target2) {
147 return target1->GetLastActivityTime() > target2->GetLastActivityTime();
148 }
149
150 } // namespace
151
152 // static
IsSupportedProtocolVersion(const std::string & version)153 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
154 const std::string& version) {
155 return devtools::IsSupportedProtocolVersion(version);
156 }
157
158 // static
GetFrontendResourceId(const std::string & name)159 int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) {
160 for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) {
161 if (name == kDevtoolsResources[i].name)
162 return kDevtoolsResources[i].value;
163 }
164 return -1;
165 }
166
167 // static
Start(scoped_ptr<ServerSocketFactory> server_socket_factory,const std::string & frontend_url,DevToolsHttpHandlerDelegate * delegate,const base::FilePath & active_port_output_directory)168 DevToolsHttpHandler* DevToolsHttpHandler::Start(
169 scoped_ptr<ServerSocketFactory> server_socket_factory,
170 const std::string& frontend_url,
171 DevToolsHttpHandlerDelegate* delegate,
172 const base::FilePath& active_port_output_directory) {
173 DevToolsHttpHandlerImpl* http_handler =
174 new DevToolsHttpHandlerImpl(server_socket_factory.Pass(),
175 frontend_url,
176 delegate,
177 active_port_output_directory);
178 http_handler->Start();
179 return http_handler;
180 }
181
ServerSocketFactory(const std::string & address,int port,int backlog)182 DevToolsHttpHandler::ServerSocketFactory::ServerSocketFactory(
183 const std::string& address,
184 int port,
185 int backlog)
186 : address_(address),
187 port_(port),
188 backlog_(backlog) {
189 }
190
~ServerSocketFactory()191 DevToolsHttpHandler::ServerSocketFactory::~ServerSocketFactory() {
192 }
193
194 scoped_ptr<net::ServerSocket>
CreateAndListen() const195 DevToolsHttpHandler::ServerSocketFactory::CreateAndListen() const {
196 scoped_ptr<net::ServerSocket> socket = Create();
197 if (socket &&
198 socket->ListenWithAddressAndPort(address_, port_, backlog_) == net::OK) {
199 return socket.Pass();
200 }
201 return scoped_ptr<net::ServerSocket>();
202 }
203
~DevToolsHttpHandlerImpl()204 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 // Stop() must be called prior to destruction.
207 DCHECK(server_.get() == NULL);
208 DCHECK(thread_.get() == NULL);
209 STLDeleteValues(&target_map_);
210 }
211
Start()212 void DevToolsHttpHandlerImpl::Start() {
213 if (thread_)
214 return;
215 thread_.reset(new base::Thread(kDevToolsHandlerThreadName));
216 BrowserThread::PostTask(
217 BrowserThread::FILE, FROM_HERE,
218 base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread, this));
219 }
220
221 // Runs on FILE thread.
StartHandlerThread()222 void DevToolsHttpHandlerImpl::StartHandlerThread() {
223 base::Thread::Options options;
224 options.message_loop_type = base::MessageLoop::TYPE_IO;
225 if (!thread_->StartWithOptions(options)) {
226 BrowserThread::PostTask(
227 BrowserThread::UI, FROM_HERE,
228 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this));
229 return;
230 }
231
232 thread_->message_loop()->PostTask(
233 FROM_HERE,
234 base::Bind(&DevToolsHttpHandlerImpl::Init, this));
235 }
236
ResetHandlerThread()237 void DevToolsHttpHandlerImpl::ResetHandlerThread() {
238 thread_.reset();
239 }
240
ResetHandlerThreadAndRelease()241 void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
242 ResetHandlerThread();
243 Release();
244 }
245
Stop()246 void DevToolsHttpHandlerImpl::Stop() {
247 if (!thread_)
248 return;
249 BrowserThread::PostTaskAndReply(
250 BrowserThread::FILE, FROM_HERE,
251 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this),
252 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease, this));
253 }
254
StopWithoutRelease()255 void DevToolsHttpHandlerImpl::StopWithoutRelease() {
256 if (!thread_)
257 return;
258 BrowserThread::PostTaskAndReply(
259 BrowserThread::FILE, FROM_HERE,
260 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this),
261 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this));
262 }
263
GetFrontendURL()264 GURL DevToolsHttpHandlerImpl::GetFrontendURL() {
265 net::IPEndPoint ip_address;
266 if (server_ && server_->GetLocalAddress(&ip_address))
267 return GURL();
268 return GURL(std::string("http://") + ip_address.ToString() + frontend_url_);
269 }
270
PathWithoutParams(const std::string & path)271 static std::string PathWithoutParams(const std::string& path) {
272 size_t query_position = path.find("?");
273 if (query_position != std::string::npos)
274 return path.substr(0, query_position);
275 return path;
276 }
277
GetMimeType(const std::string & filename)278 static std::string GetMimeType(const std::string& filename) {
279 if (EndsWith(filename, ".html", false)) {
280 return "text/html";
281 } else if (EndsWith(filename, ".css", false)) {
282 return "text/css";
283 } else if (EndsWith(filename, ".js", false)) {
284 return "application/javascript";
285 } else if (EndsWith(filename, ".png", false)) {
286 return "image/png";
287 } else if (EndsWith(filename, ".gif", false)) {
288 return "image/gif";
289 } else if (EndsWith(filename, ".json", false)) {
290 return "application/json";
291 }
292 LOG(ERROR) << "GetMimeType doesn't know mime type for: "
293 << filename
294 << " text/plain will be returned";
295 NOTREACHED();
296 return "text/plain";
297 }
298
OnHttpRequest(int connection_id,const net::HttpServerRequestInfo & info)299 void DevToolsHttpHandlerImpl::OnHttpRequest(
300 int connection_id,
301 const net::HttpServerRequestInfo& info) {
302 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
303
304 if (info.path.find("/json") == 0) {
305 BrowserThread::PostTask(
306 BrowserThread::UI,
307 FROM_HERE,
308 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI,
309 this,
310 connection_id,
311 info));
312 return;
313 }
314
315 if (info.path.find(kThumbUrlPrefix) == 0) {
316 // Thumbnail request.
317 const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
318 DevToolsTarget* target = GetTarget(target_id);
319 GURL page_url;
320 if (target)
321 page_url = target->GetURL();
322 BrowserThread::PostTask(
323 BrowserThread::UI,
324 FROM_HERE,
325 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI,
326 this,
327 connection_id,
328 page_url));
329 return;
330 }
331
332 if (info.path == "" || info.path == "/") {
333 // Discovery page request.
334 BrowserThread::PostTask(
335 BrowserThread::UI,
336 FROM_HERE,
337 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI,
338 this,
339 connection_id));
340 return;
341 }
342
343 if (info.path.find("/devtools/") != 0) {
344 server_->Send404(connection_id);
345 return;
346 }
347
348 std::string filename = PathWithoutParams(info.path.substr(10));
349 std::string mime_type = GetMimeType(filename);
350
351 base::FilePath frontend_dir = delegate_->GetDebugFrontendDir();
352 if (!frontend_dir.empty()) {
353 base::FilePath path = frontend_dir.AppendASCII(filename);
354 std::string data;
355 base::ReadFileToString(path, &data);
356 server_->Send200(connection_id, data, mime_type);
357 return;
358 }
359 if (delegate_->BundlesFrontendResources()) {
360 int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename);
361 if (resource_id != -1) {
362 base::StringPiece data = GetContentClient()->GetDataResource(
363 resource_id, ui::SCALE_FACTOR_NONE);
364 server_->Send200(connection_id, data.as_string(), mime_type);
365 return;
366 }
367 }
368 server_->Send404(connection_id);
369 }
370
OnWebSocketRequest(int connection_id,const net::HttpServerRequestInfo & request)371 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
372 int connection_id,
373 const net::HttpServerRequestInfo& request) {
374 std::string browser_prefix = "/devtools/browser";
375 size_t browser_pos = request.path.find(browser_prefix);
376 if (browser_pos == 0) {
377 scoped_refptr<DevToolsBrowserTarget> browser_target =
378 new DevToolsBrowserTarget(server_.get(), connection_id);
379 browser_target->RegisterDomainHandler(
380 devtools::Tracing::kName,
381 new DevToolsTracingHandler(DevToolsTracingHandler::Browser),
382 true /* handle on UI thread */);
383 browser_target->RegisterDomainHandler(
384 devtools::Tethering::kName,
385 new TetheringHandler(delegate_.get()),
386 false /* handle on this thread */);
387 browser_target->RegisterDomainHandler(
388 devtools::SystemInfo::kName,
389 new DevToolsSystemInfoHandler(),
390 true /* handle on UI thread */);
391 browser_targets_[connection_id] = browser_target;
392
393 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
394 server_->AcceptWebSocket(connection_id, request);
395 return;
396 }
397
398 BrowserThread::PostTask(
399 BrowserThread::UI,
400 FROM_HERE,
401 base::Bind(
402 &DevToolsHttpHandlerImpl::OnWebSocketRequestUI,
403 this,
404 connection_id,
405 request));
406 }
407
OnWebSocketMessage(int connection_id,const std::string & data)408 void DevToolsHttpHandlerImpl::OnWebSocketMessage(
409 int connection_id,
410 const std::string& data) {
411 BrowserTargets::iterator it = browser_targets_.find(connection_id);
412 if (it != browser_targets_.end()) {
413 it->second->HandleMessage(data);
414 return;
415 }
416
417 BrowserThread::PostTask(
418 BrowserThread::UI,
419 FROM_HERE,
420 base::Bind(
421 &DevToolsHttpHandlerImpl::OnWebSocketMessageUI,
422 this,
423 connection_id,
424 data));
425 }
426
OnClose(int connection_id)427 void DevToolsHttpHandlerImpl::OnClose(int connection_id) {
428 BrowserTargets::iterator it = browser_targets_.find(connection_id);
429 if (it != browser_targets_.end()) {
430 it->second->Detach();
431 browser_targets_.erase(it);
432 return;
433 }
434
435 BrowserThread::PostTask(
436 BrowserThread::UI,
437 FROM_HERE,
438 base::Bind(
439 &DevToolsHttpHandlerImpl::OnCloseUI,
440 this,
441 connection_id));
442 }
443
GetFrontendURLInternal(const std::string id,const std::string & host)444 std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal(
445 const std::string id,
446 const std::string& host) {
447 return base::StringPrintf(
448 "%s%sws=%s%s%s",
449 frontend_url_.c_str(),
450 frontend_url_.find("?") == std::string::npos ? "?" : "&",
451 host.c_str(),
452 kPageUrlPrefix,
453 id.c_str());
454 }
455
ParseJsonPath(const std::string & path,std::string * command,std::string * target_id)456 static bool ParseJsonPath(
457 const std::string& path,
458 std::string* command,
459 std::string* target_id) {
460
461 // Fall back to list in case of empty query.
462 if (path.empty()) {
463 *command = "list";
464 return true;
465 }
466
467 if (path.find("/") != 0) {
468 // Malformed command.
469 return false;
470 }
471 *command = path.substr(1);
472
473 size_t separator_pos = command->find("/");
474 if (separator_pos != std::string::npos) {
475 *target_id = command->substr(separator_pos + 1);
476 *command = command->substr(0, separator_pos);
477 }
478 return true;
479 }
480
OnJsonRequestUI(int connection_id,const net::HttpServerRequestInfo & info)481 void DevToolsHttpHandlerImpl::OnJsonRequestUI(
482 int connection_id,
483 const net::HttpServerRequestInfo& info) {
484 // Trim /json
485 std::string path = info.path.substr(5);
486
487 // Trim fragment and query
488 std::string query;
489 size_t query_pos = path.find("?");
490 if (query_pos != std::string::npos) {
491 query = path.substr(query_pos + 1);
492 path = path.substr(0, query_pos);
493 }
494
495 size_t fragment_pos = path.find("#");
496 if (fragment_pos != std::string::npos)
497 path = path.substr(0, fragment_pos);
498
499 std::string command;
500 std::string target_id;
501 if (!ParseJsonPath(path, &command, &target_id)) {
502 SendJson(connection_id,
503 net::HTTP_NOT_FOUND,
504 NULL,
505 "Malformed query: " + info.path);
506 return;
507 }
508
509 if (command == "version") {
510 base::DictionaryValue version;
511 version.SetString("Protocol-Version", devtools::kProtocolVersion);
512 version.SetString("WebKit-Version", GetWebKitVersion());
513 version.SetString("Browser", GetContentClient()->GetProduct());
514 version.SetString("User-Agent", GetContentClient()->GetUserAgent());
515 #if defined(OS_ANDROID)
516 version.SetString("Android-Package",
517 base::android::BuildInfo::GetInstance()->package_name());
518 #endif
519 SendJson(connection_id, net::HTTP_OK, &version, std::string());
520 return;
521 }
522
523 if (command == "list") {
524 std::string host = info.headers["host"];
525 AddRef(); // Balanced in OnTargetListReceived.
526 DevToolsManagerDelegate* manager_delegate =
527 DevToolsManager::GetInstance()->delegate();
528 if (manager_delegate) {
529 manager_delegate->EnumerateTargets(
530 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceived,
531 this, connection_id, host));
532 } else {
533 DevToolsManagerDelegate::TargetList empty_list;
534 OnTargetListReceived(connection_id, host, empty_list);
535 }
536 return;
537 }
538
539 if (command == "new") {
540 GURL url(net::UnescapeURLComponent(
541 query, net::UnescapeRule::URL_SPECIAL_CHARS));
542 if (!url.is_valid())
543 url = GURL(url::kAboutBlankURL);
544 scoped_ptr<DevToolsTarget> target;
545 DevToolsManagerDelegate* manager_delegate =
546 DevToolsManager::GetInstance()->delegate();
547 if (manager_delegate)
548 target = manager_delegate->CreateNewTarget(url);
549 if (!target) {
550 SendJson(connection_id,
551 net::HTTP_INTERNAL_SERVER_ERROR,
552 NULL,
553 "Could not create new page");
554 return;
555 }
556 std::string host = info.headers["host"];
557 scoped_ptr<base::DictionaryValue> dictionary(
558 SerializeTarget(*target.get(), host));
559 SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
560 const std::string target_id = target->GetId();
561 target_map_[target_id] = target.release();
562 return;
563 }
564
565 if (command == "activate" || command == "close") {
566 DevToolsTarget* target = GetTarget(target_id);
567 if (!target) {
568 SendJson(connection_id,
569 net::HTTP_NOT_FOUND,
570 NULL,
571 "No such target id: " + target_id);
572 return;
573 }
574
575 if (command == "activate") {
576 if (target->Activate()) {
577 SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
578 } else {
579 SendJson(connection_id,
580 net::HTTP_INTERNAL_SERVER_ERROR,
581 NULL,
582 "Could not activate target id: " + target_id);
583 }
584 return;
585 }
586
587 if (command == "close") {
588 if (target->Close()) {
589 SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
590 } else {
591 SendJson(connection_id,
592 net::HTTP_INTERNAL_SERVER_ERROR,
593 NULL,
594 "Could not close target id: " + target_id);
595 }
596 return;
597 }
598 }
599 SendJson(connection_id,
600 net::HTTP_NOT_FOUND,
601 NULL,
602 "Unknown command: " + command);
603 return;
604 }
605
OnTargetListReceived(int connection_id,const std::string & host,const DevToolsManagerDelegate::TargetList & targets)606 void DevToolsHttpHandlerImpl::OnTargetListReceived(
607 int connection_id,
608 const std::string& host,
609 const DevToolsManagerDelegate::TargetList& targets) {
610 DevToolsManagerDelegate::TargetList sorted_targets = targets;
611 std::sort(sorted_targets.begin(), sorted_targets.end(), TimeComparator);
612
613 STLDeleteValues(&target_map_);
614 base::ListValue list_value;
615 for (DevToolsManagerDelegate::TargetList::const_iterator it =
616 sorted_targets.begin(); it != sorted_targets.end(); ++it) {
617 DevToolsTarget* target = *it;
618 target_map_[target->GetId()] = target;
619 list_value.Append(SerializeTarget(*target, host));
620 }
621 SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
622 Release(); // Balanced in OnJsonRequestUI.
623 }
624
GetTarget(const std::string & id)625 DevToolsTarget* DevToolsHttpHandlerImpl::GetTarget(const std::string& id) {
626 TargetMap::const_iterator it = target_map_.find(id);
627 if (it == target_map_.end())
628 return NULL;
629 return it->second;
630 }
631
OnThumbnailRequestUI(int connection_id,const GURL & page_url)632 void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
633 int connection_id, const GURL& page_url) {
634 DevToolsManagerDelegate* manager_delegate =
635 DevToolsManager::GetInstance()->delegate();
636 std::string data =
637 manager_delegate ? manager_delegate->GetPageThumbnailData(page_url) : "";
638 if (!data.empty())
639 Send200(connection_id, data, "image/png");
640 else
641 Send404(connection_id);
642 }
643
OnDiscoveryPageRequestUI(int connection_id)644 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id) {
645 std::string response = delegate_->GetDiscoveryPageHTML();
646 Send200(connection_id, response, "text/html; charset=UTF-8");
647 }
648
OnWebSocketRequestUI(int connection_id,const net::HttpServerRequestInfo & request)649 void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
650 int connection_id,
651 const net::HttpServerRequestInfo& request) {
652 if (!thread_)
653 return;
654
655 size_t pos = request.path.find(kPageUrlPrefix);
656 if (pos != 0) {
657 Send404(connection_id);
658 return;
659 }
660
661 std::string page_id = request.path.substr(strlen(kPageUrlPrefix));
662 DevToolsTarget* target = GetTarget(page_id);
663 scoped_refptr<DevToolsAgentHost> agent =
664 target ? target->GetAgentHost() : NULL;
665 if (!agent.get()) {
666 Send500(connection_id, "No such target id: " + page_id);
667 return;
668 }
669
670 if (agent->IsAttached()) {
671 Send500(connection_id,
672 "Target with given id is being inspected: " + page_id);
673 return;
674 }
675
676 DevToolsAgentHostClientImpl* client_host = new DevToolsAgentHostClientImpl(
677 thread_->message_loop(), server_.get(), connection_id, agent.get());
678 connection_to_client_ui_[connection_id] = client_host;
679
680 AcceptWebSocket(connection_id, request);
681 }
682
OnWebSocketMessageUI(int connection_id,const std::string & data)683 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
684 int connection_id,
685 const std::string& data) {
686 ConnectionToClientMap::iterator it =
687 connection_to_client_ui_.find(connection_id);
688 if (it == connection_to_client_ui_.end())
689 return;
690
691 DevToolsAgentHostClientImpl* client =
692 static_cast<DevToolsAgentHostClientImpl*>(it->second);
693 client->OnMessage(data);
694 }
695
OnCloseUI(int connection_id)696 void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) {
697 ConnectionToClientMap::iterator it =
698 connection_to_client_ui_.find(connection_id);
699 if (it != connection_to_client_ui_.end()) {
700 DevToolsAgentHostClientImpl* client =
701 static_cast<DevToolsAgentHostClientImpl*>(it->second);
702 delete client;
703 connection_to_client_ui_.erase(connection_id);
704 }
705 }
706
DevToolsHttpHandlerImpl(scoped_ptr<ServerSocketFactory> server_socket_factory,const std::string & frontend_url,DevToolsHttpHandlerDelegate * delegate,const base::FilePath & active_port_output_directory)707 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
708 scoped_ptr<ServerSocketFactory> server_socket_factory,
709 const std::string& frontend_url,
710 DevToolsHttpHandlerDelegate* delegate,
711 const base::FilePath& active_port_output_directory)
712 : frontend_url_(frontend_url),
713 server_socket_factory_(server_socket_factory.Pass()),
714 delegate_(delegate),
715 active_port_output_directory_(active_port_output_directory) {
716 if (frontend_url_.empty())
717 frontend_url_ = "/devtools/devtools.html";
718
719 // Balanced in ResetHandlerThreadAndRelease().
720 AddRef();
721 }
722
723 // Runs on the handler thread
Init()724 void DevToolsHttpHandlerImpl::Init() {
725 scoped_ptr<net::ServerSocket> server_socket =
726 server_socket_factory_->CreateAndListen();
727 if (!server_socket) {
728 LOG(ERROR) << "Cannot start http server for devtools. Stop devtools.";
729 BrowserThread::PostTask(
730 BrowserThread::UI, FROM_HERE,
731 base::Bind(&DevToolsHttpHandlerImpl::StopWithoutRelease, this));
732 return;
733 }
734
735 server_.reset(new net::HttpServer(server_socket.Pass(), this));
736 if (!active_port_output_directory_.empty())
737 WriteActivePortToUserProfile();
738 }
739
740 // Runs on the handler thread
Teardown()741 void DevToolsHttpHandlerImpl::Teardown() {
742 server_.reset(NULL);
743 }
744
745 // Runs on FILE thread to make sure that it is serialized against
746 // {Start|Stop}HandlerThread and to allow calling pthread_join.
StopHandlerThread()747 void DevToolsHttpHandlerImpl::StopHandlerThread() {
748 if (!thread_->message_loop())
749 return;
750 thread_->message_loop()->PostTask(
751 FROM_HERE,
752 base::Bind(&DevToolsHttpHandlerImpl::Teardown, this));
753 // Thread::Stop joins the thread.
754 thread_->Stop();
755 }
756
WriteActivePortToUserProfile()757 void DevToolsHttpHandlerImpl::WriteActivePortToUserProfile() {
758 DCHECK(!active_port_output_directory_.empty());
759 net::IPEndPoint endpoint;
760 int err;
761 if ((err = server_->GetLocalAddress(&endpoint)) != net::OK) {
762 LOG(ERROR) << "Error " << err << " getting local address";
763 return;
764 }
765
766 // Write this port to a well-known file in the profile directory
767 // so Telemetry can pick it up.
768 base::FilePath path = active_port_output_directory_.Append(
769 kDevToolsActivePortFileName);
770 std::string port_string = base::IntToString(endpoint.port());
771 if (base::WriteFile(path, port_string.c_str(), port_string.length()) < 0) {
772 LOG(ERROR) << "Error writing DevTools active port to file";
773 }
774 }
775
SendJson(int connection_id,net::HttpStatusCode status_code,base::Value * value,const std::string & message)776 void DevToolsHttpHandlerImpl::SendJson(int connection_id,
777 net::HttpStatusCode status_code,
778 base::Value* value,
779 const std::string& message) {
780 if (!thread_)
781 return;
782
783 // Serialize value and message.
784 std::string json_value;
785 if (value) {
786 base::JSONWriter::WriteWithOptions(value,
787 base::JSONWriter::OPTIONS_PRETTY_PRINT,
788 &json_value);
789 }
790 std::string json_message;
791 scoped_ptr<base::Value> message_object(new base::StringValue(message));
792 base::JSONWriter::Write(message_object.get(), &json_message);
793
794 net::HttpServerResponseInfo response(status_code);
795 response.SetBody(json_value + message, "application/json; charset=UTF-8");
796
797 thread_->message_loop()->PostTask(
798 FROM_HERE,
799 base::Bind(&net::HttpServer::SendResponse,
800 base::Unretained(server_.get()),
801 connection_id,
802 response));
803 }
804
Send200(int connection_id,const std::string & data,const std::string & mime_type)805 void DevToolsHttpHandlerImpl::Send200(int connection_id,
806 const std::string& data,
807 const std::string& mime_type) {
808 if (!thread_)
809 return;
810 thread_->message_loop()->PostTask(
811 FROM_HERE,
812 base::Bind(&net::HttpServer::Send200,
813 base::Unretained(server_.get()),
814 connection_id,
815 data,
816 mime_type));
817 }
818
Send404(int connection_id)819 void DevToolsHttpHandlerImpl::Send404(int connection_id) {
820 if (!thread_)
821 return;
822 thread_->message_loop()->PostTask(
823 FROM_HERE,
824 base::Bind(&net::HttpServer::Send404,
825 base::Unretained(server_.get()),
826 connection_id));
827 }
828
Send500(int connection_id,const std::string & message)829 void DevToolsHttpHandlerImpl::Send500(int connection_id,
830 const std::string& message) {
831 if (!thread_)
832 return;
833 thread_->message_loop()->PostTask(
834 FROM_HERE,
835 base::Bind(&net::HttpServer::Send500,
836 base::Unretained(server_.get()),
837 connection_id,
838 message));
839 }
840
AcceptWebSocket(int connection_id,const net::HttpServerRequestInfo & request)841 void DevToolsHttpHandlerImpl::AcceptWebSocket(
842 int connection_id,
843 const net::HttpServerRequestInfo& request) {
844 if (!thread_)
845 return;
846 thread_->message_loop()->PostTask(
847 FROM_HERE,
848 base::Bind(&net::HttpServer::SetSendBufferSize,
849 base::Unretained(server_.get()),
850 connection_id,
851 kSendBufferSizeForDevTools));
852 thread_->message_loop()->PostTask(
853 FROM_HERE,
854 base::Bind(&net::HttpServer::AcceptWebSocket,
855 base::Unretained(server_.get()),
856 connection_id,
857 request));
858 }
859
SerializeTarget(const DevToolsTarget & target,const std::string & host)860 base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeTarget(
861 const DevToolsTarget& target,
862 const std::string& host) {
863 base::DictionaryValue* dictionary = new base::DictionaryValue;
864
865 std::string id = target.GetId();
866 dictionary->SetString(kTargetIdField, id);
867 std::string parent_id = target.GetParentId();
868 if (!parent_id.empty())
869 dictionary->SetString(kTargetParentIdField, parent_id);
870 dictionary->SetString(kTargetTypeField, target.GetType());
871 dictionary->SetString(kTargetTitleField,
872 net::EscapeForHTML(target.GetTitle()));
873 dictionary->SetString(kTargetDescriptionField, target.GetDescription());
874
875 GURL url = target.GetURL();
876 dictionary->SetString(kTargetUrlField, url.spec());
877
878 GURL favicon_url = target.GetFaviconURL();
879 if (favicon_url.is_valid())
880 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
881
882 DevToolsManagerDelegate* manager_delegate =
883 DevToolsManager::GetInstance()->delegate();
884 if (manager_delegate &&
885 !manager_delegate->GetPageThumbnailData(url).empty()) {
886 dictionary->SetString(kTargetThumbnailUrlField,
887 std::string(kThumbUrlPrefix) + id);
888 }
889
890 if (!target.IsAttached()) {
891 dictionary->SetString(kTargetWebSocketDebuggerUrlField,
892 base::StringPrintf("ws://%s%s%s",
893 host.c_str(),
894 kPageUrlPrefix,
895 id.c_str()));
896 std::string devtools_frontend_url = GetFrontendURLInternal(
897 id.c_str(),
898 host);
899 dictionary->SetString(
900 kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
901 }
902
903 return dictionary;
904 }
905
906 } // namespace content
907