summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/api/api_resource.cc4
-rw-r--r--chrome/browser/extensions/api/api_resource.h2
-rw-r--r--chrome/browser/extensions/api/api_resource_manager.h87
-rw-r--r--chrome/browser/extensions/api/socket/socket_api.cc29
-rw-r--r--chrome/browser/extensions/api/socket/socket_api.h68
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket.cc41
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket.h27
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc471
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h258
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc79
-rw-r--r--chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc125
-rw-r--r--chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc125
-rw-r--r--chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h66
-rw-r--r--chrome/browser/extensions/extension_function_histogram_value.h12
-rw-r--r--chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc5
-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/_permission_features.json4
-rw-r--r--chrome/common/extensions/api/api.gyp1
-rw-r--r--chrome/common/extensions/api/sockets_udp.idl287
-rw-r--r--chrome/common/extensions/permissions/api_permission.h1
-rw-r--r--chrome/common/extensions/permissions/chrome_api_permissions.cc1
-rw-r--r--chrome/common/extensions/permissions/permission_set_unittest.cc1
-rw-r--r--chrome/test/data/extensions/api_test/sockets_udp/api/background.js168
-rw-r--r--chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json15
-rw-r--r--chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js224
28 files changed, 2092 insertions, 20 deletions
diff --git a/chrome/browser/extensions/api/api_resource.cc b/chrome/browser/extensions/api/api_resource.cc
index 8339367..ccbdd77 100644
--- a/chrome/browser/extensions/api/api_resource.cc
+++ b/chrome/browser/extensions/api/api_resource.cc
@@ -15,4 +15,8 @@ ApiResource::ApiResource(const std::string& owner_extension_id)
ApiResource::~ApiResource() {
}
+bool ApiResource::persistent() const {
+ return true; // backward-compatible behavior.
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/api_resource.h b/chrome/browser/extensions/api/api_resource.h
index 4a21083..2cfdffe 100644
--- a/chrome/browser/extensions/api/api_resource.h
+++ b/chrome/browser/extensions/api/api_resource.h
@@ -23,6 +23,8 @@ class ApiResource {
return owner_extension_id_;
}
+ virtual bool persistent() const;
+
static const content::BrowserThread::ID kThreadId =
content::BrowserThread::IO;
diff --git a/chrome/browser/extensions/api/api_resource_manager.h b/chrome/browser/extensions/api/api_resource_manager.h
index d357ebe..a7c1624 100644
--- a/chrome/browser/extensions/api/api_resource_manager.h
+++ b/chrome/browser/extensions/api/api_resource_manager.h
@@ -7,11 +7,13 @@
#include <map>
+#include "base/containers/hash_tables.h"
#include "base/lazy_instance.h"
#include "base/memory/linked_ptr.h"
#include "base/threading/non_thread_safe.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/profile_keyed_api_factory.h"
+#include "chrome/browser/extensions/extension_host.h"
#include "chrome/common/extensions/extension.h"
#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
#include "content/public/browser/browser_thread.h"
@@ -69,6 +71,10 @@ class ApiResourceManager : public ProfileKeyedAPI,
this,
chrome::NOTIFICATION_EXTENSION_UNLOADED,
content::NotificationService::AllSources());
+ registrar_.Add(
+ this,
+ chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::NotificationService::AllSources());
}
// For Testing.
@@ -113,6 +119,10 @@ class ApiResourceManager : public ProfileKeyedAPI,
return data_->Get(extension_id, api_resource_id);
}
+ base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
+ return data_->GetResourceIds(extension_id);
+ }
+
protected:
// content::NotificationObserver:
virtual void Observe(int type,
@@ -123,7 +133,12 @@ class ApiResourceManager : public ProfileKeyedAPI,
std::string id =
content::Details<extensions::UnloadedExtensionInfo>(details)->
extension->id();
- data_->InitiateCleanup(id);
+ data_->InitiateExtensionUnloadedCleanup(id);
+ break;
+ }
+ case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ data_->InitiateExtensionSuspendedCleanup(host->extension_id());
break;
}
}
@@ -185,9 +200,20 @@ class ApiResourceManager : public ProfileKeyedAPI,
return GetOwnedResource(extension_id, api_resource_id);
}
- void InitiateCleanup(const std::string& extension_id) {
+ base::hash_set<int>* GetResourceIds(const std::string& extension_id) {
+ DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
+ return GetOwnedResourceIds(extension_id);
+ }
+
+ void InitiateExtensionUnloadedCleanup(const std::string& extension_id) {
+ content::BrowserThread::PostTask(thread_id_, FROM_HERE,
+ base::Bind(&ApiResourceData::CleanupResourcesFromUnloadedExtension,
+ base::Unretained(this), extension_id));
+ }
+
+ void InitiateExtensionSuspendedCleanup(const std::string& extension_id) {
content::BrowserThread::PostTask(thread_id_, FROM_HERE,
- base::Bind(&ApiResourceData::CleanupResourcesFromExtension,
+ base::Bind(&ApiResourceData::CleanupResourcesFromSuspendedExtension,
base::Unretained(this), extension_id));
}
@@ -201,16 +227,59 @@ class ApiResourceManager : public ProfileKeyedAPI,
return NULL;
}
- void CleanupResourcesFromExtension(const std::string& extension_id) {
+ base::hash_set<int>* GetOwnedResourceIds(const std::string& extension_id) {
DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
- if (extension_resource_map_.find(extension_id) !=
+ if (extension_resource_map_.find(extension_id) ==
+ extension_resource_map_.end())
+ return NULL;
+
+ return &extension_resource_map_[extension_id];
+ }
+
+ void CleanupResourcesFromUnloadedExtension(
+ const std::string& extension_id) {
+ CleanupResourcesFromExtension(extension_id, true);
+ }
+
+ void CleanupResourcesFromSuspendedExtension(
+ const std::string& extension_id) {
+ CleanupResourcesFromExtension(extension_id, false);
+ }
+
+ void CleanupResourcesFromExtension(const std::string& extension_id,
+ bool remove_all) {
+ DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
+
+ if (extension_resource_map_.find(extension_id) ==
extension_resource_map_.end()) {
- base::hash_set<int>& resource_ids =
- extension_resource_map_[extension_id];
- for (base::hash_set<int>::iterator it = resource_ids.begin();
- it != resource_ids.end(); ++it) {
+ return;
+ }
+
+ // Remove all resources, or the non persistent ones only if |remove_all|
+ // is false.
+ base::hash_set<int>& resource_ids =
+ extension_resource_map_[extension_id];
+ for (base::hash_set<int>::iterator it = resource_ids.begin();
+ it != resource_ids.end(); ) {
+ bool erase = false;
+ if (remove_all) {
+ erase = true;
+ } else {
+ linked_ptr<T> ptr = api_resource_map_[*it];
+ T* resource = ptr.get();
+ erase = (resource && !resource->persistent());
+ }
+
+ if (erase) {
api_resource_map_.erase(*it);
+ resource_ids.erase(it++);
+ } else {
+ ++it;
}
+ } // end for
+
+ // Remove extension entry if we removed all its resources.
+ if (resource_ids.size() == 0) {
extension_resource_map_.erase(extension_id);
}
}
diff --git a/chrome/browser/extensions/api/socket/socket_api.cc b/chrome/browser/extensions/api/socket/socket_api.cc
index 83f1e4e..8c1a55b 100644
--- a/chrome/browser/extensions/api/socket/socket_api.cc
+++ b/chrome/browser/extensions/api/socket/socket_api.cc
@@ -47,30 +47,39 @@ const char kMulticastSocketTypeError[] =
const char kWildcardAddress[] = "*";
const int kWildcardPort = 0;
-SocketAsyncApiFunction::SocketAsyncApiFunction()
- : manager_(NULL) {
+SocketAsyncApiFunction::SocketAsyncApiFunction() {
}
SocketAsyncApiFunction::~SocketAsyncApiFunction() {
}
bool SocketAsyncApiFunction::PrePrepare() {
- manager_ = ApiResourceManager<Socket>::Get(profile());
- DCHECK(manager_) << "There is no socket manager. "
- "If this assertion is failing during a test, then it is likely that "
- "TestExtensionSystem is failing to provide an instance of "
- "ApiResourceManager<Socket>.";
- return manager_ != NULL;
+ manager_ = CreateSocketResourceManager();
+ return manager_->SetProfile(profile());
}
bool SocketAsyncApiFunction::Respond() {
return error_.empty();
}
+scoped_ptr<SocketResourceManagerInterface>
+ SocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<Socket>()).Pass();
+}
+
+int SocketAsyncApiFunction::AddSocket(Socket* socket) {
+ return manager_->Add(socket);
+}
+
Socket* SocketAsyncApiFunction::GetSocket(int api_resource_id) {
return manager_->Get(extension_->id(), api_resource_id);
}
+base::hash_set<int>* SocketAsyncApiFunction::GetSocketIds() {
+ return manager_->GetResourceIds(extension_->id());
+}
+
void SocketAsyncApiFunction::RemoveSocket(int api_resource_id) {
manager_->Remove(extension_->id(), api_resource_id);
}
@@ -152,7 +161,7 @@ void SocketCreateFunction::Work() {
DCHECK(socket);
base::DictionaryValue* result = new base::DictionaryValue();
- result->SetInteger(kSocketIdKey, manager_->Add(socket));
+ result->SetInteger(kSocketIdKey, AddSocket(socket));
SetResult(result);
}
@@ -352,7 +361,7 @@ void SocketAcceptFunction::OnAccept(int result_code,
result->SetInteger(kResultCodeKey, result_code);
if (socket) {
Socket *client_socket = new TCPSocket(socket, extension_id(), true);
- result->SetInteger(kSocketIdKey, manager_->Add(client_socket));
+ result->SetInteger(kSocketIdKey, AddSocket(client_socket));
}
SetResult(result);
diff --git a/chrome/browser/extensions/api/socket/socket_api.h b/chrome/browser/extensions/api/socket/socket_api.h
index 4e68c58..396ddc6 100644
--- a/chrome/browser/extensions/api/socket/socket_api.h
+++ b/chrome/browser/extensions/api/socket/socket_api.h
@@ -27,6 +27,66 @@ namespace extensions {
class Socket;
+// A simple interface to ApiResourceManager<Socket> or derived class. The goal
+// of this interface is to allow Socket API functions to use distinct instances
+// of ApiResourceManager<> depending on the type of socket (old version in
+// "socket" namespace vs new version in "socket.xxx" namespaces).
+class SocketResourceManagerInterface {
+ public:
+ virtual ~SocketResourceManagerInterface() {}
+
+ virtual bool SetProfile(Profile* profile) = 0;
+ virtual int Add(Socket *socket) = 0;
+ virtual Socket* Get(const std::string& extension_id,
+ int api_resource_id) = 0;
+ virtual void Remove(const std::string& extension_id,
+ int api_resource_id) = 0;
+ virtual base::hash_set<int>* GetResourceIds(
+ const std::string& extension_id) = 0;
+};
+
+// Implementation of SocketResourceManagerInterface using an
+// ApiResourceManager<T> instance (where T derives from Socket).
+template<typename T>
+class SocketResourceManager : public SocketResourceManagerInterface {
+ public:
+ SocketResourceManager()
+ : manager_(NULL) {
+ }
+
+ virtual bool SetProfile(Profile* profile) OVERRIDE {
+ manager_ = ApiResourceManager<T>::Get(profile);
+ DCHECK(manager_) << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<Socket>.";
+ return manager_ != NULL;
+ }
+
+ virtual int Add(Socket *socket) OVERRIDE {
+ // Note: Cast needed here, because "T" may be a subclass of "Socket".
+ return manager_->Add(static_cast<T*>(socket));
+ }
+
+ virtual Socket* Get(const std::string& extension_id,
+ int api_resource_id) OVERRIDE {
+ return manager_->Get(extension_id, api_resource_id);
+ }
+
+ virtual void Remove(const std::string& extension_id,
+ int api_resource_id) OVERRIDE {
+ manager_->Remove(extension_id, api_resource_id);
+ }
+
+ virtual base::hash_set<int>* GetResourceIds(
+ const std::string& extension_id) OVERRIDE {
+ return manager_->GetResourceIds(extension_id);
+ }
+
+ private:
+ ApiResourceManager<T>* manager_;
+};
+
class SocketAsyncApiFunction : public AsyncApiFunction {
public:
SocketAsyncApiFunction();
@@ -38,10 +98,16 @@ class SocketAsyncApiFunction : public AsyncApiFunction {
virtual bool PrePrepare() OVERRIDE;
virtual bool Respond() OVERRIDE;
+ virtual scoped_ptr<SocketResourceManagerInterface>
+ CreateSocketResourceManager();
+
+ int AddSocket(Socket* socket);
Socket* GetSocket(int api_resource_id);
void RemoveSocket(int api_resource_id);
+ base::hash_set<int>* GetSocketIds();
- ApiResourceManager<Socket>* manager_;
+ private:
+ scoped_ptr<SocketResourceManagerInterface> manager_;
};
class SocketExtensionWithDnsLookupFunction : public SocketAsyncApiFunction {
diff --git a/chrome/browser/extensions/api/socket/udp_socket.cc b/chrome/browser/extensions/api/socket/udp_socket.cc
index 4a77aea..7d36ffc 100644
--- a/chrome/browser/extensions/api/socket/udp_socket.cc
+++ b/chrome/browser/extensions/api/socket/udp_socket.cc
@@ -14,6 +14,17 @@
namespace extensions {
+static base::LazyInstance<ProfileKeyedAPIFactory<
+ ApiResourceManager<ResumableUDPSocket> > >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+template <>
+ProfileKeyedAPIFactory<ApiResourceManager<ResumableUDPSocket> >*
+ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance() {
+ return &g_factory.Get();
+}
+
UDPSocket::UDPSocket(const std::string& owner_extension_id)
: Socket(owner_extension_id),
socket_(net::DatagramSocket::DEFAULT_BIND,
@@ -278,4 +289,34 @@ const std::vector<std::string>& UDPSocket::GetJoinedGroups() const {
return multicast_groups_;
}
+ResumableUDPSocket::ResumableUDPSocket(const std::string& owner_extension_id)
+ : UDPSocket(owner_extension_id),
+ persistent_(false),
+ buffer_size_(0) {
+}
+
+const std::string& ResumableUDPSocket::name() const {
+ return name_;
+}
+
+void ResumableUDPSocket::set_name(const std::string& name) {
+ name_ = name;
+}
+
+bool ResumableUDPSocket::persistent() const {
+ return persistent_;
+}
+
+void ResumableUDPSocket::set_persistent(bool persistent) {
+ persistent_ = persistent;
+}
+
+int ResumableUDPSocket::buffer_size() const {
+ return buffer_size_;
+}
+
+void ResumableUDPSocket::set_buffer_size(int buffer_size) {
+ buffer_size_ = buffer_size;
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/socket/udp_socket.h b/chrome/browser/extensions/api/socket/udp_socket.h
index b301cb2..4989e27 100644
--- a/chrome/browser/extensions/api/socket/udp_socket.h
+++ b/chrome/browser/extensions/api/socket/udp_socket.h
@@ -74,6 +74,33 @@ class UDPSocket : public Socket {
std::vector<std::string> multicast_groups_;
};
+// UDP Socket instances from the "sockets.udp" namespace. These are regular
+// socket objects with additional properties related to the behavior defined in
+// the "sockets.udp" namespace.
+class ResumableUDPSocket : public UDPSocket {
+ public:
+ explicit ResumableUDPSocket(const std::string& owner_extension_id);
+
+ const std::string& name() const;
+ void set_name(const std::string& name);
+
+ virtual bool persistent() const OVERRIDE;
+ void set_persistent(bool persistent);
+
+ int buffer_size() const;
+ void set_buffer_size(int buffer_size);
+
+ private:
+ friend class ApiResourceManager<ResumableUDPSocket>;
+ static const char* service_name() {
+ return "ResumableUDPSocketManager";
+ }
+
+ std::string name_;
+ bool persistent_;
+ int buffer_size_;
+};
+
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKET_UDP_SOCKET_H_
diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc
new file mode 100644
index 0000000..c7a6e81
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.cc
@@ -0,0 +1,471 @@
+// 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_udp/sockets_udp_api.h"
+
+#include "chrome/browser/extensions/api/socket/udp_socket.h"
+#include "chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.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"
+
+namespace extensions {
+namespace api {
+
+using content::SocketPermissionRequest;
+
+const char kSocketNotFoundError[] = "Socket not found";
+const char kPermissionError[] = "App does not have permission";
+const char kWildcardAddress[] = "*";
+const int kWildcardPort = 0;
+
+UDPSocketAsyncApiFunction::~UDPSocketAsyncApiFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+ UDPSocketAsyncApiFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableUDPSocket>()).Pass();
+}
+
+ResumableUDPSocket* UDPSocketAsyncApiFunction::GetUdpSocket(int socket_id) {
+ return static_cast<ResumableUDPSocket*>(GetSocket(socket_id));
+}
+
+UDPSocketExtensionWithDnsLookupFunction::
+ ~UDPSocketExtensionWithDnsLookupFunction() {}
+
+scoped_ptr<SocketResourceManagerInterface>
+ UDPSocketExtensionWithDnsLookupFunction::CreateSocketResourceManager() {
+ return scoped_ptr<SocketResourceManagerInterface>(
+ new SocketResourceManager<ResumableUDPSocket>()).Pass();
+}
+
+ResumableUDPSocket* UDPSocketExtensionWithDnsLookupFunction::GetUdpSocket(
+ int socket_id) {
+ return static_cast<ResumableUDPSocket*>(GetSocket(socket_id));
+}
+
+linked_ptr<sockets_udp::SocketInfo> CreateSocketInfo(
+ int socket_id,
+ ResumableUDPSocket* socket) {
+ linked_ptr<sockets_udp::SocketInfo> socket_info(
+ new sockets_udp::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();
+ if (socket->buffer_size() > 0) {
+ socket_info->buffer_size.reset(new int(socket->buffer_size()));
+ }
+
+ // 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(ResumableUDPSocket* socket,
+ sockets_udp::SocketProperties* properties) {
+ if (properties->name.get()) {
+ socket->set_name(*properties->name.get());
+ }
+ if (properties->persistent.get()) {
+ socket->set_persistent(*properties->persistent.get());
+ }
+ if (properties->buffer_size.get()) {
+ socket->set_buffer_size(*properties->buffer_size.get());
+ }
+}
+
+SocketsUdpCreateFunction::SocketsUdpCreateFunction() {}
+
+SocketsUdpCreateFunction::~SocketsUdpCreateFunction() {}
+
+bool SocketsUdpCreateFunction::Prepare() {
+ params_ = sockets_udp::Create::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpCreateFunction::Work() {
+ ResumableUDPSocket* socket = new ResumableUDPSocket(extension_->id());
+
+ sockets_udp::SocketProperties* properties = params_.get()->properties.get();
+ if (properties) {
+ SetSocketProperties(socket, properties);
+ }
+
+ sockets_udp::CreateInfo create_info;
+ create_info.socket_id = AddSocket(socket);
+ results_ = sockets_udp::Create::Results::Create(create_info);
+}
+
+SocketsUdpUpdateFunction::SocketsUdpUpdateFunction() {}
+
+SocketsUdpUpdateFunction::~SocketsUdpUpdateFunction() {}
+
+bool SocketsUdpUpdateFunction::Prepare() {
+ params_ = sockets_udp::Update::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpUpdateFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SetSocketProperties(socket, &params_.get()->properties);
+ results_ = sockets_udp::Update::Results::Create();
+}
+
+SocketsUdpBindFunction::SocketsUdpBindFunction() {}
+
+SocketsUdpBindFunction::~SocketsUdpBindFunction() {}
+
+bool SocketsUdpBindFunction::Prepare() {
+ params_ = sockets_udp::Bind::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpBindFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_BIND, params_->address, params_->port);
+ if (!PermissionsData::CheckAPIPermissionWithParam(
+ GetExtension(),
+ APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ int net_result = socket->Bind(params_->address, params_->port);
+ if (net_result == net::OK) {
+ UDPSocketEventDispatcher::Get(profile())->OnSocketBind(extension_->id(),
+ params_->socket_id);
+ }
+
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::Bind::Results::Create(net_result);
+}
+
+SocketsUdpSendFunction::SocketsUdpSendFunction()
+ : io_buffer_size_(0) {}
+
+SocketsUdpSendFunction::~SocketsUdpSendFunction() {}
+
+bool SocketsUdpSendFunction::Prepare() {
+ params_ = sockets_udp::Send::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ io_buffer_size_ = params_->data.size();
+ io_buffer_ = new net::WrappedIOBuffer(params_->data.data());
+ return true;
+}
+
+void SocketsUdpSendFunction::AsyncWorkStart() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_SEND_TO,
+ params_->address,
+ params_->port);
+ if (!PermissionsData::CheckAPIPermissionWithParam(
+ GetExtension(),
+ APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ StartDnsLookup(params_->address);
+}
+
+void SocketsUdpSendFunction::AfterDnsLookup(int lookup_result) {
+ if (lookup_result == net::OK) {
+ StartSendTo();
+ } else {
+ SetSendResult(lookup_result, -1);
+ }
+}
+
+void SocketsUdpSendFunction::StartSendTo() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ AsyncWorkCompleted();
+ return;
+ }
+
+ socket->SendTo(io_buffer_, io_buffer_size_, resolved_address_, params_->port,
+ base::Bind(&SocketsUdpSendFunction::OnCompleted, this));
+}
+
+void SocketsUdpSendFunction::OnCompleted(int net_result) {
+ if (net_result >= net::OK) {
+ SetSendResult(net::OK, net_result);
+ } else {
+ SetSendResult(net_result, -1);
+ }
+}
+
+void SocketsUdpSendFunction::SetSendResult(int net_result, int bytes_written) {
+ CHECK(net_result <= net::OK) << "Network status code must be < 0";
+
+ sockets_udp::SendInfo send_info;
+ send_info.result = net_result;
+ if (net_result == net::OK) {
+ send_info.bytes_written.reset(new int(bytes_written));
+ }
+
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::Send::Results::Create(send_info);
+ AsyncWorkCompleted();
+}
+
+SocketsUdpCloseFunction::SocketsUdpCloseFunction() {}
+
+SocketsUdpCloseFunction::~SocketsUdpCloseFunction() {}
+
+bool SocketsUdpCloseFunction::Prepare() {
+ params_ = sockets_udp::Close::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpCloseFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ RemoveSocket(params_->socket_id);
+ results_ = sockets_udp::Close::Results::Create();
+}
+
+SocketsUdpGetInfoFunction::SocketsUdpGetInfoFunction() {}
+
+SocketsUdpGetInfoFunction::~SocketsUdpGetInfoFunction() {}
+
+bool SocketsUdpGetInfoFunction::Prepare() {
+ params_ = sockets_udp::GetInfo::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpGetInfoFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ linked_ptr<sockets_udp::SocketInfo> socket_info =
+ CreateSocketInfo(params_->socket_id, socket);
+ results_ = sockets_udp::GetInfo::Results::Create(*socket_info);
+}
+
+SocketsUdpGetSocketsFunction::SocketsUdpGetSocketsFunction() {}
+
+SocketsUdpGetSocketsFunction::~SocketsUdpGetSocketsFunction() {}
+
+bool SocketsUdpGetSocketsFunction::Prepare() {
+ return true;
+}
+
+void SocketsUdpGetSocketsFunction::Work() {
+ std::vector<linked_ptr<sockets_udp::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;
+ ResumableUDPSocket* socket = GetUdpSocket(socket_id);
+ if (socket) {
+ socket_infos.push_back(CreateSocketInfo(socket_id, socket));
+ }
+ }
+ }
+ results_ = sockets_udp::GetSockets::Results::Create(socket_infos);
+}
+
+SocketsUdpJoinGroupFunction::SocketsUdpJoinGroupFunction() {}
+
+SocketsUdpJoinGroupFunction::~SocketsUdpJoinGroupFunction() {}
+
+bool SocketsUdpJoinGroupFunction::Prepare() {
+ params_ = sockets_udp::JoinGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpJoinGroupFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+
+ if (!PermissionsData::CheckAPIPermissionWithParam(
+ GetExtension(), APIPermission::kSocket, &param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ int net_result = socket->JoinGroup(params_->address);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::JoinGroup::Results::Create(net_result);
+}
+
+SocketsUdpLeaveGroupFunction::SocketsUdpLeaveGroupFunction() {}
+
+SocketsUdpLeaveGroupFunction::~SocketsUdpLeaveGroupFunction() {}
+
+bool SocketsUdpLeaveGroupFunction::Prepare() {
+ params_ = api::sockets_udp::LeaveGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpLeaveGroupFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!PermissionsData::CheckAPIPermissionWithParam(GetExtension(),
+ APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ int net_result = socket->LeaveGroup(params_->address);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::LeaveGroup::Results::Create(net_result);
+}
+
+SocketsUdpSetMulticastTimeToLiveFunction::
+ SocketsUdpSetMulticastTimeToLiveFunction() {}
+
+SocketsUdpSetMulticastTimeToLiveFunction::
+ ~SocketsUdpSetMulticastTimeToLiveFunction() {}
+
+bool SocketsUdpSetMulticastTimeToLiveFunction::Prepare() {
+ params_ = api::sockets_udp::SetMulticastTimeToLive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpSetMulticastTimeToLiveFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ int net_result = socket->SetMulticastTimeToLive(params_->ttl);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::SetMulticastTimeToLive::Results::Create(net_result);
+}
+
+SocketsUdpSetMulticastLoopbackModeFunction::
+ SocketsUdpSetMulticastLoopbackModeFunction() {}
+
+SocketsUdpSetMulticastLoopbackModeFunction::
+ ~SocketsUdpSetMulticastLoopbackModeFunction() {}
+
+bool SocketsUdpSetMulticastLoopbackModeFunction::Prepare() {
+ params_ = api::sockets_udp::SetMulticastLoopbackMode::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpSetMulticastLoopbackModeFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ int net_result = socket->SetMulticastLoopbackMode(params_->enabled);
+ if (net_result != net::OK)
+ error_ = net::ErrorToString(net_result);
+ results_ = sockets_udp::SetMulticastLoopbackMode::Results::Create(net_result);
+}
+
+SocketsUdpGetJoinedGroupsFunction::SocketsUdpGetJoinedGroupsFunction() {}
+
+SocketsUdpGetJoinedGroupsFunction::~SocketsUdpGetJoinedGroupsFunction() {}
+
+bool SocketsUdpGetJoinedGroupsFunction::Prepare() {
+ params_ = api::sockets_udp::GetJoinedGroups::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketsUdpGetJoinedGroupsFunction::Work() {
+ ResumableUDPSocket* socket = GetUdpSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!PermissionsData::CheckAPIPermissionWithParam(
+ GetExtension(),
+ APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ return;
+ }
+
+ const std::vector<std::string>& groups = socket->GetJoinedGroups();
+ results_ = sockets_udp::GetJoinedGroups::Results::Create(groups);
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h
new file mode 100644
index 0000000..b971c4f
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h
@@ -0,0 +1,258 @@
+// 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_UDP_SOCKETS_UDP_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_SOCKETS_UDP_API_H_
+
+#include "chrome/browser/extensions/api/socket/socket_api.h"
+#include "chrome/common/extensions/api/sockets_udp.h"
+
+namespace extensions {
+class ResumableUDPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+class UDPSocketAsyncApiFunction : public SocketAsyncApiFunction {
+ protected:
+ virtual ~UDPSocketAsyncApiFunction();
+
+ virtual scoped_ptr<SocketResourceManagerInterface>
+ CreateSocketResourceManager() OVERRIDE;
+
+ ResumableUDPSocket* GetUdpSocket(int socket_id);
+};
+
+class UDPSocketExtensionWithDnsLookupFunction
+ : public SocketExtensionWithDnsLookupFunction {
+ protected:
+ virtual ~UDPSocketExtensionWithDnsLookupFunction();
+
+ virtual scoped_ptr<SocketResourceManagerInterface>
+ CreateSocketResourceManager() OVERRIDE;
+
+ ResumableUDPSocket* GetUdpSocket(int socket_id);
+};
+
+class SocketsUdpCreateFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.create", SOCKETS_UDP_CREATE)
+
+ SocketsUdpCreateFunction();
+
+ protected:
+ virtual ~SocketsUdpCreateFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketsUdpUnitTest, Create);
+ scoped_ptr<sockets_udp::Create::Params> params_;
+};
+
+class SocketsUdpUpdateFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.update", SOCKETS_UDP_UPDATE)
+
+ SocketsUdpUpdateFunction();
+
+ protected:
+ virtual ~SocketsUdpUpdateFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::Update::Params> params_;
+};
+
+class SocketsUdpBindFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.bind", SOCKETS_UDP_BIND)
+
+ SocketsUdpBindFunction();
+
+ protected:
+ virtual ~SocketsUdpBindFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::Bind::Params> params_;
+};
+
+class SocketsUdpSendFunction : public UDPSocketExtensionWithDnsLookupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.send", SOCKETS_UDP_SEND)
+
+ SocketsUdpSendFunction();
+
+ protected:
+ virtual ~SocketsUdpSendFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void AsyncWorkStart() OVERRIDE;
+ void OnCompleted(int net_result);
+ void SetSendResult(int net_result, int bytes_written);
+
+ // SocketExtensionWithDnsLookupFunction:
+ virtual void AfterDnsLookup(int lookup_result) OVERRIDE;
+
+ private:
+ void StartSendTo();
+
+ scoped_ptr<sockets_udp::Send::Params> params_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+ size_t io_buffer_size_;
+};
+
+class SocketsUdpCloseFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.close", SOCKETS_UDP_CLOSE)
+
+ SocketsUdpCloseFunction();
+
+ protected:
+ virtual ~SocketsUdpCloseFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::Close::Params> params_;
+};
+
+class SocketsUdpGetInfoFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.getInfo", SOCKETS_UDP_GETINFO)
+
+ SocketsUdpGetInfoFunction();
+
+ protected:
+ virtual ~SocketsUdpGetInfoFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::GetInfo::Params> params_;
+};
+
+class SocketsUdpGetSocketsFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.getSockets", SOCKETS_UDP_GETSOCKETS)
+
+ SocketsUdpGetSocketsFunction();
+
+ protected:
+ virtual ~SocketsUdpGetSocketsFunction();
+
+ // AsyncApiFunction:
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+};
+
+class SocketsUdpJoinGroupFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.joinGroup", SOCKETS_UDP_JOINGROUP)
+
+ SocketsUdpJoinGroupFunction();
+
+ protected:
+ virtual ~SocketsUdpJoinGroupFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::JoinGroup::Params> params_;
+};
+
+class SocketsUdpLeaveGroupFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.leaveGroup", SOCKETS_UDP_LEAVEGROUP)
+
+ SocketsUdpLeaveGroupFunction();
+
+ protected:
+ virtual ~SocketsUdpLeaveGroupFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::LeaveGroup::Params> params_;
+};
+
+class SocketsUdpSetMulticastTimeToLiveFunction
+ : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.setMulticastTimeToLive",
+ SOCKETS_UDP_SETMULTICASTTIMETOLIVE)
+
+ SocketsUdpSetMulticastTimeToLiveFunction();
+
+ protected:
+ virtual ~SocketsUdpSetMulticastTimeToLiveFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::SetMulticastTimeToLive::Params> params_;
+};
+
+class SocketsUdpSetMulticastLoopbackModeFunction
+ : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.setMulticastLoopbackMode",
+ SOCKETS_UDP_SETMULTICASTLOOPBACKMODE)
+
+ SocketsUdpSetMulticastLoopbackModeFunction();
+
+ protected:
+ virtual ~SocketsUdpSetMulticastLoopbackModeFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::SetMulticastLoopbackMode::Params> params_;
+};
+
+class SocketsUdpGetJoinedGroupsFunction : public UDPSocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("sockets.udp.getJoinedGroups",
+ SOCKETS_UDP_GETJOINEDGROUPS)
+
+ SocketsUdpGetJoinedGroupsFunction();
+
+ protected:
+ virtual ~SocketsUdpGetJoinedGroupsFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<sockets_udp::GetJoinedGroups::Params> params_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_SOCKETS_UDP_API_H_
diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc
new file mode 100644
index 0000000..08d3c83
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_api_unittest.cc
@@ -0,0 +1,79 @@
+// 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/udp_socket.h"
+#include "chrome/browser/extensions/api/sockets_udp/sockets_udp_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 {
+
+BrowserContextKeyedService* ApiResourceManagerTestFactory(
+ content::BrowserContext* profile) {
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+ return ApiResourceManager<ResumableUDPSocket>::
+ CreateApiResourceManagerForTest(static_cast<Profile*>(profile), id);
+}
+
+class SocketsUdpUnitTest : public BrowserWithTestWindowTest {
+ public:
+ virtual void SetUp() {
+ BrowserWithTestWindowTest::SetUp();
+
+ ApiResourceManager<ResumableUDPSocket>::GetFactoryInstance()->
+ SetTestingFactoryAndUse(browser()->profile(),
+ ApiResourceManagerTestFactory);
+
+ 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(SocketsUdpUnitTest, Create) {
+ // Get BrowserThread
+ content::BrowserThread::ID id;
+ CHECK(content::BrowserThread::GetCurrentThreadIdentifier(&id));
+
+ // Create SocketCreateFunction and put it on BrowserThread
+ SocketsUdpCreateFunction *function = new SocketsUdpCreateFunction();
+ function->set_work_thread_id(id);
+
+ // Run tests
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDict(
+ function, "[{\"resumable\": true, \"name\": \"foo\"}]"));
+ ASSERT_TRUE(result.get());
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc b/chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc
new file mode 100644
index 0000000..943bbf1
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_udp/sockets_udp_apitest.cc
@@ -0,0 +1,125 @@
+// 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_udp/sockets_udp_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/common/chrome_switches.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;
+
+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 SocketsUdpApiTest : public ExtensionApiTest {
+ public:
+ SocketsUdpApiTest() : 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(SocketsUdpApiTest, SocketsUdpCreateGood) {
+ scoped_refptr<extensions::api::SocketsUdpCreateFunction>
+ socket_create_function(new extensions::api::SocketsUdpCreateFunction());
+ 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));
+ EXPECT_TRUE(socketId > 0);
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpExtension) {
+ scoped_ptr<net::SpawnedTestServer> test_server(
+ new net::SpawnedTestServer(
+ net::SpawnedTestServer::TYPE_UDP_ECHO,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data"))));
+ EXPECT_TRUE(test_server->Start());
+
+ net::HostPortPair host_port_pair = test_server->host_port_pair();
+ int port = host_port_pair.port();
+ ASSERT_TRUE(port > 0);
+
+ // Test that sendTo() is properly resolving hostnames.
+ host_port_pair.set_host("LOCALhost");
+
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+
+ ExtensionTestMessageListener listener("info_please", true);
+
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("sockets_udp/api")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("udp:%s:%d", host_port_pair.host().c_str(), port));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(SocketsUdpApiTest, SocketsUdpMulticast) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+ ExtensionTestMessageListener listener("info_please", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("sockets_udp/api")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("multicast:%s:%d", kHostname.c_str(), kPort));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc
new file mode 100644
index 0000000..2331417
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.cc
@@ -0,0 +1,125 @@
+// 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_udp/udp_socket_event_dispatcher.h"
+
+#include "chrome/browser/extensions/api/socket/udp_socket.h"
+#include "chrome/browser/extensions/event_router.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "net/base/net_errors.h"
+
+namespace extensions {
+namespace api {
+
+static base::LazyInstance<ProfileKeyedAPIFactory<UDPSocketEventDispatcher> >
+g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+ProfileKeyedAPIFactory<UDPSocketEventDispatcher>*
+ UDPSocketEventDispatcher::GetFactoryInstance() {
+ return &g_factory.Get();
+}
+
+// static
+UDPSocketEventDispatcher* UDPSocketEventDispatcher::Get(Profile* profile) {
+ return ProfileKeyedAPIFactory<UDPSocketEventDispatcher>::GetForProfile(
+ profile);
+}
+
+UDPSocketEventDispatcher::UDPSocketEventDispatcher(Profile* profile)
+ : thread_id_(Socket::kThreadId),
+ profile_(profile) {
+}
+
+UDPSocketEventDispatcher::~UDPSocketEventDispatcher() {
+}
+
+ResumableUDPSocket* UDPSocketEventDispatcher::GetUdpSocket(
+ const std::string& extension_id,
+ int socket_id) {
+ DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
+
+ ApiResourceManager<ResumableUDPSocket>* manager =
+ ApiResourceManager<ResumableUDPSocket>::Get(profile_);
+ DCHECK(manager) << "There is no socket manager. "
+ "If this assertion is failing during a test, then it is likely that "
+ "TestExtensionSystem is failing to provide an instance of "
+ "ApiResourceManager<ResumableUDPSocket>.";
+
+ return manager->Get(extension_id, socket_id);
+}
+
+void UDPSocketEventDispatcher::OnSocketBind(const std::string& extension_id,
+ int socket_id) {
+ DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
+ StartReceive(extension_id, socket_id);
+}
+
+void UDPSocketEventDispatcher::StartReceive(const std::string& extension_id,
+ int socket_id) {
+ DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
+ ResumableUDPSocket* socket = GetUdpSocket(extension_id, socket_id);
+ if (socket == NULL) {
+ // This can happen if the socket is closed while our callback is active.
+ return;
+ }
+ DCHECK(extension_id == socket->owner_extension_id())
+ << "Socket has wrong owner.";
+
+ int buffer_size = (socket->buffer_size() <= 0 ? 4096 : socket->buffer_size());
+ socket->RecvFrom(buffer_size,
+ base::Bind(&UDPSocketEventDispatcher::ReceiveCallback,
+ AsWeakPtr(),
+ extension_id,
+ socket_id));
+}
+
+void UDPSocketEventDispatcher::ReceiveCallback(
+ const std::string& extension_id,
+ const int socket_id,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string& address,
+ int port) {
+ DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
+
+ // Note: if "bytes_read" < 0, there was a network error, and "bytes_read" is
+ // a value from "net::ERR_".
+
+ if (bytes_read >= 0) {
+ // Dispatch event.
+ sockets_udp::ReceiveInfo receive_info;
+ receive_info.socket_id = socket_id;
+ receive_info.data = std::string(io_buffer->data(), bytes_read);
+ receive_info.remote_address = address;
+ receive_info.remote_port = port;
+ scoped_ptr<base::ListValue> args =
+ sockets_udp::OnReceive::Create(receive_info);
+ scoped_ptr<Event> event(
+ new Event(sockets_udp::OnReceive::kEventName, args.Pass()));
+ ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
+ extension_id, event.Pass());
+
+ // Post a task to delay the read until the socket is available, as
+ // calling StartReceive at this point would error with ERR_IO_PENDING.
+ content::BrowserThread::PostTask(thread_id_, FROM_HERE,
+ base::Bind(&UDPSocketEventDispatcher::StartReceive,
+ AsWeakPtr(), extension_id, socket_id));
+ } else {
+ // Dispatch event but don't start another read to avoid infinite read if
+ // we have a persistent network error.
+ sockets_udp::ReceiveErrorInfo receive_error_info;
+ receive_error_info.socket_id = socket_id;
+ receive_error_info.result = bytes_read;
+ scoped_ptr<base::ListValue> args =
+ sockets_udp::OnReceiveError::Create(receive_error_info);
+ scoped_ptr<Event> event(
+ new Event(sockets_udp::OnReceiveError::kEventName, args.Pass()));
+ ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
+ extension_id, event.Pass());
+ }
+}
+
+} // namespace api
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h
new file mode 100644
index 0000000..c5672a9
--- /dev/null
+++ b/chrome/browser/extensions/api/sockets_udp/udp_socket_event_dispatcher.h
@@ -0,0 +1,66 @@
+// 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_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_UDP_SOCKET_EVENT_DISPATCHER_H_
+
+#include "chrome/browser/extensions/api/sockets_udp/sockets_udp_api.h"
+
+namespace extensions {
+class ResumableUDPSocket;
+}
+
+namespace extensions {
+namespace api {
+
+// Dispatch events related to "sockets.udp" sockets from callback on native
+// socket instances. There is one instance per profile.
+class UDPSocketEventDispatcher
+ : public ProfileKeyedAPI,
+ public base::SupportsWeakPtr<UDPSocketEventDispatcher> {
+ public:
+ explicit UDPSocketEventDispatcher(Profile* profile);
+ virtual ~UDPSocketEventDispatcher();
+
+ // Socket is active, start receving from it.
+ void OnSocketBind(const std::string& extension_id, int socket_id);
+
+ // ProfileKeyedAPI implementation.
+ static ProfileKeyedAPIFactory<UDPSocketEventDispatcher>* GetFactoryInstance();
+
+ // Convenience method to get the SocketEventDispatcher for a profile.
+ static UDPSocketEventDispatcher* Get(Profile* profile);
+
+ private:
+ friend class ProfileKeyedAPIFactory<UDPSocketEventDispatcher>;
+
+ ResumableUDPSocket* GetUdpSocket(const std::string& extension_id,
+ int socket_id);
+
+ // Start a receive and register a callback.
+ void StartReceive(const std::string& extension_id, int socket_id);
+
+ // Called when socket receive data.
+ void ReceiveCallback(const std::string& extension_id,
+ const int socket_id,
+ int bytes_read,
+ scoped_refptr<net::IOBuffer> io_buffer,
+ const std::string& address,
+ int port);
+
+ // ProfileKeyedAPI implementation.
+ static const char* service_name() {
+ return "UDPSocketEventDispatcher";
+ }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+
+ // Usually IO thread (except for unit testing).
+ content::BrowserThread::ID thread_id_;
+ Profile* const profile_;
+};
+
+} // namespace api
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKETS_UDP_UDP_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 f6b688d..b6565db 100644
--- a/chrome/browser/extensions/extension_function_histogram_value.h
+++ b/chrome/browser/extensions/extension_function_histogram_value.h
@@ -596,6 +596,18 @@ enum HistogramValue {
PROCESSES_GETPROCESSINFO,
PROCESSES_GETPROCESSIDFORTAB,
PROCESSES_TERMINATE,
+ SOCKETS_UDP_CREATE,
+ SOCKETS_UDP_UPDATE,
+ SOCKETS_UDP_BIND,
+ SOCKETS_UDP_SEND,
+ SOCKETS_UDP_CLOSE,
+ SOCKETS_UDP_GETINFO,
+ SOCKETS_UDP_GETSOCKETS,
+ SOCKETS_UDP_JOINGROUP,
+ SOCKETS_UDP_LEAVEGROUP,
+ SOCKETS_UDP_SETMULTICASTTIMETOLIVE,
+ SOCKETS_UDP_SETMULTICASTLOOPBACKMODE,
+ SOCKETS_UDP_GETJOINEDGROUPS,
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 af227d1..90115a6 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -46,6 +46,8 @@
#include "chrome/browser/extensions/api/serial/serial_connection.h"
#include "chrome/browser/extensions/api/session_restore/session_restore_api.h"
#include "chrome/browser/extensions/api/socket/socket.h"
+#include "chrome/browser/extensions/api/socket/udp_socket.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"
#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry_factory.h"
@@ -197,11 +199,14 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() {
extensions::ActivityLogFactory::GetInstance();
extensions::ActivityLogAPI::GetFactoryInstance();
extensions::AlarmManager::GetFactoryInstance();
+ extensions::ApiResourceManager<extensions::ResumableUDPSocket>::
+ GetFactoryInstance();
extensions::ApiResourceManager<extensions::SerialConnection>::
GetFactoryInstance();
extensions::ApiResourceManager<extensions::Socket>::GetFactoryInstance();
extensions::ApiResourceManager<extensions::UsbDeviceResource>::
GetFactoryInstance();
+ extensions::api::UDPSocketEventDispatcher::GetFactoryInstance();
extensions::AudioAPI::GetFactoryInstance();
extensions::BookmarksAPI::GetFactoryInstance();
extensions::BluetoothAPIFactory::GetInstance();
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index c477498..82fb593 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -425,6 +425,10 @@
'browser/extensions/api/socket/tcp_socket.h',
'browser/extensions/api/socket/udp_socket.cc',
'browser/extensions/api/socket/udp_socket.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',
+ 'browser/extensions/api/sockets_udp/sockets_udp_api.h',
'browser/extensions/api/spellcheck/spellcheck_api.cc',
'browser/extensions/api/spellcheck/spellcheck_api.h',
'browser/extensions/api/storage/leveldb_settings_storage_factory.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index cfb0361..a4cc711 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1334,6 +1334,7 @@
'browser/extensions/api/serial/serial_apitest.cc',
'browser/extensions/api/session_restore/session_restore_apitest.cc',
'browser/extensions/api/socket/socket_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',
'browser/extensions/api/sync_file_system/sync_file_system_apitest.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 6d7f590..89acb7f 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -822,6 +822,7 @@
'browser/extensions/api/socket/socket_api_unittest.cc',
'browser/extensions/api/socket/tcp_socket_unittest.cc',
'browser/extensions/api/socket/udp_socket_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',
'browser/extensions/api/storage/settings_quota_unittest.cc',
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index e471b94..5487034 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -470,6 +470,11 @@
"dependencies": ["permission:socket"],
"contexts": ["blessed_extension"]
},
+ "sockets.udp": {
+ "dependencies": ["permission:sockets.udp"],
+ "channel": "dev",
+ "contexts": ["blessed_extension"]
+ },
"storage": {
"dependencies": ["permission:storage"],
"contexts": ["blessed_extension", "unblessed_extension", "content_script"]
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 1dbc60d..4b7a319 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -562,6 +562,10 @@
"kodldpbjkkmmnilagfdheibampofhaom"
]
}],
+ "sockets.udp": {
+ "channel": "dev",
+ "extension_types": ["platform_app"]
+ },
"syncFileSystem": {
"channel": "stable",
"extension_types": ["platform_app"]
diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp
index 5c84ab1..ddb4dd1 100644
--- a/chrome/common/extensions/api/api.gyp
+++ b/chrome/common/extensions/api/api.gyp
@@ -89,6 +89,7 @@
'serial.idl',
'session_restore.json',
'socket.idl',
+ 'sockets_udp.idl',
'storage.json',
'sync_file_system.idl',
'system_indicator.idl',
diff --git a/chrome/common/extensions/api/sockets_udp.idl b/chrome/common/extensions/api/sockets_udp.idl
new file mode 100644
index 0000000..bfc432e
--- /dev/null
+++ b/chrome/common/extensions/api/sockets_udp.idl
@@ -0,0 +1,287 @@
+// 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.udp</code> API to send and receive data over the
+// network using UDP connections. This API supersedes the UDP functionality
+// previously found in the "socket" API. Note that the socket ids created from
+// this namespace are not compatible with ids created in other namespaces.
+namespace sockets.udp {
+ // 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 is left 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;
+
+ // The size of the buffer used to receive data. If the buffer is too small
+ // to receive the UDP packet, data is lost. The default value is 4096.
+ long? bufferSize;
+ };
+
+ // Result of <code>create</code> call.
+ dictionary CreateInfo {
+ // The ID of the newly created 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>bind</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback BindCallback = void (long result);
+
+ // Result of the <code>send</code> method.
+ dictionary SendInfo {
+ // The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ long result;
+
+ // The number of bytes sent (if result == 0)
+ long? bytesWritten;
+ };
+
+ // Callback from the <code>send</code> method.
+ // |sendInfo| : Result of the <code>send</code> method.
+ callback SendCallback = void (SendInfo sendInfo);
+
+ // Callback from the <code>close<code> method.
+ callback CloseCallback = void ();
+
+ // Callback from the <code>update</code> method.
+ callback UpdateCallback = void ();
+
+ // Result of the <code>getInfo</code> method.
+ dictionary SocketInfo {
+ // The socket identifier.
+ long socketId;
+
+ // Flag indicating whether the socket is left open when the application is
+ // suspended (see <code>SocketProperties.persistent</code>).
+ boolean persistent;
+
+ // Application-defined string associated with the socket.
+ DOMString? name;
+
+ // The size of the buffer used to receive data. If no buffer size has been
+ // specified explictly, the value is not provided.
+ long? bufferSize;
+
+ // If the underlying socket is bound, contains its local
+ // IPv4/6 address.
+ DOMString? localAddress;
+
+ // If the underlying socket is bound, 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);
+
+ // Callback from the <code>joinGroup</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback JoinGroupCallback = void (long result);
+
+ // Callback from the <code>leaveGroup</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback LeaveGroupCallback = void (long result);
+
+ // Callback from the <code>setMulticastTimeToLive</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback SetMulticastTimeToLiveCallback = void (long result);
+
+ // Callback from the <code>setMulticastLoopbackMode</code> method.
+ // |result| : The result code returned from the underlying network call.
+ // A negative value indicates an error.
+ callback SetMulticastLoopbackModeCallback = void (long result);
+
+ // Callback from the <code>getJoinedGroupsCallback</code> method.
+ // |groups| : Array of groups the socket joined.
+ callback GetJoinedGroupsCallback = void (DOMString[] groups);
+
+ // Data from an <code>onReceive</code> event.
+ dictionary ReceiveInfo {
+ // The socket ID.
+ long socketId;
+
+ // The UDP packet content (truncated to the current buffer size).
+ ArrayBuffer data;
+
+ // The address of the host the packet comes from.
+ DOMString remoteAddress;
+
+ // The port of the host the packet comes from.
+ long remotePort;
+ };
+
+ // Data from an <code>onReceiveError</code> event.
+ dictionary ReceiveErrorInfo {
+ // The socket ID.
+ long socketId;
+
+ // The result code returned from the underlying recvfrom() call.
+ long result;
+ };
+
+ interface Functions {
+ // Creates a UDP 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 ID.
+ // |properties| : The properties to update.
+ // |callback| : Called when the properties are updated.
+ static void update(long socketId,
+ SocketProperties properties,
+ UpdateCallback callback);
+
+ // Binds the local address for socket. When the <code>bind</code> operation
+ // completes successfully, <code>onReceive</code> events are raised
+ // when UDP packets arrive on the address/port specified. If a network
+ // error occurs while the runtime is receiving packets, an
+ // <code>onReceiveError</code> event is raised, at which point no more
+ // <code>onReceive</code> events will be raised for this socket.
+ // |socketId| : The socket ID.
+ // |address| : The address of the local machine. DNS name, IPv4 and IPv6
+ // formats are supported. Use "0.0.0.0" to accept packets from all local
+ // available network interfaces.
+ // |port| : The port of the local machine. Use "0" to bind to a free port.
+ // |callback| : Called when the <code>bind</code> operation completes.
+ static void bind(long socketId,
+ DOMString address,
+ long port,
+ BindCallback callback);
+
+ // Sends data on the given UDP socket to the given address and port.
+ // |socketId| : The socket ID.
+ // |data| : The data to write.
+ // |address| : The address of the remote machine.
+ // |port| : The port of the remote machine.
+ // |callback| : Called when the <code>send</code> operation completes.
+ static void send(long socketId,
+ ArrayBuffer data,
+ DOMString address,
+ long port,
+ SendCallback callback);
+
+ // Closes the socket and releases the address/port the socket is bound to.
+ // Each socket created should be closed after use. The socket id is no
+ // 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 ID.
+ // |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 ID.
+ // |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);
+
+ // Joins the multicast group and starts to receive packets from that group.
+ // The socket must be bound to a local port before calling this method.
+ // |socketId| : The socket ID.
+ // |address| : The group address to join. Domain names are not supported.
+ // |callback| : Called when the <code>joinGroup</code> operation completes.
+ static void joinGroup(long socketId,
+ DOMString address,
+ JoinGroupCallback callback);
+
+ // Leaves the multicast group previously joined using
+ // <code>joinGroup</code>. This is only necessary to call if you plan to
+ // keep using the socketafterwards, since it will be done automatically by
+ // the OS when the socket is closed.
+ //
+ // Leaving the group will prevent the router from sending multicast
+ // datagrams to the local host, presuming no other process on the host is
+ // still joined to the group.
+ //
+ // |socketId| : The socket ID.
+ // |address| : The group address to leave. Domain names are not supported.
+ // |callback| : Called when the <code>leaveGroup</code> operation completes.
+ static void leaveGroup(long socketId,
+ DOMString address,
+ LeaveGroupCallback callback);
+
+ // Sets the time-to-live of multicast packets sent to the multicast group.
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socket ID.
+ // |ttl| : The time-to-live value.
+ // |callback| : Called when the configuration operation completes.
+ static void setMulticastTimeToLive(
+ long socketId,
+ long ttl,
+ SetMulticastTimeToLiveCallback callback);
+
+ // Sets whether multicast packets sent from the host to the multicast
+ // group will be looped back to the host.
+ //
+ // Note: the behavior of <code>setMulticastLoopbackMode</code> is slightly
+ // different between Windows and Unix-like systems. The inconsistency
+ // happens only when there is more than one application on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socket ID.
+ // |enabled| : Indicate whether to enable loopback mode.
+ // |callback| : Called when the configuration operation completes.
+ static void setMulticastLoopbackMode(
+ long socketId,
+ boolean enabled,
+ SetMulticastLoopbackModeCallback callback);
+
+ // Gets the multicast group addresses the socket is currently joined to.
+ // |socketId| : The socket ID.
+ // |callback| : Called with an array of strings of the result.
+ static void getJoinedGroups(long socketId,
+ GetJoinedGroupsCallback callback);
+ };
+
+ interface Events {
+ // Event raised when a UDP packet has been received for a given socket.
+ // |info| : The event data.
+ static void onReceive(ReceiveInfo info);
+
+ // Event raised when a network error occured while the runtime was waiting
+ // for data on the socket address and port. Once this event is raised, no
+ // more <code>onReceive</code> events will be raise for this socket.
+ // |info| : The event data.
+ static void onReceiveError(ReceiveErrorInfo info);
+ };
+};
diff --git a/chrome/common/extensions/permissions/api_permission.h b/chrome/common/extensions/permissions/api_permission.h
index 964c872..e03474f 100644
--- a/chrome/common/extensions/permissions/api_permission.h
+++ b/chrome/common/extensions/permissions/api_permission.h
@@ -117,6 +117,7 @@ class APIPermission {
kSerial,
kSessionRestore,
kSocket,
+ kSocketsUdp,
kStorage,
kStreamsPrivate,
kSyncFileSystem,
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index 491fa22..7cee99f 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -253,6 +253,7 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions()
{ APIPermission::kSocket, "socket",
APIPermissionInfo::kFlagCannotBeOptional, 0,
PermissionMessage::kNone, &CreateAPIPermission<SocketPermission> },
+ { APIPermission::kSocketsUdp, "sockets.udp" },
{ APIPermission::kAppCurrentWindowInternal, "app.currentWindowInternal" },
{ APIPermission::kAppRuntime, "app.runtime" },
{ APIPermission::kAppWindow, "app.window" },
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 637fb54..224a487 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -734,6 +734,7 @@ TEST(PermissionsTest, PermissionMessages) {
skip.insert(APIPermission::kFileSystem);
skip.insert(APIPermission::kFileSystemRetainEntries);
skip.insert(APIPermission::kSocket);
+ skip.insert(APIPermission::kSocketsUdp);
skip.insert(APIPermission::kUsbDevice);
PermissionsInfo* info = PermissionsInfo::GetInstance();
diff --git a/chrome/test/data/extensions/api_test/sockets_udp/api/background.js b/chrome/test/data/extensions/api_test/sockets_udp/api/background.js
new file mode 100644
index 0000000..23dd357
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_udp/api/background.js
@@ -0,0 +1,168 @@
+// 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 expectedResponsePattern = /0100000005320000005.{11}/;
+
+const socket = chrome.sockets.udp;
+var address;
+var bytesWritten = 0;
+var dataAsString;
+var dataRead = [];
+var port = -1;
+var socketId = 0;
+var succeeded = false;
+var waitCount = 0;
+
+// 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);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Test socket creation
+//
+
+var testSocketCreation = function() {
+ function onCreate(createInfo) {
+ function onGetInfo(info) {
+ if (info.localAddress || info.localPort) {
+ chrome.test.fail('Unconnected socket should not have local binding');
+ }
+
+ chrome.test.assertEq(createInfo.socketId, info.socketId);
+ chrome.test.assertEq(false, info.persistent);
+
+ socket.close(createInfo.socketId, function() {
+ socket.getInfo(createInfo.socketId, function(info) {
+ chrome.test.assertEq(undefined, info);
+ chrome.test.succeed();
+ });
+ });
+ }
+
+ chrome.test.assertTrue(createInfo.socketId > 0);
+
+ // Obtaining socket information before a connect() call should be safe, but
+ // return empty values.
+ socket.getInfo(createInfo.socketId, onGetInfo);
+ }
+
+ socket.create({}, onCreate);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Test socket send/receive
+//
+
+function waitForBlockingOperation() {
+ if (++waitCount < 10) {
+ setTimeout(waitForBlockingOperation, 1000);
+ } else {
+ // We weren't able to succeed in the given time.
+ chrome.test.fail("Operations didn't complete after " + waitCount + " " +
+ "seconds. Response so far was <" + dataAsString + ">.");
+ }
+}
+
+var testSending = function() {
+ dataRead = "";
+ succeeded = false;
+ waitCount = 0;
+
+ setTimeout(waitForBlockingOperation, 1000);
+ socket.create({}, function (socketInfo) {
+ console.log("socket created");
+ socketId = socketInfo.socketId;
+ chrome.test.assertTrue(socketId > 0, "failed to create socket");
+ socket.bind(socketId, "0.0.0.0", 0, function (result) {
+ console.log("socket bound to local host");
+ chrome.test.assertEq(0, result,
+ "Connect or bind failed with error " + result);
+ if (result == 0) {
+ socket.getInfo(socketId, function (result) {
+ console.log("got socket info");
+ chrome.test.assertTrue(
+ !!result.localAddress,
+ "Bound socket should always have local address");
+ chrome.test.assertTrue(
+ !!result.localPort,
+ "Bound socket should always have local port");
+
+ string2ArrayBuffer(request, function(arrayBuffer) {
+ socket.onReceiveError.addListener(function (info) {
+ chrome.test.fail("Socket receive error:" + info.result);
+ });
+ socket.onReceive.addListener(function (info) {
+ console.log(
+ "received bytes from echo server: " + info.data.byteLength);
+ if (socketId == info.socketId) {
+ arrayBuffer2String(info.data, function(s) {
+ dataAsString = s; // save this for error reporting
+ var match = !!s.match(expectedResponsePattern);
+ chrome.test.assertTrue(
+ match, "Received data does not match.");
+ succeeded = true;
+ chrome.test.succeed();
+ });
+ }
+ });
+ console.log(
+ "sending bytes to echo server: " + arrayBuffer.byteLength);
+ socket.send(socketId, arrayBuffer, address, port,
+ function(writeInfo) {
+ chrome.test.assertEq(0, writeInfo.result);
+ chrome.test.assertEq(
+ writeInfo.bytesWritten, arrayBuffer.byteLength);
+ });
+ });
+ });
+ }
+ });
+ });
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Test driver
+//
+
+var onMessageReply = function(message) {
+ var parts = message.split(":");
+ var test_type = parts[0];
+ address = parts[1];
+ port = parseInt(parts[2]);
+ console.log("Running tests, echo server " +
+ address + ":" + port);
+ if (test_type == 'multicast') {
+ console.log("Running multicast tests");
+ chrome.test.runTests([ testMulticast ]);
+ } else {
+ console.log("Running udp tests");
+ chrome.test.runTests([ testSocketCreation, testSending ]);
+ }
+};
+
+// 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_udp/api/manifest.json b/chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json
new file mode 100644
index 0000000..a8f9b17
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_udp/api/manifest.json
@@ -0,0 +1,15 @@
+{
+ "manifest_version": 2,
+ "name": "chrome.sockets.udp",
+ "version": "0.1",
+ "description": "end-to-end browser test for chrome.sockets.udp API",
+ "app": {
+ "background": {
+ "scripts": ["multicast.js", "background.js"]
+ }
+ },
+ "permissions": [
+ { "socket": ["udp-send-to", "udp-bind", "udp-multicast-membership"] },
+ "sockets.udp"
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js b/chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js
new file mode 100644
index 0000000..f16b2fb3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sockets_udp/api/multicast.js
@@ -0,0 +1,224 @@
+// 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.
+
+// Tests for multicast UDP socket.
+function testMulticast() {
+ function randomHexString(count) {
+ var result = '';
+ for (var i = 0; i < count; i++) {
+ result += (Math.random() * 16 >> 0).toString(16);
+ }
+ return result;
+ }
+
+ var kMulticastAddress = "237.132.100.133";
+ var kTestMessageLength = 128;
+ var kTestMessage = randomHexString(128);
+ var kPort = 11103;
+
+ function arrayBufferToString(arrayBuffer) {
+ // UTF-16LE
+ return String.fromCharCode.apply(String, new Uint16Array(arrayBuffer));
+ }
+
+ function stringToArrayBuffer(string) {
+ // UTF-16LE
+ var buf = new ArrayBuffer(string.length * 2);
+ var bufView = new Uint16Array(buf);
+ for (var i = 0, strLen = string.length; i < strLen; i++) {
+ bufView[i] = string.charCodeAt(i);
+ }
+ return buf;
+ }
+
+ // Registers a listener on receiving data on |socketId|.
+ // Calls |callback| with a |cancelled| argument of "false" when receiving data
+ // of excactly |kTestMessageLength| characters.
+ // Calls |callback| with a |cancelled| argument of "true" when the caller
+ // decides to invoke the returned function.
+ // Returns a function that can be invoked to "cancel" the operation (i.e.
+ // call |callback| with a |cancelled| argument of "true").
+ function waitForMessage(socketId, callback) {
+ var cancelled = false;
+ var relayCanceller = null;
+ socket.onReceive.addListener(function(info) {
+ console.log("Data received: " +
+ "socketId=" + info.socketId +
+ ", bytes=" + info.data.byteLength +
+ ", address=" + info.remoteAddress +
+ ", port=" + info.remotePort);
+ if (socketId != info.socketId)
+ return;
+
+ if (cancelled)
+ return;
+
+ if (info.data.byteLength == kTestMessageLength * 2 &&
+ kTestMessage === arrayBufferToString(info.data)) {
+ callback(false);
+ } else {
+ // Restart waiting.
+ relayCanceller = waitForMessage(socketId, callback);
+ }
+ });
+ return function canceller() {
+ if (relayCanceller) {
+ relayCanceller();
+ } else {
+ cancelled = true; // prevents callback from being called on receive.
+ callback(true);
+ }
+ };
+ }
+
+ function testMulticastSettings(nextTest) {
+ console.log("*************** testMulticastSettings");
+ socket.create({}, function (socketInfo) {
+ var socketId;
+ if (socketInfo) {
+ socketId = socketInfo.socketId;
+ socket.setMulticastTimeToLive(socketId, 0, function (result) {
+ chrome.test.assertEq(0, result,
+ "Error setting multicast time to live.");
+ socket.setMulticastTimeToLive(socketId, -3, function (result) {
+ chrome.test.assertEq(-4, result,
+ "Error setting multicast time to live.");
+ socket.setMulticastLoopbackMode(socketId, false,
+ function (result) {
+ chrome.test.assertEq(0, result,
+ "Error setting multicast loop back mode.");
+ socket.setMulticastLoopbackMode(socketId, true,
+ function (result) {
+ chrome.test.assertEq(0, result,
+ "Error setting multicast loop back mode.");
+ socket.close(socketId, function() {});
+ nextTest();
+ });
+ });
+ });
+ });
+ } else {
+ chrome.test.fail("Cannot create server udp socket");
+ }
+ });
+ }
+
+ function testSendMessage(message, address) {
+ // Send the UDP message to the address with multicast ttl = 0.
+ socket.create({}, function (socketInfo) {
+ var clientSocketId = socketInfo.socketId;
+ chrome.test.assertTrue(clientSocketId > 0,
+ "Cannot create client udp socket.");
+ socket.setMulticastTimeToLive(clientSocketId, 0, function (result) {
+ chrome.test.assertEq(0, result,
+ "Cannot create client udp socket.");
+ socket.bind(clientSocketId, "0.0.0.0", 0, function (result) {
+ chrome.test.assertEq(0, result,
+ "Cannot bind to localhost.");
+ socket.send(clientSocketId, stringToArrayBuffer(kTestMessage),
+ address, kPort, function (result) {
+ console.log("Sent bytes to socket:" +
+ " socketId=" + clientSocketId +
+ ", bytes=" + result.bytesWritten +
+ ", address=" + address +
+ ", port=" + kPort);
+ chrome.test.assertTrue(result.bytesWritten >= 0,
+ "Send to failed. " + JSON.stringify(result));
+ socket.close(clientSocketId, function() {});
+ });
+ });
+ });
+ });
+ }
+
+ function testRecvBeforeAddMembership(serverSocketId, nextTest) {
+ console.log("*************** testRecvBeforeAddMembership");
+ var recvTimeout;
+ var canceller = waitForMessage(serverSocketId, function (cancelled) {
+ clearTimeout(recvTimeout);
+ if (cancelled) {
+ nextTest();
+ } else {
+ chrome.test.fail("Received message before joining the group");
+ }
+ });
+ testSendMessage(kTestMessage, kMulticastAddress); // Meant to be lost.
+ recvTimeout = setTimeout(function () {
+ // This is expected to execute.
+ canceller();
+ }, 2000);
+ }
+
+ function testRecvWithMembership(serverSocketId, nextTest) {
+ console.log("*************** testRecvWithMembership");
+ socket.joinGroup(serverSocketId, kMulticastAddress, function (result) {
+ chrome.test.assertEq(0, result, "Join group failed.");
+ var recvTimeout;
+ var canceller = waitForMessage(serverSocketId, function (cancelled) {
+ clearTimeout(recvTimeout);
+ if (!cancelled) {
+ nextTest();
+ } else {
+ chrome.test.fail("Faild to receive message after joining the group");
+ }
+ });
+ testSendMessage(kTestMessage, kMulticastAddress);
+ recvTimeout = setTimeout(function () {
+ canceller();
+ chrome.test.fail("Cannot receive from multicast group.");
+ }, 2000);
+ });
+ }
+
+ function testRecvWithoutMembership(serverSocketId, nextTest) {
+ console.log("*************** testRecvWithoutMembership");
+ socket.leaveGroup(serverSocketId, kMulticastAddress, function (result) {
+ chrome.test.assertEq(0, result, "leave group failed.");
+ var recvTimeout;
+ var canceller = waitForMessage(serverSocketId, function (cancelled) {
+ clearTimeout(recvTimeout);
+ if (cancelled) {
+ nextTest();
+ } else {
+ chrome.test.fail("Received message after leaving the group");
+ }
+ });
+ testSendMessage(request, kMulticastAddress);
+ recvTimeout = setTimeout(function () {
+ // This is expected to execute.
+ canceller();
+ }, 2000);
+ });
+ }
+
+ function testMulticastRecv() {
+ console.log("*************** testMulticastRecv");
+ socket.create({}, function (socketInfo) {
+ var serverSocketId = socketInfo.socketId;
+ socket.bind(serverSocketId, "0.0.0.0", kPort, function (result) {
+ chrome.test.assertEq(0, result, "Bind failed.");
+ // Test 1
+ testRecvBeforeAddMembership(serverSocketId, function() {
+ // Test 2
+ testRecvWithMembership(serverSocketId, function() {
+ // Test 3
+ testRecvWithoutMembership(serverSocketId, function() {
+ // Success!
+ socket.close(serverSocketId, function() {
+ console.log("*************** SUCCESS! ");
+ chrome.test.succeed();
+ });
+ });
+ });
+ });
+ });
+ });
+ }
+
+ setTimeout(function() {
+ testMulticastSettings(function() {
+ testMulticastRecv();
+ })
+ }, 100);
+}