| // Copyright 2014 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/device/devtools_android_bridge.h" |
| |
| #include <map> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/json/json_reader.h" |
| #include "base/lazy_instance.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread.h" |
| #include "base/values.h" |
| #include "chrome/browser/devtools/browser_list_tabcontents_provider.h" |
| #include "chrome/browser/devtools/device/adb/adb_device_info_query.h" |
| #include "chrome/browser/devtools/device/adb/adb_device_provider.h" |
| #include "chrome/browser/devtools/device/self_device_provider.h" |
| #include "chrome/browser/devtools/device/usb/usb_device_provider.h" |
| #include "chrome/browser/devtools/devtools_protocol.h" |
| #include "chrome/browser/devtools/devtools_target_impl.h" |
| #include "chrome/browser/devtools/devtools_window.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/keyed_service/content/browser_context_dependency_manager.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/devtools_external_agent_proxy.h" |
| #include "content/public/browser/devtools_external_agent_proxy_delegate.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "net/base/escape.h" |
| |
| using content::BrowserThread; |
| |
| namespace { |
| |
| const char kPageListRequest[] = "/json"; |
| const char kVersionRequest[] = "/json/version"; |
| const char kClosePageRequest[] = "/json/close/%s"; |
| const char kNewPageRequest[] = "/json/new"; |
| const char kNewPageRequestWithURL[] = "/json/new?%s"; |
| const char kActivatePageRequest[] = "/json/activate/%s"; |
| const char kBrowserTargetSocket[] = "/devtools/browser"; |
| const int kAdbPollingIntervalMs = 1000; |
| |
| const char kUrlParam[] = "url"; |
| const char kPageReloadCommand[] = "Page.reload"; |
| const char kPageNavigateCommand[] = "Page.navigate"; |
| |
| const int kMinVersionNewWithURL = 32; |
| const int kNewPageNavigateDelayMs = 500; |
| |
| // DiscoveryRequest ----------------------------------------------------- |
| |
| class DiscoveryRequest : public base::RefCountedThreadSafe< |
| DiscoveryRequest, |
| BrowserThread::DeleteOnUIThread> { |
| public: |
| typedef AndroidDeviceManager::Device Device; |
| typedef AndroidDeviceManager::Devices Devices; |
| typedef AndroidDeviceManager::DeviceInfo DeviceInfo; |
| typedef DevToolsAndroidBridge::RemoteDevice RemoteDevice; |
| typedef DevToolsAndroidBridge::RemoteDevices RemoteDevices; |
| typedef DevToolsAndroidBridge::RemoteBrowser RemoteBrowser; |
| typedef DevToolsAndroidBridge::RemoteBrowsers RemoteBrowsers; |
| typedef base::Callback<void(const RemoteDevices&)> DiscoveryCallback; |
| |
| DiscoveryRequest(AndroidDeviceManager* device_manager, |
| const DiscoveryCallback& callback); |
| private: |
| friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; |
| friend class base::DeleteHelper<DiscoveryRequest>; |
| virtual ~DiscoveryRequest(); |
| |
| void ReceivedDevices(const Devices& devices); |
| void ReceivedDeviceInfo(scoped_refptr<Device> device, |
| const DeviceInfo& device_info); |
| void ReceivedVersion(scoped_refptr<RemoteBrowser>, |
| int result, |
| const std::string& response); |
| void ReceivedPages(scoped_refptr<RemoteBrowser>, |
| int result, |
| const std::string& response); |
| |
| DiscoveryCallback callback_; |
| RemoteDevices remote_devices_; |
| }; |
| |
| DiscoveryRequest::DiscoveryRequest( |
| AndroidDeviceManager* device_manager, |
| const DiscoveryCallback& callback) |
| : callback_(callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| device_manager->QueryDevices( |
| base::Bind(&DiscoveryRequest::ReceivedDevices, this)); |
| } |
| |
| DiscoveryRequest::~DiscoveryRequest() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| callback_.Run(remote_devices_); |
| } |
| |
| void DiscoveryRequest::ReceivedDevices(const Devices& devices) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| for (Devices::const_iterator it = devices.begin(); |
| it != devices.end(); ++it) { |
| (*it)->QueryDeviceInfo( |
| base::Bind(&DiscoveryRequest::ReceivedDeviceInfo, this, *it)); |
| } |
| } |
| |
| void DiscoveryRequest::ReceivedDeviceInfo(scoped_refptr<Device> device, |
| const DeviceInfo& device_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| scoped_refptr<RemoteDevice> remote_device = |
| new RemoteDevice(device, device_info); |
| remote_devices_.push_back(remote_device); |
| for (RemoteBrowsers::iterator it = remote_device->browsers().begin(); |
| it != remote_device->browsers().end(); ++it) { |
| (*it)->SendJsonRequest( |
| kVersionRequest, |
| base::Bind(&DiscoveryRequest::ReceivedVersion, this, *it)); |
| (*it)->SendJsonRequest( |
| kPageListRequest, |
| base::Bind(&DiscoveryRequest::ReceivedPages, this, *it)); |
| } |
| } |
| |
| void DiscoveryRequest::ReceivedVersion(scoped_refptr<RemoteBrowser> browser, |
| int result, |
| const std::string& response) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (result < 0) |
| return; |
| // Parse version, append to package name if available, |
| scoped_ptr<base::Value> value(base::JSONReader::Read(response)); |
| base::DictionaryValue* dict; |
| if (value && value->GetAsDictionary(&dict)) { |
| std::string browser_name; |
| if (dict->GetString("Browser", &browser_name)) { |
| std::vector<std::string> parts; |
| Tokenize(browser_name, "/", &parts); |
| if (parts.size() == 2) |
| browser->set_version(parts[1]); |
| else |
| browser->set_version(browser_name); |
| } |
| std::string package; |
| if (dict->GetString("Android-Package", &package)) { |
| browser->set_display_name( |
| AdbDeviceInfoQuery::GetDisplayName(browser->socket(), package)); |
| } |
| } |
| } |
| |
| void DiscoveryRequest::ReceivedPages(scoped_refptr<RemoteBrowser> browser, |
| int result, |
| const std::string& response) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (result < 0) |
| return; |
| scoped_ptr<base::Value> value(base::JSONReader::Read(response)); |
| base::ListValue* list_value; |
| if (value && value->GetAsList(&list_value)) |
| browser->SetPageDescriptors(*list_value); |
| } |
| |
| // ProtocolCommand ------------------------------------------------------------ |
| |
| class ProtocolCommand |
| : public DevToolsAndroidBridge::AndroidWebSocket::Delegate { |
| public: |
| ProtocolCommand( |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const std::string& debug_url, |
| const std::string& command, |
| const base::Closure callback); |
| |
| private: |
| virtual void OnSocketOpened() OVERRIDE; |
| virtual void OnFrameRead(const std::string& message) OVERRIDE; |
| virtual void OnSocketClosed() OVERRIDE; |
| virtual ~ProtocolCommand(); |
| |
| const std::string command_; |
| const base::Closure callback_; |
| scoped_ptr<DevToolsAndroidBridge::AndroidWebSocket> web_socket_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ProtocolCommand); |
| }; |
| |
| ProtocolCommand::ProtocolCommand( |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const std::string& debug_url, |
| const std::string& command, |
| const base::Closure callback) |
| : command_(command), |
| callback_(callback), |
| web_socket_(browser->CreateWebSocket(debug_url, this)) { |
| } |
| |
| void ProtocolCommand::OnSocketOpened() { |
| web_socket_->SendFrame(command_); |
| } |
| |
| void ProtocolCommand::OnFrameRead(const std::string& message) { |
| delete this; |
| } |
| |
| void ProtocolCommand::OnSocketClosed() { |
| delete this; |
| } |
| |
| ProtocolCommand::~ProtocolCommand() { |
| if (!callback_.is_null()) |
| callback_.Run(); |
| } |
| |
| } // namespace |
| |
| class AgentHostDelegate; |
| |
| typedef std::map<std::string, AgentHostDelegate*> AgentHostDelegates; |
| |
| base::LazyInstance<AgentHostDelegates>::Leaky g_host_delegates = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| DevToolsAndroidBridge::Wrapper::Wrapper(content::BrowserContext* context) { |
| bridge_ = new DevToolsAndroidBridge(Profile::FromBrowserContext(context)); |
| } |
| |
| DevToolsAndroidBridge::Wrapper::~Wrapper() { |
| } |
| |
| DevToolsAndroidBridge* DevToolsAndroidBridge::Wrapper::Get() { |
| return bridge_.get(); |
| } |
| |
| // static |
| DevToolsAndroidBridge::Factory* DevToolsAndroidBridge::Factory::GetInstance() { |
| return Singleton<DevToolsAndroidBridge::Factory>::get(); |
| } |
| |
| // static |
| DevToolsAndroidBridge* DevToolsAndroidBridge::Factory::GetForProfile( |
| Profile* profile) { |
| DevToolsAndroidBridge::Wrapper* wrapper = |
| static_cast<DevToolsAndroidBridge::Wrapper*>(GetInstance()-> |
| GetServiceForBrowserContext(profile, true)); |
| return wrapper ? wrapper->Get() : NULL; |
| } |
| |
| DevToolsAndroidBridge::Factory::Factory() |
| : BrowserContextKeyedServiceFactory( |
| "DevToolsAndroidBridge", |
| BrowserContextDependencyManager::GetInstance()) {} |
| |
| DevToolsAndroidBridge::Factory::~Factory() {} |
| |
| KeyedService* DevToolsAndroidBridge::Factory::BuildServiceInstanceFor( |
| content::BrowserContext* context) const { |
| return new DevToolsAndroidBridge::Wrapper(context); |
| } |
| |
| |
| // AgentHostDelegate ---------------------------------------------------------- |
| |
| class AgentHostDelegate |
| : public content::DevToolsExternalAgentProxyDelegate, |
| public DevToolsAndroidBridge::AndroidWebSocket::Delegate { |
| public: |
| static scoped_refptr<content::DevToolsAgentHost> GetOrCreateAgentHost( |
| const std::string& id, |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const std::string& debug_url); |
| |
| private: |
| AgentHostDelegate( |
| const std::string& id, |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const std::string& debug_url); |
| virtual ~AgentHostDelegate(); |
| virtual void Attach(content::DevToolsExternalAgentProxy* proxy) OVERRIDE; |
| virtual void Detach() OVERRIDE; |
| virtual void SendMessageToBackend( |
| const std::string& message) OVERRIDE; |
| virtual void OnSocketOpened() OVERRIDE; |
| virtual void OnFrameRead(const std::string& message) OVERRIDE; |
| virtual void OnSocketClosed() OVERRIDE; |
| |
| const std::string id_; |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser_; |
| const std::string debug_url_; |
| bool socket_opened_; |
| bool is_web_view_; |
| std::vector<std::string> pending_messages_; |
| scoped_ptr<DevToolsAndroidBridge::AndroidWebSocket> web_socket_; |
| content::DevToolsAgentHost* agent_host_; |
| content::DevToolsExternalAgentProxy* proxy_; |
| DISALLOW_COPY_AND_ASSIGN(AgentHostDelegate); |
| }; |
| |
| // static |
| scoped_refptr<content::DevToolsAgentHost> |
| AgentHostDelegate::GetOrCreateAgentHost( |
| const std::string& id, |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const std::string& debug_url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| AgentHostDelegates::iterator it = g_host_delegates.Get().find(id); |
| if (it != g_host_delegates.Get().end()) |
| return it->second->agent_host_; |
| |
| AgentHostDelegate* delegate = new AgentHostDelegate(id, browser, debug_url); |
| scoped_refptr<content::DevToolsAgentHost> result = |
| content::DevToolsAgentHost::Create(delegate); |
| delegate->agent_host_ = result.get(); |
| return result; |
| } |
| |
| AgentHostDelegate::AgentHostDelegate( |
| const std::string& id, |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const std::string& debug_url) |
| : id_(id), |
| browser_(browser), |
| debug_url_(debug_url), |
| socket_opened_(false), |
| is_web_view_(browser->IsWebView()), |
| agent_host_(NULL), |
| proxy_(NULL) { |
| g_host_delegates.Get()[id] = this; |
| } |
| |
| AgentHostDelegate::~AgentHostDelegate() { |
| g_host_delegates.Get().erase(id_); |
| } |
| |
| void AgentHostDelegate::Attach(content::DevToolsExternalAgentProxy* proxy) { |
| proxy_ = proxy; |
| content::RecordAction(base::UserMetricsAction(is_web_view_ ? |
| "DevTools_InspectAndroidWebView" : "DevTools_InspectAndroidPage")); |
| web_socket_.reset(browser_->CreateWebSocket(debug_url_, this)); |
| } |
| |
| void AgentHostDelegate::Detach() { |
| web_socket_.reset(); |
| } |
| |
| void AgentHostDelegate::SendMessageToBackend(const std::string& message) { |
| if (socket_opened_) |
| web_socket_->SendFrame(message); |
| else |
| pending_messages_.push_back(message); |
| } |
| |
| void AgentHostDelegate::OnSocketOpened() { |
| socket_opened_ = true; |
| for (std::vector<std::string>::iterator it = pending_messages_.begin(); |
| it != pending_messages_.end(); ++it) { |
| SendMessageToBackend(*it); |
| } |
| pending_messages_.clear(); |
| } |
| |
| void AgentHostDelegate::OnFrameRead(const std::string& message) { |
| if (proxy_) |
| proxy_->DispatchOnClientHost(message); |
| } |
| |
| void AgentHostDelegate::OnSocketClosed() { |
| if (proxy_) |
| proxy_->ConnectionClosed(); |
| } |
| |
| //// RemotePageTarget ---------------------------------------------- |
| |
| class RemotePageTarget : public DevToolsTargetImpl, |
| public DevToolsAndroidBridge::RemotePage { |
| public: |
| RemotePageTarget(scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const base::DictionaryValue& value); |
| virtual ~RemotePageTarget(); |
| |
| // DevToolsAndroidBridge::RemotePage implementation. |
| virtual DevToolsTargetImpl* GetTarget() OVERRIDE; |
| virtual std::string GetFrontendURL() OVERRIDE; |
| |
| // DevToolsTargetImpl overrides. |
| virtual std::string GetId() const OVERRIDE; |
| virtual bool IsAttached() const OVERRIDE; |
| virtual bool Activate() const OVERRIDE; |
| virtual bool Close() const OVERRIDE; |
| virtual void Inspect(Profile* profile) const OVERRIDE; |
| virtual void Reload() const OVERRIDE; |
| |
| void Navigate(const std::string& url, base::Closure callback) const; |
| |
| private: |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser_; |
| std::string debug_url_; |
| std::string frontend_url_; |
| std::string remote_id_; |
| DISALLOW_COPY_AND_ASSIGN(RemotePageTarget); |
| }; |
| |
| static std::string GetStringProperty(const base::DictionaryValue& value, |
| const std::string& name) { |
| std::string result; |
| value.GetString(name, &result); |
| return result; |
| } |
| |
| static std::string BuildUniqueTargetId( |
| DevToolsAndroidBridge::RemoteBrowser* browser, |
| const base::DictionaryValue& value) { |
| return base::StringPrintf("%s:%s:%s", browser->serial().c_str(), |
| browser->socket().c_str(), GetStringProperty(value, "id").c_str()); |
| } |
| |
| static std::string GetDebugURL(const base::DictionaryValue& value) { |
| std::string debug_url = GetStringProperty(value, "webSocketDebuggerUrl"); |
| |
| if (debug_url.find("ws://") == 0) |
| debug_url = debug_url.substr(5); |
| else |
| debug_url = ""; |
| return debug_url; |
| } |
| |
| RemotePageTarget::RemotePageTarget( |
| scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser, |
| const base::DictionaryValue& value) |
| : DevToolsTargetImpl(AgentHostDelegate::GetOrCreateAgentHost( |
| BuildUniqueTargetId(browser.get(), value), |
| browser, GetDebugURL(value))), |
| browser_(browser), |
| debug_url_(GetDebugURL(value)), |
| remote_id_(GetStringProperty(value, "id")) { |
| set_type("adb_page"); |
| set_url(GURL(GetStringProperty(value, "url"))); |
| set_title(base::UTF16ToUTF8(net::UnescapeForHTML(base::UTF8ToUTF16( |
| GetStringProperty(value, "title"))))); |
| set_description(GetStringProperty(value, "description")); |
| set_favicon_url(GURL(GetStringProperty(value, "faviconUrl"))); |
| debug_url_ = GetDebugURL(value); |
| frontend_url_ = GetStringProperty(value, "devtoolsFrontendUrl"); |
| |
| size_t ws_param = frontend_url_.find("?ws"); |
| if (ws_param != std::string::npos) |
| frontend_url_ = frontend_url_.substr(0, ws_param); |
| if (frontend_url_.find("http:") == 0) |
| frontend_url_ = "https:" + frontend_url_.substr(5); |
| } |
| |
| RemotePageTarget::~RemotePageTarget() { |
| } |
| |
| DevToolsTargetImpl* RemotePageTarget::GetTarget() { |
| return this; |
| } |
| |
| std::string RemotePageTarget::GetFrontendURL() { |
| return frontend_url_; |
| } |
| |
| std::string RemotePageTarget::GetId() const { |
| return remote_id_; |
| } |
| |
| bool RemotePageTarget::IsAttached() const { |
| return debug_url_.empty(); |
| } |
| |
| static void NoOp(int, const std::string&) {} |
| |
| void RemotePageTarget::Inspect(Profile* profile) const { |
| Activate(); |
| DevToolsWindow::OpenExternalFrontend(profile, frontend_url_, |
| GetAgentHost()); |
| } |
| |
| bool RemotePageTarget::Activate() const { |
| std::string request = base::StringPrintf(kActivatePageRequest, |
| remote_id_.c_str()); |
| browser_->SendJsonRequest(request, base::Bind(&NoOp)); |
| return true; |
| } |
| |
| bool RemotePageTarget::Close() const { |
| std::string request = base::StringPrintf(kClosePageRequest, |
| remote_id_.c_str()); |
| browser_->SendJsonRequest(request, base::Bind(&NoOp)); |
| return true; |
| } |
| |
| void RemotePageTarget::Reload() const { |
| browser_->SendProtocolCommand(debug_url_, kPageReloadCommand, NULL, |
| base::Closure()); |
| } |
| |
| void RemotePageTarget::Navigate(const std::string& url, |
| base::Closure callback) const { |
| base::DictionaryValue params; |
| params.SetString(kUrlParam, url); |
| browser_->SendProtocolCommand(debug_url_, kPageNavigateCommand, ¶ms, |
| callback); |
| } |
| |
| // DevToolsAndroidBridge::RemoteBrowser --------------------------------------- |
| |
| DevToolsAndroidBridge::RemoteBrowser::RemoteBrowser( |
| scoped_refptr<Device> device, |
| const AndroidDeviceManager::BrowserInfo& browser_info) |
| : device_(device), |
| socket_(browser_info.socket_name), |
| display_name_(browser_info.display_name), |
| type_(browser_info.type), |
| page_descriptors_(new base::ListValue()) { |
| } |
| |
| bool DevToolsAndroidBridge::RemoteBrowser::IsChrome() const { |
| return type_ == AndroidDeviceManager::BrowserInfo::kTypeChrome; |
| } |
| |
| bool DevToolsAndroidBridge::RemoteBrowser::IsWebView() const { |
| return type_ == AndroidDeviceManager::BrowserInfo::kTypeWebView; |
| } |
| |
| DevToolsAndroidBridge::RemoteBrowser::ParsedVersion |
| DevToolsAndroidBridge::RemoteBrowser::GetParsedVersion() const { |
| ParsedVersion result; |
| std::vector<std::string> parts; |
| Tokenize(version_, ".", &parts); |
| for (size_t i = 0; i != parts.size(); ++i) { |
| int value = 0; |
| base::StringToInt(parts[i], &value); |
| result.push_back(value); |
| } |
| return result; |
| } |
| |
| std::vector<DevToolsAndroidBridge::RemotePage*> |
| DevToolsAndroidBridge::RemoteBrowser::CreatePages() { |
| std::vector<DevToolsAndroidBridge::RemotePage*> result; |
| for (size_t i = 0; i < page_descriptors_->GetSize(); ++i) { |
| base::Value* item; |
| page_descriptors_->Get(i, &item); |
| if (!item) |
| continue; |
| base::DictionaryValue* dict; |
| if (!item->GetAsDictionary(&dict)) |
| continue; |
| result.push_back(new RemotePageTarget(this, *dict)); |
| } |
| return result; |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::SetPageDescriptors( |
| const base::ListValue& list) { |
| page_descriptors_.reset(list.DeepCopy()); |
| } |
| |
| static void RespondOnUIThread( |
| const DevToolsAndroidBridge::JsonRequestCallback& callback, |
| int result, |
| const std::string& response) { |
| if (callback.is_null()) |
| return; |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, base::Bind(callback, result, response)); |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::SendJsonRequest( |
| const std::string& request, const JsonRequestCallback& callback) { |
| device_->SendJsonRequest(socket_, request, callback); |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::SendProtocolCommand( |
| const std::string& debug_url, |
| const std::string& method, |
| base::DictionaryValue* params, |
| const base::Closure callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (debug_url.empty()) |
| return; |
| DevToolsProtocol::Command command(1, method, params); |
| new ProtocolCommand(this, debug_url, command.Serialize(), callback); |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::Open( |
| const std::string& url, |
| const DevToolsAndroidBridge::RemotePageCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| InnerOpen(url, base::Bind(&RemoteBrowser::RespondToOpenOnUIThread, |
| this, callback)); |
| } |
| |
| scoped_refptr<content::DevToolsAgentHost> |
| DevToolsAndroidBridge::RemoteBrowser::GetAgentHost() { |
| return AgentHostDelegate::GetOrCreateAgentHost( |
| "adb:" + device_->serial() + ":" + socket_, this, kBrowserTargetSocket); |
| } |
| |
| DevToolsAndroidBridge::AndroidWebSocket* |
| DevToolsAndroidBridge::RemoteBrowser::CreateWebSocket( |
| const std::string& url, |
| DevToolsAndroidBridge::AndroidWebSocket::Delegate* delegate) { |
| return device_->CreateWebSocket(socket_, url, delegate); |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::RespondToOpenOnUIThread( |
| const DevToolsAndroidBridge::RemotePageCallback& callback, |
| int result, |
| const std::string& response) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (result < 0) { |
| callback.Run(NULL); |
| return; |
| } |
| scoped_ptr<base::Value> value(base::JSONReader::Read(response)); |
| base::DictionaryValue* dict; |
| if (value && value->GetAsDictionary(&dict)) { |
| RemotePageTarget* new_page = new RemotePageTarget(this, *dict); |
| callback.Run(new_page); |
| } |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::InnerOpen( |
| const std::string& input_url, |
| const JsonRequestCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| GURL gurl(input_url); |
| if (!gurl.is_valid()) { |
| gurl = GURL("http://" + input_url); |
| if (!gurl.is_valid()) |
| return; |
| } |
| std::string url = gurl.spec(); |
| |
| ParsedVersion parsed_version = GetParsedVersion(); |
| if (IsChrome() && |
| !parsed_version.empty() && |
| parsed_version[0] >= kMinVersionNewWithURL) { |
| std::string query = net::EscapeQueryParamValue(url, false /* use_plus */); |
| std::string request = |
| base::StringPrintf(kNewPageRequestWithURL, query.c_str()); |
| SendJsonRequest(request, callback); |
| } else { |
| SendJsonRequest(kNewPageRequest, |
| base::Bind(&RemoteBrowser::PageCreatedOnUIThread, this, |
| callback, url)); |
| } |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::PageCreatedOnUIThread( |
| const JsonRequestCallback& callback, |
| const std::string& url, int result, const std::string& response) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (result < 0) |
| return; |
| // Navigating too soon after the page creation breaks navigation history |
| // (crbug.com/311014). This can be avoided by adding a moderate delay. |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&RemoteBrowser::NavigatePageOnUIThread, |
| this, callback, result, response, url), |
| base::TimeDelta::FromMilliseconds(kNewPageNavigateDelayMs)); |
| } |
| |
| void DevToolsAndroidBridge::RemoteBrowser::NavigatePageOnUIThread( |
| const JsonRequestCallback& callback, |
| int result, const std::string& response, const std::string& url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| scoped_ptr<base::Value> value(base::JSONReader::Read(response)); |
| base::DictionaryValue* dict; |
| |
| if (value && value->GetAsDictionary(&dict)) { |
| RemotePageTarget new_page(this, *dict); |
| new_page.Navigate(url, |
| base::Bind(&RespondOnUIThread, callback, result, response)); |
| } |
| } |
| |
| DevToolsAndroidBridge::RemoteBrowser::~RemoteBrowser() { |
| } |
| |
| // DevToolsAndroidBridge::RemoteDevice ---------------------------------------- |
| |
| DevToolsAndroidBridge::RemoteDevice::RemoteDevice( |
| scoped_refptr<AndroidDeviceManager::Device> device, |
| const AndroidDeviceManager::DeviceInfo& device_info) |
| : device_(device), |
| model_(device_info.model), |
| connected_(device_info.connected), |
| screen_size_(device_info.screen_size) { |
| for (std::vector<AndroidDeviceManager::BrowserInfo>::const_iterator it = |
| device_info.browser_info.begin(); |
| it != device_info.browser_info.end(); |
| ++it) { |
| browsers_.push_back(new DevToolsAndroidBridge::RemoteBrowser(device, *it)); |
| } |
| } |
| |
| void DevToolsAndroidBridge::RemoteDevice::OpenSocket( |
| const std::string& socket_name, |
| const AndroidDeviceManager::SocketCallback& callback) { |
| device_->OpenSocket(socket_name, callback); |
| } |
| |
| DevToolsAndroidBridge::RemoteDevice::~RemoteDevice() { |
| } |
| |
| // DevToolsAndroidBridge ------------------------------------------------------ |
| |
| DevToolsAndroidBridge::DevToolsAndroidBridge(Profile* profile) |
| : profile_(profile), |
| device_manager_(AndroidDeviceManager::Create()), |
| task_scheduler_(base::Bind(&DevToolsAndroidBridge::ScheduleTaskDefault)) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| pref_change_registrar_.Init(profile_->GetPrefs()); |
| pref_change_registrar_.Add(prefs::kDevToolsDiscoverUsbDevicesEnabled, |
| base::Bind(&DevToolsAndroidBridge::CreateDeviceProviders, |
| base::Unretained(this))); |
| CreateDeviceProviders(); |
| } |
| |
| void DevToolsAndroidBridge::AddDeviceListListener( |
| DeviceListListener* listener) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| device_list_listeners_.push_back(listener); |
| if (device_list_listeners_.size() == 1) |
| StartDeviceListPolling(); |
| } |
| |
| void DevToolsAndroidBridge::RemoveDeviceListListener( |
| DeviceListListener* listener) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DeviceListListeners::iterator it = std::find( |
| device_list_listeners_.begin(), device_list_listeners_.end(), listener); |
| DCHECK(it != device_list_listeners_.end()); |
| device_list_listeners_.erase(it); |
| if (device_list_listeners_.empty()) |
| StopDeviceListPolling(); |
| } |
| |
| void DevToolsAndroidBridge::AddDeviceCountListener( |
| DeviceCountListener* listener) { |
| device_count_listeners_.push_back(listener); |
| if (device_count_listeners_.size() == 1) |
| StartDeviceCountPolling(); |
| } |
| |
| void DevToolsAndroidBridge::RemoveDeviceCountListener( |
| DeviceCountListener* listener) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DeviceCountListeners::iterator it = std::find( |
| device_count_listeners_.begin(), device_count_listeners_.end(), listener); |
| DCHECK(it != device_count_listeners_.end()); |
| device_count_listeners_.erase(it); |
| if (device_count_listeners_.empty()) |
| StopDeviceCountPolling(); |
| } |
| |
| // static |
| bool DevToolsAndroidBridge::HasDevToolsWindow(const std::string& agent_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return g_host_delegates.Get().find(agent_id) != g_host_delegates.Get().end(); |
| } |
| |
| DevToolsAndroidBridge::~DevToolsAndroidBridge() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(device_list_listeners_.empty()); |
| DCHECK(device_count_listeners_.empty()); |
| } |
| |
| void DevToolsAndroidBridge::StartDeviceListPolling() { |
| device_list_callback_.Reset( |
| base::Bind(&DevToolsAndroidBridge::ReceivedDeviceList, this)); |
| RequestDeviceList(device_list_callback_.callback()); |
| } |
| |
| void DevToolsAndroidBridge::StopDeviceListPolling() { |
| device_list_callback_.Cancel(); |
| devices_.clear(); |
| } |
| |
| void DevToolsAndroidBridge::RequestDeviceList( |
| const base::Callback<void(const RemoteDevices&)>& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (device_list_listeners_.empty() || |
| !callback.Equals(device_list_callback_.callback())) |
| return; |
| |
| new DiscoveryRequest(device_manager_.get(), callback); |
| } |
| |
| void DevToolsAndroidBridge::ReceivedDeviceList(const RemoteDevices& devices) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DeviceListListeners copy(device_list_listeners_); |
| for (DeviceListListeners::iterator it = copy.begin(); it != copy.end(); ++it) |
| (*it)->DeviceListChanged(devices); |
| |
| if (device_list_listeners_.empty()) |
| return; |
| |
| devices_ = devices; |
| |
| task_scheduler_.Run( |
| base::Bind(&DevToolsAndroidBridge::RequestDeviceList, |
| this, device_list_callback_.callback())); |
| } |
| |
| void DevToolsAndroidBridge::StartDeviceCountPolling() { |
| device_count_callback_.Reset( |
| base::Bind(&DevToolsAndroidBridge::ReceivedDeviceCount, this)); |
| RequestDeviceCount(device_count_callback_.callback()); |
| } |
| |
| void DevToolsAndroidBridge::StopDeviceCountPolling() { |
| device_count_callback_.Cancel(); |
| } |
| |
| void DevToolsAndroidBridge::RequestDeviceCount( |
| const base::Callback<void(int)>& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (device_count_listeners_.empty() || |
| !callback.Equals(device_count_callback_.callback())) |
| return; |
| |
| UsbDeviceProvider::CountDevices(callback); |
| } |
| |
| void DevToolsAndroidBridge::ReceivedDeviceCount(int count) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DeviceCountListeners copy(device_count_listeners_); |
| for (DeviceCountListeners::iterator it = copy.begin(); it != copy.end(); ++it) |
| (*it)->DeviceCountChanged(count); |
| |
| if (device_count_listeners_.empty()) |
| return; |
| |
| task_scheduler_.Run( |
| base::Bind(&DevToolsAndroidBridge::RequestDeviceCount, |
| this, device_count_callback_.callback())); |
| } |
| |
| // static |
| void DevToolsAndroidBridge::ScheduleTaskDefault(const base::Closure& task) { |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| task, |
| base::TimeDelta::FromMilliseconds(kAdbPollingIntervalMs)); |
| } |
| |
| void DevToolsAndroidBridge::CreateDeviceProviders() { |
| AndroidDeviceManager::DeviceProviders device_providers; |
| #if defined(DEBUG_DEVTOOLS) |
| BrowserListTabContentsProvider::EnableTethering(); |
| // We cannot rely on command line switch here as we might want to connect |
| // to another instance of Chrome. Using hard-coded port number instead. |
| const int kDefaultDebuggingPort = 9222; |
| device_providers.push_back(new SelfAsDeviceProvider(kDefaultDebuggingPort)); |
| #endif |
| device_providers.push_back(new AdbDeviceProvider()); |
| |
| PrefService* service = profile_->GetPrefs(); |
| const PrefService::Preference* pref = |
| service->FindPreference(prefs::kDevToolsDiscoverUsbDevicesEnabled); |
| const base::Value* pref_value = pref->GetValue(); |
| |
| bool enabled; |
| if (pref_value->GetAsBoolean(&enabled) && enabled) { |
| device_providers.push_back(new UsbDeviceProvider(profile_)); |
| } |
| device_manager_->SetDeviceProviders(device_providers); |
| if (!device_list_listeners_.empty()) { |
| StopDeviceListPolling(); |
| StartDeviceListPolling(); |
| } |
| } |