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.
5 #include "chrome/browser/devtools/devtools_targets_ui.h"
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"
28 using content::BrowserThread
;
32 const char kTargetSourceField
[] = "source";
33 const char kTargetSourceLocal
[] = "local";
34 const char kTargetSourceRemote
[] = "remote";
36 const char kTargetIdField
[] = "id";
37 const char kTargetTypeField
[] = "type";
38 const char kAttachedField
[] = "attached";
39 const char kUrlField
[] = "url";
40 const char kNameField
[] = "name";
41 const char kFaviconUrlField
[] = "faviconUrl";
42 const char kDescriptionField
[] = "description";
44 const char kGuestList
[] = "guests";
46 const char kAdbModelField
[] = "adbModel";
47 const char kAdbConnectedField
[] = "adbConnected";
48 const char kAdbSerialField
[] = "adbSerial";
49 const char kAdbBrowsersList
[] = "browsers";
50 const char kAdbDeviceIdFormat
[] = "device:%s";
52 const char kAdbBrowserNameField
[] = "adbBrowserName";
53 const char kAdbBrowserUserField
[] = "adbBrowserUser";
54 const char kAdbBrowserVersionField
[] = "adbBrowserVersion";
55 const char kAdbBrowserChromeVersionField
[] = "adbBrowserChromeVersion";
56 const char kAdbPagesList
[] = "pages";
58 const char kAdbScreenWidthField
[] = "adbScreenWidth";
59 const char kAdbScreenHeightField
[] = "adbScreenHeight";
60 const char kAdbAttachedForeignField
[] = "adbAttachedForeign";
62 const char kPortForwardingPorts
[] = "ports";
63 const char kPortForwardingBrowserId
[] = "browserId";
65 std::string
SerializeBrowserId(
66 scoped_refptr
<DevToolsAndroidBridge::RemoteBrowser
> browser
) {
67 return base::StringPrintf(
68 "browser:%s:%s:%s:%s",
69 browser
->serial().c_str(), // Ensure uniqueness across devices.
70 browser
->display_name().c_str(), // Sort by display name.
71 browser
->version().c_str(), // Then by version.
72 browser
->socket().c_str()); // Ensure uniqueness on the device.
75 // CancelableTimer ------------------------------------------------------------
77 class CancelableTimer
{
79 CancelableTimer(base::Closure callback
, base::TimeDelta delay
)
80 : callback_(callback
),
82 base::MessageLoop::current()->PostDelayedTask(
84 base::Bind(&CancelableTimer::Fire
, weak_factory_
.GetWeakPtr()),
89 void Fire() { callback_
.Run(); }
91 base::Closure callback_
;
92 base::WeakPtrFactory
<CancelableTimer
> weak_factory_
;
95 // WorkerObserver -------------------------------------------------------------
98 : public content::WorkerServiceObserver
,
99 public base::RefCountedThreadSafe
<WorkerObserver
> {
103 void Start(base::Closure callback
) {
104 DCHECK(callback_
.is_null());
105 DCHECK(!callback
.is_null());
106 callback_
= callback
;
107 BrowserThread::PostTask(
108 BrowserThread::IO
, FROM_HERE
,
109 base::Bind(&WorkerObserver::StartOnIOThread
, this));
113 DCHECK(!callback_
.is_null());
114 callback_
= base::Closure();
115 BrowserThread::PostTask(
116 BrowserThread::IO
, FROM_HERE
,
117 base::Bind(&WorkerObserver::StopOnIOThread
, this));
121 friend class base::RefCountedThreadSafe
<WorkerObserver
>;
122 ~WorkerObserver() override
{}
124 // content::WorkerServiceObserver overrides:
125 void WorkerCreated(const GURL
& url
,
126 const base::string16
& name
,
128 int route_id
) override
{
132 void WorkerDestroyed(int process_id
, int route_id
) override
{
136 void StartOnIOThread() {
137 content::WorkerService::GetInstance()->AddObserver(this);
140 void StopOnIOThread() {
141 content::WorkerService::GetInstance()->RemoveObserver(this);
144 void NotifyOnIOThread() {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
146 BrowserThread::PostTask(
147 BrowserThread::UI
, FROM_HERE
,
148 base::Bind(&WorkerObserver::NotifyOnUIThread
, this));
151 void NotifyOnUIThread() {
152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
153 if (callback_
.is_null())
158 // Accessed on UI thread.
159 base::Closure callback_
;
162 // LocalTargetsUIHandler ---------------------------------------------
164 class LocalTargetsUIHandler
165 : public DevToolsTargetsUIHandler
,
166 public content::NotificationObserver
{
168 explicit LocalTargetsUIHandler(const Callback
& callback
);
169 ~LocalTargetsUIHandler() override
;
171 // DevToolsTargetsUIHandler overrides.
172 void ForceUpdate() override
;
175 // content::NotificationObserver overrides.
176 void Observe(int type
,
177 const content::NotificationSource
& source
,
178 const content::NotificationDetails
& details
) override
;
180 void ScheduleUpdate();
181 void UpdateTargets();
182 void SendTargets(const DevToolsTargetImpl::List
& targets
);
184 content::NotificationRegistrar notification_registrar_
;
185 scoped_ptr
<CancelableTimer
> timer_
;
186 scoped_refptr
<WorkerObserver
> observer_
;
187 base::WeakPtrFactory
<LocalTargetsUIHandler
> weak_factory_
;
190 LocalTargetsUIHandler::LocalTargetsUIHandler(
191 const Callback
& callback
)
192 : DevToolsTargetsUIHandler(kTargetSourceLocal
, callback
),
193 observer_(new WorkerObserver()),
194 weak_factory_(this) {
195 notification_registrar_
.Add(this,
196 content::NOTIFICATION_WEB_CONTENTS_CONNECTED
,
197 content::NotificationService::AllSources());
198 notification_registrar_
.Add(this,
199 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED
,
200 content::NotificationService::AllSources());
201 notification_registrar_
.Add(this,
202 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
203 content::NotificationService::AllSources());
204 observer_
->Start(base::Bind(&LocalTargetsUIHandler::ScheduleUpdate
,
205 base::Unretained(this)));
209 LocalTargetsUIHandler::~LocalTargetsUIHandler() {
210 notification_registrar_
.RemoveAll();
214 void LocalTargetsUIHandler::Observe(
216 const content::NotificationSource
& source
,
217 const content::NotificationDetails
& details
) {
221 void LocalTargetsUIHandler::ForceUpdate() {
225 void LocalTargetsUIHandler::ScheduleUpdate() {
226 const int kUpdateDelay
= 100;
229 base::Bind(&LocalTargetsUIHandler::UpdateTargets
,
230 base::Unretained(this)),
231 base::TimeDelta::FromMilliseconds(kUpdateDelay
)));
234 void LocalTargetsUIHandler::UpdateTargets() {
235 DevToolsTargetImpl::EnumerateAllTargets(base::Bind(
236 &LocalTargetsUIHandler::SendTargets
,
237 weak_factory_
.GetWeakPtr()));
240 void LocalTargetsUIHandler::SendTargets(
241 const DevToolsTargetImpl::List
& targets
) {
242 base::ListValue list_value
;
243 std::map
<std::string
, base::DictionaryValue
*> id_to_descriptor
;
245 STLDeleteValues(&targets_
);
246 for (DevToolsTargetImpl::List::const_iterator it
= targets
.begin();
247 it
!= targets
.end(); ++it
) {
248 DevToolsTargetImpl
* target
= *it
;
249 targets_
[target
->GetId()] = target
;
250 id_to_descriptor
[target
->GetId()] = Serialize(*target
);
253 for (TargetMap::iterator
it(targets_
.begin()); it
!= targets_
.end(); ++it
) {
254 DevToolsTargetImpl
* target
= it
->second
;
255 base::DictionaryValue
* descriptor
= id_to_descriptor
[target
->GetId()];
256 std::string parent_id
= target
->GetParentId();
257 if (parent_id
.empty() || id_to_descriptor
.count(parent_id
) == 0) {
258 list_value
.Append(descriptor
);
260 base::DictionaryValue
* parent
= id_to_descriptor
[parent_id
];
261 base::ListValue
* guests
= NULL
;
262 if (!parent
->GetList(kGuestList
, &guests
)) {
263 guests
= new base::ListValue();
264 parent
->Set(kGuestList
, guests
);
266 guests
->Append(descriptor
);
270 SendSerializedTargets(list_value
);
273 // AdbTargetsUIHandler --------------------------------------------------------
275 class AdbTargetsUIHandler
276 : public DevToolsTargetsUIHandler
,
277 public DevToolsAndroidBridge::DeviceListListener
{
279 AdbTargetsUIHandler(const Callback
& callback
, Profile
* profile
);
280 ~AdbTargetsUIHandler() override
;
282 void Open(const std::string
& browser_id
,
283 const std::string
& url
,
284 const DevToolsTargetsUIHandler::TargetCallback
&) override
;
286 scoped_refptr
<content::DevToolsAgentHost
> GetBrowserAgentHost(
287 const std::string
& browser_id
) override
;
290 // DevToolsAndroidBridge::Listener overrides.
291 void DeviceListChanged(
292 const DevToolsAndroidBridge::RemoteDevices
& devices
) override
;
294 DevToolsAndroidBridge
* GetAndroidBridge();
296 Profile
* const profile_
;
297 DevToolsAndroidBridge
* const android_bridge_
;
299 typedef std::map
<std::string
,
300 scoped_refptr
<DevToolsAndroidBridge::RemoteBrowser
> > RemoteBrowsers
;
301 RemoteBrowsers remote_browsers_
;
304 AdbTargetsUIHandler::AdbTargetsUIHandler(const Callback
& callback
,
306 : DevToolsTargetsUIHandler(kTargetSourceRemote
, callback
),
309 DevToolsAndroidBridge::Factory::GetForProfile(profile_
)) {
310 DCHECK(android_bridge_
);
311 android_bridge_
->AddDeviceListListener(this);
314 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
315 android_bridge_
->RemoveDeviceListListener(this);
318 static void CallOnTarget(
319 const DevToolsTargetsUIHandler::TargetCallback
& callback
,
320 DevToolsAndroidBridge
* bridge
,
321 scoped_refptr
<DevToolsAndroidBridge::RemotePage
> page
) {
322 callback
.Run(bridge
&& page
.get() ? bridge
->CreatePageTarget(page
) : nullptr);
325 void AdbTargetsUIHandler::Open(
326 const std::string
& browser_id
,
327 const std::string
& url
,
328 const DevToolsTargetsUIHandler::TargetCallback
& callback
) {
329 RemoteBrowsers::iterator it
= remote_browsers_
.find(browser_id
);
330 if (it
== remote_browsers_
.end())
333 android_bridge_
->OpenRemotePage(
336 base::Bind(&CallOnTarget
, callback
, android_bridge_
));
339 scoped_refptr
<content::DevToolsAgentHost
>
340 AdbTargetsUIHandler::GetBrowserAgentHost(
341 const std::string
& browser_id
) {
342 RemoteBrowsers::iterator it
= remote_browsers_
.find(browser_id
);
343 if (it
== remote_browsers_
.end())
346 return android_bridge_
->GetBrowserAgentHost(it
->second
);
349 void AdbTargetsUIHandler::DeviceListChanged(
350 const DevToolsAndroidBridge::RemoteDevices
& devices
) {
351 remote_browsers_
.clear();
352 STLDeleteValues(&targets_
);
354 base::ListValue device_list
;
355 for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit
=
356 devices
.begin(); dit
!= devices
.end(); ++dit
) {
357 DevToolsAndroidBridge::RemoteDevice
* device
= dit
->get();
358 base::DictionaryValue
* device_data
= new base::DictionaryValue();
359 device_data
->SetString(kAdbModelField
, device
->model());
360 device_data
->SetString(kAdbSerialField
, device
->serial());
361 device_data
->SetBoolean(kAdbConnectedField
, device
->is_connected());
362 std::string device_id
= base::StringPrintf(
364 device
->serial().c_str());
365 device_data
->SetString(kTargetIdField
, device_id
);
366 base::ListValue
* browser_list
= new base::ListValue();
367 device_data
->Set(kAdbBrowsersList
, browser_list
);
369 DevToolsAndroidBridge::RemoteBrowsers
& browsers
= device
->browsers();
370 for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit
=
371 browsers
.begin(); bit
!= browsers
.end(); ++bit
) {
372 DevToolsAndroidBridge::RemoteBrowser
* browser
= bit
->get();
373 base::DictionaryValue
* browser_data
= new base::DictionaryValue();
374 browser_data
->SetString(kAdbBrowserNameField
, browser
->display_name());
375 browser_data
->SetString(kAdbBrowserUserField
, browser
->user());
376 browser_data
->SetString(kAdbBrowserVersionField
, browser
->version());
377 DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed
=
378 browser
->GetParsedVersion();
379 browser_data
->SetInteger(
380 kAdbBrowserChromeVersionField
,
381 browser
->IsChrome() && !parsed
.empty() ? parsed
[0] : 0);
382 std::string browser_id
= SerializeBrowserId(browser
);
383 browser_data
->SetString(kTargetIdField
, browser_id
);
384 browser_data
->SetString(kTargetSourceField
, source_id());
386 base::ListValue
* page_list
= new base::ListValue();
387 remote_browsers_
[browser_id
] = browser
;
388 browser_data
->Set(kAdbPagesList
, page_list
);
389 for (const auto& page
: browser
->pages()) {
390 DevToolsTargetImpl
* target
= android_bridge_
->CreatePageTarget(page
);
391 base::DictionaryValue
* target_data
= Serialize(*target
);
392 target_data
->SetBoolean(
393 kAdbAttachedForeignField
,
394 target
->IsAttached() &&
395 !android_bridge_
->HasDevToolsWindow(target
->GetId()));
396 // Pass the screen size in the target object to make sure that
397 // the caching logic does not prevent the target item from updating
398 // when the screen size changes.
399 gfx::Size screen_size
= device
->screen_size();
400 target_data
->SetInteger(kAdbScreenWidthField
, screen_size
.width());
401 target_data
->SetInteger(kAdbScreenHeightField
, screen_size
.height());
402 targets_
[target
->GetId()] = target
;
403 page_list
->Append(target_data
);
405 browser_list
->Append(browser_data
);
408 device_list
.Append(device_data
);
410 SendSerializedTargets(device_list
);
415 // DevToolsTargetsUIHandler ---------------------------------------------------
417 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
418 const std::string
& source_id
,
419 const Callback
& callback
)
420 : source_id_(source_id
),
421 callback_(callback
) {
424 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
425 STLDeleteValues(&targets_
);
429 scoped_ptr
<DevToolsTargetsUIHandler
>
430 DevToolsTargetsUIHandler::CreateForLocal(
431 const DevToolsTargetsUIHandler::Callback
& callback
) {
432 return scoped_ptr
<DevToolsTargetsUIHandler
>(
433 new LocalTargetsUIHandler(callback
));
437 scoped_ptr
<DevToolsTargetsUIHandler
>
438 DevToolsTargetsUIHandler::CreateForAdb(
439 const DevToolsTargetsUIHandler::Callback
& callback
, Profile
* profile
) {
440 return scoped_ptr
<DevToolsTargetsUIHandler
>(
441 new AdbTargetsUIHandler(callback
, profile
));
444 DevToolsTargetImpl
* DevToolsTargetsUIHandler::GetTarget(
445 const std::string
& target_id
) {
446 TargetMap::iterator it
= targets_
.find(target_id
);
447 if (it
!= targets_
.end())
452 void DevToolsTargetsUIHandler::Open(const std::string
& browser_id
,
453 const std::string
& url
,
454 const TargetCallback
& callback
) {
458 scoped_refptr
<content::DevToolsAgentHost
>
459 DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string
& browser_id
) {
463 base::DictionaryValue
* DevToolsTargetsUIHandler::Serialize(
464 const DevToolsTargetImpl
& target
) {
465 base::DictionaryValue
* target_data
= new base::DictionaryValue();
466 target_data
->SetString(kTargetSourceField
, source_id_
);
467 target_data
->SetString(kTargetIdField
, target
.GetId());
468 target_data
->SetString(kTargetTypeField
, target
.GetType());
469 target_data
->SetBoolean(kAttachedField
, target
.IsAttached());
470 target_data
->SetString(kUrlField
, target
.GetURL().spec());
471 target_data
->SetString(kNameField
, net::EscapeForHTML(target
.GetTitle()));
472 target_data
->SetString(kFaviconUrlField
, target
.GetFaviconURL().spec());
473 target_data
->SetString(kDescriptionField
, target
.GetDescription());
477 void DevToolsTargetsUIHandler::SendSerializedTargets(
478 const base::ListValue
& list
) {
479 callback_
.Run(source_id_
, list
);
482 void DevToolsTargetsUIHandler::ForceUpdate() {
485 // PortForwardingStatusSerializer ---------------------------------------------
487 PortForwardingStatusSerializer::PortForwardingStatusSerializer(
488 const Callback
& callback
, Profile
* profile
)
489 : callback_(callback
),
491 DevToolsAndroidBridge
* android_bridge
=
492 DevToolsAndroidBridge::Factory::GetForProfile(profile_
);
494 android_bridge
->AddPortForwardingListener(this);
497 PortForwardingStatusSerializer::~PortForwardingStatusSerializer() {
498 DevToolsAndroidBridge
* android_bridge
=
499 DevToolsAndroidBridge::Factory::GetForProfile(profile_
);
501 android_bridge
->RemovePortForwardingListener(this);
504 void PortForwardingStatusSerializer::PortStatusChanged(
505 const ForwardingStatus
& status
) {
506 base::DictionaryValue result
;
507 for (ForwardingStatus::const_iterator sit
= status
.begin();
508 sit
!= status
.end(); ++sit
) {
509 base::DictionaryValue
* port_status_dict
= new base::DictionaryValue();
510 const PortStatusMap
& port_status_map
= sit
->second
;
511 for (PortStatusMap::const_iterator it
= port_status_map
.begin();
512 it
!= port_status_map
.end(); ++it
) {
513 port_status_dict
->SetInteger(
514 base::StringPrintf("%d", it
->first
), it
->second
);
517 base::DictionaryValue
* device_status_dict
= new base::DictionaryValue();
518 device_status_dict
->Set(kPortForwardingPorts
, port_status_dict
);
519 device_status_dict
->SetString(kPortForwardingBrowserId
,
520 SerializeBrowserId(sit
->first
));
522 std::string device_id
= base::StringPrintf(
524 sit
->first
->serial().c_str());
525 result
.Set(device_id
, device_status_dict
);
527 callback_
.Run(result
);