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 kAdbBrowserVersionField[] = "adbBrowserVersion";
54 const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion";
55 const char kCompatibleVersion[] = "compatibleVersion";
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();
297 scoped_refptr<DevToolsAndroidBridge> 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_.get());
311 android_bridge_->AddDeviceListListener(this);
314 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
315 android_bridge_->RemoveDeviceListListener(this);
318 static void CallOnTarget(
319 const DevToolsTargetsUIHandler::TargetCallback& callback,
320 scoped_refptr<DevToolsAndroidBridge> bridge,
321 scoped_refptr<DevToolsAndroidBridge::RemotePage> page) {
322 callback.Run(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(kAdbBrowserVersionField, browser->version());
376 DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed =
377 browser->GetParsedVersion();
378 browser_data->SetInteger(
379 kAdbBrowserChromeVersionField,
380 browser->IsChrome() && !parsed.empty() ? parsed[0] : 0);
381 std::string browser_id = SerializeBrowserId(browser);
382 browser_data->SetString(kTargetIdField, browser_id);
383 browser_data->SetString(kTargetSourceField, source_id());
385 base::Version remote_version;
386 remote_version = base::Version(browser->version());
388 chrome::VersionInfo version_info;
389 base::Version local_version(version_info.Version());
391 browser_data->SetBoolean(kCompatibleVersion,
392 (!remote_version.IsValid()) || (!local_version.IsValid()) ||
393 remote_version.components()[0] <= local_version.components()[0]);
395 base::ListValue* page_list = new base::ListValue();
396 remote_browsers_[browser_id] = browser;
397 browser_data->Set(kAdbPagesList, page_list);
398 for (const auto& page : browser->pages()) {
399 DevToolsTargetImpl* target = android_bridge_->CreatePageTarget(page);
400 base::DictionaryValue* target_data = Serialize(*target);
401 target_data->SetBoolean(
402 kAdbAttachedForeignField,
403 target->IsAttached() &&
404 !android_bridge_->HasDevToolsWindow(target->GetId()));
405 // Pass the screen size in the target object to make sure that
406 // the caching logic does not prevent the target item from updating
407 // when the screen size changes.
408 gfx::Size screen_size = device->screen_size();
409 target_data->SetInteger(kAdbScreenWidthField, screen_size.width());
410 target_data->SetInteger(kAdbScreenHeightField, screen_size.height());
411 targets_[target->GetId()] = target;
412 page_list->Append(target_data);
414 browser_list->Append(browser_data);
417 device_list.Append(device_data);
419 SendSerializedTargets(device_list);
424 // DevToolsTargetsUIHandler ---------------------------------------------------
426 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
427 const std::string& source_id,
428 const Callback& callback)
429 : source_id_(source_id),
430 callback_(callback) {
433 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
434 STLDeleteValues(&targets_);
438 scoped_ptr<DevToolsTargetsUIHandler>
439 DevToolsTargetsUIHandler::CreateForLocal(
440 const DevToolsTargetsUIHandler::Callback& callback) {
441 return scoped_ptr<DevToolsTargetsUIHandler>(
442 new LocalTargetsUIHandler(callback));
446 scoped_ptr<DevToolsTargetsUIHandler>
447 DevToolsTargetsUIHandler::CreateForAdb(
448 const DevToolsTargetsUIHandler::Callback& callback, Profile* profile) {
449 return scoped_ptr<DevToolsTargetsUIHandler>(
450 new AdbTargetsUIHandler(callback, profile));
453 DevToolsTargetImpl* DevToolsTargetsUIHandler::GetTarget(
454 const std::string& target_id) {
455 TargetMap::iterator it = targets_.find(target_id);
456 if (it != targets_.end())
461 void DevToolsTargetsUIHandler::Open(const std::string& browser_id,
462 const std::string& url,
463 const TargetCallback& callback) {
467 scoped_refptr<content::DevToolsAgentHost>
468 DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) {
472 base::DictionaryValue* DevToolsTargetsUIHandler::Serialize(
473 const DevToolsTargetImpl& target) {
474 base::DictionaryValue* target_data = new base::DictionaryValue();
475 target_data->SetString(kTargetSourceField, source_id_);
476 target_data->SetString(kTargetIdField, target.GetId());
477 target_data->SetString(kTargetTypeField, target.GetType());
478 target_data->SetBoolean(kAttachedField, target.IsAttached());
479 target_data->SetString(kUrlField, target.GetURL().spec());
480 target_data->SetString(kNameField, net::EscapeForHTML(target.GetTitle()));
481 target_data->SetString(kFaviconUrlField, target.GetFaviconURL().spec());
482 target_data->SetString(kDescriptionField, target.GetDescription());
486 void DevToolsTargetsUIHandler::SendSerializedTargets(
487 const base::ListValue& list) {
488 callback_.Run(source_id_, list);
491 void DevToolsTargetsUIHandler::ForceUpdate() {
494 // PortForwardingStatusSerializer ---------------------------------------------
496 PortForwardingStatusSerializer::PortForwardingStatusSerializer(
497 const Callback& callback, Profile* profile)
498 : callback_(callback),
500 DevToolsAndroidBridge* android_bridge =
501 DevToolsAndroidBridge::Factory::GetForProfile(profile_);
503 android_bridge->AddPortForwardingListener(this);
506 PortForwardingStatusSerializer::~PortForwardingStatusSerializer() {
507 DevToolsAndroidBridge* android_bridge =
508 DevToolsAndroidBridge::Factory::GetForProfile(profile_);
510 android_bridge->RemovePortForwardingListener(this);
513 void PortForwardingStatusSerializer::PortStatusChanged(
514 const ForwardingStatus& status) {
515 base::DictionaryValue result;
516 for (ForwardingStatus::const_iterator sit = status.begin();
517 sit != status.end(); ++sit) {
518 base::DictionaryValue* port_status_dict = new base::DictionaryValue();
519 const PortStatusMap& port_status_map = sit->second;
520 for (PortStatusMap::const_iterator it = port_status_map.begin();
521 it != port_status_map.end(); ++it) {
522 port_status_dict->SetInteger(
523 base::StringPrintf("%d", it->first), it->second);
526 base::DictionaryValue* device_status_dict = new base::DictionaryValue();
527 device_status_dict->Set(kPortForwardingPorts, port_status_dict);
528 device_status_dict->SetString(kPortForwardingBrowserId,
529 SerializeBrowserId(sit->first));
531 std::string device_id = base::StringPrintf(
533 sit->first->serial().c_str());
534 result.Set(device_id, device_status_dict);
536 callback_.Run(result);