inspect_ui.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// found in the LICENSE file.
45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/webui/inspect_ui.h"
65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <set>
85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/bind.h"
105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/bind_helpers.h"
115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/json/json_writer.h"
125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/memory/ref_counted_memory.h"
135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/string_number_conversions.h"
145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/string_util.h"
155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/utf_string_conversions.h"
165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/values.h"
175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/debugger/devtools_window.h"
185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/extensions/extension_service.h"
195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/profiles/profile.h"
205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/tab_contents/tab_contents.h"
215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/webui/chrome_url_data_manager_backend.h"
2453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#include "chrome/browser/ui/webui/chrome_web_ui_data_source.h"
255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/common/url_constants.h"
26c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)#include "content/public/browser/browser_thread.h"
275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/child_process_data.h"
2853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#include "content/public/browser/devtools_agent_host_registry.h"
295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/devtools_client_host.h"
30d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)#include "content/public/browser/devtools_manager.h"
315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/favicon_status.h"
325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/navigation_entry.h"
335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/notification_service.h"
345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/notification_source.h"
355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/notification_types.h"
3609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/render_process_host.h"
3709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/render_view_host.h"
385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/render_widget_host.h"
395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/web_contents.h"
405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/web_ui.h"
4109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/web_ui_message_handler.h"
425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/worker_service.h"
4309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/worker_service_observer.h"
445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/common/process_type.h"
455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "grit/browser_resources.h"
465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "grit/generated_resources.h"
475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "net/base/escape.h"
485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
49a854de003a23bf3c7f95ec0f8154ada64092ff5cTorne (Richard Coles)
500019e4eead4d990e4304c54a9028aca9122fb256Ben Murdochusing content::BrowserThread;
51c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)using content::ChildProcessData;
525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)using content::DevToolsAgentHost;
535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)using content::DevToolsAgentHostRegistry;
54using content::DevToolsClientHost;
55using content::DevToolsManager;
56using content::RenderProcessHost;
57using content::RenderViewHost;
58using content::RenderViewHostDelegate;
59using content::RenderWidgetHost;
60using content::WebContents;
61using content::WebUIMessageHandler;
62using content::WorkerService;
63using content::WorkerServiceObserver;
64
65static const char kDataFile[] = "targets-data.json";
66
67static const char kExtensionTargetType[]  = "extension";
68static const char kPageTargetType[]  = "page";
69static const char kWorkerTargetType[]  = "worker";
70
71static const char kInspectCommand[]  = "inspect";
72static const char kTerminateCommand[]  = "terminate";
73
74static const char kTargetTypeField[]  = "type";
75static const char kAttachedField[]  = "attached";
76static const char kProcessIdField[]  = "processId";
77static const char kRouteIdField[]  = "routeId";
78static const char kUrlField[]  = "url";
79static const char kNameField[]  = "name";
80static const char kFaviconUrlField[] = "favicon_url";
81static const char kPidField[]  = "pid";
82
83namespace {
84
85DictionaryValue* BuildTargetDescriptor(
86    const std::string& target_type,
87    bool attached,
88    const GURL& url,
89    const std::string& name,
90    const GURL& favicon_url,
91    int process_id,
92    int route_id,
93    base::ProcessHandle handle = base::kNullProcessHandle) {
94  DictionaryValue* target_data = new DictionaryValue();
95  target_data->SetString(kTargetTypeField, target_type);
96  target_data->SetBoolean(kAttachedField, attached);
97  target_data->SetInteger(kProcessIdField, process_id);
98  target_data->SetInteger(kRouteIdField, route_id);
99  target_data->SetString(kUrlField, url.spec());
100  target_data->SetString(kNameField, net::EscapeForHTML(name));
101  target_data->SetInteger(kPidField, base::GetProcId(handle));
102  target_data->SetString(kFaviconUrlField, favicon_url.spec());
103
104  return target_data;
105}
106
107bool HasClientHost(RenderViewHost* rvh) {
108  if (!DevToolsAgentHostRegistry::HasDevToolsAgentHost(rvh))
109    return false;
110
111  DevToolsAgentHost* agent =
112      DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh);
113  return !!DevToolsManager::GetInstance()->GetDevToolsClientHostFor(agent);
114}
115
116DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) {
117  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
118  std::string title;
119  std::string target_type = is_tab ? kPageTargetType : "";
120  GURL url;
121  GURL favicon_url;
122  if (web_contents) {
123    url = web_contents->GetURL();
124    title = UTF16ToUTF8(web_contents->GetTitle());
125    content::NavigationController& controller = web_contents->GetController();
126    content::NavigationEntry* entry = controller.GetActiveEntry();
127    if (entry != NULL && entry->GetURL().is_valid())
128      favicon_url = entry->GetFavicon().url;
129
130    Profile* profile = Profile::FromBrowserContext(
131        web_contents->GetBrowserContext());
132    if (profile) {
133      ExtensionService* extension_service = profile->GetExtensionService();
134      const extensions::Extension* extension = extension_service->
135          extensions()->GetByID(url.host());
136      if (extension) {
137        target_type = kExtensionTargetType;
138        title = extension->name();
139      }
140    }
141  }
142
143  return BuildTargetDescriptor(target_type,
144                               HasClientHost(rvh),
145                               url,
146                               title,
147                               favicon_url,
148                               rvh->GetProcess()->GetID(),
149                               rvh->GetRoutingID());
150}
151
152class InspectDataSource : public ChromeWebUIDataSource {
153 public:
154  InspectDataSource();
155
156  virtual void StartDataRequest(const std::string& path,
157                                bool is_incognito,
158                                int request_id);
159 private:
160  virtual ~InspectDataSource() {}
161  void SendDescriptors(int request_id, ListValue* rvh_list);
162  DISALLOW_COPY_AND_ASSIGN(InspectDataSource);
163};
164
165InspectDataSource::InspectDataSource()
166    : ChromeWebUIDataSource(chrome::kChromeUIInspectHost,
167                            MessageLoop::current()) {
168  add_resource_path("inspect.css", IDR_INSPECT_CSS);
169  add_resource_path("inspect.js", IDR_INSPECT_JS);
170  set_default_resource(IDR_INSPECT_HTML);
171}
172
173void InspectDataSource::StartDataRequest(const std::string& path,
174                                         bool is_incognito,
175                                         int request_id) {
176  if (path != kDataFile) {
177    ChromeWebUIDataSource::StartDataRequest(path, is_incognito, request_id);
178    return;
179  }
180
181  std::set<RenderViewHost*> tab_rvhs;
182  for (TabContentsIterator it; !it.done(); ++it)
183    tab_rvhs.insert(it->web_contents()->GetRenderViewHost());
184
185  scoped_ptr<ListValue> rvh_list(new ListValue());
186
187  for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
188       !it.IsAtEnd(); it.Advance()) {
189    RenderProcessHost* render_process_host = it.GetCurrentValue();
190    DCHECK(render_process_host);
191
192    // Ignore processes that don't have a connection, such as crashed tabs.
193    if (!render_process_host->HasConnection())
194      continue;
195
196    RenderProcessHost::RenderWidgetHostsIterator rwit(
197        render_process_host->GetRenderWidgetHostsIterator());
198    for (; !rwit.IsAtEnd(); rwit.Advance()) {
199      const RenderWidgetHost* widget = rwit.GetCurrentValue();
200      DCHECK(widget);
201      if (!widget || !widget->IsRenderView())
202        continue;
203
204      RenderViewHost* rvh =
205          RenderViewHost::From(const_cast<RenderWidgetHost*>(widget));
206
207      bool is_tab = tab_rvhs.find(rvh) != tab_rvhs.end();
208      rvh_list->Append(BuildTargetDescriptor(rvh, is_tab));
209    }
210  }
211
212  BrowserThread::PostTask(
213      BrowserThread::IO,
214      FROM_HERE,
215      base::Bind(&InspectDataSource::SendDescriptors,
216                 this,
217                 request_id,
218                 base::Owned(rvh_list.release())));
219}
220
221void InspectDataSource::SendDescriptors(int request_id,
222                                        ListValue* rvh_list) {
223  std::vector<WorkerService::WorkerInfo> worker_info =
224      WorkerService::GetInstance()->GetWorkers();
225  for (size_t i = 0; i < worker_info.size(); ++i) {
226    rvh_list->Append(BuildTargetDescriptor(
227        kWorkerTargetType,
228        false,
229        worker_info[i].url,
230        UTF16ToUTF8(worker_info[i].name),
231        GURL(),
232        worker_info[i].process_id,
233        worker_info[i].route_id,
234        worker_info[i].handle));
235  }
236
237  std::string json_string;
238  base::JSONWriter::Write(rvh_list, &json_string);
239
240  SendResponse(request_id, base::RefCountedString::TakeString(&json_string));
241}
242
243class InspectMessageHandler : public WebUIMessageHandler {
244 public:
245  InspectMessageHandler() {}
246  virtual ~InspectMessageHandler() {}
247
248 private:
249  // WebUIMessageHandler implementation.
250  virtual void RegisterMessages() OVERRIDE;
251
252  // Callback for "openDevTools" message.
253  void HandleInspectCommand(const ListValue* args);
254  void HandleTerminateCommand(const ListValue* args);
255
256  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
257};
258
259void InspectMessageHandler::RegisterMessages() {
260  web_ui()->RegisterMessageCallback(kInspectCommand,
261      base::Bind(&InspectMessageHandler::HandleInspectCommand,
262                 base::Unretained(this)));
263  web_ui()->RegisterMessageCallback(kTerminateCommand,
264      base::Bind(&InspectMessageHandler::HandleTerminateCommand,
265                 base::Unretained(this)));
266}
267
268void InspectMessageHandler::HandleInspectCommand(const ListValue* args) {
269  std::string process_id_str;
270  std::string route_id_str;
271  int process_id;
272  int route_id;
273  CHECK(args->GetSize() == 2);
274  CHECK(args->GetString(0, &process_id_str));
275  CHECK(args->GetString(1, &route_id_str));
276  CHECK(base::StringToInt(process_id_str,
277                          &process_id));
278  CHECK(base::StringToInt(route_id_str, &route_id));
279
280  Profile* profile = Profile::FromWebUI(web_ui());
281  if (!profile)
282    return;
283
284  RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
285  if (rvh) {
286    DevToolsWindow::OpenDevToolsWindow(rvh);
287    return;
288  }
289
290  DevToolsAgentHost* agent_host =
291      DevToolsAgentHostRegistry::GetDevToolsAgentHostForWorker(process_id,
292                                                               route_id);
293  if (agent_host)
294    DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host);
295}
296
297static void TerminateWorker(int process_id, int route_id) {
298  WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
299}
300
301void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) {
302  std::string process_id_str;
303  std::string route_id_str;
304  int process_id;
305  int route_id;
306  CHECK(args->GetSize() == 2);
307  CHECK(args->GetString(0, &process_id_str));
308  CHECK(args->GetString(1, &route_id_str));
309  CHECK(base::StringToInt(process_id_str,
310                          &process_id));
311  CHECK(base::StringToInt(route_id_str, &route_id));
312
313  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
314      base::Bind(&TerminateWorker, process_id, route_id));
315}
316
317}  // namespace
318
319class InspectUI::WorkerCreationDestructionListener
320    : public WorkerServiceObserver,
321      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
322 public:
323  explicit WorkerCreationDestructionListener(InspectUI* workers_ui)
324      : discovery_ui_(workers_ui) {
325    BrowserThread::PostTask(
326        BrowserThread::IO, FROM_HERE,
327        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
328                   this));
329  }
330
331  void InspectUIDestroyed() {
332    discovery_ui_ = NULL;
333    BrowserThread::PostTask(
334        BrowserThread::IO, FROM_HERE,
335        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
336                   this));
337  }
338
339 private:
340  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
341  virtual ~WorkerCreationDestructionListener() {}
342
343  virtual void WorkerCreated(
344      const GURL& url,
345      const string16& name,
346      int process_id,
347      int route_id) OVERRIDE {
348    BrowserThread::PostTask(
349        BrowserThread::UI, FROM_HERE,
350        base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged,
351                   this));
352  }
353
354  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
355    BrowserThread::PostTask(
356        BrowserThread::UI, FROM_HERE,
357        base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged,
358                   this));
359  }
360
361  void NotifyItemsChanged() {
362    if (discovery_ui_)
363      discovery_ui_->RefreshUI();
364  }
365
366  void RegisterObserver() {
367    WorkerService::GetInstance()->AddObserver(this);
368  }
369
370  void UnregisterObserver() {
371    WorkerService::GetInstance()->RemoveObserver(this);
372  }
373
374  InspectUI* discovery_ui_;
375};
376
377InspectUI::InspectUI(content::WebUI* web_ui)
378    : WebUIController(web_ui),
379      observer_(new WorkerCreationDestructionListener(this)) {
380  web_ui->AddMessageHandler(new InspectMessageHandler());
381
382  InspectDataSource* html_source = new InspectDataSource();
383
384  Profile* profile = Profile::FromWebUI(web_ui);
385  ChromeURLDataManager::AddDataSource(profile, html_source);
386
387  registrar_.Add(this,
388                 content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
389                 content::NotificationService::AllSources());
390  registrar_.Add(this,
391                 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
392                 content::NotificationService::AllSources());
393  registrar_.Add(this,
394                 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
395                 content::NotificationService::AllSources());
396}
397
398InspectUI::~InspectUI() {
399  StopListeningNotifications();
400}
401
402void InspectUI::RefreshUI() {
403  web_ui()->CallJavascriptFunction("populateLists");
404}
405
406void InspectUI::Observe(int type,
407    const content::NotificationSource& source,
408    const content::NotificationDetails& details) {
409  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
410    RefreshUI();
411  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
412    StopListeningNotifications();
413}
414
415void InspectUI::StopListeningNotifications()
416{
417  if (!observer_)
418    return;
419  observer_->InspectUIDestroyed();
420  observer_ = NULL;
421  registrar_.RemoveAll();
422}
423