inspect_ui.cc revision 558790d6acca3451cf3a6b497803a5f07d0bec58
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 "chrome/browser/ui/webui/inspect_ui.h"
6
7#include <set>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/json/json_writer.h"
12#include "base/memory/ref_counted_memory.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/string_util.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/values.h"
17#include "chrome/browser/devtools/devtools_window.h"
18#include "chrome/browser/extensions/extension_service.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
21#include "chrome/common/url_constants.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/browser/child_process_data.h"
24#include "content/public/browser/devtools_agent_host.h"
25#include "content/public/browser/devtools_client_host.h"
26#include "content/public/browser/devtools_manager.h"
27#include "content/public/browser/favicon_status.h"
28#include "content/public/browser/navigation_entry.h"
29#include "content/public/browser/notification_service.h"
30#include "content/public/browser/notification_source.h"
31#include "content/public/browser/notification_types.h"
32#include "content/public/browser/render_process_host.h"
33#include "content/public/browser/render_view_host.h"
34#include "content/public/browser/web_contents.h"
35#include "content/public/browser/web_ui.h"
36#include "content/public/browser/web_ui_data_source.h"
37#include "content/public/browser/web_ui_message_handler.h"
38#include "content/public/browser/worker_service.h"
39#include "content/public/browser/worker_service_observer.h"
40#include "content/public/common/process_type.h"
41#include "grit/browser_resources.h"
42#include "grit/generated_resources.h"
43#include "net/base/escape.h"
44#include "net/base/net_errors.h"
45#include "ui/base/resource/resource_bundle.h"
46
47using content::BrowserThread;
48using content::ChildProcessData;
49using content::DevToolsAgentHost;
50using content::DevToolsClientHost;
51using content::DevToolsManager;
52using content::RenderProcessHost;
53using content::RenderViewHost;
54using content::RenderViewHostDelegate;
55using content::RenderWidgetHost;
56using content::WebContents;
57using content::WebUIMessageHandler;
58using content::WorkerService;
59using content::WorkerServiceObserver;
60
61namespace {
62
63static const char kDataFile[] = "targets-data.json";
64static const char kAdbPages[] = "adb-pages";
65
66static const char kAppTargetType[] = "app";
67static const char kExtensionTargetType[]  = "extension";
68static const char kPageTargetType[]  = "page";
69static const char kWorkerTargetType[]  = "worker";
70static const char kAdbTargetType[]  = "adb_page";
71
72static const char kInitUICommand[]  = "init-ui";
73static const char kInspectCommand[]  = "inspect";
74static const char kTerminateCommand[]  = "terminate";
75
76static const char kTargetTypeField[]  = "type";
77static const char kAttachedField[]  = "attached";
78static const char kProcessIdField[]  = "processId";
79static const char kRouteIdField[]  = "routeId";
80static const char kUrlField[]  = "url";
81static const char kNameField[]  = "name";
82static const char kFaviconUrlField[] = "faviconUrl";
83static const char kPidField[]  = "pid";
84static const char kAdbSerialField[] = "adbSerial";
85static const char kAdbModelField[] = "adbModel";
86static const char kAdbPackageField[] = "adbPackage";
87static const char kAdbPageIdField[] = "adbPageId";
88
89DictionaryValue* BuildTargetDescriptor(
90    const std::string& target_type,
91    bool attached,
92    const GURL& url,
93    const std::string& name,
94    const GURL& favicon_url,
95    int process_id,
96    int route_id,
97    base::ProcessHandle handle = base::kNullProcessHandle) {
98  DictionaryValue* target_data = new DictionaryValue();
99  target_data->SetString(kTargetTypeField, target_type);
100  target_data->SetBoolean(kAttachedField, attached);
101  target_data->SetInteger(kProcessIdField, process_id);
102  target_data->SetInteger(kRouteIdField, route_id);
103  target_data->SetString(kUrlField, url.spec());
104  target_data->SetString(kNameField, net::EscapeForHTML(name));
105  target_data->SetInteger(kPidField, base::GetProcId(handle));
106  target_data->SetString(kFaviconUrlField, favicon_url.spec());
107
108  return target_data;
109}
110
111bool HasClientHost(RenderViewHost* rvh) {
112  if (!DevToolsAgentHost::HasFor(rvh))
113    return false;
114
115  scoped_refptr<DevToolsAgentHost> agent(
116      DevToolsAgentHost::GetOrCreateFor(rvh));
117  return agent->IsAttached();
118}
119
120DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) {
121  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
122  std::string title;
123  std::string target_type = is_tab ? kPageTargetType : "";
124  GURL url;
125  GURL favicon_url;
126  if (web_contents) {
127    url = web_contents->GetURL();
128    title = UTF16ToUTF8(web_contents->GetTitle());
129    content::NavigationController& controller = web_contents->GetController();
130    content::NavigationEntry* entry = controller.GetActiveEntry();
131    if (entry != NULL && entry->GetURL().is_valid())
132      favicon_url = entry->GetFavicon().url;
133
134    Profile* profile = Profile::FromBrowserContext(
135        web_contents->GetBrowserContext());
136    if (profile) {
137      ExtensionService* extension_service = profile->GetExtensionService();
138      const extensions::Extension* extension = extension_service->
139          extensions()->GetByID(url.host());
140      if (extension) {
141        if (extension->is_hosted_app()
142            || extension->is_legacy_packaged_app()
143            || extension->is_platform_app())
144          target_type = kAppTargetType;
145        else
146          target_type = kExtensionTargetType;
147        title = extension->name();
148      }
149    }
150  }
151
152  return BuildTargetDescriptor(target_type,
153                               HasClientHost(rvh),
154                               url,
155                               title,
156                               favicon_url,
157                               rvh->GetProcess()->GetID(),
158                               rvh->GetRoutingID());
159}
160
161class InspectMessageHandler : public WebUIMessageHandler {
162 public:
163  explicit InspectMessageHandler(InspectUI* inspect_ui)
164      : inspect_ui_(inspect_ui) {}
165  virtual ~InspectMessageHandler() {}
166
167 private:
168  // WebUIMessageHandler implementation.
169  virtual void RegisterMessages() OVERRIDE;
170
171  void HandleInitUICommand(const ListValue* args);
172  void HandleInspectCommand(const ListValue* args);
173  void HandleTerminateCommand(const ListValue* args);
174
175  bool GetProcessAndRouteId(const ListValue* args,
176                            int* process_id,
177                            int* route_id);
178
179  InspectUI* inspect_ui_;
180
181  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
182};
183
184void InspectMessageHandler::RegisterMessages() {
185  web_ui()->RegisterMessageCallback(kInitUICommand,
186      base::Bind(&InspectMessageHandler::HandleInitUICommand,
187                 base::Unretained(this)));
188  web_ui()->RegisterMessageCallback(kInspectCommand,
189      base::Bind(&InspectMessageHandler::HandleInspectCommand,
190                 base::Unretained(this)));
191  web_ui()->RegisterMessageCallback(kTerminateCommand,
192      base::Bind(&InspectMessageHandler::HandleTerminateCommand,
193                 base::Unretained(this)));
194}
195
196void InspectMessageHandler::HandleInitUICommand(const ListValue*) {
197  inspect_ui_->InitUI();
198}
199
200void InspectMessageHandler::HandleInspectCommand(const ListValue* args) {
201  Profile* profile = Profile::FromWebUI(web_ui());
202  if (!profile)
203    return;
204
205  int process_id;
206  int route_id;
207  if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0
208      || route_id == 0) {
209    // Check for ADB page id
210    const DictionaryValue* data;
211    std::string page_id;
212    if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
213        data->GetString(kAdbPageIdField, &page_id)) {
214      scoped_refptr<DevToolsAdbBridge> adb_bridge =
215          DevToolsAdbBridge::Factory::GetForProfile(profile);
216      if (adb_bridge)
217        adb_bridge->Attach(page_id);
218    }
219    return;
220  }
221
222  RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
223  if (rvh) {
224    DevToolsWindow::OpenDevToolsWindow(rvh);
225    return;
226  }
227
228  scoped_refptr<DevToolsAgentHost> agent_host(
229      DevToolsAgentHost::GetForWorker(process_id, route_id));
230  if (!agent_host.get())
231    return;
232
233  DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get());
234}
235
236static void TerminateWorker(int process_id, int route_id) {
237  WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
238}
239
240void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) {
241  int process_id;
242  int route_id;
243  if (!GetProcessAndRouteId(args, &process_id, &route_id))
244    return;
245
246  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
247      base::Bind(&TerminateWorker, process_id, route_id));
248}
249
250bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args,
251                                                 int* process_id,
252                                                 int* route_id) {
253  const DictionaryValue* data;
254  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
255      data->GetInteger(kProcessIdField, process_id) &&
256      data->GetInteger(kRouteIdField, route_id)) {
257    return true;
258  }
259  return false;
260}
261
262}  // namespace
263
264class InspectUI::WorkerCreationDestructionListener
265    : public WorkerServiceObserver,
266      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
267 public:
268  WorkerCreationDestructionListener()
269      : discovery_ui_(NULL) {}
270
271  void Init(InspectUI* workers_ui) {
272    DCHECK(workers_ui);
273    DCHECK(!discovery_ui_);
274    discovery_ui_ = workers_ui;
275    BrowserThread::PostTask(
276        BrowserThread::IO, FROM_HERE,
277        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
278                   this));
279  }
280
281  void InspectUIDestroyed() {
282    DCHECK(discovery_ui_);
283    discovery_ui_ = NULL;
284    BrowserThread::PostTask(
285        BrowserThread::IO, FROM_HERE,
286        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
287                   this));
288  }
289
290  void InitUI() {
291    BrowserThread::PostTask(
292        BrowserThread::IO, FROM_HERE,
293        base::Bind(&WorkerCreationDestructionListener::CollectWorkersData,
294                   this));
295  }
296
297 private:
298  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
299  virtual ~WorkerCreationDestructionListener() {}
300
301  virtual void WorkerCreated(
302      const GURL& url,
303      const string16& name,
304      int process_id,
305      int route_id) OVERRIDE {
306    CollectWorkersData();
307  }
308
309  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
310    CollectWorkersData();
311  }
312
313  void CollectWorkersData() {
314    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
315    scoped_ptr<ListValue> target_list(new ListValue());
316    std::vector<WorkerService::WorkerInfo> worker_info =
317        WorkerService::GetInstance()->GetWorkers();
318    for (size_t i = 0; i < worker_info.size(); ++i) {
319      target_list->Append(BuildTargetDescriptor(
320          kWorkerTargetType,
321          false,
322          worker_info[i].url,
323          UTF16ToUTF8(worker_info[i].name),
324          GURL(),
325          worker_info[i].process_id,
326          worker_info[i].route_id,
327          worker_info[i].handle));
328    }
329
330    BrowserThread::PostTask(
331        BrowserThread::UI, FROM_HERE,
332        base::Bind(&WorkerCreationDestructionListener::PopulateWorkersList,
333                   this, base::Owned(target_list.release())));
334  }
335
336  void RegisterObserver() {
337    WorkerService::GetInstance()->AddObserver(this);
338  }
339
340  void UnregisterObserver() {
341    WorkerService::GetInstance()->RemoveObserver(this);
342  }
343
344  void PopulateWorkersList(ListValue* target_list) {
345    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
346    if (discovery_ui_) {
347      discovery_ui_->web_ui()->CallJavascriptFunction(
348          "populateWorkersList", *target_list);
349    }
350  }
351
352  InspectUI* discovery_ui_;
353};
354
355InspectUI::InspectUI(content::WebUI* web_ui)
356    : WebUIController(web_ui) {
357  web_ui->AddMessageHandler(new InspectMessageHandler(this));
358  Profile* profile = Profile::FromWebUI(web_ui);
359  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
360}
361
362InspectUI::~InspectUI() {
363  StopListeningNotifications();
364}
365
366void InspectUI::InitUI() {
367  StartListeningNotifications();
368  PopulateLists();
369  observer_->InitUI();
370}
371
372void InspectUI::PopulateLists() {
373  std::set<RenderViewHost*> tab_rvhs;
374  for (TabContentsIterator it; !it.done(); it.Next())
375    tab_rvhs.insert(it->GetRenderViewHost());
376
377  scoped_ptr<ListValue> target_list(new ListValue());
378
379  std::vector<RenderViewHost*> rvh_vector =
380      DevToolsAgentHost::GetValidRenderViewHosts();
381
382  for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin());
383       it != rvh_vector.end(); it++) {
384    bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end();
385    target_list->Append(BuildTargetDescriptor(*it, is_tab));
386  }
387  web_ui()->CallJavascriptFunction("populateLists", *target_list.get());
388}
389
390void InspectUI::Observe(int type,
391    const content::NotificationSource& source,
392    const content::NotificationDetails& details) {
393  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
394    PopulateLists();
395  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
396    StopListeningNotifications();
397}
398
399void InspectUI::StartListeningNotifications() {
400  if (observer_)
401    return;
402
403  observer_ = new WorkerCreationDestructionListener();
404  observer_->Init(this);
405
406  Profile* profile = Profile::FromWebUI(web_ui());
407  DevToolsAdbBridge* adb_bridge =
408      DevToolsAdbBridge::Factory::GetForProfile(profile);
409  if (adb_bridge)
410    adb_bridge->AddListener(this);
411
412  registrar_.Add(this,
413                 content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
414                 content::NotificationService::AllSources());
415  registrar_.Add(this,
416                 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
417                 content::NotificationService::AllSources());
418  registrar_.Add(this,
419                 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
420                 content::NotificationService::AllSources());
421}
422
423void InspectUI::StopListeningNotifications()
424{
425  if (!observer_.get())
426    return;
427  Profile* profile = Profile::FromWebUI(web_ui());
428  DevToolsAdbBridge* adb_bridge =
429      DevToolsAdbBridge::Factory::GetForProfile(profile);
430  if (adb_bridge)
431    adb_bridge->RemoveListener(this);
432  observer_->InspectUIDestroyed();
433  observer_ = NULL;
434  registrar_.RemoveAll();
435}
436
437content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
438  content::WebUIDataSource* source =
439      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
440  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
441  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
442  source->SetDefaultResource(IDR_INSPECT_HTML);
443  return source;
444}
445
446void InspectUI::RemotePagesChanged(DevToolsAdbBridge::RemotePages* pages) {
447  ListValue targets;
448  for (DevToolsAdbBridge::RemotePages::iterator it = pages->begin();
449       it != pages->end(); ++it) {
450    DevToolsAdbBridge::RemotePage* page = it->get();
451    DictionaryValue* target_data = BuildTargetDescriptor(kAdbTargetType,
452        false, GURL(page->url()), page->title(), GURL(page->favicon_url()), 0,
453        0);
454    target_data->SetString(kAdbSerialField, page->serial());
455    target_data->SetString(kAdbModelField, page->model());
456    target_data->SetString(kAdbPackageField, page->package());
457    target_data->SetString(kAdbPageIdField, page->id());
458    targets.Append(target_data);
459  }
460  web_ui()->CallJavascriptFunction("populateDeviceLists", targets);
461}
462