Bug 745283 - Part 4: WebIDL binding for UDPSocket API. r+sr=smaug, r=khuey, r=baku

This commit is contained in:
Shih-Chiang Chien 2014-04-14 09:21:26 +08:00
parent dc69c216ff
commit 7a957ee1c9
13 changed files with 1438 additions and 0 deletions

View File

@ -1013,3 +1013,6 @@ pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
#ifndef XP_WIN
pref("dom.mapped_arraybuffer.enabled", true);
#endif
// UDPSocket API
pref("dom.udpsocket.enabled", true);

View File

@ -0,0 +1,705 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "UDPSocket.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/UDPMessageEvent.h"
#include "mozilla/dom/UDPSocketBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/net/DNS.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsIDOMFile.h"
#include "nsINetAddr.h"
#include "nsStringStream.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpened)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed)
tmp->CloseWithReason(NS_OK);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(UDPSocket)
NS_INTERFACE_MAP_ENTRY(nsIUDPSocketListener)
NS_INTERFACE_MAP_ENTRY(nsIUDPSocketInternal)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
/* static */ already_AddRefed<UDPSocket>
UDPSocket::Constructor(const GlobalObject& aGlobal,
const UDPOptions& aOptions,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
if (!ownerWindow) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
bool addressReuse = aOptions.mAddressReuse;
bool loopback = aOptions.mLoopback;
nsCString remoteAddress;
if (aOptions.mRemoteAddress.WasPassed()) {
remoteAddress = NS_ConvertUTF16toUTF8(aOptions.mRemoteAddress.Value());
} else {
remoteAddress.SetIsVoid(true);
}
Nullable<uint16_t> remotePort;
if (aOptions.mRemotePort.WasPassed()) {
remotePort.SetValue(aOptions.mRemotePort.Value());
if (remotePort.Value() == 0) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
}
nsString localAddress;
if (aOptions.mLocalAddress.WasPassed()) {
localAddress = aOptions.mLocalAddress.Value();
// check if localAddress is a valid IPv4/6 address
NS_ConvertUTF16toUTF8 address(localAddress);
PRNetAddr prAddr;
PRStatus status = PR_StringToNetAddr(address.BeginReading(), &prAddr);
if (status != PR_SUCCESS) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
} else {
SetDOMStringToNull(localAddress);
}
Nullable<uint16_t> localPort;
if (aOptions.mLocalPort.WasPassed()) {
localPort.SetValue(aOptions.mLocalPort.Value());
if (localPort.Value() == 0) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
}
nsRefPtr<UDPSocket> socket = new UDPSocket(ownerWindow, remoteAddress, remotePort);
aRv = socket->Init(localAddress, localPort, addressReuse, loopback);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return socket.forget();
}
UDPSocket::UDPSocket(nsPIDOMWindow* aOwner,
const nsCString& aRemoteAddress,
const Nullable<uint16_t>& aRemotePort)
: DOMEventTargetHelper(aOwner)
, mRemoteAddress(aRemoteAddress)
, mRemotePort(aRemotePort)
, mReadyState(SocketReadyState::Opening)
{
MOZ_ASSERT(aOwner);
MOZ_ASSERT(aOwner->IsInnerWindow());
nsIDocument* aDoc = aOwner->GetExtantDoc();
if (aDoc) {
aDoc->DisallowBFCaching();
}
}
UDPSocket::~UDPSocket()
{
CloseWithReason(NS_OK);
}
JSObject*
UDPSocket::WrapObject(JSContext* aCx)
{
return UDPSocketBinding::Wrap(aCx, this);
}
void
UDPSocket::DisconnectFromOwner()
{
DOMEventTargetHelper::DisconnectFromOwner();
CloseWithReason(NS_OK);
}
already_AddRefed<Promise>
UDPSocket::Close()
{
MOZ_ASSERT(mClosed);
nsRefPtr<Promise> promise = mClosed;
if (mReadyState == SocketReadyState::Closed) {
return promise.forget();
}
CloseWithReason(NS_OK);
return promise.forget();
}
void
UDPSocket::CloseWithReason(nsresult aReason)
{
if (mReadyState == SocketReadyState::Closed) {
return;
}
if (mOpened) {
if (mReadyState == SocketReadyState::Opening) {
// reject openedPromise with AbortError if socket is closed without error
nsresult openFailedReason = NS_FAILED(aReason) ? aReason : NS_ERROR_DOM_ABORT_ERR;
mOpened->MaybeReject(openFailedReason);
}
}
mReadyState = SocketReadyState::Closed;
if (mSocket) {
mSocket->Close();
mSocket = nullptr;
}
if (mSocketChild) {
mSocketChild->Close();
mSocketChild = nullptr;
}
if (mClosed) {
if (NS_SUCCEEDED(aReason)) {
mClosed->MaybeResolve(JS::UndefinedHandleValue);
} else {
mClosed->MaybeReject(aReason);
}
}
mPendingMcastCommands.Clear();
}
void
UDPSocket::JoinMulticastGroup(const nsAString& aMulticastGroupAddress,
ErrorResult& aRv)
{
if (mReadyState == SocketReadyState::Closed) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mReadyState == SocketReadyState::Opening) {
MulticastCommand joinCommand(MulticastCommand::Join, aMulticastGroupAddress);
mPendingMcastCommands.AppendElement(joinCommand);
return;
}
MOZ_ASSERT(mSocket || mSocketChild);
NS_ConvertUTF16toUTF8 address(aMulticastGroupAddress);
if (mSocket) {
MOZ_ASSERT(!mSocketChild);
aRv = mSocket->JoinMulticast(address, EmptyCString());
NS_WARN_IF(aRv.Failed());
return;
}
MOZ_ASSERT(mSocketChild);
aRv = mSocketChild->JoinMulticast(address, EmptyCString());
NS_WARN_IF(aRv.Failed());
}
void
UDPSocket::LeaveMulticastGroup(const nsAString& aMulticastGroupAddress,
ErrorResult& aRv)
{
if (mReadyState == SocketReadyState::Closed) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mReadyState == SocketReadyState::Opening) {
MulticastCommand leaveCommand(MulticastCommand::Leave, aMulticastGroupAddress);
mPendingMcastCommands.AppendElement(leaveCommand);
return;
}
MOZ_ASSERT(mSocket || mSocketChild);
nsCString address = NS_ConvertUTF16toUTF8(aMulticastGroupAddress);
if (mSocket) {
MOZ_ASSERT(!mSocketChild);
aRv = mSocket->LeaveMulticast(address, EmptyCString());
NS_WARN_IF(aRv.Failed());
return;
}
MOZ_ASSERT(mSocketChild);
aRv = mSocketChild->LeaveMulticast(address, EmptyCString());
NS_WARN_IF(aRv.Failed());
}
nsresult
UDPSocket::DoPendingMcastCommand()
{
MOZ_ASSERT(mReadyState == SocketReadyState::Open, "Multicast command can only be executed after socket opened");
for (uint32_t i = 0; i < mPendingMcastCommands.Length(); ++i) {
MulticastCommand& command = mPendingMcastCommands[i];
ErrorResult rv;
switch (command.mCommand) {
case MulticastCommand::Join: {
JoinMulticastGroup(command.mAddress, rv);
break;
}
case MulticastCommand::Leave: {
LeaveMulticastGroup(command.mAddress, rv);
break;
}
}
if (NS_WARN_IF(rv.Failed())) {
return rv.ErrorCode();
}
}
mPendingMcastCommands.Clear();
return NS_OK;
}
bool
UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
const Optional<nsAString>& aRemoteAddress,
const Optional<Nullable<uint16_t>>& aRemotePort,
ErrorResult& aRv)
{
if (mReadyState != SocketReadyState::Open) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return false;
}
MOZ_ASSERT(mSocket || mSocketChild);
// If the remote address and port were not specified in the constructor or as arguments,
// throw InvalidAccessError.
nsCString remoteAddress;
if (aRemoteAddress.WasPassed()) {
remoteAddress = NS_ConvertUTF16toUTF8(aRemoteAddress.Value());
} else if (!mRemoteAddress.IsVoid()) {
remoteAddress = mRemoteAddress;
} else {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return false;
}
uint16_t remotePort;
if (aRemotePort.WasPassed() && !aRemotePort.Value().IsNull()) {
remotePort = aRemotePort.Value().Value();
} else if (!mRemotePort.IsNull()) {
remotePort = mRemotePort.Value();
} else {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return false;
}
nsCOMPtr<nsIInputStream> stream;
if (aData.IsBlob()) {
nsCOMPtr<nsIDOMBlob> blob = aData.GetAsBlob();
aRv = blob->GetInternalStream(getter_AddRefs(stream));
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
} else {
nsresult rv;
nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
if (aData.IsString()) {
NS_ConvertUTF16toUTF8 data(aData.GetAsString());
aRv = strStream->SetData(data.BeginReading(), data.Length());
} else if (aData.IsArrayBuffer()) {
const ArrayBuffer& data = aData.GetAsArrayBuffer();
data.ComputeLengthAndData();
aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()), data.Length());
} else {
const ArrayBufferView& data = aData.GetAsArrayBufferView();
data.ComputeLengthAndData();
aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()), data.Length());
}
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
stream = strStream;
}
if (mSocket) {
aRv = mSocket->SendBinaryStream(remoteAddress, remotePort, stream);
} else if (mSocketChild) {
aRv = mSocketChild->SendBinaryStream(remoteAddress, remotePort, stream);
}
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
return true;
}
nsresult
UDPSocket::InitLocal(const nsAString& aLocalAddress,
const uint16_t& aLocalPort)
{
nsresult rv;
nsCOMPtr<nsIUDPSocket> sock =
do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
if (aLocalAddress.IsEmpty()) {
rv = sock->Init(aLocalPort, /* loopback = */ false, mAddressReuse, /* optionalArgc = */ 1);
} else {
PRNetAddr prAddr;
PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr);
PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress).BeginReading(), &prAddr);
mozilla::net::NetAddr addr;
PRNetAddrToNetAddr(&prAddr, &addr);
rv = sock->InitWithAddress(&addr, mAddressReuse, /* optionalArgc = */ 1);
}
if (NS_FAILED(rv)) {
return rv;
}
rv = sock->SetMulticastLoopback(mLoopback);
if (NS_FAILED(rv)) {
return rv;
}
mSocket = sock;
// Get real local address and local port
nsCOMPtr<nsINetAddr> localAddr;
rv = mSocket->GetLocalAddr(getter_AddRefs(localAddr));
if (NS_FAILED(rv)) {
return rv;
}
nsCString localAddress;
rv = localAddr->GetAddress(localAddress);
if (NS_FAILED(rv)) {
return rv;
}
mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
uint16_t localPort;
rv = localAddr->GetPort(&localPort);
if (NS_FAILED(rv)) {
return rv;
}
mLocalPort.SetValue(localPort);
rv = mSocket->AsyncListen(this);
if (NS_FAILED(rv)) {
return rv;
}
mReadyState = SocketReadyState::Open;
rv = DoPendingMcastCommand();
if (NS_FAILED(rv)) {
return rv;
}
mOpened->MaybeResolve(JS::UndefinedHandleValue);
return NS_OK;
}
nsresult
UDPSocket::InitRemote(const nsAString& aLocalAddress,
const uint16_t& aLocalPort)
{
nsresult rv;
nsCOMPtr<nsIUDPSocketChild> sock =
do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
rv = sock->Bind(this, NS_ConvertUTF16toUTF8(aLocalAddress), aLocalPort, mAddressReuse, mLoopback);
if (NS_FAILED(rv)) {
return rv;
}
mSocketChild = sock;
return NS_OK;
}
nsresult
UDPSocket::Init(const nsString& aLocalAddress,
const Nullable<uint16_t>& aLocalPort,
const bool& aAddressReuse,
const bool& aLoopback)
{
MOZ_ASSERT(!mSocket && !mSocketChild);
mLocalAddress = aLocalAddress;
mLocalPort = aLocalPort;
mAddressReuse = aAddressReuse;
mLoopback = aLoopback;
ErrorResult rv;
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
mOpened = Promise::Create(global, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.ErrorCode();
}
mClosed = Promise::Create(global, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.ErrorCode();
}
class OpenSocketRunnable MOZ_FINAL : public nsRunnable
{
public:
OpenSocketRunnable(UDPSocket* aSocket) : mSocket(aSocket)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(mSocket);
if (mSocket->mReadyState != SocketReadyState::Opening) {
return NS_OK;
}
uint16_t localPort = 0;
if (!mSocket->mLocalPort.IsNull()) {
localPort = mSocket->mLocalPort.Value();
}
nsresult rv;
if (XRE_GetProcessType() != GeckoProcessType_Default) {
rv = mSocket->InitRemote(mSocket->mLocalAddress, localPort);
} else {
rv = mSocket->InitLocal(mSocket->mLocalAddress, localPort);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
mSocket->CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
}
return NS_OK;
}
private:
nsRefPtr<UDPSocket> mSocket;
};
nsCOMPtr<nsIRunnable> runnable = new OpenSocketRunnable(this);
return NS_DispatchToMainThread(runnable);
}
void
UDPSocket::HandleReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength)
{
if (mReadyState != SocketReadyState::Open) {
return;
}
if (NS_FAILED(CheckInnerWindowCorrectness())) {
return;
}
if (NS_FAILED(DispatchReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength))) {
CloseWithReason(NS_ERROR_TYPE_ERR);
}
}
nsresult
UDPSocket::DispatchReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength)
{
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
// Copy packet data to ArrayBuffer
JS::Rooted<JSObject*> arrayBuf(cx, ArrayBuffer::Create(cx, aDataLength, aData));
if (NS_WARN_IF(!arrayBuf)) {
return NS_ERROR_FAILURE;
}
JS::Rooted<JS::Value> jsData(cx, JS::ObjectValue(*arrayBuf));
// Create DOM event
RootedDictionary<UDPMessageEventInit> init(cx);
init.mRemoteAddress = NS_ConvertUTF8toUTF16(aRemoteAddress);
init.mRemotePort = aRemotePort;
init.mData = jsData;
nsRefPtr<UDPMessageEvent> udpEvent =
UDPMessageEvent::Constructor(this, NS_LITERAL_STRING("message"), init);
if (NS_WARN_IF(!udpEvent)) {
return NS_ERROR_FAILURE;
}
udpEvent->SetTrusted(true);
nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, udpEvent);
return asyncDispatcher->PostDOMEvent();
}
// nsIUDPSocketListener
NS_IMETHODIMP
UDPSocket::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage)
{
// nsIUDPSocketListener callbacks should be invoked on main thread.
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
// Create appropriate JS object for message
FallibleTArray<uint8_t>& buffer = aMessage->GetDataAsTArray();
nsCOMPtr<nsINetAddr> addr;
if (NS_WARN_IF(NS_FAILED(aMessage->GetFromAddr(getter_AddRefs(addr))))) {
return NS_OK;
}
nsCString remoteAddress;
if (NS_WARN_IF(NS_FAILED(addr->GetAddress(remoteAddress)))) {
return NS_OK;
}
uint16_t remotePort;
if (NS_WARN_IF(NS_FAILED(addr->GetPort(&remotePort)))) {
return NS_OK;
}
HandleReceivedData(remoteAddress, remotePort, buffer.Elements(), buffer.Length());
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus)
{
// nsIUDPSocketListener callbacks should be invoked on main thread.
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
CloseWithReason(aStatus);
return NS_OK;
}
// nsIUDPSocketInternal
NS_IMETHODIMP
UDPSocket::CallListenerError(const nsACString& aMessage,
const nsACString& aFilename,
uint32_t aLineNumber)
{
CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerReceivedData(const nsACString& aRemoteAddress,
uint16_t aRemotePort,
const uint8_t* aData,
uint32_t aDataLength)
{
HandleReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength);
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerOpened()
{
if (mReadyState != SocketReadyState::Opening) {
return NS_OK;
}
MOZ_ASSERT(mSocketChild);
// Get real local address and local port
nsCString localAddress;
mSocketChild->GetLocalAddress(localAddress);
mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
uint16_t localPort;
mSocketChild->GetLocalPort(&localPort);
mLocalPort.SetValue(localPort);
mReadyState = SocketReadyState::Open;
nsresult rv = DoPendingMcastCommand();
if (NS_WARN_IF(NS_FAILED(rv))) {
CloseWithReason(rv);
return NS_OK;
}
mOpened->MaybeResolve(JS::UndefinedHandleValue);
return NS_OK;
}
NS_IMETHODIMP
UDPSocket::CallListenerClosed()
{
CloseWithReason(NS_OK);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

197
dom/network/src/UDPSocket.h Normal file
View File

@ -0,0 +1,197 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_UDPSocket_h__
#define mozilla_dom_UDPSocket_h__
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/SocketCommonBinding.h"
#include "nsIUDPSocket.h"
#include "nsIUDPSocketChild.h"
#include "nsTArray.h"
struct JSContext;
namespace mozilla {
namespace dom {
struct UDPOptions;
class StringOrBlobOrArrayBufferOrArrayBufferView;
class UDPSocket MOZ_FINAL : public DOMEventTargetHelper
, public nsIUDPSocketListener
, public nsIUDPSocketInternal
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UDPSocket, DOMEventTargetHelper)
NS_DECL_NSIUDPSOCKETLISTENER
NS_DECL_NSIUDPSOCKETINTERNAL
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
public:
nsPIDOMWindow*
GetParentObject() const
{
return GetOwner();
}
virtual JSObject*
WrapObject(JSContext* aCx) MOZ_OVERRIDE;
virtual void
DisconnectFromOwner() MOZ_OVERRIDE;
static already_AddRefed<UDPSocket>
Constructor(const GlobalObject& aGlobal, const UDPOptions& aOptions, ErrorResult& aRv);
void
GetLocalAddress(nsString& aRetVal) const
{
aRetVal = mLocalAddress;
}
Nullable<uint16_t>
GetLocalPort() const
{
return mLocalPort;
}
void
GetRemoteAddress(nsString& aRetVal) const
{
if (mRemoteAddress.IsVoid()) {
SetDOMStringToNull(aRetVal);
return;
}
aRetVal = NS_ConvertUTF8toUTF16(mRemoteAddress);
}
Nullable<uint16_t>
GetRemotePort() const
{
return mRemotePort;
}
bool
AddressReuse() const
{
return mAddressReuse;
}
bool
Loopback() const
{
return mLoopback;
}
SocketReadyState
ReadyState() const
{
return mReadyState;
}
Promise*
Opened() const
{
return mOpened;
}
Promise*
Closed() const
{
return mClosed;
}
IMPL_EVENT_HANDLER(message)
already_AddRefed<Promise>
Close();
void
JoinMulticastGroup(const nsAString& aMulticastGroupAddress, ErrorResult& aRv);
void
LeaveMulticastGroup(const nsAString& aMulticastGroupAddress, ErrorResult& aRv);
bool
Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
const Optional<nsAString>& aRemoteAddress,
const Optional<Nullable<uint16_t>>& aRemotePort,
ErrorResult& aRv);
private:
UDPSocket(nsPIDOMWindow* aOwner,
const nsCString& aRemoteAddress,
const Nullable<uint16_t>& aRemotePort);
virtual ~UDPSocket();
nsresult
Init(const nsString& aLocalAddress,
const Nullable<uint16_t>& aLocalPort,
const bool& aAddressReuse,
const bool& aLoopback);
nsresult
InitLocal(const nsAString& aLocalAddress, const uint16_t& aLocalPort);
nsresult
InitRemote(const nsAString& aLocalAddress, const uint16_t& aLocalPort);
void
HandleReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength);
nsresult
DispatchReceivedData(const nsACString& aRemoteAddress,
const uint16_t& aRemotePort,
const uint8_t* aData,
const uint32_t& aDataLength);
void
CloseWithReason(nsresult aReason);
nsresult
DoPendingMcastCommand();
nsString mLocalAddress;
Nullable<uint16_t> mLocalPort;
nsCString mRemoteAddress;
Nullable<uint16_t> mRemotePort;
bool mAddressReuse;
bool mLoopback;
SocketReadyState mReadyState;
nsRefPtr<Promise> mOpened;
nsRefPtr<Promise> mClosed;
nsCOMPtr<nsIUDPSocket> mSocket;
nsCOMPtr<nsIUDPSocketChild> mSocketChild;
struct MulticastCommand {
enum CommandType { Join, Leave };
MulticastCommand(CommandType aCommand, const nsAString& aAddress)
: mCommand(aCommand), mAddress(aAddress)
{ }
CommandType mCommand;
nsString mAddress;
};
nsTArray<MulticastCommand> mPendingMcastCommands;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_UDPSocket_h__

View File

@ -4,6 +4,10 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
'UDPSocket.h',
]
EXPORTS.mozilla.dom.network += [
'Connection.h',
'Constants.h',
@ -22,6 +26,7 @@ UNIFIED_SOURCES += [
'TCPServerSocketParent.cpp',
'TCPSocketChild.cpp',
'TCPSocketParent.cpp',
'UDPSocket.cpp',
'UDPSocketChild.cpp',
'UDPSocketParent.cpp',
]

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test UDPSocket BFCache</title>
</head>
<body>
<script type="application/javascript;version=1.8">
'use strict';
window.addEventListener('load', function onload() {
window.removeEventListener('load', onload);
let remotePort = parseInt(window.location.search.substring(1), 10);
let socket = new UDPSocket();
socket.addEventListener('message', function () {
socket.send('fail', '127.0.0.1', remotePort);
});
socket.opened.then(function() {
socket.send('ready', '127.0.0.1', remotePort);
});
});
</script>
</body>
</html>

View File

@ -1,3 +1,7 @@
[DEFAULT]
support-files =
file_udpsocket_iframe.html
[test_network_basics.html]
skip-if = toolkit == "gonk" || toolkit == 'android'
[test_tcpsocket_default_permissions.html]
@ -16,3 +20,4 @@ skip-if = toolkit != "gonk"
skip-if = toolkit != "gonk"
[test_networkstats_enabled_perm.html]
skip-if = toolkit != "gonk"
[test_udpsocket.html]

View File

@ -0,0 +1,409 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test UDPSocket API</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<iframe id="iframe"></iframe>
<pre id="test">
<script type="application/javascript;version=1.8">
'use strict';
SimpleTest.waitForExplicitFinish();
const HELLO_WORLD = 'hlo wrld. ';
const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
const BIG_ARRAY = new Array(4096);
const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length);
const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER);
for (let i = 0; i < BIG_ARRAY.length; i++) {
BIG_ARRAY[i] = Math.floor(Math.random() * 256);
}
TYPED_DATA_ARRAY.set(DATA_ARRAY);
BIG_TYPED_ARRAY.set(BIG_ARRAY);
function is_same_buffer(recv_data, expect_data) {
let recv_dataview = new Uint8Array(recv_data);
let expected_dataview = new Uint8Array(expect_data);
if (recv_dataview.length !== expected_dataview.length) {
return false;
}
for (let i = 0; i < recv_dataview.length; i++) {
if (recv_dataview[i] != expected_dataview[i]) {
info('discover byte differenct at ' + i);
return false;
}
}
return true;
}
function testOpen() {
info('test for creating an UDP Socket');
let socket = new UDPSocket();
is(socket.localPort, null, 'expect no local port before socket opened');
is(socket.localAddress, null, 'expect no local address before socket opened');
is(socket.remotePort, null, 'expected no default remote port');
is(socket.remoteAddress, null, 'expected no default remote address');
is(socket.readyState, 'opening', 'expected ready state = opening');
is(socket.loopback, false, 'expected no loopback');
is(socket.addressReuse, true, 'expect to reuse address');
return socket.opened.then(function() {
ok(true, 'expect openedPromise to be resolved after successful socket binding');
ok(!(socket.localPort === 0), 'expect allocated a local port');
is(socket.localAddress, '0.0.0.0', 'expect assigned to default address');
is(socket.readyState, 'open', 'expected ready state = open');
return socket;
});
}
function testSendString(socket) {
info('test for sending string data');
socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
resolve(socket);
});
});
}
function testSendArrayBuffer(socket) {
info('test for sending ArrayBuffer');
socket.send(DATA_ARRAY_BUFFER, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, DATA_ARRAY_BUFFER), 'expected same buffer data');
resolve(socket);
});
});
}
function testSendArrayBufferView(socket) {
info('test for sending ArrayBufferView');
socket.send(TYPED_DATA_ARRAY, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, TYPED_DATA_ARRAY), 'expected same buffer data');
resolve(socket);
});
});
}
function testSendBlob(socket) {
info('test for sending Blob');
let blob = new Blob([HELLO_WORLD], {type : 'text/plain'});
socket.send(blob, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
resolve(socket);
});
});
}
function testSendBigArray(socket) {
info('test for sending Big ArrayBuffer');
socket.send(BIG_TYPED_ARRAY, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
let byteReceived = 0;
socket.addEventListener('message', function recv_callback(msg) {
let byteBegin = byteReceived;
byteReceived += msg.data.byteLength;
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
if (byteReceived >= BIG_TYPED_ARRAY.length) {
socket.removeEventListener('message', recv_callback);
clearTimeout(timeout);
resolve(socket);
}
});
let timeout = setTimeout(function() {
ok(false, 'timeout for sending big array');
resolve(socket);
}, 5000);
});
}
function testSendBigBlob(socket) {
info('test for sending Big Blob');
let blob = new Blob([BIG_TYPED_ARRAY]);
socket.send(blob, '127.0.0.1', socket.localPort);
return new Promise(function(resolve, reject) {
let byteReceived = 0;
socket.addEventListener('message', function recv_callback(msg) {
let byteBegin = byteReceived;
byteReceived += msg.data.byteLength;
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
if (byteReceived >= BIG_TYPED_ARRAY.length) {
socket.removeEventListener('message', recv_callback);
clearTimeout(timeout);
resolve(socket);
}
});
let timeout = setTimeout(function() {
ok(false, 'timeout for sending big blob');
resolve(socket);
}, 5000);
});
}
function testUDPOptions(socket) {
info('test for UDP init options');
let remoteSocket = new UDPSocket({addressReuse: false,
loopback: true,
localAddress: '127.0.0.1',
remoteAddress: '127.0.0.1',
remotePort: socket.localPort});
is(remoteSocket.localAddress, '127.0.0.1', 'expected local address');
is(remoteSocket.remoteAddress, '127.0.0.1', 'expected remote address');
is(remoteSocket.remotePort, socket.localPort, 'expected remote port');
is(remoteSocket.addressReuse, false, 'expected address not reusable');
is(remoteSocket.loopback, true, 'expected loopback mode is on');
return remoteSocket.opened.then(function() {
remoteSocket.send(HELLO_WORLD);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, remoteSocket.localPort, 'expected packet from ' + remoteSocket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
resolve(socket);
});
});
});
}
function testClose(socket) {
info('test for close');
socket.close();
is(socket.readyState, 'closed', 'expect ready state to be "closed"');
try {
socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort);
ok(false, 'unexpect to send successfully');
} catch (e) {
ok(true, 'expected send fail after socket closed');
}
return socket.closed.then(function() {
ok(true, 'expected closedPromise is resolved after socket.close()');
});
}
function testMulticast() {
info('test for multicast');
let socket = new UDPSocket({loopback: true});
const MCAST_ADDRESS = '224.0.0.255';
socket.joinMulticastGroup(MCAST_ADDRESS);
return socket.opened.then(function() {
socket.send(HELLO_WORLD, MCAST_ADDRESS, socket.localPort);
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
is(recvData, HELLO_WORLD, 'expected same string data');
socket.leaveMulticastGroup(MCAST_ADDRESS);
resolve();
});
});
});
}
function testInvalidUDPOptions() {
info('test for invalid UDPOptions');
try {
let socket = new UDPSocket({localAddress: 'not-a-valid-address'});
ok(false, 'should not create an UDPSocket with an invalid localAddress');
} catch (e) {
is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localAddress is not a valid IPv4/6 address');
}
try {
let socket = new UDPSocket({localPort: 0});
ok(false, 'should not create an UDPSocket with an invalid localPort');
} catch (e) {
is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number');
}
try {
let socket = new UDPSocket({remotePort: 0});
ok(false, 'should not create an UDPSocket with an invalid remotePort');
} catch (e) {
is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number');
}
}
function testOpenFailed() {
info('test for falied on open');
//according to RFC5737, address block 192.0.2.0/24 should not be used in both local and public contexts
let socket = new UDPSocket({localAddress: '192.0.2.0'});
return socket.opened.then(function() {
ok(false, 'should not resolve openedPromise while fail to bind socket');
}).catch(function(reason) {
is(reason.name, 'NetworkError', 'expected openedPromise to be rejected while fail to bind socket');
});
}
function testSendBeforeOpen() {
info('test for send before open');
let socket = new UDPSocket();
try {
socket.send(HELLO_WORLD, '127.0.0.1', 9);
ok(false, 'unexpect to send successfully');
} catch (e) {
ok(true, 'expected send fail before openedPromise is resolved');
}
return socket.opened;
}
function testCloseBeforeOpened() {
info('test for close socket before opened');
let socket = new UDPSocket();
socket.opened.then(function() {
ok(false, 'should not resolve openedPromise if it has already been closed');
}).catch(function(reason) {
is(reason.name, 'AbortError', 'expected openedPromise to be rejected while socket is closed during opening');
});
return socket.close().then(function() {
ok(true, 'expected closedPromise to be resolved');
}).then(socket.opened);
}
function testOpenWithoutClose() {
info('test for open without close');
let opened = [];
for (let i = 0; i < 50; i++) {
let socket = new UDPSocket();
opened.push(socket.opened);
}
return Promise.all(opened);
}
function testBFCache() {
info('test for bfcache behavior');
let socket = new UDPSocket();
return socket.opened.then(function() {
let iframe = document.getElementById('iframe');
SpecialPowers.wrap(iframe).mozbrowser = true;
iframe.src = 'file_udpsocket_iframe.html?' + socket.localPort;
return new Promise(function(resolve, reject) {
socket.addEventListener('message', function recv_callback(msg) {
socket.removeEventListener('message', recv_callback);
iframe.src = 'about:blank';
iframe.addEventListener('load', function onload() {
iframe.removeEventListener('load', onload);
socket.send(HELLO_WORLD, '127.0.0.1', msg.remotePort);
function recv_again_callback(msg) {
socket.removeEventListener('message', recv_again_callback);
ok(false, 'should not receive packet after page unload');
}
socket.addEventListener('message', recv_again_callback);
let timeout = setTimeout(function() {
socket.removeEventListener('message', recv_again_callback);
socket.close();
resolve();
}, 5000);
});
});
});
});
}
function runTest() {
testOpen()
.then(testSendString)
.then(testSendArrayBuffer)
.then(testSendArrayBufferView)
.then(testSendBlob)
.then(testSendBigArray)
.then(testSendBigBlob)
.then(testUDPOptions)
.then(testClose)
.then(testMulticast)
.then(testInvalidUDPOptions)
.then(testOpenFailed)
.then(testSendBeforeOpen)
.then(testCloseBeforeOpened)
.then(testOpenWithoutClose)
.then(testBFCache)
.then(function() {
info('test finished');
SimpleTest.finish();
});
}
window.addEventListener('load', function () {
SpecialPowers.pushPermissions([
{type: 'udp-socket', allow: true, context: document}], function() {
SpecialPowers.pushPrefEnv({
'set': [
['dom.udpsocket.enabled', true],
['browser.sessionhistory.max_total_viewers', 10]
]
}, runTest);
});
});
</script>
</pre>
</body>
</html>

View File

@ -1168,6 +1168,10 @@ var interfaceNamesInGlobalScope =
{name: "TreeSelection", xbl: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
"TreeWalker",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "UDPMessageEvent", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "UDPSocket", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
// IMPORTANT: Do not change this list without review from a DOM peer!
"UIEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!

View File

@ -0,0 +1,16 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://www.w3.org/2012/sysapps/tcp-udp-sockets/#readystate
*/
enum SocketReadyState {
"opening",
"open",
"closing",
"closed",
"halfclosed"
};

View File

@ -0,0 +1,24 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://www.w3.org/TR/raw-sockets/#interface-udpmessageevent
*/
//Bug 1056444: This interface should be removed after UDPSocket.input/UDPSocket.output are ready.
[Constructor(DOMString type, optional UDPMessageEventInit eventInitDict),
Pref="dom.udpsocket.enabled",
CheckPermissions="udp-socket"]
interface UDPMessageEvent : Event {
readonly attribute DOMString remoteAddress;
readonly attribute unsigned short remotePort;
readonly attribute any data;
};
dictionary UDPMessageEventInit : EventInit {
DOMString remoteAddress = "";
unsigned short remotePort = 0;
any data = null;
};

View File

@ -0,0 +1,40 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://www.w3.org/2012/sysapps/tcp-udp-sockets/#interface-udpsocket
* http://www.w3.org/2012/sysapps/tcp-udp-sockets/#dictionary-udpoptions
*/
dictionary UDPOptions {
DOMString localAddress;
unsigned short localPort;
DOMString remoteAddress;
unsigned short remotePort;
boolean addressReuse = true;
boolean loopback = false;
};
[Constructor (optional UDPOptions options),
Pref="dom.udpsocket.enabled",
CheckPermissions="udp-socket"]
interface UDPSocket : EventTarget {
readonly attribute DOMString? localAddress;
readonly attribute unsigned short? localPort;
readonly attribute DOMString? remoteAddress;
readonly attribute unsigned short? remotePort;
readonly attribute boolean addressReuse;
readonly attribute boolean loopback;
readonly attribute SocketReadyState readyState;
readonly attribute Promise<void> opened;
readonly attribute Promise<void> closed;
// readonly attribute ReadableStream input; //Bug 1056444: Stream API is not ready
// readonly attribute WriteableStream output; //Bug 1056444: Stream API is not ready
attribute EventHandler onmessage; //Bug 1056444: use event interface before Stream API is ready
Promise<void> close ();
[Throws] void joinMulticastGroup (DOMString multicastGroupAddress);
[Throws] void leaveMulticastGroup (DOMString multicastGroupAddress);
[Throws] boolean send ((DOMString or Blob or ArrayBuffer or ArrayBufferView) data, optional DOMString? remoteAddress, optional unsigned short? remotePort); //Bug 1056444: use send method before Stream API is ready
};

View File

@ -333,6 +333,7 @@ WEBIDL_FILES = [
'SharedWorker.webidl',
'SharedWorkerGlobalScope.webidl',
'SimpleGestureEvent.webidl',
'SocketCommon.webidl',
'SourceBuffer.webidl',
'SourceBufferList.webidl',
'Storage.webidl',
@ -468,6 +469,8 @@ WEBIDL_FILES = [
'TransitionEvent.webidl',
'TreeColumns.webidl',
'TreeWalker.webidl',
'UDPMessageEvent.webidl',
'UDPSocket.webidl',
'UIEvent.webidl',
'UndoManager.webidl',
'URL.webidl',
@ -676,6 +679,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'StyleSheetApplicableStateChangeEvent.webidl',
'StyleSheetChangeEvent.webidl',
'TrackEvent.webidl',
'UDPMessageEvent.webidl',
'UserProximityEvent.webidl',
'USSDReceivedEvent.webidl',
]

View File

@ -4256,3 +4256,6 @@ pref("dom.fetch.enabled", false);
// platforms; and set to 0 to disable the low-memory check altogether.
pref("camera.control.low_memory_thresholdMB", 404);
#endif
// UDPSocket API
pref("dom.udpsocket.enabled", false);