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 28using content::BrowserThread; 29 30namespace { 31 32const char kTargetSourceField[] = "source"; 33const char kTargetSourceLocal[] = "local"; 34const char kTargetSourceRemote[] = "remote"; 35 36const char kTargetIdField[] = "id"; 37const char kTargetTypeField[] = "type"; 38const char kAttachedField[] = "attached"; 39const char kUrlField[] = "url"; 40const char kNameField[] = "name"; 41const char kFaviconUrlField[] = "faviconUrl"; 42const char kDescriptionField[] = "description"; 43 44const char kGuestList[] = "guests"; 45 46const char kAdbModelField[] = "adbModel"; 47const char kAdbConnectedField[] = "adbConnected"; 48const char kAdbSerialField[] = "adbSerial"; 49const char kAdbBrowsersList[] = "browsers"; 50const char kAdbDeviceIdFormat[] = "device:%s"; 51 52const char kAdbBrowserNameField[] = "adbBrowserName"; 53const char kAdbBrowserVersionField[] = "adbBrowserVersion"; 54const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion"; 55const char kCompatibleVersion[] = "compatibleVersion"; 56const char kAdbPagesList[] = "pages"; 57 58const char kAdbScreenWidthField[] = "adbScreenWidth"; 59const char kAdbScreenHeightField[] = "adbScreenHeight"; 60const char kAdbAttachedForeignField[] = "adbAttachedForeign"; 61 62// CancelableTimer ------------------------------------------------------------ 63 64class CancelableTimer { 65 public: 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: 76 void Fire() { callback_.Run(); } 77 78 base::Closure callback_; 79 base::WeakPtrFactory<CancelableTimer> weak_factory_; 80}; 81 82// WorkerObserver ------------------------------------------------------------- 83 84class WorkerObserver 85 : public content::WorkerServiceObserver, 86 public base::RefCountedThreadSafe<WorkerObserver> { 87 public: 88 WorkerObserver() {} 89 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 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>; 109 virtual ~WorkerObserver() {} 110 111 // content::WorkerServiceObserver overrides: 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 120 virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { 121 NotifyOnIOThread(); 122 } 123 124 void StartOnIOThread() { 125 content::WorkerService::GetInstance()->AddObserver(this); 126 } 127 128 void StopOnIOThread() { 129 content::WorkerService::GetInstance()->RemoveObserver(this); 130 } 131 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 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 152class 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 162private: 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 178LocalTargetsUIHandler::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 197LocalTargetsUIHandler::~LocalTargetsUIHandler() { 198 notification_registrar_.RemoveAll(); 199 observer_->Stop(); 200} 201 202void LocalTargetsUIHandler::Observe( 203 int type, 204 const content::NotificationSource& source, 205 const content::NotificationDetails& details) { 206 ScheduleUpdate(); 207} 208 209void LocalTargetsUIHandler::ForceUpdate() { 210 ScheduleUpdate(); 211} 212 213void 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 222void LocalTargetsUIHandler::UpdateTargets() { 223 DevToolsTargetImpl::EnumerateAllTargets(base::Bind( 224 &LocalTargetsUIHandler::SendTargets, 225 weak_factory_.GetWeakPtr())); 226} 227 228void 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 263class 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 289AdbTargetsUIHandler::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 299AdbTargetsUIHandler::~AdbTargetsUIHandler() { 300 DevToolsAndroidBridge* android_bridge = 301 DevToolsAndroidBridge::Factory::GetForProfile(profile_); 302 if (android_bridge) 303 android_bridge->RemoveDeviceListListener(this); 304} 305 306static 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 313void 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 322scoped_refptr<content::DevToolsAgentHost> 323AdbTargetsUIHandler::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 329void 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 415DevToolsTargetsUIHandler::DevToolsTargetsUIHandler( 416 const std::string& source_id, 417 const Callback& callback) 418 : source_id_(source_id), 419 callback_(callback) { 420} 421 422DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() { 423 STLDeleteValues(&targets_); 424} 425 426// static 427scoped_ptr<DevToolsTargetsUIHandler> 428DevToolsTargetsUIHandler::CreateForLocal( 429 const DevToolsTargetsUIHandler::Callback& callback) { 430 return scoped_ptr<DevToolsTargetsUIHandler>( 431 new LocalTargetsUIHandler(callback)); 432} 433 434// static 435scoped_ptr<DevToolsTargetsUIHandler> 436DevToolsTargetsUIHandler::CreateForAdb( 437 const DevToolsTargetsUIHandler::Callback& callback, Profile* profile) { 438 return scoped_ptr<DevToolsTargetsUIHandler>( 439 new AdbTargetsUIHandler(callback, profile)); 440} 441 442DevToolsTargetImpl* 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 450void DevToolsTargetsUIHandler::Open(const std::string& browser_id, 451 const std::string& url, 452 const TargetCallback& callback) { 453 callback.Run(NULL); 454} 455 456scoped_refptr<content::DevToolsAgentHost> 457DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) { 458 return NULL; 459} 460 461base::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 475void DevToolsTargetsUIHandler::SendSerializedTargets( 476 const base::ListValue& list) { 477 callback_.Run(source_id_, list); 478} 479 480void DevToolsTargetsUIHandler::ForceUpdate() { 481} 482 483// PortForwardingStatusSerializer --------------------------------------------- 484 485PortForwardingStatusSerializer::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 495PortForwardingStatusSerializer::~PortForwardingStatusSerializer() { 496 DevToolsAndroidBridge* android_bridge = 497 DevToolsAndroidBridge::Factory::GetForProfile(profile_); 498 if (android_bridge) 499 android_bridge->RemovePortForwardingListener(this); 500} 501 502void 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