Home | History | Annotate | Download | only in webui
      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 
     50 using content::BrowserThread;
     51 using content::ChildProcessData;
     52 using content::DevToolsAgentHost;
     53 using content::DevToolsClientHost;
     54 using content::DevToolsManager;
     55 using content::RenderProcessHost;
     56 using content::RenderViewHost;
     57 using content::RenderViewHostDelegate;
     58 using content::RenderWidgetHost;
     59 using content::WebContents;
     60 using content::WebUIMessageHandler;
     61 using content::WorkerService;
     62 using content::WorkerServiceObserver;
     63 
     64 namespace {
     65 
     66 static const char kDataFile[] = "targets-data.json";
     67 static const char kAdbPages[] = "adb-pages";
     68 
     69 static const char kAppTargetType[] = "app";
     70 static const char kExtensionTargetType[]  = "extension";
     71 static const char kPageTargetType[]  = "page";
     72 static const char kWorkerTargetType[]  = "worker";
     73 static const char kAdbTargetType[]  = "adb_page";
     74 
     75 static const char kInitUICommand[]  = "init-ui";
     76 static const char kInspectCommand[]  = "inspect";
     77 static const char kTerminateCommand[]  = "terminate";
     78 static const char kReloadCommand[]  = "reload";
     79 static const char kOpenCommand[]  = "open";
     80 
     81 static const char kPortForwardingEnabledCommand[] =
     82     "set-port-forwarding-enabled";
     83 static const char kPortForwardingConfigCommand[] = "set-port-forwarding-config";
     84 
     85 static const char kTargetTypeField[]  = "type";
     86 static const char kAttachedField[]  = "attached";
     87 static const char kProcessIdField[]  = "processId";
     88 static const char kRouteIdField[]  = "routeId";
     89 static const char kUrlField[]  = "url";
     90 static const char kNameField[]  = "name";
     91 static const char kFaviconUrlField[] = "faviconUrl";
     92 static const char kPidField[]  = "pid";
     93 static const char kAdbSerialField[] = "adbSerial";
     94 static const char kAdbModelField[] = "adbModel";
     95 static const char kAdbBrowserProductField[] = "adbBrowserProduct";
     96 static const char kAdbBrowserVersionField[] = "adbBrowserVersion";
     97 static const char kAdbGlobalIdField[] = "adbGlobalId";
     98 static const char kAdbBrowsersField[] = "browsers";
     99 static const char kAdbPagesField[] = "pages";
    100 static const char kAdbPortStatus[] = "adbPortStatus";
    101 
    102 DictionaryValue* 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 
    124 bool 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 
    133 DictionaryValue* 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 
    174 class 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 
    203 void 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 
    227 void InspectMessageHandler::HandleInitUICommand(const ListValue*) {
    228   inspect_ui_->InitUI();
    229 }
    230 
    231 void 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 
    263 static void TerminateWorker(int process_id, int route_id) {
    264   WorkerService::GetInstance()->TerminateWorker(process_id, route_id);
    265 }
    266 
    267 void 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 
    283 void InspectMessageHandler::HandleReloadCommand(const ListValue* args) {
    284   std::string page_id;
    285   if (GetRemotePageId(args, &page_id))
    286     inspect_ui_->ReloadRemotePage(page_id);
    287 }
    288 
    289 void 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 
    299 bool 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 
    311 void 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 
    324 void 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 
    335 bool 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 
    347 class 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 
    438 InspectUI::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 
    449 InspectUI::~InspectUI() {
    450   StopListeningNotifications();
    451 }
    452 
    453 void InspectUI::InitUI() {
    454   StartListeningNotifications();
    455   PopulateLists();
    456   UpdatePortForwardingEnabled();
    457   UpdatePortForwardingConfig();
    458   observer_->InitUI();
    459 }
    460 
    461 void 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 
    469 void 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 
    475 void 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 
    481 void 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 
    494 void 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 
    512 void 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 
    521 void 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 
    553 void 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 
    568 content::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 
    577 void 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 
    646 void 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 
    654 void 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