Bug 504553 - patch 3 - WebSockes in Workers: Workers supported, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 10 Oct 2014 17:58:05 +0100
changeset 233086 f23cea600bd00a7cce19a0a4bd178e8e4c9f81d1
parent 233085 e7950dac9a28912c5a7bacba0d3d06ad889c1b11
child 233087 8edaa32e2278b7f1a8835a4f85d9a7b0d4eb5b53
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 3 - WebSockes in Workers: Workers supported, r=smaug
content/base/src/WebSocket.cpp
content/base/src/WebSocket.h
content/base/src/moz.build
dom/bindings/Bindings.conf
dom/webidl/WebSocket.webidl
dom/workers/test/test_worker_interfaces.js
netwerk/protocol/websocket/WebSocketChannel.cpp
--- a/content/base/src/WebSocket.cpp
+++ b/content/base/src/WebSocket.cpp
@@ -10,16 +10,18 @@
 
 #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 "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDOMWindow.h"
 #include "nsIDocument.h"
 #include "nsXPCOM.h"
 #include "nsIXPConnect.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsIScriptObjectPrincipal.h"
@@ -45,107 +47,140 @@
 #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 "nsProxyRelease.h"
 #include "nsWeakReference.h"
 
 using namespace mozilla::net;
+using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
 class WebSocketImpl MOZ_FINAL : public nsIInterfaceRequestor
                               , public nsIWebSocketListener
                               , public nsIObserver
                               , public nsSupportsWeakReference
                               , public nsIRequest
+                              , public nsIEventTarget
 {
-friend class CallDispatchConnectionCloseEvents;
-friend class nsAutoCloseWS;
-
 public:
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIWEBSOCKETLISTENER
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIREQUEST
   NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIEVENTTARGET
 
   WebSocketImpl(WebSocket* aWebSocket)
-  : mParent(aWebSocket)
+  : mWebSocket(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)
+  , mMutex("WebSocketImpl::mMutex")
+  , mWorkerPrivate(nullptr)
+  , mReadyState(WebSocket::CONNECTING)
   {
+    if (!NS_IsMainThread()) {
+      mWorkerPrivate = GetCurrentThreadWorkerPrivate();
+      MOZ_ASSERT(mWorkerPrivate);
+    }
+  }
+
+  void AssertIsOnTargetThread() const
+  {
+    MOZ_ASSERT(IsTargetThread());
   }
 
-  uint16_t ReadyState() const
+  bool IsTargetThread() const;
+
+  uint16_t ReadyState()
   {
+    MutexAutoLock lock(mMutex);
     return mReadyState;
   }
 
+  void SetReadyState(uint16_t aReadyState)
+  {
+    MutexAutoLock lock(mMutex);
+    mReadyState = aReadyState;
+  }
+
   uint32_t BufferedAmount() const
   {
+    AssertIsOnTargetThread();
     return mOutgoingBufferedAmount;
   }
 
   dom::BinaryType BinaryType() const
   {
+    AssertIsOnTargetThread();
     return mBinaryType;
   }
 
   void SetBinaryType(dom::BinaryType aData)
   {
+    AssertIsOnTargetThread();
     mBinaryType = aData;
   }
 
-  void GetUrl(nsAString& aURL)
+  void GetUrl(nsAString& aURL) const
   {
+    AssertIsOnTargetThread();
+
     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 Init(JSContext* aCx,
+            nsIPrincipal* aPrincipal,
+            const nsAString& aURL,
+            nsTArray<nsString>& aProtocolArray,
+            const nsACString& aScriptFile,
+            uint32_t aScriptLine,
+            ErrorResult& aRv,
+            bool* aConnectionFailed);
+
+  void AsyncOpen(ErrorResult& aRv);
 
   void Send(nsIInputStream* aMsgStream,
             const nsACString& aMsgString,
             uint32_t aMsgLength,
             bool aIsBinary,
             ErrorResult& aRv);
 
   nsresult ParseURL(const nsAString& aURL);
-  nsresult EstablishConnection();
+  nsresult InitializeConnection();
 
   // 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();
+  void DisconnectInternal();
 
   nsresult ConsoleError();
   nsresult PrintErrorOnConsole(const char* aBundleURI,
                                const char16_t* aError,
                                const char16_t** aFormatStrings,
                                uint32_t aFormatStringsLen);
 
   nsresult DoOnMessageAvailable(const nsACString& aMsg,
@@ -161,17 +196,24 @@ public:
   // 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;
+  void AddRefObject();
+  void ReleaseObject();
+
+  void UnregisterFeature();
+
+  nsresult CancelInternal();
+
+  nsRefPtr<WebSocket> mWebSocket;
 
   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
@@ -185,23 +227,21 @@ public:
   nsString  mCloseEventReason;
   uint16_t  mCloseEventCode;
 
   nsCString mAsciiHost;  // hostname
   uint32_t  mPort;
   nsCString mResource; // [filepath[?query]]
   nsString  mUTF16Origin;
 
-  nsCOMPtr<nsIURI> mURI;
+  nsCString 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:
@@ -209,62 +249,127 @@ public:
   // - 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;
 
+  // This mutex protects mReadyState that is the only variable that is used in
+  // different threads.
+  mozilla::Mutex mMutex;
+
+  WorkerPrivate* mWorkerPrivate;
+  nsAutoPtr<WorkerFeature> mWorkerFeature;
+
 private:
   ~WebSocketImpl()
   {
     // If we threw during Init we never called disconnect
     if (!mDisconnected) {
       Disconnect();
     }
   }
 
+  // This value should not be used directly but use ReadyState() instead.
+  uint16_t mReadyState;
 };
 
 NS_IMPL_ISUPPORTS(WebSocketImpl,
                   nsIInterfaceRequestor,
                   nsIWebSocketListener,
                   nsIObserver,
                   nsISupportsWeakReference,
-                  nsIRequest)
-
-class CallDispatchConnectionCloseEvents: public nsRunnable
+                  nsIRequest,
+                  nsIEventTarget)
+
+class CallDispatchConnectionCloseEvents MOZ_FINAL : public nsRunnable
 {
 public:
   explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
     : mWebSocketImpl(aWebSocketImpl)
-  {}
+  {
+    aWebSocketImpl->AssertIsOnTargetThread();
+  }
 
   NS_IMETHOD Run()
   {
+    mWebSocketImpl->AssertIsOnTargetThread();
     mWebSocketImpl->DispatchConnectionCloseEvents();
     return NS_OK;
   }
 
 private:
   nsRefPtr<WebSocketImpl> mWebSocketImpl;
 };
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl
 //-----------------------------------------------------------------------------
 
+namespace {
+
+class PrintErrorOnConsoleRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl,
+                              const char* aBundleURI,
+                              const char16_t* aError,
+                              const char16_t** aFormatStrings,
+                              uint32_t aFormatStringsLen)
+    : WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
+    , mImpl(aImpl)
+    , mBundleURI(aBundleURI)
+    , mError(aError)
+    , mFormatStrings(aFormatStrings)
+    , mFormatStringsLen(aFormatStringsLen)
+    , mRv(NS_ERROR_FAILURE)
+  { }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    mRv = mImpl->PrintErrorOnConsole(mBundleURI, mError, mFormatStrings,
+                                     mFormatStringsLen);
+    return true;
+  }
+
+  nsresult ErrorCode() const
+  {
+    return mRv;
+  }
+
+private:
+  // Raw pointer because this runnable is sync.
+  WebSocketImpl* mImpl;
+
+  const char* mBundleURI;
+  const char16_t* mError;
+  const char16_t** mFormatStrings;
+  uint32_t mFormatStringsLen;
+  nsresult mRv;
+};
+
+} // anonymous namespace
+
 nsresult
 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");
+  // This method must run on the main thread.
+
+  if (!NS_IsMainThread()) {
+    nsRefPtr<PrintErrorOnConsoleRunnable> runnable =
+      new PrintErrorOnConsoleRunnable(this, aBundleURI, aError, aFormatStrings,
+                                      aFormatStringsLen);
+    runnable->Dispatch(mWorkerPrivate->GetJSContext());
+    return runnable->ErrorCode();
+  }
 
   nsresult rv;
   nsCOMPtr<nsIStringBundleService> bundleService =
     do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIStringBundle> strBundle;
   rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
@@ -298,228 +403,331 @@ WebSocketImpl::PrintErrorOnConsole(const
 
   // print the error message directly to the JS console
   rv = console->LogMessage(errorObject);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+namespace {
+
+class CloseRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  CloseRunnable(WebSocketImpl* aImpl, uint16_t aReasonCode,
+                const nsACString& aReasonString)
+    : WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
+    , mImpl(aImpl)
+    , mReasonCode(aReasonCode)
+    , mReasonString(aReasonString)
+    , mRv(NS_ERROR_FAILURE)
+  { }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    mRv = mImpl->mChannel->Close(mReasonCode, mReasonString);
+    return true;
+  }
+
+  nsresult ErrorCode() const
+  {
+    return mRv;
+  }
+
+private:
+  // A raw pointer because this runnable is sync.
+  WebSocketImpl* mImpl;
+
+  uint16_t mReasonCode;
+  const nsACString& mReasonString;
+  nsresult mRv;
+};
+
+} // anonymous namespace
+
 nsresult
 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) {
+  AssertIsOnTargetThread();
+
+  uint16_t readyState = ReadyState();
+  if (readyState == WebSocket::CLOSING ||
+      readyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
   // The common case...
   if (mChannel) {
-    mReadyState = WebSocket::CLOSING;
-    return mChannel->Close(aReasonCode, aReasonString);
+    SetReadyState(WebSocket::CLOSING);
+
+    // The channel has to be closed on the main-thread.
+
+    if (NS_IsMainThread()) {
+      return mChannel->Close(aReasonCode, aReasonString);
+    }
+
+    nsRefPtr<CloseRunnable> runnable =
+      new CloseRunnable(this, aReasonCode, aReasonString);
+    runnable->Dispatch(mWorkerPrivate->GetJSContext());
+    return runnable->ErrorCode();
   }
 
   // No channel, but not disconnected: canceled or failed early
   //
-  MOZ_ASSERT(mReadyState == WebSocket::CONNECTING,
+  MOZ_ASSERT(readyState == WebSocket::CONNECTING,
              "Should only get here for early websocket cancel/error");
 
   // Server won't be sending us a close code, so use what's passed in here.
   mCloseEventCode = aReasonCode;
   CopyUTF8toUTF16(aReasonString, mCloseEventReason);
 
-  mReadyState = WebSocket::CLOSING;
+  SetReadyState(WebSocket::CLOSING);
 
   // Can be called from Cancel() or Init() codepaths, so need to dispatch
   // onerror/onclose asynchronously
   ScheduleConnectionCloseEvents(
                     nullptr,
                     (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
                      aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
                      NS_OK : NS_ERROR_FAILURE,
                     false);
 
   return NS_OK;
 }
 
 nsresult
 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");
+  AssertIsOnTargetThread();
+
+  NS_ConvertUTF8toUTF16 specUTF16(mURI);
+  const char16_t* formatStrings[] = { specUTF16.get() };
+
+  if (ReadyState() < WebSocket::OPEN) {
+    PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+                        MOZ_UTF16("connectionFailure"),
+                        formatStrings, ArrayLength(formatStrings));
   } else {
-    NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
-    const char16_t* formatStrings[] = { specUTF16.get() };
-
-    if (mReadyState < WebSocket::OPEN) {
-      PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
-                          MOZ_UTF16("connectionFailure"),
-                          formatStrings, ArrayLength(formatStrings));
-    } else {
-      PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
-                          MOZ_UTF16("netInterrupt"),
-                          formatStrings, ArrayLength(formatStrings));
-    }
+    PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+                        MOZ_UTF16("netInterrupt"),
+                        formatStrings, ArrayLength(formatStrings));
   }
   /// todo some specific errors - like for message too large
-  return rv;
+  return NS_OK;
 }
 
 void
 WebSocketImpl::FailConnection(uint16_t aReasonCode,
                               const nsACString& aReasonString)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  AssertIsOnTargetThread();
 
   ConsoleError();
   mFailed = true;
   CloseConnection(aReasonCode, aReasonString);
 }
 
+namespace {
+
+class DisconnectInternalRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  DisconnectInternalRunnable(WebSocketImpl* aImpl)
+    : WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
+    , mImpl(aImpl)
+  { }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    mImpl->DisconnectInternal();
+    return true;
+  }
+
+private:
+  // A raw pointer because this runnable is sync.
+  WebSocketImpl* mImpl;
+};
+
+} // anonymous namespace
+
 nsresult
 WebSocketImpl::Disconnect()
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
-  if (mDisconnected)
+  if (mDisconnected) {
     return NS_OK;
+  }
+
+  AssertIsOnTargetThread();
+
+  // DisconnectInternal touches observers and nsILoadGroup and it must run on
+  // the main thread.
+
+  if (NS_IsMainThread()) {
+    DisconnectInternal();
+  } else {
+    nsRefPtr<DisconnectInternalRunnable> runnable =
+      new DisconnectInternalRunnable(this);
+    runnable->Dispatch(mWorkerPrivate->GetJSContext());
+  }
+
+  // DontKeepAliveAnyMore() can release the object. So hold a reference to this
+  // until the end of the method.
+  nsRefPtr<WebSocketImpl> kungfuDeathGrip = this;
+
+  if (mWorkerPrivate && mWorkerFeature) {
+    UnregisterFeature();
+  }
+
+  nsCOMPtr<nsIThread> mainThread;
+  if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread))) ||
+      NS_FAILED(NS_ProxyRelease(mainThread, mChannel))) {
+    NS_WARNING("Failed to proxy release of channel, leaking instead!");
+  }
+
+  mDisconnected = true;
+  mWebSocket->DontKeepAliveAnyMore();
+  mWebSocket->mImpl = nullptr;
+
+  // We want to release the WebSocket in the correct thread.
+  mWebSocket = nullptr;
+
+  return NS_OK;
+}
+
+void
+WebSocketImpl::DisconnectInternal()
+{
+  AssertIsOnMainThread();
 
   nsCOMPtr<nsILoadGroup> loadGroup;
   GetLoadGroup(getter_AddRefs(loadGroup));
-  if (loadGroup)
+  if (loadGroup) {
     loadGroup->RemoveRequest(this, nullptr, NS_OK);
+  }
 
   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<WebSocketImpl> kungfuDeathGrip = this;
-
-  mChannel = nullptr;
-  mDisconnected = true;
-  mParent->DontKeepAliveAnyMore();
-  mParent->mImpl = nullptr;
-
-  return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl::nsIWebSocketListener methods:
 //-----------------------------------------------------------------------------
 
 nsresult
 WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
-  if (mReadyState == WebSocket::CLOSED) {
+  AssertIsOnTargetThread();
+
+  int16_t readyState = ReadyState();
+  if (readyState == WebSocket::CLOSED) {
     NS_ERROR("Received message after CLOSED");
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (mReadyState == WebSocket::OPEN) {
+  if (readyState == WebSocket::OPEN) {
     // Dispatch New Message
-    nsresult rv = mParent->CreateAndDispatchMessageEvent(aMsg, isBinary);
+    nsresult rv = mWebSocket->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,
+    MOZ_ASSERT(readyState == WebSocket::CLOSING,
                "Received message while CONNECTING or CLOSED");
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
                                   const nsACString& aMsg)
 {
+  AssertIsOnTargetThread();
   return DoOnMessageAvailable(aMsg, false);
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
                                         const nsACString& aMsg)
 {
+  AssertIsOnTargetThread();
   return DoOnMessageAvailable(aMsg, true);
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnStart(nsISupports* aContext)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  AssertIsOnTargetThread();
+
+  int16_t readyState = ReadyState();
 
   // This is the only function that sets OPEN, and should be called only once
-  MOZ_ASSERT(mReadyState != WebSocket::OPEN,
+  MOZ_ASSERT(readyState != WebSocket::OPEN,
              "readyState already OPEN! OnStart called twice?");
 
   // Nothing to do if we've already closed/closing
-  if (mReadyState != WebSocket::CONNECTING) {
+  if (readyState != WebSocket::CONNECTING) {
     return NS_OK;
   }
 
   // Attempt to kill "ghost" websocket: but usually too early for check to fail
-  nsresult rv = mParent->CheckInnerWindowCorrectness();
+  nsresult rv = mWebSocket->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;
+  SetReadyState(WebSocket::OPEN);
 
   // Call 'onopen'
-  rv = mParent->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
+  rv = mWebSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch the open event");
   }
 
-  mParent->UpdateMustKeepAlive();
+  mWebSocket->UpdateMustKeepAlive();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode)
 {
+  AssertIsOnTargetThread();
+
   // 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,
+  MOZ_ASSERT(ReadyState() != 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
 WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
                                              nsresult aStatusCode,
                                              bool sync)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnTargetThread();
 
   // no-op if some other code has already initiated close event
   if (!mOnCloseScheduled) {
     mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
 
     if (aStatusCode == NS_BASE_STREAM_CLOSED) {
       // don't generate an error event just because of an unclean close
       aStatusCode = NS_OK;
@@ -540,78 +748,83 @@ WebSocketImpl::ScheduleConnectionCloseEv
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
-  if (aSize > mOutgoingBufferedAmount)
+  AssertIsOnTargetThread();
+
+  if (aSize > mOutgoingBufferedAmount) {
     return NS_ERROR_UNEXPECTED;
+  }
 
   mOutgoingBufferedAmount -= aSize;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 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,
+  AssertIsOnTargetThread();
+
+  int16_t readyState = ReadyState();
+
+  MOZ_ASSERT(readyState != WebSocket::CONNECTING,
              "Received server close before connected?");
-  MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
+  MOZ_ASSERT(readyState != WebSocket::CLOSED,
              "Received server close after already closed!");
 
   // store code/string for onclose DOM event
   mCloseEventCode = aCode;
   CopyUTF8toUTF16(aReason, mCloseEventReason);
 
-  if (mReadyState == WebSocket::OPEN) {
+  if (readyState == WebSocket::OPEN) {
     // Server initiating close.
     // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
     // typically echos the status code it received".
     // But never send certain codes, per section 7.4.1
     if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
       CloseConnection(0, EmptyCString());
     } else {
       CloseConnection(aCode, aReason);
     }
   } else {
     // We initiated close, and server has replied: OnStop does rest of the work.
-    MOZ_ASSERT(mReadyState == WebSocket::CLOSING, "unknown state");
+    MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
-  if (mReadyState == WebSocket::CLOSED)
+  AssertIsOnMainThread();
+
+  if (ReadyState() == WebSocket::CLOSED) {
     return NS_ERROR_FAILURE;
+  }
 
   if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
       aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
     nsresult rv;
-    nsIScriptContext* sc = mParent->GetContextForEventHandlers(&rv);
+    nsIScriptContext* sc = mWebSocket->GetContextForEventHandlers(&rv);
     nsCOMPtr<nsIDocument> doc =
       nsContentUtils::GetDocumentFromScriptContext(sc);
-    if (!doc)
+    if (!doc) {
       return NS_ERROR_NOT_AVAILABLE;
+    }
 
     nsCOMPtr<nsIPromptFactory> wwatch =
       do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsPIDOMWindow> outerWindow = doc->GetWindow();
     return wwatch->GetPrompt(outerWindow, aIID, aResult);
   }
@@ -619,22 +832,23 @@ WebSocketImpl::GetInterface(const nsIID&
   return QueryInterface(aIID, aResult);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // WebSocket
 ////////////////////////////////////////////////////////////////////////////////
 
 WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
-: DOMEventTargetHelper(aOwnerWindow)
-, mKeepingAlive(false)
-, mCheckMustKeepAlive(true)
+  : DOMEventTargetHelper(aOwnerWindow)
+  , mWorkerPrivate(nullptr)
+  , mKeepingAlive(false)
+  , mCheckMustKeepAlive(true)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   mImpl = new WebSocketImpl(this);
+  mWorkerPrivate = mImpl->mWorkerPrivate;
 }
 
 WebSocket::~WebSocket()
 {
 }
 
 JSObject*
 WebSocket::WrapObject(JSContext* cx)
@@ -662,16 +876,137 @@ WebSocket::Constructor(const GlobalObjec
                        const nsAString& aProtocol,
                        ErrorResult& aRv)
 {
   Sequence<nsString> protocols;
   protocols.AppendElement(aProtocol);
   return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
 }
 
+namespace {
+
+// This class is used to clear any exception.
+class MOZ_STACK_CLASS ClearException
+{
+public:
+  explicit ClearException(JSContext* aCx)
+    : mCx(aCx)
+  {
+  }
+
+  ~ClearException()
+  {
+    JS_ClearPendingException(mCx);
+  }
+
+private:
+  JSContext* mCx;
+};
+
+class InitRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  InitRunnable(WebSocketImpl* aImpl, const nsAString& aURL,
+               nsTArray<nsString>& aProtocolArray,
+               const nsACString& aScriptFile, uint32_t aScriptLine,
+               ErrorResult& aRv, bool* aConnectionFailed)
+    : WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
+    , mImpl(aImpl)
+    , mURL(aURL)
+    , mProtocolArray(aProtocolArray)
+    , mScriptFile(aScriptFile)
+    , mScriptLine(aScriptLine)
+    , mRv(aRv)
+    , mConnectionFailed(aConnectionFailed)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+
+    // Walk up to our containing page
+    WorkerPrivate* wp = mWorkerPrivate;
+    while (wp->GetParent()) {
+      wp = wp->GetParent();
+    }
+
+    nsPIDOMWindow* window = wp->GetWindow();
+    if (!window) {
+      mRv.Throw(NS_ERROR_FAILURE);
+      return true;
+    }
+
+    AutoJSAPI jsapi;
+    if (NS_WARN_IF(!jsapi.Init(window))) {
+      mRv.Throw(NS_ERROR_FAILURE);
+      return true;
+    }
+
+    ClearException ce(jsapi.cx());
+
+    nsIDocument* doc = window->GetExtantDoc();
+    if (!doc) {
+      mRv.Throw(NS_ERROR_FAILURE);
+      return true;
+    }
+
+    nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+    if (!principal) {
+      mRv.Throw(NS_ERROR_FAILURE);
+      return true;
+    }
+
+    mImpl->Init(jsapi.cx(), principal, mURL, mProtocolArray, mScriptFile,
+                mScriptLine, mRv, mConnectionFailed);
+    return true;
+  }
+
+private:
+  // Raw pointer. This worker runs synchronously.
+  WebSocketImpl* mImpl;
+
+  const nsAString& mURL;
+  nsTArray<nsString>& mProtocolArray;
+  nsCString mScriptFile;
+  uint32_t mScriptLine;
+  ErrorResult& mRv;
+  bool* mConnectionFailed;
+};
+
+class AsyncOpenRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  AsyncOpenRunnable(WebSocketImpl* aImpl, ErrorResult& aRv)
+    : WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
+    , mImpl(aImpl)
+    , mRv(aRv)
+  {
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    mImpl->AsyncOpen(mRv);
+    return true;
+  }
+
+private:
+  // Raw pointer. This worker runs synchronously.
+  WebSocketImpl* mImpl;
+
+  ErrorResult& mRv;
+};
+
+} // anonymous namespace
+
 already_AddRefed<WebSocket>
 WebSocket::Constructor(const GlobalObject& aGlobal,
                        const nsAString& aUrl,
                        const Sequence<nsString>& aProtocols,
                        ErrorResult& aRv)
 {
   nsCOMPtr<nsIPrincipal> principal;
   nsCOMPtr<nsPIDOMWindow> ownerWindow;
@@ -722,23 +1057,101 @@ 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->mImpl->Init(aGlobal.Context(), principal,
-                                       aUrl, protocolArray);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
+  nsRefPtr<WebSocketImpl> kungfuDeathGrip = webSocket->mImpl;
+
+  bool connectionFailed = true;
+
+  if (NS_IsMainThread()) {
+    webSocket->mImpl->Init(aGlobal.Context(), principal, aUrl, protocolArray,
+                           EmptyCString(), 0, aRv, &connectionFailed);
+  } else {
+    unsigned lineno;
+    JS::AutoFilename file;
+    if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno)) {
+      NS_WARNING("Failed to get line number and filename in workers.");
+    }
+
+    nsRefPtr<InitRunnable> runnable =
+      new InitRunnable(webSocket->mImpl, aUrl, protocolArray,
+                       nsAutoCString(file.get()), lineno, aRv,
+                       &connectionFailed);
+    runnable->Dispatch(aGlobal.Context());
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
+  // We don't return an error if the connection just failed. Instead we dispatch
+  // an event.
+  if (connectionFailed) {
+    webSocket->mImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
+  }
+
+  // If we don't have a channel, the connection is failed and onerror() will be
+  // called asynchrounsly.
+  if (!webSocket->mImpl->mChannel) {
+    return webSocket.forget();
+  }
+
+  class MOZ_STACK_CLASS ClearWebSocket
+  {
+  public:
+    ClearWebSocket(WebSocketImpl* aWebSocketImpl)
+      : mWebSocketImpl(aWebSocketImpl)
+      , mDone(false)
+    {
+    }
+
+    void Done()
+    {
+       mDone = true;
+    }
+
+    ~ClearWebSocket()
+    {
+      if (!mDone) {
+        mWebSocketImpl->mChannel = nullptr;
+        mWebSocketImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
+      }
+    }
+
+    WebSocketImpl* mWebSocketImpl;
+    bool mDone;
+  };
+
+  ClearWebSocket cws(webSocket->mImpl);
+
+  // This operation must be done on the correct thread. The rest must run on the
+  // main-thread.
+  aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  if (NS_IsMainThread()) {
+    webSocket->mImpl->AsyncOpen(aRv);
+  } else {
+    nsRefPtr<AsyncOpenRunnable> runnable =
+      new AsyncOpenRunnable(webSocket->mImpl, aRv);
+    runnable->Dispatch(aGlobal.Context());
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  cws.Done();
   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) {
@@ -764,173 +1177,247 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_E
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
                                                DOMEventTargetHelper)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
                                                   DOMEventTargetHelper)
   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)
   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_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
 
 void
 WebSocket::DisconnectFromOwner()
 {
+  AssertIsOnMainThread();
   DOMEventTargetHelper::DisconnectFromOwner();
 
   if (mImpl) {
     mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
   }
 
   DontKeepAliveAnyMore();
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl:: initialization
 //-----------------------------------------------------------------------------
 
-nsresult
+void
 WebSocketImpl::Init(JSContext* aCx,
                     nsIPrincipal* aPrincipal,
                     const nsAString& aURL,
-                    nsTArray<nsString>& aProtocolArray)
+                    nsTArray<nsString>& aProtocolArray,
+                    const nsACString& aScriptFile,
+                    uint32_t aScriptLine,
+                    ErrorResult& aRv,
+                    bool* aConnectionFailed)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  AssertIsOnMainThread();
   MOZ_ASSERT(aPrincipal);
 
   // 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 = mParent->CheckInnerWindowCorrectness();
-  NS_ENSURE_SUCCESS(rv, rv);
+  aRv = mWebSocket->CheckInnerWindowCorrectness();
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
 
   // 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);
-  rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  unsigned lineno;
-  JS::AutoFilename file;
-  if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) {
-    mScriptFile = file.get();
-    mScriptLine = lineno;
+  if (!mWorkerPrivate) {
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (NS_WARN_IF(!os)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+
+    aRv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    aRv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+  }
+
+  if (mWorkerPrivate) {
+    mScriptFile = aScriptFile;
+    mScriptLine = aScriptLine;
+  } else {
+    unsigned lineno;
+    JS::AutoFilename file;
+    if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) {
+      mScriptFile = file.get();
+      mScriptLine = lineno;
+    }
   }
 
   // Get WindowID
   mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
 
   // parses the url
-  rv = ParseURL(PromiseFlatString(aURL));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsIScriptContext* sc = mParent->GetContextForEventHandlers(&rv);
-  NS_ENSURE_SUCCESS(rv, rv);
+  aRv = ParseURL(PromiseFlatString(aURL));
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  nsIScriptContext* sc = nullptr;
+  {
+    nsresult rv;
+    sc = mWebSocket->GetContextForEventHandlers(&rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRv.Throw(rv);
+      return;
+    }
+  }
 
   // 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
     // we were loaded from https.
     nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
     if (globalObject) {
       nsCOMPtr<nsIPrincipal> principal(globalObject->PrincipalOrNull());
       if (principal) {
         nsCOMPtr<nsIURI> uri;
         principal->GetURI(getter_AddRefs(uri));
         if (uri) {
           bool originIsHttps = false;
-          rv = uri->SchemeIs("https", &originIsHttps);
-          NS_ENSURE_SUCCESS(rv,rv);
+          aRv = uri->SchemeIs("https", &originIsHttps);
+          if (NS_WARN_IF(aRv.Failed())) {
+            return;
+          }
+
           if (originIsHttps) {
-            return NS_ERROR_DOM_SECURITY_ERR;
+            aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+            return;
           }
         }
       }
     }
   }
 
   // Assign the sub protocol list and scan it for illegal values
   for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
     for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
       if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
-          aProtocolArray[index][i] > static_cast<char16_t>(0x007E))
-        return NS_ERROR_DOM_SYNTAX_ERR;
+          aProtocolArray[index][i] > static_cast<char16_t>(0x007E)) {
+        aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+        return;
+      }
     }
 
     if (!mRequestedProtocolList.IsEmpty()) {
       mRequestedProtocolList.AppendLiteral(", ");
     }
 
     AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
   }
 
+  nsCOMPtr<nsIURI> uri;
+  {
+    nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
+
+    // We crash here because we are sure that mURI is a valid URI, so either we
+    // are OOM'ing or something else bad is happening.
+    if (NS_FAILED(rv)) {
+      MOZ_CRASH();
+    }
+  }
+
   // Check content policy.
   int16_t shouldLoad = nsIContentPolicy::ACCEPT;
   nsCOMPtr<nsIDocument> originDoc = nsContentUtils::GetDocumentFromScriptContext(sc);
   mOriginDocument = do_GetWeakReference(originDoc);
-  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
-                                 mURI,
-                                 mPrincipal,
-                                 originDoc,
-                                 EmptyCString(),
-                                 nullptr,
-                                 &shouldLoad,
-                                 nsContentUtils::GetContentPolicy(),
-                                 nsContentUtils::GetSecurityManager());
-  NS_ENSURE_SUCCESS(rv, rv);
+  aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
+                                  uri,
+                                  mPrincipal,
+                                  originDoc,
+                                  EmptyCString(),
+                                  nullptr,
+                                  &shouldLoad,
+                                  nsContentUtils::GetContentPolicy(),
+                                  nsContentUtils::GetSecurityManager());
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
   if (NS_CP_REJECTED(shouldLoad)) {
     // Disallowed by content policy.
-    return NS_ERROR_CONTENT_BLOCKED;
+    aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
+    return;
   }
 
   // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
-  // url parameter, so don't throw if EstablishConnection fails, and call
+  // url parameter, so don't throw if InitializeConnection fails, and call
   // onerror/onclose asynchronously
-  if (NS_FAILED(EstablishConnection())) {
-    FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
+  if (NS_FAILED(InitializeConnection())) {
+    *aConnectionFailed = true;
+  } else {
+    *aConnectionFailed = false;
   }
-
-  return NS_OK;
+}
+
+void
+WebSocketImpl::AsyncOpen(ErrorResult& aRv)
+{
+  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+
+  nsCString asciiOrigin;
+  aRv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  ToLowerCase(asciiOrigin);
+
+  nsCOMPtr<nsIURI> uri;
+  aRv = NS_NewURI(getter_AddRefs(uri), mURI);
+  MOZ_ASSERT(!aRv.Failed());
+
+  aRv = mChannel->AsyncOpen(uri, asciiOrigin, this, nullptr);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl methods:
 //-----------------------------------------------------------------------------
 
-class nsAutoCloseWS
+class nsAutoCloseWS MOZ_FINAL
 {
 public:
   explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
     : mWebSocketImpl(aWebSocketImpl)
   {}
 
   ~nsAutoCloseWS()
   {
@@ -938,37 +1425,34 @@ public:
       mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
     }
   }
 private:
   nsRefPtr<WebSocketImpl> mWebSocketImpl;
 };
 
 nsresult
-WebSocketImpl::EstablishConnection()
+WebSocketImpl::InitializeConnection()
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  AssertIsOnMainThread();
   NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null");
 
   nsCOMPtr<nsIWebSocketChannel> wsChannel;
   nsAutoCloseWS autoClose(this);
   nsresult rv;
 
   if (mSecure) {
     wsChannel =
       do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
   } else {
     wsChannel =
       do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = wsChannel->SetNotificationCallbacks(this);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // add ourselves to the document's load group and
   // provide the http stack the loadgroup info too
   nsCOMPtr<nsILoadGroup> loadGroup;
   rv = GetLoadGroup(getter_AddRefs(loadGroup));
   if (loadGroup) {
     rv = wsChannel->SetLoadGroup(loadGroup);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = loadGroup->AddRequest(this, nullptr);
@@ -986,58 +1470,59 @@ WebSocketImpl::EstablishConnection()
   rv = wsChannel->SetLoadInfo(loadInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!mRequestedProtocolList.IsEmpty()) {
     rv = wsChannel->SetProtocol(mRequestedProtocolList);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  nsCString asciiOrigin;
-  rv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  ToLowerCase(asciiOrigin);
-
-  rv = wsChannel->AsyncOpen(mURI, asciiOrigin, this, nullptr);
+  nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
+  NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);
+
+  rv = rr->RetargetDeliveryTo(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mChannel = wsChannel;
 
   return NS_OK;
 }
 
 void
 WebSocketImpl::DispatchConnectionCloseEvents()
 {
-  mReadyState = WebSocket::CLOSED;
+  AssertIsOnTargetThread();
+  SetReadyState(WebSocket::CLOSED);
 
   // Call 'onerror' if needed
   if (mFailed) {
     nsresult rv =
-      mParent->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
+      mWebSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to dispatch the error event");
     }
   }
 
-  nsresult rv = mParent->CreateAndDispatchCloseEvent(mCloseEventWasClean,
-                                                     mCloseEventCode,
-                                                     mCloseEventReason);
+  nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mCloseEventWasClean,
+                                                        mCloseEventCode,
+                                                        mCloseEventReason);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch the close event");
   }
 
-  mParent->UpdateMustKeepAlive();
+  mWebSocket->UpdateMustKeepAlive();
   Disconnect();
 }
 
 nsresult
 WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName)
 {
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
+
   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);
@@ -1048,51 +1533,70 @@ WebSocket::CreateAndDispatchSimpleEvent(
 
   event->SetTrusted(true);
 
   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
 nsresult
 WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
-                                         bool isBinary)
+                                         bool aIsBinary)
 {
   MOZ_ASSERT(mImpl);
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  mImpl->AssertIsOnTargetThread();
+
+  if (NS_IsMainThread()) {
+    AutoJSAPI jsapi;
+    if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    return CreateAndDispatchMessageEvent(jsapi.cx(), aData, aIsBinary);
+  }
+
+  MOZ_ASSERT(mWorkerPrivate);
+  return CreateAndDispatchMessageEvent(mWorkerPrivate->GetJSContext(), aData,
+                                       aIsBinary);
+}
+
+nsresult
+WebSocket::CreateAndDispatchMessageEvent(JSContext* aCx,
+                                         const nsACString& aData,
+                                         bool aIsBinary)
+{
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
 
   nsresult rv = CheckInnerWindowCorrectness();
-  if (NS_FAILED(rv))
+  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) {
+  JS::Rooted<JS::Value> jsData(aCx);
+  if (aIsBinary) {
     if (mImpl->mBinaryType == dom::BinaryType::Blob) {
-      rv = nsContentUtils::CreateBlobBuffer(cx, GetOwner(), aData, &jsData);
+      nsresult rv = nsContentUtils::CreateBlobBuffer(aCx, GetOwner(), aData,
+                                                     &jsData);
       NS_ENSURE_SUCCESS(rv, rv);
     } else if (mImpl->mBinaryType == dom::BinaryType::Arraybuffer) {
-      JS::Rooted<JSObject*> arrayBuf(cx);
-      rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
+      JS::Rooted<JSObject*> arrayBuf(aCx);
+      nsresult rv = nsContentUtils::CreateArrayBuffer(aCx, aData,
+                                                      arrayBuf.address());
       NS_ENSURE_SUCCESS(rv, rv);
       jsData = OBJECT_TO_JSVAL(arrayBuf);
     } else {
       NS_RUNTIMEABORT("Unknown binary type!");
       return NS_ERROR_UNEXPECTED;
     }
   } else {
     // JS string
     NS_ConvertUTF8toUTF16 utf16Data(aData);
     JSString* jsString;
-    jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
+    jsString = JS_NewUCStringCopyN(aCx, utf16Data.get(), utf16Data.Length());
     NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
 
     jsData = STRING_TO_JSVAL(jsString);
   }
 
   // create an event that uses the MessageEvent interface,
   // which does not bubble, is not cancelable, and has no default action
 
@@ -1113,17 +1617,18 @@ WebSocket::CreateAndDispatchMessageEvent
   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
 nsresult
 WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
                                        uint16_t aCode,
                                        const nsAString &aReason)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
   CloseEventInit init;
   init.mBubbles = false;
@@ -1134,25 +1639,66 @@ WebSocket::CreateAndDispatchCloseEvent(b
 
   nsRefPtr<CloseEvent> event =
     CloseEvent::Constructor(this, NS_LITERAL_STRING("close"), init);
   event->SetTrusted(true);
 
   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
+namespace {
+
+class PrefEnabledRunnable MOZ_FINAL : public WorkerMainThreadRunnable
+{
+public:
+  PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate)
+    : WorkerMainThreadRunnable(aWorkerPrivate)
+    , mEnabled(false)
+  { }
+
+  bool MainThreadRun() MOZ_OVERRIDE
+  {
+    AssertIsOnMainThread();
+    mEnabled = WebSocket::PrefEnabled(nullptr, nullptr);
+    return true;
+  }
+
+  bool IsEnabled() const
+  {
+    return mEnabled;
+  }
+
+private:
+  bool mEnabled;
+};
+
+} // anonymous namespace
+
 bool
-WebSocket::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
+WebSocket::PrefEnabled(JSContext* /* aCx */, JSObject* /* aGlobal */)
 {
-  return Preferences::GetBool("network.websocket.enabled", true);
+  if (NS_IsMainThread()) {
+    return Preferences::GetBool("network.websocket.enabled", true);
+  } else {
+    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+
+    nsRefPtr<PrefEnabledRunnable> runnable =
+      new PrefEnabledRunnable(workerPrivate);
+    runnable->Dispatch(workerPrivate->GetJSContext());
+
+    return runnable->IsEnabled();
+  }
 }
 
 nsresult
 WebSocketImpl::ParseURL(const nsAString& aURL)
 {
+  AssertIsOnMainThread();
   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);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
@@ -1215,38 +1761,44 @@ WebSocketImpl::ParseURL(const nsAString&
   for (i = 0; i < length; ++i) {
     if (mResource[i] < static_cast<char16_t>(0x0021) ||
         mResource[i] > static_cast<char16_t>(0x007E)) {
       return NS_ERROR_DOM_SYNTAX_ERR;
     }
   }
 
   mOriginalURL = aURL;
-  mURI = parsedURL;
+
+  rv = parsedURL->GetSpec(mURI);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // Methods that keep alive the WebSocket object when:
 //   1. the object has registered event listeners that can be triggered
 //      ("strong event listeners");
 //   2. there are outgoing not sent messages.
 //-----------------------------------------------------------------------------
 
 void
 WebSocket::UpdateMustKeepAlive()
 {
+  // Here we could not have mImpl.
+  MOZ_ASSERT(NS_IsMainThread() == !mWorkerPrivate);
+
   if (!mCheckMustKeepAlive || !mImpl) {
     return;
   }
 
   bool shouldKeepAlive = false;
 
   if (mListenerManager) {
-    switch (mImpl->mReadyState)
+    switch (mImpl->ReadyState())
     {
       case WebSocket::CONNECTING:
       {
         if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
           shouldKeepAlive = true;
@@ -1270,118 +1822,219 @@ WebSocket::UpdateMustKeepAlive()
       {
         shouldKeepAlive = false;
       }
     }
   }
 
   if (mKeepingAlive && !shouldKeepAlive) {
     mKeepingAlive = false;
-    mImpl->Release();
+    mImpl->ReleaseObject();
   } else if (!mKeepingAlive && shouldKeepAlive) {
     mKeepingAlive = true;
-    mImpl->AddRef();
+    mImpl->AddRefObject();
   }
 }
 
 void
 WebSocket::DontKeepAliveAnyMore()
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
+  // Here we could not have mImpl.
+  MOZ_ASSERT(NS_IsMainThread() == !mWorkerPrivate);
+
   if (mKeepingAlive) {
+    MOZ_ASSERT(mImpl);
+
     mKeepingAlive = false;
-    mImpl->Release();
+    mImpl->ReleaseObject();
+  }
+
+  mCheckMustKeepAlive = false;
+}
+
+namespace {
+
+class WebSocketWorkerFeature MOZ_FINAL : public WorkerFeature
+{
+public:
+  explicit WebSocketWorkerFeature(WebSocketImpl* aWebSocketImpl)
+    : mWebSocketImpl(aWebSocketImpl)
+  {
+  }
+
+  bool Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(aStatus > workers::Running);
+
+    if (aStatus >= Canceling) {
+      mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+      mWebSocketImpl->UnregisterFeature();
+    }
+
+    return true;
+  }
+
+  bool Suspend(JSContext* aCx)
+  {
+    mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+    mWebSocketImpl->UnregisterFeature();
+    return true;
   }
-  mCheckMustKeepAlive = false;
+
+private:
+  WebSocketImpl* mWebSocketImpl;
+};
+
+} // anonymous namespace
+
+void
+WebSocketImpl::AddRefObject()
+{
+  AssertIsOnTargetThread();
+  AddRef();
+
+  if (mWorkerPrivate && !mWorkerFeature) {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(!mWorkerFeature);
+    mWorkerFeature = new WebSocketWorkerFeature(this);
+
+    JSContext* cx = GetCurrentThreadJSContext();
+    if (!mWorkerPrivate->AddFeature(cx, mWorkerFeature)) {
+      NS_WARNING("Failed to register a feature.");
+      mWorkerFeature = nullptr;
+    }
+  }
+}
+
+void
+WebSocketImpl::ReleaseObject()
+{
+  AssertIsOnTargetThread();
+
+  if (mWorkerPrivate && !mWorkerFeature) {
+    UnregisterFeature();
+  }
+
+  Release();
+}
+
+void
+WebSocketImpl::UnregisterFeature()
+{
+  MOZ_ASSERT(mWorkerPrivate);
+  mWorkerPrivate->AssertIsOnWorkerThread();
+  MOZ_ASSERT(mWorkerFeature);
+
+  JSContext* cx = GetCurrentThreadJSContext();
+  mWorkerPrivate->RemoveFeature(cx, mWorkerFeature);
+  mWorkerFeature = nullptr;
 }
 
 nsresult
 WebSocketImpl::UpdateURI()
 {
+  AssertIsOnTargetThread();
+
   // Check for Redirections
   nsRefPtr<BaseWebSocketChannel> channel;
   channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
   MOZ_ASSERT(channel);
 
   channel->GetEffectiveURL(mEffectiveURL);
   mSecure = channel->IsEncrypted();
 
   return NS_OK;
 }
 
 void
 WebSocket::EventListenerAdded(nsIAtom* aType)
 {
+  AssertIsOnMainThread();
   UpdateMustKeepAlive();
 }
 
 void
 WebSocket::EventListenerRemoved(nsIAtom* aType)
 {
+  AssertIsOnMainThread();
   UpdateMustKeepAlive();
 }
 
 //-----------------------------------------------------------------------------
 // WebSocket - methods
 //-----------------------------------------------------------------------------
 
 // webIDL: readonly attribute unsigned short readyState;
 uint16_t
 WebSocket::ReadyState() const
 {
+  MOZ_ASSERT(mImpl);
   return mImpl->ReadyState();
 }
 
 // webIDL: readonly attribute unsigned long bufferedAmount;
 uint32_t
 WebSocket::BufferedAmount() const
 {
+  MOZ_ASSERT(mImpl);
   return mImpl->BufferedAmount();
 }
 
 // webIDL: attribute BinaryType binaryType;
 dom::BinaryType
 WebSocket::BinaryType() const
 {
+  MOZ_ASSERT(mImpl);
   return mImpl->BinaryType();
 }
 
 // webIDL: attribute BinaryType binaryType;
 void
 WebSocket::SetBinaryType(dom::BinaryType aData)
 {
+  MOZ_ASSERT(mImpl);
   mImpl->SetBinaryType(aData);
 }
 
 // webIDL: readonly attribute DOMString url
 void
 WebSocket::GetUrl(nsAString& aURL)
 {
+  MOZ_ASSERT(mImpl);
   mImpl->GetUrl(aURL);
 }
 
 // webIDL: readonly attribute DOMString extensions;
 void
 WebSocket::GetExtensions(nsAString& aExtensions)
 {
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
+
   CopyUTF8toUTF16(mImpl->mEstablishedExtensions, aExtensions);
 }
 
 // webIDL: readonly attribute DOMString protocol;
 void
 WebSocket::GetProtocol(nsAString& aProtocol)
 {
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
+
   CopyUTF8toUTF16(mImpl->mEstablishedProtocol, aProtocol);
 }
 
 // webIDL: void send(DOMString data);
 void
 WebSocket::Send(const nsAString& aData,
                 ErrorResult& aRv)
 {
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
+
   NS_ConvertUTF16toUTF8 msgString(aData);
   mImpl->Send(nullptr, msgString, msgString.Length(), false, aRv);
 }
 
 void
 WebSocket::Send(File& aData, ErrorResult& aRv)
 {
   nsCOMPtr<nsIInputStream> msgStream;
@@ -1405,34 +2058,36 @@ WebSocket::Send(File& aData, ErrorResult
 
   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");
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
 
   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);
   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");
+  MOZ_ASSERT(mImpl);
+  mImpl->AssertIsOnTargetThread();
 
   aData.ComputeLengthAndData();
 
   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
 
   uint32_t len = aData.Length();
   char* data = reinterpret_cast<char*>(aData.Data());
 
@@ -1442,30 +2097,33 @@ WebSocket::Send(const ArrayBufferView& a
 
 void
 WebSocketImpl::Send(nsIInputStream* aMsgStream,
                     const nsACString& aMsgString,
                     uint32_t aMsgLength,
                     bool aIsBinary,
                     ErrorResult& aRv)
 {
-  if (mReadyState == WebSocket::CONNECTING) {
+  AssertIsOnTargetThread();
+
+  int64_t readyState = ReadyState();
+  if (readyState == WebSocket::CONNECTING) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Always increment outgoing buffer len, even if closed
   mOutgoingBufferedAmount += aMsgLength;
 
-  if (mReadyState == WebSocket::CLOSING ||
-      mReadyState == WebSocket::CLOSED) {
+  if (readyState == WebSocket::CLOSING ||
+      readyState == WebSocket::CLOSED) {
     return;
   }
 
-  MOZ_ASSERT(mReadyState == WebSocket::OPEN,
+  MOZ_ASSERT(readyState == WebSocket::OPEN,
              "Unknown state in WebSocket::Send");
 
   nsresult rv;
   if (aMsgStream) {
     rv = mChannel->SendBinaryStream(aMsgStream, aMsgLength);
   } else {
     if (aIsBinary) {
       rv = mChannel->SendBinaryMsg(aMsgString);
@@ -1474,34 +2132,35 @@ WebSocketImpl::Send(nsIInputStream* aMsg
     }
   }
 
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
-  mParent->UpdateMustKeepAlive();
+  mWebSocket->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)
 {
+  MOZ_ASSERT(mImpl);
   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");
+  AssertIsOnTargetThread();
 
   // 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;
     }
@@ -1514,46 +2173,50 @@ WebSocketImpl::Close(const Optional<uint
 
     // The API requires the UTF-8 string to be 123 or less bytes
     if (closeReason.Length() > 123) {
       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
       return;
     }
   }
 
-  if (mReadyState == WebSocket::CLOSING ||
-      mReadyState == WebSocket::CLOSED) {
+  int64_t readyState = ReadyState();
+  if (readyState == WebSocket::CLOSING ||
+      readyState == WebSocket::CLOSED) {
     return;
   }
 
-  if (mReadyState == WebSocket::CONNECTING) {
+  if (readyState == WebSocket::CONNECTING) {
     FailConnection(closeCode, closeReason);
     return;
   }
 
-  MOZ_ASSERT(mReadyState == WebSocket::OPEN);
+  MOZ_ASSERT(readyState == WebSocket::OPEN);
   CloseConnection(closeCode, closeReason);
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl::nsIObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 WebSocketImpl::Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const char16_t* aData)
 {
-  if ((mReadyState == WebSocket::CLOSING) ||
-      (mReadyState == WebSocket::CLOSED)) {
+  AssertIsOnMainThread();
+
+  int64_t readyState = ReadyState();
+  if ((readyState == WebSocket::CLOSING) ||
+      (readyState == WebSocket::CLOSED)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
-  if (!mParent->GetOwner() || window != mParent->GetOwner()) {
+  if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
     return NS_OK;
   }
 
   if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
       (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
   {
     CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
   }
@@ -1563,92 +2226,261 @@ WebSocketImpl::Observe(nsISupports* aSub
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl::nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 WebSocketImpl::GetName(nsACString& aName)
 {
+  AssertIsOnMainThread();
+
   CopyUTF16toUTF8(mOriginalURL, aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::IsPending(bool* aValue)
 {
-  *aValue = (mReadyState != WebSocket::CLOSED);
+  AssertIsOnTargetThread();
+
+  int64_t readyState = ReadyState();
+  *aValue = (readyState != WebSocket::CLOSED);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::GetStatus(nsresult* aStatus)
 {
+  AssertIsOnTargetThread();
+
   *aStatus = NS_OK;
   return NS_OK;
 }
 
+namespace {
+
+class CancelRunnable MOZ_FINAL : public WorkerRunnable
+{
+public:
+  CancelRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    , mImpl(aImpl)
+  {
+  }
+
+  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
+    return !NS_FAILED(mImpl->CancelInternal());
+  }
+
+  void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+  {
+    aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
+  }
+
+  bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    return true;
+  }
+
+  void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult)
+  {
+  }
+
+private:
+  nsRefPtr<WebSocketImpl> mImpl;
+};
+
+} // anonymous namespace
+
 // Window closed, stop/reload button pressed, user navigated away from page, etc.
 NS_IMETHODIMP
 WebSocketImpl::Cancel(nsresult aStatus)
 {
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
-  if (mReadyState == WebSocket::CLOSING || mReadyState == WebSocket::CLOSED) {
+  AssertIsOnMainThread();
+
+  if (mWorkerPrivate) {
+    nsRefPtr<CancelRunnable> runnable =
+      new CancelRunnable(mWorkerPrivate, this);
+    if (!runnable->Dispatch(nullptr)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    return NS_OK;
+  }
+
+  return CancelInternal();
+}
+
+nsresult
+WebSocketImpl::CancelInternal()
+{
+  AssertIsOnTargetThread();
+
+  int64_t readyState = ReadyState();
+  if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
   ConsoleError();
 
   return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
 }
 
 NS_IMETHODIMP
 WebSocketImpl::Suspend()
 {
+  AssertIsOnMainThread();
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::Resume()
 {
+  AssertIsOnMainThread();
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup)
 {
+  AssertIsOnMainThread();
+
   *aLoadGroup = nullptr;
 
-  nsresult rv;
-  nsIScriptContext* sc = mParent->GetContextForEventHandlers(&rv);
-  nsCOMPtr<nsIDocument> doc =
-    nsContentUtils::GetDocumentFromScriptContext(sc);
-
+  if (!mWorkerPrivate) {
+    nsresult rv;
+    nsIScriptContext* sc = mWebSocket->GetContextForEventHandlers(&rv);
+    nsCOMPtr<nsIDocument> doc =
+      nsContentUtils::GetDocumentFromScriptContext(sc);
+
+    if (doc) {
+      *aLoadGroup = doc->GetDocumentLoadGroup().take();
+    }
+
+    return NS_OK;
+  }
+
+  // Walk up to our containing page
+  WorkerPrivate* wp = mWorkerPrivate;
+  while (wp->GetParent()) {
+    wp = wp->GetParent();
+  }
+
+  nsPIDOMWindow* window = wp->GetWindow();
+  if (!window) {
+    return NS_OK;
+  }
+
+  nsIDocument* doc = window->GetExtantDoc();
   if (doc) {
     *aLoadGroup = doc->GetDocumentLoadGroup().take();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup)
 {
+  AssertIsOnMainThread();
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags)
 {
+  AssertIsOnMainThread();
+
   *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags)
 {
+  AssertIsOnMainThread();
+
   // we won't change the load flags at all.
   return NS_OK;
 }
 
+namespace {
+
+class WorkerRunnableDispatcher MOZ_FINAL : public WorkerRunnable
+{
+public:
+  WorkerRunnableDispatcher(WorkerPrivate* aWorkerPrivate, nsIRunnable* aEvent)
+    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    , mEvent(aEvent)
+  {
+  }
+
+  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
+    return !NS_FAILED(mEvent->Run());
+  }
+
+  void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+  {
+    aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
+  }
+
+  bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    return true;
+  }
+
+  void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult)
+  {
+  }
+
+private:
+  nsCOMPtr<nsIRunnable> mEvent;
+};
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+WebSocketImpl::Dispatch(nsIRunnable* aEvent, uint32_t aFlags)
+{
+  // If the target is the main-thread we can just dispatch the runnable.
+  if (!mWorkerPrivate) {
+    return NS_DispatchToMainThread(aEvent);
+  }
+
+  // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
+  // runnable.
+  nsRefPtr<WorkerRunnableDispatcher> event =
+    new WorkerRunnableDispatcher(mWorkerPrivate, aEvent);
+  if (!event->Dispatch(nullptr)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::IsOnCurrentThread(bool* aResult)
+{
+  *aResult = IsTargetThread();
+  return NS_OK;
+}
+
+bool
+WebSocketImpl::IsTargetThread() const
+{
+  return NS_IsMainThread() == !mWorkerPrivate;
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/content/base/src/WebSocket.h
+++ b/content/base/src/WebSocket.h
@@ -21,16 +21,20 @@
 #include "nsWrapperCache.h"
 
 #define DEFAULT_WS_SCHEME_PORT  80
 #define DEFAULT_WSS_SCHEME_PORT 443
 
 namespace mozilla {
 namespace dom {
 
+namespace workers {
+class WorkerPrivate;
+}
+
 class File;
 
 class WebSocketImpl;
 
 class WebSocket MOZ_FINAL : public DOMEventTargetHelper
 {
   friend class WebSocketImpl;
 
@@ -51,17 +55,17 @@ public:
   virtual void EventListenerAdded(nsIAtom* aType) MOZ_OVERRIDE;
   virtual void EventListenerRemoved(nsIAtom* aType) MOZ_OVERRIDE;
 
   virtual void DisconnectFromOwner() MOZ_OVERRIDE;
 
   // nsWrapperCache
   nsPIDOMWindow* GetParentObject() { return GetOwner(); }
 
-  virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE;
+  virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
 public: // static helpers:
 
   // Determine if preferences allow WebSocket
   static bool PrefEnabled(JSContext* aCx = nullptr, JSObject* aGlobal = nullptr);
 
 public: // WebIDL interface:
 
@@ -128,37 +132,44 @@ public: // WebIDL interface:
 
 private: // constructor && distructor
   explicit WebSocket(nsPIDOMWindow* aOwnerWindow);
   virtual ~WebSocket();
 
   // These methods actually do the dispatch for various events.
   nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
   nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
-                                         bool isBinary);
+                                         bool aIsBinary);
+  nsresult CreateAndDispatchMessageEvent(JSContext* aCx,
+                                         const nsACString& aData,
+                                         bool aIsBinary);
   nsresult CreateAndDispatchCloseEvent(bool aWasClean,
                                        uint16_t aCode,
                                        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();
 
 private:
   WebSocket(const WebSocket& x) MOZ_DELETE;   // prevent bad usage
   WebSocket& operator=(const WebSocket& x) MOZ_DELETE;
 
-  // Raw pointer because this WebSocketImpl is created, managed an destroyed by
+  // Raw pointer because this WebSocketImpl is created, managed and destroyed by
   // WebSocket.
   WebSocketImpl* mImpl;
 
+  // This is used just to check in which thread this object is used when mImpl
+  // is null.
+  workers::WorkerPrivate* mWorkerPrivate;
+
   bool mKeepingAlive;
   bool mCheckMustKeepAlive;
 };
 
 } //namespace dom
 } //namespace mozilla
 
 #endif
--- a/content/base/src/moz.build
+++ b/content/base/src/moz.build
@@ -76,16 +76,17 @@ EXPORTS.mozilla.dom += [
     'ImportManager.h',
     'Link.h',
     'NodeIterator.h',
     'ResponsiveImageSelector.h',
     'ShadowRoot.h',
     'StyleSheetList.h',
     'Text.h',
     'TreeWalker.h',
+    'WebSocket.h',
 ]
 
 UNIFIED_SOURCES += [
     'Attr.cpp',
     'ChildIterator.cpp',
     'Comment.cpp',
     'DirectionalityUtils.cpp',
     'DocumentFragment.cpp',
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1435,20 +1435,16 @@ DOMInterfaces = {
 
 'WebrtcGlobalInformation': {
     'nativeType': 'mozilla::dom::WebrtcGlobalInformation',
     'headerFile': 'WebrtcGlobalInformation.h',
     'wrapperCache': False,
     'concrete': False,
 },
 
-'WebSocket': {
-    'headerFile': 'WebSocket.h',
-},
-
 'Window': {
     'nativeType': 'nsGlobalWindow',
     'binaryNames': {
         'postMessage': 'postMessageMoz',
     },
 },
 
 'WindowProxy': [
--- a/dom/webidl/WebSocket.webidl
+++ b/dom/webidl/WebSocket.webidl
@@ -8,16 +8,17 @@
  *
  * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and Opera Software ASA.
  * You are granted a license to use, reproduce and create derivative works of this document.
  */
 
 enum BinaryType { "blob", "arraybuffer" };
 
 [Func="mozilla::dom::WebSocket::PrefEnabled",
+ Exposed=(Window,Worker),
  Constructor(DOMString url),
  Constructor(DOMString url, DOMString protocols),
  Constructor(DOMString url, sequence<DOMString> protocols)]
 interface WebSocket : EventTarget {
 
   readonly attribute DOMString url;
 
   // ready state
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -122,16 +122,18 @@ var interfaceNamesInGlobalScope =
     "XMLHttpRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "XMLHttpRequestUpload",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URL",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "URLSearchParams",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "WebSocket",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "Worker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WorkerGlobalScope",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WorkerLocation",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WorkerNavigator",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -1185,18 +1185,24 @@ bool
 WebSocketChannel::IsEncrypted() const
 {
   return mEncrypted;
 }
 
 void
 WebSocketChannel::BeginOpen()
 {
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(
+      NS_NewRunnableMethod(this, &WebSocketChannel::BeginOpen),
+                           NS_DISPATCH_NORMAL);
+    return;
+  }
+
   LOG(("WebSocketChannel::BeginOpen() %p\n", this));
-  NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
 
   nsresult rv;
 
   // Important that we set CONNECTING_IN_PROGRESS before any call to
   // AbortSession here: ensures that any remaining queued connection(s) are
   // scheduled in OnStopSession
   mConnecting = CONNECTING_IN_PROGRESS;