Bug 885982 - Part 2: Convert TCPServerSocket to WebIDL and rewrite in C++. r=asuth,mayhemer,bz
authorJosh Matthews <josh@joshmatthews.net>
Wed, 25 Mar 2015 10:36:24 -0400
changeset 283376 2ad05ac8aac349d23626add9ffd85cbd06bd4de5
parent 283375 bf8d9233a7bd5ea2e7a1a7916dd1a28021bca7f8
child 283377 7877ab6c64ca511513120de41818fc97162ddc5d
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-esr52@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, mayhemer, bz
bugs885982
milestone43.0a1
Bug 885982 - Part 2: Convert TCPServerSocket to WebIDL and rewrite in C++. r=asuth,mayhemer,bz
dom/events/test/test_all_synthetic_events.html
dom/network/TCPServerSocket.cpp
dom/network/TCPServerSocket.h
dom/network/TCPSocket.cpp
dom/network/TCPSocket.h
dom/network/moz.build
dom/network/tests/mochitest.ini
dom/network/tests/test_tcpsocket_client_and_server_basics.js
dom/network/tests/test_tcpsocket_legacy.html
dom/webidl/Navigator.webidl
dom/webidl/TCPServerSocket.webidl
dom/webidl/TCPServerSocketEvent.webidl
dom/webidl/TCPSocket.webidl
dom/webidl/moz.build
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -487,16 +487,20 @@ const kEventConstructors = {
   TCPSocketErrorEvent:                       { create: function(aName, aProps) {
                                                          return new TCPSocketErrorEvent(aName, aProps);
                                                        },
                                              },
   TCPSocketEvent:                            { create: function(aName, aProps) {
                                                          return new TCPSocketEvent(aName, aProps);
                                                        },
                                              },
+  TCPServerSocketEvent:                      { create: function(aName, aProps) {
+                                                         return new TCPServerSocketEvent(aName, aProps);
+                                                       },
+                                             },
   TimeEvent:                                 { create: function (aName, aProps) {
                                                          var e = document.createEvent("timeevent");
                                                          e.initTimeEvent(aName, aProps.view, aProps.detail);
                                                          return e;
                                                        },
                                              },
   TouchEvent:                                { create: function (aName, aProps) {
                                                          var e = document.createEvent("touchevent");
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPServerSocket.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; 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/. */
+
+#include "mozilla/dom/TCPServerSocketBinding.h"
+#include "mozilla/dom/TCPServerSocketEvent.h"
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/ErrorResult.h"
+#include "TCPServerSocket.h"
+#include "TCPSocket.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPServerSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPServerSocket,
+                                               DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPServerSocket,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerSocket)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPServerSocket,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerSocket)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TCPServerSocket)
+  NS_INTERFACE_MAP_ENTRY(nsIServerSocketListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TCPServerSocket::TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort,
+                                 bool aUseArrayBuffers, uint16_t aBacklog)
+  : DOMEventTargetHelper(aGlobal)
+  , mPort(aPort)
+  , mBacklog(aBacklog)
+  , mUseArrayBuffers(aUseArrayBuffers)
+{
+}
+
+TCPServerSocket::~TCPServerSocket()
+{
+}
+
+nsresult
+TCPServerSocket::Init()
+{
+  if (mServerSocket) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  mServerSocket = do_CreateInstance("@mozilla.org/network/server-socket;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mServerSocket->Init(mPort, false, mBacklog);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mServerSocket->GetPort(&mPort);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mServerSocket->AsyncListen(this);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+already_AddRefed<TCPServerSocket>
+TCPServerSocket::Constructor(const GlobalObject& aGlobal,
+                             uint16_t aPort,
+                             const ServerSocketOptions& aOptions,
+                             uint16_t aBacklog,
+                             mozilla::ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!global) {
+    aRv = NS_ERROR_FAILURE;
+    return nullptr;
+  }
+  bool useArrayBuffers = aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer;
+  nsRefPtr<TCPServerSocket> socket = new TCPServerSocket(global, aPort, useArrayBuffers, aBacklog);
+  nsresult rv = socket->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv = NS_ERROR_FAILURE;
+    return nullptr;
+  }
+  return socket.forget();
+}
+
+uint16_t
+TCPServerSocket::LocalPort()
+{
+  return mPort;
+}
+
+void
+TCPServerSocket::Close()
+{
+  if (mServerSocket) {
+    mServerSocket->Close();
+  }
+}
+
+void
+TCPServerSocket::FireEvent(const nsAString& aType, TCPSocket* aSocket)
+{
+  AutoJSAPI api;
+  api.Init(GetOwner());
+
+  TCPServerSocketEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mSocket = aSocket;
+
+  nsRefPtr<TCPServerSocketEvent> event =
+      TCPServerSocketEvent::Constructor(this, aType, init);
+  event->SetTrusted(true);
+  bool dummy;
+  DispatchEvent(event, &dummy);
+}
+
+NS_IMETHODIMP
+TCPServerSocket::OnSocketAccepted(nsIServerSocket* aServer, nsISocketTransport* aTransport)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+  nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aTransport, mUseArrayBuffers);
+  FireEvent(NS_LITERAL_STRING("connect"), socket);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPServerSocket::OnStopListening(nsIServerSocket* aServer, nsresult aStatus)
+{
+  if (aStatus != NS_BINDING_ABORTED) {
+    nsRefPtr<Event> event = new Event(GetOwner());
+    nsresult rv = event->InitEvent(NS_LITERAL_STRING("error"), false, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+    event->SetTrusted(true);
+    bool dummy;
+    DispatchEvent(event, &dummy);
+
+    NS_WARNING("Server socket was closed by unexpected reason.");
+    return NS_ERROR_FAILURE;
+  }
+  mServerSocket = nullptr;
+  return NS_OK;
+}
+
+JSObject*
+TCPServerSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return TCPServerSocketBinding::Wrap(aCx, this, aGivenProto);
+}
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPServerSocket.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_dom_TCPServerSocket_h
+#define mozilla_dom_TCPServerSocket_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIServerSocket.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+
+struct ServerSocketOptions;
+class GlobalObject;
+class TCPSocket;
+
+class TCPServerSocket final : public DOMEventTargetHelper
+                            , public nsIServerSocketListener
+{
+public:
+  TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort, bool aUseArrayBuffers,
+                  uint16_t aBacklog);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+  NS_DECL_NSISERVERSOCKETLISTENER
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+    return GetOwner();
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  uint16_t LocalPort();
+  void Close();
+
+  static already_AddRefed<TCPServerSocket>
+  Constructor(const GlobalObject& aGlobal,
+              uint16_t aPort,
+              const ServerSocketOptions& aOptions,
+              uint16_t aBacklog,
+              mozilla::ErrorResult& aRv);
+
+  IMPL_EVENT_HANDLER(connect);
+  IMPL_EVENT_HANDLER(error);
+
+private:
+  ~TCPServerSocket();
+  nsresult Init();
+  // Dispatch a TCPServerSocketEvent event of a given type at this object.
+  void FireEvent(const nsAString& aType, TCPSocket* aSocket);
+
+  // The server socket associated with this object.
+  nsCOMPtr<nsIServerSocket> mServerSocket;
+  int32_t mPort;
+  uint16_t mBacklog;
+  // True if any accepted sockets should use array buffers for received messages.
+  bool mUseArrayBuffers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TCPServerSocket_h
--- a/dom/network/TCPSocket.cpp
+++ b/dom/network/TCPSocket.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; 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/. */
 
 #include "mozilla/ErrorResult.h"
 #include "TCPSocket.h"
+#include "TCPServerSocket.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/TCPSocketBinding.h"
 #include "mozilla/dom/TCPSocketErrorEvent.h"
 #include "mozilla/dom/TCPSocketErrorEventBinding.h"
 #include "mozilla/dom/TCPSocketEvent.h"
 #include "mozilla/dom/TCPSocketEventBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "nsIArrayBufferInputStream.h"
@@ -68,16 +69,30 @@ LegacyMozTCPSocket::Open(const nsAString
   if (NS_WARN_IF(!api.Init(mGlobal))) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
   return TCPSocket::Constructor(globalObj, aHost, aPort, aOptions, aRv);
 }
 
+already_AddRefed<TCPServerSocket>
+LegacyMozTCPSocket::Listen(uint16_t aPort,
+                           const ServerSocketOptions& aOptions,
+                           uint16_t aBacklog,
+                           mozilla::ErrorResult& aRv)
+{
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(mGlobal))) {
+    return nullptr;
+  }
+  GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
+  return TCPServerSocket::Constructor(globalObj, aPort, aOptions, aBacklog, aRv);
+}
+
 bool
 LegacyMozTCPSocket::WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto,
                                JS::MutableHandle<JSObject*> aReflector)
 {
   return LegacyMozTCPSocketBinding::Wrap(aCx, this, aGivenProto, aReflector);
 }
 
@@ -154,16 +169,70 @@ TCPSocket::TCPSocket(nsIGlobalObject* aG
   }
 }
 
 TCPSocket::~TCPSocket()
 {
 }
 
 nsresult
+TCPSocket::CreateStream()
+{
+  nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If the other side is not listening, we will
+  // get an onInputStreamReady callback where available
+  // raises to indicate the connection was refused.
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mSocketInputStream);
+  NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE);
+
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+
+  rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mUseArrayBuffers) {
+    mInputStreamBinary = do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mInputStreamBinary->SetInputStream(mSocketInputStream);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mInputStreamScriptable->Init(mSocketInputStream);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISocketTransportService> sts =
+      do_GetService("@mozilla.org/network/socket-transport-service;1");
+
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts);
+  rv = mMultiplexStreamCopier->Init(mMultiplexStream,
+                                    mSocketOutputStream,
+                                    target,
+                                    true, /* source buffered */
+                                    false, /* sink buffered */
+                                    BUFFER_SIZE,
+                                    false, /* close source */
+                                    false); /* close sink */
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+nsresult
 TCPSocket::Init()
 {
   nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
   if (obs) {
     obs->AddObserver(this, "inner-window-destroyed", true);
   }
 
   mReadyState = TCPReadyState::Connecting;
@@ -181,57 +250,39 @@ TCPSocket::Init()
                                      nullptr, getter_AddRefs(mTransport));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
 
   mTransport->SetEventSink(this, mainThread);
 
-  rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // If the other side is not listening, we will
-  // get an onInputStreamReady callback where available
-  // raises to indicate the connection was refused.
-  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mSocketInputStream);
-  NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE);
-  rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread);
+  rv = CreateStream();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (mUseArrayBuffers) {
-    mInputStreamBinary = do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mInputStreamBinary->SetInputStream(mSocketInputStream);
-    NS_ENSURE_SUCCESS(rv, rv);
-  } else {
-    mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mInputStreamScriptable->Init(mSocketInputStream);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  return NS_OK;
+}
 
-  mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+nsresult
+TCPSocket::InitWithTransport(nsISocketTransport* aTransport)
+{
+  mTransport = aTransport;
+  nsresult rv = CreateStream();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
+  mReadyState = TCPReadyState::Open;
+  rv = CreateInputStreamPump();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts);
-  rv = mMultiplexStreamCopier->Init(mMultiplexStream,
-                                    mSocketOutputStream,
-                                    target,
-                                    true, /* source buffered */
-                                    false, /* sink buffered */
-                                    BUFFER_SIZE,
-                                    false, /* close source */
-                                    false); /* close sink */
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoCString host;
+  mTransport->GetHost(host);
+  mHost = NS_ConvertUTF8toUTF16(host);
+  int32_t port;
+  mTransport->GetPort(&port);
+  mPort = port;
 
 #ifdef MOZ_WIDGET_GONK
   nsCOMPtr<nsINetworkManager> networkManager = do_GetService("@mozilla.org/network/manager;1");
   if (networkManager) {
     networkManager->GetActiveNetworkInfo(getter_AddRefs(mActiveNetworkInfo));
   }
 #endif
 
@@ -700,16 +751,27 @@ TCPSocket::BinaryType()
   if (mUseArrayBuffers) {
     return TCPSocketBinaryType::Arraybuffer;
   } else {
     return TCPSocketBinaryType::String;
   }
 }
 
 already_AddRefed<TCPSocket>
+TCPSocket::CreateAcceptedSocket(nsIGlobalObject* aGlobal,
+                                nsISocketTransport* aTransport,
+                                bool aUseArrayBuffers)
+{
+  nsRefPtr<TCPSocket> socket = new TCPSocket(aGlobal, EmptyString(), 0, false, aUseArrayBuffers);
+  nsresult rv = socket->InitWithTransport(aTransport);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+  return socket.forget();
+}
+
+already_AddRefed<TCPSocket>
 TCPSocket::Constructor(const GlobalObject& aGlobal,
                        const nsAString& aHost,
                        uint16_t aPort,
                        const SocketOptions& aOptions,
                        mozilla::ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<TCPSocket> socket =
@@ -719,27 +781,19 @@ TCPSocket::Constructor(const GlobalObjec
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   return socket.forget();
 }
 
-NS_IMETHODIMP
-TCPSocket::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
-                             int64_t aProgress, int64_t aProgressMax)
+nsresult
+TCPSocket::CreateInputStreamPump()
 {
-  if (static_cast<uint32_t>(aStatus) != nsISocketTransport::STATUS_CONNECTED_TO) {
-    return NS_OK;
-  }
-
-  mReadyState = TCPReadyState::Open;
-  FireEvent(NS_LITERAL_STRING("open"));
-
   nsresult rv;
   mInputStreamPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint64_t suspendCount = mSuspendCount;
@@ -748,16 +802,32 @@ TCPSocket::OnTransportStatus(nsITranspor
   }
 
   rv = mInputStreamPump->AsyncRead(this, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TCPSocket::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+                             int64_t aProgress, int64_t aProgressMax)
+{
+  if (static_cast<uint32_t>(aStatus) != nsISocketTransport::STATUS_CONNECTED_TO) {
+    return NS_OK;
+  }
+
+  mReadyState = TCPReadyState::Open;
+  FireEvent(NS_LITERAL_STRING("open"));
+
+  nsresult rv = CreateInputStreamPump();
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 TCPSocket::OnInputStreamReady(nsIAsyncInputStream* aStream)
 {
   // Only used for detecting if the connection was refused.
 
   uint64_t dummy;
   nsresult rv = aStream->Available(&dummy);
   if (NS_FAILED(rv)) {
     MaybeReportErrorAndCloseIfOpen(NS_ERROR_CONNECTION_REFUSED);
--- a/dom/network/TCPSocket.h
+++ b/dom/network/TCPSocket.h
@@ -25,27 +25,35 @@ class nsIAsyncStreamCopier;
 class nsIInputStream;
 class nsINetworkInfo;
 
 namespace mozilla {
 class ErrorResult;
 namespace dom {
 
 class DOMError;
+struct ServerSocketOptions;
+class TCPServerSocket;
 class USVStringOrArrayBuffer;
 
 // This interface is only used for legacy navigator.mozTCPSocket API compatibility.
 class LegacyMozTCPSocket : public nsISupports
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(LegacyMozTCPSocket)
 
   explicit LegacyMozTCPSocket(nsPIDOMWindow* aWindow);
 
+  already_AddRefed<TCPServerSocket>
+  Listen(uint16_t aPort,
+         const ServerSocketOptions& aOptions,
+         uint16_t aBacklog,
+         ErrorResult& aRv);
+
   already_AddRefed<TCPSocket>
   Open(const nsAString& aHost,
        uint16_t aPort,
        const SocketOptions& aOptions,
        ErrorResult& aRv);
 
   bool WrapObject(JSContext* aCx,
                   JS::Handle<JSObject*> aGivenProto,
@@ -101,32 +109,43 @@ public:
 
   static already_AddRefed<TCPSocket>
   Constructor(const GlobalObject& aGlobal,
               const nsAString& aHost,
               uint16_t aPort,
               const SocketOptions& aOptions,
               ErrorResult& aRv);
 
+  // Create a TCPSocket object from an existing low-level socket connection.
+  // Used by the TCPServerSocket implementation when a new connection is accepted.
+  static already_AddRefed<TCPSocket>
+  CreateAcceptedSocket(nsIGlobalObject* aGlobal, nsISocketTransport* aTransport, bool aUseArrayBuffers);
+
   static bool SocketEnabled();
 
   IMPL_EVENT_HANDLER(open);
   IMPL_EVENT_HANDLER(drain);
   IMPL_EVENT_HANDLER(data);
   IMPL_EVENT_HANDLER(error);
   IMPL_EVENT_HANDLER(close);
 
   nsresult Init();
 
   // Inform this socket that a buffered send() has completed sending.
   void NotifyCopyComplete(nsresult aStatus);
 
 private:
   ~TCPSocket();
 
+  // Initialize this socket from an existing low-level connection.
+  nsresult InitWithTransport(nsISocketTransport* aTransport);
+  // Initialize the input/output streams for this socket object.
+  nsresult CreateStream();
+  // Initialize the asynchronous read operation from this socket's input stream.
+  nsresult CreateInputStreamPump();
   // Send the contents of the provided input stream, which is assumed to be the given length
   // for reporting and buffering purposes.
   bool Send(nsIInputStream* aStream, uint32_t aByteLength);
   // Begin an asynchronous copy operation if one is not already in progress.
   nsresult EnsureCopying();
   // Enable TLS on this socket.
   void ActivateTLS();
   // Dispatch an "error" event at this object.
--- a/dom/network/moz.build
+++ b/dom/network/moz.build
@@ -12,16 +12,17 @@ XPCSHELL_TESTS_MANIFESTS += [
 ]
 
 if CONFIG['MOZ_B2G_RIL']:
     XPCSHELL_TESTS_MANIFESTS += ['tests/unit_stats/xpcshell.ini']
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 EXPORTS.mozilla.dom += [
+    'TCPServerSocket.h',
     'TCPSocket.h',
     'UDPSocket.h',
 ]
 
 EXPORTS.mozilla.dom.network += [
     'Connection.h',
     'Constants.h',
     'TCPServerSocketChild.h',
@@ -30,16 +31,17 @@ EXPORTS.mozilla.dom.network += [
     'TCPSocketParent.h',
     'Types.h',
     'UDPSocketChild.h',
     'UDPSocketParent.h',
 ]
 
 UNIFIED_SOURCES += [
     'Connection.cpp',
+    'TCPServerSocket.cpp',
     'TCPServerSocketChild.cpp',
     'TCPServerSocketParent.cpp',
     'TCPSocket.cpp',
     'TCPSocketChild.cpp',
     'TCPSocketParent.cpp',
     'UDPSocket.cpp',
     'UDPSocketChild.cpp',
     'UDPSocketParent.cpp',
--- a/dom/network/tests/mochitest.ini
+++ b/dom/network/tests/mochitest.ini
@@ -8,16 +8,17 @@ support-files =
 skip-if = toolkit == "gonk" || toolkit == 'android'
 [test_tcpsocket_client_and_server_basics.html]
 [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
+[test_tcpsocket_legacy.html]
 [test_networkstats_alarms.html]
 skip-if = toolkit != "gonk"
 [test_networkstats_basics.html]
 skip-if = toolkit != "gonk"
 [test_networkstats_disabled.html]
 skip-if = toolkit != "gonk"
 [test_networkstats_enabled_no_perm.html]
 skip-if = toolkit != "gonk"
--- a/dom/network/tests/test_tcpsocket_client_and_server_basics.js
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js
@@ -122,26 +122,26 @@ function listenForEventsOnSocket(socket,
  * calling listenForEventsOnSocket(socket).  This must be done because we need
  * to add the event listener during the connection.
  */
 function waitForConnection(listeningServer) {
   return new Promise(function(resolve, reject) {
     // Because of the event model of sockets, we can't use the
     // listenForEventsOnSocket mechanism; we need to hook up listeners during
     // the connect event.
-    listeningServer.onconnect = function(socket) {
+    listeningServer.onconnect = function(event) {
       // Clobber the listener to get upset if it receives any more connections
       // after this.
       listeningServer.onconnect = function() {
         ok(false, 'Received a connection when not expecting one.');
       };
       ok(true, 'Listening server accepted socket');
       resolve({
-        socket: socket,
-        queue: listenForEventsOnSocket(socket, 'server')
+        socket: event.socket,
+        queue: listenForEventsOnSocket(event.socket, 'server')
       });
     };
   });
 }
 
 function defer() {
   var deferred = {};
   deferred.promise = new Promise(function(resolve, reject) {
@@ -166,21 +166,20 @@ function* test_basics() {
     permDeferred.resolve);
   yield permDeferred.promise;
 
   // See bug 903830; in e10s mode we never get to find out the localPort if we
   // let it pick a free port by choosing 0.  This is the same port the xpcshell
   // test was using.
   let serverPort = 8085;
 
-  let mozTCPSocket = navigator.mozTCPSocket;
   // - Start up a listening socket.
-  let listeningServer = mozTCPSocket.listen(serverPort,
-                                         { binaryType: 'arraybuffer' },
-                                         SERVER_BACKLOG);
+  let listeningServer = new TCPServerSocket(serverPort,
+                                            { binaryType: 'arraybuffer' },
+                                            SERVER_BACKLOG);
 
   let connectedPromise = waitForConnection(listeningServer);
 
   // -- Open a connection to the server
   let clientSocket = new TCPSocket('127.0.0.1', serverPort,
                                    { binaryType: 'arraybuffer' });
   let clientQueue = listenForEventsOnSocket(clientSocket, 'client');
 
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_legacy.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test of legacy navigator interface for opening TCPSocket/TCPServerSocket.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 885982</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv(
+    { set: [ ['dom.mozTCPSocket.enabled', true] ] },
+    setPerms);
+
+  function setPerms() {
+    SpecialPowers.pushPermissions(
+      [ { type: 'tcp-socket', allow: true, context: document } ],
+      runTest);
+  }
+
+  function runTest() {
+    // See bug 903830; in e10s mode we never get to find out the localPort if we
+    // let it pick a free port by choosing 0.  This is the same port the xpcshell
+    // test was using.
+    var serverPort = 8085;
+
+    var listeningServer = navigator.mozTCPSocket.listen(serverPort,
+                                                        { binaryType: 'arraybuffer' },
+                                                        -1);
+    listeningServer.onconnect = function(ev) {
+      ok(true, "got server connect");
+      ev.socket.close()
+    }
+
+    var clientSocket = navigator.mozTCPSocket.open('127.0.0.1', serverPort,
+                                                   { binaryType: 'arraybuffer' });
+    clientSocket.onopen = function() { ok(true, "got client open"); }
+    clientSocket.onclose = function() {
+      ok(true, "got client close");
+      SimpleTest.finish();
+    }
+  }
+</script>
+</body>
+</html>
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -431,23 +431,20 @@ partial interface Navigator {
   readonly attribute InputPortManager inputPortManager;
 };
 
 partial interface Navigator {
   [Throws, Pref="dom.presentation.enabled", CheckAnyPermissions="presentation", AvailableIn="PrivilegedApps", SameObject]
   readonly attribute Presentation? presentation;
 };
 
-/*
-  // Commented out due to the continued presence of nsIDOMTCPSocket and its navigator
-  // properties that interfere with tests.
-  partial interface Navigator {
+partial interface Navigator {
   [NewObject, Pref="dom.mozTCPSocket.enabled", CheckAnyPermissions="tcp-socket"]
   readonly attribute LegacyMozTCPSocket mozTCPSocket;
-};*/
+};
 
 #ifdef MOZ_EME
 partial interface Navigator {
   [Pref="media.eme.apiVisible", NewObject]
   Promise<MediaKeySystemAccess>
   requestMediaKeySystemAccess(DOMString keySystem,
                               optional sequence<MediaKeySystemOptions> supportedConfigurations);
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TCPServerSocket.webidl
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+
+/**
+ * TCPServerSocket
+ *
+ * An interface to a server socket that can accept incoming connections for gaia apps.
+ */
+
+dictionary ServerSocketOptions {
+  TCPSocketBinaryType binaryType = "string";
+};
+
+[Constructor(unsigned short port, optional ServerSocketOptions options, optional unsigned short backlog = 0),
+ Pref="dom.mozTCPSocket.enabled",
+ CheckAnyPermissions="tcp-socket",
+ Exposed=Window]
+interface TCPServerSocket : EventTarget {
+  /**
+   * The port of this server socket object.
+   */
+  readonly attribute unsigned short localPort;
+
+  /**
+   * The "connect" event is dispatched when a client connection is accepted.
+   * The event object will be a TCPServerSocketEvent containing a TCPSocket
+   * instance, which is used for communication between client and server.
+   */
+  attribute EventHandler onconnect;
+
+  /**
+   * The "error" event will be dispatched when a listening server socket is
+   * unexpectedly disconnected.
+   */
+  attribute EventHandler onerror;
+
+  /**
+   * Close the server socket.
+   */
+  void close();
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TCPServerSocketEvent.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/. */
+
+[Constructor(DOMString type, optional TCPServerSocketEventInit eventInitDict),
+ Pref="dom.mozTCPSocket.enabled",
+ CheckAnyPermissions="tcp-socket",
+ Exposed=Window]
+interface TCPServerSocketEvent : Event {
+  readonly attribute TCPSocket socket;
+};
+
+dictionary TCPServerSocketEventInit : EventInit {
+  TCPSocket? socket = null;
+};
--- a/dom/webidl/TCPSocket.webidl
+++ b/dom/webidl/TCPSocket.webidl
@@ -29,16 +29,19 @@ enum TCPReadyState {
 
 [NoInterfaceObject]
 interface LegacyMozTCPSocket {
   /**
    * Legacy constructor for API compatibility.
    */
   [Throws]
   TCPSocket open(DOMString host, unsigned short port, optional SocketOptions options);
+
+  [Throws]
+  TCPServerSocket listen(unsigned short port, optional ServerSocketOptions options, optional unsigned short backlog = 0);
 };
 
 [Constructor(DOMString host, unsigned short port, optional SocketOptions options),
  Pref="dom.mozTCPSocket.enabled",
  CheckAnyPermissions="tcp-socket",
  Exposed=Window]
 interface TCPSocket : EventTarget {
   /**
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -519,16 +519,18 @@ WEBIDL_FILES = [
     'SVGTSpanElement.webidl',
     'SVGUnitTypes.webidl',
     'SVGURIReference.webidl',
     'SVGUseElement.webidl',
     'SVGViewElement.webidl',
     'SVGZoomAndPan.webidl',
     'SVGZoomEvent.webidl',
     'SystemUpdate.webidl',
+    'TCPServerSocket.webidl',
+    'TCPServerSocketEvent.webidl',
     'TCPSocket.webidl',
     'TCPSocketErrorEvent.webidl',
     'TCPSocketEvent.webidl',
     'Telephony.webidl',
     'TelephonyCall.webidl',
     'TelephonyCallGroup.webidl',
     'TelephonyCallId.webidl',
     'Text.webidl',
@@ -794,16 +796,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'PresentationSessionConnectEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
     'ScrollViewChangeEvent.webidl',
     'SelectionStateChangedEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
+    'TCPServerSocketEvent.webidl',
     'TCPSocketErrorEvent.webidl',
     'TCPSocketEvent.webidl',
     'TrackEvent.webidl',
     'TVCurrentChannelChangedEvent.webidl',
     'TVCurrentSourceChangedEvent.webidl',
     'TVEITBroadcastedEvent.webidl',
     'TVScanningStateChangedEvent.webidl',
     'UDPMessageEvent.webidl',