Bug 504553 - patch 2 - WebSockes in Workers: WebSocketImpl, thread-safe class, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 10 Oct 2014 17:56:43 +0100
changeset 233085 e7950dac9a28912c5a7bacba0d3d06ad889c1b11
parent 233084 e72ed2e97e83b574adead215e9e01888ffa6aec9
child 233086 f23cea600bd00a7cce19a0a4bd178e8e4c9f81d1
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
reviewerssmaug
bugs504553
milestone35.0a1
Bug 504553 - patch 2 - WebSockes in Workers: WebSocketImpl, thread-safe class, r=smaug
content/base/src/WebSocket.cpp
content/base/src/WebSocket.h
netwerk/ipc/ChannelEventQueue.h
netwerk/protocol/websocket/BaseWebSocketChannel.h
netwerk/protocol/websocket/WebSocketChannel.cpp
--- a/content/base/src/WebSocket.cpp
+++ b/content/base/src/WebSocket.cpp
@@ -7,16 +7,17 @@
 #include "WebSocket.h"
 #include "mozilla/dom/WebSocketBinding.h"
 #include "mozilla/net/WebSocketChannel.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/OldDebugAPI.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/net/WebSocketChannel.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDOMWindow.h"
 #include "nsIDocument.h"
 #include "nsXPCOM.h"
 #include "nsIXPConnect.h"
 #include "nsContentUtils.h"
@@ -37,50 +38,231 @@
 #include "nsIScriptError.h"
 #include "nsNetUtil.h"
 #include "nsILoadGroup.h"
 #include "mozilla/Preferences.h"
 #include "xpcpublic.h"
 #include "nsContentPolicyUtils.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsIObserverService.h"
+#include "nsIEventTarget.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsIRequest.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsWeakReference.h"
 
 using namespace mozilla::net;
 
 namespace mozilla {
 namespace dom {
 
-#define UTF_8_REPLACEMENT_CHAR    static_cast<char16_t>(0xFFFD)
+class WebSocketImpl MOZ_FINAL : public nsIInterfaceRequestor
+                              , public nsIWebSocketListener
+                              , public nsIObserver
+                              , public nsSupportsWeakReference
+                              , public nsIRequest
+{
+friend class CallDispatchConnectionCloseEvents;
+friend class nsAutoCloseWS;
+
+public:
+  NS_DECL_NSIINTERFACEREQUESTOR
+  NS_DECL_NSIWEBSOCKETLISTENER
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIREQUEST
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  WebSocketImpl(WebSocket* aWebSocket)
+  : mParent(aWebSocket)
+  , mOnCloseScheduled(false)
+  , mFailed(false)
+  , mDisconnected(false)
+  , mCloseEventWasClean(false)
+  , mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL)
+  , mReadyState(WebSocket::CONNECTING)
+  , mOutgoingBufferedAmount(0)
+  , mBinaryType(dom::BinaryType::Blob)
+  , mScriptLine(0)
+  , mInnerWindowID(0)
+  {
+  }
+
+  uint16_t ReadyState() const
+  {
+    return mReadyState;
+  }
+
+  uint32_t BufferedAmount() const
+  {
+    return mOutgoingBufferedAmount;
+  }
+
+  dom::BinaryType BinaryType() const
+  {
+    return mBinaryType;
+  }
+
+  void SetBinaryType(dom::BinaryType aData)
+  {
+    mBinaryType = aData;
+  }
+
+  void GetUrl(nsAString& aURL)
+  {
+    if (mEffectiveURL.IsEmpty()) {
+      aURL = mOriginalURL;
+    } else {
+      aURL = mEffectiveURL;
+    }
+  }
+
+  void Close(const Optional<uint16_t>& aCode,
+             const Optional<nsAString>& aReason,
+             ErrorResult& aRv);
+
+  nsresult Init(JSContext* aCx,
+                nsIPrincipal* aPrincipal,
+                const nsAString& aURL,
+                nsTArray<nsString>& aProtocolArray);
+
+  void Send(nsIInputStream* aMsgStream,
+            const nsACString& aMsgString,
+            uint32_t aMsgLength,
+            bool aIsBinary,
+            ErrorResult& aRv);
+
+  nsresult ParseURL(const nsAString& aURL);
+  nsresult EstablishConnection();
+
+  // These methods when called can release the WebSocket object
+  void FailConnection(uint16_t reasonCode,
+                      const nsACString& aReasonString = EmptyCString());
+  nsresult CloseConnection(uint16_t reasonCode,
+                           const nsACString& aReasonString = EmptyCString());
+  nsresult Disconnect();
+
+  nsresult ConsoleError();
+  nsresult PrintErrorOnConsole(const char* aBundleURI,
+                               const char16_t* aError,
+                               const char16_t** aFormatStrings,
+                               uint32_t aFormatStringsLen);
+
+  nsresult DoOnMessageAvailable(const nsACString& aMsg,
+                                bool isBinary);
+
+  // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
+  // - These must not be dispatched while we are still within an incoming call
+  //   from JS (ex: close()).  Set 'sync' to false in that case to dispatch in a
+  //   separate new event.
+  nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
+                                         nsresult aStatusCode,
+                                         bool sync);
+  // 2nd half of ScheduleConnectionCloseEvents, sometimes run in its own event.
+  void DispatchConnectionCloseEvents();
+
+  // Dispatch a runnable to the right thread.
+  nsresult DispatchRunnable(nsIRunnable* aRunnable);
+
+  nsresult UpdateURI();
+
+  nsRefPtr<WebSocket> mParent;
+
+  nsCOMPtr<nsIWebSocketChannel> mChannel;
+
+  // related to the WebSocket constructor steps
+  nsString mOriginalURL;
+  nsString mEffectiveURL;   // after redirects
+  bool mSecure; // if true it is using SSL and the wss scheme,
+                // otherwise it is using the ws scheme with no SSL
+
+  bool mOnCloseScheduled;
+  bool mFailed;
+  bool mDisconnected;
+
+  // Set attributes of DOM 'onclose' message
+  bool      mCloseEventWasClean;
+  nsString  mCloseEventReason;
+  uint16_t  mCloseEventCode;
+
+  nsCString mAsciiHost;  // hostname
+  uint32_t  mPort;
+  nsCString mResource; // [filepath[?query]]
+  nsString  mUTF16Origin;
+
+  nsCOMPtr<nsIURI> mURI;
+  nsCString mRequestedProtocolList;
+  nsCString mEstablishedProtocol;
+  nsCString mEstablishedExtensions;
+
+  uint16_t mReadyState;
+
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsWeakPtr              mOriginDocument;
+
+  uint32_t mOutgoingBufferedAmount;
+
+  dom::BinaryType mBinaryType;
+
+  // Web Socket owner information:
+  // - the script file name, UTF8 encoded.
+  // - source code line number where the Web Socket object was constructed.
+  // - the ID of the inner window where the script lives. Note that this may not
+  //   be the same as the Web Socket owner window.
+  // These attributes are used for error reporting.
+  nsCString mScriptFile;
+  uint32_t mScriptLine;
+  uint64_t mInnerWindowID;
+
+private:
+  ~WebSocketImpl()
+  {
+    // If we threw during Init we never called disconnect
+    if (!mDisconnected) {
+      Disconnect();
+    }
+  }
+
+};
+
+NS_IMPL_ISUPPORTS(WebSocketImpl,
+                  nsIInterfaceRequestor,
+                  nsIWebSocketListener,
+                  nsIObserver,
+                  nsISupportsWeakReference,
+                  nsIRequest)
 
 class CallDispatchConnectionCloseEvents: public nsRunnable
 {
 public:
-  explicit CallDispatchConnectionCloseEvents(WebSocket* aWebSocket)
-    : mWebSocket(aWebSocket)
+  explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
+    : mWebSocketImpl(aWebSocketImpl)
   {}
 
   NS_IMETHOD Run()
   {
-    mWebSocket->DispatchConnectionCloseEvents();
+    mWebSocketImpl->DispatchConnectionCloseEvents();
     return NS_OK;
   }
 
 private:
-  nsRefPtr<WebSocket> mWebSocket;
+  nsRefPtr<WebSocketImpl> mWebSocketImpl;
 };
 
 //-----------------------------------------------------------------------------
-// WebSocket
+// WebSocketImpl
 //-----------------------------------------------------------------------------
 
 nsresult
-WebSocket::PrintErrorOnConsole(const char *aBundleURI,
-                               const char16_t *aError,
-                               const char16_t **aFormatStrings,
-                               uint32_t aFormatStringsLen)
+WebSocketImpl::PrintErrorOnConsole(const char *aBundleURI,
+                                   const char16_t *aError,
+                                   const char16_t **aFormatStrings,
+                                   uint32_t aFormatStringsLen)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   nsresult rv;
   nsCOMPtr<nsIStringBundleService> bundleService =
     do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -117,18 +299,18 @@ WebSocket::PrintErrorOnConsole(const cha
   // print the error message directly to the JS console
   rv = console->LogMessage(errorObject);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-WebSocket::CloseConnection(uint16_t aReasonCode,
-                             const nsACString& aReasonString)
+WebSocketImpl::CloseConnection(uint16_t aReasonCode,
+                               const nsACString& aReasonString)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   if (mReadyState == WebSocket::CLOSING ||
       mReadyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
   // The common case...
@@ -156,17 +338,17 @@ WebSocket::CloseConnection(uint16_t aRea
                      aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
                      NS_OK : NS_ERROR_FAILURE,
                     false);
 
   return NS_OK;
 }
 
 nsresult
-WebSocket::ConsoleError()
+WebSocketImpl::ConsoleError()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   nsAutoCString targetSpec;
   nsresult rv = mURI->GetSpec(targetSpec);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get targetSpec");
   } else {
@@ -182,30 +364,29 @@ WebSocket::ConsoleError()
                           MOZ_UTF16("netInterrupt"),
                           formatStrings, ArrayLength(formatStrings));
     }
   }
   /// todo some specific errors - like for message too large
   return rv;
 }
 
-
 void
-WebSocket::FailConnection(uint16_t aReasonCode,
-                          const nsACString& aReasonString)
+WebSocketImpl::FailConnection(uint16_t aReasonCode,
+                              const nsACString& aReasonString)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   ConsoleError();
   mFailed = true;
   CloseConnection(aReasonCode, aReasonString);
 }
 
 nsresult
-WebSocket::Disconnect()
+WebSocketImpl::Disconnect()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mDisconnected)
     return NS_OK;
 
   nsCOMPtr<nsILoadGroup> loadGroup;
   GetLoadGroup(getter_AddRefs(loadGroup));
@@ -215,126 +396,128 @@ WebSocket::Disconnect()
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
     os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
     os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
   }
 
   // DontKeepAliveAnyMore() can release the object. So hold a reference to this
   // until the end of the method.
-  nsRefPtr<WebSocket> kungfuDeathGrip = this;
+  nsRefPtr<WebSocketImpl> kungfuDeathGrip = this;
 
-  DontKeepAliveAnyMore();
   mChannel = nullptr;
   mDisconnected = true;
+  mParent->DontKeepAliveAnyMore();
+  mParent->mImpl = nullptr;
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// WebSocket::nsIWebSocketListener methods:
+// WebSocketImpl::nsIWebSocketListener methods:
 //-----------------------------------------------------------------------------
 
 nsresult
-WebSocket::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
+WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mReadyState == WebSocket::CLOSED) {
     NS_ERROR("Received message after CLOSED");
     return NS_ERROR_UNEXPECTED;
   }
 
   if (mReadyState == WebSocket::OPEN) {
     // Dispatch New Message
-    nsresult rv = CreateAndDispatchMessageEvent(aMsg, isBinary);
+    nsresult rv = mParent->CreateAndDispatchMessageEvent(aMsg, isBinary);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to dispatch the message event");
     }
   } else {
     // CLOSING should be the only other state where it's possible to get msgs
     // from channel: Spec says to drop them.
     MOZ_ASSERT(mReadyState == WebSocket::CLOSING,
                "Received message while CONNECTING or CLOSED");
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::OnMessageAvailable(nsISupports* aContext, const nsACString& aMsg)
+WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
+                                  const nsACString& aMsg)
 {
   return DoOnMessageAvailable(aMsg, false);
 }
 
 NS_IMETHODIMP
-WebSocket::OnBinaryMessageAvailable(nsISupports* aContext,
-                                    const nsACString& aMsg)
+WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
+                                        const nsACString& aMsg)
 {
   return DoOnMessageAvailable(aMsg, true);
 }
 
 NS_IMETHODIMP
-WebSocket::OnStart(nsISupports* aContext)
+WebSocketImpl::OnStart(nsISupports* aContext)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   // This is the only function that sets OPEN, and should be called only once
   MOZ_ASSERT(mReadyState != WebSocket::OPEN,
              "readyState already OPEN! OnStart called twice?");
 
   // Nothing to do if we've already closed/closing
   if (mReadyState != WebSocket::CONNECTING) {
     return NS_OK;
   }
 
   // Attempt to kill "ghost" websocket: but usually too early for check to fail
-  nsresult rv = CheckInnerWindowCorrectness();
+  nsresult rv = mParent->CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
     return rv;
   }
 
   if (!mRequestedProtocolList.IsEmpty()) {
     mChannel->GetProtocol(mEstablishedProtocol);
   }
 
   mChannel->GetExtensions(mEstablishedExtensions);
   UpdateURI();
 
   mReadyState = WebSocket::OPEN;
 
   // Call 'onopen'
-  rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
+  rv = mParent->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch the open event");
   }
 
-  UpdateMustKeepAlive();
+  mParent->UpdateMustKeepAlive();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::OnStop(nsISupports* aContext, nsresult aStatusCode)
+WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode)
 {
   // We can be CONNECTING here if connection failed.
   // We can be OPEN if we have encountered a fatal protocol error
   // We can be CLOSING if close() was called and/or server initiated close.
   MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
              "Shouldn't already be CLOSED when OnStop called");
 
   // called by network stack, not JS, so can dispatch JS events synchronously
   return ScheduleConnectionCloseEvents(aContext, aStatusCode, true);
 }
 
 nsresult
-WebSocket::ScheduleConnectionCloseEvents(nsISupports* aContext,
-                                         nsresult aStatusCode,
-                                         bool sync)
+WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
+                                             nsresult aStatusCode,
+                                             bool sync)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // no-op if some other code has already initiated close event
   if (!mOnCloseScheduled) {
     mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
 
     if (aStatusCode == NS_BASE_STREAM_CLOSED) {
@@ -355,30 +538,30 @@ WebSocket::ScheduleConnectionCloseEvents
       NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
+WebSocketImpl::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (aSize > mOutgoingBufferedAmount)
     return NS_ERROR_UNEXPECTED;
 
   mOutgoingBufferedAmount -= aSize;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::OnServerClose(nsISupports *aContext, uint16_t aCode,
-                           const nsACString &aReason)
+WebSocketImpl::OnServerClose(nsISupports *aContext, uint16_t aCode,
+                             const nsACString &aReason)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   MOZ_ASSERT(mReadyState != WebSocket::CONNECTING,
              "Received server close before connected?");
   MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
              "Received server close after already closed!");
 
@@ -400,31 +583,31 @@ WebSocket::OnServerClose(nsISupports *aC
     // We initiated close, and server has replied: OnStop does rest of the work.
     MOZ_ASSERT(mReadyState == WebSocket::CLOSING, "unknown state");
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// WebSocket::nsIInterfaceRequestor
+// WebSocketImpl::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
-WebSocket::GetInterface(const nsIID& aIID, void** aResult)
+WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mReadyState == WebSocket::CLOSED)
     return NS_ERROR_FAILURE;
 
   if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
       aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
     nsresult rv;
-    nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+    nsIScriptContext* sc = mParent->GetContextForEventHandlers(&rv);
     nsCOMPtr<nsIDocument> doc =
       nsContentUtils::GetDocumentFromScriptContext(sc);
     if (!doc)
       return NS_ERROR_NOT_AVAILABLE;
 
     nsCOMPtr<nsIPromptFactory> wwatch =
       do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -436,43 +619,26 @@ WebSocket::GetInterface(const nsIID& aII
   return QueryInterface(aIID, aResult);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // WebSocket
 ////////////////////////////////////////////////////////////////////////////////
 
 WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
-: DOMEventTargetHelper(aOwnerWindow),
-  mKeepingAlive(false),
-  mCheckMustKeepAlive(true),
-  mOnCloseScheduled(false),
-  mFailed(false),
-  mDisconnected(false),
-  mCloseEventWasClean(false),
-  mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
-  mReadyState(WebSocket::CONNECTING),
-  mOutgoingBufferedAmount(0),
-  mBinaryType(dom::BinaryType::Blob),
-  mScriptLine(0),
-  mInnerWindowID(0)
+: DOMEventTargetHelper(aOwnerWindow)
+, mKeepingAlive(false)
+, mCheckMustKeepAlive(true)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-  MOZ_ASSERT(aOwnerWindow);
-  MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
+  MOZ_ASSERT(NS_IsMainThread());
+  mImpl = new WebSocketImpl(this);
 }
 
 WebSocket::~WebSocket()
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
-  // If we threw during Init we never called disconnect
-  if (!mDisconnected) {
-    Disconnect();
-  }
 }
 
 JSObject*
 WebSocket::WrapObject(JSContext* cx)
 {
   return WebSocketBinding::Wrap(cx, this);
 }
 
@@ -502,44 +668,45 @@ WebSocket::Constructor(const GlobalObjec
 }
 
 already_AddRefed<WebSocket>
 WebSocket::Constructor(const GlobalObject& aGlobal,
                        const nsAString& aUrl,
                        const Sequence<nsString>& aProtocols,
                        ErrorResult& aRv)
 {
-  if (!PrefEnabled()) {
-    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return nullptr;
-  }
+  nsCOMPtr<nsIPrincipal> principal;
+  nsCOMPtr<nsPIDOMWindow> ownerWindow;
 
-  nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
-    do_QueryInterface(aGlobal.GetAsSupports());
-  if (!scriptPrincipal) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+      do_QueryInterface(aGlobal.GetAsSupports());
+    if (!scriptPrincipal) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
 
-  nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
-  if (!principal) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
+    principal = scriptPrincipal->GetPrincipal();
+    if (!principal) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
 
-  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
-  if (!sgo) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
+    nsCOMPtr<nsIScriptGlobalObject> sgo =
+      do_QueryInterface(aGlobal.GetAsSupports());
+    if (!sgo) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
 
-  nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
-  if (!ownerWindow) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
+    ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
+    if (!ownerWindow) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
   }
 
   nsTArray<nsString> protocolArray;
 
   for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
 
     const nsString& protocolElement = aProtocols[index];
 
@@ -555,31 +722,31 @@ WebSocket::Constructor(const GlobalObjec
       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return nullptr;
     }
 
     protocolArray.AppendElement(protocolElement);
   }
 
   nsRefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
-  nsresult rv = webSocket->Init(aGlobal.Context(), principal,
-                                aUrl, protocolArray);
+  nsresult rv = webSocket->mImpl->Init(aGlobal.Context(), principal,
+                                       aUrl, protocolArray);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   return webSocket.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket)
   bool isBlack = tmp->IsBlack();
-  if (isBlack|| tmp->mKeepingAlive) {
+  if (isBlack || tmp->mKeepingAlive) {
     if (tmp->mListenerManager) {
       tmp->mListenerManager->MarkForCC();
     }
     if (!isBlack && tmp->PreservingWrapper()) {
       // This marks the wrapper black.
       tmp->GetWrapper();
     }
     return true;
@@ -595,69 +762,73 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_B
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
                                                DOMEventTargetHelper)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
                                                   DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mURI)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
+  if (tmp->mImpl) {
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mPrincipal)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mURI)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
+  }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket,
                                                 DOMEventTargetHelper)
-  tmp->Disconnect();
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mURI)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
+  if (tmp->mImpl) {
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mPrincipal)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mURI)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
+    tmp->mImpl->Disconnect();
+    MOZ_ASSERT(!tmp->mImpl);
+  }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)
-  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
-  NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener)
-  NS_INTERFACE_MAP_ENTRY(nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-  NS_INTERFACE_MAP_ENTRY(nsIRequest)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
 
 void
 WebSocket::DisconnectFromOwner()
 {
   DOMEventTargetHelper::DisconnectFromOwner();
-  CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+
+  if (mImpl) {
+    mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+  }
+
   DontKeepAliveAnyMore();
 }
 
 //-----------------------------------------------------------------------------
-// WebSocket:: initialization
+// WebSocketImpl:: initialization
 //-----------------------------------------------------------------------------
 
 nsresult
-WebSocket::Init(JSContext* aCx,
-                nsIPrincipal* aPrincipal,
-                const nsAString& aURL,
-                nsTArray<nsString>& aProtocolArray)
+WebSocketImpl::Init(JSContext* aCx,
+                    nsIPrincipal* aPrincipal,
+                    const nsAString& aURL,
+                    nsTArray<nsString>& aProtocolArray)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   MOZ_ASSERT(aPrincipal);
 
-  if (!PrefEnabled()) {
-    return NS_ERROR_DOM_SECURITY_ERR;
-  }
+  // We need to keep the implementation alive in case the init disconnects it
+  // because of some error.
+  nsRefPtr<WebSocketImpl> kungfuDeathGrip = this;
 
   mPrincipal = aPrincipal;
 
   // Attempt to kill "ghost" websocket: but usually too early for check to fail
-  nsresult rv = CheckInnerWindowCorrectness();
+  nsresult rv = mParent->CheckInnerWindowCorrectness();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Shut down websocket if window is frozen or destroyed (only needed for
   // "ghost" websockets--see bug 696085)
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   NS_ENSURE_STATE(os);
   rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -673,17 +844,17 @@ WebSocket::Init(JSContext* aCx,
 
   // Get WindowID
   mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
 
   // parses the url
   rv = ParseURL(PromiseFlatString(aURL));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+  nsIScriptContext* sc = mParent->GetContextForEventHandlers(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Don't allow https:// to open ws://
   if (!mSecure &&
       !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
                             false)) {
     // Confirmed we are opening plain ws:// and want to prevent this from a
     // secure context (e.g. https). Check the principal's uri to determine if
@@ -746,38 +917,38 @@ WebSocket::Init(JSContext* aCx,
   if (NS_FAILED(EstablishConnection())) {
     FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// WebSocket methods:
+// WebSocketImpl methods:
 //-----------------------------------------------------------------------------
 
 class nsAutoCloseWS
 {
 public:
-  explicit nsAutoCloseWS(WebSocket* aWebSocket)
-    : mWebSocket(aWebSocket)
+  explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
+    : mWebSocketImpl(aWebSocketImpl)
   {}
 
   ~nsAutoCloseWS()
   {
-    if (!mWebSocket->mChannel) {
-      mWebSocket->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
+    if (!mWebSocketImpl->mChannel) {
+      mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
     }
   }
 private:
-  nsRefPtr<WebSocket> mWebSocket;
+  nsRefPtr<WebSocketImpl> mWebSocketImpl;
 };
 
 nsresult
-WebSocket::EstablishConnection()
+WebSocketImpl::EstablishConnection()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null");
 
   nsCOMPtr<nsIWebSocketChannel> wsChannel;
   nsAutoCloseWS autoClose(this);
   nsresult rv;
 
@@ -830,43 +1001,43 @@ WebSocket::EstablishConnection()
   NS_ENSURE_SUCCESS(rv, rv);
 
   mChannel = wsChannel;
 
   return NS_OK;
 }
 
 void
-WebSocket::DispatchConnectionCloseEvents()
+WebSocketImpl::DispatchConnectionCloseEvents()
 {
   mReadyState = WebSocket::CLOSED;
 
   // Call 'onerror' if needed
   if (mFailed) {
-    nsresult rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
+    nsresult rv =
+      mParent->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to dispatch the error event");
     }
   }
 
-  nsresult rv = CreateAndDispatchCloseEvent(mCloseEventWasClean, mCloseEventCode,
-                                            mCloseEventReason);
+  nsresult rv = mParent->CreateAndDispatchCloseEvent(mCloseEventWasClean,
+                                                     mCloseEventCode,
+                                                     mCloseEventReason);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch the close event");
   }
 
-  UpdateMustKeepAlive();
+  mParent->UpdateMustKeepAlive();
   Disconnect();
 }
 
 nsresult
-WebSocket::CreateAndDispatchSimpleEvent(const nsString& aName)
+WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIDOMEvent> event;
   rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -879,35 +1050,36 @@ WebSocket::CreateAndDispatchSimpleEvent(
 
   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
 nsresult
 WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
                                          bool isBinary)
 {
+  MOZ_ASSERT(mImpl);
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv))
     return NS_OK;
 
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
     return NS_ERROR_FAILURE;
   }
   JSContext* cx = jsapi.cx();
 
   // Create appropriate JS object for message
   JS::Rooted<JS::Value> jsData(cx);
   if (isBinary) {
-    if (mBinaryType == dom::BinaryType::Blob) {
+    if (mImpl->mBinaryType == dom::BinaryType::Blob) {
       rv = nsContentUtils::CreateBlobBuffer(cx, GetOwner(), aData, &jsData);
       NS_ENSURE_SUCCESS(rv, rv);
-    } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
+    } else if (mImpl->mBinaryType == dom::BinaryType::Arraybuffer) {
       JS::Rooted<JSObject*> arrayBuf(cx);
       rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
       NS_ENSURE_SUCCESS(rv, rv);
       jsData = OBJECT_TO_JSVAL(arrayBuf);
     } else {
       NS_RUNTIMEABORT("Unknown binary type!");
       return NS_ERROR_UNEXPECTED;
     }
@@ -927,29 +1099,29 @@ WebSocket::CreateAndDispatchMessageEvent
   nsCOMPtr<nsIDOMEvent> event;
   rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event);
   rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"),
                                       false, false,
                                       jsData,
-                                      mUTF16Origin,
+                                      mImpl->mUTF16Origin,
                                       EmptyString(), nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   event->SetTrusted(true);
 
   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
 nsresult
 WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
                                        uint16_t aCode,
-                                       const nsString &aReason)
+                                       const nsAString &aReason)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
@@ -969,17 +1141,17 @@ WebSocket::CreateAndDispatchCloseEvent(b
 
 bool
 WebSocket::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
 {
   return Preferences::GetBool("network.websocket.enabled", true);
 }
 
 nsresult
-WebSocket::ParseURL(const nsString& aURL)
+WebSocketImpl::ParseURL(const nsAString& aURL)
 {
   NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
 
   nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
@@ -1057,25 +1229,24 @@ WebSocket::ParseURL(const nsString& aURL
 //   1. the object has registered event listeners that can be triggered
 //      ("strong event listeners");
 //   2. there are outgoing not sent messages.
 //-----------------------------------------------------------------------------
 
 void
 WebSocket::UpdateMustKeepAlive()
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-  if (!mCheckMustKeepAlive) {
+  if (!mCheckMustKeepAlive || !mImpl) {
     return;
   }
 
   bool shouldKeepAlive = false;
 
   if (mListenerManager) {
-    switch (mReadyState)
+    switch (mImpl->mReadyState)
     {
       case WebSocket::CONNECTING:
       {
         if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
           shouldKeepAlive = true;
@@ -1084,51 +1255,51 @@ WebSocket::UpdateMustKeepAlive()
       break;
 
       case WebSocket::OPEN:
       case WebSocket::CLOSING:
       {
         if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
-            mOutgoingBufferedAmount != 0) {
+            mImpl->mOutgoingBufferedAmount != 0) {
           shouldKeepAlive = true;
         }
       }
       break;
 
       case WebSocket::CLOSED:
       {
         shouldKeepAlive = false;
       }
     }
   }
 
   if (mKeepingAlive && !shouldKeepAlive) {
     mKeepingAlive = false;
-    static_cast<EventTarget*>(this)->Release();
+    mImpl->Release();
   } else if (!mKeepingAlive && shouldKeepAlive) {
     mKeepingAlive = true;
-    static_cast<EventTarget*>(this)->AddRef();
+    mImpl->AddRef();
   }
 }
 
 void
 WebSocket::DontKeepAliveAnyMore()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   if (mKeepingAlive) {
     mKeepingAlive = false;
-    static_cast<EventTarget*>(this)->Release();
+    mImpl->Release();
   }
   mCheckMustKeepAlive = false;
 }
 
 nsresult
-WebSocket::UpdateURI()
+WebSocketImpl::UpdateURI()
 {
   // Check for Redirections
   nsRefPtr<BaseWebSocketChannel> channel;
   channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
   MOZ_ASSERT(channel);
 
   channel->GetEffectiveURL(mEffectiveURL);
   mSecure = channel->IsEncrypted();
@@ -1147,57 +1318,77 @@ WebSocket::EventListenerRemoved(nsIAtom*
 {
   UpdateMustKeepAlive();
 }
 
 //-----------------------------------------------------------------------------
 // WebSocket - methods
 //-----------------------------------------------------------------------------
 
+// webIDL: readonly attribute unsigned short readyState;
+uint16_t
+WebSocket::ReadyState() const
+{
+  return mImpl->ReadyState();
+}
+
+// webIDL: readonly attribute unsigned long bufferedAmount;
+uint32_t
+WebSocket::BufferedAmount() const
+{
+  return mImpl->BufferedAmount();
+}
+
+// webIDL: attribute BinaryType binaryType;
+dom::BinaryType
+WebSocket::BinaryType() const
+{
+  return mImpl->BinaryType();
+}
+
+// webIDL: attribute BinaryType binaryType;
+void
+WebSocket::SetBinaryType(dom::BinaryType aData)
+{
+  mImpl->SetBinaryType(aData);
+}
+
 // webIDL: readonly attribute DOMString url
 void
 WebSocket::GetUrl(nsAString& aURL)
 {
-  if (mEffectiveURL.IsEmpty()) {
-    aURL = mOriginalURL;
-  } else {
-    aURL = mEffectiveURL;
-  }
+  mImpl->GetUrl(aURL);
 }
 
 // webIDL: readonly attribute DOMString extensions;
 void
 WebSocket::GetExtensions(nsAString& aExtensions)
 {
-  CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
+  CopyUTF8toUTF16(mImpl->mEstablishedExtensions, aExtensions);
 }
 
 // webIDL: readonly attribute DOMString protocol;
 void
 WebSocket::GetProtocol(nsAString& aProtocol)
 {
-  CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
+  CopyUTF8toUTF16(mImpl->mEstablishedProtocol, aProtocol);
 }
 
 // webIDL: void send(DOMString data);
 void
 WebSocket::Send(const nsAString& aData,
                 ErrorResult& aRv)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
   NS_ConvertUTF16toUTF8 msgString(aData);
-  Send(nullptr, msgString, msgString.Length(), false, aRv);
+  mImpl->Send(nullptr, msgString, msgString.Length(), false, aRv);
 }
 
 void
 WebSocket::Send(File& aData, ErrorResult& aRv)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
   nsCOMPtr<nsIInputStream> msgStream;
   nsresult rv = aData.GetInternalStream(getter_AddRefs(msgStream));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   uint64_t msgLength;
@@ -1207,59 +1398,59 @@ WebSocket::Send(File& aData, ErrorResult
     return;
   }
 
   if (msgLength > UINT32_MAX) {
     aRv.Throw(NS_ERROR_FILE_TOO_BIG);
     return;
   }
 
-  Send(msgStream, EmptyCString(), msgLength, true, aRv);
+  mImpl->Send(msgStream, EmptyCString(), msgLength, true, aRv);
 }
 
 void
 WebSocket::Send(const ArrayBuffer& aData,
                 ErrorResult& aRv)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   aData.ComputeLengthAndData();
 
   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
 
   uint32_t len = aData.Length();
   char* data = reinterpret_cast<char*>(aData.Data());
 
   nsDependentCSubstring msgString(data, len);
-  Send(nullptr, msgString, len, true, aRv);
+  mImpl->Send(nullptr, msgString, len, true, aRv);
 }
 
 void
 WebSocket::Send(const ArrayBufferView& aData,
                 ErrorResult& aRv)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   aData.ComputeLengthAndData();
 
   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
 
   uint32_t len = aData.Length();
   char* data = reinterpret_cast<char*>(aData.Data());
 
   nsDependentCSubstring msgString(data, len);
-  Send(nullptr, msgString, len, true, aRv);
+  mImpl->Send(nullptr, msgString, len, true, aRv);
 }
 
 void
-WebSocket::Send(nsIInputStream* aMsgStream,
-                const nsACString& aMsgString,
-                uint32_t aMsgLength,
-                bool aIsBinary,
-                ErrorResult& aRv)
+WebSocketImpl::Send(nsIInputStream* aMsgStream,
+                    const nsACString& aMsgString,
+                    uint32_t aMsgLength,
+                    bool aIsBinary,
+                    ErrorResult& aRv)
 {
   if (mReadyState == WebSocket::CONNECTING) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Always increment outgoing buffer len, even if closed
   mOutgoingBufferedAmount += aMsgLength;
@@ -1283,25 +1474,33 @@ WebSocket::Send(nsIInputStream* aMsgStre
     }
   }
 
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
-  UpdateMustKeepAlive();
+  mParent->UpdateMustKeepAlive();
 }
 
 // webIDL: void close(optional unsigned short code, optional DOMString reason):
 void
 WebSocket::Close(const Optional<uint16_t>& aCode,
                  const Optional<nsAString>& aReason,
                  ErrorResult& aRv)
 {
+  mImpl->Close(aCode, aReason, aRv);
+}
+
+void
+WebSocketImpl::Close(const Optional<uint16_t>& aCode,
+                     const Optional<nsAString>& aReason,
+                     ErrorResult& aRv)
+{
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   // the reason code is optional, but if provided it must be in a specific range
   uint16_t closeCode = 0;
   if (aCode.WasPassed()) {
     if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) {
       aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
       return;
@@ -1330,126 +1529,126 @@ WebSocket::Close(const Optional<uint16_t
     return;
   }
 
   MOZ_ASSERT(mReadyState == WebSocket::OPEN);
   CloseConnection(closeCode, closeReason);
 }
 
 //-----------------------------------------------------------------------------
-// WebSocket::nsIObserver
+// WebSocketImpl::nsIObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
-WebSocket::Observe(nsISupports* aSubject,
-                   const char* aTopic,
-                   const char16_t* aData)
+WebSocketImpl::Observe(nsISupports* aSubject,
+                       const char* aTopic,
+                       const char16_t* aData)
 {
   if ((mReadyState == WebSocket::CLOSING) ||
       (mReadyState == WebSocket::CLOSED)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
-  if (!GetOwner() || window != GetOwner()) {
+  if (!mParent->GetOwner() || window != mParent->GetOwner()) {
     return NS_OK;
   }
 
   if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
       (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
   {
     CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// WebSocket::nsIRequest
+// WebSocketImpl::nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
-WebSocket::GetName(nsACString& aName)
+WebSocketImpl::GetName(nsACString& aName)
 {
   CopyUTF16toUTF8(mOriginalURL, aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::IsPending(bool* aValue)
+WebSocketImpl::IsPending(bool* aValue)
 {
   *aValue = (mReadyState != WebSocket::CLOSED);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::GetStatus(nsresult* aStatus)
+WebSocketImpl::GetStatus(nsresult* aStatus)
 {
   *aStatus = NS_OK;
   return NS_OK;
 }
 
 // Window closed, stop/reload button pressed, user navigated away from page, etc.
 NS_IMETHODIMP
-WebSocket::Cancel(nsresult aStatus)
+WebSocketImpl::Cancel(nsresult aStatus)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
-  if (mReadyState == CLOSING || mReadyState == CLOSED) {
+  if (mReadyState == WebSocket::CLOSING || mReadyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
   ConsoleError();
 
   return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
 }
 
 NS_IMETHODIMP
-WebSocket::Suspend()
+WebSocketImpl::Suspend()
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-WebSocket::Resume()
+WebSocketImpl::Resume()
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-WebSocket::GetLoadGroup(nsILoadGroup** aLoadGroup)
+WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup)
 {
   *aLoadGroup = nullptr;
 
   nsresult rv;
-  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+  nsIScriptContext* sc = mParent->GetContextForEventHandlers(&rv);
   nsCOMPtr<nsIDocument> doc =
     nsContentUtils::GetDocumentFromScriptContext(sc);
 
   if (doc) {
     *aLoadGroup = doc->GetDocumentLoadGroup().take();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::SetLoadGroup(nsILoadGroup* aLoadGroup)
+WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup)
 {
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMETHODIMP
-WebSocket::GetLoadFlags(nsLoadFlags* aLoadFlags)
+WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags)
 {
   *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebSocket::SetLoadFlags(nsLoadFlags aLoadFlags)
+WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags)
 {
   // we won't change the load flags at all.
   return NS_OK;
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/content/base/src/WebSocket.h
+++ b/content/base/src/WebSocket.h
@@ -10,61 +10,47 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/WebSocketBinding.h" // for BinaryType
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/ErrorResult.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIInterfaceRequestor.h"
-#include "nsIObserver.h"
-#include "nsIRequest.h"
 #include "nsISupports.h"
 #include "nsISupportsUtils.h"
-#include "nsIWebSocketChannel.h"
-#include "nsIWebSocketListener.h"
 #include "nsString.h"
-#include "nsWeakReference.h"
 #include "nsWrapperCache.h"
 
 #define DEFAULT_WS_SCHEME_PORT  80
 #define DEFAULT_WSS_SCHEME_PORT 443
 
 namespace mozilla {
 namespace dom {
 
 class File;
 
-class WebSocket MOZ_FINAL : public DOMEventTargetHelper,
-                            public nsIInterfaceRequestor,
-                            public nsIWebSocketListener,
-                            public nsIObserver,
-                            public nsSupportsWeakReference,
-                            public nsIRequest
+class WebSocketImpl;
+
+class WebSocket MOZ_FINAL : public DOMEventTargetHelper
 {
-friend class CallDispatchConnectionCloseEvents;
-friend class nsAutoCloseWS;
+  friend class WebSocketImpl;
 
 public:
   enum {
     CONNECTING = 0,
     OPEN       = 1,
     CLOSING    = 2,
     CLOSED     = 3
   };
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(
     WebSocket, DOMEventTargetHelper)
-  NS_DECL_NSIINTERFACEREQUESTOR
-  NS_DECL_NSIWEBSOCKETLISTENER
-  NS_DECL_NSIOBSERVER
-  NS_DECL_NSIREQUEST
 
   // EventTarget
   virtual void EventListenerAdded(nsIAtom* aType) MOZ_OVERRIDE;
   virtual void EventListenerRemoved(nsIAtom* aType) MOZ_OVERRIDE;
 
   virtual void DisconnectFromOwner() MOZ_OVERRIDE;
 
   // nsWrapperCache
@@ -93,20 +79,20 @@ public: // WebIDL interface:
                                                  const nsAString& aUrl,
                                                  const Sequence<nsString>& aProtocols,
                                                  ErrorResult& rv);
 
   // webIDL: readonly attribute DOMString url
   void GetUrl(nsAString& aResult);
 
   // webIDL: readonly attribute unsigned short readyState;
-  uint16_t ReadyState() const { return mReadyState; }
+  uint16_t ReadyState() const;
 
   // webIDL: readonly attribute unsigned long bufferedAmount;
-  uint32_t BufferedAmount() const { return mOutgoingBufferedAmount; }
+  uint32_t BufferedAmount() const;
 
   // webIDL: attribute Function? onopen;
   IMPL_EVENT_HANDLER(open)
 
   // webIDL: attribute Function? onerror;
   IMPL_EVENT_HANDLER(error)
 
   // webIDL: attribute Function? onclose;
@@ -122,143 +108,57 @@ public: // WebIDL interface:
   void Close(const Optional<uint16_t>& aCode,
              const Optional<nsAString>& aReason,
              ErrorResult& aRv);
 
   // webIDL: attribute Function? onmessage;
   IMPL_EVENT_HANDLER(message)
 
   // webIDL: attribute DOMString binaryType;
-  dom::BinaryType BinaryType() const { return mBinaryType; }
-  void SetBinaryType(dom::BinaryType aData) { mBinaryType = aData; }
+  dom::BinaryType BinaryType() const;
+  void SetBinaryType(dom::BinaryType aData);
 
   // webIDL: void send(DOMString|Blob|ArrayBufferView data);
   void Send(const nsAString& aData,
             ErrorResult& aRv);
   void Send(File& aData,
             ErrorResult& aRv);
   void Send(const ArrayBuffer& aData,
             ErrorResult& aRv);
   void Send(const ArrayBufferView& aData,
             ErrorResult& aRv);
 
 private: // constructor && distructor
   explicit WebSocket(nsPIDOMWindow* aOwnerWindow);
   virtual ~WebSocket();
 
-protected:
-  nsresult Init(JSContext* aCx,
-                nsIPrincipal* aPrincipal,
-                const nsAString& aURL,
-                nsTArray<nsString>& aProtocolArray);
-
-  void Send(nsIInputStream* aMsgStream,
-            const nsACString& aMsgString,
-            uint32_t aMsgLength,
-            bool aIsBinary,
-            ErrorResult& aRv);
-
-  nsresult ParseURL(const nsString& aURL);
-  nsresult EstablishConnection();
-
-  // These methods when called can release the WebSocket object
-  void FailConnection(uint16_t reasonCode,
-                      const nsACString& aReasonString = EmptyCString());
-  nsresult CloseConnection(uint16_t reasonCode,
-                           const nsACString& aReasonString = EmptyCString());
-  nsresult Disconnect();
-
-  nsresult ConsoleError();
-  nsresult PrintErrorOnConsole(const char* aBundleURI,
-                               const char16_t* aError,
-                               const char16_t** aFormatStrings,
-                               uint32_t aFormatStringsLen);
-
-  nsresult DoOnMessageAvailable(const nsACString& aMsg,
-                                bool isBinary);
-
-  // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
-  // - These must not be dispatched while we are still within an incoming call
-  //   from JS (ex: close()).  Set 'sync' to false in that case to dispatch in a
-  //   separate new event.
-  nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
-                                         nsresult aStatusCode,
-                                         bool sync);
-  // 2nd half of ScheduleConnectionCloseEvents, sometimes run in its own event.
-  void DispatchConnectionCloseEvents();
-
   // These methods actually do the dispatch for various events.
-  nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
+  nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
   nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
                                          bool isBinary);
   nsresult CreateAndDispatchCloseEvent(bool aWasClean,
                                        uint16_t aCode,
-                                       const nsString& aReason);
+                                       const nsAString& aReason);
 
   // if there are "strong event listeners" (see comment in WebSocket.cpp) or
   // outgoing not sent messages then this method keeps the object alive
   // when js doesn't have strong references to it.
   void UpdateMustKeepAlive();
   // ATTENTION, when calling this method the object can be released
   // (and possibly collected).
   void DontKeepAliveAnyMore();
 
-  nsresult UpdateURI();
-
-protected: //data
-
-  nsCOMPtr<nsIWebSocketChannel> mChannel;
+private:
+  WebSocket(const WebSocket& x) MOZ_DELETE;   // prevent bad usage
+  WebSocket& operator=(const WebSocket& x) MOZ_DELETE;
 
-  // related to the WebSocket constructor steps
-  nsString mOriginalURL;
-  nsString mEffectiveURL;   // after redirects
-  bool mSecure; // if true it is using SSL and the wss scheme,
-                // otherwise it is using the ws scheme with no SSL
+  // Raw pointer because this WebSocketImpl is created, managed an destroyed by
+  // WebSocket.
+  WebSocketImpl* mImpl;
 
   bool mKeepingAlive;
   bool mCheckMustKeepAlive;
-  bool mOnCloseScheduled;
-  bool mFailed;
-  bool mDisconnected;
-
-  // Set attributes of DOM 'onclose' message
-  bool      mCloseEventWasClean;
-  nsString  mCloseEventReason;
-  uint16_t  mCloseEventCode;
-
-  nsCString mAsciiHost;  // hostname
-  uint32_t  mPort;
-  nsCString mResource; // [filepath[?query]]
-  nsString  mUTF16Origin;
-
-  nsCOMPtr<nsIURI> mURI;
-  nsCString mRequestedProtocolList;
-  nsCString mEstablishedProtocol;
-  nsCString mEstablishedExtensions;
-
-  uint16_t mReadyState;
-
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsWeakPtr              mOriginDocument;
-
-  uint32_t mOutgoingBufferedAmount;
-
-  dom::BinaryType mBinaryType;
-
-  // Web Socket owner information:
-  // - the script file name, UTF8 encoded.
-  // - source code line number where the Web Socket object was constructed.
-  // - the ID of the inner window where the script lives. Note that this may not
-  //   be the same as the Web Socket owner window.
-  // These attributes are used for error reporting.
-  nsCString mScriptFile;
-  uint32_t mScriptLine;
-  uint64_t mInnerWindowID;
-
-private:
-  WebSocket(const WebSocket& x) MOZ_DELETE;   // prevent bad usage
-  WebSocket& operator=(const WebSocket& x) MOZ_DELETE;
 };
 
 } //namespace dom
 } //namespace mozilla
 
 #endif
--- a/netwerk/ipc/ChannelEventQueue.h
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -89,18 +89,18 @@ class ChannelEventQueue MOZ_FINAL
   uint32_t mSuspendCount;
   bool     mSuspended;
   bool mForced;
   bool mFlushing;
 
   // Keep ptr to avoid refcount cycle: only grab ref during flushing.
   nsISupports *mOwner;
 
-  // Target thread for delivery of events.
-  nsCOMPtr<nsIThread> mTargetThread;
+  // EventTarget for delivery of events to the correct thread.
+  nsCOMPtr<nsIEventTarget> mTargetThread;
 
   friend class AutoEventEnqueuer;
 };
 
 inline bool
 ChannelEventQueue::ShouldEnqueue()
 {
   bool answer =  mForced || mSuspended || mFlushing;
--- a/netwerk/protocol/websocket/BaseWebSocketChannel.h
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -60,17 +60,17 @@ class BaseWebSocketChannel : public nsIW
  protected:
   nsCOMPtr<nsIURI>                mOriginalURI;
   nsCOMPtr<nsIURI>                mURI;
   nsCOMPtr<nsIWebSocketListener>  mListener;
   nsCOMPtr<nsISupports>           mContext;
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   nsCOMPtr<nsILoadGroup>          mLoadGroup;
   nsCOMPtr<nsILoadInfo>           mLoadInfo;
-  nsCOMPtr<nsIThread>             mTargetThread;
+  nsCOMPtr<nsIEventTarget>        mTargetThread;
 
   nsCString                       mProtocol;
   nsCString                       mOrigin;
 
   nsCString                       mNegotiatedExtensions;
 
   uint32_t                        mEncrypted                 : 1;
   uint32_t                        mWasOpened                 : 1;
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -649,21 +649,19 @@ private:
   nsCString                         mReason;
 };
 NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
 
 //-----------------------------------------------------------------------------
 // CallAcknowledge
 //-----------------------------------------------------------------------------
 
-class CallAcknowledge MOZ_FINAL : public nsIRunnable
+class CallAcknowledge MOZ_FINAL : public nsCancelableRunnable
 {
 public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
   CallAcknowledge(WebSocketChannel *aChannel,
                   uint32_t          aSize)
     : mChannel(aChannel),
       mSize(aSize) {}
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(mChannel->IsOnTargetThread());
@@ -674,17 +672,16 @@ public:
   }
 
 private:
   ~CallAcknowledge() {}
 
   nsRefPtr<WebSocketChannel>        mChannel;
   uint32_t                          mSize;
 };
-NS_IMPL_ISUPPORTS(CallAcknowledge, nsIRunnable)
 
 //-----------------------------------------------------------------------------
 // CallOnTransportAvailable
 //-----------------------------------------------------------------------------
 
 class CallOnTransportAvailable MOZ_FINAL : public nsIRunnable
 {
 public: