Bug 885982 - Part 1: Convert TCPSocket to WebIDL and rewrite in C++. r=asuth,mayhemer,bz
authorJosh Matthews <josh@joshmatthews.net>
Wed, 25 Mar 2015 10:35:59 -0400
changeset 296053 bf8d9233a7bd5ea2e7a1a7916dd1a28021bca7f8
parent 296052 5559e86a2f3ba157d2bb45cc7bfb9f7a690c3327
child 296054 2ad05ac8aac349d23626add9ffd85cbd06bd4de5
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, mayhemer, bz
bugs885982
milestone43.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 885982 - Part 1: Convert TCPSocket to WebIDL and rewrite in C++. r=asuth,mayhemer,bz
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsGkAtomList.h
dom/bindings/Bindings.conf
dom/events/test/test_all_synthetic_events.html
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_default_permissions.html
dom/network/tests/test_tcpsocket_enabled_no_perm.html
dom/network/tests/test_tcpsocket_enabled_with_perm.html
dom/permission/tests/test_tcp-socket.html
dom/webidl/Navigator.webidl
dom/webidl/TCPSocket.webidl
dom/webidl/TCPSocketErrorEvent.webidl
dom/webidl/TCPSocketEvent.webidl
dom/webidl/moz.build
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -38,16 +38,17 @@
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
+#include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/Telephony.h"
 #include "mozilla/dom/Voicemail.h"
 #include "mozilla/dom/TVManager.h"
 #include "mozilla/dom/VRDevice.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
@@ -1800,16 +1801,23 @@ Navigator::GetInputPortManager(ErrorResu
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   }
 
   return mInputPortManager;
 }
 
+already_AddRefed<LegacyMozTCPSocket>
+Navigator::MozTCPSocket()
+{
+  nsRefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
+  return socket.forget();
+}
+
 #ifdef MOZ_B2G
 already_AddRefed<Promise>
 Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
                                 ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -94,16 +94,17 @@ class PowerManager;
 class CellBroadcast;
 class IccManager;
 class Telephony;
 class Voicemail;
 class TVManager;
 class InputPortManager;
 class DeviceStorageAreaListener;
 class Presentation;
+class LegacyMozTCPSocket;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 class AudioChannelManager;
@@ -237,16 +238,17 @@ public:
   DesktopNotificationCenter* GetMozNotification(ErrorResult& aRv);
   CellBroadcast* GetMozCellBroadcast(ErrorResult& aRv);
   IccManager* GetMozIccManager(ErrorResult& aRv);
   MobileMessageManager* GetMozMobileMessage();
   Telephony* GetMozTelephony(ErrorResult& aRv);
   Voicemail* GetMozVoicemail(ErrorResult& aRv);
   TVManager* GetTv();
   InputPortManager* GetInputPortManager(ErrorResult& aRv);
+  already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
   network::Connection* GetConnection(ErrorResult& aRv);
   nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
   void MozSetMessageHandler(const nsAString& aType,
                             systemMessageCallback* aCallback,
                             ErrorResult& aRv);
   bool MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv);
   void MozSetMessageHandlerPromise(Promise& aPromise, ErrorResult& aRv);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -748,25 +748,27 @@ GK_ATOM(onDOMCharacterDataModified, "onD
 GK_ATOM(onDOMFocusIn, "onDOMFocusIn")
 GK_ATOM(onDOMFocusOut, "onDOMFocusOut")
 GK_ATOM(onDOMMouseScroll, "onDOMMouseScroll")
 GK_ATOM(onDOMNodeInserted, "onDOMNodeInserted")
 GK_ATOM(onDOMNodeInsertedIntoDocument, "onDOMNodeInsertedIntoDocument")
 GK_ATOM(onDOMNodeRemoved, "onDOMNodeRemoved")
 GK_ATOM(onDOMNodeRemovedFromDocument, "onDOMNodeRemovedFromDocument")
 GK_ATOM(onDOMSubtreeModified, "onDOMSubtreeModified")
+GK_ATOM(ondata, "ondata")
 GK_ATOM(ondrag, "ondrag")
 GK_ATOM(ondragdrop, "ondragdrop")
 GK_ATOM(ondragend, "ondragend")
 GK_ATOM(ondragenter, "ondragenter")
 GK_ATOM(ondragexit, "ondragexit")
 GK_ATOM(ondraggesture, "ondraggesture")
 GK_ATOM(ondragleave, "ondragleave")
 GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
+GK_ATOM(ondrain, "ondrain")
 GK_ATOM(ondrop, "ondrop")
 GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
 GK_ATOM(onfacesdetected, "onfacesdetected")
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -691,16 +691,21 @@ DOMInterfaces = {
     'nativeType': 'nsIInputStream',
     'notflattened': True
 },
 
 'KeyEvent': {
     'concrete': False
 },
 
+'LegacyMozTCPSocket': {
+    'headerFile': 'TCPSocket.h',
+    'wrapperCache': False,
+},
+
 'LocalMediaStream': {
     'headerFile': 'DOMMediaStream.h',
     'nativeType': 'mozilla::DOMLocalMediaStream'
 },
 
 'Location': {
     'nativeType': 'nsLocation',
 },
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -479,16 +479,24 @@ const kEventConstructors = {
                                              },
   SVGZoomEvent:                              { create: function (aName, aProps) {
                                                          var e = document.createEvent("svgzoomevent");
                                                          e.initUIEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                        aProps.view, aProps.detail);
                                                          return e;
                                                        },
                                              },
+  TCPSocketErrorEvent:                       { create: function(aName, aProps) {
+                                                         return new TCPSocketErrorEvent(aName, aProps);
+                                                       },
+                                             },
+  TCPSocketEvent:                            { create: function(aName, aProps) {
+                                                         return new TCPSocketEvent(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/TCPSocket.cpp
@@ -0,0 +1,896 @@
+/* -*- 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 "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"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIInputStream.h"
+#include "nsIBinaryInputStream.h"
+#include "nsIScriptableInputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIAsyncInputStream.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransport.h"
+#include "nsIOutputStream.h"
+#include "nsINSSErrorsService.h"
+#include "nsISSLSocketControl.h"
+#include "nsStringStream.h"
+#include "secerr.h"
+#include "sslerr.h"
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkStatsServiceProxy.h"
+#include "nsINetworkManager.h"
+#include "nsINetworkInterface.h"
+#endif
+
+#define BUFFER_SIZE 65536
+#define NETWORK_STATS_THRESHOLD 65536
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(LegacyMozTCPSocket, mGlobal)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LegacyMozTCPSocket)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LegacyMozTCPSocket)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LegacyMozTCPSocket)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+LegacyMozTCPSocket::LegacyMozTCPSocket(nsPIDOMWindow* aWindow)
+: mGlobal(do_QueryInterface(aWindow))
+{
+}
+
+LegacyMozTCPSocket::~LegacyMozTCPSocket()
+{
+}
+
+already_AddRefed<TCPSocket>
+LegacyMozTCPSocket::Open(const nsAString& aHost,
+                         uint16_t aPort,
+                         const SocketOptions& aOptions,
+                         mozilla::ErrorResult& aRv)
+{
+  AutoJSAPI api;
+  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);
+}
+
+bool
+LegacyMozTCPSocket::WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto,
+                               JS::MutableHandle<JSObject*> aReflector)
+{
+  return LegacyMozTCPSocketBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPSocket,
+                                               DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPSocket,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransport)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketInputStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketOutputStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamPump)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamScriptable)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamBinary)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMultiplexStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMultiplexStreamCopier)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingDataAfterStartTLS)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPSocket,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransport)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketInputStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketOutputStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamPump)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamScriptable)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamBinary)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMultiplexStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMultiplexStreamCopier)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingDataAfterStartTLS)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TCPSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TCPSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TCPSocket)
+  NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
+                     bool aSsl, bool aUseArrayBuffers)
+  : DOMEventTargetHelper(aGlobal)
+  , mReadyState(TCPReadyState::Closed)
+  , mUseArrayBuffers(aUseArrayBuffers)
+  , mHost(aHost)
+  , mPort(aPort)
+  , mSsl(aSsl)
+  , mAsyncCopierActive(false)
+  , mWaitingForDrain(false)
+  , mInnerWindowID(0)
+  , mSuspendCount(0)
+  , mWaitingForStartTLS(false)
+#ifdef MOZ_WIDGET_GONK
+  , mTxBytes(0)
+  , mRxBytes(0)
+  , mAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
+  , mInBrowser(false)
+#endif
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
+  if (window && window->IsOuterWindow()) {
+    window = window->GetCurrentInnerWindow();
+  }
+  if (window) {
+    mInnerWindowID = window->WindowID();
+  }
+}
+
+TCPSocket::~TCPSocket()
+{
+}
+
+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;
+
+  nsCOMPtr<nsISocketTransportService> sts =
+    do_GetService("@mozilla.org/network/socket-transport-service;1");
+
+  const char* socketTypes[1];
+  if (mSsl) {
+    socketTypes[0] = "ssl";
+  } else {
+    socketTypes[0] = "starttls";
+  }
+  nsresult rv = sts->CreateTransport(socketTypes, 1, NS_ConvertUTF16toUTF8(mHost), mPort,
+                                     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);
+  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<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);
+
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsINetworkManager> networkManager = do_GetService("@mozilla.org/network/manager;1");
+  if (networkManager) {
+    networkManager->GetActiveNetworkInfo(getter_AddRefs(mActiveNetworkInfo));
+  }
+#endif
+
+  return NS_OK;
+}
+
+void
+TCPSocket::UpgradeToSecure(mozilla::ErrorResult& aRv)
+{
+  if (mReadyState != TCPReadyState::Open) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  if (mSsl) {
+    return;
+  }
+
+  mSsl = true;
+
+  uint32_t count = 0;
+  mMultiplexStream->GetCount(&count);
+  if (!count) {
+    ActivateTLS();
+  } else {
+    mWaitingForStartTLS = true;
+  }
+}
+
+namespace {
+class CopierCallbacks final : public nsIRequestObserver
+{
+  nsRefPtr<TCPSocket> mOwner;
+public:
+  explicit CopierCallbacks(TCPSocket* aSocket) : mOwner(aSocket) {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+private:
+  ~CopierCallbacks() {}
+};
+
+NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
+
+NS_IMETHODIMP
+CopierCallbacks::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
+{
+  mOwner->NotifyCopyComplete(aStatus);
+  return NS_OK;
+}
+} // unnamed namespace
+
+nsresult
+TCPSocket::EnsureCopying()
+{
+  if (mAsyncCopierActive) {
+    return NS_OK;
+  }
+
+  mAsyncCopierActive = true;
+  nsRefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
+  return mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr);
+}
+
+void
+TCPSocket::NotifyCopyComplete(nsresult aStatus)
+{
+  mAsyncCopierActive = false;
+  mMultiplexStream->RemoveStream(0);
+  if (NS_FAILED(aStatus)) {
+    MaybeReportErrorAndCloseIfOpen(aStatus);
+    return;
+  }
+
+  uint32_t count;
+  nsresult rv = mMultiplexStream->GetCount(&count);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  if (count) {
+    EnsureCopying();
+    return;
+  }
+
+  // If we are waiting for initiating starttls, we can begin to
+  // activate tls now.
+  if (mWaitingForStartTLS && mReadyState == TCPReadyState::Open) {
+    ActivateTLS();
+    mWaitingForStartTLS = false;
+    // If we have pending data, we should send them, or fire
+    // a drain event if we are waiting for it.
+    if (!mPendingDataAfterStartTLS.IsEmpty()) {
+      while (!mPendingDataAfterStartTLS.IsEmpty()) {
+        nsCOMPtr<nsIInputStream> stream = mPendingDataAfterStartTLS[0];
+        mMultiplexStream->AppendStream(stream);
+        mPendingDataAfterStartTLS.RemoveElementAt(0);
+      }
+      EnsureCopying();
+      return;
+    }
+  }
+
+  if (mWaitingForDrain) {
+    mWaitingForDrain = false;
+    FireEvent(NS_LITERAL_STRING("drain"));
+  }
+
+  if (mReadyState == TCPReadyState::Closing) {
+    mSocketOutputStream->Close();
+    mReadyState = TCPReadyState::Closed;
+    FireEvent(NS_LITERAL_STRING("close"));
+  }
+}
+
+void
+TCPSocket::ActivateTLS()
+{
+  nsCOMPtr<nsISupports> securityInfo;
+  mTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
+  nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(securityInfo);
+  if (socketControl) {
+    socketControl->StartTLS();
+  }
+}
+
+void
+TCPSocket::FireErrorEvent(const nsAString& aName, const nsAString& aType)
+{
+  TCPSocketErrorEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mName = aName;
+  init.mMessage = aType;
+
+  nsRefPtr<TCPSocketErrorEvent> event =
+    TCPSocketErrorEvent::Constructor(this, NS_LITERAL_STRING("error"), init);
+  event->SetTrusted(true);
+  bool dummy;
+  DispatchEvent(event, &dummy);
+}
+
+void
+TCPSocket::FireEvent(const nsAString& aType)
+{
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+    return;
+  }
+  JS::Rooted<JS::Value> val(api.cx());
+  FireDataEvent(api.cx(), aType, val);
+}
+
+void
+TCPSocket::FireDataEvent(JSContext* aCx, const nsAString& aType, JS::Handle<JS::Value> aData)
+{
+  RootedDictionary<TCPSocketEventInit> init(aCx);
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mData = aData;
+
+  nsRefPtr<TCPSocketEvent> event =
+    TCPSocketEvent::Constructor(this, aType, init);
+  event->SetTrusted(true);
+  bool dummy;
+  DispatchEvent(event, &dummy);
+}
+
+JSObject*
+TCPSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return TCPSocketBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+TCPSocket::GetHost(nsAString& aHost)
+{
+  aHost.Assign(mHost);
+}
+
+uint32_t
+TCPSocket::Port()
+{
+  return mPort;
+}
+
+bool
+TCPSocket::Ssl()
+{
+  return mSsl;
+}
+
+uint64_t
+TCPSocket::BufferedAmount()
+{
+  if (mMultiplexStream) {
+    uint64_t available = 0;
+    mMultiplexStream->Available(&available);
+    return available;
+  }
+  return 0;
+}
+
+void
+TCPSocket::Suspend()
+{
+  if (mInputStreamPump) {
+    mInputStreamPump->Suspend();
+  }
+  mSuspendCount++;
+}
+
+void
+TCPSocket::Resume(mozilla::ErrorResult& aRv)
+{
+  if (!mSuspendCount) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  if (mInputStreamPump) {
+    mInputStreamPump->Resume();
+  }
+  mSuspendCount--;
+}
+
+nsresult
+TCPSocket::MaybeReportErrorAndCloseIfOpen(nsresult status) {
+#ifdef MOZ_WIDGET_GONK
+  // Save network statistics once the connection is closed.
+  // For now this function is Gonk-specific.
+  SaveNetworkStats(true);
+#endif
+
+  // If we're closed, we've already reported the error or just don't need to
+  // report the error.
+  if (mReadyState == TCPReadyState::Closed) {
+    return NS_OK;
+  }
+  mReadyState = TCPReadyState::Closed;
+
+  if (NS_FAILED(status)) {
+    // Convert the status code to an appropriate error message.
+
+    nsString errorType, errName;
+
+    // security module? (and this is an error)
+    if ((static_cast<uint32_t>(status) & 0xFF0000) == 0x5a0000) {
+      nsCOMPtr<nsINSSErrorsService> errSvc = do_GetService("@mozilla.org/nss_errors_service;1");
+      // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+      // somehow not in the set of covered errors.
+      uint32_t errorClass;
+      nsresult rv = errSvc->GetErrorClass(status, &errorClass);
+      if (NS_FAILED(rv)) {
+        errorType.AssignLiteral("SecurityProtocol");
+      } else {
+        switch (errorClass) {
+          case nsINSSErrorsService::ERROR_CLASS_BAD_CERT:
+            errorType.AssignLiteral("SecurityCertificate");
+            break;
+          default:
+            errorType.AssignLiteral("SecurityProtocol");
+            break;
+        }
+      }
+
+      // NSS_SEC errors (happen below the base value because of negative vals)
+      if ((static_cast<int32_t>(status) & 0xFFFF) < abs(nsINSSErrorsService::NSS_SEC_ERROR_BASE)) {
+        switch (static_cast<SECErrorCodes>(status)) {
+          case SEC_ERROR_EXPIRED_CERTIFICATE:
+            errName.AssignLiteral("SecurityExpiredCertificateError");
+            break;
+          case SEC_ERROR_REVOKED_CERTIFICATE:
+            errName.AssignLiteral("SecurityRevokedCertificateError");
+            break;
+            // per bsmith, we will be unable to tell these errors apart very soon,
+            // so it makes sense to just folder them all together already.
+          case SEC_ERROR_UNKNOWN_ISSUER:
+          case SEC_ERROR_UNTRUSTED_ISSUER:
+          case SEC_ERROR_UNTRUSTED_CERT:
+          case SEC_ERROR_CA_CERT_INVALID:
+            errName.AssignLiteral("SecurityUntrustedCertificateIssuerError");
+            break;
+          case SEC_ERROR_INADEQUATE_KEY_USAGE:
+            errName.AssignLiteral("SecurityInadequateKeyUsageError");
+            break;
+          case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
+            errName.AssignLiteral("SecurityCertificateSignatureAlgorithmDisabledError");
+            break;
+          default:
+            errName.AssignLiteral("SecurityError");
+            break;
+        }
+      } else {
+        // NSS_SSL errors
+        switch (static_cast<SSLErrorCodes>(status)) {
+          case SSL_ERROR_NO_CERTIFICATE:
+            errName.AssignLiteral("SecurityNoCertificateError");
+            break;
+          case SSL_ERROR_BAD_CERTIFICATE:
+            errName.AssignLiteral("SecurityBadCertificateError");
+            break;
+          case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:
+            errName.AssignLiteral("SecurityUnsupportedCertificateTypeError");
+            break;
+          case SSL_ERROR_UNSUPPORTED_VERSION:
+            errName.AssignLiteral("SecurityUnsupportedTLSVersionError");
+            break;
+          case SSL_ERROR_BAD_CERT_DOMAIN:
+            errName.AssignLiteral("SecurityCertificateDomainMismatchError");
+            break;
+          default:
+            errName.AssignLiteral("SecurityError");
+            break;
+        }
+      }
+    } else {
+      // must be network
+      errorType.AssignLiteral("Network");
+
+      switch (status) {
+        // connect to host:port failed
+        case NS_ERROR_CONNECTION_REFUSED:
+          errName.AssignLiteral("ConnectionRefusedError");
+          break;
+          // network timeout error
+        case NS_ERROR_NET_TIMEOUT:
+          errName.AssignLiteral("NetworkTimeoutError");
+          break;
+          // hostname lookup failed
+        case NS_ERROR_UNKNOWN_HOST:
+          errName.AssignLiteral("DomainNotFoundError");
+          break;
+        case NS_ERROR_NET_INTERRUPT:
+          errName.AssignLiteral("NetworkInterruptError");
+          break;
+        default:
+          errName.AssignLiteral("NetworkError");
+          break;
+      }
+    }
+
+    FireErrorEvent(errName, errorType);
+  }
+
+  FireEvent(NS_LITERAL_STRING("close"));
+  return NS_OK;
+}
+
+void
+TCPSocket::Close()
+{
+  if (mReadyState == TCPReadyState::Closed || mReadyState == TCPReadyState::Closing) {
+    return;
+  }
+
+  mReadyState = TCPReadyState::Closing;
+
+  uint32_t count = 0;
+  mMultiplexStream->GetCount(&count);
+  if (!count) {
+    mSocketOutputStream->Close();
+  }
+  mSocketInputStream->Close();
+}
+
+bool
+TCPSocket::Send(const nsACString& aData, mozilla::ErrorResult& aRv)
+{
+  if (mReadyState != TCPReadyState::Open) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return false;
+  }
+
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), aData);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return false;
+  }
+  uint64_t byteLength;
+  rv = stream->Available(&byteLength);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return false;
+  }
+  return Send(stream, byteLength);
+}
+
+bool
+TCPSocket::Send(const ArrayBuffer& aData,
+                uint32_t aByteOffset,
+                const Optional<uint32_t>& aByteLength,
+                mozilla::ErrorResult& aRv)
+{
+  AutoJSAPI api;
+  if (!api.Init(GetOwner()) ||
+      mReadyState != TCPReadyState::Open) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return false;
+  }
+
+  aData.ComputeLengthAndData();
+  uint32_t byteLength = aByteLength.WasPassed() ? aByteLength.Value() : aData.Length();
+
+  JS::Rooted<JSObject*> obj(api.cx(), aData.Obj());
+  JSAutoCompartment ac(api.cx(), obj);
+  JS::Rooted<JS::Value> value(api.cx(), JS::ObjectValue(*obj));
+
+  nsCOMPtr<nsIArrayBufferInputStream> stream =
+      do_CreateInstance("@mozilla.org/io/arraybuffer-input-stream;1");
+  nsresult rv = stream->SetData(value, aByteOffset, byteLength, api.cx());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return false;
+  }
+
+  return Send(stream, byteLength);
+}
+
+bool
+TCPSocket::Send(nsIInputStream* aStream, uint32_t aByteLength)
+{
+  bool bufferFull = BufferedAmount() + aByteLength > BUFFER_SIZE;
+  if (bufferFull) {
+    // If we buffered more than some arbitrary amount of data,
+    // (65535 right now) we should tell the caller so they can
+    // wait until ondrain is called if they so desire. Once all the
+    // buffered data has been written to the socket, ondrain is
+    // called.
+    mWaitingForDrain = true;
+  }
+
+  if (mWaitingForStartTLS) {
+    // When we are waiting for starttls, newStream is added to pendingData
+    // and will be appended to multiplexStream after tls had been set up.
+    mPendingDataAfterStartTLS.AppendElement(aStream);
+  } else {
+    mMultiplexStream->AppendStream(aStream);
+  }
+
+  EnsureCopying();
+
+#ifdef MOZ_WIDGET_GONK
+  // Collect transmitted amount for network statistics.
+  mTxBytes += aByteLength;
+  SaveNetworkStats(false);
+#endif
+
+  return !bufferFull;
+}
+
+TCPReadyState
+TCPSocket::ReadyState()
+{
+  return mReadyState;
+}
+
+TCPSocketBinaryType
+TCPSocket::BinaryType()
+{
+  if (mUseArrayBuffers) {
+    return TCPSocketBinaryType::Arraybuffer;
+  } else {
+    return TCPSocketBinaryType::String;
+  }
+}
+
+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 =
+    new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport,
+                  aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer);
+  nsresult rv = socket->Init();
+  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)
+{
+  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;
+  while (suspendCount--) {
+    mInputStreamPump->Suspend();
+  }
+
+  rv = mInputStreamPump->AsyncRead(this, nullptr);
+  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);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream,
+                           uint64_t aOffset, uint32_t aCount)
+{
+  AutoJSAPI api;
+  if (!api.Init(GetOwner())) {
+    return NS_ERROR_FAILURE;
+  }
+  JSContext* cx = api.cx();
+
+  if (mUseArrayBuffers) {
+    nsTArray<uint8_t> buffer;
+    buffer.SetCapacity(aCount);
+    uint32_t actual;
+    nsresult rv = aStream->Read(reinterpret_cast<char*>(buffer.Elements()), aCount, &actual);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(actual == aCount);
+    buffer.SetLength(actual);
+
+    JS::Rooted<JS::Value> value(cx);
+    if (!ToJSValue(cx, TypedArrayCreator<Uint8Array>(buffer), &value)) {
+      return NS_ERROR_FAILURE;
+    }
+    FireDataEvent(cx, NS_LITERAL_STRING("data"), value);
+  } else {
+    nsCString data;
+    nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    JS::Rooted<JS::Value> value(cx);
+    if (!ToJSValue(cx, NS_ConvertASCIItoUTF16(data), &value)) {
+      return NS_ERROR_FAILURE;
+    }
+    FireDataEvent(cx, NS_LITERAL_STRING("data"), value);
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  // Collect received amount for network statistics.
+  mRxBytes += aCount;
+  SaveNetworkStats(false);
+#endif
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
+{
+  uint32_t count;
+  nsresult rv = mMultiplexStream->GetCount(&count);
+  NS_ENSURE_SUCCESS(rv, rv);
+  bool bufferedOutput = count != 0;
+
+  mInputStreamPump = nullptr;
+
+  if (bufferedOutput && NS_SUCCEEDED(aStatus)) {
+    // If we have some buffered output still, and status is not an
+    // error, the other side has done a half-close, but we don't
+    // want to be in the close state until we are done sending
+    // everything that was buffered. We also don't want to call onclose
+    // yet.
+    return NS_OK;
+  }
+
+  // We call this even if there is no error.
+  MaybeReportErrorAndCloseIfOpen(aStatus);
+  return NS_OK;
+}
+
+#ifdef MOZ_WIDGET_GONK
+void
+TCPSocket::SaveNetworkStats(bool aEnforce)
+{
+  if (!mTxBytes && !mRxBytes) {
+    // There is no traffic at all. No need to save statistics.
+    return;
+  }
+
+  // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy
+  // only when the total amount exceeds the predefined threshold value.
+  // The purpose is to avoid too much overhead for collecting statistics.
+  uint32_t totalBytes = mTxBytes + mRxBytes;
+  if (!aEnforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+    return;
+  }
+
+  nsCOMPtr<nsINetworkStatsServiceProxy> nssProxy =
+    do_GetService("@mozilla.org/networkstatsServiceProxy;1");
+  if (!nssProxy) {
+    return;
+  }
+
+  nssProxy->SaveAppStats(mAppId, mInBrowser, mActiveNetworkInfo, PR_Now(),
+                         mRxBytes, mTxBytes, false, nullptr);
+
+  // Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
+  mTxBytes = mRxBytes = 0;
+}
+#endif
+
+NS_IMETHODIMP
+TCPSocket::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+  if (!strcmp(aTopic, "inner-window-destroyed")) {
+    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+    uint64_t innerID;
+    nsresult rv = wrapper->GetData(&innerID);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (innerID == mInnerWindowID) {
+      nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
+      if (obs) {
+        obs->RemoveObserver(this, "inner-window-destroyed");
+      }
+
+      Close();
+    }
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPSocket.h
@@ -0,0 +1,201 @@
+/* -*- 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_TCPSocket_h
+#define mozilla_dom_TCPSocket_h
+
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsITransport.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsISupportsImpl.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "js/RootingAPI.h"
+
+class nsISocketTransport;
+class nsIInputStreamPump;
+class nsIScriptableInputStream;
+class nsIBinaryInputStream;
+class nsIMultiplexInputStream;
+class nsIAsyncStreamCopier;
+class nsIInputStream;
+class nsINetworkInfo;
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+
+class DOMError;
+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<TCPSocket>
+  Open(const nsAString& aHost,
+       uint16_t aPort,
+       const SocketOptions& aOptions,
+       ErrorResult& aRv);
+
+  bool WrapObject(JSContext* aCx,
+                  JS::Handle<JSObject*> aGivenProto,
+                  JS::MutableHandle<JSObject*> aReflector);
+
+private:
+  virtual ~LegacyMozTCPSocket();
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+class TCPSocket final : public DOMEventTargetHelper
+                      , public nsIStreamListener
+                      , public nsITransportEventSink
+                      , public nsIInputStreamCallback
+                      , public nsIObserver
+                      , public nsSupportsWeakReference
+{
+public:
+  TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
+            bool aSsl, bool aUseArrayBuffers);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPSocket, DOMEventTargetHelper)
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSITRANSPORTEVENTSINK
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSIOBSERVER
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+    return GetOwner();
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void GetHost(nsAString& aHost);
+  uint32_t Port();
+  bool Ssl();
+  uint64_t BufferedAmount();
+  void Suspend();
+  void Resume(ErrorResult& aRv);
+  void Close();
+  bool Send(const nsCString& aData, ErrorResult& aRv);
+  bool Send(const ArrayBuffer& aData,
+            uint32_t aByteOffset,
+            const Optional<uint32_t>& aByteLength,
+            ErrorResult& aRv);
+  TCPReadyState ReadyState();
+  TCPSocketBinaryType BinaryType();
+  void UpgradeToSecure(ErrorResult& aRv);
+
+  static already_AddRefed<TCPSocket>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aHost,
+              uint16_t aPort,
+              const SocketOptions& aOptions,
+              ErrorResult& aRv);
+
+  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();
+
+  // 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.
+  void FireErrorEvent(const nsAString& aName, const nsAString& aMessage);
+  // Dispatch an event of the given type at this object.
+  void FireEvent(const nsAString& aType);
+  // Dispatch a "data" event at this object.
+  void FireDataEvent(JSContext* aCx, const nsAString& aType, JS::Handle<JS::Value> aData);
+  // Dispatch an error event if necessary, then dispatch a "close" event.
+  nsresult MaybeReportErrorAndCloseIfOpen(nsresult status);
+#ifdef MOZ_WIDGET_GONK
+  // Store and reset any saved network stats for this socket.
+  void SaveNetworkStats(bool aEnforce);
+#endif
+
+  TCPReadyState mReadyState;
+  // Whether to use strings or array buffers for the "data" event.
+  bool mUseArrayBuffers;
+  nsString mHost;
+  uint16_t mPort;
+  // Whether this socket is using a secure transport.
+  bool mSsl;
+
+  // Raw socket streams
+  nsCOMPtr<nsISocketTransport> mTransport;
+  nsCOMPtr<nsIInputStream> mSocketInputStream;
+  nsCOMPtr<nsIOutputStream> mSocketOutputStream;
+
+  // Input stream machinery
+  nsCOMPtr<nsIInputStreamPump> mInputStreamPump;
+  nsCOMPtr<nsIScriptableInputStream> mInputStreamScriptable;
+  nsCOMPtr<nsIBinaryInputStream> mInputStreamBinary;
+
+  // Output stream machinery
+  nsCOMPtr<nsIMultiplexInputStream> mMultiplexStream;
+  nsCOMPtr<nsIAsyncStreamCopier> mMultiplexStreamCopier;
+
+  // Is there an async copy operation in progress?
+  bool mAsyncCopierActive;
+  // True if the buffer is full and a "drain" event is expected by the client.
+  bool mWaitingForDrain;
+
+  // The id of the window that created this socket.
+  uint64_t mInnerWindowID;
+
+  // The number of times this socket has had `Suspend` called without a corresponding `Resume`.
+  uint32_t mSuspendCount;
+
+  // True if this socket has been upgraded to secure after the initial connection,
+  // but the actual upgrade is waiting for an in-progress copy operation to complete.
+  bool mWaitingForStartTLS;
+  // The buffered data awaiting the TLS upgrade to finish.
+  nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
+
+#ifdef MOZ_WIDGET_GONK
+  // Number of bytes sent.
+  uint32_t mTxBytes;
+  // Number of bytes received.
+  uint32_t mRxBytes;
+  // The app that owns this socket.
+  uint32_t mAppId;
+  // Was this socket created inside of a mozbrowser frame?
+  bool mInBrowser;
+  // The name of the active network used by this socket.
+  nsCOMPtr<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TCPSocket_h
--- 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 += [
+    'TCPSocket.h',
     'UDPSocket.h',
 ]
 
 EXPORTS.mozilla.dom.network += [
     'Connection.h',
     'Constants.h',
     'TCPServerSocketChild.h',
     'TCPServerSocketParent.h',
@@ -31,16 +32,17 @@ EXPORTS.mozilla.dom.network += [
     'UDPSocketChild.h',
     'UDPSocketParent.h',
 ]
 
 UNIFIED_SOURCES += [
     'Connection.cpp',
     'TCPServerSocketChild.cpp',
     'TCPServerSocketParent.cpp',
+    'TCPSocket.cpp',
     'TCPSocketChild.cpp',
     'TCPSocketParent.cpp',
     'UDPSocket.cpp',
     'UDPSocketChild.cpp',
     'UDPSocketParent.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
--- a/dom/network/tests/mochitest.ini
+++ b/dom/network/tests/mochitest.ini
@@ -18,9 +18,9 @@ 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"
 [test_networkstats_enabled_perm.html]
 skip-if = toolkit != "gonk"
-[test_udpsocket.html]
+[test_udpsocket.html]
\ No newline at end of file
--- a/dom/network/tests/test_tcpsocket_client_and_server_basics.js
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js
@@ -25,17 +25,17 @@ function assertUint8ArraysEqual(a, b, co
     }
   }
   ok(true, comparingWhat + ' arrays were equivalent.');
 }
 
 /**
  * Helper method to add event listeners to a socket and provide two Promise-returning
  * helpers (see below for docs on them).  This *must* be called during the turn of
- * the event loop where TCPSocket.open is called or the onconnect method is being
+ * the event loop where TCPSocket's constructor is called or the onconnect method is being
  * invoked.
  */
 function listenForEventsOnSocket(socket, socketType) {
   let wantDataLength = null;
   let pendingResolve = null;
   let receivedEvents = [];
   let receivedData = null;
   let handleGenericEvent = function(event) {
@@ -166,27 +166,27 @@ 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 TCPSocket = navigator.mozTCPSocket;
+  let mozTCPSocket = navigator.mozTCPSocket;
   // - Start up a listening socket.
-  let listeningServer = TCPSocket.listen(serverPort,
+  let listeningServer = mozTCPSocket.listen(serverPort,
                                          { binaryType: 'arraybuffer' },
                                          SERVER_BACKLOG);
 
   let connectedPromise = waitForConnection(listeningServer);
 
   // -- Open a connection to the server
-  let clientSocket = TCPSocket.open('127.0.0.1', serverPort,
-                                    { binaryType: 'arraybuffer' });
+  let clientSocket = new TCPSocket('127.0.0.1', serverPort,
+                                   { binaryType: 'arraybuffer' });
   let clientQueue = listenForEventsOnSocket(clientSocket, 'client');
 
   // (the client connects)
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
   is(clientSocket.readyState, 'open', 'client readyState is open');
 
   // (the server connected)
   let { socket: serverSocket, queue: serverQueue } = yield connectedPromise;
@@ -282,18 +282,18 @@ function* test_basics() {
      'client readyState should be closed after close event');
   is((yield serverQueue.waitForEvent()).type, 'close',
      'The server should get a close event when it closes itself.');
   is(serverSocket.readyState, 'closed',
      'server readyState should be closed after close event');
 
   // -- Re-establish connection
   connectedPromise = waitForConnection(listeningServer);
-  clientSocket = TCPSocket.open('127.0.0.1', serverPort,
-                                { binaryType: 'arraybuffer' });
+  clientSocket = new TCPSocket('127.0.0.1', serverPort,
+                               { binaryType: 'arraybuffer' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
 
   let connectedResult = yield connectedPromise;
   // destructuring assignment is not yet ES6 compliant, must manually unpack
   serverSocket = connectedResult.socket;
   serverQueue = connectedResult.queue;
 
@@ -309,18 +309,18 @@ function* test_basics() {
   is((yield serverQueue.waitForEvent()).type, 'close',
      'The server should get a close event when the client closes.');
   is(serverSocket.readyState, 'closed',
      'server readyState should be closed after the close event is received');
 
 
   // -- Re-establish connection
   connectedPromise = waitForConnection(listeningServer);
-  clientSocket = TCPSocket.open('127.0.0.1', serverPort,
-                                { binaryType: 'arraybuffer' });
+  clientSocket = new TCPSocket('127.0.0.1', serverPort,
+                               { binaryType: 'arraybuffer' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
 
   connectedResult = yield connectedPromise;
   // destructuring assignment is not yet ES6 compliant, must manually unpack
   serverSocket = connectedResult.socket;
   serverQueue = connectedResult.queue;
 
@@ -343,18 +343,18 @@ function* test_basics() {
                          'server received/client sent');
   // And a close.
   is((yield serverQueue.waitForEvent()).type, 'close',
      'The drain event should fire after a large send that returned true.');
 
 
   // -- Re-establish connection
   connectedPromise = waitForConnection(listeningServer);
-  clientSocket = TCPSocket.open('127.0.0.1', serverPort,
-                                { binaryType: 'string' });
+  clientSocket = new TCPSocket('127.0.0.1', serverPort,
+                               { binaryType: 'string' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
 
   connectedResult = yield connectedPromise;
   // destructuring assignment is not yet ES6 compliant, must manually unpack
   serverSocket = connectedResult.socket;
   serverQueue = connectedResult.queue;
 
@@ -371,17 +371,17 @@ function* test_basics() {
 
 
   // -- Close the listening server (and try to connect)
   // We want to verify that the server actually closes / stops listening when
   // we tell it to.
   listeningServer.close();
 
   // - try and connect, get an error
-  clientSocket = TCPSocket.open('127.0.0.1', serverPort,
-                                { binaryType: 'arraybuffer' });
+  clientSocket = new TCPSocket('127.0.0.1', serverPort,
+                               { binaryType: 'arraybuffer' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'error', 'fail to connect');
   is(clientSocket.readyState, 'closed',
      'client readyState should be closed after the failure to connect');
 }
 
 add_task(test_basics);
--- a/dom/network/tests/test_tcpsocket_default_permissions.html
+++ b/dom/network/tests/test_tcpsocket_default_permissions.html
@@ -9,19 +9,30 @@
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test to ensure TCPSocket permission is disabled by default **/
 
+var caught = false;
 try {
-  navigator.mozTCPSocket;
-  throw new Error("Error: navigator.mozTCPSocket should not exist by default");
+  new TCPSocket("localhost", 80, {})
 } catch (e) {
-  ok(true, "navigator.mozTCPSocket should not exist by default");  
+  caught = true;
 }
 
+ok(caught, "TCPSocket should not exist by default");
+
+var caught = false;
+try {
+  navigator.mozTCPSocket.open("localhost", 80, {})
+} catch (e) {
+  caught = true;
+}
+
+ok(caught, "navigator.mozTCPSocket.open should not exist by default");
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/network/tests/test_tcpsocket_enabled_no_perm.html
+++ b/dom/network/tests/test_tcpsocket_enabled_no_perm.html
@@ -7,29 +7,23 @@
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script type="application/javascript">
 
-/** Test to ensure TCPSocket permission being turned on enables 
-  navigator.mozTCPSocket, but mozTCPSocket.open does not work
-  in content.
+/** Test to ensure TCPSocket preference being turned on does not enable
+  navigator.mozTCPSocket.
 **/
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({"set": [['dom.mozTCPSocket.enabled', true]]}, runTest);
 function runTest() {
-  ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled  is true");
+  is('TCPSocket' in this, false, "TCPSocket should not be accessible if dom.mozTCPSocket.enabled is true");
+  is('mozTCPSocket' in navigator, true, "mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
 
-  try {
-    navigator.mozTCPSocket.open('localhost', 80);
-    throw new Error("Error: navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
-  } catch (e) {
-      ok(true, "navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
-  }
   SimpleTest.finish();
 }
 </script>
 </pre>
 </body>
 </html>
--- a/dom/network/tests/test_tcpsocket_enabled_with_perm.html
+++ b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
@@ -16,17 +16,18 @@
   navigator.mozTCPSocket, and mozTCPSocket.open works when
   the tcp-socket permission has been granted.
 **/
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({"set": [['dom.mozTCPSocket.enabled', true]]}, runTest);
 function runTest() {
   SpecialPowers.addPermission("tcp-socket", true, document);
 
-  ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
+  ok('TCPSocket' in this, "TCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
 
+  ok(new TCPSocket('localhost', 80), "TCPSocket constructor should work for content that has the tcp-socket permission");
   ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
   SimpleTest.finish();
 }
 </script>
 </pre>
 </body>
 </html>
--- a/dom/permission/tests/test_tcp-socket.html
+++ b/dom/permission/tests/test_tcp-socket.html
@@ -16,34 +16,36 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script type="application/javascript;version=1.8" src="file_framework.js"></script>
 <script type="application/javascript;version=1.8">
 /* mozTCPSocket only returns null on window init
  * if the permission isn't set
  */
 function verifier(success, failure) {
   try {
-    var conn = this.getObj().open("http://mochi.test/", 80);
+    var conn = navigator.mozTCPSocket.open("http://mochi.test/", 80);
 
     if (conn) {
-      success("Opened connection");
+      if (conn instanceof window.TCPSocket) {
+        success("Opened connection");
+      } else {
+        failure("connection didn't match interface");
+      }
     } else {
       failure("failed to open connection");
     }
   } catch (e) {
     failure("Got an exception " + e);
   }
 }
 
 var gData = [
   {
     perm: ["tcp-socket"],
     needParentPerm: true,
-    obj: "mozTCPSocket",
-    idl: "nsIDOMTCPSocket",
     settings: [["dom.mozTCPSocket.enabled", true]],
     verifier: verifier.toSource(),
   }
 ]
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -431,16 +431,24 @@ 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 {
+  [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);
 };
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TCPSocket.webidl
@@ -0,0 +1,203 @@
+/* -*- 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/. */
+
+/**
+ * TCPSocket exposes a TCP client socket (no server sockets yet)
+ * to highly privileged apps. It provides a buffered, non-blocking
+ * interface for sending. For receiving, it uses an asynchronous,
+ * event handler based interface.
+ */
+
+enum TCPSocketBinaryType {
+  "arraybuffer",
+  "string"
+};
+
+dictionary SocketOptions {
+  boolean useSecureTransport = false;
+  TCPSocketBinaryType binaryType = "string";
+};
+
+enum TCPReadyState {
+  "connecting",
+  "open",
+  "closing",
+  "closed",
+};
+
+[NoInterfaceObject]
+interface LegacyMozTCPSocket {
+  /**
+   * Legacy constructor for API compatibility.
+   */
+  [Throws]
+  TCPSocket open(DOMString host, unsigned short port, optional SocketOptions options);
+};
+
+[Constructor(DOMString host, unsigned short port, optional SocketOptions options),
+ Pref="dom.mozTCPSocket.enabled",
+ CheckAnyPermissions="tcp-socket",
+ Exposed=Window]
+interface TCPSocket : EventTarget {
+  /**
+   * Upgrade an insecure connection to use TLS. Throws if the ready state is not OPEN.
+   */
+  [Throws] void upgradeToSecure();
+
+  /**
+   * The UTF16 host of this socket object.
+   */
+  readonly attribute USVString host;
+
+  /**
+   * The port of this socket object.
+   */
+  readonly attribute unsigned short port;
+
+  /**
+   * True if this socket object is an SSL socket.
+   */
+  readonly attribute boolean ssl;
+
+  /**
+   * The number of bytes which have previously been buffered by calls to
+   * send on this socket.
+   */
+  readonly attribute unsigned long long bufferedAmount;
+
+  /**
+   * Pause reading incoming data and invocations of the ondata handler until
+   * resume is called. Can be called multiple times without resuming.
+   */
+  void suspend();
+
+  /**
+   * Resume reading incoming data and invoking ondata as usual. There must be
+   * an equal number of resume as suspends that took place. Throws if the
+   * socket is not suspended.
+   */
+  [Throws]
+  void resume();
+
+  /**
+   * Close the socket.
+   */
+  void close();
+
+  /**
+   * Write data to the socket.
+   *
+   * @param data The data to write to the socket.
+   *
+   * @return Send returns true or false as a hint to the caller that
+   *         they may either continue sending more data immediately, or
+   *         may want to wait until the other side has read some of the
+   *         data which has already been written to the socket before
+   *         buffering more. If send returns true, then less than 64k
+   *         has been buffered and it's safe to immediately write more.
+   *         If send returns false, then more than 64k has been buffered,
+   *         and the caller may wish to wait until the ondrain event
+   *         handler has been called before buffering more data by more
+   *         calls to send.
+   * 
+   * @throws Throws if the ready state is not OPEN.
+   */
+  [Throws]
+  boolean send(ByteString data);
+
+  /**
+   * Write data to the socket.
+   *
+   * @param data The data to write to the socket.
+   * @param byteOffset The offset within the data from which to begin writing.
+   * @param byteLength The number of bytes to write.
+   *                   Defaults to the byte length of the ArrayBuffer if not present,
+   *                   and clamped to (length - byteOffset).
+   *
+   * @return Send returns true or false as a hint to the caller that
+   *         they may either continue sending more data immediately, or
+   *         may want to wait until the other side has read some of the
+   *         data which has already been written to the socket before
+   *         buffering more. If send returns true, then less than 64k
+   *         has been buffered and it's safe to immediately write more.
+   *         If send returns false, then more than 64k has been buffered,
+   *         and the caller may wish to wait until the ondrain event
+   *         handler has been called before buffering more data by more
+   *         calls to send.
+   * 
+   * @throws Throws if the ready state is not OPEN.
+   */
+  [Throws]
+  boolean send(ArrayBuffer data, optional unsigned long byteOffset = 0, optional unsigned long byteLength);
+
+  /**
+   * The readyState attribute indicates which state the socket is currently
+   * in.
+   */
+  readonly attribute TCPReadyState readyState;
+
+  /**
+   * The binaryType attribute indicates which mode this socket uses for
+   * sending and receiving data. If the binaryType: "arraybuffer" option
+   * was passed to the open method that created this socket, binaryType
+   * will be "arraybuffer". Otherwise, it will be "string".
+   */
+  readonly attribute TCPSocketBinaryType binaryType;
+
+  /**
+   * The "open" event is dispatched when the connection to the server
+   * has been established. If the connection is refused, the "error" event
+   * will be dispatched, instead.
+   */
+  attribute EventHandler onopen;
+
+  /**
+   * After send has buffered more than 64k of data, it returns false to
+   * indicate that the client should pause before sending more data, to
+   * avoid accumulating large buffers. This is only advisory, and the client
+   * is free to ignore it and buffer as much data as desired, but if reducing
+   * the size of buffers is important (especially for a streaming application)
+   * the "drain" event will be dispatched once the previously-buffered data has
+   * been written to the network, at which point the client can resume calling
+   * send again.
+   */
+  attribute EventHandler ondrain;
+
+  /**
+   * The "data" event will be dispatched repeatedly and asynchronously after
+   * "open" is dispatched, every time some data was available from the server
+   * and was read. The event object will be a TCPSocketEvent; if the "arraybuffer"
+   * binaryType was passed to the constructor, the data attribute of the event
+   * object will be an ArrayBuffer. If not, it will be a normal JavaScript string,
+   * truncated at the first null byte found in the payload and the remainder
+   * interpreted as ASCII bytes.
+   *
+   * At any time, the client may choose to pause reading and receiving "data"
+   * events by calling the socket's suspend() method. Further "data" events
+   * will be paused until resume() is called.
+   */
+  attribute EventHandler ondata;
+
+  /**
+   * The "error" event will be dispatched when there is an error. The event
+   * object will be a TCPSocketErrorEvent.
+   *
+   * If an "error" event is dispatched before an "open" one, the connection
+   * was refused, and the "close" event will not be dispatched. If an "error"
+   * event is dispatched after an "open" event, the connection was lost,
+   * and a "close" event will be dispatched subsequently.
+   */
+  attribute EventHandler onerror;
+
+  /**
+   * The "close" event is dispatched once the underlying network socket
+   * has been closed, either by the server, or by the client calling
+   * close.
+   *
+   * If the "error" event was not dispatched before "close", then one of
+   * the sides cleanly closed the connection.
+   */
+  attribute EventHandler onclose;  
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TCPSocketErrorEvent.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/. */
+
+/* Dispatched as part of the "error" event in the following situations:
+* - if there's an error detected when the TCPSocket closes
+* - if there's an internal error while sending data
+* - if there's an error connecting to the host
+*/
+
+[Pref="dom.mozTCPSocket.enabled",
+ CheckAnyPermissions="tcp-socket",
+ Constructor(DOMString type, optional TCPSocketErrorEventInit eventInitDict)]
+interface TCPSocketErrorEvent : Event {
+  readonly attribute DOMString name;
+  readonly attribute DOMString message;
+};
+
+dictionary TCPSocketErrorEventInit : EventInit
+{
+  DOMString name = "";
+  DOMString message = "";
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/TCPSocketEvent.webidl
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+/**
+ * TCPSocketEvent is the event dispatched for all of the events described by TCPSocket,
+ * except the "error" event. It contains the socket that was associated with the event,
+ * the type of event, and the data associated with the event if the event is a "data" event.
+ */
+
+[Constructor(DOMString type, optional TCPSocketEventInit eventInitDict),
+ Pref="dom.mozTCPSocket.enabled",
+ CheckAnyPermissions="tcp-socket",
+ Exposed=Window]
+interface TCPSocketEvent : Event {
+  /**
+   * If the event is a "data" event, data will be the bytes read from the network;
+   * if the binaryType of the socket was "arraybuffer", this value will be of type
+   * ArrayBuffer, otherwise, it will be a ByteString.
+   *
+   * For other events, data will be an empty string.
+   */
+  //TODO: make this (ArrayBuffer or ByteString) after sorting out the rooting required. (bug 1121634)
+  readonly attribute any data;
+};
+
+dictionary TCPSocketEventInit : EventInit {
+  //TODO: make this (ArrayBuffer or ByteString) after sorting out the rooting required. (bug 1121634)
+  any data = null;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -519,16 +519,19 @@ WEBIDL_FILES = [
     'SVGTSpanElement.webidl',
     'SVGUnitTypes.webidl',
     'SVGURIReference.webidl',
     'SVGUseElement.webidl',
     'SVGViewElement.webidl',
     'SVGZoomAndPan.webidl',
     'SVGZoomEvent.webidl',
     'SystemUpdate.webidl',
+    'TCPSocket.webidl',
+    'TCPSocketErrorEvent.webidl',
+    'TCPSocketEvent.webidl',
     'Telephony.webidl',
     'TelephonyCall.webidl',
     'TelephonyCallGroup.webidl',
     'TelephonyCallId.webidl',
     'Text.webidl',
     'TextDecoder.webidl',
     'TextEncoder.webidl',
     'TextTrack.webidl',
@@ -791,16 +794,18 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'PresentationSessionConnectEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
     'ScrollViewChangeEvent.webidl',
     'SelectionStateChangedEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
+    'TCPSocketErrorEvent.webidl',
+    'TCPSocketEvent.webidl',
     'TrackEvent.webidl',
     'TVCurrentChannelChangedEvent.webidl',
     'TVCurrentSourceChangedEvent.webidl',
     'TVEITBroadcastedEvent.webidl',
     'TVScanningStateChangedEvent.webidl',
     'UDPMessageEvent.webidl',
     'UserProximityEvent.webidl',
     'USSDReceivedEvent.webidl',