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 209871 f23cea600bd00a7cce19a0a4bd178e8e4c9f81d1
parent 209870 e7950dac9a28912c5a7bacba0d3d06ad889c1b11
child 209872 8edaa32e2278b7f1a8835a4f85d9a7b0d4eb5b53
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerssmaug
bugs504553
milestone35.0a1
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;