| // 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 <utility> |
| |
| #include "base/location.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_task_runner_handle.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 "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 kAdbBrowserUserField[] = "adbBrowserUser"; |
| const char kAdbBrowserVersionField[] = "adbBrowserVersion"; |
| const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion"; |
| const char kAdbPagesList[] = "pages"; |
| |
| const char kAdbScreenWidthField[] = "adbScreenWidth"; |
| const char kAdbScreenHeightField[] = "adbScreenHeight"; |
| const char kAdbAttachedForeignField[] = "adbAttachedForeign"; |
| |
| const char kPortForwardingPorts[] = "ports"; |
| const char kPortForwardingBrowserId[] = "browserId"; |
| |
| // CancelableTimer ------------------------------------------------------------ |
| |
| class CancelableTimer { |
| public: |
| CancelableTimer(base::Closure callback, base::TimeDelta delay) |
| : callback_(callback), |
| weak_factory_(this) { |
| base::ThreadTaskRunnerHandle::Get()->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_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&WorkerObserver::NotifyOnUIThread, this)); |
| } |
| |
| void NotifyOnUIThread() { |
| DCHECK_CURRENTLY_ON(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 std::vector<DevToolsTargetImpl*>& targets); |
| |
| content::NotificationRegistrar notification_registrar_; |
| std::unique_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() { |
| SendTargets(DevToolsTargetImpl::EnumerateAll()); |
| } |
| |
| void LocalTargetsUIHandler::SendTargets( |
| const std::vector<DevToolsTargetImpl*>& targets) { |
| base::ListValue list_value; |
| std::map<std::string, base::DictionaryValue*> id_to_descriptor; |
| |
| STLDeleteValues(&targets_); |
| for (DevToolsTargetImpl* target : targets) { |
| 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) 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* const profile_; |
| DevToolsAndroidBridge* const 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_)) { |
| if (android_bridge_) |
| android_bridge_->AddDeviceListListener(this); |
| } |
| |
| AdbTargetsUIHandler::~AdbTargetsUIHandler() { |
| if (android_bridge_) |
| android_bridge_->RemoveDeviceListListener(this); |
| } |
| |
| void AdbTargetsUIHandler::Open(const std::string& browser_id, |
| const std::string& url) { |
| RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); |
| if (it != remote_browsers_.end() && android_bridge_) |
| android_bridge_->OpenRemotePage(it->second, url); |
| } |
| |
| scoped_refptr<content::DevToolsAgentHost> |
| AdbTargetsUIHandler::GetBrowserAgentHost( |
| const std::string& browser_id) { |
| RemoteBrowsers::iterator it = remote_browsers_.find(browser_id); |
| if (it == remote_browsers_.end() || !android_bridge_) |
| return nullptr; |
| |
| return android_bridge_->GetBrowserAgentHost(it->second); |
| } |
| |
| void AdbTargetsUIHandler::DeviceListChanged( |
| const DevToolsAndroidBridge::RemoteDevices& devices) { |
| remote_browsers_.clear(); |
| STLDeleteValues(&targets_); |
| if (!android_bridge_) |
| return; |
| |
| base::ListValue device_list; |
| for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit = |
| devices.begin(); dit != devices.end(); ++dit) { |
| DevToolsAndroidBridge::RemoteDevice* device = dit->get(); |
| std::unique_ptr<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(); |
| std::unique_ptr<base::DictionaryValue> browser_data( |
| new base::DictionaryValue()); |
| browser_data->SetString(kAdbBrowserNameField, browser->display_name()); |
| browser_data->SetString(kAdbBrowserUserField, browser->user()); |
| 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 = browser->GetId(); |
| browser_data->SetString(kTargetIdField, browser_id); |
| browser_data->SetString(kTargetSourceField, source_id()); |
| |
| 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(std::move(browser_data)); |
| } |
| |
| device_list.Append(std::move(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 |
| std::unique_ptr<DevToolsTargetsUIHandler> |
| DevToolsTargetsUIHandler::CreateForLocal( |
| const DevToolsTargetsUIHandler::Callback& callback) { |
| return std::unique_ptr<DevToolsTargetsUIHandler>( |
| new LocalTargetsUIHandler(callback)); |
| } |
| |
| // static |
| std::unique_ptr<DevToolsTargetsUIHandler> |
| DevToolsTargetsUIHandler::CreateForAdb( |
| const DevToolsTargetsUIHandler::Callback& callback, |
| Profile* profile) { |
| return std::unique_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) { |
| } |
| |
| 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::IntToString(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, |
| sit->first->GetId()); |
| |
| std::string device_id = base::StringPrintf( |
| kAdbDeviceIdFormat, |
| sit->first->serial().c_str()); |
| result.Set(device_id, device_status_dict); |
| } |
| callback_.Run(result); |
| } |