1 // Copyright (c) 2011 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 "chrome/browser/debugger/devtools_http_protocol_handler.h"
6
7 #include <utility>
8
9 #include "base/compiler_specific.h"
10 #include "base/json/json_writer.h"
11 #include "base/logging.h"
12 #include "base/message_loop_proxy.h"
13 #include "base/string_number_conversions.h"
14 #include "base/threading/thread.h"
15 #include "base/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "chrome/browser/debugger/devtools_client_host.h"
18 #include "chrome/browser/debugger/devtools_manager.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
21 #include "chrome/browser/ui/webui/devtools_ui.h"
22 #include "chrome/common/devtools_messages.h"
23 #include "content/browser/browser_thread.h"
24 #include "content/browser/tab_contents/tab_contents.h"
25 #include "googleurl/src/gurl.h"
26 #include "net/base/io_buffer.h"
27 #include "net/server/http_server_request_info.h"
28 #include "net/url_request/url_request_context.h"
29 #include "net/url_request/url_request_context_getter.h"
30
31 const int kBufferSize = 16 * 1024;
32
33 namespace {
34
35 // An internal implementation of DevToolsClientHost that delegates
36 // messages sent for DevToolsClient to a DebuggerShell instance.
37 class DevToolsClientHostImpl : public DevToolsClientHost {
38 public:
DevToolsClientHostImpl(net::HttpServer * server,int connection_id)39 DevToolsClientHostImpl(
40 net::HttpServer* server,
41 int connection_id)
42 : server_(server),
43 connection_id_(connection_id) {
44 }
~DevToolsClientHostImpl()45 ~DevToolsClientHostImpl() {}
46
47 // DevToolsClientHost interface
InspectedTabClosing()48 virtual void InspectedTabClosing() {
49 BrowserThread::PostTask(
50 BrowserThread::IO,
51 FROM_HERE,
52 NewRunnableMethod(server_,
53 &net::HttpServer::Close,
54 connection_id_));
55 }
56
SendMessageToClient(const IPC::Message & msg)57 virtual void SendMessageToClient(const IPC::Message& msg) {
58 IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg)
59 IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
60 OnDispatchOnInspectorFrontend);
61 IPC_MESSAGE_UNHANDLED_ERROR()
62 IPC_END_MESSAGE_MAP()
63 }
64
TabReplaced(TabContentsWrapper * new_tab)65 virtual void TabReplaced(TabContentsWrapper* new_tab) {
66 }
67
NotifyCloseListener()68 void NotifyCloseListener() {
69 DevToolsClientHost::NotifyCloseListener();
70 }
71 private:
72 // Message handling routines
OnDispatchOnInspectorFrontend(const std::string & data)73 void OnDispatchOnInspectorFrontend(const std::string& data) {
74 BrowserThread::PostTask(
75 BrowserThread::IO,
76 FROM_HERE,
77 NewRunnableMethod(server_,
78 &net::HttpServer::SendOverWebSocket,
79 connection_id_,
80 data));
81 }
82
FrameNavigating(const std::string & url)83 virtual void FrameNavigating(const std::string& url) {}
84 net::HttpServer* server_;
85 int connection_id_;
86 };
87
88 } // namespace
89
90
91 // static
Start(const std::string & ip,int port,const std::string & frontend_url,TabContentsProvider * provider)92 scoped_refptr<DevToolsHttpProtocolHandler> DevToolsHttpProtocolHandler::Start(
93 const std::string& ip,
94 int port,
95 const std::string& frontend_url,
96 TabContentsProvider* provider) {
97 scoped_refptr<DevToolsHttpProtocolHandler> http_handler =
98 new DevToolsHttpProtocolHandler(ip, port, frontend_url, provider);
99 http_handler->Start();
100 return http_handler;
101 }
102
~DevToolsHttpProtocolHandler()103 DevToolsHttpProtocolHandler::~DevToolsHttpProtocolHandler() {
104 // Stop() must be called prior to this being called
105 DCHECK(server_.get() == NULL);
106 }
107
Start()108 void DevToolsHttpProtocolHandler::Start() {
109 BrowserThread::PostTask(
110 BrowserThread::IO, FROM_HERE,
111 NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Init));
112 }
113
Stop()114 void DevToolsHttpProtocolHandler::Stop() {
115 BrowserThread::PostTask(
116 BrowserThread::IO, FROM_HERE,
117 NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Teardown));
118 }
119
OnHttpRequest(int connection_id,const net::HttpServerRequestInfo & info)120 void DevToolsHttpProtocolHandler::OnHttpRequest(
121 int connection_id,
122 const net::HttpServerRequestInfo& info) {
123 if (info.path == "" || info.path == "/") {
124 // Pages discovery request.
125 BrowserThread::PostTask(
126 BrowserThread::UI,
127 FROM_HERE,
128 NewRunnableMethod(this,
129 &DevToolsHttpProtocolHandler::OnRootRequestUI,
130 connection_id,
131 info));
132 return;
133 }
134
135 if (info.path == "/json") {
136 // Pages discovery json request.
137 BrowserThread::PostTask(
138 BrowserThread::UI,
139 FROM_HERE,
140 NewRunnableMethod(this,
141 &DevToolsHttpProtocolHandler::OnJsonRequestUI,
142 connection_id,
143 info));
144 return;
145 }
146
147 size_t pos = info.path.find("/devtools/");
148 if (pos != 0) {
149 server_->Send404(connection_id);
150 return;
151 }
152
153 // Proxy static files from chrome-devtools://devtools/*.
154 if (!Profile::GetDefaultRequestContext()) {
155 server_->Send404(connection_id);
156 return;
157 }
158
159 // Make sure DevTools data source is registered.
160 DevToolsUI::RegisterDevToolsDataSource();
161
162 net::URLRequest* request = new net::URLRequest(
163 GURL("chrome-devtools:/" + info.path), this);
164 Bind(request, connection_id);
165 request->set_context(
166 Profile::GetDefaultRequestContext()->GetURLRequestContext());
167 request->Start();
168 }
169
OnWebSocketRequest(int connection_id,const net::HttpServerRequestInfo & request)170 void DevToolsHttpProtocolHandler::OnWebSocketRequest(
171 int connection_id,
172 const net::HttpServerRequestInfo& request) {
173 BrowserThread::PostTask(
174 BrowserThread::UI,
175 FROM_HERE,
176 NewRunnableMethod(
177 this,
178 &DevToolsHttpProtocolHandler::OnWebSocketRequestUI,
179 connection_id,
180 request));
181 }
182
OnWebSocketMessage(int connection_id,const std::string & data)183 void DevToolsHttpProtocolHandler::OnWebSocketMessage(
184 int connection_id,
185 const std::string& data) {
186 BrowserThread::PostTask(
187 BrowserThread::UI,
188 FROM_HERE,
189 NewRunnableMethod(
190 this,
191 &DevToolsHttpProtocolHandler::OnWebSocketMessageUI,
192 connection_id,
193 data));
194 }
195
OnClose(int connection_id)196 void DevToolsHttpProtocolHandler::OnClose(int connection_id) {
197 ConnectionToRequestsMap::iterator it =
198 connection_to_requests_io_.find(connection_id);
199 if (it != connection_to_requests_io_.end()) {
200 // Dispose delegating socket.
201 for (std::set<net::URLRequest*>::iterator it2 = it->second.begin();
202 it2 != it->second.end(); ++it2) {
203 net::URLRequest* request = *it2;
204 request->Cancel();
205 request_to_connection_io_.erase(request);
206 request_to_buffer_io_.erase(request);
207 delete request;
208 }
209 connection_to_requests_io_.erase(connection_id);
210 }
211
212 BrowserThread::PostTask(
213 BrowserThread::UI,
214 FROM_HERE,
215 NewRunnableMethod(
216 this,
217 &DevToolsHttpProtocolHandler::OnCloseUI,
218 connection_id));
219 }
220
221 struct PageInfo
222 {
223 int id;
224 std::string url;
225 bool attached;
226 std::string title;
227 std::string favicon_url;
228 };
229 typedef std::vector<PageInfo> PageList;
230
GeneratePageList(DevToolsHttpProtocolHandler::TabContentsProvider * tab_contents_provider,int connection_id,const net::HttpServerRequestInfo & info)231 static PageList GeneratePageList(
232 DevToolsHttpProtocolHandler::TabContentsProvider* tab_contents_provider,
233 int connection_id,
234 const net::HttpServerRequestInfo& info) {
235 typedef DevToolsHttpProtocolHandler::InspectableTabs Tabs;
236 Tabs inspectable_tabs = tab_contents_provider->GetInspectableTabs();
237
238 PageList page_list;
239 for (Tabs::iterator it = inspectable_tabs.begin();
240 it != inspectable_tabs.end(); ++it) {
241
242 TabContentsWrapper* tab_contents = *it;
243 NavigationController& controller = tab_contents->controller();
244
245 NavigationEntry* entry = controller.GetActiveEntry();
246 if (entry == NULL || !entry->url().is_valid())
247 continue;
248
249 DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
250 GetDevToolsClientHostFor(tab_contents->tab_contents()->
251 render_view_host());
252 PageInfo page_info;
253 page_info.id = controller.session_id().id();
254 page_info.attached = client_host != NULL;
255 page_info.url = entry->url().spec();
256 page_info.title = UTF16ToUTF8(entry->title());
257 page_info.favicon_url = entry->favicon().url().spec();
258 page_list.push_back(page_info);
259 }
260 return page_list;
261 }
262
OnRootRequestUI(int connection_id,const net::HttpServerRequestInfo & info)263 void DevToolsHttpProtocolHandler::OnRootRequestUI(
264 int connection_id,
265 const net::HttpServerRequestInfo& info) {
266 std::string host = info.headers["Host"];
267 std::string response = "<html><body>";
268 PageList page_list = GeneratePageList(tab_contents_provider_.get(),
269 connection_id, info);
270 for (PageList::iterator i = page_list.begin();
271 i != page_list.end(); ++i) {
272
273 std::string frontendURL = StringPrintf("%s?host=%s&page=%d",
274 overriden_frontend_url_.c_str(),
275 host.c_str(),
276 i->id);
277 response += "<div>";
278 response += StringPrintf(
279 "<img style=\"margin-right:5px;width:16px;height:16px\" src=\"%s\">",
280 i->favicon_url.c_str());
281
282 if (i->attached) {
283 response += i->url.c_str();
284 } else {
285 response += StringPrintf("<a href=\"%s\">%s</a><br>",
286 frontendURL.c_str(),
287 i->url.c_str());
288 }
289 response += "</div>";
290 }
291 response += "</body></html>";
292 Send200(connection_id, response, "text/html; charset=UTF-8");
293 }
294
OnJsonRequestUI(int connection_id,const net::HttpServerRequestInfo & info)295 void DevToolsHttpProtocolHandler::OnJsonRequestUI(
296 int connection_id,
297 const net::HttpServerRequestInfo& info) {
298 PageList page_list = GeneratePageList(tab_contents_provider_.get(),
299 connection_id, info);
300 ListValue json_pages_list;
301 std::string host = info.headers["Host"];
302 for (PageList::iterator i = page_list.begin();
303 i != page_list.end(); ++i) {
304
305 DictionaryValue* page_info = new DictionaryValue;
306 json_pages_list.Append(page_info);
307 page_info->SetString("title", i->title);
308 page_info->SetString("url", i->url);
309 page_info->SetString("faviconUrl", i->favicon_url);
310 if (!i->attached) {
311 page_info->SetString("webSocketDebuggerUrl",
312 StringPrintf("ws://%s/devtools/page/%d",
313 host.c_str(),
314 i->id));
315 page_info->SetString(
316 "devtoolsFrontendUrl",
317 StringPrintf("http://%s/devtools/devtools.html?page=%d",
318 host.c_str(),
319 i->id));
320 }
321 }
322
323 std::string response;
324 base::JSONWriter::Write(&json_pages_list, true, &response);
325 Send200(connection_id, response, "application/json; charset=UTF-8");
326 }
327
OnWebSocketRequestUI(int connection_id,const net::HttpServerRequestInfo & request)328 void DevToolsHttpProtocolHandler::OnWebSocketRequestUI(
329 int connection_id,
330 const net::HttpServerRequestInfo& request) {
331 std::string prefix = "/devtools/page/";
332 size_t pos = request.path.find(prefix);
333 if (pos != 0) {
334 Send404(connection_id);
335 return;
336 }
337 std::string page_id = request.path.substr(prefix.length());
338 int id = 0;
339 if (!base::StringToInt(page_id, &id)) {
340 Send500(connection_id, "Invalid page id: " + page_id);
341 return;
342 }
343
344 TabContents* tab_contents = GetTabContents(id);
345 if (tab_contents == NULL) {
346 Send500(connection_id, "No such page id: " + page_id);
347 return;
348 }
349
350 DevToolsManager* manager = DevToolsManager::GetInstance();
351 if (manager->GetDevToolsClientHostFor(tab_contents->render_view_host())) {
352 Send500(connection_id, "Page with given id is being inspected: " + page_id);
353 return;
354 }
355
356 DevToolsClientHostImpl* client_host =
357 new DevToolsClientHostImpl(server_, connection_id);
358 connection_to_client_host_ui_[connection_id] = client_host;
359
360 manager->RegisterDevToolsClientHostFor(
361 tab_contents->render_view_host(),
362 client_host);
363 manager->ForwardToDevToolsAgent(
364 client_host,
365 DevToolsAgentMsg_FrontendLoaded());
366
367 AcceptWebSocket(connection_id, request);
368 }
369
OnWebSocketMessageUI(int connection_id,const std::string & data)370 void DevToolsHttpProtocolHandler::OnWebSocketMessageUI(
371 int connection_id,
372 const std::string& data) {
373 ConnectionToClientHostMap::iterator it =
374 connection_to_client_host_ui_.find(connection_id);
375 if (it == connection_to_client_host_ui_.end())
376 return;
377
378 DevToolsManager* manager = DevToolsManager::GetInstance();
379 manager->ForwardToDevToolsAgent(
380 it->second,
381 DevToolsAgentMsg_DispatchOnInspectorBackend(data));
382 }
383
OnCloseUI(int connection_id)384 void DevToolsHttpProtocolHandler::OnCloseUI(int connection_id) {
385 ConnectionToClientHostMap::iterator it =
386 connection_to_client_host_ui_.find(connection_id);
387 if (it != connection_to_client_host_ui_.end()) {
388 DevToolsClientHostImpl* client_host =
389 static_cast<DevToolsClientHostImpl*>(it->second);
390 client_host->NotifyCloseListener();
391 delete client_host;
392 connection_to_client_host_ui_.erase(connection_id);
393 }
394 }
395
OnResponseStarted(net::URLRequest * request)396 void DevToolsHttpProtocolHandler::OnResponseStarted(net::URLRequest* request) {
397 RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
398 if (it == request_to_connection_io_.end())
399 return;
400
401 int connection_id = it->second;
402
403 std::string content_type;
404 request->GetMimeType(&content_type);
405
406 if (request->status().is_success()) {
407 server_->Send(connection_id, StringPrintf("HTTP/1.1 200 OK\r\n"
408 "Content-Type:%s\r\n"
409 "Transfer-Encoding: chunked\r\n"
410 "\r\n",
411 content_type.c_str()));
412 } else {
413 server_->Send404(connection_id);
414 }
415
416 int bytes_read = 0;
417 // Some servers may treat HEAD requests as GET requests. To free up the
418 // network connection as soon as possible, signal that the request has
419 // completed immediately, without trying to read any data back (all we care
420 // about is the response code and headers, which we already have).
421 net::IOBuffer* buffer = request_to_buffer_io_[request].get();
422 if (request->status().is_success())
423 request->Read(buffer, kBufferSize, &bytes_read);
424 OnReadCompleted(request, bytes_read);
425 }
426
OnReadCompleted(net::URLRequest * request,int bytes_read)427 void DevToolsHttpProtocolHandler::OnReadCompleted(net::URLRequest* request,
428 int bytes_read) {
429 RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
430 if (it == request_to_connection_io_.end())
431 return;
432
433 int connection_id = it->second;
434
435 net::IOBuffer* buffer = request_to_buffer_io_[request].get();
436 do {
437 if (!request->status().is_success() || bytes_read <= 0)
438 break;
439 std::string chunk_size = StringPrintf("%X\r\n", bytes_read);
440 server_->Send(connection_id, chunk_size);
441 server_->Send(connection_id, buffer->data(), bytes_read);
442 server_->Send(connection_id, "\r\n");
443 } while (request->Read(buffer, kBufferSize, &bytes_read));
444
445
446 // See comments re: HEAD requests in OnResponseStarted().
447 if (!request->status().is_io_pending()) {
448 server_->Send(connection_id, "0\r\n\r\n");
449 RequestCompleted(request);
450 }
451 }
452
DevToolsHttpProtocolHandler(const std::string & ip,int port,const std::string & frontend_host,TabContentsProvider * provider)453 DevToolsHttpProtocolHandler::DevToolsHttpProtocolHandler(
454 const std::string& ip,
455 int port,
456 const std::string& frontend_host,
457 TabContentsProvider* provider)
458 : ip_(ip),
459 port_(port),
460 overriden_frontend_url_(frontend_host),
461 tab_contents_provider_(provider) {
462 if (overriden_frontend_url_.empty())
463 overriden_frontend_url_ = "/devtools/devtools.html";
464 }
465
Init()466 void DevToolsHttpProtocolHandler::Init() {
467 server_ = new net::HttpServer(ip_, port_, this);
468 }
469
470 // Run on I/O thread
Teardown()471 void DevToolsHttpProtocolHandler::Teardown() {
472 server_ = NULL;
473 }
474
Bind(net::URLRequest * request,int connection_id)475 void DevToolsHttpProtocolHandler::Bind(net::URLRequest* request,
476 int connection_id) {
477 request_to_connection_io_[request] = connection_id;
478 ConnectionToRequestsMap::iterator it =
479 connection_to_requests_io_.find(connection_id);
480 if (it == connection_to_requests_io_.end()) {
481 std::pair<int, std::set<net::URLRequest*> > value(
482 connection_id,
483 std::set<net::URLRequest*>());
484 it = connection_to_requests_io_.insert(value).first;
485 }
486 it->second.insert(request);
487 request_to_buffer_io_[request] = new net::IOBuffer(kBufferSize);
488 }
489
RequestCompleted(net::URLRequest * request)490 void DevToolsHttpProtocolHandler::RequestCompleted(net::URLRequest* request) {
491 RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
492 if (it == request_to_connection_io_.end())
493 return;
494
495 int connection_id = it->second;
496 request_to_connection_io_.erase(request);
497 ConnectionToRequestsMap::iterator it2 =
498 connection_to_requests_io_.find(connection_id);
499 it2->second.erase(request);
500 request_to_buffer_io_.erase(request);
501 delete request;
502 }
503
Send200(int connection_id,const std::string & data,const std::string & mime_type)504 void DevToolsHttpProtocolHandler::Send200(int connection_id,
505 const std::string& data,
506 const std::string& mime_type) {
507 BrowserThread::PostTask(
508 BrowserThread::IO, FROM_HERE,
509 NewRunnableMethod(server_.get(),
510 &net::HttpServer::Send200,
511 connection_id,
512 data,
513 mime_type));
514 }
515
Send404(int connection_id)516 void DevToolsHttpProtocolHandler::Send404(int connection_id) {
517 BrowserThread::PostTask(
518 BrowserThread::IO, FROM_HERE,
519 NewRunnableMethod(server_.get(),
520 &net::HttpServer::Send404,
521 connection_id));
522 }
523
Send500(int connection_id,const std::string & message)524 void DevToolsHttpProtocolHandler::Send500(int connection_id,
525 const std::string& message) {
526 BrowserThread::PostTask(
527 BrowserThread::IO, FROM_HERE,
528 NewRunnableMethod(server_.get(),
529 &net::HttpServer::Send500,
530 connection_id,
531 message));
532 }
533
AcceptWebSocket(int connection_id,const net::HttpServerRequestInfo & request)534 void DevToolsHttpProtocolHandler::AcceptWebSocket(
535 int connection_id,
536 const net::HttpServerRequestInfo& request) {
537 BrowserThread::PostTask(
538 BrowserThread::IO, FROM_HERE,
539 NewRunnableMethod(server_.get(),
540 &net::HttpServer::AcceptWebSocket,
541 connection_id,
542 request));
543 }
544
GetTabContents(int session_id)545 TabContents* DevToolsHttpProtocolHandler::GetTabContents(int session_id) {
546 InspectableTabs inspectable_tabs =
547 tab_contents_provider_->GetInspectableTabs();
548
549 for (InspectableTabs::iterator it = inspectable_tabs.begin();
550 it != inspectable_tabs.end(); ++it) {
551 TabContentsWrapper* tab_contents = *it;
552 NavigationController& controller =
553 tab_contents->controller();
554 if (controller.session_id().id() == session_id)
555 return controller.tab_contents();
556 }
557 return NULL;
558 }
559