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:
CancelableTimer(base::Closure callback,base::TimeDelta delay)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:
Fire()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
RenderViewHostTargetsUIHandler(Callback callback)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
~RenderViewHostTargetsUIHandler()118 RenderViewHostTargetsUIHandler::~RenderViewHostTargetsUIHandler() {
119 notification_registrar_.RemoveAll();
120 }
121
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
UpdateTargets()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:
WorkerObserver()177 WorkerObserver() {}
178
Start(DevToolsTargetImpl::Callback callback)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
Stop()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
Enumerate()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>;
~WorkerObserver()205 virtual ~WorkerObserver() {}
206
207 // content::WorkerServiceObserver overrides:
WorkerCreated(const GURL & url,const base::string16 & name,int process_id,int route_id)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
WorkerDestroyed(int process_id,int route_id)216 virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
217 EnumerateOnIOThread();
218 }
219
StartOnIOThread()220 void StartOnIOThread() {
221 content::WorkerService::GetInstance()->AddObserver(this);
222 EnumerateOnIOThread();
223 }
224
StopOnIOThread()225 void StopOnIOThread() {
226 content::WorkerService::GetInstance()->RemoveObserver(this);
227 }
228
EnumerateOnIOThread()229 void EnumerateOnIOThread() {
230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
231 DevToolsTargetImpl::EnumerateWorkerTargets(
232 base::Bind(&WorkerObserver::RespondOnUIThread, this));
233 }
234
RespondOnUIThread(const DevToolsTargetImpl::List & targets)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
WorkerTargetsUIHandler(Callback callback)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
~WorkerTargetsUIHandler()274 WorkerTargetsUIHandler::~WorkerTargetsUIHandler() {
275 BrowserChildProcessObserver::Remove(this);
276 observer_->Stop();
277 }
278
BrowserChildProcessHostConnected(const content::ChildProcessData & data)279 void WorkerTargetsUIHandler::BrowserChildProcessHostConnected(
280 const content::ChildProcessData& data) {
281 if (data.process_type == content::PROCESS_TYPE_WORKER)
282 observer_->Enumerate();
283 }
284
BrowserChildProcessHostDisconnected(const content::ChildProcessData & data)285 void WorkerTargetsUIHandler::BrowserChildProcessHostDisconnected(
286 const content::ChildProcessData& data) {
287 if (data.process_type == content::PROCESS_TYPE_WORKER)
288 observer_->Enumerate();
289 }
290
UpdateTargets(const DevToolsTargetImpl::List & targets)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
AdbTargetsUIHandler(Callback callback,Profile * profile)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
~AdbTargetsUIHandler()342 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
343 DevToolsAndroidBridge* android_bridge =
344 DevToolsAndroidBridge::Factory::GetForProfile(profile_);
345 if (android_bridge)
346 android_bridge->RemoveDeviceListListener(this);
347 }
348
CallOnTarget(const DevToolsTargetsUIHandler::TargetCallback & callback,DevToolsAndroidBridge::RemotePage * page)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
Open(const std::string & browser_id,const std::string & url,const DevToolsTargetsUIHandler::TargetCallback & callback)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>
GetBrowserAgentHost(const std::string & browser_id)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
DeviceListChanged(const DevToolsAndroidBridge::RemoteDevices & devices)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
DevToolsTargetsUIHandler(const std::string & source_id,Callback callback)466 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
467 const std::string& source_id,
468 Callback callback)
469 : source_id_(source_id),
470 callback_(callback) {
471 }
472
~DevToolsTargetsUIHandler()473 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
474 STLDeleteValues(&targets_);
475 }
476
477 // static
478 scoped_ptr<DevToolsTargetsUIHandler>
CreateForRenderers(DevToolsTargetsUIHandler::Callback callback)479 DevToolsTargetsUIHandler::CreateForRenderers(
480 DevToolsTargetsUIHandler::Callback callback) {
481 return scoped_ptr<DevToolsTargetsUIHandler>(
482 new RenderViewHostTargetsUIHandler(callback));
483 }
484
485 // static
486 scoped_ptr<DevToolsTargetsUIHandler>
CreateForWorkers(DevToolsTargetsUIHandler::Callback callback)487 DevToolsTargetsUIHandler::CreateForWorkers(
488 DevToolsTargetsUIHandler::Callback callback) {
489 return scoped_ptr<DevToolsTargetsUIHandler>(
490 new WorkerTargetsUIHandler(callback));
491 }
492
493 // static
494 scoped_ptr<DevToolsTargetsUIHandler>
CreateForAdb(DevToolsTargetsUIHandler::Callback callback,Profile * profile)495 DevToolsTargetsUIHandler::CreateForAdb(
496 DevToolsTargetsUIHandler::Callback callback, Profile* profile) {
497 return scoped_ptr<DevToolsTargetsUIHandler>(
498 new AdbTargetsUIHandler(callback, profile));
499 }
500
GetTarget(const std::string & target_id)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
Open(const std::string & browser_id,const std::string & url,const TargetCallback & callback)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>
GetBrowserAgentHost(const std::string & browser_id)516 DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) {
517 return NULL;
518 }
519
Serialize(const DevToolsTargetImpl & target)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
SendSerializedTargets(scoped_ptr<base::ListValue> list)534 void DevToolsTargetsUIHandler::SendSerializedTargets(
535 scoped_ptr<base::ListValue> list) {
536 callback_.Run(source_id_, list.Pass());
537 }
538
539 // PortForwardingStatusSerializer ---------------------------------------------
540
PortForwardingStatusSerializer(const Callback & callback,Profile * profile)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
~PortForwardingStatusSerializer()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
PortStatusChanged(const DevicesStatus & status)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