OLD | NEW |
| (Empty) |
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/debugger/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/file_util.h" | |
13 #include "base/json/json_writer.h" | |
14 #include "base/lazy_instance.h" | |
15 #include "base/logging.h" | |
16 #include "base/message_loop_proxy.h" | |
17 #include "base/string_number_conversions.h" | |
18 #include "base/stringprintf.h" | |
19 #include "base/threading/thread.h" | |
20 #include "base/utf_string_conversions.h" | |
21 #include "base/values.h" | |
22 #include "content/browser/debugger/devtools_browser_target.h" | |
23 #include "content/browser/debugger/devtools_tracing_handler.h" | |
24 #include "content/browser/web_contents/web_contents_impl.h" | |
25 #include "content/common/devtools_messages.h" | |
26 #include "content/public/browser/browser_thread.h" | |
27 #include "content/public/browser/devtools_agent_host_registry.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/favicon_status.h" | |
32 #include "content/public/browser/navigation_entry.h" | |
33 #include "content/public/browser/notification_service.h" | |
34 #include "content/public/browser/notification_types.h" | |
35 #include "content/public/browser/render_process_host.h" | |
36 #include "content/public/browser/render_view_host.h" | |
37 #include "content/public/browser/render_widget_host.h" | |
38 #include "content/public/common/content_client.h" | |
39 #include "content/public/common/url_constants.h" | |
40 #include "googleurl/src/gurl.h" | |
41 #include "grit/devtools_resources_map.h" | |
42 #include "net/base/escape.h" | |
43 #include "net/base/io_buffer.h" | |
44 #include "net/base/ip_endpoint.h" | |
45 #include "net/server/http_server_request_info.h" | |
46 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDevToolsAgent.h" | |
47 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h" | |
48 #include "ui/base/layout.h" | |
49 #include "webkit/user_agent/user_agent.h" | |
50 #include "webkit/user_agent/user_agent_util.h" | |
51 | |
52 namespace content { | |
53 | |
54 const int kBufferSize = 16 * 1024; | |
55 | |
56 namespace { | |
57 | |
58 static const char* kDevToolsHandlerThreadName = "Chrome_DevToolsHandlerThread"; | |
59 | |
60 class DevToolsDefaultBindingHandler | |
61 : public DevToolsHttpHandler::RenderViewHostBinding { | |
62 public: | |
63 DevToolsDefaultBindingHandler() { | |
64 } | |
65 | |
66 virtual std::string GetIdentifier(RenderViewHost* rvh) OVERRIDE { | |
67 int process_id = rvh->GetProcess()->GetID(); | |
68 int routing_id = rvh->GetRoutingID(); | |
69 return base::StringPrintf("%d_%d", process_id, routing_id); | |
70 } | |
71 | |
72 virtual RenderViewHost* ForIdentifier( | |
73 const std::string& identifier) OVERRIDE { | |
74 size_t pos = identifier.find("_"); | |
75 if (pos == std::string::npos) | |
76 return NULL; | |
77 | |
78 int process_id; | |
79 if (!base::StringToInt(identifier.substr(0, pos), &process_id)) | |
80 return NULL; | |
81 | |
82 int routing_id; | |
83 if (!base::StringToInt(identifier.substr(pos+1), &routing_id)) | |
84 return NULL; | |
85 | |
86 return RenderViewHost::FromID(process_id, routing_id); | |
87 } | |
88 }; | |
89 | |
90 | |
91 // An internal implementation of DevToolsClientHost that delegates | |
92 // messages sent for DevToolsClient to a DebuggerShell instance. | |
93 class DevToolsClientHostImpl : public DevToolsClientHost { | |
94 public: | |
95 DevToolsClientHostImpl( | |
96 MessageLoop* message_loop, | |
97 net::HttpServer* server, | |
98 int connection_id) | |
99 : message_loop_(message_loop), | |
100 server_(server), | |
101 connection_id_(connection_id), | |
102 is_closed_(false), | |
103 detach_reason_("target_closed") { | |
104 } | |
105 | |
106 ~DevToolsClientHostImpl() {} | |
107 | |
108 // DevToolsClientHost interface | |
109 virtual void InspectedContentsClosing() { | |
110 if (is_closed_) | |
111 return; | |
112 is_closed_ = true; | |
113 | |
114 std::string response = | |
115 WebKit::WebDevToolsAgent::inspectorDetachedEvent( | |
116 WebKit::WebString::fromUTF8(detach_reason_)).utf8(); | |
117 message_loop_->PostTask( | |
118 FROM_HERE, | |
119 base::Bind(&net::HttpServer::SendOverWebSocket, | |
120 server_, | |
121 connection_id_, | |
122 response)); | |
123 | |
124 message_loop_->PostTask( | |
125 FROM_HERE, | |
126 base::Bind(&net::HttpServer::Close, server_, connection_id_)); | |
127 } | |
128 | |
129 virtual void DispatchOnInspectorFrontend(const std::string& data) { | |
130 message_loop_->PostTask( | |
131 FROM_HERE, | |
132 base::Bind(&net::HttpServer::SendOverWebSocket, | |
133 server_, | |
134 connection_id_, | |
135 data)); | |
136 } | |
137 | |
138 virtual void ContentsReplaced(WebContents* new_contents) { | |
139 } | |
140 | |
141 virtual void ReplacedWithAnotherClient() { | |
142 detach_reason_ = "replaced_with_devtools"; | |
143 } | |
144 | |
145 private: | |
146 virtual void FrameNavigating(const std::string& url) {} | |
147 MessageLoop* message_loop_; | |
148 net::HttpServer* server_; | |
149 int connection_id_; | |
150 bool is_closed_; | |
151 std::string detach_reason_; | |
152 }; | |
153 | |
154 } // namespace | |
155 | |
156 // static | |
157 int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) { | |
158 for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) { | |
159 if (name == kDevtoolsResources[i].name) | |
160 return kDevtoolsResources[i].value; | |
161 } | |
162 return -1; | |
163 } | |
164 | |
165 // static | |
166 DevToolsHttpHandler* DevToolsHttpHandler::Start( | |
167 const net::StreamListenSocketFactory* socket_factory, | |
168 const std::string& frontend_url, | |
169 DevToolsHttpHandlerDelegate* delegate) { | |
170 DevToolsHttpHandlerImpl* http_handler = | |
171 new DevToolsHttpHandlerImpl(socket_factory, | |
172 frontend_url, | |
173 delegate); | |
174 http_handler->Start(); | |
175 return http_handler; | |
176 } | |
177 | |
178 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() { | |
179 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
180 // Stop() must be called prior to destruction. | |
181 DCHECK(server_.get() == NULL); | |
182 DCHECK(thread_.get() == NULL); | |
183 } | |
184 | |
185 void DevToolsHttpHandlerImpl::Start() { | |
186 if (thread_.get()) | |
187 return; | |
188 thread_.reset(new base::Thread(kDevToolsHandlerThreadName)); | |
189 BrowserThread::PostTask( | |
190 BrowserThread::FILE, FROM_HERE, | |
191 base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread, this)); | |
192 } | |
193 | |
194 // Runs on FILE thread. | |
195 void DevToolsHttpHandlerImpl::StartHandlerThread() { | |
196 base::Thread::Options options; | |
197 options.message_loop_type = MessageLoop::TYPE_IO; | |
198 if (!thread_->StartWithOptions(options)) { | |
199 BrowserThread::PostTask( | |
200 BrowserThread::UI, FROM_HERE, | |
201 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this)); | |
202 return; | |
203 } | |
204 | |
205 thread_->message_loop()->PostTask( | |
206 FROM_HERE, | |
207 base::Bind(&DevToolsHttpHandlerImpl::Init, this)); | |
208 } | |
209 | |
210 void DevToolsHttpHandlerImpl::ResetHandlerThread() { | |
211 thread_.reset(); | |
212 } | |
213 | |
214 void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() { | |
215 ResetHandlerThread(); | |
216 Release(); | |
217 } | |
218 | |
219 void DevToolsHttpHandlerImpl::Stop() { | |
220 if (!thread_.get()) | |
221 return; | |
222 BrowserThread::PostTaskAndReply( | |
223 BrowserThread::FILE, FROM_HERE, | |
224 base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this), | |
225 base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease, this)); | |
226 } | |
227 | |
228 void DevToolsHttpHandlerImpl::SetRenderViewHostBinding( | |
229 RenderViewHostBinding* binding) { | |
230 if (binding) | |
231 binding_ = binding; | |
232 else | |
233 binding_ = default_binding_.get(); | |
234 } | |
235 | |
236 GURL DevToolsHttpHandlerImpl::GetFrontendURL(RenderViewHost* render_view_host) { | |
237 net::IPEndPoint ip_address; | |
238 if (server_->GetLocalAddress(&ip_address)) | |
239 return GURL(); | |
240 std::string host = ip_address.ToString(); | |
241 std::string id = binding_->GetIdentifier(render_view_host); | |
242 return GURL(std::string("http://") + | |
243 ip_address.ToString() + | |
244 GetFrontendURLInternal(id, host)); | |
245 } | |
246 | |
247 static std::string PathWithoutParams(const std::string& path) { | |
248 size_t query_position = path.find("?"); | |
249 if (query_position != std::string::npos) | |
250 return path.substr(0, query_position); | |
251 return path; | |
252 } | |
253 | |
254 static std::string GetMimeType(const std::string& filename) { | |
255 if (EndsWith(filename, ".html", false)) { | |
256 return "text/html"; | |
257 } else if (EndsWith(filename, ".css", false)) { | |
258 return "text/css"; | |
259 } else if (EndsWith(filename, ".js", false)) { | |
260 return "application/javascript"; | |
261 } else if (EndsWith(filename, ".png", false)) { | |
262 return "image/png"; | |
263 } else if (EndsWith(filename, ".gif", false)) { | |
264 return "image/gif"; | |
265 } | |
266 NOTREACHED(); | |
267 return "text/plain"; | |
268 } | |
269 | |
270 void DevToolsHttpHandlerImpl::Observe(int type, | |
271 const NotificationSource& source, | |
272 const NotificationDetails& details) { | |
273 RenderProcessHost* process = Source<RenderProcessHost>(source).ptr(); | |
274 DevToolsManager* manager = DevToolsManager::GetInstance(); | |
275 for (ConnectionToClientHostMap::iterator it = | |
276 connection_to_client_host_ui_.begin(); | |
277 it != connection_to_client_host_ui_.end(); ++it) { | |
278 DevToolsAgentHost* agent = manager->GetDevToolsAgentHostFor(it->second); | |
279 if (!agent) | |
280 continue; | |
281 RenderViewHost* rvh = DevToolsAgentHostRegistry::GetRenderViewHost(agent); | |
282 if (rvh && rvh->GetProcess() == process) | |
283 it->second->InspectedContentsClosing(); | |
284 } | |
285 } | |
286 | |
287 void DevToolsHttpHandlerImpl::OnHttpRequest( | |
288 int connection_id, | |
289 const net::HttpServerRequestInfo& info) { | |
290 if (info.path.find("/json") == 0) { | |
291 BrowserThread::PostTask( | |
292 BrowserThread::UI, | |
293 FROM_HERE, | |
294 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI, | |
295 this, | |
296 connection_id, | |
297 info)); | |
298 return; | |
299 } | |
300 | |
301 if (info.path.find("/thumb/") == 0) { | |
302 // Thumbnail request. | |
303 BrowserThread::PostTask( | |
304 BrowserThread::UI, | |
305 FROM_HERE, | |
306 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI, | |
307 this, | |
308 connection_id, | |
309 info)); | |
310 return; | |
311 } | |
312 | |
313 if (info.path == "" || info.path == "/") { | |
314 // Discovery page request. | |
315 BrowserThread::PostTask( | |
316 BrowserThread::UI, | |
317 FROM_HERE, | |
318 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI, | |
319 this, | |
320 connection_id)); | |
321 return; | |
322 } | |
323 | |
324 if (info.path.find("/devtools/") != 0) { | |
325 server_->Send404(connection_id); | |
326 return; | |
327 } | |
328 | |
329 std::string filename = PathWithoutParams(info.path.substr(10)); | |
330 std::string mime_type = GetMimeType(filename); | |
331 | |
332 FilePath frontend_dir = delegate_->GetDebugFrontendDir(); | |
333 if (!frontend_dir.empty()) { | |
334 FilePath path = frontend_dir.AppendASCII(filename); | |
335 std::string data; | |
336 file_util::ReadFileToString(path, &data); | |
337 server_->Send200(connection_id, data, mime_type); | |
338 return; | |
339 } | |
340 if (delegate_->BundlesFrontendResources()) { | |
341 int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename); | |
342 if (resource_id != -1) { | |
343 base::StringPiece data = GetContentClient()->GetDataResource( | |
344 resource_id, ui::SCALE_FACTOR_NONE); | |
345 server_->Send200(connection_id, data.as_string(), mime_type); | |
346 return; | |
347 } | |
348 } | |
349 server_->Send404(connection_id); | |
350 } | |
351 | |
352 void DevToolsHttpHandlerImpl::OnWebSocketRequest( | |
353 int connection_id, | |
354 const net::HttpServerRequestInfo& request) { | |
355 BrowserThread::PostTask( | |
356 BrowserThread::UI, | |
357 FROM_HERE, | |
358 base::Bind( | |
359 &DevToolsHttpHandlerImpl::OnWebSocketRequestUI, | |
360 this, | |
361 connection_id, | |
362 request)); | |
363 } | |
364 | |
365 void DevToolsHttpHandlerImpl::OnWebSocketMessage( | |
366 int connection_id, | |
367 const std::string& data) { | |
368 BrowserThread::PostTask( | |
369 BrowserThread::UI, | |
370 FROM_HERE, | |
371 base::Bind( | |
372 &DevToolsHttpHandlerImpl::OnWebSocketMessageUI, | |
373 this, | |
374 connection_id, | |
375 data)); | |
376 } | |
377 | |
378 void DevToolsHttpHandlerImpl::OnClose(int connection_id) { | |
379 BrowserThread::PostTask( | |
380 BrowserThread::UI, | |
381 FROM_HERE, | |
382 base::Bind( | |
383 &DevToolsHttpHandlerImpl::OnCloseUI, | |
384 this, | |
385 connection_id)); | |
386 } | |
387 | |
388 struct DevToolsHttpHandlerImpl::PageInfo { | |
389 PageInfo() | |
390 : attached(false) { | |
391 } | |
392 | |
393 std::string id; | |
394 std::string url; | |
395 bool attached; | |
396 std::string title; | |
397 std::string thumbnail_url; | |
398 std::string favicon_url; | |
399 base::TimeTicks last_selected_time; | |
400 }; | |
401 | |
402 // static | |
403 bool DevToolsHttpHandlerImpl::SortPageListByTime(const PageInfo& info1, | |
404 const PageInfo& info2) { | |
405 return info1.last_selected_time > info2.last_selected_time; | |
406 } | |
407 | |
408 DevToolsHttpHandlerImpl::PageList DevToolsHttpHandlerImpl::GeneratePageList() { | |
409 PageList page_list; | |
410 for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator()); | |
411 !it.IsAtEnd(); it.Advance()) { | |
412 RenderProcessHost* render_process_host = it.GetCurrentValue(); | |
413 DCHECK(render_process_host); | |
414 | |
415 // Ignore processes that don't have a connection, such as crashed contents. | |
416 if (!render_process_host->HasConnection()) | |
417 continue; | |
418 | |
419 RenderProcessHost::RenderWidgetHostsIterator rwit( | |
420 render_process_host->GetRenderWidgetHostsIterator()); | |
421 for (; !rwit.IsAtEnd(); rwit.Advance()) { | |
422 const RenderWidgetHost* widget = rwit.GetCurrentValue(); | |
423 DCHECK(widget); | |
424 if (!widget || !widget->IsRenderView()) | |
425 continue; | |
426 | |
427 RenderViewHost* host = | |
428 RenderViewHost::From(const_cast<RenderWidgetHost*>(widget)); | |
429 page_list.push_back(CreatePageInfo(host)); | |
430 } | |
431 } | |
432 std::sort(page_list.begin(), page_list.end(), SortPageListByTime); | |
433 return page_list; | |
434 } | |
435 | |
436 std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal( | |
437 const std::string rvh_id, | |
438 const std::string& host) { | |
439 return base::StringPrintf( | |
440 "%s%sws=%s/devtools/page/%s", | |
441 overridden_frontend_url_.c_str(), | |
442 overridden_frontend_url_.find("?") == std::string::npos ? "?" : "&", | |
443 host.c_str(), | |
444 rvh_id.c_str()); | |
445 } | |
446 | |
447 static bool ParseJsonPath( | |
448 const std::string& path, | |
449 std::string* command, | |
450 std::string* target_id) { | |
451 | |
452 // Fall back to list in case of empty query. | |
453 if (path.empty()) { | |
454 *command = "list"; | |
455 return true; | |
456 } | |
457 | |
458 if (path.find("/") != 0) { | |
459 // Malformed command. | |
460 return false; | |
461 } | |
462 *command = path.substr(1); | |
463 | |
464 size_t separator_pos = command->find("/"); | |
465 if (separator_pos != std::string::npos) { | |
466 *target_id = command->substr(separator_pos + 1); | |
467 *command = command->substr(0, separator_pos); | |
468 } | |
469 return true; | |
470 } | |
471 | |
472 void DevToolsHttpHandlerImpl::OnJsonRequestUI( | |
473 int connection_id, | |
474 const net::HttpServerRequestInfo& info) { | |
475 // Trim /json and ?jsonp=... | |
476 std::string path = info.path.substr(5); | |
477 std::string jsonp; | |
478 size_t jsonp_pos = path.find("?jsonp="); | |
479 if (jsonp_pos != std::string::npos) { | |
480 jsonp = path.substr(jsonp_pos + 7); | |
481 path = path.substr(0, jsonp_pos); | |
482 } | |
483 | |
484 // Trim fragment and query | |
485 size_t query_pos = path.find("?"); | |
486 if (query_pos != std::string::npos) | |
487 path = path.substr(0, query_pos); | |
488 | |
489 size_t fragment_pos = path.find("#"); | |
490 if (fragment_pos != std::string::npos) | |
491 path = path.substr(0, fragment_pos); | |
492 | |
493 std::string command; | |
494 std::string target_id; | |
495 if (!ParseJsonPath(path, &command, &target_id)) { | |
496 SendJson(connection_id, | |
497 net::HTTP_NOT_FOUND, | |
498 NULL, | |
499 "Malformed query: " + info.path, | |
500 jsonp); | |
501 return; | |
502 } | |
503 | |
504 if (command == "version") { | |
505 DictionaryValue version; | |
506 version.SetString("Protocol-Version", | |
507 WebKit::WebDevToolsAgent::inspectorProtocolVersion()); | |
508 version.SetString("WebKit-Version", | |
509 webkit_glue::GetWebKitVersion()); | |
510 version.SetString("User-Agent", | |
511 webkit_glue::GetUserAgent(GURL(chrome::kAboutBlankURL))); | |
512 SendJson(connection_id, net::HTTP_OK, &version, "", jsonp); | |
513 return; | |
514 } | |
515 | |
516 if (command == "list") { | |
517 PageList page_list = GeneratePageList(); | |
518 ListValue json_pages_list; | |
519 std::string host = info.headers["Host"]; | |
520 for (PageList::iterator i = page_list.begin(); i != page_list.end(); ++i) | |
521 json_pages_list.Append(SerializePageInfo(*i, host)); | |
522 SendJson(connection_id, net::HTTP_OK, &json_pages_list, "", jsonp); | |
523 return; | |
524 } | |
525 | |
526 if (command == "new") { | |
527 RenderViewHost* rvh = delegate_->CreateNewTarget(); | |
528 if (!rvh) { | |
529 SendJson(connection_id, | |
530 net::HTTP_INTERNAL_SERVER_ERROR, | |
531 NULL, | |
532 "Could not create new page", | |
533 jsonp); | |
534 return; | |
535 } | |
536 PageInfo page_info = CreatePageInfo(rvh); | |
537 std::string host = info.headers["Host"]; | |
538 scoped_ptr<DictionaryValue> dictionary(SerializePageInfo(page_info, host)); | |
539 SendJson(connection_id, net::HTTP_OK, dictionary.get(), "", jsonp); | |
540 return; | |
541 } | |
542 | |
543 if (command == "activate" || command == "close") { | |
544 RenderViewHost* rvh = binding_->ForIdentifier(target_id); | |
545 if (!rvh) { | |
546 SendJson(connection_id, | |
547 net::HTTP_NOT_FOUND, | |
548 NULL, | |
549 "No such target id: " + target_id, | |
550 jsonp); | |
551 return; | |
552 } | |
553 | |
554 if (command == "activate") { | |
555 rvh->GetDelegate()->Activate(); | |
556 SendJson(connection_id, net::HTTP_OK, NULL, "Target activated", jsonp); | |
557 return; | |
558 } | |
559 | |
560 if (command == "close") { | |
561 rvh->ClosePage(); | |
562 SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing", jsonp); | |
563 return; | |
564 } | |
565 } | |
566 SendJson(connection_id, | |
567 net::HTTP_NOT_FOUND, | |
568 NULL, | |
569 "Unknown command: " + command, | |
570 jsonp); | |
571 return; | |
572 } | |
573 | |
574 void DevToolsHttpHandlerImpl::OnThumbnailRequestUI( | |
575 int connection_id, | |
576 const net::HttpServerRequestInfo& info) { | |
577 std::string prefix = "/thumb/"; | |
578 size_t pos = info.path.find(prefix); | |
579 if (pos != 0) { | |
580 Send404(connection_id); | |
581 return; | |
582 } | |
583 | |
584 std::string page_url = info.path.substr(prefix.length()); | |
585 std::string data = delegate_->GetPageThumbnailData(GURL(page_url)); | |
586 if (!data.empty()) | |
587 Send200(connection_id, data, "image/png"); | |
588 else | |
589 Send404(connection_id); | |
590 } | |
591 | |
592 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id) { | |
593 std::string response = delegate_->GetDiscoveryPageHTML(); | |
594 Send200(connection_id, response, "text/html; charset=UTF-8"); | |
595 } | |
596 | |
597 void DevToolsHttpHandlerImpl::OnWebSocketRequestUI( | |
598 int connection_id, | |
599 const net::HttpServerRequestInfo& request) { | |
600 if (!thread_.get()) | |
601 return; | |
602 std::string browser_prefix = "/devtools/browser"; | |
603 size_t browser_pos = request.path.find(browser_prefix); | |
604 if (browser_pos == 0) { | |
605 if (browser_target_) { | |
606 Send500(connection_id, "Another client already attached"); | |
607 return; | |
608 } | |
609 browser_target_.reset(new DevToolsBrowserTarget(connection_id)); | |
610 browser_target_->RegisterHandler(new DevToolsTracingHandler()); | |
611 AcceptWebSocket(connection_id, request); | |
612 return; | |
613 } | |
614 | |
615 std::string page_prefix = "/devtools/page/"; | |
616 size_t pos = request.path.find(page_prefix); | |
617 if (pos != 0) { | |
618 Send404(connection_id); | |
619 return; | |
620 } | |
621 | |
622 std::string page_id = request.path.substr(page_prefix.length()); | |
623 RenderViewHost* rvh = binding_->ForIdentifier(page_id); | |
624 if (!rvh) { | |
625 Send500(connection_id, "No such target id: " + page_id); | |
626 return; | |
627 } | |
628 | |
629 DevToolsManager* manager = DevToolsManager::GetInstance(); | |
630 DevToolsAgentHost* agent = DevToolsAgentHostRegistry::GetDevToolsAgentHost( | |
631 rvh); | |
632 if (manager->GetDevToolsClientHostFor(agent)) { | |
633 Send500(connection_id, | |
634 "Target with given id is being inspected: " + page_id); | |
635 return; | |
636 } | |
637 | |
638 DevToolsClientHostImpl* client_host = | |
639 new DevToolsClientHostImpl(thread_->message_loop(), | |
640 server_, | |
641 connection_id); | |
642 connection_to_client_host_ui_[connection_id] = client_host; | |
643 | |
644 manager->RegisterDevToolsClientHostFor(agent, client_host); | |
645 | |
646 AcceptWebSocket(connection_id, request); | |
647 } | |
648 | |
649 void DevToolsHttpHandlerImpl::OnWebSocketMessageUI( | |
650 int connection_id, | |
651 const std::string& data) { | |
652 if (browser_target_ && connection_id == browser_target_->connection_id()) { | |
653 std::string json_response = browser_target_->HandleMessage(data); | |
654 | |
655 thread_->message_loop()->PostTask( | |
656 FROM_HERE, | |
657 base::Bind(&net::HttpServer::SendOverWebSocket, | |
658 server_.get(), | |
659 connection_id, | |
660 json_response)); | |
661 return; | |
662 } | |
663 | |
664 ConnectionToClientHostMap::iterator it = | |
665 connection_to_client_host_ui_.find(connection_id); | |
666 if (it == connection_to_client_host_ui_.end()) | |
667 return; | |
668 | |
669 DevToolsManager* manager = DevToolsManager::GetInstance(); | |
670 manager->DispatchOnInspectorBackend(it->second, data); | |
671 } | |
672 | |
673 void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) { | |
674 ConnectionToClientHostMap::iterator it = | |
675 connection_to_client_host_ui_.find(connection_id); | |
676 if (it != connection_to_client_host_ui_.end()) { | |
677 DevToolsClientHostImpl* client_host = | |
678 static_cast<DevToolsClientHostImpl*>(it->second); | |
679 DevToolsManager::GetInstance()->ClientHostClosing(client_host); | |
680 delete client_host; | |
681 connection_to_client_host_ui_.erase(connection_id); | |
682 } | |
683 if (browser_target_ && browser_target_->connection_id() == connection_id) { | |
684 browser_target_.reset(); | |
685 return; | |
686 } | |
687 } | |
688 | |
689 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl( | |
690 const net::StreamListenSocketFactory* socket_factory, | |
691 const std::string& frontend_url, | |
692 DevToolsHttpHandlerDelegate* delegate) | |
693 : overridden_frontend_url_(frontend_url), | |
694 socket_factory_(socket_factory), | |
695 delegate_(delegate) { | |
696 if (overridden_frontend_url_.empty()) | |
697 overridden_frontend_url_ = "/devtools/devtools.html"; | |
698 | |
699 default_binding_.reset(new DevToolsDefaultBindingHandler); | |
700 binding_ = default_binding_.get(); | |
701 | |
702 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, | |
703 NotificationService::AllBrowserContextsAndSources()); | |
704 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED, | |
705 NotificationService::AllBrowserContextsAndSources()); | |
706 | |
707 // Balanced in ResetHandlerThreadAndRelease(). | |
708 AddRef(); | |
709 } | |
710 | |
711 // Runs on the handler thread | |
712 void DevToolsHttpHandlerImpl::Init() { | |
713 server_ = new net::HttpServer(*socket_factory_.get(), this); | |
714 } | |
715 | |
716 // Runs on the handler thread | |
717 void DevToolsHttpHandlerImpl::Teardown() { | |
718 server_ = NULL; | |
719 } | |
720 | |
721 // Runs on FILE thread to make sure that it is serialized against | |
722 // {Start|Stop}HandlerThread and to allow calling pthread_join. | |
723 void DevToolsHttpHandlerImpl::StopHandlerThread() { | |
724 if (!thread_->message_loop()) | |
725 return; | |
726 thread_->message_loop()->PostTask( | |
727 FROM_HERE, | |
728 base::Bind(&DevToolsHttpHandlerImpl::Teardown, this)); | |
729 // Thread::Stop joins the thread. | |
730 thread_->Stop(); | |
731 } | |
732 | |
733 void DevToolsHttpHandlerImpl::SendJson(int connection_id, | |
734 net::HttpStatusCode status_code, | |
735 Value* value, | |
736 const std::string& message, | |
737 const std::string& jsonp) { | |
738 if (!thread_.get()) | |
739 return; | |
740 | |
741 // Serialize value and message. | |
742 std::string json_value; | |
743 if (value) { | |
744 base::JSONWriter::WriteWithOptions(value, | |
745 base::JSONWriter::OPTIONS_PRETTY_PRINT, | |
746 &json_value); | |
747 } | |
748 std::string json_message; | |
749 scoped_ptr<Value> message_object(Value::CreateStringValue(message)); | |
750 base::JSONWriter::Write(message_object.get(), &json_message); | |
751 | |
752 std::string response; | |
753 std::string mime_type = "application/json; charset=UTF-8"; | |
754 | |
755 // Wrap jsonp if necessary. | |
756 if (!jsonp.empty()) { | |
757 mime_type = "text/javascript; charset=UTF-8"; | |
758 response = StringPrintf("%s(%s, %d, %s);", | |
759 jsonp.c_str(), | |
760 json_value.empty() ? "undefined" | |
761 : json_value.c_str(), | |
762 status_code, | |
763 json_message.c_str()); | |
764 // JSONP always returns 200. | |
765 status_code = net::HTTP_OK; | |
766 } else { | |
767 response = StringPrintf("%s%s", json_value.c_str(), message.c_str()); | |
768 } | |
769 | |
770 thread_->message_loop()->PostTask( | |
771 FROM_HERE, | |
772 base::Bind(&net::HttpServer::Send, | |
773 server_.get(), | |
774 connection_id, | |
775 status_code, | |
776 response, | |
777 mime_type)); | |
778 } | |
779 | |
780 void DevToolsHttpHandlerImpl::Send200(int connection_id, | |
781 const std::string& data, | |
782 const std::string& mime_type) { | |
783 if (!thread_.get()) | |
784 return; | |
785 thread_->message_loop()->PostTask( | |
786 FROM_HERE, | |
787 base::Bind(&net::HttpServer::Send200, | |
788 server_.get(), | |
789 connection_id, | |
790 data, | |
791 mime_type)); | |
792 } | |
793 | |
794 void DevToolsHttpHandlerImpl::Send404(int connection_id) { | |
795 if (!thread_.get()) | |
796 return; | |
797 thread_->message_loop()->PostTask( | |
798 FROM_HERE, | |
799 base::Bind(&net::HttpServer::Send404, server_.get(), connection_id)); | |
800 } | |
801 | |
802 void DevToolsHttpHandlerImpl::Send500(int connection_id, | |
803 const std::string& message) { | |
804 if (!thread_.get()) | |
805 return; | |
806 thread_->message_loop()->PostTask( | |
807 FROM_HERE, | |
808 base::Bind(&net::HttpServer::Send500, server_.get(), connection_id, | |
809 message)); | |
810 } | |
811 | |
812 void DevToolsHttpHandlerImpl::AcceptWebSocket( | |
813 int connection_id, | |
814 const net::HttpServerRequestInfo& request) { | |
815 if (!thread_.get()) | |
816 return; | |
817 thread_->message_loop()->PostTask( | |
818 FROM_HERE, | |
819 base::Bind(&net::HttpServer::AcceptWebSocket, server_.get(), | |
820 connection_id, request)); | |
821 } | |
822 | |
823 DevToolsHttpHandlerImpl::PageInfo | |
824 DevToolsHttpHandlerImpl::CreatePageInfo(RenderViewHost* rvh) | |
825 { | |
826 RenderViewHostDelegate* host_delegate = rvh->GetDelegate(); | |
827 DevToolsAgentHost* agent = | |
828 DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh); | |
829 DevToolsClientHost* client_host = DevToolsManager::GetInstance()-> | |
830 GetDevToolsClientHostFor(agent); | |
831 PageInfo page_info; | |
832 page_info.id = binding_->GetIdentifier(rvh); | |
833 page_info.attached = client_host != NULL; | |
834 page_info.url = host_delegate->GetURL().spec(); | |
835 | |
836 WebContents* web_contents = host_delegate->GetAsWebContents(); | |
837 if (web_contents) { | |
838 page_info.title = UTF16ToUTF8( | |
839 net::EscapeForHTML(web_contents->GetTitle())); | |
840 page_info.last_selected_time = web_contents->GetLastSelectedTime(); | |
841 | |
842 NavigationController& controller = web_contents->GetController(); | |
843 NavigationEntry* entry = controller.GetActiveEntry(); | |
844 if (entry != NULL && entry->GetURL().is_valid()) { | |
845 page_info.thumbnail_url = "/thumb/" + entry->GetURL().spec(); | |
846 page_info.favicon_url = entry->GetFavicon().url.spec(); | |
847 } | |
848 } | |
849 return page_info; | |
850 } | |
851 | |
852 DictionaryValue* DevToolsHttpHandlerImpl::SerializePageInfo( | |
853 const PageInfo& page_info, | |
854 const std::string& host) { | |
855 DictionaryValue* dictionary = new DictionaryValue; | |
856 dictionary->SetString("title", page_info.title); | |
857 dictionary->SetString("url", page_info.url); | |
858 dictionary->SetString("thumbnailUrl", page_info.thumbnail_url); | |
859 dictionary->SetString("faviconUrl", page_info.favicon_url); | |
860 if (!page_info.attached) { | |
861 dictionary->SetString("webSocketDebuggerUrl", | |
862 base::StringPrintf("ws://%s/devtools/page/%s", | |
863 host.c_str(), | |
864 page_info.id.c_str())); | |
865 std::string devtools_frontend_url = GetFrontendURLInternal( | |
866 page_info.id.c_str(), | |
867 host); | |
868 dictionary->SetString("devtoolsFrontendUrl", devtools_frontend_url); | |
869 } | |
870 return dictionary; | |
871 } | |
872 | |
873 } // namespace content | |
OLD | NEW |