Home | History | Annotate | Download | only in devtools
      1 // Copyright 2013 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/devtools/devtools_targets_ui.h"
      6 
      7 #include "base/memory/weak_ptr.h"
      8 #include "base/stl_util.h"
      9 #include "base/strings/stringprintf.h"
     10 #include "base/values.h"
     11 #include "base/version.h"
     12 #include "chrome/browser/devtools/device/devtools_android_bridge.h"
     13 #include "chrome/browser/devtools/devtools_target_impl.h"
     14 #include "chrome/common/chrome_version_info.h"
     15 #include "content/public/browser/browser_child_process_observer.h"
     16 #include "content/public/browser/browser_thread.h"
     17 #include "content/public/browser/child_process_data.h"
     18 #include "content/public/browser/notification_observer.h"
     19 #include "content/public/browser/notification_registrar.h"
     20 #include "content/public/browser/notification_service.h"
     21 #include "content/public/browser/notification_source.h"
     22 #include "content/public/browser/notification_types.h"
     23 #include "content/public/browser/worker_service.h"
     24 #include "content/public/browser/worker_service_observer.h"
     25 #include "content/public/common/process_type.h"
     26 #include "net/base/escape.h"
     27 
     28 using content::BrowserThread;
     29 
     30 namespace {
     31 
     32 const char kTargetSourceField[]  = "source";
     33 const char kTargetSourceRenderer[]  = "renderers";
     34 const char kTargetSourceWorker[]  = "workers";
     35 const char kTargetSourceAdb[]  = "adb";
     36 
     37 const char kTargetIdField[]  = "id";
     38 const char kTargetTypeField[]  = "type";
     39 const char kAttachedField[]  = "attached";
     40 const char kUrlField[]  = "url";
     41 const char kNameField[]  = "name";
     42 const char kFaviconUrlField[] = "faviconUrl";
     43 const char kDescriptionField[] = "description";
     44 
     45 const char kGuestList[] = "guests";
     46 
     47 const char kAdbModelField[] = "adbModel";
     48 const char kAdbConnectedField[] = "adbConnected";
     49 const char kAdbSerialField[] = "adbSerial";
     50 const char kAdbBrowsersList[] = "browsers";
     51 const char kAdbDeviceIdFormat[] = "device:%s";
     52 
     53 const char kAdbBrowserNameField[] = "adbBrowserName";
     54 const char kAdbBrowserVersionField[] = "adbBrowserVersion";
     55 const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion";
     56 const char kCompatibleVersion[] = "compatibleVersion";
     57 const char kAdbPagesList[] = "pages";
     58 
     59 const char kAdbScreenWidthField[] = "adbScreenWidth";
     60 const char kAdbScreenHeightField[] = "adbScreenHeight";
     61 const char kAdbAttachedForeignField[]  = "adbAttachedForeign";
     62 
     63 // CancelableTimer ------------------------------------------------------------
     64 
     65 class CancelableTimer {
     66  public:
     67   CancelableTimer(base::Closure callback, base::TimeDelta delay)
     68       : callback_(callback),
     69         weak_factory_(this) {
     70     base::MessageLoop::current()->PostDelayedTask(
     71         FROM_HERE,
     72         base::Bind(&CancelableTimer::Fire, weak_factory_.GetWeakPtr()),
     73         delay);
     74   }
     75 
     76  private:
     77   void Fire() { callback_.Run(); }
     78 
     79   base::Closure callback_;
     80   base::WeakPtrFactory<CancelableTimer> weak_factory_;
     81 };
     82 
     83 // RenderViewHostTargetsUIHandler ---------------------------------------------
     84 
     85 class RenderViewHostTargetsUIHandler
     86     : public DevToolsTargetsUIHandler,
     87       public content::NotificationObserver {
     88  public:
     89   explicit RenderViewHostTargetsUIHandler(Callback callback);
     90   virtual ~RenderViewHostTargetsUIHandler();
     91  private:
     92   // content::NotificationObserver overrides.
     93   virtual void Observe(int type,
     94                        const content::NotificationSource& source,
     95                        const content::NotificationDetails& details) OVERRIDE;
     96 
     97   void UpdateTargets();
     98 
     99   content::NotificationRegistrar notification_registrar_;
    100   scoped_ptr<CancelableTimer> timer_;
    101 };
    102 
    103 RenderViewHostTargetsUIHandler::RenderViewHostTargetsUIHandler(
    104     Callback callback)
    105     : DevToolsTargetsUIHandler(kTargetSourceRenderer, callback) {
    106   notification_registrar_.Add(this,
    107                               content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
    108                               content::NotificationService::AllSources());
    109   notification_registrar_.Add(this,
    110                               content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
    111                               content::NotificationService::AllSources());
    112   notification_registrar_.Add(this,
    113                               content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    114                               content::NotificationService::AllSources());
    115   UpdateTargets();
    116 }
    117 
    118 RenderViewHostTargetsUIHandler::~RenderViewHostTargetsUIHandler() {
    119   notification_registrar_.RemoveAll();
    120 }
    121 
    122 void RenderViewHostTargetsUIHandler::Observe(
    123     int type,
    124     const content::NotificationSource& source,
    125     const content::NotificationDetails& details) {
    126   const int kUpdateDelay = 100;
    127   timer_.reset(
    128       new CancelableTimer(
    129           base::Bind(&RenderViewHostTargetsUIHandler::UpdateTargets,
    130                      base::Unretained(this)),
    131           base::TimeDelta::FromMilliseconds(kUpdateDelay)));
    132 }
    133 
    134 void RenderViewHostTargetsUIHandler::UpdateTargets() {
    135   scoped_ptr<base::ListValue> list_value(new base::ListValue());
    136 
    137   std::map<std::string, base::DictionaryValue*> id_to_descriptor;
    138 
    139   DevToolsTargetImpl::List targets =
    140       DevToolsTargetImpl::EnumerateRenderViewHostTargets();
    141 
    142   STLDeleteValues(&targets_);
    143   for (DevToolsTargetImpl::List::iterator it = targets.begin();
    144       it != targets.end(); ++it) {
    145     DevToolsTargetImpl* target = *it;
    146     targets_[target->GetId()] = target;
    147     id_to_descriptor[target->GetId()] = Serialize(*target);
    148   }
    149 
    150   for (TargetMap::iterator it(targets_.begin()); it != targets_.end(); ++it) {
    151     DevToolsTargetImpl* target = it->second;
    152     base::DictionaryValue* descriptor = id_to_descriptor[target->GetId()];
    153 
    154     std::string parent_id = target->GetParentId();
    155     if (parent_id.empty() || id_to_descriptor.count(parent_id) == 0) {
    156       list_value->Append(descriptor);
    157     } else {
    158       base::DictionaryValue* parent = id_to_descriptor[parent_id];
    159       base::ListValue* guests = NULL;
    160       if (!parent->GetList(kGuestList, &guests)) {
    161         guests = new base::ListValue();
    162         parent->Set(kGuestList, guests);
    163       }
    164       guests->Append(descriptor);
    165     }
    166   }
    167 
    168   SendSerializedTargets(list_value.Pass());
    169 }
    170 
    171 // WorkerObserver -------------------------------------------------------------
    172 
    173 class WorkerObserver
    174     : public content::WorkerServiceObserver,
    175       public base::RefCountedThreadSafe<WorkerObserver> {
    176  public:
    177   WorkerObserver() {}
    178 
    179   void Start(DevToolsTargetImpl::Callback callback) {
    180     DCHECK(callback_.is_null());
    181     DCHECK(!callback.is_null());
    182     callback_ = callback;
    183     BrowserThread::PostTask(
    184         BrowserThread::IO, FROM_HERE,
    185         base::Bind(&WorkerObserver::StartOnIOThread, this));
    186   }
    187 
    188   void Stop() {
    189     DCHECK(!callback_.is_null());
    190     callback_ = DevToolsTargetImpl::Callback();
    191     BrowserThread::PostTask(
    192         BrowserThread::IO, FROM_HERE,
    193         base::Bind(&WorkerObserver::StopOnIOThread, this));
    194   }
    195 
    196   void Enumerate() {
    197     BrowserThread::PostTask(
    198         BrowserThread::IO, FROM_HERE,
    199         base::Bind(&WorkerObserver::EnumerateOnIOThread,
    200                    this));
    201   }
    202 
    203  private:
    204   friend class base::RefCountedThreadSafe<WorkerObserver>;
    205   virtual ~WorkerObserver() {}
    206 
    207   // content::WorkerServiceObserver overrides:
    208   virtual void WorkerCreated(
    209       const GURL& url,
    210       const base::string16& name,
    211       int process_id,
    212       int route_id) OVERRIDE {
    213     EnumerateOnIOThread();
    214   }
    215 
    216   virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
    217     EnumerateOnIOThread();
    218   }
    219 
    220   void StartOnIOThread() {
    221     content::WorkerService::GetInstance()->AddObserver(this);
    222     EnumerateOnIOThread();
    223   }
    224 
    225   void StopOnIOThread() {
    226     content::WorkerService::GetInstance()->RemoveObserver(this);
    227   }
    228 
    229   void EnumerateOnIOThread() {
    230     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    231     DevToolsTargetImpl::EnumerateWorkerTargets(
    232         base::Bind(&WorkerObserver::RespondOnUIThread, this));
    233   }
    234 
    235   void RespondOnUIThread(const DevToolsTargetImpl::List& targets) {
    236     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    237     if (callback_.is_null())
    238       return;
    239     callback_.Run(targets);
    240   }
    241 
    242   DevToolsTargetImpl::Callback callback_;
    243 };
    244 
    245 // WorkerTargetsUIHandler -----------------------------------------------------
    246 
    247 class WorkerTargetsUIHandler
    248     : public DevToolsTargetsUIHandler,
    249       public content::BrowserChildProcessObserver {
    250  public:
    251   explicit WorkerTargetsUIHandler(Callback callback);
    252   virtual ~WorkerTargetsUIHandler();
    253 
    254  private:
    255   // content::BrowserChildProcessObserver overrides.
    256   virtual void BrowserChildProcessHostConnected(
    257       const content::ChildProcessData& data) OVERRIDE;
    258   virtual void BrowserChildProcessHostDisconnected(
    259       const content::ChildProcessData& data) OVERRIDE;
    260 
    261   void UpdateTargets(const DevToolsTargetImpl::List& targets);
    262 
    263   scoped_refptr<WorkerObserver> observer_;
    264 };
    265 
    266 WorkerTargetsUIHandler::WorkerTargetsUIHandler(Callback callback)
    267     : DevToolsTargetsUIHandler(kTargetSourceWorker, callback),
    268       observer_(new WorkerObserver()) {
    269   observer_->Start(base::Bind(&WorkerTargetsUIHandler::UpdateTargets,
    270                               base::Unretained(this)));
    271   BrowserChildProcessObserver::Add(this);
    272 }
    273 
    274 WorkerTargetsUIHandler::~WorkerTargetsUIHandler() {
    275   BrowserChildProcessObserver::Remove(this);
    276   observer_->Stop();
    277 }
    278 
    279 void WorkerTargetsUIHandler::BrowserChildProcessHostConnected(
    280     const content::ChildProcessData& data) {
    281   if (data.process_type == content::PROCESS_TYPE_WORKER)
    282     observer_->Enumerate();
    283 }
    284 
    285 void WorkerTargetsUIHandler::BrowserChildProcessHostDisconnected(
    286     const content::ChildProcessData& data) {
    287   if (data.process_type == content::PROCESS_TYPE_WORKER)
    288     observer_->Enumerate();
    289 }
    290 
    291 void WorkerTargetsUIHandler::UpdateTargets(
    292     const DevToolsTargetImpl::List& targets) {
    293   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    294   scoped_ptr<base::ListValue> list_value(new base::ListValue());
    295   STLDeleteValues(&targets_);
    296   for (DevToolsTargetImpl::List::const_iterator it = targets.begin();
    297       it != targets.end(); ++it) {
    298     DevToolsTargetImpl* target = *it;
    299     list_value->Append(Serialize(*target));
    300     targets_[target->GetId()] = target;
    301   }
    302   SendSerializedTargets(list_value.Pass());
    303 }
    304 
    305 // AdbTargetsUIHandler --------------------------------------------------------
    306 
    307 class AdbTargetsUIHandler
    308     : public DevToolsTargetsUIHandler,
    309       public DevToolsAndroidBridge::DeviceListListener {
    310  public:
    311   AdbTargetsUIHandler(Callback callback, Profile* profile);
    312   virtual ~AdbTargetsUIHandler();
    313 
    314   virtual void Open(const std::string& browser_id,
    315                     const std::string& url,
    316                     const DevToolsTargetsUIHandler::TargetCallback&) OVERRIDE;
    317 
    318   virtual scoped_refptr<content::DevToolsAgentHost> GetBrowserAgentHost(
    319       const std::string& browser_id) OVERRIDE;
    320 
    321  private:
    322   // DevToolsAndroidBridge::Listener overrides.
    323   virtual void DeviceListChanged(
    324       const DevToolsAndroidBridge::RemoteDevices& devices) OVERRIDE;
    325 
    326   Profile* profile_;
    327 
    328   typedef std::map<std::string,
    329       scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> > RemoteBrowsers;
    330   RemoteBrowsers remote_browsers_;
    331 };
    332 
    333 AdbTargetsUIHandler::AdbTargetsUIHandler(Callback callback, Profile* profile)
    334     : DevToolsTargetsUIHandler(kTargetSourceAdb, callback),
    335       profile_(profile) {
    336   DevToolsAndroidBridge* android_bridge =
    337       DevToolsAndroidBridge::Factory::GetForProfile(profile_);
    338   if (android_bridge)
    339     android_bridge->AddDeviceListListener(this);
    340 }
    341 
    342 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
    343   DevToolsAndroidBridge* android_bridge =
    344       DevToolsAndroidBridge::Factory::GetForProfile(profile_);
    345   if (android_bridge)
    346     android_bridge->RemoveDeviceListListener(this);
    347 }
    348 
    349 static void CallOnTarget(
    350     const DevToolsTargetsUIHandler::TargetCallback& callback,
    351     DevToolsAndroidBridge::RemotePage* page) {
    352   scoped_ptr<DevToolsAndroidBridge::RemotePage> my_page(page);
    353   callback.Run(my_page ? my_page->GetTarget() : NULL);
    354 }
    355 
    356 void AdbTargetsUIHandler::Open(
    357     const std::string& browser_id,
    358     const std::string& url,
    359     const DevToolsTargetsUIHandler::TargetCallback& callback) {
    360   RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
    361   if (it !=  remote_browsers_.end())
    362     it->second->Open(url, base::Bind(&CallOnTarget, callback));
    363 }
    364 
    365 scoped_refptr<content::DevToolsAgentHost>
    366 AdbTargetsUIHandler::GetBrowserAgentHost(
    367     const std::string& browser_id) {
    368   RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
    369   return it != remote_browsers_.end() ? it->second->GetAgentHost() : NULL;
    370 }
    371 
    372 void AdbTargetsUIHandler::DeviceListChanged(
    373     const DevToolsAndroidBridge::RemoteDevices& devices) {
    374   remote_browsers_.clear();
    375   STLDeleteValues(&targets_);
    376 
    377   scoped_ptr<base::ListValue> device_list(new base::ListValue());
    378   for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit =
    379       devices.begin(); dit != devices.end(); ++dit) {
    380     DevToolsAndroidBridge::RemoteDevice* device = dit->get();
    381     base::DictionaryValue* device_data = new base::DictionaryValue();
    382     device_data->SetString(kAdbModelField, device->model());
    383     device_data->SetString(kAdbSerialField, device->serial());
    384     device_data->SetBoolean(kAdbConnectedField, device->is_connected());
    385     std::string device_id = base::StringPrintf(
    386         kAdbDeviceIdFormat,
    387         device->serial().c_str());
    388     device_data->SetString(kTargetIdField, device_id);
    389     base::ListValue* browser_list = new base::ListValue();
    390     device_data->Set(kAdbBrowsersList, browser_list);
    391 
    392     DevToolsAndroidBridge::RemoteBrowsers& browsers = device->browsers();
    393     for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit =
    394         browsers.begin(); bit != browsers.end(); ++bit) {
    395       DevToolsAndroidBridge::RemoteBrowser* browser = bit->get();
    396       base::DictionaryValue* browser_data = new base::DictionaryValue();
    397       browser_data->SetString(kAdbBrowserNameField, browser->display_name());
    398       browser_data->SetString(kAdbBrowserVersionField, browser->version());
    399       DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed =
    400           browser->GetParsedVersion();
    401       browser_data->SetInteger(
    402           kAdbBrowserChromeVersionField,
    403           browser->IsChrome() && !parsed.empty() ? parsed[0] : 0);
    404       std::string browser_id = base::StringPrintf(
    405           "browser:%s:%s:%s:%s",
    406           device->serial().c_str(), // Ensure uniqueness across devices.
    407           browser->display_name().c_str(),  // Sort by display name.
    408           browser->version().c_str(),  // Then by version.
    409           browser->socket().c_str());  // Ensure uniqueness on the device.
    410       browser_data->SetString(kTargetIdField, browser_id);
    411       browser_data->SetString(kTargetSourceField, source_id());
    412 
    413       base::Version remote_version;
    414       if (browser->IsChrome()) {
    415         remote_version = base::Version(browser->version());
    416       } else {
    417         // Try parse WebView version.
    418         std::string version = browser->version();
    419         size_t pos = version.find("Chrome/");
    420         if (pos != std::string::npos) {
    421           remote_version = base::Version(browser->version().substr(pos + 7));
    422         }
    423       }
    424       chrome::VersionInfo version_info;
    425       base::Version local_version(version_info.Version());
    426 
    427       browser_data->SetBoolean(kCompatibleVersion,
    428           (!remote_version.IsValid()) || (!local_version.IsValid()) ||
    429           remote_version.components()[0] <= local_version.components()[0]);
    430 
    431       base::ListValue* page_list = new base::ListValue();
    432       remote_browsers_[browser_id] = browser;
    433             browser_data->Set(kAdbPagesList, page_list);
    434       std::vector<DevToolsAndroidBridge::RemotePage*> pages =
    435           browser->CreatePages();
    436       for (std::vector<DevToolsAndroidBridge::RemotePage*>::iterator it =
    437           pages.begin(); it != pages.end(); ++it) {
    438         DevToolsAndroidBridge::RemotePage* page =  *it;
    439         DevToolsTargetImpl* target = page->GetTarget();
    440         base::DictionaryValue* target_data = Serialize(*target);
    441         target_data->SetBoolean(
    442             kAdbAttachedForeignField,
    443             target->IsAttached() &&
    444                 !DevToolsAndroidBridge::HasDevToolsWindow(target->GetId()));
    445         // Pass the screen size in the target object to make sure that
    446         // the caching logic does not prevent the target item from updating
    447         // when the screen size changes.
    448         gfx::Size screen_size = device->screen_size();
    449         target_data->SetInteger(kAdbScreenWidthField, screen_size.width());
    450         target_data->SetInteger(kAdbScreenHeightField, screen_size.height());
    451         targets_[target->GetId()] = target;
    452         page_list->Append(target_data);
    453       }
    454       browser_list->Append(browser_data);
    455     }
    456 
    457     device_list->Append(device_data);
    458   }
    459   SendSerializedTargets(device_list.Pass());
    460 }
    461 
    462 } // namespace
    463 
    464 // DevToolsTargetsUIHandler ---------------------------------------------------
    465 
    466 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
    467     const std::string& source_id,
    468     Callback callback)
    469     : source_id_(source_id),
    470       callback_(callback) {
    471 }
    472 
    473 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
    474   STLDeleteValues(&targets_);
    475 }
    476 
    477 // static
    478 scoped_ptr<DevToolsTargetsUIHandler>
    479 DevToolsTargetsUIHandler::CreateForRenderers(
    480     DevToolsTargetsUIHandler::Callback callback) {
    481   return scoped_ptr<DevToolsTargetsUIHandler>(
    482       new RenderViewHostTargetsUIHandler(callback));
    483 }
    484 
    485 // static
    486 scoped_ptr<DevToolsTargetsUIHandler>
    487 DevToolsTargetsUIHandler::CreateForWorkers(
    488     DevToolsTargetsUIHandler::Callback callback) {
    489   return scoped_ptr<DevToolsTargetsUIHandler>(
    490       new WorkerTargetsUIHandler(callback));
    491 }
    492 
    493 // static
    494 scoped_ptr<DevToolsTargetsUIHandler>
    495 DevToolsTargetsUIHandler::CreateForAdb(
    496     DevToolsTargetsUIHandler::Callback callback, Profile* profile) {
    497   return scoped_ptr<DevToolsTargetsUIHandler>(
    498       new AdbTargetsUIHandler(callback, profile));
    499 }
    500 
    501 DevToolsTargetImpl* DevToolsTargetsUIHandler::GetTarget(
    502     const std::string& target_id) {
    503   TargetMap::iterator it = targets_.find(target_id);
    504   if (it != targets_.end())
    505     return it->second;
    506   return NULL;
    507 }
    508 
    509 void DevToolsTargetsUIHandler::Open(const std::string& browser_id,
    510                                     const std::string& url,
    511                                     const TargetCallback& callback) {
    512   callback.Run(NULL);
    513 }
    514 
    515 scoped_refptr<content::DevToolsAgentHost>
    516 DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) {
    517   return NULL;
    518 }
    519 
    520 base::DictionaryValue* DevToolsTargetsUIHandler::Serialize(
    521     const DevToolsTargetImpl& target) {
    522   base::DictionaryValue* target_data = new base::DictionaryValue();
    523   target_data->SetString(kTargetSourceField, source_id_);
    524   target_data->SetString(kTargetIdField, target.GetId());
    525   target_data->SetString(kTargetTypeField, target.GetType());
    526   target_data->SetBoolean(kAttachedField, target.IsAttached());
    527   target_data->SetString(kUrlField, target.GetURL().spec());
    528   target_data->SetString(kNameField, net::EscapeForHTML(target.GetTitle()));
    529   target_data->SetString(kFaviconUrlField, target.GetFaviconURL().spec());
    530   target_data->SetString(kDescriptionField, target.GetDescription());
    531   return target_data;
    532 }
    533 
    534 void DevToolsTargetsUIHandler::SendSerializedTargets(
    535     scoped_ptr<base::ListValue> list) {
    536   callback_.Run(source_id_, list.Pass());
    537 }
    538 
    539 // PortForwardingStatusSerializer ---------------------------------------------
    540 
    541 PortForwardingStatusSerializer::PortForwardingStatusSerializer(
    542     const Callback& callback, Profile* profile)
    543       : callback_(callback),
    544         profile_(profile) {
    545   PortForwardingController* port_forwarding_controller =
    546       PortForwardingController::Factory::GetForProfile(profile_);
    547   if (port_forwarding_controller)
    548     port_forwarding_controller->AddListener(this);
    549 }
    550 
    551 PortForwardingStatusSerializer::~PortForwardingStatusSerializer() {
    552   PortForwardingController* port_forwarding_controller =
    553       PortForwardingController::Factory::GetForProfile(profile_);
    554   if (port_forwarding_controller)
    555     port_forwarding_controller->RemoveListener(this);
    556 }
    557 
    558 void PortForwardingStatusSerializer::PortStatusChanged(
    559     const DevicesStatus& status) {
    560   base::DictionaryValue result;
    561   for (DevicesStatus::const_iterator sit = status.begin();
    562       sit != status.end(); ++sit) {
    563     base::DictionaryValue* device_status_dict = new base::DictionaryValue();
    564     const PortStatusMap& device_status_map = sit->second;
    565     for (PortStatusMap::const_iterator it = device_status_map.begin();
    566          it != device_status_map.end(); ++it) {
    567       device_status_dict->SetInteger(
    568           base::StringPrintf("%d", it->first), it->second);
    569     }
    570 
    571     std::string device_id = base::StringPrintf(
    572         kAdbDeviceIdFormat,
    573         sit->first.c_str());
    574     result.Set(device_id, device_status_dict);
    575   }
    576   callback_.Run(result);
    577 }
    578