| // 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 kTargetSourceLocal[] = "local"; |
| const char kTargetSourceRemote[] = "remote"; |
| |
| 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"; |
| |
| const char kPortForwardingPorts[] = "ports"; |
| const char kPortForwardingBrowserId[] = "browserId"; |
| |
| std::string SerializeBrowserId( |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser) { |
| return base::StringPrintf( |
| "browser:%s:%s:%s:%s", |
| browser->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. |
| } |
| |
| // 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<CancelableTimer> weak_factory_; |
| }; |
| |
| // WorkerObserver ------------------------------------------------------------- |
| |
| class WorkerObserver |
| : public content::WorkerServiceObserver, |
| public base::RefCountedThreadSafe<WorkerObserver> { |
| public: |
| WorkerObserver() {} |
| |
| void Start(base::Closure 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_ = base::Closure(); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&WorkerObserver::StopOnIOThread, this)); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<WorkerObserver>; |
| ~WorkerObserver() override {} |
| |
| // content::WorkerServiceObserver overrides: |
| void WorkerCreated(const GURL& url, |
| const base::string16& name, |
| int process_id, |
| int route_id) override { |
| NotifyOnIOThread(); |
| } |
| |
| void WorkerDestroyed(int process_id, int route_id) override { |
| NotifyOnIOThread(); |
| } |
| |
| void StartOnIOThread() { |
| content::WorkerService::GetInstance()->AddObserver(this); |
| } |
| |
| void StopOnIOThread() { |
| content::WorkerService::GetInstance()->RemoveObserver(this); |
| } |
| |
| void NotifyOnIOThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&WorkerObserver::NotifyOnUIThread, this)); |
| } |
| |
| void NotifyOnUIThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (callback_.is_null()) |
| return; |
| callback_.Run(); |
| } |
| |
| // Accessed on UI thread. |
| base::Closure callback_; |
| }; |
| |
| // LocalTargetsUIHandler --------------------------------------------- |
| |
| class LocalTargetsUIHandler |
| : public DevToolsTargetsUIHandler, |
| public content::NotificationObserver { |
| public: |
| explicit LocalTargetsUIHandler(const Callback& callback); |
| ~LocalTargetsUIHandler() override; |
| |
| // DevToolsTargetsUIHandler overrides. |
| void ForceUpdate() override; |
| |
| private: |
| // content::NotificationObserver overrides. |
| void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) override; |
| |
| void ScheduleUpdate(); |
| void UpdateTargets(); |
| void SendTargets(const DevToolsTargetImpl::List& targets); |
| |
| content::NotificationRegistrar notification_registrar_; |
| scoped_ptr<CancelableTimer> timer_; |
| scoped_refptr<WorkerObserver> observer_; |
| base::WeakPtrFactory<LocalTargetsUIHandler> weak_factory_; |
| }; |
| |
| LocalTargetsUIHandler::LocalTargetsUIHandler( |
| const Callback& callback) |
| : DevToolsTargetsUIHandler(kTargetSourceLocal, callback), |
| observer_(new WorkerObserver()), |
| weak_factory_(this) { |
| 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()); |
| observer_->Start(base::Bind(&LocalTargetsUIHandler::ScheduleUpdate, |
| base::Unretained(this))); |
| UpdateTargets(); |
| } |
| |
| LocalTargetsUIHandler::~LocalTargetsUIHandler() { |
| notification_registrar_.RemoveAll(); |
| observer_->Stop(); |
| } |
| |
| void LocalTargetsUIHandler::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| ScheduleUpdate(); |
| } |
| |
| void LocalTargetsUIHandler::ForceUpdate() { |
| ScheduleUpdate(); |
| } |
| |
| void LocalTargetsUIHandler::ScheduleUpdate() { |
| const int kUpdateDelay = 100; |
| timer_.reset( |
| new CancelableTimer( |
| base::Bind(&LocalTargetsUIHandler::UpdateTargets, |
| base::Unretained(this)), |
| base::TimeDelta::FromMilliseconds(kUpdateDelay))); |
| } |
| |
| void LocalTargetsUIHandler::UpdateTargets() { |
| DevToolsTargetImpl::EnumerateAllTargets(base::Bind( |
| &LocalTargetsUIHandler::SendTargets, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void LocalTargetsUIHandler::SendTargets( |
| const DevToolsTargetImpl::List& targets) { |
| base::ListValue list_value; |
| std::map<std::string, base::DictionaryValue*> id_to_descriptor; |
| |
| STLDeleteValues(&targets_); |
| for (DevToolsTargetImpl::List::const_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); |
| } |
| |
| // AdbTargetsUIHandler -------------------------------------------------------- |
| |
| class AdbTargetsUIHandler |
| : public DevToolsTargetsUIHandler, |
| public DevToolsAndroidBridge::DeviceListListener { |
| public: |
| AdbTargetsUIHandler(const Callback& callback, Profile* profile); |
| ~AdbTargetsUIHandler() override; |
| |
| void Open(const std::string& browser_id, |
| const std::string& url, |
| const DevToolsTargetsUIHandler::TargetCallback&) override; |
| |
| scoped_refptr<content::DevToolsAgentHost> GetBrowserAgentHost( |
| const std::string& browser_id) override; |
| |
| private: |
| // DevToolsAndroidBridge::Listener overrides. |
| void DeviceListChanged( |
| const DevToolsAndroidBridge::RemoteDevices& devices) override; |
| |
| DevToolsAndroidBridge* GetAndroidBridge(); |
| |
| Profile* profile_; |
| scoped_refptr<DevToolsAndroidBridge> android_bridge_; |
| |
| typedef std::map<std::string, |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> > RemoteBrowsers; |
| RemoteBrowsers remote_browsers_; |
| }; |
| |
| AdbTargetsUIHandler::AdbTargetsUIHandler(const Callback& callback, |
| Profile* profile) |
| : DevToolsTargetsUIHandler(kTargetSourceRemote, callback), |
| profile_(profile), |
| android_bridge_( |
| DevToolsAndroidBridge::Factory::GetForProfile(profile_)) { |
| DCHECK(android_bridge_.get()); |
| android_bridge_->AddDeviceListListener(this); |
| } |
| |
| AdbTargetsUIHandler::~AdbTargetsUIHandler() { |
| android_bridge_->RemoveDeviceListListener(this); |
| } |
| |
| static void CallOnTarget( |
| const DevToolsTargetsUIHandler::TargetCallback& callback, |
| scoped_refptr<DevToolsAndroidBridge> bridge, |
| scoped_refptr<DevToolsAndroidBridge::RemotePage> page) { |
| callback.Run(page.get() ? bridge->CreatePageTarget(page) : nullptr); |
| } |
| |
| 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()) |
| return; |
| |
| android_bridge_->OpenRemotePage( |
| it->second, |
| url, |
| base::Bind(&CallOnTarget, callback, android_bridge_)); |
| } |
| |
| scoped_refptr<content::DevToolsAgentHost> |
| AdbTargetsUIHandler::GetBrowserAgentHost( |
| const std::string& browser_id) { |
| RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); |
| if (it == remote_browsers_.end()) |
| return NULL; |
| |
| return android_bridge_->GetBrowserAgentHost(it->second); |
| } |
| |
| void AdbTargetsUIHandler::DeviceListChanged( |
| const DevToolsAndroidBridge::RemoteDevices& devices) { |
| remote_browsers_.clear(); |
| STLDeleteValues(&targets_); |
| |
| base::ListValue device_list; |
| 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 = SerializeBrowserId(browser); |
| browser_data->SetString(kTargetIdField, browser_id); |
| browser_data->SetString(kTargetSourceField, source_id()); |
| |
| base::Version remote_version; |
| remote_version = base::Version(browser->version()); |
| |
| 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); |
| for (const auto& page : browser->pages()) { |
| DevToolsTargetImpl* target = android_bridge_->CreatePageTarget(page); |
| base::DictionaryValue* target_data = Serialize(*target); |
| target_data->SetBoolean( |
| kAdbAttachedForeignField, |
| target->IsAttached() && |
| !android_bridge_->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); |
| } |
| |
| } // namespace |
| |
| // DevToolsTargetsUIHandler --------------------------------------------------- |
| |
| DevToolsTargetsUIHandler::DevToolsTargetsUIHandler( |
| const std::string& source_id, |
| const Callback& callback) |
| : source_id_(source_id), |
| callback_(callback) { |
| } |
| |
| DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() { |
| STLDeleteValues(&targets_); |
| } |
| |
| // static |
| scoped_ptr<DevToolsTargetsUIHandler> |
| DevToolsTargetsUIHandler::CreateForLocal( |
| const DevToolsTargetsUIHandler::Callback& callback) { |
| return scoped_ptr<DevToolsTargetsUIHandler>( |
| new LocalTargetsUIHandler(callback)); |
| } |
| |
| // static |
| scoped_ptr<DevToolsTargetsUIHandler> |
| DevToolsTargetsUIHandler::CreateForAdb( |
| const DevToolsTargetsUIHandler::Callback& callback, Profile* profile) { |
| return scoped_ptr<DevToolsTargetsUIHandler>( |
| 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<content::DevToolsAgentHost> |
| 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( |
| const base::ListValue& list) { |
| callback_.Run(source_id_, list); |
| } |
| |
| void DevToolsTargetsUIHandler::ForceUpdate() { |
| } |
| |
| // PortForwardingStatusSerializer --------------------------------------------- |
| |
| PortForwardingStatusSerializer::PortForwardingStatusSerializer( |
| const Callback& callback, Profile* profile) |
| : callback_(callback), |
| profile_(profile) { |
| DevToolsAndroidBridge* android_bridge = |
| DevToolsAndroidBridge::Factory::GetForProfile(profile_); |
| if (android_bridge) |
| android_bridge->AddPortForwardingListener(this); |
| } |
| |
| PortForwardingStatusSerializer::~PortForwardingStatusSerializer() { |
| DevToolsAndroidBridge* android_bridge = |
| DevToolsAndroidBridge::Factory::GetForProfile(profile_); |
| if (android_bridge) |
| android_bridge->RemovePortForwardingListener(this); |
| } |
| |
| void PortForwardingStatusSerializer::PortStatusChanged( |
| const ForwardingStatus& status) { |
| base::DictionaryValue result; |
| for (ForwardingStatus::const_iterator sit = status.begin(); |
| sit != status.end(); ++sit) { |
| base::DictionaryValue* port_status_dict = new base::DictionaryValue(); |
| const PortStatusMap& port_status_map = sit->second; |
| for (PortStatusMap::const_iterator it = port_status_map.begin(); |
| it != port_status_map.end(); ++it) { |
| port_status_dict->SetInteger( |
| base::StringPrintf("%d", it->first), it->second); |
| } |
| |
| base::DictionaryValue* device_status_dict = new base::DictionaryValue(); |
| device_status_dict->Set(kPortForwardingPorts, port_status_dict); |
| device_status_dict->SetString(kPortForwardingBrowserId, |
| SerializeBrowserId(sit->first)); |
| |
| std::string device_id = base::StringPrintf( |
| kAdbDeviceIdFormat, |
| sit->first->serial().c_str()); |
| result.Set(device_id, device_status_dict); |
| } |
| callback_.Run(result); |
| } |