inspect_ui.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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 kAdbBrowserNameField[] = "adbBrowserName";
87static const char kAdbPageIdField[] = "adbPageId";
88static const char kAdbBrowsersField[] = "browsers";
89static const char kAdbPagesField[] = "pages";
90
91DictionaryValue* BuildTargetDescriptor(
92    const std::string& target_type,
93    bool attached,
94    const GURL& url,
95    const std::string& name,
96    const GURL& favicon_url,
97    int process_id,
98    int route_id,
99    base::ProcessHandle handle = base::kNullProcessHandle) {
100  DictionaryValue* target_data = new DictionaryValue();
101  target_data->SetString(kTargetTypeField, target_type);
102  target_data->SetBoolean(kAttachedField, attached);
103  target_data->SetInteger(kProcessIdField, process_id);
104  target_data->SetInteger(kRouteIdField, route_id);
105  target_data->SetString(kUrlField, url.spec());
106  target_data->SetString(kNameField, net::EscapeForHTML(name));
107  target_data->SetInteger(kPidField, base::GetProcId(handle));
108  target_data->SetString(kFaviconUrlField, favicon_url.spec());
109
110  return target_data;
111}
112
113bool HasClientHost(RenderViewHost* rvh) {
114  if (!DevToolsAgentHost::HasFor(rvh))
115    return false;
116
117  scoped_refptr<DevToolsAgentHost> agent(
118      DevToolsAgentHost::GetOrCreateFor(rvh));
119  return agent->IsAttached();
120}
121
122DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) {
123  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
124  std::string title;
125  std::string target_type = is_tab ? kPageTargetType : "";
126  GURL url;
127  GURL favicon_url;
128  if (web_contents) {
129    url = web_contents->GetURL();
130    title = UTF16ToUTF8(web_contents->GetTitle());
131    content::NavigationController& controller = web_contents->GetController();
132    content::NavigationEntry* entry = controller.GetActiveEntry();
133    if (entry != NULL && entry->GetURL().is_valid())
134      favicon_url = entry->GetFavicon().url;
135
136    Profile* profile = Profile::FromBrowserContext(
137        web_contents->GetBrowserContext());
138    if (profile) {
139      ExtensionService* extension_service = profile->GetExtensionService();
140      const extensions::Extension* extension = extension_service->
141          extensions()->GetByID(url.host());
142      if (extension) {
143        if (extension->is_hosted_app()
144            || extension->is_legacy_packaged_app()
145            || extension->is_platform_app())
146          target_type = kAppTargetType;
147        else
148          target_type = kExtensionTargetType;
149        title = extension->name();
150      }
151    }
152  }
153
154  return BuildTargetDescriptor(target_type,
155                               HasClientHost(rvh),
156                               url,
157                               title,
158                               favicon_url,
159                               rvh->GetProcess()->GetID(),
160                               rvh->GetRoutingID());
161}
162
163class InspectMessageHandler : public WebUIMessageHandler {
164 public:
165  explicit InspectMessageHandler(InspectUI* inspect_ui)
166      : inspect_ui_(inspect_ui) {}
167  virtual ~InspectMessageHandler() {}
168
169 private:
170  // WebUIMessageHandler implementation.
171  virtual void RegisterMessages() OVERRIDE;
172
173  void HandleInitUICommand(const ListValue* args);
174  void HandleInspectCommand(const ListValue* args);
175  void HandleTerminateCommand(const ListValue* args);
176
177  bool GetProcessAndRouteId(const ListValue* args,
178                            int* process_id,
179                            int* route_id);
180
181  InspectUI* inspect_ui_;
182
183  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
184};
185
186void InspectMessageHandler::RegisterMessages() {
187  web_ui()->RegisterMessageCallback(kInitUICommand,
188      base::Bind(&InspectMessageHandler::HandleInitUICommand,
189                 base::Unretained(this)));
190  web_ui()->RegisterMessageCallback(kInspectCommand,
191      base::Bind(&InspectMessageHandler::HandleInspectCommand,
192                 base::Unretained(this)));
193  web_ui()->RegisterMessageCallback(kTerminateCommand,
194      base::Bind(&InspectMessageHandler::HandleTerminateCommand,
195                 base::Unretained(this)));
196}
197
198void InspectMessageHandler::HandleInitUICommand(const ListValue*) {
199  inspect_ui_->InitUI();
200}
201
202void InspectMessageHandler::HandleInspectCommand(const ListValue* args) {
203  Profile* profile = Profile::FromWebUI(web_ui());
204  if (!profile)
205    return;
206
207  int process_id;
208  int route_id;
209  if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0
210      || route_id == 0) {
211    // Check for ADB page id
212    const DictionaryValue* data;
213    std::string page_id;
214    if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
215        data->GetString(kAdbPageIdField, &page_id)) {
216      inspect_ui_->InspectRemotePage(page_id);
217    }
218    return;
219  }
220
221  RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
222  if (rvh) {
223    DevToolsWindow::OpenDevToolsWindow(rvh);
224    return;
225  }
226
227  scoped_refptr<DevToolsAgentHost> agent_host(
228      DevToolsAgentHost::GetForWorker(process_id, route_id));
229  if (!agent_host.get())
230    return;
231
232  DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get());
233}
234
235static void TerminateWorker(int process_id, int route_id) {
236  WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
237}
238
239void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) {
240  int process_id;
241  int route_id;
242  if (!GetProcessAndRouteId(args, &process_id, &route_id))
243    return;
244
245  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
246      base::Bind(&TerminateWorker, process_id, route_id));
247}
248
249bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args,
250                                                 int* process_id,
251                                                 int* route_id) {
252  const DictionaryValue* data;
253  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
254      data->GetInteger(kProcessIdField, process_id) &&
255      data->GetInteger(kRouteIdField, route_id)) {
256    return true;
257  }
258  return false;
259}
260
261}  // namespace
262
263class InspectUI::WorkerCreationDestructionListener
264    : public WorkerServiceObserver,
265      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
266 public:
267  WorkerCreationDestructionListener()
268      : discovery_ui_(NULL) {}
269
270  void Init(InspectUI* workers_ui) {
271    DCHECK(workers_ui);
272    DCHECK(!discovery_ui_);
273    discovery_ui_ = workers_ui;
274    BrowserThread::PostTask(
275        BrowserThread::IO, FROM_HERE,
276        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
277                   this));
278  }
279
280  void InspectUIDestroyed() {
281    DCHECK(discovery_ui_);
282    discovery_ui_ = NULL;
283    BrowserThread::PostTask(
284        BrowserThread::IO, FROM_HERE,
285        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
286                   this));
287  }
288
289  void InitUI() {
290    BrowserThread::PostTask(
291        BrowserThread::IO, FROM_HERE,
292        base::Bind(&WorkerCreationDestructionListener::CollectWorkersData,
293                   this));
294  }
295
296 private:
297  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
298  virtual ~WorkerCreationDestructionListener() {}
299
300  virtual void WorkerCreated(
301      const GURL& url,
302      const string16& name,
303      int process_id,
304      int route_id) OVERRIDE {
305    CollectWorkersData();
306  }
307
308  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
309    CollectWorkersData();
310  }
311
312  void CollectWorkersData() {
313    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
314    scoped_ptr<ListValue> target_list(new ListValue());
315    std::vector<WorkerService::WorkerInfo> worker_info =
316        WorkerService::GetInstance()->GetWorkers();
317    for (size_t i = 0; i < worker_info.size(); ++i) {
318      target_list->Append(BuildTargetDescriptor(
319          kWorkerTargetType,
320          false,
321          worker_info[i].url,
322          UTF16ToUTF8(worker_info[i].name),
323          GURL(),
324          worker_info[i].process_id,
325          worker_info[i].route_id,
326          worker_info[i].handle));
327    }
328
329    BrowserThread::PostTask(
330        BrowserThread::UI, FROM_HERE,
331        base::Bind(&WorkerCreationDestructionListener::PopulateWorkersList,
332                   this, base::Owned(target_list.release())));
333  }
334
335  void RegisterObserver() {
336    WorkerService::GetInstance()->AddObserver(this);
337  }
338
339  void UnregisterObserver() {
340    WorkerService::GetInstance()->RemoveObserver(this);
341  }
342
343  void PopulateWorkersList(ListValue* target_list) {
344    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345    if (discovery_ui_) {
346      discovery_ui_->web_ui()->CallJavascriptFunction(
347          "populateWorkersList", *target_list);
348    }
349  }
350
351  InspectUI* discovery_ui_;
352};
353
354InspectUI::InspectUI(content::WebUI* web_ui)
355    : WebUIController(web_ui) {
356  web_ui->AddMessageHandler(new InspectMessageHandler(this));
357  Profile* profile = Profile::FromWebUI(web_ui);
358  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
359}
360
361InspectUI::~InspectUI() {
362  StopListeningNotifications();
363}
364
365void InspectUI::InitUI() {
366  StartListeningNotifications();
367  PopulateLists();
368  observer_->InitUI();
369}
370
371void InspectUI::InspectRemotePage(const std::string& id) {
372  RemotePages::iterator it = remote_pages_.find(id);
373  if (it != remote_pages_.end()) {
374    Profile* profile = Profile::FromWebUI(web_ui());
375    it->second->Inspect(profile);
376  }
377}
378
379void InspectUI::PopulateLists() {
380  std::set<RenderViewHost*> tab_rvhs;
381  for (TabContentsIterator it; !it.done(); it.Next())
382    tab_rvhs.insert(it->GetRenderViewHost());
383
384  scoped_ptr<ListValue> target_list(new ListValue());
385
386  std::vector<RenderViewHost*> rvh_vector =
387      DevToolsAgentHost::GetValidRenderViewHosts();
388
389  for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin());
390       it != rvh_vector.end(); it++) {
391    bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end();
392    target_list->Append(BuildTargetDescriptor(*it, is_tab));
393  }
394  web_ui()->CallJavascriptFunction("populateLists", *target_list.get());
395}
396
397void InspectUI::Observe(int type,
398    const content::NotificationSource& source,
399    const content::NotificationDetails& details) {
400  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
401    PopulateLists();
402  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
403    StopListeningNotifications();
404}
405
406void InspectUI::StartListeningNotifications() {
407  if (observer_)
408    return;
409
410  observer_ = new WorkerCreationDestructionListener();
411  observer_->Init(this);
412
413  Profile* profile = Profile::FromWebUI(web_ui());
414  DevToolsAdbBridge* adb_bridge =
415      DevToolsAdbBridge::Factory::GetForProfile(profile);
416  if (adb_bridge)
417    adb_bridge->AddListener(this);
418
419  registrar_.Add(this,
420                 content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
421                 content::NotificationService::AllSources());
422  registrar_.Add(this,
423                 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
424                 content::NotificationService::AllSources());
425  registrar_.Add(this,
426                 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
427                 content::NotificationService::AllSources());
428}
429
430void InspectUI::StopListeningNotifications()
431{
432  if (!observer_.get())
433    return;
434  Profile* profile = Profile::FromWebUI(web_ui());
435  DevToolsAdbBridge* adb_bridge =
436      DevToolsAdbBridge::Factory::GetForProfile(profile);
437  if (adb_bridge)
438    adb_bridge->RemoveListener(this);
439  observer_->InspectUIDestroyed();
440  observer_ = NULL;
441  registrar_.RemoveAll();
442}
443
444content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
445  content::WebUIDataSource* source =
446      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
447  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
448  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
449  source->SetDefaultResource(IDR_INSPECT_HTML);
450  return source;
451}
452
453void InspectUI::RemoteDevicesChanged(
454    DevToolsAdbBridge::RemoteDevices* devices) {
455  remote_pages_.clear();
456  ListValue device_list;
457  for (DevToolsAdbBridge::RemoteDevices::iterator dit = devices->begin();
458       dit != devices->end(); ++dit) {
459    DevToolsAdbBridge::RemoteDevice& device = *(dit->get());
460    DictionaryValue* device_data = new DictionaryValue();
461    device_data->SetString(kAdbModelField, device.model());
462    device_data->SetString(kAdbSerialField, device.serial());
463    ListValue* browser_list = new ListValue();
464    device_data->Set(kAdbBrowsersField, browser_list);
465
466    DevToolsAdbBridge::RemoteBrowsers& browsers = device.browsers();
467    for (DevToolsAdbBridge::RemoteBrowsers::iterator bit =
468        browsers.begin(); bit != browsers.end(); ++bit) {
469      DevToolsAdbBridge::RemoteBrowser& browser = *(bit->get());
470      DictionaryValue* browser_data = new DictionaryValue();
471      browser_data->SetString(kAdbBrowserNameField, browser.name());
472      ListValue* page_list = new ListValue();
473      browser_data->Set(kAdbPagesField, page_list);
474
475      DevToolsAdbBridge::RemotePages& pages = browser.pages();
476      for (DevToolsAdbBridge::RemotePages::iterator it =
477          pages.begin(); it != pages.end(); ++it) {
478        DevToolsAdbBridge::RemotePage* page =  it->get();
479        DictionaryValue* page_data = BuildTargetDescriptor(kAdbTargetType,
480            false, GURL(page->url()), page->title(), GURL(page->favicon_url()),
481            0, 0);
482        page_data->SetString(kAdbPageIdField, page->global_id());
483        page_list->Append(page_data);
484        remote_pages_[page->global_id()] = page;
485      }
486      browser_list->Append(browser_data);
487    }
488    device_list.Append(device_data);
489  }
490  web_ui()->CallJavascriptFunction("populateDeviceLists", device_list);
491}
492