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 id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs504553
milestone35.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 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: