inspect_ui.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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/stringprintf.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/values.h"
18#include "chrome/browser/devtools/devtools_window.h"
19#include "chrome/browser/devtools/port_forwarding_controller.h"
20#include "chrome/browser/extensions/extension_service.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
23#include "chrome/browser/ui/webui/theme_source.h"
24#include "chrome/common/pref_names.h"
25#include "chrome/common/url_constants.h"
26#include "content/public/browser/browser_child_process_observer.h"
27#include "content/public/browser/browser_thread.h"
28#include "content/public/browser/child_process_data.h"
29#include "content/public/browser/devtools_agent_host.h"
30#include "content/public/browser/devtools_client_host.h"
31#include "content/public/browser/devtools_manager.h"
32#include "content/public/browser/favicon_status.h"
33#include "content/public/browser/navigation_entry.h"
34#include "content/public/browser/notification_service.h"
35#include "content/public/browser/notification_source.h"
36#include "content/public/browser/notification_types.h"
37#include "content/public/browser/render_process_host.h"
38#include "content/public/browser/render_view_host.h"
39#include "content/public/browser/web_contents.h"
40#include "content/public/browser/web_ui.h"
41#include "content/public/browser/web_ui_data_source.h"
42#include "content/public/browser/web_ui_message_handler.h"
43#include "content/public/browser/worker_service.h"
44#include "content/public/browser/worker_service_observer.h"
45#include "content/public/common/process_type.h"
46#include "grit/browser_resources.h"
47#include "grit/generated_resources.h"
48#include "net/base/escape.h"
49#include "net/base/net_errors.h"
50#include "ui/base/resource/resource_bundle.h"
51
52using content::BrowserThread;
53using content::ChildProcessData;
54using content::DevToolsAgentHost;
55using content::DevToolsClientHost;
56using content::DevToolsManager;
57using content::RenderProcessHost;
58using content::RenderViewHost;
59using content::RenderViewHostDelegate;
60using content::RenderWidgetHost;
61using content::WebContents;
62using content::WebUIMessageHandler;
63using content::WorkerService;
64using content::WorkerServiceObserver;
65
66namespace {
67
68static const char kDataFile[] = "targets-data.json";
69static const char kAdbPages[] = "adb-pages";
70
71static const char kAppTargetType[] = "app";
72static const char kExtensionTargetType[]  = "extension";
73static const char kPageTargetType[]  = "page";
74static const char kWorkerTargetType[]  = "worker";
75static const char kAdbTargetType[]  = "adb_page";
76
77static const char kInitUICommand[]  = "init-ui";
78static const char kInspectCommand[]  = "inspect";
79static const char kActivateCommand[]  = "activate";
80static const char kTerminateCommand[]  = "terminate";
81static const char kReloadCommand[]  = "reload";
82static const char kOpenCommand[]  = "open";
83
84static const char kPortForwardingEnabledCommand[] =
85    "set-port-forwarding-enabled";
86static const char kPortForwardingConfigCommand[] = "set-port-forwarding-config";
87
88static const char kPortForwardingDefaultPort[] = "8080";
89static const char kPortForwardingDefaultLocation[] = "localhost:8080";
90
91static const char kTargetTypeField[]  = "type";
92static const char kAttachedField[]  = "attached";
93static const char kProcessIdField[]  = "processId";
94static const char kRouteIdField[]  = "routeId";
95static const char kUrlField[]  = "url";
96static const char kNameField[]  = "name";
97static const char kFaviconUrlField[] = "faviconUrl";
98static const char kDescription[] = "description";
99static const char kPidField[]  = "pid";
100static const char kAdbSerialField[] = "adbSerial";
101static const char kAdbModelField[] = "adbModel";
102static const char kAdbBrowserProductField[] = "adbBrowserProduct";
103static const char kAdbBrowserPackageField[] = "adbBrowserPackage";
104static const char kAdbBrowserVersionField[] = "adbBrowserVersion";
105static const char kAdbGlobalIdField[] = "adbGlobalId";
106static const char kAdbBrowsersField[] = "browsers";
107static const char kAdbPagesField[] = "pages";
108static const char kAdbPortStatus[] = "adbPortStatus";
109static const char kAdbScreenWidthField[] = "adbScreenWidth";
110static const char kAdbScreenHeightField[] = "adbScreenHeight";
111static const char kGuestList[] = "guests";
112
113DictionaryValue* BuildTargetDescriptor(
114    const std::string& target_type,
115    bool attached,
116    const GURL& url,
117    const std::string& name,
118    const GURL& favicon_url,
119    const std::string& description,
120    int process_id,
121    int route_id,
122    base::ProcessHandle handle = base::kNullProcessHandle) {
123  DictionaryValue* target_data = new DictionaryValue();
124  target_data->SetString(kTargetTypeField, target_type);
125  target_data->SetBoolean(kAttachedField, attached);
126  target_data->SetInteger(kProcessIdField, process_id);
127  target_data->SetInteger(kRouteIdField, route_id);
128  target_data->SetString(kUrlField, url.spec());
129  target_data->SetString(kNameField, net::EscapeForHTML(name));
130  target_data->SetInteger(kPidField, base::GetProcId(handle));
131  target_data->SetString(kFaviconUrlField, favicon_url.spec());
132  target_data->SetString(kDescription, description);
133
134  return target_data;
135}
136
137bool HasClientHost(RenderViewHost* rvh) {
138  if (!DevToolsAgentHost::HasFor(rvh))
139    return false;
140
141  scoped_refptr<DevToolsAgentHost> agent(
142      DevToolsAgentHost::GetOrCreateFor(rvh));
143  return agent->IsAttached();
144}
145
146bool HasClientHost(int process_id, int route_id) {
147  if (!DevToolsAgentHost::HasForWorker(process_id, route_id))
148    return false;
149
150  scoped_refptr<DevToolsAgentHost> agent(
151      DevToolsAgentHost::GetForWorker(process_id, route_id));
152  return agent->IsAttached();
153}
154
155DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) {
156  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
157  std::string title;
158  std::string target_type = is_tab ? kPageTargetType : "";
159  GURL url;
160  GURL favicon_url;
161  if (web_contents) {
162    url = web_contents->GetURL();
163    title = UTF16ToUTF8(web_contents->GetTitle());
164    content::NavigationController& controller = web_contents->GetController();
165    content::NavigationEntry* entry = controller.GetActiveEntry();
166    if (entry != NULL && entry->GetURL().is_valid())
167      favicon_url = entry->GetFavicon().url;
168
169    Profile* profile = Profile::FromBrowserContext(
170        web_contents->GetBrowserContext());
171    if (profile) {
172      ExtensionService* extension_service = profile->GetExtensionService();
173      const extensions::Extension* extension = extension_service->
174          extensions()->GetByID(url.host());
175      if (extension) {
176        if (extension->is_hosted_app()
177            || extension->is_legacy_packaged_app()
178            || extension->is_platform_app())
179          target_type = kAppTargetType;
180        else
181          target_type = kExtensionTargetType;
182        title = extension->name();
183      }
184    }
185  }
186
187  return BuildTargetDescriptor(target_type,
188                               HasClientHost(rvh),
189                               url,
190                               title,
191                               favicon_url,
192                               "",
193                               rvh->GetProcess()->GetID(),
194                               rvh->GetRoutingID());
195}
196
197class InspectMessageHandler : public WebUIMessageHandler {
198 public:
199  explicit InspectMessageHandler(InspectUI* inspect_ui)
200      : inspect_ui_(inspect_ui) {}
201  virtual ~InspectMessageHandler() {}
202
203 private:
204  // WebUIMessageHandler implementation.
205  virtual void RegisterMessages() OVERRIDE;
206
207  void HandleInitUICommand(const ListValue* args);
208  void HandleInspectCommand(const ListValue* args);
209  void HandleActivateCommand(const ListValue* args);
210  void HandleTerminateCommand(const ListValue* args);
211  void HandleReloadCommand(const ListValue* args);
212  void HandleOpenCommand(const ListValue* args);
213  void HandlePortForwardingEnabledCommand(const ListValue* args);
214  void HandlePortForwardingConfigCommand(const ListValue* args);
215
216  static bool GetProcessAndRouteId(const ListValue* args,
217                                   int* process_id,
218                                   int* route_id);
219
220  static bool GetRemotePageId(const ListValue* args, std::string* page_id);
221
222  InspectUI* inspect_ui_;
223
224  DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler);
225};
226
227void InspectMessageHandler::RegisterMessages() {
228  web_ui()->RegisterMessageCallback(kInitUICommand,
229      base::Bind(&InspectMessageHandler::HandleInitUICommand,
230                 base::Unretained(this)));
231  web_ui()->RegisterMessageCallback(kInspectCommand,
232      base::Bind(&InspectMessageHandler::HandleInspectCommand,
233                 base::Unretained(this)));
234  web_ui()->RegisterMessageCallback(kActivateCommand,
235      base::Bind(&InspectMessageHandler::HandleActivateCommand,
236                 base::Unretained(this)));
237  web_ui()->RegisterMessageCallback(kTerminateCommand,
238      base::Bind(&InspectMessageHandler::HandleTerminateCommand,
239                 base::Unretained(this)));
240  web_ui()->RegisterMessageCallback(kPortForwardingEnabledCommand,
241      base::Bind(&InspectMessageHandler::HandlePortForwardingEnabledCommand,
242                 base::Unretained(this)));
243  web_ui()->RegisterMessageCallback(kPortForwardingConfigCommand,
244      base::Bind(&InspectMessageHandler::HandlePortForwardingConfigCommand,
245                 base::Unretained(this)));
246  web_ui()->RegisterMessageCallback(kReloadCommand,
247      base::Bind(&InspectMessageHandler::HandleReloadCommand,
248                 base::Unretained(this)));
249  web_ui()->RegisterMessageCallback(kOpenCommand,
250      base::Bind(&InspectMessageHandler::HandleOpenCommand,
251                 base::Unretained(this)));
252}
253
254void InspectMessageHandler::HandleInitUICommand(const ListValue*) {
255  inspect_ui_->InitUI();
256}
257
258void InspectMessageHandler::HandleInspectCommand(const ListValue* args) {
259  Profile* profile = Profile::FromWebUI(web_ui());
260  if (!profile)
261    return;
262
263  std::string page_id;
264  if (GetRemotePageId(args, &page_id)) {
265    inspect_ui_->InspectRemotePage(page_id);
266    return;
267  }
268
269  int process_id;
270  int route_id;
271  if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0
272      || route_id == 0) {
273    return;
274  }
275
276  RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id);
277  if (rvh) {
278    DevToolsWindow::OpenDevToolsWindow(rvh);
279    return;
280  }
281
282  scoped_refptr<DevToolsAgentHost> agent_host(
283      DevToolsAgentHost::GetForWorker(process_id, route_id));
284  if (!agent_host.get())
285    return;
286
287  DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get());
288}
289
290void InspectMessageHandler::HandleActivateCommand(const ListValue* args) {
291  std::string page_id;
292  if (GetRemotePageId(args, &page_id))
293    inspect_ui_->ActivateRemotePage(page_id);
294}
295
296static void TerminateWorker(int process_id, int route_id) {
297  WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
298}
299
300void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) {
301  std::string page_id;
302  if (GetRemotePageId(args, &page_id)) {
303    inspect_ui_->CloseRemotePage(page_id);
304    return;
305  }
306
307  int process_id;
308  int route_id;
309  if (!GetProcessAndRouteId(args, &process_id, &route_id))
310    return;
311
312  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
313      base::Bind(&TerminateWorker, process_id, route_id));
314}
315
316void InspectMessageHandler::HandleReloadCommand(const ListValue* args) {
317  std::string page_id;
318  if (GetRemotePageId(args, &page_id))
319    inspect_ui_->ReloadRemotePage(page_id);
320}
321
322void InspectMessageHandler::HandleOpenCommand(const ListValue* args) {
323  std::string browser_id;
324  std::string url;
325  if (args->GetSize() == 2 &&
326      args->GetString(0, &browser_id) &&
327      args->GetString(1, &url)) {
328    inspect_ui_->OpenRemotePage(browser_id, url);
329  }
330}
331
332bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args,
333                                                 int* process_id,
334                                                 int* route_id) {
335  const DictionaryValue* data;
336  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
337      data->GetInteger(kProcessIdField, process_id) &&
338      data->GetInteger(kRouteIdField, route_id)) {
339    return true;
340  }
341  return false;
342}
343
344void InspectMessageHandler::HandlePortForwardingEnabledCommand(
345    const ListValue* args) {
346  Profile* profile = Profile::FromWebUI(web_ui());
347  if (!profile)
348    return;
349
350  bool enabled;
351  if (args->GetSize() == 1 && args->GetBoolean(0, &enabled)) {
352    profile->GetPrefs()->SetBoolean(
353        prefs::kDevToolsPortForwardingEnabled, enabled);
354  }
355}
356
357void InspectMessageHandler::HandlePortForwardingConfigCommand(
358    const ListValue* args) {
359  Profile* profile = Profile::FromWebUI(web_ui());
360  if (!profile)
361    return;
362
363  const DictionaryValue* dict_src;
364  if (args->GetSize() == 1 && args->GetDictionary(0, &dict_src))
365    profile->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig, *dict_src);
366}
367
368bool InspectMessageHandler::GetRemotePageId(const ListValue* args,
369                                            std::string* page_id) {
370  const DictionaryValue* data;
371  if (args->GetSize() == 1 && args->GetDictionary(0, &data) &&
372      data->GetString(kAdbGlobalIdField, page_id)) {
373    return true;
374  }
375  return false;
376}
377
378}  // namespace
379
380class InspectUI::WorkerCreationDestructionListener
381    : public WorkerServiceObserver,
382      public content::BrowserChildProcessObserver,
383      public base::RefCountedThreadSafe<WorkerCreationDestructionListener> {
384 public:
385  WorkerCreationDestructionListener()
386      : discovery_ui_(NULL) {}
387
388  void Init(InspectUI* workers_ui) {
389    DCHECK(workers_ui);
390    DCHECK(!discovery_ui_);
391    discovery_ui_ = workers_ui;
392    BrowserChildProcessObserver::Add(this);
393    BrowserThread::PostTask(
394        BrowserThread::IO, FROM_HERE,
395        base::Bind(&WorkerCreationDestructionListener::RegisterObserver,
396                   this));
397  }
398
399  void InspectUIDestroyed() {
400    DCHECK(discovery_ui_);
401    discovery_ui_ = NULL;
402    BrowserChildProcessObserver::Remove(this);
403    BrowserThread::PostTask(
404        BrowserThread::IO, FROM_HERE,
405        base::Bind(&WorkerCreationDestructionListener::UnregisterObserver,
406                   this));
407  }
408
409  void UpdateUI() {
410    BrowserThread::PostTask(
411        BrowserThread::IO, FROM_HERE,
412        base::Bind(&WorkerCreationDestructionListener::CollectWorkersData,
413                   this));
414  }
415
416 private:
417  friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>;
418  virtual ~WorkerCreationDestructionListener() {}
419
420  virtual void WorkerCreated(
421      const GURL& url,
422      const string16& name,
423      int process_id,
424      int route_id) OVERRIDE {
425    CollectWorkersData();
426  }
427
428  virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
429    CollectWorkersData();
430  }
431
432  virtual void BrowserChildProcessHostConnected(
433      const content::ChildProcessData& data) OVERRIDE {
434    if (data.process_type == content::PROCESS_TYPE_WORKER)
435      UpdateUI();
436  }
437
438  virtual void BrowserChildProcessHostDisconnected(
439      const content::ChildProcessData& data) OVERRIDE {
440    if (data.process_type == content::PROCESS_TYPE_WORKER)
441      UpdateUI();
442  }
443
444  void CollectWorkersData() {
445    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
446    BrowserThread::PostTask(
447        BrowserThread::UI, FROM_HERE,
448        base::Bind(&WorkerCreationDestructionListener::PopulateWorkersList,
449                   this, WorkerService::GetInstance()->GetWorkers()));
450  }
451
452  void RegisterObserver() {
453    WorkerService::GetInstance()->AddObserver(this);
454  }
455
456  void UnregisterObserver() {
457    WorkerService::GetInstance()->RemoveObserver(this);
458  }
459
460  void PopulateWorkersList(
461      const std::vector<WorkerService::WorkerInfo>& worker_info) {
462    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
463    if (!discovery_ui_)
464      return;
465
466    ListValue target_list;
467    for (size_t i = 0; i < worker_info.size(); ++i) {
468      if (!worker_info[i].handle)
469        continue;  // Process is still being created.
470      target_list.Append(BuildTargetDescriptor(
471          kWorkerTargetType,
472          HasClientHost(worker_info[i].process_id, worker_info[i].route_id),
473          worker_info[i].url,
474          UTF16ToUTF8(worker_info[i].name),
475          GURL(),
476          "",
477          worker_info[i].process_id,
478          worker_info[i].route_id,
479          worker_info[i].handle));
480    }
481    discovery_ui_->web_ui()->CallJavascriptFunction(
482        "populateWorkersList", target_list);
483  }
484
485  InspectUI* discovery_ui_;
486};
487
488InspectUI::InspectUI(content::WebUI* web_ui)
489    : WebUIController(web_ui) {
490  web_ui->AddMessageHandler(new InspectMessageHandler(this));
491  Profile* profile = Profile::FromWebUI(web_ui);
492  content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource());
493
494  // Set up the chrome://theme/ source.
495  ThemeSource* theme = new ThemeSource(profile);
496  content::URLDataSource::Add(profile, theme);
497}
498
499InspectUI::~InspectUI() {
500  StopListeningNotifications();
501}
502
503void InspectUI::InitUI() {
504  SetPortForwardingDefaults();
505  StartListeningNotifications();
506  PopulateLists();
507  UpdatePortForwardingEnabled();
508  UpdatePortForwardingConfig();
509  observer_->UpdateUI();
510}
511
512void InspectUI::InspectRemotePage(const std::string& id) {
513  RemotePages::iterator it = remote_pages_.find(id);
514  if (it != remote_pages_.end()) {
515    Profile* profile = Profile::FromWebUI(web_ui());
516    it->second->Inspect(profile);
517  }
518}
519
520void InspectUI::ActivateRemotePage(const std::string& id) {
521  RemotePages::iterator it = remote_pages_.find(id);
522  if (it != remote_pages_.end())
523    it->second->Activate();
524}
525
526void InspectUI::ReloadRemotePage(const std::string& id) {
527  RemotePages::iterator it = remote_pages_.find(id);
528  if (it != remote_pages_.end())
529    it->second->Reload();
530}
531
532void InspectUI::CloseRemotePage(const std::string& id) {
533  RemotePages::iterator it = remote_pages_.find(id);
534  if (it != remote_pages_.end())
535    it->second->Close();
536}
537
538void InspectUI::OpenRemotePage(const std::string& browser_id,
539                               const std::string& url) {
540  GURL gurl(url);
541  if (!gurl.is_valid()) {
542    gurl = GURL("http://" + url);
543    if (!gurl.is_valid())
544      return;
545  }
546  RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
547  if (it != remote_browsers_.end())
548    it->second->Open(gurl.spec());
549}
550
551void InspectUI::PopulateLists() {
552  std::set<RenderViewHost*> tab_rvhs;
553  for (TabContentsIterator it; !it.done(); it.Next())
554    tab_rvhs.insert(it->GetRenderViewHost());
555
556  scoped_ptr<ListValue> target_list(new ListValue());
557
558  std::vector<RenderViewHost*> rvh_vector =
559      DevToolsAgentHost::GetValidRenderViewHosts();
560
561  std::map<WebContents*, DictionaryValue*> description_map;
562  std::vector<WebContents*> guest_contents;
563
564  for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin());
565       it != rvh_vector.end(); it++) {
566    bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end();
567    RenderViewHost* rvh = (*it);
568    WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
569    if (rvh->GetProcess()->IsGuest()) {
570      if (web_contents)
571        guest_contents.push_back(web_contents);
572    } else {
573      DictionaryValue* dictionary = BuildTargetDescriptor(rvh, is_tab);
574      if (web_contents)
575        description_map[web_contents] = dictionary;
576      target_list->Append(dictionary);
577    }
578  }
579
580  // Add the list of guest-views to each of its embedders.
581  for (std::vector<WebContents*>::iterator it(guest_contents.begin());
582       it != guest_contents.end(); ++it) {
583    WebContents* guest = (*it);
584    WebContents* embedder = guest->GetEmbedderWebContents();
585    if (embedder && description_map.count(embedder) > 0) {
586      DictionaryValue* description = description_map[embedder];
587      ListValue* guests = NULL;
588      if (!description->GetList(kGuestList, &guests)) {
589        guests = new ListValue();
590        description->Set(kGuestList, guests);
591      }
592      RenderViewHost* rvh = guest->GetRenderViewHost();
593      if (rvh)
594        guests->Append(BuildTargetDescriptor(rvh, false));
595    }
596  }
597
598  web_ui()->CallJavascriptFunction("populateLists", *target_list.get());
599}
600
601void InspectUI::Observe(int type,
602    const content::NotificationSource& source,
603    const content::NotificationDetails& details) {
604  if (source != content::Source<WebContents>(web_ui()->GetWebContents()))
605    PopulateLists();
606  else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED)
607    StopListeningNotifications();
608}
609
610void InspectUI::StartListeningNotifications() {
611  if (observer_)
612    return;
613
614  observer_ = new WorkerCreationDestructionListener();
615  observer_->Init(this);
616
617  Profile* profile = Profile::FromWebUI(web_ui());
618  DevToolsAdbBridge* adb_bridge =
619      DevToolsAdbBridge::Factory::GetForProfile(profile);
620  if (adb_bridge)
621    adb_bridge->AddListener(this);
622
623  notification_registrar_.Add(this,
624                              content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
625                              content::NotificationService::AllSources());
626  notification_registrar_.Add(this,
627                              content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
628                              content::NotificationService::AllSources());
629  notification_registrar_.Add(this,
630                              content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
631                              content::NotificationService::AllSources());
632
633  pref_change_registrar_.Init(profile->GetPrefs());
634  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled,
635      base::Bind(&InspectUI::UpdatePortForwardingEnabled,
636                 base::Unretained(this)));
637  pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig,
638      base::Bind(&InspectUI::UpdatePortForwardingConfig,
639                 base::Unretained(this)));
640}
641
642void InspectUI::StopListeningNotifications()
643{
644  if (!observer_.get())
645    return;
646  Profile* profile = Profile::FromWebUI(web_ui());
647  DevToolsAdbBridge* adb_bridge =
648      DevToolsAdbBridge::Factory::GetForProfile(profile);
649  if (adb_bridge)
650    adb_bridge->RemoveListener(this);
651  observer_->InspectUIDestroyed();
652  observer_ = NULL;
653  notification_registrar_.RemoveAll();
654  pref_change_registrar_.RemoveAll();
655}
656
657content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() {
658  content::WebUIDataSource* source =
659      content::WebUIDataSource::Create(chrome::kChromeUIInspectHost);
660  source->AddResourcePath("inspect.css", IDR_INSPECT_CSS);
661  source->AddResourcePath("inspect.js", IDR_INSPECT_JS);
662  source->SetDefaultResource(IDR_INSPECT_HTML);
663  return source;
664}
665
666void InspectUI::RemoteDevicesChanged(
667    DevToolsAdbBridge::RemoteDevices* devices) {
668  Profile* profile = Profile::FromWebUI(web_ui());
669  PortForwardingController* port_forwarding_controller =
670      PortForwardingController::Factory::GetForProfile(profile);
671  PortForwardingController::DevicesStatus port_forwarding_status;
672  if (port_forwarding_controller)
673    port_forwarding_status =
674        port_forwarding_controller->UpdateDeviceList(*devices);
675
676  remote_browsers_.clear();
677  remote_pages_.clear();
678  ListValue device_list;
679  for (DevToolsAdbBridge::RemoteDevices::iterator dit = devices->begin();
680       dit != devices->end(); ++dit) {
681    DevToolsAdbBridge::RemoteDevice* device = dit->get();
682    DictionaryValue* device_data = new DictionaryValue();
683    device_data->SetString(kAdbModelField, device->model());
684    device_data->SetString(kAdbSerialField, device->serial());
685    std::string device_id = base::StringPrintf(
686        "device:%s",
687        device->serial().c_str());
688    device_data->SetString(kAdbGlobalIdField, device_id);
689    ListValue* browser_list = new ListValue();
690    device_data->Set(kAdbBrowsersField, browser_list);
691
692    DevToolsAdbBridge::RemoteBrowsers& browsers = device->browsers();
693    for (DevToolsAdbBridge::RemoteBrowsers::iterator bit =
694        browsers.begin(); bit != browsers.end(); ++bit) {
695      DevToolsAdbBridge::RemoteBrowser* browser = bit->get();
696      DictionaryValue* browser_data = new DictionaryValue();
697      browser_data->SetString(kAdbBrowserProductField, browser->product());
698      browser_data->SetString(kAdbBrowserPackageField, browser->package());
699      browser_data->SetString(kAdbBrowserVersionField, browser->version());
700      std::string browser_id = base::StringPrintf(
701          "browser:%s:%s:%s",
702          device->serial().c_str(),
703          browser->product().c_str(),  // Force sorting by product name.
704          browser->socket().c_str());
705      browser_data->SetString(kAdbGlobalIdField, browser_id);
706      remote_browsers_[browser_id] = browser;
707      ListValue* page_list = new ListValue();
708      browser_data->Set(kAdbPagesField, page_list);
709
710      DevToolsAdbBridge::RemotePages& pages = browser->pages();
711      for (DevToolsAdbBridge::RemotePages::iterator it =
712          pages.begin(); it != pages.end(); ++it) {
713        DevToolsAdbBridge::RemotePage* page =  it->get();
714        DictionaryValue* page_data = BuildTargetDescriptor(
715            kAdbTargetType, page->attached(),
716            GURL(page->url()), page->title(), GURL(page->favicon_url()),
717            page->description(), 0, 0);
718        std::string page_id = base::StringPrintf("page:%s:%s:%s",
719            device->serial().c_str(),
720            browser->socket().c_str(),
721            page->id().c_str());
722        page_data->SetString(kAdbGlobalIdField, page_id);
723        // Pass the screen size in the page object to make sure that
724        // the caching logic does not prevent the page item from updating
725        // when the screen size changes.
726        gfx::Size screen_size = device->GetScreenSize();
727        page_data->SetInteger(kAdbScreenWidthField, screen_size.width());
728        page_data->SetInteger(kAdbScreenHeightField, screen_size.height());
729        remote_pages_[page_id] = page;
730        page_list->Append(page_data);
731      }
732      browser_list->Append(browser_data);
733    }
734
735    if (port_forwarding_controller) {
736      PortForwardingController::DevicesStatus::iterator sit =
737          port_forwarding_status.find(device->serial());
738      if (sit != port_forwarding_status.end()) {
739        DictionaryValue* port_status_dict = new DictionaryValue();
740        typedef PortForwardingController::PortStatusMap StatusMap;
741        const StatusMap& port_status = sit->second;
742        for (StatusMap::const_iterator it = port_status.begin();
743             it != port_status.end(); ++it) {
744          port_status_dict->SetInteger(
745              base::StringPrintf("%d", it->first), it->second);
746        }
747        device_data->Set(kAdbPortStatus, port_status_dict);
748      }
749    }
750
751    device_list.Append(device_data);
752  }
753  web_ui()->CallJavascriptFunction("populateDeviceLists", device_list);
754}
755
756void InspectUI::UpdatePortForwardingEnabled() {
757  web_ui()->CallJavascriptFunction("updatePortForwardingEnabled",
758      *GetPrefValue(prefs::kDevToolsPortForwardingEnabled));
759
760}
761
762void InspectUI::UpdatePortForwardingConfig() {
763  web_ui()->CallJavascriptFunction("updatePortForwardingConfig",
764      *GetPrefValue(prefs::kDevToolsPortForwardingConfig));
765}
766
767void InspectUI::SetPortForwardingDefaults() {
768  Profile* profile = Profile::FromWebUI(web_ui());
769  PrefService* prefs = profile->GetPrefs();
770
771  bool default_set;
772  if (!GetPrefValue(prefs::kDevToolsPortForwardingDefaultSet)->
773      GetAsBoolean(&default_set) || default_set) {
774    return;
775  }
776
777  // This is the first chrome://inspect invocation on a fresh profile or after
778  // upgrade from a version that did not have kDevToolsPortForwardingDefaultSet.
779  prefs->SetBoolean(prefs::kDevToolsPortForwardingDefaultSet, true);
780
781  bool enabled;
782  const base::DictionaryValue* config;
783  if (!GetPrefValue(prefs::kDevToolsPortForwardingEnabled)->
784        GetAsBoolean(&enabled) ||
785      !GetPrefValue(prefs::kDevToolsPortForwardingConfig)->
786        GetAsDictionary(&config)) {
787    return;
788  }
789
790  // Do nothing if user already took explicit action.
791  if (enabled || config->size() != 0)
792    return;
793
794  base::DictionaryValue default_config;
795  default_config.SetString(
796      kPortForwardingDefaultPort, kPortForwardingDefaultLocation);
797  prefs->Set(prefs::kDevToolsPortForwardingConfig, default_config);
798}
799
800const base::Value* InspectUI::GetPrefValue(const char* name) {
801  Profile* profile = Profile::FromWebUI(web_ui());
802  return profile->GetPrefs()->FindPreference(name)->GetValue();
803}
804