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/render_frame_host.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/render_view_host.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/worker_service.h"
28 #include "content/public/browser/worker_service_observer.h"
29 #include "content/public/common/process_type.h"
30 #include "net/base/escape.h"
32 using content::BrowserThread
;
33 using content::RenderFrameHost
;
34 using content::WebContents
;
38 const char kTargetSourceField
[] = "source";
39 const char kTargetSourceRenderer
[] = "renderers";
40 const char kTargetSourceWorker
[] = "workers";
41 const char kTargetSourceAdb
[] = "adb";
43 const char kTargetIdField
[] = "id";
44 const char kTargetTypeField
[] = "type";
45 const char kAttachedField
[] = "attached";
46 const char kUrlField
[] = "url";
47 const char kNameField
[] = "name";
48 const char kFaviconUrlField
[] = "faviconUrl";
49 const char kDescriptionField
[] = "description";
51 const char kGuestList
[] = "guests";
53 const char kAdbModelField
[] = "adbModel";
54 const char kAdbConnectedField
[] = "adbConnected";
55 const char kAdbSerialField
[] = "adbSerial";
56 const char kAdbBrowsersList
[] = "browsers";
57 const char kAdbDeviceIdFormat
[] = "device:%s";
59 const char kAdbBrowserNameField
[] = "adbBrowserName";
60 const char kAdbBrowserVersionField
[] = "adbBrowserVersion";
61 const char kAdbBrowserChromeVersionField
[] = "adbBrowserChromeVersion";
62 const char kCompatibleVersion
[] = "compatibleVersion";
63 const char kAdbPagesList
[] = "pages";
65 const char kAdbScreenWidthField
[] = "adbScreenWidth";
66 const char kAdbScreenHeightField
[] = "adbScreenHeight";
67 const char kAdbAttachedForeignField
[] = "adbAttachedForeign";
69 // CancelableTimer ------------------------------------------------------------
71 class CancelableTimer
{
73 CancelableTimer(base::Closure callback
, base::TimeDelta delay
)
74 : callback_(callback
),
76 base::MessageLoop::current()->PostDelayedTask(
78 base::Bind(&CancelableTimer::Fire
, weak_factory_
.GetWeakPtr()),
83 void Fire() { callback_
.Run(); }
85 base::Closure callback_
;
86 base::WeakPtrFactory
<CancelableTimer
> weak_factory_
;
89 // RenderViewHostTargetsUIHandler ---------------------------------------------
91 class RenderViewHostTargetsUIHandler
92 : public DevToolsTargetsUIHandler
,
93 public content::NotificationObserver
{
95 explicit RenderViewHostTargetsUIHandler(Callback callback
);
96 virtual ~RenderViewHostTargetsUIHandler();
98 // content::NotificationObserver overrides.
99 virtual void Observe(int type
,
100 const content::NotificationSource
& source
,
101 const content::NotificationDetails
& details
) OVERRIDE
;
103 void UpdateTargets();
105 content::NotificationRegistrar notification_registrar_
;
106 scoped_ptr
<CancelableTimer
> timer_
;
109 RenderViewHostTargetsUIHandler::RenderViewHostTargetsUIHandler(
111 : DevToolsTargetsUIHandler(kTargetSourceRenderer
, callback
) {
112 notification_registrar_
.Add(this,
113 content::NOTIFICATION_WEB_CONTENTS_CONNECTED
,
114 content::NotificationService::AllSources());
115 notification_registrar_
.Add(this,
116 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED
,
117 content::NotificationService::AllSources());
118 notification_registrar_
.Add(this,
119 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
120 content::NotificationService::AllSources());
124 RenderViewHostTargetsUIHandler::~RenderViewHostTargetsUIHandler() {
125 notification_registrar_
.RemoveAll();
128 void RenderViewHostTargetsUIHandler::Observe(
130 const content::NotificationSource
& source
,
131 const content::NotificationDetails
& details
) {
132 const int kUpdateDelay
= 100;
135 base::Bind(&RenderViewHostTargetsUIHandler::UpdateTargets
,
136 base::Unretained(this)),
137 base::TimeDelta::FromMilliseconds(kUpdateDelay
)));
140 void RenderViewHostTargetsUIHandler::UpdateTargets() {
141 scoped_ptr
<base::ListValue
> list_value(new base::ListValue());
143 std::map
<RenderFrameHost
*, base::DictionaryValue
*> rfh_to_descriptor
;
144 std::vector
<RenderFrameHost
*> nested_frames
;
146 DevToolsTargetImpl::List targets
=
147 DevToolsTargetImpl::EnumerateRenderViewHostTargets();
149 STLDeleteValues(&targets_
);
150 for (DevToolsTargetImpl::List::iterator it
= targets
.begin();
151 it
!= targets
.end(); ++it
) {
152 scoped_ptr
<DevToolsTargetImpl
> target(*it
);
153 content::RenderViewHost
* rvh
= target
->GetRenderViewHost();
157 DevToolsTargetImpl
* target_ptr
= target
.get();
158 targets_
[target_ptr
->GetId()] = target
.release();
159 base::DictionaryValue
* descriptor
= Serialize(*target_ptr
);
161 // TODO (kaznacheev): GetMainFrame() call is a temporary hack.
162 // Revisit this when multiple OOP frames are supported.
163 RenderFrameHost
* rfh
= rvh
->GetMainFrame();
164 rfh_to_descriptor
[rfh
] = descriptor
;
165 if (rvh
->GetProcess()->IsGuest() || rfh
->IsCrossProcessSubframe()) {
166 nested_frames
.push_back(rfh
);
168 list_value
->Append(descriptor
);
172 // Add the list of nested targets to each of its owners.
173 for (std::vector
<RenderFrameHost
*>::iterator
it(nested_frames
.begin());
174 it
!= nested_frames
.end(); ++it
) {
175 RenderFrameHost
* rfh
= (*it
);
176 RenderFrameHost
* parent_rfh
= NULL
;
177 content::RenderViewHost
* rvh
= rfh
->GetRenderViewHost();
178 if (rvh
->GetProcess()->IsGuest()) {
179 WebContents
* nested_web_contents
= WebContents::FromRenderViewHost(rvh
);
180 WebContents
* embedder
= nested_web_contents
->GetEmbedderWebContents();
181 parent_rfh
= embedder
->GetRenderViewHost()->GetMainFrame();
183 parent_rfh
= rfh
->GetParent();
186 if (parent_rfh
&& rfh_to_descriptor
.count(parent_rfh
) > 0) {
187 base::DictionaryValue
* parent
= rfh_to_descriptor
[parent_rfh
];
188 base::ListValue
* guests
= NULL
;
189 if (!parent
->GetList(kGuestList
, &guests
)) {
190 guests
= new base::ListValue();
191 parent
->Set(kGuestList
, guests
);
193 guests
->Append(rfh_to_descriptor
[rfh
]);
197 SendSerializedTargets(list_value
.Pass());
200 // WorkerObserver -------------------------------------------------------------
203 : public content::WorkerServiceObserver
,
204 public base::RefCountedThreadSafe
<WorkerObserver
> {
208 void Start(DevToolsTargetImpl::Callback callback
) {
209 DCHECK(callback_
.is_null());
210 DCHECK(!callback
.is_null());
211 callback_
= callback
;
212 BrowserThread::PostTask(
213 BrowserThread::IO
, FROM_HERE
,
214 base::Bind(&WorkerObserver::StartOnIOThread
, this));
218 DCHECK(!callback_
.is_null());
219 callback_
= DevToolsTargetImpl::Callback();
220 BrowserThread::PostTask(
221 BrowserThread::IO
, FROM_HERE
,
222 base::Bind(&WorkerObserver::StopOnIOThread
, this));
226 BrowserThread::PostTask(
227 BrowserThread::IO
, FROM_HERE
,
228 base::Bind(&WorkerObserver::EnumerateOnIOThread
,
233 friend class base::RefCountedThreadSafe
<WorkerObserver
>;
234 virtual ~WorkerObserver() {}
236 // content::WorkerServiceObserver overrides:
237 virtual void WorkerCreated(
239 const base::string16
& name
,
241 int route_id
) OVERRIDE
{
242 EnumerateOnIOThread();
245 virtual void WorkerDestroyed(int process_id
, int route_id
) OVERRIDE
{
246 EnumerateOnIOThread();
249 void StartOnIOThread() {
250 content::WorkerService::GetInstance()->AddObserver(this);
251 EnumerateOnIOThread();
254 void StopOnIOThread() {
255 content::WorkerService::GetInstance()->RemoveObserver(this);
258 void EnumerateOnIOThread() {
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
260 DevToolsTargetImpl::EnumerateWorkerTargets(
261 base::Bind(&WorkerObserver::RespondOnUIThread
, this));
264 void RespondOnUIThread(const DevToolsTargetImpl::List
& targets
) {
265 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
266 if (callback_
.is_null())
268 callback_
.Run(targets
);
271 DevToolsTargetImpl::Callback callback_
;
274 // WorkerTargetsUIHandler -----------------------------------------------------
276 class WorkerTargetsUIHandler
277 : public DevToolsTargetsUIHandler
,
278 public content::BrowserChildProcessObserver
{
280 explicit WorkerTargetsUIHandler(Callback callback
);
281 virtual ~WorkerTargetsUIHandler();
284 // content::BrowserChildProcessObserver overrides.
285 virtual void BrowserChildProcessHostConnected(
286 const content::ChildProcessData
& data
) OVERRIDE
;
287 virtual void BrowserChildProcessHostDisconnected(
288 const content::ChildProcessData
& data
) OVERRIDE
;
290 void UpdateTargets(const DevToolsTargetImpl::List
& targets
);
292 scoped_refptr
<WorkerObserver
> observer_
;
295 WorkerTargetsUIHandler::WorkerTargetsUIHandler(Callback callback
)
296 : DevToolsTargetsUIHandler(kTargetSourceWorker
, callback
),
297 observer_(new WorkerObserver()) {
298 observer_
->Start(base::Bind(&WorkerTargetsUIHandler::UpdateTargets
,
299 base::Unretained(this)));
300 BrowserChildProcessObserver::Add(this);
303 WorkerTargetsUIHandler::~WorkerTargetsUIHandler() {
304 BrowserChildProcessObserver::Remove(this);
308 void WorkerTargetsUIHandler::BrowserChildProcessHostConnected(
309 const content::ChildProcessData
& data
) {
310 if (data
.process_type
== content::PROCESS_TYPE_WORKER
)
311 observer_
->Enumerate();
314 void WorkerTargetsUIHandler::BrowserChildProcessHostDisconnected(
315 const content::ChildProcessData
& data
) {
316 if (data
.process_type
== content::PROCESS_TYPE_WORKER
)
317 observer_
->Enumerate();
320 void WorkerTargetsUIHandler::UpdateTargets(
321 const DevToolsTargetImpl::List
& targets
) {
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
323 scoped_ptr
<base::ListValue
> list_value(new base::ListValue());
324 STLDeleteValues(&targets_
);
325 for (DevToolsTargetImpl::List::const_iterator it
= targets
.begin();
326 it
!= targets
.end(); ++it
) {
327 DevToolsTargetImpl
* target
= *it
;
328 list_value
->Append(Serialize(*target
));
329 targets_
[target
->GetId()] = target
;
331 SendSerializedTargets(list_value
.Pass());
334 // AdbTargetsUIHandler --------------------------------------------------------
336 class AdbTargetsUIHandler
337 : public DevToolsTargetsUIHandler
,
338 public DevToolsAndroidBridge::DeviceListListener
{
340 AdbTargetsUIHandler(Callback callback
, Profile
* profile
);
341 virtual ~AdbTargetsUIHandler();
343 virtual void Open(const std::string
& browser_id
,
344 const std::string
& url
,
345 const DevToolsTargetsUIHandler::TargetCallback
&) OVERRIDE
;
348 // DevToolsAndroidBridge::Listener overrides.
349 virtual void DeviceListChanged(
350 const DevToolsAndroidBridge::RemoteDevices
& devices
) OVERRIDE
;
354 typedef std::map
<std::string
,
355 scoped_refptr
<DevToolsAndroidBridge::RemoteBrowser
> > RemoteBrowsers
;
356 RemoteBrowsers remote_browsers_
;
359 AdbTargetsUIHandler::AdbTargetsUIHandler(Callback callback
, Profile
* profile
)
360 : DevToolsTargetsUIHandler(kTargetSourceAdb
, callback
),
362 DevToolsAndroidBridge
* android_bridge
=
363 DevToolsAndroidBridge::Factory::GetForProfile(profile_
);
365 android_bridge
->AddDeviceListListener(this);
368 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
369 DevToolsAndroidBridge
* android_bridge
=
370 DevToolsAndroidBridge::Factory::GetForProfile(profile_
);
372 android_bridge
->RemoveDeviceListListener(this);
375 void AdbTargetsUIHandler::Open(
376 const std::string
& browser_id
,
377 const std::string
& url
,
378 const DevToolsTargetsUIHandler::TargetCallback
& callback
) {
379 RemoteBrowsers::iterator it
= remote_browsers_
.find(browser_id
);
380 if (it
!= remote_browsers_
.end())
381 it
->second
->Open(url
, callback
);
384 void AdbTargetsUIHandler::DeviceListChanged(
385 const DevToolsAndroidBridge::RemoteDevices
& devices
) {
386 remote_browsers_
.clear();
387 STLDeleteValues(&targets_
);
389 scoped_ptr
<base::ListValue
> device_list(new base::ListValue());
390 for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit
=
391 devices
.begin(); dit
!= devices
.end(); ++dit
) {
392 DevToolsAndroidBridge::RemoteDevice
* device
= dit
->get();
393 base::DictionaryValue
* device_data
= new base::DictionaryValue();
394 device_data
->SetString(kAdbModelField
, device
->model());
395 device_data
->SetString(kAdbSerialField
, device
->serial());
396 device_data
->SetBoolean(kAdbConnectedField
, device
->is_connected());
397 std::string device_id
= base::StringPrintf(
399 device
->serial().c_str());
400 device_data
->SetString(kTargetIdField
, device_id
);
401 base::ListValue
* browser_list
= new base::ListValue();
402 device_data
->Set(kAdbBrowsersList
, browser_list
);
404 DevToolsAndroidBridge::RemoteBrowsers
& browsers
= device
->browsers();
405 for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit
=
406 browsers
.begin(); bit
!= browsers
.end(); ++bit
) {
407 DevToolsAndroidBridge::RemoteBrowser
* browser
= bit
->get();
408 base::DictionaryValue
* browser_data
= new base::DictionaryValue();
409 browser_data
->SetString(kAdbBrowserNameField
, browser
->display_name());
410 browser_data
->SetString(kAdbBrowserVersionField
, browser
->version());
411 DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed
=
412 browser
->GetParsedVersion();
413 browser_data
->SetInteger(
414 kAdbBrowserChromeVersionField
,
415 browser
->IsChrome() && !parsed
.empty() ? parsed
[0] : 0);
416 std::string browser_id
= base::StringPrintf(
417 "browser:%s:%s:%s:%s",
418 device
->serial().c_str(), // Ensure uniqueness across devices.
419 browser
->display_name().c_str(), // Sort by display name.
420 browser
->version().c_str(), // Then by version.
421 browser
->socket().c_str()); // Ensure uniqueness on the device.
422 browser_data
->SetString(kTargetIdField
, browser_id
);
423 browser_data
->SetString(kTargetSourceField
, source_id());
425 base::Version remote_version
;
426 if (browser
->IsChrome()) {
427 remote_version
= base::Version(browser
->version());
429 // Try parse WebView version.
430 std::string version
= browser
->version();
431 size_t pos
= version
.find("Chrome/");
432 if (pos
!= std::string::npos
) {
433 remote_version
= base::Version(browser
->version().substr(pos
+ 7));
436 chrome::VersionInfo version_info
;
437 base::Version
local_version(version_info
.Version());
439 browser_data
->SetBoolean(kCompatibleVersion
,
440 (!remote_version
.IsValid()) || (!local_version
.IsValid()) ||
441 remote_version
.components()[0] <= local_version
.components()[0]);
443 base::ListValue
* page_list
= new base::ListValue();
444 remote_browsers_
[browser_id
] = browser
;
445 browser_data
->Set(kAdbPagesList
, page_list
);
446 DevToolsTargetImpl::List pages
= browser
->CreatePageTargets();
447 for (DevToolsTargetImpl::List::iterator it
=
448 pages
.begin(); it
!= pages
.end(); ++it
) {
449 DevToolsTargetImpl
* target
= *it
;
450 base::DictionaryValue
* target_data
= Serialize(*target
);
451 target_data
->SetBoolean(
452 kAdbAttachedForeignField
,
453 target
->IsAttached() &&
454 !DevToolsAndroidBridge::HasDevToolsWindow(target
->GetId()));
455 // Pass the screen size in the target object to make sure that
456 // the caching logic does not prevent the target item from updating
457 // when the screen size changes.
458 gfx::Size screen_size
= device
->screen_size();
459 target_data
->SetInteger(kAdbScreenWidthField
, screen_size
.width());
460 target_data
->SetInteger(kAdbScreenHeightField
, screen_size
.height());
461 targets_
[target
->GetId()] = target
;
462 page_list
->Append(target_data
);
464 browser_list
->Append(browser_data
);
467 device_list
->Append(device_data
);
469 SendSerializedTargets(device_list
.Pass());
474 // DevToolsTargetsUIHandler ---------------------------------------------------
476 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
477 const std::string
& source_id
,
479 : source_id_(source_id
),
480 callback_(callback
) {
483 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
484 STLDeleteValues(&targets_
);
488 scoped_ptr
<DevToolsTargetsUIHandler
>
489 DevToolsTargetsUIHandler::CreateForRenderers(
490 DevToolsTargetsUIHandler::Callback callback
) {
491 return scoped_ptr
<DevToolsTargetsUIHandler
>(
492 new RenderViewHostTargetsUIHandler(callback
));
496 scoped_ptr
<DevToolsTargetsUIHandler
>
497 DevToolsTargetsUIHandler::CreateForWorkers(
498 DevToolsTargetsUIHandler::Callback callback
) {
499 return scoped_ptr
<DevToolsTargetsUIHandler
>(
500 new WorkerTargetsUIHandler(callback
));
504 scoped_ptr
<DevToolsTargetsUIHandler
>
505 DevToolsTargetsUIHandler::CreateForAdb(
506 DevToolsTargetsUIHandler::Callback callback
, Profile
* profile
) {
507 return scoped_ptr
<DevToolsTargetsUIHandler
>(
508 new AdbTargetsUIHandler(callback
, profile
));
511 DevToolsTargetImpl
* DevToolsTargetsUIHandler::GetTarget(
512 const std::string
& target_id
) {
513 TargetMap::iterator it
= targets_
.find(target_id
);
514 if (it
!= targets_
.end())
519 void DevToolsTargetsUIHandler::Open(const std::string
& browser_id
,
520 const std::string
& url
,
521 const TargetCallback
& callback
) {
525 base::DictionaryValue
* DevToolsTargetsUIHandler::Serialize(
526 const DevToolsTargetImpl
& target
) {
527 base::DictionaryValue
* target_data
= new base::DictionaryValue();
528 target_data
->SetString(kTargetSourceField
, source_id_
);
529 target_data
->SetString(kTargetIdField
, target
.GetId());
530 target_data
->SetString(kTargetTypeField
, target
.GetType());
531 target_data
->SetBoolean(kAttachedField
, target
.IsAttached());
532 target_data
->SetString(kUrlField
, target
.GetUrl().spec());
533 target_data
->SetString(kNameField
, net::EscapeForHTML(target
.GetTitle()));
534 target_data
->SetString(kFaviconUrlField
, target
.GetFaviconUrl().spec());
535 target_data
->SetString(kDescriptionField
, target
.GetDescription());
539 void DevToolsTargetsUIHandler::SendSerializedTargets(
540 scoped_ptr
<base::ListValue
> list
) {
541 callback_
.Run(source_id_
, list
.Pass());
544 // PortForwardingStatusSerializer ---------------------------------------------
546 PortForwardingStatusSerializer::PortForwardingStatusSerializer(
547 const Callback
& callback
, Profile
* profile
)
548 : callback_(callback
),
550 PortForwardingController
* port_forwarding_controller
=
551 PortForwardingController::Factory::GetForProfile(profile_
);
552 if (port_forwarding_controller
)
553 port_forwarding_controller
->AddListener(this);
556 PortForwardingStatusSerializer::~PortForwardingStatusSerializer() {
557 PortForwardingController
* port_forwarding_controller
=
558 PortForwardingController::Factory::GetForProfile(profile_
);
559 if (port_forwarding_controller
)
560 port_forwarding_controller
->RemoveListener(this);
563 void PortForwardingStatusSerializer::PortStatusChanged(
564 const DevicesStatus
& status
) {
565 base::DictionaryValue result
;
566 for (DevicesStatus::const_iterator sit
= status
.begin();
567 sit
!= status
.end(); ++sit
) {
568 base::DictionaryValue
* device_status_dict
= new base::DictionaryValue();
569 const PortStatusMap
& device_status_map
= sit
->second
;
570 for (PortStatusMap::const_iterator it
= device_status_map
.begin();
571 it
!= device_status_map
.end(); ++it
) {
572 device_status_dict
->SetInteger(
573 base::StringPrintf("%d", it
->first
), it
->second
);
576 std::string device_id
= base::StringPrintf(
579 result
.Set(device_id
, device_status_dict
);
581 callback_
.Run(result
);