From a1a6ea6fe12a4c56ec93b92d3d1288d6d505a8c5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 27 Aug 2015 12:25:28 +0800 Subject: [PATCH 1/5] Revert "Remove our own debugger implementation" This reverts commit 1d148fe2fb0d7013d7d6633539de952e71d756f1. --- atom/browser/atom_browser_main_parts.cc | 8 + atom/browser/atom_browser_main_parts.h | 2 + atom/browser/node_debugger.cc | 202 ++++++++++++++++++++++++ atom/browser/node_debugger.h | 59 +++++++ atom/common/node_bindings.cc | 5 - filenames.gypi | 2 + 6 files changed, 273 insertions(+), 5 deletions(-) create mode 100644 atom/browser/node_debugger.cc create mode 100644 atom/browser/node_debugger.h diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index 15ffd05b39f0..c472b8742f06 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -9,6 +9,7 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/browser.h" #include "atom/browser/javascript_environment.h" +#include "atom/browser/node_debugger.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/node_bindings.h" #include "base/command_line.h" @@ -69,9 +70,16 @@ void AtomBrowserMainParts::PostEarlyInitialization() { node_bindings_->Initialize(); + // Support the "--debug" switch. + node_debugger_.reset(new NodeDebugger(js_env_->isolate())); + // Create the global environment. global_env = node_bindings_->CreateEnvironment(js_env_->context()); + // Make sure node can get correct environment when debugging. + if (node_debugger_->IsRunning()) + global_env->AssignToContext(v8::Debug::GetDebugContext()); + // Add atom-shell extended APIs. atom_bindings_->BindTo(js_env_->isolate(), global_env->process_object()); diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index 2b308e8c9aff..97663a72404e 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -19,6 +19,7 @@ class AtomBindings; class Browser; class JavascriptEnvironment; class NodeBindings; +class NodeDebugger; class AtomBrowserMainParts : public brightray::BrowserMainParts { public: @@ -57,6 +58,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { scoped_ptr js_env_; scoped_ptr node_bindings_; scoped_ptr atom_bindings_; + scoped_ptr node_debugger_; base::Timer gc_timer_; diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc new file mode 100644 index 000000000000..4e2302763c7e --- /dev/null +++ b/atom/browser/node_debugger.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/node_debugger.h" + +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" +#include "net/socket/tcp_listen_socket.h" + +#include "atom/common/node_includes.h" + +namespace atom { + +namespace { + +// NodeDebugger is stored in Isolate's data, slots 0, 1, 3 have already been +// taken by gin, blink and node, using 2 is a safe option for now. +const int kIsolateSlot = 2; + +const char* kContentLength = "Content-Length"; + +} // namespace + +NodeDebugger::NodeDebugger(v8::Isolate* isolate) + : isolate_(isolate), + thread_("NodeDebugger"), + content_length_(-1), + weak_factory_(this) { + bool use_debug_agent = false; + int port = 5858; + bool wait_for_connection = false; + + std::string port_str; + base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch("debug")) { + use_debug_agent = true; + port_str = cmd->GetSwitchValueASCII("debug"); + } + if (cmd->HasSwitch("debug-brk")) { + use_debug_agent = true; + wait_for_connection = true; + port_str = cmd->GetSwitchValueASCII("debug-brk"); + } + + if (use_debug_agent) { + if (!port_str.empty()) + base::StringToInt(port_str, &port); + + isolate_->SetData(kIsolateSlot, this); + v8::Debug::SetMessageHandler(DebugMessageHandler); + + if (wait_for_connection) + v8::Debug::DebugBreak(isolate_); + + // Start a new IO thread. + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + if (!thread_.StartWithOptions(options)) { + LOG(ERROR) << "Unable to start debugger thread"; + return; + } + + // Start the server in new IO thread. + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&NodeDebugger::StartServer, weak_factory_.GetWeakPtr(), + port)); + } +} + +NodeDebugger::~NodeDebugger() { + thread_.Stop(); +} + +bool NodeDebugger::IsRunning() const { + return thread_.IsRunning(); +} + +void NodeDebugger::StartServer(int port) { + server_ = net::TCPListenSocket::CreateAndListen("127.0.0.1", port, this); + if (!server_) { + LOG(ERROR) << "Cannot start debugger server"; + return; + } +} + +void NodeDebugger::CloseSession() { + accepted_socket_.reset(); +} + +void NodeDebugger::OnMessage(const std::string& message) { + if (message.find("\"type\":\"request\",\"command\":\"disconnect\"}") != + std::string::npos) + CloseSession(); + + base::string16 message16 = base::UTF8ToUTF16(message); + v8::Debug::SendCommand( + isolate_, + reinterpret_cast(message16.data()), message16.size()); + + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&v8::Debug::ProcessDebugMessages)); +} + +void NodeDebugger::SendMessage(const std::string& message) { + if (accepted_socket_) { + std::string header = base::StringPrintf( + "%s: %d\r\n\r\n", kContentLength, static_cast(message.size())); + accepted_socket_->Send(header); + accepted_socket_->Send(message); + } +} + +void NodeDebugger::SendConnectMessage() { + accepted_socket_->Send(base::StringPrintf( + "Type: connect\r\n" + "V8-Version: %s\r\n" + "Protocol-Version: 1\r\n" + "Embedding-Host: %s\r\n" + "%s: 0\r\n", + v8::V8::GetVersion(), ATOM_PRODUCT_NAME, kContentLength), true); +} + +// static +void NodeDebugger::DebugMessageHandler(const v8::Debug::Message& message) { + NodeDebugger* self = static_cast( + message.GetIsolate()->GetData(kIsolateSlot)); + + if (self) { + std::string message8(*v8::String::Utf8Value(message.GetJSON())); + self->thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&NodeDebugger::SendMessage, self->weak_factory_.GetWeakPtr(), + message8)); + } +} + +void NodeDebugger::DidAccept(net::StreamListenSocket* server, + scoped_ptr socket) { + // Only accept one session. + if (accepted_socket_) { + socket->Send(std::string("Remote debugging session already active"), true); + return; + } + + accepted_socket_ = socket.Pass(); + SendConnectMessage(); +} + +void NodeDebugger::DidRead(net::StreamListenSocket* socket, + const char* data, + int len) { + buffer_.append(data, len); + + do { + if (buffer_.size() == 0) + return; + + // Read the "Content-Length" header. + if (content_length_ < 0) { + size_t pos = buffer_.find("\r\n\r\n"); + if (pos == std::string::npos) + return; + + // We can be sure that the header is "Content-Length: xxx\r\n". + std::string content_length = buffer_.substr(16, pos - 16); + if (!base::StringToInt(content_length, &content_length_)) { + DidClose(accepted_socket_.get()); + return; + } + + // Strip header from buffer. + buffer_ = buffer_.substr(pos + 4); + } + + // Read the message. + if (buffer_.size() >= static_cast(content_length_)) { + std::string message = buffer_.substr(0, content_length_); + buffer_ = buffer_.substr(content_length_); + + OnMessage(message); + + // Get ready for next message. + content_length_ = -1; + } + } while (true); +} + +void NodeDebugger::DidClose(net::StreamListenSocket* socket) { + // If we lost the connection, then simulate a disconnect msg: + OnMessage("{\"seq\":1,\"type\":\"request\",\"command\":\"disconnect\"}"); +} + +} // namespace atom diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h new file mode 100644 index 000000000000..6ee5b1e20688 --- /dev/null +++ b/atom/browser/node_debugger.h @@ -0,0 +1,59 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NODE_DEBUGGER_H_ +#define ATOM_BROWSER_NODE_DEBUGGER_H_ + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread.h" +#include "net/socket/stream_listen_socket.h" +#include "v8/include/v8-debug.h" + +namespace atom { + +// Add support for node's "--debug" switch. +class NodeDebugger : public net::StreamListenSocket::Delegate { + public: + explicit NodeDebugger(v8::Isolate* isolate); + virtual ~NodeDebugger(); + + bool IsRunning() const; + + private: + void StartServer(int port); + void CloseSession(); + void OnMessage(const std::string& message); + void SendMessage(const std::string& message); + void SendConnectMessage(); + + static void DebugMessageHandler(const v8::Debug::Message& message); + + // net::StreamListenSocket::Delegate: + void DidAccept(net::StreamListenSocket* server, + scoped_ptr socket) override; + void DidRead(net::StreamListenSocket* socket, + const char* data, + int len) override; + void DidClose(net::StreamListenSocket* socket) override; + + v8::Isolate* isolate_; + + base::Thread thread_; + scoped_ptr server_; + scoped_ptr accepted_socket_; + + std::string buffer_; + int content_length_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NodeDebugger); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NODE_DEBUGGER_H_ diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 304b6a711a5c..827033bbccbc 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -180,14 +180,9 @@ node::Environment* NodeBindings::CreateEnvironment( void NodeBindings::LoadEnvironment(node::Environment* env) { node::node_isolate = env->isolate(); - if (node::use_debug_agent) - node::StartDebug(env, node::debug_wait_connect); node::LoadEnvironment(env); - if (node::use_debug_agent) - node::EnableDebug(env); - mate::EmitEvent(env->isolate(), env->process_object(), "loaded"); } diff --git a/filenames.gypi b/filenames.gypi index 260c9a4c8a17..b4015921e809 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -164,6 +164,8 @@ 'atom/browser/net/url_request_buffer_job.h', 'atom/browser/net/url_request_fetch_job.cc', 'atom/browser/net/url_request_fetch_job.h', + 'atom/browser/node_debugger.cc', + 'atom/browser/node_debugger.h', 'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', From e7791a5486f7f6f44e27995102d345665b5e9596 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 27 Aug 2015 12:59:54 +0800 Subject: [PATCH 2/5] Import the TCPListenSocket removed by Chromium --- atom/browser/node_debugger.cc | 14 +- atom/browser/node_debugger.h | 19 +- .../stream_listen_socket.cc | 330 ++++++++++++++++++ .../stream_listen_socket.h | 151 ++++++++ .../embedded_test_server/tcp_listen_socket.cc | 118 +++++++ .../embedded_test_server/tcp_listen_socket.h | 55 +++ filenames.gypi | 4 + 7 files changed, 676 insertions(+), 15 deletions(-) create mode 100644 chromium_src/net/test/embedded_test_server/stream_listen_socket.cc create mode 100644 chromium_src/net/test/embedded_test_server/stream_listen_socket.h create mode 100644 chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc create mode 100644 chromium_src/net/test/embedded_test_server/tcp_listen_socket.h diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index 4e2302763c7e..beaf682a8d4b 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -12,7 +12,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" -#include "net/socket/tcp_listen_socket.h" +#include "net/test/embedded_test_server/tcp_listen_socket.h" #include "atom/common/node_includes.h" @@ -84,7 +84,8 @@ bool NodeDebugger::IsRunning() const { } void NodeDebugger::StartServer(int port) { - server_ = net::TCPListenSocket::CreateAndListen("127.0.0.1", port, this); + server_ = net::test_server::TCPListenSocket::CreateAndListen( + "127.0.0.1", port, this); if (!server_) { LOG(ERROR) << "Cannot start debugger server"; return; @@ -143,8 +144,9 @@ void NodeDebugger::DebugMessageHandler(const v8::Debug::Message& message) { } } -void NodeDebugger::DidAccept(net::StreamListenSocket* server, - scoped_ptr socket) { +void NodeDebugger::DidAccept( + net::test_server::StreamListenSocket* server, + scoped_ptr socket) { // Only accept one session. if (accepted_socket_) { socket->Send(std::string("Remote debugging session already active"), true); @@ -155,7 +157,7 @@ void NodeDebugger::DidAccept(net::StreamListenSocket* server, SendConnectMessage(); } -void NodeDebugger::DidRead(net::StreamListenSocket* socket, +void NodeDebugger::DidRead(net::test_server::StreamListenSocket* socket, const char* data, int len) { buffer_.append(data, len); @@ -194,7 +196,7 @@ void NodeDebugger::DidRead(net::StreamListenSocket* socket, } while (true); } -void NodeDebugger::DidClose(net::StreamListenSocket* socket) { +void NodeDebugger::DidClose(net::test_server::StreamListenSocket* socket) { // If we lost the connection, then simulate a disconnect msg: OnMessage("{\"seq\":1,\"type\":\"request\",\"command\":\"disconnect\"}"); } diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h index 6ee5b1e20688..bf77cb72da64 100644 --- a/atom/browser/node_debugger.h +++ b/atom/browser/node_debugger.h @@ -10,13 +10,13 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/threading/thread.h" -#include "net/socket/stream_listen_socket.h" +#include "net/test/embedded_test_server/stream_listen_socket.h" #include "v8/include/v8-debug.h" namespace atom { // Add support for node's "--debug" switch. -class NodeDebugger : public net::StreamListenSocket::Delegate { +class NodeDebugger : public net::test_server::StreamListenSocket::Delegate { public: explicit NodeDebugger(v8::Isolate* isolate); virtual ~NodeDebugger(); @@ -32,19 +32,20 @@ class NodeDebugger : public net::StreamListenSocket::Delegate { static void DebugMessageHandler(const v8::Debug::Message& message); - // net::StreamListenSocket::Delegate: - void DidAccept(net::StreamListenSocket* server, - scoped_ptr socket) override; - void DidRead(net::StreamListenSocket* socket, + // net::test_server::StreamListenSocket::Delegate: + void DidAccept( + net::test_server::StreamListenSocket* server, + scoped_ptr socket) override; + void DidRead(net::test_server::StreamListenSocket* socket, const char* data, int len) override; - void DidClose(net::StreamListenSocket* socket) override; + void DidClose(net::test_server::StreamListenSocket* socket) override; v8::Isolate* isolate_; base::Thread thread_; - scoped_ptr server_; - scoped_ptr accepted_socket_; + scoped_ptr server_; + scoped_ptr accepted_socket_; std::string buffer_; int content_length_; diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc new file mode 100644 index 000000000000..1056983a8e2f --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2012 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 "net/test/embedded_test_server/stream_listen_socket.h" + +#if defined(OS_WIN) +// winsock2.h must be included first in order to ensure it is included before +// windows.h. +#include +#elif defined(OS_POSIX) +#include +#include +#include +#include +#include +#include "net/base/net_errors.h" +#endif + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/sys_byteorder.h" +#include "base/threading/platform_thread.h" +#include "build/build_config.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/socket/socket_descriptor.h" + +using std::string; + +#if defined(OS_WIN) +typedef int socklen_t; +#endif // defined(OS_WIN) + +namespace net { + +namespace test_server { + +namespace { + +const int kReadBufSize = 4096; + +} // namespace + +#if defined(OS_WIN) +const int StreamListenSocket::kSocketError = SOCKET_ERROR; +#elif defined(OS_POSIX) +const int StreamListenSocket::kSocketError = -1; +#endif + +StreamListenSocket::StreamListenSocket(SocketDescriptor s, + StreamListenSocket::Delegate* del) + : socket_delegate_(del), + socket_(s), + reads_paused_(false), + has_pending_reads_(false) { +#if defined(OS_WIN) + socket_event_ = WSACreateEvent(); + // TODO(ibrar): error handling in case of socket_event_ == WSA_INVALID_EVENT. + WatchSocket(NOT_WAITING); +#elif defined(OS_POSIX) + wait_state_ = NOT_WAITING; +#endif +} + +StreamListenSocket::~StreamListenSocket() { + CloseSocket(); +#if defined(OS_WIN) + if (socket_event_) { + WSACloseEvent(socket_event_); + socket_event_ = WSA_INVALID_EVENT; + } +#endif +} + +void StreamListenSocket::Send(const char* bytes, + int len, + bool append_linefeed) { + SendInternal(bytes, len); + if (append_linefeed) + SendInternal("\r\n", 2); +} + +void StreamListenSocket::Send(const string& str, bool append_linefeed) { + Send(str.data(), static_cast(str.length()), append_linefeed); +} + +int StreamListenSocket::GetLocalAddress(IPEndPoint* address) const { + SockaddrStorage storage; + if (getsockname(socket_, storage.addr, &storage.addr_len)) { +#if defined(OS_WIN) + int err = WSAGetLastError(); +#else + int err = errno; +#endif + return MapSystemError(err); + } + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + return OK; +} + +int StreamListenSocket::GetPeerAddress(IPEndPoint* address) const { + SockaddrStorage storage; + if (getpeername(socket_, storage.addr, &storage.addr_len)) { +#if defined(OS_WIN) + int err = WSAGetLastError(); +#else + int err = errno; +#endif + return MapSystemError(err); + } + + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + + return OK; +} + +SocketDescriptor StreamListenSocket::AcceptSocket() { + SocketDescriptor conn = HANDLE_EINTR(accept(socket_, NULL, NULL)); + if (conn == kInvalidSocket) + LOG(ERROR) << "Error accepting connection."; + else + SetNonBlocking(conn); + return conn; +} + +void StreamListenSocket::SendInternal(const char* bytes, int len) { + char* send_buf = const_cast(bytes); + int len_left = len; + while (true) { + int sent = HANDLE_EINTR(send(socket_, send_buf, len_left, 0)); + if (sent == len_left) { // A shortcut to avoid extraneous checks. + break; + } + if (sent == kSocketError) { +#if defined(OS_WIN) + if (WSAGetLastError() != WSAEWOULDBLOCK) { + LOG(ERROR) << "send failed: WSAGetLastError()==" << WSAGetLastError(); +#elif defined(OS_POSIX) + if (errno != EWOULDBLOCK && errno != EAGAIN) { + LOG(ERROR) << "send failed: errno==" << errno; +#endif + break; + } + // Otherwise we would block, and now we have to wait for a retry. + // Fall through to PlatformThread::YieldCurrentThread() + } else { + // sent != len_left according to the shortcut above. + // Shift the buffer start and send the remainder after a short while. + send_buf += sent; + len_left -= sent; + } + base::PlatformThread::YieldCurrentThread(); + } +} + +void StreamListenSocket::Listen() { + int backlog = 10; // TODO(erikkay): maybe don't allow any backlog? + if (listen(socket_, backlog) == -1) { + // TODO(erikkay): error handling. + LOG(ERROR) << "Could not listen on socket."; + return; + } +#if defined(OS_POSIX) + WatchSocket(WAITING_ACCEPT); +#endif +} + +void StreamListenSocket::Read() { + char buf[kReadBufSize + 1]; // +1 for null termination. + int len; + do { + len = HANDLE_EINTR(recv(socket_, buf, kReadBufSize, 0)); + if (len == kSocketError) { +#if defined(OS_WIN) + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { +#elif defined(OS_POSIX) + if (errno == EWOULDBLOCK || errno == EAGAIN) { +#endif + break; + } else { + // TODO(ibrar): some error handling required here. + break; + } + } else if (len == 0) { +#if defined(OS_POSIX) + // In Windows, Close() is called by OnObjectSignaled. In POSIX, we need + // to call it here. + Close(); +#endif + } else { + // TODO(ibrar): maybe change DidRead to take a length instead. + DCHECK_GT(len, 0); + DCHECK_LE(len, kReadBufSize); + buf[len] = 0; // Already create a buffer with +1 length. + socket_delegate_->DidRead(this, buf, len); + } + } while (len == kReadBufSize); +} + +void StreamListenSocket::Close() { +#if defined(OS_POSIX) + if (wait_state_ == NOT_WAITING) + return; + wait_state_ = NOT_WAITING; +#endif + UnwatchSocket(); + socket_delegate_->DidClose(this); +} + +void StreamListenSocket::CloseSocket() { + if (socket_ != kInvalidSocket) { + UnwatchSocket(); +#if defined(OS_WIN) + closesocket(socket_); +#elif defined(OS_POSIX) + close(socket_); +#endif + } +} + +void StreamListenSocket::WatchSocket(WaitState state) { +#if defined(OS_WIN) + WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ); + watcher_.StartWatching(socket_event_, this); +#elif defined(OS_POSIX) + // Implicitly calls StartWatchingFileDescriptor(). + base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this); + wait_state_ = state; +#endif +} + +void StreamListenSocket::UnwatchSocket() { +#if defined(OS_WIN) + watcher_.StopWatching(); +#elif defined(OS_POSIX) + watcher_.StopWatchingFileDescriptor(); +#endif +} + +// TODO(ibrar): We can add these functions into OS dependent files. +#if defined(OS_WIN) +// MessageLoop watcher callback. +void StreamListenSocket::OnObjectSignaled(HANDLE object) { + WSANETWORKEVENTS ev; + if (kSocketError == WSAEnumNetworkEvents(socket_, socket_event_, &ev)) { + // TODO + return; + } + + // If both FD_CLOSE and FD_READ are set we only call Read(). + // This will cause OnObjectSignaled to be called immediately again + // unless this socket is destroyed in Read(). + if ((ev.lNetworkEvents & (FD_CLOSE | FD_READ)) == FD_CLOSE) { + Close(); + // Close might have deleted this object. We should return immediately. + return; + } + // The object was reset by WSAEnumNetworkEvents. Watch for the next signal. + watcher_.StartWatching(object, this); + + if (ev.lNetworkEvents == 0) { + // Occasionally the event is set even though there is no new data. + // The net seems to think that this is ignorable. + return; + } + if (ev.lNetworkEvents & FD_ACCEPT) { + Accept(); + } + if (ev.lNetworkEvents & FD_READ) { + if (reads_paused_) { + has_pending_reads_ = true; + } else { + Read(); + // Read might have deleted this object. We should return immediately. + } + } +} +#elif defined(OS_POSIX) +void StreamListenSocket::OnFileCanReadWithoutBlocking(int fd) { + switch (wait_state_) { + case WAITING_ACCEPT: + Accept(); + break; + case WAITING_READ: + if (reads_paused_) { + has_pending_reads_ = true; + } else { + Read(); + } + break; + default: + // Close() is called by Read() in the Linux case. + NOTREACHED(); + break; + } +} + +void StreamListenSocket::OnFileCanWriteWithoutBlocking(int fd) { + // MessagePumpLibevent callback, we don't listen for write events + // so we shouldn't ever reach here. + NOTREACHED(); +} + +#endif + +void StreamListenSocket::PauseReads() { + DCHECK(!reads_paused_); + reads_paused_ = true; +} + +void StreamListenSocket::ResumeReads() { + DCHECK(reads_paused_); + reads_paused_ = false; + if (has_pending_reads_) { + has_pending_reads_ = false; + Read(); + } +} + +} // namespace test_server + +} // namespace net diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.h b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h new file mode 100644 index 000000000000..02a8b9827a2e --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h @@ -0,0 +1,151 @@ +// Copyright (c) 2012 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. + +// Stream-based listen socket implementation that handles reading and writing +// to the socket, but does not handle creating the socket nor connecting +// sockets, which are handled by subclasses on creation and in Accept, +// respectively. + +// StreamListenSocket handles IO asynchronously in the specified MessageLoop. +// This class is NOT thread safe. It uses WSAEVENT handles to monitor activity +// in a given MessageLoop. This means that callbacks will happen in that loop's +// thread always and that all other methods (including constructor and +// destructor) should also be called from the same thread. + +#ifndef NET_TEST_EMBEDDED_TEST_SERVER_STREAM_LISTEN_SOCKET_H_ +#define NET_TEST_EMBEDDED_TEST_SERVER_STREAM_LISTEN_SOCKET_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif +#include +#if defined(OS_WIN) +#include "base/win/object_watcher.h" +#elif defined(OS_POSIX) +#include "base/message_loop/message_loop.h" +#endif + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" +#include "net/socket/socket_descriptor.h" + +namespace net { + +class IPEndPoint; + +namespace test_server { + +class StreamListenSocket : +#if defined(OS_WIN) + public base::win::ObjectWatcher::Delegate { +#elif defined(OS_POSIX) + public base::MessageLoopForIO::Watcher { +#endif + + public: + ~StreamListenSocket() override; + + // TODO(erikkay): this delegate should really be split into two parts + // to split up the listener from the connected socket. Perhaps this class + // should be split up similarly. + class Delegate { + public: + // |server| is the original listening Socket, connection is the new + // Socket that was created. + virtual void DidAccept(StreamListenSocket* server, + scoped_ptr connection) = 0; + virtual void DidRead(StreamListenSocket* connection, + const char* data, + int len) = 0; + virtual void DidClose(StreamListenSocket* sock) = 0; + + protected: + virtual ~Delegate() {} + }; + + // Send data to the socket. + void Send(const char* bytes, int len, bool append_linefeed = false); + void Send(const std::string& str, bool append_linefeed = false); + + // Copies the local address to |address|. Returns a network error code. + // This method is virtual to support unit testing. + virtual int GetLocalAddress(IPEndPoint* address) const; + // Copies the peer address to |address|. Returns a network error code. + // This method is virtual to support unit testing. + virtual int GetPeerAddress(IPEndPoint* address) const; + + static const int kSocketError; + + protected: + enum WaitState { NOT_WAITING = 0, WAITING_ACCEPT = 1, WAITING_READ = 2 }; + + StreamListenSocket(SocketDescriptor s, Delegate* del); + + SocketDescriptor AcceptSocket(); + virtual void Accept() = 0; + + void Listen(); + void Read(); + void Close(); + void CloseSocket(); + + // Pass any value in case of Windows, because in Windows + // we are not using state. + void WatchSocket(WaitState state); + void UnwatchSocket(); + + Delegate* const socket_delegate_; + + private: + friend class TransportClientSocketTest; + + void SendInternal(const char* bytes, int len); + +#if defined(OS_WIN) + // ObjectWatcher delegate. + void OnObjectSignaled(HANDLE object) override; + base::win::ObjectWatcher watcher_; + HANDLE socket_event_; +#elif defined(OS_POSIX) + // Called by MessagePumpLibevent when the socket is ready to do I/O. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override; + WaitState wait_state_; + // The socket's libevent wrapper. + base::MessageLoopForIO::FileDescriptorWatcher watcher_; +#endif + + // NOTE: This is for unit test use only! + // Pause/Resume calling Read(). Note that ResumeReads() will also call + // Read() if there is anything to read. + void PauseReads(); + void ResumeReads(); + + const SocketDescriptor socket_; + bool reads_paused_; + bool has_pending_reads_; + + DISALLOW_COPY_AND_ASSIGN(StreamListenSocket); +}; + +// Abstract factory that must be subclassed for each subclass of +// StreamListenSocket. +class StreamListenSocketFactory { + public: + virtual ~StreamListenSocketFactory() {} + + // Returns a new instance of StreamListenSocket or NULL if an error occurred. + virtual scoped_ptr CreateAndListen( + StreamListenSocket::Delegate* delegate) const = 0; +}; + +} // namespace test_server + +} // namespace net + +#endif // NET_TEST_EMBEDDED_TEST_SERVER_STREAM_LISTEN_SOCKET_H_ diff --git a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc new file mode 100644 index 000000000000..418f34592127 --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2012 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 "net/test/embedded_test_server/tcp_listen_socket.h" + +#if defined(OS_WIN) +// winsock2.h must be included first in order to ensure it is included before +// windows.h. +#include +#elif defined(OS_POSIX) +#include +#include +#include +#include +#include +#include "net/base/net_errors.h" +#endif + +#include "base/logging.h" +#include "base/sys_byteorder.h" +#include "base/threading/platform_thread.h" +#include "build/build_config.h" +#include "net/base/net_util.h" +#include "net/base/winsock_init.h" +#include "net/socket/socket_descriptor.h" + +using std::string; + +namespace net { + +namespace test_server { + +// static +scoped_ptr TCPListenSocket::CreateAndListen( + const string& ip, + uint16 port, + StreamListenSocket::Delegate* del) { + SocketDescriptor s = CreateAndBind(ip, port); + if (s == kInvalidSocket) + return scoped_ptr(); + scoped_ptr sock(new TCPListenSocket(s, del)); + sock->Listen(); + return sock.Pass(); +} + +TCPListenSocket::TCPListenSocket(SocketDescriptor s, + StreamListenSocket::Delegate* del) + : StreamListenSocket(s, del) { +} + +TCPListenSocket::~TCPListenSocket() { +} + +SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, uint16 port) { + SocketDescriptor s = CreatePlatformSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s != kInvalidSocket) { +#if defined(OS_POSIX) + // Allow rapid reuse. + static const int kOn = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); +#endif + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip.c_str()); + addr.sin_port = base::HostToNet16(port); + if (bind(s, reinterpret_cast(&addr), sizeof(addr))) { +#if defined(OS_WIN) + closesocket(s); +#elif defined(OS_POSIX) + close(s); +#endif + LOG(ERROR) << "Could not bind socket to " << ip << ":" << port; + s = kInvalidSocket; + } + } + return s; +} + +SocketDescriptor TCPListenSocket::CreateAndBindAnyPort(const string& ip, + uint16* port) { + SocketDescriptor s = CreateAndBind(ip, 0); + if (s == kInvalidSocket) + return kInvalidSocket; + sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + bool failed = getsockname(s, reinterpret_cast(&addr), + &addr_size) != 0; + if (addr_size != sizeof(addr)) + failed = true; + if (failed) { + LOG(ERROR) << "Could not determine bound port, getsockname() failed"; +#if defined(OS_WIN) + closesocket(s); +#elif defined(OS_POSIX) + close(s); +#endif + return kInvalidSocket; + } + *port = base::NetToHost16(addr.sin_port); + return s; +} + +void TCPListenSocket::Accept() { + SocketDescriptor conn = AcceptSocket(); + if (conn == kInvalidSocket) + return; + scoped_ptr sock(new TCPListenSocket(conn, socket_delegate_)); +#if defined(OS_POSIX) + sock->WatchSocket(WAITING_READ); +#endif + socket_delegate_->DidAccept(this, sock.Pass()); +} + +} // namespace test_server + +} // namespace net diff --git a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h new file mode 100644 index 000000000000..12b3fa40745e --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 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. + +#ifndef NET_TEST_EMBEDDED_TEST_SERVER_TCP_LISTEN_SOCKET_H_ +#define NET_TEST_EMBEDDED_TEST_SERVER_TCP_LISTEN_SOCKET_H_ + +#include + +#include "base/basictypes.h" +#include "net/base/net_export.h" +#include "net/socket/socket_descriptor.h" +#include "net/test/embedded_test_server/stream_listen_socket.h" + +namespace net { + +namespace test_server { + +// Implements a TCP socket. +class TCPListenSocket : public StreamListenSocket { + public: + ~TCPListenSocket() override; + + // Listen on port for the specified IP address. Use 127.0.0.1 to only + // accept local connections. + static scoped_ptr CreateAndListen( + const std::string& ip, + uint16 port, + StreamListenSocket::Delegate* del); + + protected: + TCPListenSocket(SocketDescriptor s, StreamListenSocket::Delegate* del); + + // Implements StreamListenSocket::Accept. + void Accept() override; + + private: + friend class EmbeddedTestServer; + friend class TCPListenSocketTester; + + // Get raw TCP socket descriptor bound to ip:port. + static SocketDescriptor CreateAndBind(const std::string& ip, uint16 port); + + // Get raw TCP socket descriptor bound to ip and return port it is bound to. + static SocketDescriptor CreateAndBindAnyPort(const std::string& ip, + uint16* port); + + DISALLOW_COPY_AND_ASSIGN(TCPListenSocket); +}; + +} // namespace test_server + +} // namespace net + +#endif // NET_TEST_EMBEDDED_TEST_SERVER_TCP_LISTEN_SOCKET_H_ diff --git a/filenames.gypi b/filenames.gypi index b4015921e809..feb35789afe1 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -412,6 +412,10 @@ 'chromium_src/chrome/utility/utility_message_handler.h', 'chromium_src/library_loaders/libspeechd_loader.cc', 'chromium_src/library_loaders/libspeechd.h', + 'chromium_src/net/test/embedded_test_server/stream_listen_socket.cc', + 'chromium_src/net/test/embedded_test_server/stream_listen_socket.h', + 'chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc', + 'chromium_src/net/test/embedded_test_server/tcp_listen_socket.h', '<@(native_mate_files)', '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', ], From 573c959a75080ce8aec1e99713f2e3f7bfcb6ec5 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 27 Aug 2015 13:16:19 +0800 Subject: [PATCH 3/5] Use our debugger implementation in Node --- atom/app/node_main.cc | 19 +++++++++---------- atom/common/node_bindings.cc | 5 ----- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index 8823e583107c..356fe03e139d 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -5,15 +5,18 @@ #include "atom/app/node_main.h" #include "atom/browser/javascript_environment.h" +#include "atom/browser/node_debugger.h" +#include "atom/common/node_includes.h" +#include "base/command_line.h" #include "gin/array_buffer.h" #include "gin/public/isolate_holder.h" #include "gin/v8_initializer.h" -#include "atom/common/node_includes.h" - namespace atom { int NodeMain(int argc, char *argv[]) { + base::CommandLine::Init(argc, argv); + argv = uv_setup_args(argc, argv); int exec_argc; const char** exec_argv; @@ -31,17 +34,13 @@ int NodeMain(int argc, char *argv[]) { gin_env.isolate(), uv_default_loop(), gin_env.context(), argc, argv, exec_argc, exec_argv); - // Start debugger. - node::node_isolate = gin_env.isolate(); - if (node::use_debug_agent) - node::StartDebug(env, node::debug_wait_connect); + // Start our custom debugger implementation. + NodeDebugger node_debugger(gin_env.isolate()); + if (node_debugger.IsRunning()) + env->AssignToContext(v8::Debug::GetDebugContext()); node::LoadEnvironment(env); - // Enable debugger. - if (node::use_debug_agent) - node::EnableDebug(env); - bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 827033bbccbc..e6d59f2ed366 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -137,11 +137,6 @@ void NodeBindings::Initialize() { AtomCommandLine::InitializeFromCommandLine(); #endif - // Parse the debug args. - auto args = AtomCommandLine::argv(); - for (const std::string& arg : args) - node::ParseDebugOpt(arg.c_str()); - // Init node. // (we assume node::Init would not modify the parameters under embedded mode). node::Init(nullptr, nullptr, nullptr, nullptr); From 134ccb550cd0cab442ea3ef396dc0f39e7d7fc53 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 27 Aug 2015 13:30:04 +0800 Subject: [PATCH 4/5] Use libuv to wake up main thread --- atom/browser/node_debugger.cc | 11 ++++++++--- atom/browser/node_debugger.h | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index beaf682a8d4b..da5602a88137 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -59,6 +59,8 @@ NodeDebugger::NodeDebugger(v8::Isolate* isolate) if (wait_for_connection) v8::Debug::DebugBreak(isolate_); + uv_async_init(uv_default_loop(), &weak_up_ui_handle_, ProcessMessageInUI); + // Start a new IO thread. base::Thread::Options options; options.message_loop_type = base::MessageLoop::TYPE_IO; @@ -106,9 +108,7 @@ void NodeDebugger::OnMessage(const std::string& message) { isolate_, reinterpret_cast(message16.data()), message16.size()); - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&v8::Debug::ProcessDebugMessages)); + uv_async_send(&weak_up_ui_handle_); } void NodeDebugger::SendMessage(const std::string& message) { @@ -130,6 +130,11 @@ void NodeDebugger::SendConnectMessage() { v8::V8::GetVersion(), ATOM_PRODUCT_NAME, kContentLength), true); } +// static +void NodeDebugger::ProcessMessageInUI(uv_async_t* handle) { + v8::Debug::ProcessDebugMessages(); +} + // static void NodeDebugger::DebugMessageHandler(const v8::Debug::Message& message) { NodeDebugger* self = static_cast( diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h index bf77cb72da64..aedf7b2c0310 100644 --- a/atom/browser/node_debugger.h +++ b/atom/browser/node_debugger.h @@ -12,6 +12,7 @@ #include "base/threading/thread.h" #include "net/test/embedded_test_server/stream_listen_socket.h" #include "v8/include/v8-debug.h" +#include "vendor/node/deps/uv/include/uv.h" namespace atom { @@ -30,6 +31,8 @@ class NodeDebugger : public net::test_server::StreamListenSocket::Delegate { void SendMessage(const std::string& message); void SendConnectMessage(); + static void ProcessMessageInUI(uv_async_t* handle); + static void DebugMessageHandler(const v8::Debug::Message& message); // net::test_server::StreamListenSocket::Delegate: @@ -43,6 +46,8 @@ class NodeDebugger : public net::test_server::StreamListenSocket::Delegate { v8::Isolate* isolate_; + uv_async_t weak_up_ui_handle_; + base::Thread thread_; scoped_ptr server_; scoped_ptr accepted_socket_; From e432638b7dcf4f6160d48dd77848c0d6fe58fa95 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 27 Aug 2015 13:51:46 +0800 Subject: [PATCH 5/5] Update node: Remove unneeded exports --- atom/common/node_bindings.cc | 3 --- vendor/node | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index e6d59f2ed366..8c03f1459967 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -174,10 +174,7 @@ node::Environment* NodeBindings::CreateEnvironment( } void NodeBindings::LoadEnvironment(node::Environment* env) { - node::node_isolate = env->isolate(); - node::LoadEnvironment(env); - mate::EmitEvent(env->isolate(), env->process_object(), "loaded"); } diff --git a/vendor/node b/vendor/node index b9b6dd9f3fc0..8253eb682526 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit b9b6dd9f3fc095e66a3b89d3efd50f7c576da2c8 +Subproject commit 8253eb68252639db471090edb059eaa4fea4ce46