summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrpaquay@chromium.org <rpaquay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-18 03:49:15 +0000
committerrpaquay@chromium.org <rpaquay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-18 03:49:15 +0000
commit96a0c067d816576735856452608a03bd7e914504 (patch)
tree513fe241364a307f7c83963d05f028055a66ab76
parentb1d1ea068f578e933054c8573e985402ca014319 (diff)
downloadchromium_src-96a0c067d816576735856452608a03bd7e914504.zip
chromium_src-96a0c067d816576735856452608a03bd7e914504.tar.gz
chromium_src-96a0c067d816576735856452608a03bd7e914504.tar.bz2
sockets.tcpServer API implementation.
Implement a new API for TCP *server* sockets. This CL is similar to sockets.tcp (see https://codereview.chromium.org/24684002) and follows the same design pattern. BUG=165273 BUG=173241 Review URL: https://codereview.chromium.org/27076004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@229304 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/api/api_resource_manager.h2
-rw-r--r--chrome/browser/extensions/api/socket/tcp_socket.cc31
-rw-r--r--chrome/browser/extensions/api/socket/tcp_socket.h36
-rw-r--r--chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc298
-rw-r--r--chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h179
-rw-r--r--chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc94
-rw-r--r--chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc111
-rw-r--r--chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc198
-rw-r--r--chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h99
-rw-r--r--chrome/browser/extensions/extension_function_histogram_value.h8
-rw-r--r--chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc4
-rw-r--r--chrome/chrome_browser_extensions.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/extensions/api/_api_features.json5
-rw-r--r--chrome/common/extensions/api/api.gyp1
-rw-r--r--chrome/common/extensions/api/manifest_types.json12
-rw-r--r--chrome/common/extensions/api/sockets/sockets_handler.cc8
-rw-r--r--chrome/common/extensions/api/sockets_tcp_server.idl191
-rw-r--r--chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js134
-rw-r--r--chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json18
-rw-r--r--chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js24
-rw-r--r--chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json15
23 files changed, 1474 insertions, 0 deletions
diff --git a/chrome/browser/extensions/api/api_resource_manager.h b/chrome/browser/extensions/api/api_resource_manager.h
index d39c41b..28adefd 100644
--- a/chrome/browser/extensions/api/api_resource_manager.h
+++ b/chrome/browser/extensions/api/api_resource_manager.h
@@ -24,6 +24,7 @@
namespace extensions {
namespace api {
+class TCPServerSocketEventDispatcher;
class TCPSocketEventDispatcher;
class UDPSocketEventDispatcher;
}
@@ -153,6 +154,7 @@ class ApiResourceManager : public ProfileKeyedAPI,
}
private:
+ friend class api::TCPServerSocketEventDispatcher;
friend class api::TCPSocketEventDispatcher;
friend class api::UDPSocketEventDispatcher;
friend class ProfileKeyedAPIFactory<ApiResourceManager<T> >;
diff --git a/chrome/browser/extensions/api/socket/tcp_socket.cc b/chrome/browser/extensions/api/socket/tcp_socket.cc
index 8401dc26..4c0de16 100644
--- a/chrome/browser/extensions/api/socket/tcp_socket.cc
+++ b/chrome/browser/extensions/api/socket/tcp_socket.cc
@@ -28,6 +28,17 @@ ApiResourceManager<ResumableTCPSocket>::GetFactoryInstance() {
return &g_factory.Get();
}
+static base::LazyInstance<ProfileKeyedAPIFactory<
+ ApiResourceManager<ResumableTCPServerSocket> > >
+ g_server_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+ProfileKeyedAPIFactory<ApiResourceManager<ResumableTCPServerSocket> >*
+ApiResourceManager<ResumableTCPServerSocket>::GetFactoryInstance() {
+ return &g_server_factory.Get();
+}
+
TCPSocket::TCPSocket(const std::string& owner_extension_id)
: Socket(owner_extension_id),
socket_mode_(UNKNOWN) {
@@ -304,8 +315,28 @@ ResumableTCPSocket::ResumableTCPSocket(const std::string& owner_extension_id)
paused_(false) {
}
+ResumableTCPSocket::ResumableTCPSocket(net::TCPClientSocket* tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected)
+ : TCPSocket(tcp_client_socket, owner_extension_id, is_connected),
+ persistent_(false),
+ buffer_size_(0),
+ paused_(false) {
+}
+
bool ResumableTCPSocket::persistent() const {
return persistent_;
}
+ResumableTCPServerSocket::ResumableTCPServerSocket(
+ const std::string& owner_extension_id)
+ : TCPSocket(owner_extension_id),
+ persistent_(false),
+ paused_(false) {
+}
+
+bool ResumableTCPServerSocket::persistent() const {
+ return persistent_;
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/socket/tcp_socket.h b/chrome/browser/extensions/api/socket/tcp_socket.h
index c684824..d5b4844 100644
--- a/chrome/browser/extensions/api/socket/tcp_socket.h
+++ b/chrome/browser/extensions/api/socket/tcp_socket.h
@@ -102,10 +102,14 @@ class TCPSocket : public Socket {
class ResumableTCPSocket : public TCPSocket {
public:
explicit ResumableTCPSocket(const std::string& owner_extension_id);
+ explicit ResumableTCPSocket(net::TCPClientSocket* tcp_client_socket,
+ const std::string& owner_extension_id,
+ bool is_connected);
const std::string& name() const { return name_; }
void set_name(const std::string& name) { name_ = name; }
+ // Overriden from ApiResource
virtual bool persistent() const OVERRIDE;
void set_persistent(bool persistent) { persistent_ = persistent; }
@@ -133,6 +137,38 @@ class ResumableTCPSocket : public TCPSocket {
bool paused_;
};
+// TCP Socket instances from the "sockets.tcpServer" namespace. These are
+// regular socket objects with additional properties related to the behavior
+// defined in the "sockets.tcpServer" namespace.
+class ResumableTCPServerSocket : public TCPSocket {
+ public:
+ explicit ResumableTCPServerSocket(const std::string& owner_extension_id);
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ virtual bool persistent() const OVERRIDE;
+ void set_persistent(bool persistent) { persistent_ = persistent; }
+
+ bool paused() const { return paused_; }
+ void set_paused(bool paused) { paused_ = paused; }
+
+ private:
+ friend class ApiResourceManager<ResumableTCPServerSocket>;
+ static const char* service_name() {
+ return "ResumableTCPServerSocketManager";
+ }
+
+ // Application-defined string - see sockets_tcp_server.idl.
+ std::string name_;
+ // Flag indicating whether the socket is left open when the application is
+ // suspended - see sockets_tcp_server.idl.
+ bool persistent_;
+ // Flag indicating whether a connected socket blocks its peer from sending
+ // more data - see sockets_tcp_server.idl.
+ bool paused_;
+};
+
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKET_TCP_SOCKET_H_
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc
new file mode 100644
index 0000000..0827e29
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc
@@ -0,0 +1,298 @@
+// 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/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h"
+
+#include "chrome/browser/extensions/api/socket/tcp_socket.h"
+#include "chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h"
+#include "chrome/common/extensions/api/sockets/sockets_handler.h"
+#include "chrome/common/extensions/permissions/permissions_data.h"
+#include "chrome/common/extensions/permissions/socket_permission.h"
+#include "content/public/common/socket_permission_request.h"
+#include "net/base/net_errors.h"
+
+using content::SocketPermissionRequest;
+using extensions::ResumableTCPServerSocket;
+using extensions::api::sockets_tcp_server::SocketInfo;
+using extensions::api::sockets_tcp_server::SocketProperties;
+
+namespace {
+
+const char kSocketNotFoundError[] = "Socket not found";
+const char kPermissionError[] = "Does not have permission";
+const int kDefaultListenBacklog = SOMAXCONN;
+
+linked_ptr<SocketInfo> CreateSocketInfo(int socket_id,
+ ResumableTCPServerSocket* socket) {
+ linked_ptr<SocketInfo> socket_info(new SocketInfo());
+ // This represents what we know about the socket, and does not call through
+ // to the system.
+ socket_info->socket_id = socket_id;
+ if (!socket->name().empty()) {
+ socket_info->name.reset(new std::string(socket->name()));
+ }
+ socket_info->persistent = socket->persistent();
+ socket_info->paused = socket->paused();
+
+ // Grab the local address as known by the OS.
+ net::IPEndPoint localAddress;
+ if (socket->GetLocalAddress(&localAddress)) {
+ socket_info->local_address.reset(
+ new std::string(localAddress.ToStringWithoutPort()));
+ socket_info->local_port.reset(new int(localAddress.port()));
+ }
+
+ return socket_info;
+}
+
+void SetSocketProperties(ResumableTCPServerSocket* socket,
+ SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+}
+
+} // namespace
+
+namespace extensions {
+namespace api {
+
+TCPServerSocketAsyncApiFunction::~TCPServerSocketAsyncApiFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+ TCPServerSocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableTCPServerSocket>()).Pass();
+}
+
+ResumableTCPServerSocket* TCPServerSocketAsyncApiFunction::GetTcpSocket(
+ int socket_id) {
+ return static_cast<ResumableTCPServerSocket*>(GetSocket(socket_id));
+}
+
+SocketsTcpServerCreateFunction::SocketsTcpServerCreateFunction() {}
+
+SocketsTcpServerCreateFunction::~SocketsTcpServerCreateFunction() {}
+
+bool SocketsTcpServerCreateFunction::Prepare() {
+ params_ = sockets_tcp_server::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerCreateFunction::Work() {
+ ResumableTCPServerSocket* socket =
+ new ResumableTCPServerSocket(extension_->id());
+
+ sockets_tcp_server::SocketProperties* properties =
+ params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ sockets_tcp_server::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = sockets_tcp_server::Create::Results::Create(create_info);
+}
+
+SocketsTcpServerUpdateFunction::SocketsTcpServerUpdateFunction() {}
+
+SocketsTcpServerUpdateFunction::~SocketsTcpServerUpdateFunction() {}
+
+bool SocketsTcpServerUpdateFunction::Prepare() {
+ params_ = sockets_tcp_server::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerUpdateFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = sockets_tcp_server::Update::Results::Create();
+}
+
+SocketsTcpServerSetPausedFunction::SocketsTcpServerSetPausedFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsTcpServerSetPausedFunction::~SocketsTcpServerSetPausedFunction() {}
+
+bool SocketsTcpServerSetPausedFunction::Prepare() {
+ params_ = api::sockets_tcp_server::SetPaused::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = TCPServerSocketEventDispatcher::Get(profile());
+ DCHECK(socket_event_dispatcher_) << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "TCPServerSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsTcpServerSetPausedFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ if (socket->paused() != params_->paused) {
+ socket->set_paused(params_->paused);
+ if (socket->IsConnected() && !params_->paused) {
+ socket_event_dispatcher_->OnServerSocketResume(extension_->id(),
+ params_->socket_id);
+ }
+ }
+
+ results_ = sockets_tcp_server::SetPaused::Results::Create();
+}
+
+SocketsTcpServerListenFunction::SocketsTcpServerListenFunction()
+ : socket_event_dispatcher_(NULL) {}
+
+SocketsTcpServerListenFunction::~SocketsTcpServerListenFunction() {}
+
+bool SocketsTcpServerListenFunction::Prepare() {
+ params_ = api::sockets_tcp_server::Listen::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+
+ socket_event_dispatcher_ = TCPServerSocketEventDispatcher::Get(profile());
+ DCHECK(socket_event_dispatcher_) << "There is no socket event dispatcher. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "TCPServerSocketEventDispatcher.";
+ return socket_event_dispatcher_ != NULL;
+}
+
+void SocketsTcpServerListenFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SocketPermissionRequest param(
+ SocketPermissionRequest::TCP_LISTEN,
+ params_->address,
+ params_->port);
+ if (!SocketsManifestData::CheckRequest(GetExtension(), param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ int net_result = socket->Listen(
+ params_->address,
+ params_->port,
+ params_->backlog.get() ? *params_->backlog.get() : kDefaultListenBacklog,
+ &error_);
+
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+
+
+ if (net_result == net::OK) {
+ socket_event_dispatcher_->OnServerSocketListen(extension_->id(),
+ params_->socket_id);
+ }
+
+ results_ = sockets_tcp_server::Listen::Results::Create(net_result);
+}
+
+SocketsTcpServerDisconnectFunction::SocketsTcpServerDisconnectFunction() {}
+
+SocketsTcpServerDisconnectFunction::~SocketsTcpServerDisconnectFunction() {}
+
+bool SocketsTcpServerDisconnectFunction::Prepare() {
+ params_ = sockets_tcp_server::Disconnect::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerDisconnectFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ socket->Disconnect();
+ results_ = sockets_tcp_server::Disconnect::Results::Create();
+}
+
+SocketsTcpServerCloseFunction::SocketsTcpServerCloseFunction() {}
+
+SocketsTcpServerCloseFunction::~SocketsTcpServerCloseFunction() {}
+
+bool SocketsTcpServerCloseFunction::Prepare() {
+ params_ = sockets_tcp_server::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerCloseFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ RemoveSocket(params_->socket_id);
+ results_ = sockets_tcp_server::Close::Results::Create();
+}
+
+SocketsTcpServerGetInfoFunction::SocketsTcpServerGetInfoFunction() {}
+
+SocketsTcpServerGetInfoFunction::~SocketsTcpServerGetInfoFunction() {}
+
+bool SocketsTcpServerGetInfoFunction::Prepare() {
+ params_ = sockets_tcp_server::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsTcpServerGetInfoFunction::Work() {
+ ResumableTCPServerSocket* socket = GetTcpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ linked_ptr<sockets_tcp_server::SocketInfo> socket_info =
+ CreateSocketInfo(params_->socket_id, socket);
+ results_ = sockets_tcp_server::GetInfo::Results::Create(*socket_info);
+}
+
+SocketsTcpServerGetSocketsFunction::SocketsTcpServerGetSocketsFunction() {}
+
+SocketsTcpServerGetSocketsFunction::~SocketsTcpServerGetSocketsFunction() {}
+
+bool SocketsTcpServerGetSocketsFunction::Prepare() {
+ return true;
+}
+
+void SocketsTcpServerGetSocketsFunction::Work() {
+ std::vector<linked_ptr<sockets_tcp_server::SocketInfo> > socket_infos;
+ base::hash_set<int>* resource_ids = GetSocketIds();
+ if (resource_ids != NULL) {
+ for (base::hash_set<int>::iterator it = resource_ids->begin();
+ it != resource_ids->end(); ++it) {
+ int socket_id = *it;
+ ResumableTCPServerSocket* socket = GetTcpSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = sockets_tcp_server::GetSockets::Results::Create(socket_infos);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h
new file mode 100644
index 0000000..439b857
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h
@@ -0,0 +1,179 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_
+
+#include "chrome/browser/extensions/api/socket/socket_api.h"
+#include "chrome/common/extensions/api/sockets_tcp_server.h"
+
+namespace extensions {
+class ResumableTCPServerSocket;
+}
+
+namespace extensions {
+namespace api {
+
+class TCPServerSocketAsyncApiFunction : public SocketAsyncApiFunction {
+ protected:
+ virtual ~TCPServerSocketAsyncApiFunction();
+
+ virtual scoped_ptr<SocketResourceManagerInterface>
+ CreateSocketResourceManager() OVERRIDE;
+
+ ResumableTCPServerSocket* GetTcpSocket(int socket_id);
+};
+
+class SocketsTcpServerCreateFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.create",
+ SOCKETS_TCP_SERVER_CREATE)
+
+ SocketsTcpServerCreateFunction();
+
+ protected:
+ virtual ~SocketsTcpServerCreateFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketsTcpServerUnitTest, Create);
+ scoped_ptr<sockets_tcp_server::Create::Params> params_;
+};
+
+class SocketsTcpServerUpdateFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.update",
+ SOCKETS_TCP_SERVER_UPDATE)
+
+ SocketsTcpServerUpdateFunction();
+
+ protected:
+ virtual ~SocketsTcpServerUpdateFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Update::Params> params_;
+};
+
+class SocketsTcpServerSetPausedFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.setPaused",
+ SOCKETS_TCP_SERVER_SETPAUSED)
+
+ SocketsTcpServerSetPausedFunction();
+
+ protected:
+ virtual ~SocketsTcpServerSetPausedFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_tcp_server::SetPaused::Params> params_;
+ TCPServerSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsTcpServerListenFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.listen",
+ SOCKETS_TCP_SERVER_LISTEN)
+
+ SocketsTcpServerListenFunction();
+
+ protected:
+ virtual ~SocketsTcpServerListenFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Listen::Params> params_;
+ TCPServerSocketEventDispatcher* socket_event_dispatcher_;
+};
+
+class SocketsTcpServerDisconnectFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.disconnect",
+ SOCKETS_TCP_SERVER_DISCONNECT)
+
+ SocketsTcpServerDisconnectFunction();
+
+ protected:
+ virtual ~SocketsTcpServerDisconnectFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Disconnect::Params> params_;
+};
+
+class SocketsTcpServerCloseFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.close",
+ SOCKETS_TCP_SERVER_CLOSE)
+
+ SocketsTcpServerCloseFunction();
+
+ protected:
+ virtual ~SocketsTcpServerCloseFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_tcp_server::Close::Params> params_;
+};
+
+class SocketsTcpServerGetInfoFunction : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.getInfo",
+ SOCKETS_TCP_SERVER_GETINFO)
+
+ SocketsTcpServerGetInfoFunction();
+
+ protected:
+ virtual ~SocketsTcpServerGetInfoFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_tcp_server::GetInfo::Params> params_;
+};
+
+class SocketsTcpServerGetSocketsFunction
+ : public TCPServerSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.tcpServer.getSockets",
+ SOCKETS_TCP_SERVER_GETSOCKETS)
+
+ SocketsTcpServerGetSocketsFunction();
+
+ protected:
+ virtual ~SocketsTcpServerGetSocketsFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_SOCKETS_TCP_SERVER_API_H_
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc
new file mode 100644
index 0000000..6ed0f9e
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc
@@ -0,0 +1,94 @@
+// 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 "base/values.h"
+#include "chrome/browser/browser_process_impl.h"
+#include "chrome/browser/extensions/api/api_function.h"
+#include "chrome/browser/extensions/api/api_resource_manager.h"
+#include "chrome/browser/extensions/api/socket/socket.h"
+#include "chrome/browser/extensions/api/socket/tcp_socket.h"
+#include "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace utils = extension_function_test_utils;
+
+namespace extensions {
+namespace api {
+
+static
+BrowserContextKeyedService* ApiResourceManagerTestFactory(
+ content::BrowserContext* profile) {
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+ return ApiResourceManager<ResumableTCPSocket>::
+ CreateApiResourceManagerForTest(static_cast<Profile*>(profile), id);
+}
+
+static
+BrowserContextKeyedService* ApiResourceManagerTestServerFactory(
+ content::BrowserContext* profile) {
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+ return ApiResourceManager<ResumableTCPServerSocket>::
+ CreateApiResourceManagerForTest(static_cast<Profile*>(profile), id);
+}
+
+class SocketsTcpServerUnitTest : public BrowserWithTestWindowTest {
+ public:
+ virtual void SetUp() {
+ BrowserWithTestWindowTest::SetUp();
+
+ ApiResourceManager<ResumableTCPSocket>::GetFactoryInstance()->
+ SetTestingFactoryAndUse(browser()->profile(),
+ ApiResourceManagerTestFactory);
+
+ ApiResourceManager<ResumableTCPServerSocket>::GetFactoryInstance()->
+ SetTestingFactoryAndUse(browser()->profile(),
+ ApiResourceManagerTestServerFactory);
+
+ extension_ = utils::CreateEmptyExtensionWithLocation(
+ extensions::Manifest::UNPACKED);
+ }
+
+ base::Value* RunFunctionWithExtension(
+ UIThreadExtensionFunction* function, const std::string& args) {
+ scoped_refptr<UIThreadExtensionFunction> delete_function(function);
+ function->set_extension(extension_.get());
+ return utils::RunFunctionAndReturnSingleResult(function, args, browser());
+ }
+
+ base::DictionaryValue* RunFunctionAndReturnDict(
+ UIThreadExtensionFunction* function, const std::string& args) {
+ base::Value* result = RunFunctionWithExtension(function, args);
+ return result ? utils::ToDictionary(result) : NULL;
+ }
+
+ protected:
+ scoped_refptr<extensions::Extension> extension_;
+};
+
+TEST_F(SocketsTcpServerUnitTest, Create) {
+ // Get BrowserThread
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+
+ // Create SocketCreateFunction and put it on BrowserThread
+ SocketsTcpServerCreateFunction *function =
+ new SocketsTcpServerCreateFunction();
+ function->set_work_thread_id(id);
+
+ // Run tests
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDict(
+ function, "[{\"persistent\": true, \"name\": \"foo\"}]"));
+ ASSERT_TRUE(result.get());
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc
new file mode 100644
index 0000000..6e36a8c
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc
@@ -0,0 +1,111 @@
+// 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 "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/extensions/api/dns/host_resolver_wrapper.h"
+#include "chrome/browser/extensions/api/dns/mock_host_resolver_creator.h"
+#include "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+using extensions::Extension;
+using extensions::api::SocketsTcpServerCreateFunction;
+
+namespace utils = extension_function_test_utils;
+
+namespace {
+
+// TODO(jschuh): Hanging plugin tests. crbug.com/244653
+#if defined(OS_WIN) && defined(ARCH_CPU_X86_64)
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+const std::string kHostname = "127.0.0.1";
+const int kPort = 8888;
+
+class SocketsTcpServerApiTest : public ExtensionApiTest {
+ public:
+ SocketsTcpServerApiTest() : resolver_event_(true, false),
+ resolver_creator_(
+ new extensions::MockHostResolverCreator()) {
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ extensions::HostResolverWrapper::GetInstance()->SetHostResolverForTesting(
+ resolver_creator_->CreateMockHostResolver());
+ }
+
+ virtual void CleanUpOnMainThread() OVERRIDE {
+ extensions::HostResolverWrapper::GetInstance()->
+ SetHostResolverForTesting(NULL);
+ resolver_creator_->DeleteMockHostResolver();
+ }
+
+ private:
+ base::WaitableEvent resolver_event_;
+
+ // The MockHostResolver asserts that it's used on the same thread on which
+ // it's created, which is actually a stronger rule than its real counterpart.
+ // But that's fine; it's good practice.
+ scoped_refptr<extensions::MockHostResolverCreator> resolver_creator_;
+};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPCreateGood) {
+ scoped_refptr<SocketsTcpServerCreateFunction>
+ socket_create_function(new SocketsTcpServerCreateFunction());
+ scoped_refptr<Extension> empty_extension(utils::CreateEmptyExtension());
+
+ socket_create_function->set_extension(empty_extension.get());
+ socket_create_function->set_has_callback(true);
+
+ scoped_ptr<base::Value> result(utils::RunFunctionAndReturnSingleResult(
+ socket_create_function.get(), "[]", browser(), utils::NONE));
+ ASSERT_EQ(base::Value::TYPE_DICTIONARY, result->GetType());
+ base::DictionaryValue *value =
+ static_cast<base::DictionaryValue*>(result.get());
+ int socketId = -1;
+ EXPECT_TRUE(value->GetInteger("socketId", &socketId));
+ ASSERT_TRUE(socketId > 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPServerExtension) {
+ base::FilePath path = test_data_dir_.AppendASCII("sockets_tcp_server/api");
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+ ExtensionTestMessageListener listener("info_please", true);
+ ASSERT_TRUE(LoadExtension(path));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("tcp_server:%s:%d", kHostname.c_str(), kPort));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsTcpServerApiTest, SocketTCPServerUnbindOnUnload) {
+ base::FilePath path = test_data_dir_.AppendASCII("sockets_tcp_server/unload");
+ ResultCatcher catcher;
+ const Extension* extension = LoadExtension(path);
+ ASSERT_TRUE(extension);
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+
+ UnloadExtension(extension->id());
+
+ ASSERT_TRUE(LoadExtension(path)) << message_;
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
new file mode 100644
index 0000000..d849e8c
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc
@@ -0,0 +1,198 @@
+// 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/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h"
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/api/socket/tcp_socket.h"
+#include "chrome/browser/extensions/event_router.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+
+using content::BrowserThread;
+
+static
+ base::LazyInstance<ProfileKeyedAPIFactory<TCPServerSocketEventDispatcher> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+ProfileKeyedAPIFactory<TCPServerSocketEventDispatcher>*
+ TCPServerSocketEventDispatcher::GetFactoryInstance() {
+ return &g_factory.Get();
+}
+
+// static
+TCPServerSocketEventDispatcher* TCPServerSocketEventDispatcher::Get(
+ Profile* profile) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ return ProfileKeyedAPIFactory<TCPServerSocketEventDispatcher>::GetForProfile(
+ profile);
+}
+
+TCPServerSocketEventDispatcher::TCPServerSocketEventDispatcher(Profile* profile)
+ : thread_id_(Socket::kThreadId),
+ profile_(profile) {
+ ApiResourceManager<ResumableTCPServerSocket>* server_manager =
+ ApiResourceManager<ResumableTCPServerSocket>::Get(profile);
+ DCHECK(server_manager) << "There is no server socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableTCPServerSocket>.";
+ server_sockets_ = server_manager->data_;
+
+ ApiResourceManager<ResumableTCPSocket>* client_manager =
+ ApiResourceManager<ResumableTCPSocket>::Get(profile);
+ DCHECK(client_manager) << "There is no client socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableTCPSocket>.";
+ client_sockets_ = client_manager->data_;
+}
+
+TCPServerSocketEventDispatcher::~TCPServerSocketEventDispatcher() {}
+
+TCPServerSocketEventDispatcher::AcceptParams::AcceptParams() {}
+
+TCPServerSocketEventDispatcher::AcceptParams::~AcceptParams() {}
+
+void TCPServerSocketEventDispatcher::OnServerSocketListen(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(BrowserThread::CurrentlyOn(thread_id_));
+
+ StartSocketAccept(extension_id, socket_id);
+}
+
+void TCPServerSocketEventDispatcher::OnServerSocketResume(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(BrowserThread::CurrentlyOn(thread_id_));
+
+ StartSocketAccept(extension_id, socket_id);
+}
+
+void TCPServerSocketEventDispatcher::StartSocketAccept(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(BrowserThread::CurrentlyOn(thread_id_));
+
+ AcceptParams params;
+ params.thread_id = thread_id_;
+ params.profile_id = profile_;
+ params.extension_id = extension_id;
+ params.server_sockets = server_sockets_;
+ params.client_sockets = client_sockets_;
+ params.socket_id = socket_id;
+
+ StartAccept(params);
+}
+
+// static
+void TCPServerSocketEventDispatcher::StartAccept(const AcceptParams& params) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ ResumableTCPServerSocket* socket =
+ params.server_sockets->Get(params.extension_id, params.socket_id);
+ if (!socket) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(params.extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ // Don't start another accept if the socket has been paused.
+ if (socket->paused())
+ return;
+
+ socket->Accept(base::Bind(&TCPServerSocketEventDispatcher::AcceptCallback,
+ params));
+}
+
+// static
+void TCPServerSocketEventDispatcher::AcceptCallback(
+ const AcceptParams& params,
+ int result_code,
+ net::TCPClientSocket *socket) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ if (result_code >= 0) {
+ ResumableTCPSocket *client_socket =
+ new ResumableTCPSocket(socket, params.extension_id, true);
+ client_socket->set_paused(true);
+ int client_socket_id = params.client_sockets->Add(client_socket);
+
+ // Dispatch "onAccept" event.
+ sockets_tcp_server::AcceptInfo accept_info;
+ accept_info.socket_id = params.socket_id;
+ accept_info.client_socket_id = client_socket_id;
+ scoped_ptr<base::ListValue> args =
+ sockets_tcp_server::OnAccept::Create(accept_info);
+ scoped_ptr<Event> event(
+ new Event(sockets_tcp_server::OnAccept::kEventName, args.Pass()));
+ PostEvent(params, event.Pass());
+
+ // Post a task to delay the "accept" until the socket is available, as
+ // calling StartAccept at this point would error with ERR_IO_PENDING.
+ BrowserThread::PostTask(
+ params.thread_id, FROM_HERE,
+ base::Bind(&TCPServerSocketEventDispatcher::StartAccept, params));
+ } else {
+ // Dispatch "onAcceptError" event but don't start another accept to avoid
+ // potential infinite "accepts" if we have a persistent network error.
+ sockets_tcp_server::AcceptErrorInfo accept_error_info;
+ accept_error_info.socket_id = params.socket_id;
+ accept_error_info.result_code = result_code;
+ scoped_ptr<base::ListValue> args =
+ sockets_tcp_server::OnAcceptError::Create(accept_error_info);
+ scoped_ptr<Event> event(
+ new Event(sockets_tcp_server::OnAcceptError::kEventName, args.Pass()));
+ PostEvent(params, event.Pass());
+
+ // Since we got an error, the socket is now "paused" until the application
+ // "resumes" it.
+ ResumableTCPServerSocket* socket =
+ params.server_sockets->Get(params.extension_id, params.socket_id);
+ if (socket) {
+ socket->set_paused(true);
+ }
+ }
+}
+
+// static
+void TCPServerSocketEventDispatcher::PostEvent(const AcceptParams& params,
+ scoped_ptr<Event> event) {
+ DCHECK(BrowserThread::CurrentlyOn(params.thread_id));
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&DispatchEvent,
+ params.profile_id,
+ params.extension_id,
+ base::Passed(event.Pass())));
+}
+
+// static
+void TCPServerSocketEventDispatcher::DispatchEvent(
+ void* profile_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ Profile* profile = reinterpret_cast<Profile*>(profile_id);
+ if (!g_browser_process->profile_manager()->IsValidProfile(profile))
+ return;
+
+ EventRouter* router = ExtensionSystem::Get(profile)->event_router();
+ if (router)
+ router->DispatchEventToExtension(extension_id, event.Pass());
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h
new file mode 100644
index 0000000..4d15bc3
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h
@@ -0,0 +1,99 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_
+
+#include "chrome/browser/extensions/api/api_resource_manager.h"
+#include "chrome/browser/extensions/api/sockets_tcp/sockets_tcp_api.h"
+#include "chrome/browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h"
+
+namespace extensions {
+struct Event;
+class ResumableTCPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+// Dispatch events related to "sockets.tcp" sockets from callback on native
+// socket instances. There is one instance per profile.
+class TCPServerSocketEventDispatcher
+ : public ProfileKeyedAPI,
+ public base::SupportsWeakPtr<TCPServerSocketEventDispatcher> {
+ public:
+ explicit TCPServerSocketEventDispatcher(Profile* profile);
+ virtual ~TCPServerSocketEventDispatcher();
+
+ // Server socket is active, start accepting connections from it.
+ void OnServerSocketListen(const std::string& extension_id, int socket_id);
+
+ // Server socket is active again, start accepting connections from it.
+ void OnServerSocketResume(const std::string& extension_id, int socket_id);
+
+ // ProfileKeyedAPI implementation.
+ static ProfileKeyedAPIFactory<TCPServerSocketEventDispatcher>*
+ GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static TCPServerSocketEventDispatcher* Get(Profile* profile);
+
+ private:
+ typedef ApiResourceManager<ResumableTCPServerSocket>::ApiResourceData
+ ServerSocketData;
+ typedef ApiResourceManager<ResumableTCPSocket>::ApiResourceData
+ ClientSocketData;
+ friend class ProfileKeyedAPIFactory<TCPServerSocketEventDispatcher>;
+ // ProfileKeyedAPI implementation.
+ static const char* service_name() {
+ return "TCPServerSocketEventDispatcher";
+ }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ // base::Bind supports methods with up to 6 parameters. AcceptParams is used
+ // as a workaround that limitation for invoking StartAccept.
+ struct AcceptParams {
+ AcceptParams();
+ ~AcceptParams();
+
+ content::BrowserThread::ID thread_id;
+ void* profile_id;
+ std::string extension_id;
+ scoped_refptr<ServerSocketData> server_sockets;
+ scoped_refptr<ClientSocketData> client_sockets;
+ int socket_id;
+ };
+
+ // Start an accept and register a callback.
+ void StartSocketAccept(const std::string& extension_id, int socket_id);
+
+ // Start an accept and register a callback.
+ static void StartAccept(const AcceptParams& params);
+
+ // Called when socket accepts a new connection.
+ static void AcceptCallback(const AcceptParams& params,
+ int result_code,
+ net::TCPClientSocket *socket);
+
+ // Post an extension event from |thread_id| to UI thread
+ static void PostEvent(const AcceptParams& params,
+ scoped_ptr<Event> event);
+
+ // Dispatch an extension event on to EventRouter instance on UI thread.
+ static void DispatchEvent(void* profile_id,
+ const std::string& extension_id,
+ scoped_ptr<Event> event);
+
+ // Usually IO thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ Profile* const profile_;
+ scoped_refptr<ServerSocketData> server_sockets_;
+ scoped_refptr<ClientSocketData> client_sockets_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_TCP_SERVER_TCP_SERVER_SOCKET_EVENT_DISPATCHER_H_
diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h
index 47ae023..5b9d5ba 100644
--- a/chrome/browser/extensions/extension_function_histogram_value.h
+++ b/chrome/browser/extensions/extension_function_histogram_value.h
@@ -663,6 +663,14 @@ enum HistogramValue {
NETWORKINGPRIVATE_GETENABLEDNETWORKTYPES,
NETWORKINGPRIVATE_ENABLENETWORKTYPE,
NETWORKINGPRIVATE_DISABLENETWORKTYPE,
+ SOCKETS_TCP_SERVER_CREATE,
+ SOCKETS_TCP_SERVER_UPDATE,
+ SOCKETS_TCP_SERVER_SETPAUSED,
+ SOCKETS_TCP_SERVER_LISTEN,
+ SOCKETS_TCP_SERVER_DISCONNECT,
+ SOCKETS_TCP_SERVER_CLOSE,
+ SOCKETS_TCP_SERVER_GETINFO,
+ SOCKETS_TCP_SERVER_GETSOCKETS,
ENUM_BOUNDARY // Last entry: Add new entries above.
};
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 4d3f8ca..f1ece96 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -89,6 +89,7 @@
#include "chrome/browser/extensions/api/socket/tcp_socket.h"
#include "chrome/browser/extensions/api/socket/udp_socket.h"
#include "chrome/browser/extensions/api/sockets_tcp/tcp_socket_event_dispatcher.h"
+#include "chrome/browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h"
#include "chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h"
#include "chrome/browser/extensions/api/streams_private/streams_private_api.h"
#include "chrome/browser/extensions/api/system_info/system_info_api.h"
@@ -218,6 +219,8 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() {
extensions::ActivityLogFactory::GetInstance();
extensions::ActivityLogAPI::GetFactoryInstance();
extensions::AlarmManager::GetFactoryInstance();
+ extensions::ApiResourceManager<extensions::ResumableTCPServerSocket>::
+ GetFactoryInstance();
extensions::ApiResourceManager<extensions::ResumableTCPSocket>::
GetFactoryInstance();
extensions::ApiResourceManager<extensions::ResumableUDPSocket>::
@@ -227,6 +230,7 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() {
extensions::ApiResourceManager<extensions::Socket>::GetFactoryInstance();
extensions::ApiResourceManager<extensions::UsbDeviceResource>::
GetFactoryInstance();
+ extensions::api::TCPServerSocketEventDispatcher::GetFactoryInstance();
extensions::api::TCPSocketEventDispatcher::GetFactoryInstance();
extensions::api::UDPSocketEventDispatcher::GetFactoryInstance();
extensions::AudioAPI::GetFactoryInstance();
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 3343ca5..9f2d058 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -468,6 +468,10 @@
'browser/extensions/api/sockets_tcp/tcp_socket_event_dispatcher.cc',
'browser/extensions/api/sockets_tcp/sockets_tcp_api.cc',
'browser/extensions/api/sockets_tcp/sockets_tcp_api.h',
+ 'browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.h',
+ 'browser/extensions/api/sockets_tcp_server/tcp_server_socket_event_dispatcher.cc',
+ 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.cc',
+ 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api.h',
'browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h',
'browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc',
'browser/extensions/api/sockets_udp/sockets_udp_api.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index baa183f..c1a2559 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1118,6 +1118,7 @@
'browser/extensions/api/sessions/sessions_apitest.cc',
'browser/extensions/api/socket/socket_apitest.cc',
'browser/extensions/api/sockets_tcp/sockets_tcp_apitest.cc',
+ 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_apitest.cc',
'browser/extensions/api/sockets_udp/sockets_udp_apitest.cc',
'browser/extensions/api/storage/settings_apitest.cc',
'browser/extensions/api/streams_private/streams_private_apitest.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index f97dc0c..d9f91ea 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -831,6 +831,7 @@
'browser/extensions/api/socket/tcp_socket_unittest.cc',
'browser/extensions/api/socket/udp_socket_unittest.cc',
'browser/extensions/api/sockets_tcp/sockets_tcp_api_unittest.cc',
+ 'browser/extensions/api/sockets_tcp_server/sockets_tcp_server_api_unittest.cc',
'browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc',
'browser/extensions/api/storage/policy_value_store_unittest.cc',
'browser/extensions/api/storage/settings_frontend_unittest.cc',
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index 8e58146..b6e72e7 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -525,6 +525,11 @@
"channel": "dev",
"contexts": ["blessed_extension"]
},
+ "sockets.tcpServer": {
+ "dependencies": ["manifest:sockets"],
+ "channel": "dev",
+ "contexts": ["blessed_extension"]
+ },
"sockets.udp": {
"dependencies": ["manifest:sockets"],
"channel": "dev",
diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp
index 84e2f28..3c90b0d 100644
--- a/chrome/common/extensions/api/api.gyp
+++ b/chrome/common/extensions/api/api.gyp
@@ -94,6 +94,7 @@
'signed_in_devices.idl',
'socket.idl',
'sockets_tcp.idl',
+ 'sockets_tcp_server.idl',
'sockets_udp.idl',
'storage.json',
'sync_file_system.idl',
diff --git a/chrome/common/extensions/api/manifest_types.json b/chrome/common/extensions/api/manifest_types.json
index 28d89ee..4a798a7 100644
--- a/chrome/common/extensions/api/manifest_types.json
+++ b/chrome/common/extensions/api/manifest_types.json
@@ -76,6 +76,18 @@
"type": "string"
}
}
+ },
+ "tcpServer": {
+ "description": "The <code>tcpServer</code> manifest property declares which sockets.tcpServer operations an app can issue.",
+ "optional": true,
+ "type": "object",
+ "properties": {
+ "listen": {
+ "description": "<p>The host:port pattern for <code>listen</code> operations.</p>",
+ "optional": true,
+ "type": "string"
+ }
+ }
}
}
}
diff --git a/chrome/common/extensions/api/sockets/sockets_handler.cc b/chrome/common/extensions/api/sockets/sockets_handler.cc
index b10a8de..a1b51c2 100644
--- a/chrome/common/extensions/api/sockets/sockets_handler.cc
+++ b/chrome/common/extensions/api/sockets/sockets_handler.cc
@@ -107,6 +107,14 @@ scoped_ptr<SocketsManifestData> SocketsManifestData::FromValue(
return scoped_ptr<SocketsManifestData>();
}
}
+ if (sockets->tcp_server) {
+ if (!ParseHostPattern(result.get(),
+ content::SocketPermissionRequest::TCP_LISTEN,
+ sockets->tcp_server->listen,
+ error)) {
+ return scoped_ptr<SocketsManifestData>();
+ }
+ }
return result.Pass();
}
diff --git a/chrome/common/extensions/api/sockets_tcp_server.idl b/chrome/common/extensions/api/sockets_tcp_server.idl
new file mode 100644
index 0000000..60c9b61
--- /dev/null
+++ b/chrome/common/extensions/api/sockets_tcp_server.idl
@@ -0,0 +1,191 @@
+// 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.
+
+// Use the <code>chrome.sockets.tcpServer</code> API to create server
+// applications using TCP connections. This API supersedes the TCP functionality
+// previously found in the <code>chrome.socket</code> API. Note that the socket
+// ids created from this namespace are not compatible with ids created in other
+// namespaces.
+namespace sockets.tcpServer {
+ // The socket properties specified in the <code>create</code> or
+ // <code>update</code> function. Each property is optional. If a property
+ // value is not specified, a default value is used when calling
+ // <code>create</code>, or the existing value if preserved when calling
+ // <code>update</code>.
+ dictionary SocketProperties {
+ // Flag indicating if the socket remains open when the event page of the
+ // application is unloaded (see
+ // <a href="http://developer.chrome.com/apps/app_lifecycle.html">Manage App
+ // Lifecycle</a>). The default value is "false." When the application is
+ // loaded, any sockets previously opened with persistent=true can be fetched
+ // with <code>getSockets</code>.
+ boolean? persistent;
+
+ // An application-defined string associated with the socket.
+ DOMString? name;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created server socket.
+ long socketId;
+ };
+
+ // Callback from the <code>create</code> method.
+ // |createInfo| : The result of the socket creation.
+ callback CreateCallback = void (CreateInfo createInfo);
+
+ // Callback from the <code>listen</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback ListenCallback = void (long result);
+
+ // Callback from the <code>disconnect</code> method.
+ callback DisconnectCallback = void ();
+
+ // Callback from the <code>close<code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Callback from the <code>setPaused<code> method.
+ callback SetPausedCallback = void ();
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating if the socket remains open when the event page of the
+ // application is unloaded (see <code>SocketProperties.persistent</code>).
+ // The default value is "false".
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // Flag indicating whether connection requests on a listening socket are
+ // dispatched through the <code>onAccept<code> event or queued up in the
+ // listen queue backlog.
+ // See <code>setPaused<code>. The default value is "false".
+ boolean paused;
+
+ // If the socket is listening, contains its local IPv4/6 address.
+ DOMString? localAddress;
+
+ // If the socket is listening, contains its local port.
+ long? localPort;
+ };
+
+ // Callback from the <code>getInfo</code> method.
+ // |socketInfo| : Object containing the socket information.
+ callback GetInfoCallback = void (SocketInfo socketInfo);
+
+ // Callback from the <code>getSockets</code> method.
+ // |socketInfos| : Array of object containing socket information.
+ callback GetSocketsCallback = void (SocketInfo[] socketInfos);
+
+ // Data from an <code>onAccept</code> event.
+ dictionary AcceptInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The client socket identifier, i.e. the socket identifier of the newly
+ // established connection. This socket identifier should be used only with
+ // functions from the <code>chrome.sockets.tcp<code> namespace.
+ long clientSocketId;
+ };
+
+ // Data from an <code>onAcceptError</code> event.
+ dictionary AcceptErrorInfo {
+ // The server socket identifier.
+ long socketId;
+
+ // The result code returned from the underlying network call.
+ long resultCode;
+ };
+
+ interface Functions {
+ // Creates a TCP server socket.
+ // |properties| : The socket properties (optional).
+ // |callback| : Called when the socket has been created.
+ static void create(optional SocketProperties properties,
+ CreateCallback callback);
+
+ // Updates the socket properties.
+ // |socketId| : The socket identifier.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ optional UpdateCallback callback);
+
+ // Enables or disables a listening socket from accepting new connections.
+ // When paused, a listening socket accepts new connections until its backlog
+ // (see <code>listen<code> function) is full then refuses additional
+ // connection requests. <code>onAccept<code> events are raised only when
+ // the socket is un-paused.
+ static void setPaused(long socketId,
+ boolean paused,
+ optional SetPausedCallback callback);
+
+ // Listens for connections on the specified port and address.
+ // If the port/address is in use, the callback indicates a failure.
+ // |socketId| : The socket identifier.
+ // |address| : The address of the local machine.
+ // |port| : The port of the local machine.
+ // |backlog| : Length of the socket's listen queue. The default value
+ // depends on the Operating System (SOMAXCONN), which ensures a reasonable
+ // queue length for most applications.
+ // |callback| : Called when listen operation completes.
+ static void listen(long socketId,
+ DOMString address,
+ long port,
+ optional long backlog,
+ ListenCallback callback);
+
+ // Disconnects the listening socket, i.e. stops accepting new connections
+ // and releases the address/port the socket is bound to. The socket
+ // identifier remains valid, e.g. it can be used with <code>listen<code> to
+ // accept connections on a new port and address.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the disconnect attempt is complete.
+ static void disconnect(long socketId,
+ optional DisconnectCallback callback);
+
+ // Disconnects and destroys the socket. Each socket created should be
+ // closed after use. The socket id is no longer valid as soon at the
+ // function is called. However, the socket is guaranteed to be closed only
+ // when the callback is invoked.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the <code>close</code> operation completes.
+ static void close(long socketId,
+ optional CloseCallback callback);
+
+ // Retrieves the state of the given socket.
+ // |socketId| : The socket identifier.
+ // |callback| : Called when the socket state is available.
+ static void getInfo(long socketId,
+ GetInfoCallback callback);
+
+ // Retrieves the list of currently opened sockets owned by the application.
+ // |callback| : Called when the list of sockets is available.
+ static void getSockets(GetSocketsCallback callback);
+ };
+
+ interface Events {
+ // Event raised when a connection has been made to the server socket.
+ // |info| : The event data.
+ static void onAccept(AcceptInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for new connections on the socket address and port. Once this event is
+ // raised, the socket is set to <code>paused</code> and no more
+ // <code>onAccept</code> events are raised for this socket until the socket
+ // is resumed.
+ // |info| : The event data.
+ static void onAcceptError(AcceptErrorInfo info);
+ };
+};
diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js
new file mode 100644
index 0000000..bd7ed4f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/background.js
@@ -0,0 +1,134 @@
+// 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.
+
+// net/tools/testserver/testserver.py is picky about the format of what it
+// calls its "echo" messages. One might go so far as to mutter to oneself that
+// it isn't an echo server at all.
+//
+// The response is based on the request but obfuscated using a random key.
+const request = "0100000005320000005hello";
+
+var address;
+var port = -1;
+var socketId = 0;
+var succeeded = false;
+
+// Many thanks to Dennis for his StackOverflow answer: http://goo.gl/UDanx
+// Since amended to handle BlobBuilder deprecation.
+function string2ArrayBuffer(string, callback) {
+ var blob = new Blob([string]);
+ var f = new FileReader();
+ f.onload = function(e) {
+ callback(e.target.result);
+ };
+ f.readAsArrayBuffer(blob);
+}
+
+function arrayBuffer2String(buf, callback) {
+ var blob = new Blob([new Uint8Array(buf)]);
+ var f = new FileReader();
+ f.onload = function(e) {
+ callback(e.target.result);
+ };
+ f.readAsText(blob);
+}
+
+// Tests listening on a socket and sending/receiving from accepted sockets.
+var testSocketListening = function() {
+ var tmpSocketId = 0;
+
+ chrome.sockets.tcpServer.create({}, onServerSocketCreate);
+
+ function onServerSocketCreate(socketInfo) {
+ console.log("Server socket created: sd=" + socketInfo.socketId);
+ socketId = socketInfo.socketId;
+ chrome.sockets.tcpServer.listen(socketId, address, port, onListen);
+ }
+
+ function onListen(result) {
+ console.log("Server socket 'listen' completed: sd=" + socketId +
+ ", result=" + result);
+ chrome.test.assertEq(0, result, "Listen failed.");
+ chrome.sockets.tcpServer.onAccept.addListener(onServerSocketAccept);
+ chrome.sockets.tcp.onReceive.addListener(onReceive);
+ chrome.sockets.tcp.onReceiveError.addListener(onReceiveError);
+
+ // Create a new socket to connect to the TCP server.
+ chrome.sockets.tcp.create({}, function(socketInfo) {
+ console.log("Client socket created: sd=" + socketInfo.socketId);
+ tmpSocketId = socketInfo.socketId;
+ chrome.sockets.tcp.connect(tmpSocketId, address, port,
+ function(result) {
+ console.log("Client socket connected: sd=" + tmpSocketId);
+ chrome.test.assertEq(0, result, "Connect failed");
+
+ // Write.
+ string2ArrayBuffer(request, function(buf) {
+ chrome.sockets.tcp.send(tmpSocketId, buf, function(sendInfo) {
+ console.log("Client socket data sent: sd=" + tmpSocketId
+ + ", result=" + sendInfo.resultCode);
+ chrome.sockets.tcp.disconnect(tmpSocketId, function() {
+ console.log("Client socket disconnected: sd=" + tmpSocketId);
+ });
+ });
+ });
+ });
+ });
+ }
+
+ var clientSocketId;
+
+ function onServerSocketAccept(acceptInfo) {
+ console.log("Server socket 'accept' event: sd=" + acceptInfo.socketId +
+ ", client sd=" + acceptInfo.clientSocketId);
+ chrome.test.assertEq(socketId, acceptInfo.socketId, "Wrong server socket.");
+ chrome.test.assertTrue(acceptInfo.clientSocketId > 0);
+ clientSocketId = acceptInfo.clientSocketId;
+ chrome.sockets.tcp.setPaused(clientSocketId, false, function() {});
+ }
+
+ function onReceive(receiveInfo) {
+ console.log("Client socket 'receive' event: sd=" + receiveInfo.socketId
+ + ", bytes=" + receiveInfo.data.byteLength);
+ chrome.test.assertEq(clientSocketId, receiveInfo.socketId,
+ "Received data on wrong socket");
+ if (receiveInfo.data.byteLength == 0)
+ return;
+ arrayBuffer2String(receiveInfo.data, function(s) {
+ var match = !!s.match(request);
+ chrome.test.assertTrue(match, "Received data does not match.");
+ succeeded = true;
+ // Test whether socket.getInfo correctly reflects the connection status
+ // if the peer has closed the connection.
+ setTimeout(function() {
+ chrome.sockets.tcp.getInfo(receiveInfo.socketId, function(info) {
+ chrome.test.assertFalse(info.connected);
+ chrome.test.succeed();
+ });
+ }, 500);
+ });
+ }
+
+ function onReceiveError(receiveInfo) {
+ console.log("Client socket 'receive error' event: sd="
+ + receiveInfo.socketId + ", result=" + receiveInfo.resultCode);
+ //chrome.test.fail("Receive failed.");
+ }
+};
+
+var onMessageReply = function(message) {
+ var parts = message.split(":");
+ var test_type = parts[0];
+ address = parts[1];
+ port = parseInt(parts[2]);
+ console.log("Running tests, protocol " + test_type + ", echo server " +
+ address + ":" + port);
+ if (test_type == 'tcp_server') {
+ chrome.test.runTests([testSocketListening]);
+ }
+};
+
+// Find out which protocol we're supposed to test, and which echo server we
+// should be using, then kick off the tests.
+chrome.test.sendMessage("info_please", onMessageReply);
diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json
new file mode 100644
index 0000000..9e2923c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/api/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "chrome.sockets.tcpServer extension",
+ "version": "0.1",
+ "description": "end-to-end browser test for chrome.sockets.tcpServer API",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "sockets": {
+ "tcp": {
+ "connect": ""
+ },
+ "tcpServer": {
+ "listen": ""
+ }
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js
new file mode 100644
index 0000000..d7946bd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/background.js
@@ -0,0 +1,24 @@
+// 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.
+
+var socketId;
+
+var onListen = function(result) {
+ console.log("Server socket 'listen' completed: sd=" + socketId +
+ ", result=" + result);
+ chrome.test.assertEq(0, result);
+ chrome.test.succeed();
+};
+
+var onCreate = function (socketInfo) {
+ console.log("Server socket created: sd=" + socketInfo.socketId);
+ socketId = socketInfo.socketId;
+ chrome.sockets.tcpServer.listen(socketId, '0.0.0.0', 1234, onListen);
+};
+
+chrome.test.runTests([
+ function bind() {
+ chrome.sockets.tcpServer.create({}, onCreate);
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json
new file mode 100644
index 0000000..3e508ce
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_tcp_server/unload/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "chrome.sockets.tcpServer unload",
+ "version": "0.1",
+ "description": "browser test for chrome.sockets.tcpServer API to make sure sockets are free'd when extension is reloaded",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "sockets": {
+ "tcpServer": {
+ "listen": ""
+ }
+ }
+}