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