Index: chrome/browser/devtools/devtools_adb_bridge.cc |
diff --git a/chrome/browser/devtools/devtools_adb_bridge.cc b/chrome/browser/devtools/devtools_adb_bridge.cc |
index 0efd62b92ab911a85da4cb72d37b2152b1bc535e..145e5e55d7cd50ba7604c9f18ec25b4e3c8ab7b7 100644 |
--- a/chrome/browser/devtools/devtools_adb_bridge.cc |
+++ b/chrome/browser/devtools/devtools_adb_bridge.cc |
@@ -17,10 +17,16 @@ |
#include "base/threading/thread.h" |
#include "base/values.h" |
#include "chrome/browser/devtools/adb_client_socket.h" |
+#include "chrome/browser/profiles/profile.h" |
#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/devtools_agent_host.h" |
+#include "content/public/browser/devtools_client_host.h" |
+#include "content/public/browser/devtools_manager.h" |
#include "net/base/net_errors.h" |
+#include "net/server/web_socket.h" |
using content::BrowserThread; |
+using net::WebSocket; |
namespace { |
@@ -29,237 +35,298 @@ static const char kDevToolsChannelName[] = "chrome_devtools_remote"; |
static const char kHostDevicesCommand[] = "host:devices"; |
static const char kDeviceModelCommand[] = |
"host:transport:%s|shell:getprop ro.product.model"; |
-static const char kPageListQuery[] = "/json"; |
+ |
+static const char kPageListRequest[] = "GET /json HTTP/1.1\r\n\r\n"; |
+static const char kWebSocketUpgradeRequest[] = "GET %s HTTP/1.1\r\n" |
+ "Upgrade: WebSocket\r\n" |
+ "Connection: Upgrade\r\n" |
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" |
+ "Sec-WebSocket-Version: 13\r\n" |
+ "\r\n"; |
const int kAdbPort = 5037; |
+const int kBufferSize = 16 * 1024; |
+ |
+typedef DevToolsAdbBridge::Callback Callback; |
+typedef DevToolsAdbBridge::PagesCallback PagesCallback; |
+ |
+class AdbQueryCommand : public base::RefCounted<AdbQueryCommand> { |
+ public: |
+ AdbQueryCommand(const std::string& query, |
+ const Callback& callback) |
+ : query_(query), |
+ callback_(callback) { |
+ } |
+ |
+ void Run() { |
+ AdbClientSocket::AdbQuery(kAdbPort, query_, |
+ base::Bind(&AdbQueryCommand::Handle, this)); |
+ } |
+ |
+ private: |
+ friend class base::RefCounted<AdbQueryCommand>; |
+ virtual ~AdbQueryCommand() {} |
+ |
+ void Handle(int result, const std::string& response) { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&AdbQueryCommand::Respond, this, result, response)); |
+ } |
+ |
+ void Respond(int result, const std::string& response) { |
+ callback_.Run(result, response); |
+ } |
+ |
+ std::string query_; |
+ Callback callback_; |
+}; |
+ |
+class AdbPagesCommand : public base::RefCounted<AdbPagesCommand> { |
+ public: |
+ explicit AdbPagesCommand(const PagesCallback& callback) |
+ : callback_(callback) { |
+ pages_.reset(new DevToolsAdbBridge::RemotePages()); |
+ } |
+ |
+ void Run() { |
+ AdbClientSocket::AdbQuery( |
+ kAdbPort, kHostDevicesCommand, |
+ base::Bind(&AdbPagesCommand::ReceivedDevices, this)); |
+ } |
+ |
+ private: |
+ friend class base::RefCounted<AdbPagesCommand>; |
+ virtual ~AdbPagesCommand() {} |
+ |
+ void ReceivedDevices(int result, const std::string& response) { |
+ if (result != net::OK) { |
+ ProcessSerials(); |
+ return; |
+ } |
+ |
+ std::vector<std::string> devices; |
+ Tokenize(response, "\n", &devices); |
+ for (size_t i = 0; i < devices.size(); ++i) { |
+ std::vector<std::string> tokens; |
+ Tokenize(devices[i], "\t ", &tokens); |
+ std::string serial = tokens[0]; |
+ serials_.push_back(serial); |
+ } |
+ |
+ ProcessSerials(); |
+ } |
+ |
+ void ProcessSerials() { |
+ if (serials_.size() == 0) { |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&AdbPagesCommand::Respond, this)); |
+ return; |
+ } |
+ |
+ AdbClientSocket::AdbQuery( |
+ kAdbPort, |
+ base::StringPrintf(kDeviceModelCommand, serials_.back().c_str()), |
+ base::Bind(&AdbPagesCommand::ReceivedModel, this)); |
+ } |
+ |
+ void ReceivedModel(int result, const std::string& response) { |
+ if (result != net::OK) { |
+ serials_.pop_back(); |
+ ProcessSerials(); |
+ return; |
+ } |
+ |
+ AdbClientSocket::HttpQuery( |
+ kAdbPort, serials_.back(), kDevToolsChannelName, kPageListRequest, |
+ base::Bind(&AdbPagesCommand::ReceivedPages, this, response)); |
+ } |
+ |
+ void ReceivedPages(const std::string& model, |
+ int result, |
+ const std::string& response) { |
+ std::string serial = serials_.back(); |
+ serials_.pop_back(); |
+ if (result < 0) { |
+ ProcessSerials(); |
+ return; |
+ } |
+ |
+ std::string body = response.substr(result); |
+ scoped_ptr<base::Value> value(base::JSONReader::Read(body)); |
+ base::ListValue* list_value; |
+ if (!value || !value->GetAsList(&list_value)) { |
+ ProcessSerials(); |
+ return; |
+ } |
+ |
+ base::Value* item; |
+ for (size_t i = 0; i < list_value->GetSize(); ++i) { |
+ list_value->Get(i, &item); |
+ base::DictionaryValue* dict; |
+ if (!item || !item->GetAsDictionary(&dict)) |
+ continue; |
+ pages_->push_back( |
+ new DevToolsAdbBridge::RemotePage(serial, model, *dict)); |
+ } |
+ ProcessSerials(); |
+ } |
+ |
+ void Respond() { |
+ callback_.Run(net::OK, pages_.release()); |
+ } |
+ |
+ PagesCallback callback_; |
+ std::vector<std::string> serials_; |
+ scoped_ptr<DevToolsAdbBridge::RemotePages> pages_; |
+}; |
+ |
+class AdbAttachCommand : public base::RefCounted<AdbAttachCommand> { |
+ public: |
+ explicit AdbAttachCommand(scoped_refptr<DevToolsAdbBridge::RemotePage> page) |
+ : page_(page) { |
+ } |
+ |
+ void Run() { |
+ AdbClientSocket::HttpQuery( |
+ kAdbPort, page_->serial(), kDevToolsChannelName, |
+ base::StringPrintf(kWebSocketUpgradeRequest, |
+ page_->debug_url().c_str()), |
+ base::Bind(&AdbAttachCommand::Handle, this)); |
+ } |
+ |
+ private: |
+ friend class base::RefCounted<AdbAttachCommand>; |
+ virtual ~AdbAttachCommand() {} |
+ |
+ void Handle(int result, net::TCPClientSocket* socket) { |
+ if (result != net::OK || socket == NULL) |
+ return; |
+ |
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
+ base::Bind(&AdbAttachCommand::OpenDevToolsWindow, this, socket)); |
+ } |
+ |
+ void OpenDevToolsWindow(net::TCPClientSocket* socket) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ scoped_ptr<net::TCPClientSocket> tcp_socket(socket); |
+ // TODO(pfeldman): Show DevToolsWindow here. |
+ } |
+ |
+ scoped_refptr<DevToolsAdbBridge::RemotePage> page_; |
+}; |
} // namespace |
-DevToolsAdbBridge::AgentHost::AgentHost(const std::string& serial, |
- const std::string& model, |
- const base::DictionaryValue& value) |
+DevToolsAdbBridge::RemotePage::RemotePage(const std::string& serial, |
+ const std::string& model, |
+ const base::DictionaryValue& value) |
: serial_(serial), |
model_(model) { |
value.GetString("id", &id_); |
+ value.GetString("url", &url_); |
value.GetString("title", &title_); |
value.GetString("descirption", &description_); |
value.GetString("faviconUrl", &favicon_url_); |
value.GetString("webSocketDebuggerUrl", &debug_url_); |
+ value.GetString("devtoolsFrontendUrl", &frontend_url_); |
+ |
+ if (debug_url_.find("ws://") == 0) |
+ debug_url_ = debug_url_.substr(5); |
+ else |
+ debug_url_ = ""; |
+ |
+ size_t ws_param = frontend_url_.find("?ws"); |
+ if (ws_param != std::string::npos) |
+ frontend_url_ = frontend_url_.substr(0, ws_param); |
} |
-DevToolsAdbBridge::AgentHost::~AgentHost() { |
+DevToolsAdbBridge::RemotePage::~RemotePage() { |
} |
+DevToolsAdbBridge::RefCountedAdbThread* |
+DevToolsAdbBridge::RefCountedAdbThread::instance_ = NULL; |
+ |
// static |
-DevToolsAdbBridge* DevToolsAdbBridge::Start() { |
+scoped_refptr<DevToolsAdbBridge::RefCountedAdbThread> |
+DevToolsAdbBridge::RefCountedAdbThread::GetInstance() { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- return new DevToolsAdbBridge(); |
+ if (!instance_) |
+ new RefCountedAdbThread(); |
+ return instance_; |
} |
-void DevToolsAdbBridge::Stop() { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- if (!thread_.get()) { |
- ResetHandlerAndReleaseOnUIThread(); |
- return; |
+DevToolsAdbBridge::RefCountedAdbThread::RefCountedAdbThread() { |
+ instance_ = this; |
+ thread_ = new base::Thread(kDevToolsAdbBridgeThreadName); |
+ base::Thread::Options options; |
+ options.message_loop_type = MessageLoop::TYPE_IO; |
+ if (!thread_->StartWithOptions(options)) { |
+ delete thread_; |
+ thread_ = NULL; |
} |
- BrowserThread::PostTaskAndReply( |
- BrowserThread::FILE, FROM_HERE, |
- base::Bind(&DevToolsAdbBridge::StopHandlerOnFileThread, |
- base::Unretained(this)), |
- base::Bind(&DevToolsAdbBridge::ResetHandlerAndReleaseOnUIThread, |
- base::Unretained(this))); |
} |
-void DevToolsAdbBridge::Query( |
- const std::string query, |
- const Callback& callback) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+MessageLoop* DevToolsAdbBridge::RefCountedAdbThread::message_loop() { |
+ return thread_ ? thread_->message_loop() : NULL; |
+} |
- // There is a race condition in case Query immediately follows start. We |
- // consider it Ok since query is polling anyways. |
- if (!thread_.get()) { |
- callback.Run(net::ERR_FAILED, "ADB is not yet connected"); |
- return; |
- } |
- thread_->message_loop()->PostTask( |
- FROM_HERE, |
- base::Bind(&DevToolsAdbBridge::QueryOnHandlerThread, |
- base::Unretained(this), query, callback)); |
+// static |
+void DevToolsAdbBridge::RefCountedAdbThread::StopThread(base::Thread* thread) { |
+ thread->Stop(); |
} |
-void DevToolsAdbBridge::Devices() { |
+DevToolsAdbBridge::RefCountedAdbThread::~RefCountedAdbThread() { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- if (!thread_.get()) |
+ instance_ = NULL; |
+ if (!thread_) |
return; |
- |
- thread_->message_loop()->PostTask( |
- FROM_HERE, |
- base::Bind(&DevToolsAdbBridge::DevicesOnHandlerThread, |
- base::Unretained(this), |
- base::Bind(&DevToolsAdbBridge::PrintHosts, |
- base::Unretained(this)))); |
+ // Shut down thread on FILE thread to join into IO. |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&RefCountedAdbThread::StopThread, thread_)); |
} |
-DevToolsAdbBridge::DevToolsAdbBridge() { |
- thread_.reset(new base::Thread(kDevToolsAdbBridgeThreadName)); |
- |
- base::Thread::Options options; |
- options.message_loop_type = MessageLoop::TYPE_IO; |
- if (!thread_->StartWithOptions(options)) |
- thread_.reset(); |
+DevToolsAdbBridge::DevToolsAdbBridge(Profile* profile) |
+ : profile_(profile), |
+ adb_thread_(RefCountedAdbThread::GetInstance()), |
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), |
+ has_message_loop_(adb_thread_->message_loop() != NULL) { |
} |
DevToolsAdbBridge::~DevToolsAdbBridge() { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- // Stop() must be called prior to destruction. |
- DCHECK(thread_.get() == NULL); |
-} |
- |
-// Runs on FILE thread to make sure that it is serialized against |
-// {Start|Stop}HandlerThread and to allow calling pthread_join. |
-void DevToolsAdbBridge::StopHandlerOnFileThread() { |
- if (!thread_->message_loop()) |
- return; |
- // Thread::Stop joins the thread. |
- thread_->Stop(); |
-} |
- |
-void DevToolsAdbBridge::ResetHandlerAndReleaseOnUIThread() { |
- ResetHandlerOnUIThread(); |
- delete this; |
} |
-void DevToolsAdbBridge::ResetHandlerOnUIThread() { |
- thread_.reset(); |
-} |
- |
-void DevToolsAdbBridge::QueryOnHandlerThread( |
+void DevToolsAdbBridge::Query( |
const std::string query, |
const Callback& callback) { |
- AdbClientSocket::AdbQuery(kAdbPort, query, |
- base::Bind(&DevToolsAdbBridge::QueryResponseOnHandlerThread, |
- base::Unretained(this), callback)); |
-} |
- |
-void DevToolsAdbBridge::QueryResponseOnHandlerThread( |
- const Callback& callback, |
- int result, |
- const std::string& response) { |
- BrowserThread::PostTask( |
- BrowserThread::UI, FROM_HERE, |
- base::Bind(&DevToolsAdbBridge::RespondOnUIThread, base::Unretained(this), |
- callback, result, response)); |
-} |
- |
-void DevToolsAdbBridge::DevicesOnHandlerThread( |
- const HostsCallback& callback) { |
- AdbClientSocket::AdbQuery( |
- kAdbPort, kHostDevicesCommand, |
- base::Bind(&DevToolsAdbBridge::ReceivedDevices, |
- base::Unretained(this), callback)); |
-} |
- |
-void DevToolsAdbBridge::ReceivedDevices( |
- const HostsCallback& callback, |
- int result, |
- const std::string& response) { |
- AgentHosts* hosts = new AgentHosts(); |
- if (result != net::OK) { |
- callback.Run(result, hosts); |
- return; |
- } |
- |
- std::vector<std::string> devices; |
- Tokenize(response, "\n", &devices); |
- std::vector<std::string>* serials = new std::vector<std::string>(); |
- for (size_t i = 0; i < devices.size(); ++i) { |
- std::vector<std::string> tokens; |
- Tokenize(devices[i], "\t ", &tokens); |
- std::string serial = tokens[0]; |
- serials->push_back(serial); |
- } |
- |
- ProcessSerials(callback, hosts, serials); |
-} |
- |
-void DevToolsAdbBridge::ProcessSerials( |
- const HostsCallback& callback, |
- AgentHosts* hosts, |
- std::vector<std::string>* serials) { |
- if (serials->size() == 0) { |
- delete serials; |
- callback.Run(net::OK, hosts); |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!has_message_loop_) { |
+ callback.Run(net::ERR_FAILED, "Could not start ADB thread"); |
return; |
} |
- |
- AdbClientSocket::AdbQuery( |
- kAdbPort, |
- base::StringPrintf(kDeviceModelCommand, serials->back().c_str()), |
- base::Bind(&DevToolsAdbBridge::ReceivedModel, base::Unretained(this), |
- callback, hosts, serials)); |
+ scoped_refptr<AdbQueryCommand> command(new AdbQueryCommand(query, callback)); |
+ adb_thread_->message_loop()->PostTask(FROM_HERE, |
+ base::Bind(&AdbQueryCommand::Run, command)); |
} |
-void DevToolsAdbBridge::ReceivedModel(const HostsCallback& callback, |
- AgentHosts* hosts, |
- std::vector<std::string>* serials, |
- int result, |
- const std::string& response) { |
- if (result != net::OK) { |
- serials->pop_back(); |
- ProcessSerials(callback, hosts, serials); |
+void DevToolsAdbBridge::Pages(const PagesCallback& callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!has_message_loop_) |
return; |
- } |
- |
- AdbClientSocket::HttpQuery( |
- kAdbPort, serials->back(), kDevToolsChannelName, kPageListQuery, |
- base::Bind(&DevToolsAdbBridge::ReceivedPages, base::Unretained(this), |
- callback, hosts, serials, response)); |
+ scoped_refptr<AdbPagesCommand> command(new AdbPagesCommand(callback)); |
+ adb_thread_->message_loop()->PostTask(FROM_HERE, |
+ base::Bind(&AdbPagesCommand::Run, command)); |
} |
-void DevToolsAdbBridge::ReceivedPages(const HostsCallback& callback, |
- AgentHosts* hosts, |
- std::vector<std::string>* serials, |
- const std::string& model, |
- int result, |
- const std::string& response) { |
- std::string serial = serials->back(); |
- serials->pop_back(); |
- if (result != net::OK) { |
- ProcessSerials(callback, hosts, serials); |
- return; |
- } |
- |
- scoped_ptr<base::Value> value(base::JSONReader::Read(response)); |
- base::ListValue* list_value; |
- if (!value || !value->GetAsList(&list_value)) { |
- ProcessSerials(callback, hosts, serials); |
+void DevToolsAdbBridge::Attach(scoped_refptr<RemotePage> page) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!has_message_loop_) |
return; |
- } |
- |
- base::Value* item; |
- for (size_t i = 0; i < list_value->GetSize(); ++i) { |
- list_value->Get(i, &item); |
- base::DictionaryValue* dict; |
- if (!item || !item->GetAsDictionary(&dict)) |
- continue; |
- scoped_refptr<AgentHost> host = new AgentHost(serial, model, *dict); |
- hosts->push_back(host); |
- } |
- ProcessSerials(callback, hosts, serials); |
-} |
- |
-void DevToolsAdbBridge::RespondOnUIThread(const Callback& callback, |
- int result, |
- const std::string& response) { |
- callback.Run(result, response); |
-} |
-void DevToolsAdbBridge::PrintHosts(int result, AgentHosts* hosts) { |
- for (AgentHosts::iterator it = hosts->begin(); it != hosts->end(); ++it) { |
- AgentHost* host = it->get(); |
- fprintf(stderr, "HOST %s %s %s %s %s %s %s\n", host->serial().c_str(), |
- host->model().c_str(), host->id().c_str(), host->title().c_str(), |
- host->description().c_str(), host->favicon_url().c_str(), |
- host->debug_url().c_str()); |
- } |
+ scoped_refptr<AdbAttachCommand> command(new AdbAttachCommand(page)); |
+ adb_thread_->message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&AdbAttachCommand::Run, command)); |
} |