Bug 745283 - Part 4: WebIDL binding for UDPSocket API. r+sr=smaug, r=khuey, r=baku a=reland
authorShih-Chiang Chien <schien@mozilla.com>
Mon, 14 Apr 2014 09:21:26 +0800
changeset 201690 4f6affdf52b6db940cc6bacde684d765d99cfd12
parent 201689 d0ab836a651799713886dd5edf309a945f0b694c
child 201697 d851690b8255d2673c8a6f5350e061c442ae87c6
push id27377
push userkwierso@gmail.com
push dateTue, 26 Aug 2014 23:45:59 +0000
treeherdermozilla-central@4f6affdf52b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey, baku, reland
bugs745283
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 745283 - Part 4: WebIDL binding for UDPSocket API. r+sr=smaug, r=khuey, r=baku a=reland
b2g/app/b2g.js
dom/network/src/UDPSocket.cpp
dom/network/src/UDPSocket.h
dom/network/src/moz.build
dom/network/tests/file_udpsocket_iframe.html
dom/network/tests/mochitest.ini
dom/network/tests/test_udpsocket.html
dom/tests/mochitest/general/test_interfaces.html
dom/webidl/SocketCommon.webidl
dom/webidl/UDPMessageEvent.webidl
dom/webidl/UDPSocket.webidl
dom/webidl/moz.build
modules/libpref/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1008,8 +1008,11 @@ pref("identity.fxaccounts.enabled", true
 
 // Mobile Identity API.
 pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
 
 // Enable mapped array buffer.
 #ifndef XP_WIN
 pref("dom.mapped_arraybuffer.enabled", true);
 #endif
+
+// UDPSocket API
+pref("dom.udpsocket.enabled", true);
new file mode 100644
--- /dev/null
+++ b/dom/network/src/UDPSocket.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/dom/network/src/UDPSocket.h
@@ -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__
--- a/dom/network/src/moz.build
+++ b/dom/network/src/moz.build
@@ -1,14 +1,18 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+EXPORTS.mozilla.dom += [
+    'UDPSocket.h',
+]
+
 EXPORTS.mozilla.dom.network += [
     'Connection.h',
     'Constants.h',
     'TCPServerSocketChild.h',
     'TCPServerSocketParent.h',
     'TCPSocketChild.h',
     'TCPSocketParent.h',
     'Types.h',
@@ -17,16 +21,17 @@ EXPORTS.mozilla.dom.network += [
 ]
 
 UNIFIED_SOURCES += [
     'Connection.cpp',
     'TCPServerSocketChild.cpp',
     'TCPServerSocketParent.cpp',
     'TCPSocketChild.cpp',
     'TCPSocketParent.cpp',
+    'UDPSocket.cpp',
     'UDPSocketChild.cpp',
     'UDPSocketParent.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     EXTRA_JS_MODULES += [
         'NetworkStatsDB.jsm',
         'NetworkStatsService.jsm',
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/file_udpsocket_iframe.html
@@ -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>
--- a/dom/network/tests/mochitest.ini
+++ b/dom/network/tests/mochitest.ini
@@ -1,8 +1,12 @@
+[DEFAULT]
+support-files =
+  file_udpsocket_iframe.html
+
 [test_network_basics.html]
 skip-if = toolkit == "gonk" || toolkit == 'android'
 [test_tcpsocket_default_permissions.html]
 skip-if = toolkit == "gonk"
 [test_tcpsocket_enabled_no_perm.html]
 skip-if = toolkit == "gonk"
 [test_tcpsocket_enabled_with_perm.html]
 skip-if = toolkit == "gonk" || e10s
@@ -11,8 +15,9 @@ skip-if = toolkit != "gonk"
 [test_networkstats_basics.html]
 skip-if = toolkit != "gonk" || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(Will be fixed in bug 858005) b2g-desktop(Will be fixed in bug 858005)
 [test_networkstats_disabled.html]
 skip-if = toolkit != "gonk"
 [test_networkstats_enabled_no_perm.html]
 skip-if = toolkit != "gonk"
 [test_networkstats_enabled_perm.html]
 skip-if = toolkit != "gonk"
+[test_udpsocket.html]
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_udpsocket.html
@@ -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>
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -1164,16 +1164,20 @@ var interfaceNamesInGlobalScope =
     {name: "TreeColumns", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "TreeContentView", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {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!
     "UndoManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URL",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URLSearchParams",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/SocketCommon.webidl
@@ -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"
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/UDPMessageEvent.webidl
@@ -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;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/UDPSocket.webidl
@@ -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
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -328,16 +328,17 @@ WEBIDL_FILES = [
     'ServiceWorkerContainer.webidl',
     'ServiceWorkerGlobalScope.webidl',
     'ServiceWorkerRegistration.webidl',
     'SettingsManager.webidl',
     'ShadowRoot.webidl',
     'SharedWorker.webidl',
     'SharedWorkerGlobalScope.webidl',
     'SimpleGestureEvent.webidl',
+    'SocketCommon.webidl',
     'SourceBuffer.webidl',
     'SourceBufferList.webidl',
     'Storage.webidl',
     'StorageEvent.webidl',
     'StorageType.webidl',
     'StyleSheet.webidl',
     'StyleSheetList.webidl',
     'SubtleCrypto.webidl',
@@ -463,16 +464,18 @@ WEBIDL_FILES = [
     'TimeEvent.webidl',
     'TimeRanges.webidl',
     'Touch.webidl',
     'TouchEvent.webidl',
     'TouchList.webidl',
     'TransitionEvent.webidl',
     'TreeColumns.webidl',
     'TreeWalker.webidl',
+    'UDPMessageEvent.webidl',
+    'UDPSocket.webidl',
     'UIEvent.webidl',
     'UndoManager.webidl',
     'URL.webidl',
     'URLSearchParams.webidl',
     'URLUtils.webidl',
     'URLUtilsReadOnly.webidl',
     'ValidityState.webidl',
     'VideoPlaybackQuality.webidl',
@@ -671,16 +674,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'RTCPeerConnectionIceEvent.webidl',
     'RTCPeerConnectionIdentityErrorEvent.webidl',
     'RTCPeerConnectionIdentityEvent.webidl',
     'SelectionChangeEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
     'TrackEvent.webidl',
+    'UDPMessageEvent.webidl',
     'UserProximityEvent.webidl',
     'USSDReceivedEvent.webidl',
 ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     GENERATED_EVENTS_WEBIDL_FILES += [
         'SpeechRecognitionEvent.webidl',
         'SpeechSynthesisEvent.webidl',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4251,8 +4251,11 @@ pref("dom.fetch.enabled", false);
 #ifdef MOZ_WIDGET_GONK
 // Empirically, this is the value returned by hal::GetTotalSystemMemory()
 // when Flame's memory is limited to 512MiB. If the camera stack determines
 // it is running on a low memory platform, features that can be reliably
 // supported will be disabled. This threshold can be adjusted to suit other
 // 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);