mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
Bug 745283 - Part 4: WebIDL binding for UDPSocket API. r+sr=smaug, r=khuey, r=baku a=reland
This commit is contained in:
parent
6fb3b1ce8f
commit
2cbce5655a
@ -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);
|
||||
|
705
dom/network/src/UDPSocket.cpp
Normal file
705
dom/network/src/UDPSocket.cpp
Normal 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
197
dom/network/src/UDPSocket.h
Normal 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__
|
@ -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',
|
||||
]
|
||||
|
23
dom/network/tests/file_udpsocket_iframe.html
Normal file
23
dom/network/tests/file_udpsocket_iframe.html
Normal 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>
|
@ -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]
|
||||
|
409
dom/network/tests/test_udpsocket.html
Normal file
409
dom/network/tests/test_udpsocket.html
Normal 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>
|
@ -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!
|
||||
|
16
dom/webidl/SocketCommon.webidl
Normal file
16
dom/webidl/SocketCommon.webidl
Normal 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"
|
||||
};
|
24
dom/webidl/UDPMessageEvent.webidl
Normal file
24
dom/webidl/UDPMessageEvent.webidl
Normal 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;
|
||||
};
|
40
dom/webidl/UDPSocket.webidl
Normal file
40
dom/webidl/UDPSocket.webidl
Normal 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
|
||||
};
|
@ -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',
|
||||
]
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user