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