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/browser/guest_view/guest_view_base.h"
15 #include "chrome/common/chrome_version_info.h"
16 #include "content/public/browser/browser_child_process_observer.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/child_process_data.h"
19 #include "content/public/browser/notification_observer.h"
20 #include "content/public/browser/notification_registrar.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_source.h"
23 #include "content/public/browser/notification_types.h"
24 #include "content/public/browser/render_frame_host.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "content/public/browser/render_view_host.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/worker_service.h"
29 #include "content/public/browser/worker_service_observer.h"
30 #include "content/public/common/process_type.h"
31 #include "net/base/escape.h"
33 using content::BrowserThread
;
34 using content::RenderFrameHost
;
35 using content::WebContents
;
39 const char kTargetSourceField
[] = "source";
40 const char kTargetSourceRenderer
[] = "renderers";
41 const char kTargetSourceWorker
[] = "workers";
42 const char kTargetSourceAdb
[] = "adb";
44 const char kTargetIdField
[] = "id";
45 const char kTargetTypeField
[] = "type";
46 const char kAttachedField
[] = "attached";
47 const char kUrlField
[] = "url";
48 const char kNameField
[] = "name";
49 const char kFaviconUrlField
[] = "faviconUrl";
50 const char kDescriptionField
[] = "description";
52 const char kGuestList
[] = "guests";
54 const char kAdbModelField
[] = "adbModel";
55 const char kAdbConnectedField
[] = "adbConnected";
56 const char kAdbSerialField
[] = "adbSerial";
57 const char kAdbBrowsersList
[] = "browsers";
58 const char kAdbDeviceIdFormat
[] = "device:%s";
60 const char kAdbBrowserNameField
[] = "adbBrowserName";
61 const char kAdbBrowserVersionField
[] = "adbBrowserVersion";
62 const char kAdbBrowserChromeVersionField
[] = "adbBrowserChromeVersion";
63 const char kCompatibleVersion
[] = "compatibleVersion";
64 const char kAdbPagesList
[] = "pages";
66 const char kAdbScreenWidthField
[] = "adbScreenWidth";
67 const char kAdbScreenHeightField
[] = "adbScreenHeight";
68 const char kAdbAttachedForeignField
[] = "adbAttachedForeign";
70 // CancelableTimer ------------------------------------------------------------
72 class CancelableTimer
{
74 CancelableTimer(base::Closure callback
, base::TimeDelta delay
)
75 : callback_(callback
),
77 base::MessageLoop::current()->PostDelayedTask(
79 base::Bind(&CancelableTimer::Fire
, weak_factory_
.GetWeakPtr()),
84 void Fire() { callback_
.Run(); }
86 base::Closure callback_
;
87 base::WeakPtrFactory
<CancelableTimer
> weak_factory_
;
90 // RenderViewHostTargetsUIHandler ---------------------------------------------
92 class RenderViewHostTargetsUIHandler
93 : public DevToolsTargetsUIHandler
,
94 public content::NotificationObserver
{
96 explicit RenderViewHostTargetsUIHandler(Callback callback
);
97 virtual ~RenderViewHostTargetsUIHandler();
99 // content::NotificationObserver overrides.
100 virtual void Observe(int type
,
101 const content::NotificationSource
& source
,
102 const content::NotificationDetails
& details
) OVERRIDE
;
104 void UpdateTargets();
106 content::NotificationRegistrar notification_registrar_
;
107 scoped_ptr
<CancelableTimer
> timer_
;
110 RenderViewHostTargetsUIHandler::RenderViewHostTargetsUIHandler(
112 : DevToolsTargetsUIHandler(kTargetSourceRenderer
, callback
) {
113 notification_registrar_
.Add(this,
114 content::NOTIFICATION_WEB_CONTENTS_CONNECTED
,
115 content::NotificationService::AllSources());
116 notification_registrar_
.Add(this,
117 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED
,
118 content::NotificationService::AllSources());
119 notification_registrar_
.Add(this,
120 content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
121 content::NotificationService::AllSources());
125 RenderViewHostTargetsUIHandler::~RenderViewHostTargetsUIHandler() {
126 notification_registrar_
.RemoveAll();
129 void RenderViewHostTargetsUIHandler::Observe(
131 const content::NotificationSource
& source
,
132 const content::NotificationDetails
& details
) {
133 const int kUpdateDelay
= 100;
136 base::Bind(&RenderViewHostTargetsUIHandler::UpdateTargets
,
137 base::Unretained(this)),
138 base::TimeDelta::FromMilliseconds(kUpdateDelay
)));
141 void RenderViewHostTargetsUIHandler::UpdateTargets() {
142 scoped_ptr
<base::ListValue
> list_value(new base::ListValue());
144 std::map
<RenderFrameHost
*, base::DictionaryValue
*> rfh_to_descriptor
;
145 std::vector
<RenderFrameHost
*> nested_frames
;
147 DevToolsTargetImpl::List targets
=
148 DevToolsTargetImpl::EnumerateRenderViewHostTargets();
150 STLDeleteValues(&targets_
);
151 for (DevToolsTargetImpl::List::iterator it
= targets
.begin();
152 it
!= targets
.end(); ++it
) {
153 scoped_ptr
<DevToolsTargetImpl
> target(*it
);
154 content::RenderViewHost
* rvh
= target
->GetRenderViewHost();
158 DevToolsTargetImpl
* target_ptr
= target
.get();
159 targets_
[target_ptr
->GetId()] = target
.release();
160 base::DictionaryValue
* descriptor
= Serialize(*target_ptr
);
162 // TODO (kaznacheev): GetMainFrame() call is a temporary hack.
163 // Revisit this when multiple OOP frames are supported.
164 RenderFrameHost
* rfh
= rvh
->GetMainFrame();
165 rfh_to_descriptor
[rfh
] = descriptor
;
166 if (rvh
->GetProcess()->IsGuest() || rfh
->IsCrossProcessSubframe()) {
167 nested_frames
.push_back(rfh
);
169 list_value
->Append(descriptor
);
173 // Add the list of nested targets to each of its owners.
174 for (std::vector
<RenderFrameHost
*>::iterator
it(nested_frames
.begin());
175 it
!= nested_frames
.end(); ++it
) {
176 RenderFrameHost
* rfh
= (*it
);
177 RenderFrameHost
* parent_rfh
= NULL
;
178 content::RenderViewHost
* rvh
= rfh
->GetRenderViewHost();
179 WebContents
* nested_web_contents
= WebContents::FromRenderViewHost(rvh
);
180 GuestViewBase
* guest
= GuestViewBase::FromWebContents(nested_web_contents
);
182 WebContents
* embedder
= guest
->embedder_web_contents();
183 parent_rfh
= embedder
->GetRenderViewHost()->GetMainFrame();
185 parent_rfh
= rfh
->GetParent();
188 if (parent_rfh
&& rfh_to_descriptor
.count(parent_rfh
) > 0) {
189 base::DictionaryValue
* parent
= rfh_to_descriptor
[parent_rfh
];
190 base::ListValue
* guests
= NULL
;
191 if (!parent
->GetList(kGuestList
, &guests
)) {
192 guests
= new base::ListValue();
193 parent
->Set(kGuestList
, guests
);
195 guests
->Append(rfh_to_descriptor
[rfh
]);
199 SendSerializedTargets(list_value
.Pass());
202 // WorkerObserver -------------------------------------------------------------
205 : public content::WorkerServiceObserver
,
206 public base::RefCountedThreadSafe
<WorkerObserver
> {
210 void Start(DevToolsTargetImpl::Callback callback
) {
211 DCHECK(callback_
.is_null());
212 DCHECK(!callback
.is_null());
213 callback_
= callback
;
214 BrowserThread::PostTask(
215 BrowserThread::IO
, FROM_HERE
,
216 base::Bind(&WorkerObserver::StartOnIOThread
, this));
220 DCHECK(!callback_
.is_null());
221 callback_
= DevToolsTargetImpl::Callback();
222 BrowserThread::PostTask(
223 BrowserThread::IO
, FROM_HERE
,
224 base::Bind(&WorkerObserver::StopOnIOThread
, this));
228 BrowserThread::PostTask(
229 BrowserThread::IO
, FROM_HERE
,
230 base::Bind(&WorkerObserver::EnumerateOnIOThread
,
235 friend class base::RefCountedThreadSafe
<WorkerObserver
>;
236 virtual ~WorkerObserver() {}
238 // content::WorkerServiceObserver overrides:
239 virtual void WorkerCreated(
241 const base::string16
& name
,
243 int route_id
) OVERRIDE
{
244 EnumerateOnIOThread();
247 virtual void WorkerDestroyed(int process_id
, int route_id
) OVERRIDE
{
248 EnumerateOnIOThread();
251 void StartOnIOThread() {
252 content::WorkerService::GetInstance()->AddObserver(this);
253 EnumerateOnIOThread();
256 void StopOnIOThread() {
257 content::WorkerService::GetInstance()->RemoveObserver(this);
260 void EnumerateOnIOThread() {
261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
262 DevToolsTargetImpl::EnumerateWorkerTargets(
263 base::Bind(&WorkerObserver::RespondOnUIThread
, this));
266 void RespondOnUIThread(const DevToolsTargetImpl::List
& targets
) {
267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
268 if (callback_
.is_null())
270 callback_
.Run(targets
);
273 DevToolsTargetImpl::Callback callback_
;
276 // WorkerTargetsUIHandler -----------------------------------------------------
278 class WorkerTargetsUIHandler
279 : public DevToolsTargetsUIHandler
,
280 public content::BrowserChildProcessObserver
{
282 explicit WorkerTargetsUIHandler(Callback callback
);
283 virtual ~WorkerTargetsUIHandler();
286 // content::BrowserChildProcessObserver overrides.
287 virtual void BrowserChildProcessHostConnected(
288 const content::ChildProcessData
& data
) OVERRIDE
;
289 virtual void BrowserChildProcessHostDisconnected(
290 const content::ChildProcessData
& data
) OVERRIDE
;
292 void UpdateTargets(const DevToolsTargetImpl::List
& targets
);
294 scoped_refptr
<WorkerObserver
> observer_
;
297 WorkerTargetsUIHandler::WorkerTargetsUIHandler(Callback callback
)
298 : DevToolsTargetsUIHandler(kTargetSourceWorker
, callback
),
299 observer_(new WorkerObserver()) {
300 observer_
->Start(base::Bind(&WorkerTargetsUIHandler::UpdateTargets
,
301 base::Unretained(this)));
302 BrowserChildProcessObserver::Add(this);
305 WorkerTargetsUIHandler::~WorkerTargetsUIHandler() {
306 BrowserChildProcessObserver::Remove(this);
310 void WorkerTargetsUIHandler::BrowserChildProcessHostConnected(
311 const content::ChildProcessData
& data
) {
312 if (data
.process_type
== content::PROCESS_TYPE_WORKER
)
313 observer_
->Enumerate();
316 void WorkerTargetsUIHandler::BrowserChildProcessHostDisconnected(
317 const content::ChildProcessData
& data
) {
318 if (data
.process_type
== content::PROCESS_TYPE_WORKER
)
319 observer_
->Enumerate();
322 void WorkerTargetsUIHandler::UpdateTargets(
323 const DevToolsTargetImpl::List
& targets
) {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
325 scoped_ptr
<base::ListValue
> list_value(new base::ListValue());
326 STLDeleteValues(&targets_
);
327 for (DevToolsTargetImpl::List::const_iterator it
= targets
.begin();
328 it
!= targets
.end(); ++it
) {
329 DevToolsTargetImpl
* target
= *it
;
330 list_value
->Append(Serialize(*target
));
331 targets_
[target
->GetId()] = target
;
333 SendSerializedTargets(list_value
.Pass());
336 // AdbTargetsUIHandler --------------------------------------------------------
338 class AdbTargetsUIHandler
339 : public DevToolsTargetsUIHandler
,
340 public DevToolsAndroidBridge::DeviceListListener
{
342 AdbTargetsUIHandler(Callback callback
, Profile
* profile
);
343 virtual ~AdbTargetsUIHandler();
345 virtual void Open(const std::string
& browser_id
,
346 const std::string
& url
,
347 const DevToolsTargetsUIHandler::TargetCallback
&) OVERRIDE
;
349 virtual scoped_refptr
<content::DevToolsAgentHost
> GetBrowserAgentHost(
350 const std::string
& browser_id
) OVERRIDE
;
353 // DevToolsAndroidBridge::Listener overrides.
354 virtual void DeviceListChanged(
355 const DevToolsAndroidBridge::RemoteDevices
& devices
) OVERRIDE
;
359 typedef std::map
<std::string
,
360 scoped_refptr
<DevToolsAndroidBridge::RemoteBrowser
> > RemoteBrowsers
;
361 RemoteBrowsers remote_browsers_
;
364 AdbTargetsUIHandler::AdbTargetsUIHandler(Callback callback
, Profile
* profile
)
365 : DevToolsTargetsUIHandler(kTargetSourceAdb
, callback
),
367 DevToolsAndroidBridge
* android_bridge
=
368 DevToolsAndroidBridge::Factory::GetForProfile(profile_
);
370 android_bridge
->AddDeviceListListener(this);
373 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
374 DevToolsAndroidBridge
* android_bridge
=
375 DevToolsAndroidBridge::Factory::GetForProfile(profile_
);
377 android_bridge
->RemoveDeviceListListener(this);
380 static void CallOnTarget(
381 const DevToolsTargetsUIHandler::TargetCallback
& callback
,
382 DevToolsAndroidBridge::RemotePage
* page
) {
383 scoped_ptr
<DevToolsAndroidBridge::RemotePage
> my_page(page
);
384 callback
.Run(my_page
? my_page
->GetTarget() : NULL
);
387 void AdbTargetsUIHandler::Open(
388 const std::string
& browser_id
,
389 const std::string
& url
,
390 const DevToolsTargetsUIHandler::TargetCallback
& callback
) {
391 RemoteBrowsers::iterator it
= remote_browsers_
.find(browser_id
);
392 if (it
!= remote_browsers_
.end())
393 it
->second
->Open(url
, base::Bind(&CallOnTarget
, callback
));
396 scoped_refptr
<content::DevToolsAgentHost
>
397 AdbTargetsUIHandler::GetBrowserAgentHost(
398 const std::string
& browser_id
) {
399 RemoteBrowsers::iterator it
= remote_browsers_
.find(browser_id
);
400 return it
!= remote_browsers_
.end() ? it
->second
->GetAgentHost() : NULL
;
403 void AdbTargetsUIHandler::DeviceListChanged(
404 const DevToolsAndroidBridge::RemoteDevices
& devices
) {
405 remote_browsers_
.clear();
406 STLDeleteValues(&targets_
);
408 scoped_ptr
<base::ListValue
> device_list(new base::ListValue());
409 for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit
=
410 devices
.begin(); dit
!= devices
.end(); ++dit
) {
411 DevToolsAndroidBridge::RemoteDevice
* device
= dit
->get();
412 base::DictionaryValue
* device_data
= new base::DictionaryValue();
413 device_data
->SetString(kAdbModelField
, device
->model());
414 device_data
->SetString(kAdbSerialField
, device
->serial());
415 device_data
->SetBoolean(kAdbConnectedField
, device
->is_connected());
416 std::string device_id
= base::StringPrintf(
418 device
->serial().c_str());
419 device_data
->SetString(kTargetIdField
, device_id
);
420 base::ListValue
* browser_list
= new base::ListValue();
421 device_data
->Set(kAdbBrowsersList
, browser_list
);
423 DevToolsAndroidBridge::RemoteBrowsers
& browsers
= device
->browsers();
424 for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit
=
425 browsers
.begin(); bit
!= browsers
.end(); ++bit
) {
426 DevToolsAndroidBridge::RemoteBrowser
* browser
= bit
->get();
427 base::DictionaryValue
* browser_data
= new base::DictionaryValue();
428 browser_data
->SetString(kAdbBrowserNameField
, browser
->display_name());
429 browser_data
->SetString(kAdbBrowserVersionField
, browser
->version());
430 DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed
=
431 browser
->GetParsedVersion();
432 browser_data
->SetInteger(
433 kAdbBrowserChromeVersionField
,
434 browser
->IsChrome() && !parsed
.empty() ? parsed
[0] : 0);
435 std::string browser_id
= base::StringPrintf(
436 "browser:%s:%s:%s:%s",
437 device
->serial().c_str(), // Ensure uniqueness across devices.
438 browser
->display_name().c_str(), // Sort by display name.
439 browser
->version().c_str(), // Then by version.
440 browser
->socket().c_str()); // Ensure uniqueness on the device.
441 browser_data
->SetString(kTargetIdField
, browser_id
);
442 browser_data
->SetString(kTargetSourceField
, source_id());
444 base::Version remote_version
;
445 if (browser
->IsChrome()) {
446 remote_version
= base::Version(browser
->version());
448 // Try parse WebView version.
449 std::string version
= browser
->version();
450 size_t pos
= version
.find("Chrome/");
451 if (pos
!= std::string::npos
) {
452 remote_version
= base::Version(browser
->version().substr(pos
+ 7));
455 chrome::VersionInfo version_info
;
456 base::Version
local_version(version_info
.Version());
458 browser_data
->SetBoolean(kCompatibleVersion
,
459 (!remote_version
.IsValid()) || (!local_version
.IsValid()) ||
460 remote_version
.components()[0] <= local_version
.components()[0]);
462 base::ListValue
* page_list
= new base::ListValue();
463 remote_browsers_
[browser_id
] = browser
;
464 browser_data
->Set(kAdbPagesList
, page_list
);
465 std::vector
<DevToolsAndroidBridge::RemotePage
*> pages
=
466 browser
->CreatePages();
467 for (std::vector
<DevToolsAndroidBridge::RemotePage
*>::iterator it
=
468 pages
.begin(); it
!= pages
.end(); ++it
) {
469 DevToolsAndroidBridge::RemotePage
* page
= *it
;
470 DevToolsTargetImpl
* target
= page
->GetTarget();
471 base::DictionaryValue
* target_data
= Serialize(*target
);
472 target_data
->SetBoolean(
473 kAdbAttachedForeignField
,
474 target
->IsAttached() &&
475 !DevToolsAndroidBridge::HasDevToolsWindow(target
->GetId()));
476 // Pass the screen size in the target object to make sure that
477 // the caching logic does not prevent the target item from updating
478 // when the screen size changes.
479 gfx::Size screen_size
= device
->screen_size();
480 target_data
->SetInteger(kAdbScreenWidthField
, screen_size
.width());
481 target_data
->SetInteger(kAdbScreenHeightField
, screen_size
.height());
482 targets_
[target
->GetId()] = target
;
483 page_list
->Append(target_data
);
485 browser_list
->Append(browser_data
);
488 device_list
->Append(device_data
);
490 SendSerializedTargets(device_list
.Pass());
495 // DevToolsTargetsUIHandler ---------------------------------------------------
497 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
498 const std::string
& source_id
,
500 : source_id_(source_id
),
501 callback_(callback
) {
504 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
505 STLDeleteValues(&targets_
);
509 scoped_ptr
<DevToolsTargetsUIHandler
>
510 DevToolsTargetsUIHandler::CreateForRenderers(
511 DevToolsTargetsUIHandler::Callback callback
) {
512 return scoped_ptr
<DevToolsTargetsUIHandler
>(
513 new RenderViewHostTargetsUIHandler(callback
));
517 scoped_ptr
<DevToolsTargetsUIHandler
>
518 DevToolsTargetsUIHandler::CreateForWorkers(
519 DevToolsTargetsUIHandler::Callback callback
) {
520 return scoped_ptr
<DevToolsTargetsUIHandler
>(
521 new WorkerTargetsUIHandler(callback
));
525 scoped_ptr
<DevToolsTargetsUIHandler
>
526 DevToolsTargetsUIHandler::CreateForAdb(
527 DevToolsTargetsUIHandler::Callback callback
, Profile
* profile
) {
528 return scoped_ptr
<DevToolsTargetsUIHandler
>(
529 new AdbTargetsUIHandler(callback
, profile
));
532 DevToolsTargetImpl
* DevToolsTargetsUIHandler::GetTarget(
533 const std::string
& target_id
) {
534 TargetMap::iterator it
= targets_
.find(target_id
);
535 if (it
!= targets_
.end())
540 void DevToolsTargetsUIHandler::Open(const std::string
& browser_id
,
541 const std::string
& url
,
542 const TargetCallback
& callback
) {
546 scoped_refptr
<content::DevToolsAgentHost
>
547 DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string
& browser_id
) {
551 base::DictionaryValue
* DevToolsTargetsUIHandler::Serialize(
552 const DevToolsTargetImpl
& target
) {
553 base::DictionaryValue
* target_data
= new base::DictionaryValue();
554 target_data
->SetString(kTargetSourceField
, source_id_
);
555 target_data
->SetString(kTargetIdField
, target
.GetId());
556 target_data
->SetString(kTargetTypeField
, target
.GetType());
557 target_data
->SetBoolean(kAttachedField
, target
.IsAttached());
558 target_data
->SetString(kUrlField
, target
.GetURL().spec());
559 target_data
->SetString(kNameField
, net::EscapeForHTML(target
.GetTitle()));
560 target_data
->SetString(kFaviconUrlField
, target
.GetFaviconURL().spec());
561 target_data
->SetString(kDescriptionField
, target
.GetDescription());
565 void DevToolsTargetsUIHandler::SendSerializedTargets(
566 scoped_ptr
<base::ListValue
> list
) {
567 callback_
.Run(source_id_
, list
.Pass());
570 // PortForwardingStatusSerializer ---------------------------------------------
572 PortForwardingStatusSerializer::PortForwardingStatusSerializer(
573 const Callback
& callback
, Profile
* profile
)
574 : callback_(callback
),
576 PortForwardingController
* port_forwarding_controller
=
577 PortForwardingController::Factory::GetForProfile(profile_
);
578 if (port_forwarding_controller
)
579 port_forwarding_controller
->AddListener(this);
582 PortForwardingStatusSerializer::~PortForwardingStatusSerializer() {
583 PortForwardingController
* port_forwarding_controller
=
584 PortForwardingController::Factory::GetForProfile(profile_
);
585 if (port_forwarding_controller
)
586 port_forwarding_controller
->RemoveListener(this);
589 void PortForwardingStatusSerializer::PortStatusChanged(
590 const DevicesStatus
& status
) {
591 base::DictionaryValue result
;
592 for (DevicesStatus::const_iterator sit
= status
.begin();
593 sit
!= status
.end(); ++sit
) {
594 base::DictionaryValue
* device_status_dict
= new base::DictionaryValue();
595 const PortStatusMap
& device_status_map
= sit
->second
;
596 for (PortStatusMap::const_iterator it
= device_status_map
.begin();
597 it
!= device_status_map
.end(); ++it
) {
598 device_status_dict
->SetInteger(
599 base::StringPrintf("%d", it
->first
), it
->second
);
602 std::string device_id
= base::StringPrintf(
605 result
.Set(device_id
, device_status_dict
);
607 callback_
.Run(result
);