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