// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/devtools/devtools_targets_ui.h" #include "base/memory/weak_ptr.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "base/version.h" #include "chrome/browser/devtools/device/devtools_android_bridge.h" #include "chrome/browser/devtools/devtools_target_impl.h" #include "chrome/common/chrome_version_info.h" #include "content/public/browser/browser_child_process_observer.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_data.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/worker_service.h" #include "content/public/browser/worker_service_observer.h" #include "content/public/common/process_type.h" #include "net/base/escape.h" using content::BrowserThread; namespace { const char kTargetSourceField[] = "source"; const char kTargetSourceRenderer[] = "renderers"; const char kTargetSourceWorker[] = "workers"; const char kTargetSourceAdb[] = "adb"; const char kTargetIdField[] = "id"; const char kTargetTypeField[] = "type"; const char kAttachedField[] = "attached"; const char kUrlField[] = "url"; const char kNameField[] = "name"; const char kFaviconUrlField[] = "faviconUrl"; const char kDescriptionField[] = "description"; const char kGuestList[] = "guests"; const char kAdbModelField[] = "adbModel"; const char kAdbConnectedField[] = "adbConnected"; const char kAdbSerialField[] = "adbSerial"; const char kAdbBrowsersList[] = "browsers"; const char kAdbDeviceIdFormat[] = "device:%s"; const char kAdbBrowserNameField[] = "adbBrowserName"; const char kAdbBrowserVersionField[] = "adbBrowserVersion"; const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion"; const char kCompatibleVersion[] = "compatibleVersion"; const char kAdbPagesList[] = "pages"; const char kAdbScreenWidthField[] = "adbScreenWidth"; const char kAdbScreenHeightField[] = "adbScreenHeight"; const char kAdbAttachedForeignField[] = "adbAttachedForeign"; // CancelableTimer ------------------------------------------------------------ class CancelableTimer { public: CancelableTimer(base::Closure callback, base::TimeDelta delay) : callback_(callback), weak_factory_(this) { base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&CancelableTimer::Fire, weak_factory_.GetWeakPtr()), delay); } private: void Fire() { callback_.Run(); } base::Closure callback_; base::WeakPtrFactory weak_factory_; }; // RenderViewHostTargetsUIHandler --------------------------------------------- class RenderViewHostTargetsUIHandler : public DevToolsTargetsUIHandler, public content::NotificationObserver { public: explicit RenderViewHostTargetsUIHandler(Callback callback); virtual ~RenderViewHostTargetsUIHandler(); private: // content::NotificationObserver overrides. virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE; void UpdateTargets(); content::NotificationRegistrar notification_registrar_; scoped_ptr timer_; }; RenderViewHostTargetsUIHandler::RenderViewHostTargetsUIHandler( Callback callback) : DevToolsTargetsUIHandler(kTargetSourceRenderer, callback) { notification_registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_CONNECTED, content::NotificationService::AllSources()); notification_registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, content::NotificationService::AllSources()); notification_registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::NotificationService::AllSources()); UpdateTargets(); } RenderViewHostTargetsUIHandler::~RenderViewHostTargetsUIHandler() { notification_registrar_.RemoveAll(); } void RenderViewHostTargetsUIHandler::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { const int kUpdateDelay = 100; timer_.reset( new CancelableTimer( base::Bind(&RenderViewHostTargetsUIHandler::UpdateTargets, base::Unretained(this)), base::TimeDelta::FromMilliseconds(kUpdateDelay))); } void RenderViewHostTargetsUIHandler::UpdateTargets() { scoped_ptr list_value(new base::ListValue()); std::map id_to_descriptor; DevToolsTargetImpl::List targets = DevToolsTargetImpl::EnumerateRenderViewHostTargets(); STLDeleteValues(&targets_); for (DevToolsTargetImpl::List::iterator it = targets.begin(); it != targets.end(); ++it) { DevToolsTargetImpl* target = *it; targets_[target->GetId()] = target; id_to_descriptor[target->GetId()] = Serialize(*target); } for (TargetMap::iterator it(targets_.begin()); it != targets_.end(); ++it) { DevToolsTargetImpl* target = it->second; base::DictionaryValue* descriptor = id_to_descriptor[target->GetId()]; std::string parent_id = target->GetParentId(); if (parent_id.empty() || id_to_descriptor.count(parent_id) == 0) { list_value->Append(descriptor); } else { base::DictionaryValue* parent = id_to_descriptor[parent_id]; base::ListValue* guests = NULL; if (!parent->GetList(kGuestList, &guests)) { guests = new base::ListValue(); parent->Set(kGuestList, guests); } guests->Append(descriptor); } } SendSerializedTargets(list_value.Pass()); } // WorkerObserver ------------------------------------------------------------- class WorkerObserver : public content::WorkerServiceObserver, public base::RefCountedThreadSafe { public: WorkerObserver() {} void Start(DevToolsTargetImpl::Callback callback) { DCHECK(callback_.is_null()); DCHECK(!callback.is_null()); callback_ = callback; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&WorkerObserver::StartOnIOThread, this)); } void Stop() { DCHECK(!callback_.is_null()); callback_ = DevToolsTargetImpl::Callback(); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&WorkerObserver::StopOnIOThread, this)); } void Enumerate() { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&WorkerObserver::EnumerateOnIOThread, this)); } private: friend class base::RefCountedThreadSafe; virtual ~WorkerObserver() {} // content::WorkerServiceObserver overrides: virtual void WorkerCreated( const GURL& url, const base::string16& name, int process_id, int route_id) OVERRIDE { EnumerateOnIOThread(); } virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { EnumerateOnIOThread(); } void StartOnIOThread() { content::WorkerService::GetInstance()->AddObserver(this); EnumerateOnIOThread(); } void StopOnIOThread() { content::WorkerService::GetInstance()->RemoveObserver(this); } void EnumerateOnIOThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DevToolsTargetImpl::EnumerateWorkerTargets( base::Bind(&WorkerObserver::RespondOnUIThread, this)); } void RespondOnUIThread(const DevToolsTargetImpl::List& targets) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (callback_.is_null()) return; callback_.Run(targets); } DevToolsTargetImpl::Callback callback_; }; // WorkerTargetsUIHandler ----------------------------------------------------- class WorkerTargetsUIHandler : public DevToolsTargetsUIHandler, public content::BrowserChildProcessObserver { public: explicit WorkerTargetsUIHandler(Callback callback); virtual ~WorkerTargetsUIHandler(); private: // content::BrowserChildProcessObserver overrides. virtual void BrowserChildProcessHostConnected( const content::ChildProcessData& data) OVERRIDE; virtual void BrowserChildProcessHostDisconnected( const content::ChildProcessData& data) OVERRIDE; void UpdateTargets(const DevToolsTargetImpl::List& targets); scoped_refptr observer_; }; WorkerTargetsUIHandler::WorkerTargetsUIHandler(Callback callback) : DevToolsTargetsUIHandler(kTargetSourceWorker, callback), observer_(new WorkerObserver()) { observer_->Start(base::Bind(&WorkerTargetsUIHandler::UpdateTargets, base::Unretained(this))); BrowserChildProcessObserver::Add(this); } WorkerTargetsUIHandler::~WorkerTargetsUIHandler() { BrowserChildProcessObserver::Remove(this); observer_->Stop(); } void WorkerTargetsUIHandler::BrowserChildProcessHostConnected( const content::ChildProcessData& data) { if (data.process_type == content::PROCESS_TYPE_WORKER) observer_->Enumerate(); } void WorkerTargetsUIHandler::BrowserChildProcessHostDisconnected( const content::ChildProcessData& data) { if (data.process_type == content::PROCESS_TYPE_WORKER) observer_->Enumerate(); } void WorkerTargetsUIHandler::UpdateTargets( const DevToolsTargetImpl::List& targets) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); scoped_ptr list_value(new base::ListValue()); STLDeleteValues(&targets_); for (DevToolsTargetImpl::List::const_iterator it = targets.begin(); it != targets.end(); ++it) { DevToolsTargetImpl* target = *it; list_value->Append(Serialize(*target)); targets_[target->GetId()] = target; } SendSerializedTargets(list_value.Pass()); } // AdbTargetsUIHandler -------------------------------------------------------- class AdbTargetsUIHandler : public DevToolsTargetsUIHandler, public DevToolsAndroidBridge::DeviceListListener { public: AdbTargetsUIHandler(Callback callback, Profile* profile); virtual ~AdbTargetsUIHandler(); virtual void Open(const std::string& browser_id, const std::string& url, const DevToolsTargetsUIHandler::TargetCallback&) OVERRIDE; virtual scoped_refptr GetBrowserAgentHost( const std::string& browser_id) OVERRIDE; private: // DevToolsAndroidBridge::Listener overrides. virtual void DeviceListChanged( const DevToolsAndroidBridge::RemoteDevices& devices) OVERRIDE; Profile* profile_; typedef std::map > RemoteBrowsers; RemoteBrowsers remote_browsers_; }; AdbTargetsUIHandler::AdbTargetsUIHandler(Callback callback, Profile* profile) : DevToolsTargetsUIHandler(kTargetSourceAdb, callback), profile_(profile) { DevToolsAndroidBridge* android_bridge = DevToolsAndroidBridge::Factory::GetForProfile(profile_); if (android_bridge) android_bridge->AddDeviceListListener(this); } AdbTargetsUIHandler::~AdbTargetsUIHandler() { DevToolsAndroidBridge* android_bridge = DevToolsAndroidBridge::Factory::GetForProfile(profile_); if (android_bridge) android_bridge->RemoveDeviceListListener(this); } static void CallOnTarget( const DevToolsTargetsUIHandler::TargetCallback& callback, DevToolsAndroidBridge::RemotePage* page) { scoped_ptr my_page(page); callback.Run(my_page ? my_page->GetTarget() : NULL); } void AdbTargetsUIHandler::Open( const std::string& browser_id, const std::string& url, const DevToolsTargetsUIHandler::TargetCallback& callback) { RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); if (it != remote_browsers_.end()) it->second->Open(url, base::Bind(&CallOnTarget, callback)); } scoped_refptr AdbTargetsUIHandler::GetBrowserAgentHost( const std::string& browser_id) { RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); return it != remote_browsers_.end() ? it->second->GetAgentHost() : NULL; } void AdbTargetsUIHandler::DeviceListChanged( const DevToolsAndroidBridge::RemoteDevices& devices) { remote_browsers_.clear(); STLDeleteValues(&targets_); scoped_ptr device_list(new base::ListValue()); for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit = devices.begin(); dit != devices.end(); ++dit) { DevToolsAndroidBridge::RemoteDevice* device = dit->get(); base::DictionaryValue* device_data = new base::DictionaryValue(); device_data->SetString(kAdbModelField, device->model()); device_data->SetString(kAdbSerialField, device->serial()); device_data->SetBoolean(kAdbConnectedField, device->is_connected()); std::string device_id = base::StringPrintf( kAdbDeviceIdFormat, device->serial().c_str()); device_data->SetString(kTargetIdField, device_id); base::ListValue* browser_list = new base::ListValue(); device_data->Set(kAdbBrowsersList, browser_list); DevToolsAndroidBridge::RemoteBrowsers& browsers = device->browsers(); for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit = browsers.begin(); bit != browsers.end(); ++bit) { DevToolsAndroidBridge::RemoteBrowser* browser = bit->get(); base::DictionaryValue* browser_data = new base::DictionaryValue(); browser_data->SetString(kAdbBrowserNameField, browser->display_name()); browser_data->SetString(kAdbBrowserVersionField, browser->version()); DevToolsAndroidBridge::RemoteBrowser::ParsedVersion parsed = browser->GetParsedVersion(); browser_data->SetInteger( kAdbBrowserChromeVersionField, browser->IsChrome() && !parsed.empty() ? parsed[0] : 0); std::string browser_id = base::StringPrintf( "browser:%s:%s:%s:%s", device->serial().c_str(), // Ensure uniqueness across devices. browser->display_name().c_str(), // Sort by display name. browser->version().c_str(), // Then by version. browser->socket().c_str()); // Ensure uniqueness on the device. browser_data->SetString(kTargetIdField, browser_id); browser_data->SetString(kTargetSourceField, source_id()); base::Version remote_version; if (browser->IsChrome()) { remote_version = base::Version(browser->version()); } else { // Try parse WebView version. std::string version = browser->version(); size_t pos = version.find("Chrome/"); if (pos != std::string::npos) { remote_version = base::Version(browser->version().substr(pos + 7)); } } chrome::VersionInfo version_info; base::Version local_version(version_info.Version()); browser_data->SetBoolean(kCompatibleVersion, (!remote_version.IsValid()) || (!local_version.IsValid()) || remote_version.components()[0] <= local_version.components()[0]); base::ListValue* page_list = new base::ListValue(); remote_browsers_[browser_id] = browser; browser_data->Set(kAdbPagesList, page_list); std::vector pages = browser->CreatePages(); for (std::vector::iterator it = pages.begin(); it != pages.end(); ++it) { DevToolsAndroidBridge::RemotePage* page = *it; DevToolsTargetImpl* target = page->GetTarget(); base::DictionaryValue* target_data = Serialize(*target); target_data->SetBoolean( kAdbAttachedForeignField, target->IsAttached() && !DevToolsAndroidBridge::HasDevToolsWindow(target->GetId())); // Pass the screen size in the target object to make sure that // the caching logic does not prevent the target item from updating // when the screen size changes. gfx::Size screen_size = device->screen_size(); target_data->SetInteger(kAdbScreenWidthField, screen_size.width()); target_data->SetInteger(kAdbScreenHeightField, screen_size.height()); targets_[target->GetId()] = target; page_list->Append(target_data); } browser_list->Append(browser_data); } device_list->Append(device_data); } SendSerializedTargets(device_list.Pass()); } } // namespace // DevToolsTargetsUIHandler --------------------------------------------------- DevToolsTargetsUIHandler::DevToolsTargetsUIHandler( const std::string& source_id, Callback callback) : source_id_(source_id), callback_(callback) { } DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() { STLDeleteValues(&targets_); } // static scoped_ptr DevToolsTargetsUIHandler::CreateForRenderers( DevToolsTargetsUIHandler::Callback callback) { return scoped_ptr( new RenderViewHostTargetsUIHandler(callback)); } // static scoped_ptr DevToolsTargetsUIHandler::CreateForWorkers( DevToolsTargetsUIHandler::Callback callback) { return scoped_ptr( new WorkerTargetsUIHandler(callback)); } // static scoped_ptr DevToolsTargetsUIHandler::CreateForAdb( DevToolsTargetsUIHandler::Callback callback, Profile* profile) { return scoped_ptr( new AdbTargetsUIHandler(callback, profile)); } DevToolsTargetImpl* DevToolsTargetsUIHandler::GetTarget( const std::string& target_id) { TargetMap::iterator it = targets_.find(target_id); if (it != targets_.end()) return it->second; return NULL; } void DevToolsTargetsUIHandler::Open(const std::string& browser_id, const std::string& url, const TargetCallback& callback) { callback.Run(NULL); } scoped_refptr DevToolsTargetsUIHandler::GetBrowserAgentHost(const std::string& browser_id) { return NULL; } base::DictionaryValue* DevToolsTargetsUIHandler::Serialize( const DevToolsTargetImpl& target) { base::DictionaryValue* target_data = new base::DictionaryValue(); target_data->SetString(kTargetSourceField, source_id_); target_data->SetString(kTargetIdField, target.GetId()); target_data->SetString(kTargetTypeField, target.GetType()); target_data->SetBoolean(kAttachedField, target.IsAttached()); target_data->SetString(kUrlField, target.GetURL().spec()); target_data->SetString(kNameField, net::EscapeForHTML(target.GetTitle())); target_data->SetString(kFaviconUrlField, target.GetFaviconURL().spec()); target_data->SetString(kDescriptionField, target.GetDescription()); return target_data; } void DevToolsTargetsUIHandler::SendSerializedTargets( scoped_ptr list) { callback_.Run(source_id_, list.Pass()); } // PortForwardingStatusSerializer --------------------------------------------- PortForwardingStatusSerializer::PortForwardingStatusSerializer( const Callback& callback, Profile* profile) : callback_(callback), profile_(profile) { PortForwardingController* port_forwarding_controller = PortForwardingController::Factory::GetForProfile(profile_); if (port_forwarding_controller) port_forwarding_controller->AddListener(this); } PortForwardingStatusSerializer::~PortForwardingStatusSerializer() { PortForwardingController* port_forwarding_controller = PortForwardingController::Factory::GetForProfile(profile_); if (port_forwarding_controller) port_forwarding_controller->RemoveListener(this); } void PortForwardingStatusSerializer::PortStatusChanged( const DevicesStatus& status) { base::DictionaryValue result; for (DevicesStatus::const_iterator sit = status.begin(); sit != status.end(); ++sit) { base::DictionaryValue* device_status_dict = new base::DictionaryValue(); const PortStatusMap& device_status_map = sit->second; for (PortStatusMap::const_iterator it = device_status_map.begin(); it != device_status_map.end(); ++it) { device_status_dict->SetInteger( base::StringPrintf("%d", it->first), it->second); } std::string device_id = base::StringPrintf( kAdbDeviceIdFormat, sit->first.c_str()); result.Set(device_id, device_status_dict); } callback_.Run(result); }