Bug 1081686 - WebSocket can be still accessible once it is disconnected. r=smaug, a=sledru
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 14 Oct 2014 10:28:07 +0100
changeset 233337 fc795bf846a720719b3396b966d00eed5ceeb36a
parent 233336 a06058806b8bf998c3f3a132a8105cfcda0866b9
child 233338 e56868e0038b1ae55ac97e4cdafeb235e3427b4e
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, sledru
bugs1081686
milestone35.0a2
Bug 1081686 - WebSocket can be still accessible once it is disconnected. r=smaug, a=sledru
content/base/src/WebSocket.cpp
content/base/src/WebSocket.h
content/base/test/mochitest.ini
content/base/test/test_bug1081686.html
--- a/content/base/src/WebSocket.cpp
+++ b/content/base/src/WebSocket.cpp
@@ -78,99 +78,44 @@ public:
 
   WebSocketImpl(WebSocket* aWebSocket)
   : mWebSocket(aWebSocket)
   , mOnCloseScheduled(false)
   , mFailed(false)
   , mDisconnected(false)
   , mCloseEventWasClean(false)
   , mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL)
-  , 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());
   }
 
   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) const
-  {
-    AssertIsOnTargetThread();
-
-    if (mEffectiveURL.IsEmpty()) {
-      aURL = mOriginalURL;
-    } else {
-      aURL = mEffectiveURL;
-    }
-  }
-
-  void Close(const Optional<uint16_t>& aCode,
-             const Optional<nsAString>& aReason,
-             ErrorResult& aRv);
-
   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 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());
@@ -207,19 +152,16 @@ public:
   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
 
   bool mOnCloseScheduled;
   bool mFailed;
   bool mDisconnected;
 
   // Set attributes of DOM 'onclose' message
@@ -229,54 +171,41 @@ public:
 
   nsCString mAsciiHost;  // hostname
   uint32_t  mPort;
   nsCString mResource; // [filepath[?query]]
   nsString  mUTF16Origin;
 
   nsCString mURI;
   nsCString mRequestedProtocolList;
-  nsCString mEstablishedProtocol;
-  nsCString mEstablishedExtensions;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsWeakPtr              mOriginDocument;
 
-  uint32_t mOutgoingBufferedAmount;
-
-  dom::BinaryType mBinaryType;
-
   // Web Socket owner information:
   // - the script file name, UTF8 encoded.
   // - source code line number where the Web Socket object was constructed.
   // - the ID of the inner window where the script lives. Note that this may not
   //   be the same as the Web Socket owner window.
   // These attributes are used for error reporting.
   nsCString mScriptFile;
   uint32_t mScriptLine;
   uint64_t mInnerWindowID;
 
-  // 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,
@@ -445,25 +374,25 @@ private:
 } // anonymous namespace
 
 nsresult
 WebSocketImpl::CloseConnection(uint16_t aReasonCode,
                                const nsACString& aReasonString)
 {
   AssertIsOnTargetThread();
 
-  uint16_t readyState = ReadyState();
+  uint16_t readyState = mWebSocket->ReadyState();
   if (readyState == WebSocket::CLOSING ||
       readyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
   // The common case...
   if (mChannel) {
-    SetReadyState(WebSocket::CLOSING);
+    mWebSocket->SetReadyState(WebSocket::CLOSING);
 
     // The channel has to be closed on the main-thread.
 
     if (NS_IsMainThread()) {
       return mChannel->Close(aReasonCode, aReasonString);
     }
 
     nsRefPtr<CloseRunnable> runnable =
@@ -476,17 +405,17 @@ WebSocketImpl::CloseConnection(uint16_t 
   //
   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);
 
-  SetReadyState(WebSocket::CLOSING);
+  mWebSocket->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,
@@ -498,17 +427,17 @@ WebSocketImpl::CloseConnection(uint16_t 
 nsresult
 WebSocketImpl::ConsoleError()
 {
   AssertIsOnTargetThread();
 
   NS_ConvertUTF8toUTF16 specUTF16(mURI);
   const char16_t* formatStrings[] = { specUTF16.get() };
 
-  if (ReadyState() < WebSocket::OPEN) {
+  if (mWebSocket->ReadyState() < 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));
   }
@@ -616,17 +545,17 @@ WebSocketImpl::DisconnectInternal()
 // WebSocketImpl::nsIWebSocketListener methods:
 //-----------------------------------------------------------------------------
 
 nsresult
 WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
 {
   AssertIsOnTargetThread();
 
-  int16_t readyState = ReadyState();
+  int16_t readyState = mWebSocket->ReadyState();
   if (readyState == WebSocket::CLOSED) {
     NS_ERROR("Received message after CLOSED");
     return NS_ERROR_UNEXPECTED;
   }
 
   if (readyState == WebSocket::OPEN) {
     // Dispatch New Message
     nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
@@ -659,17 +588,17 @@ WebSocketImpl::OnBinaryMessageAvailable(
   return DoOnMessageAvailable(aMsg, true);
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnStart(nsISupports* aContext)
 {
   AssertIsOnTargetThread();
 
-  int16_t readyState = ReadyState();
+  int16_t readyState = mWebSocket->ReadyState();
 
   // This is the only function that sets OPEN, and should be called only once
   MOZ_ASSERT(readyState != WebSocket::OPEN,
              "readyState already OPEN! OnStart called twice?");
 
   // Nothing to do if we've already closed/closing
   if (readyState != WebSocket::CONNECTING) {
     return NS_OK;
@@ -678,23 +607,23 @@ WebSocketImpl::OnStart(nsISupports* aCon
   // Attempt to kill "ghost" websocket: but usually too early for check to fail
   nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
     return rv;
   }
 
   if (!mRequestedProtocolList.IsEmpty()) {
-    mChannel->GetProtocol(mEstablishedProtocol);
+    mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
   }
 
-  mChannel->GetExtensions(mEstablishedExtensions);
+  mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
   UpdateURI();
 
-  SetReadyState(WebSocket::OPEN);
+  mWebSocket->SetReadyState(WebSocket::OPEN);
 
   // Call 'onopen'
   rv = mWebSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch the open event");
   }
 
   mWebSocket->UpdateMustKeepAlive();
@@ -705,17 +634,17 @@ WebSocketImpl::OnStart(nsISupports* aCon
 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(ReadyState() != WebSocket::CLOSED,
+  MOZ_ASSERT(mWebSocket->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,
@@ -750,31 +679,31 @@ WebSocketImpl::ScheduleConnectionCloseEv
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
 {
   AssertIsOnTargetThread();
 
-  if (aSize > mOutgoingBufferedAmount) {
+  if (aSize > mWebSocket->mOutgoingBufferedAmount) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  mOutgoingBufferedAmount -= aSize;
+  mWebSocket->mOutgoingBufferedAmount -= aSize;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::OnServerClose(nsISupports *aContext, uint16_t aCode,
                              const nsACString &aReason)
 {
   AssertIsOnTargetThread();
 
-  int16_t readyState = ReadyState();
+  int16_t readyState = mWebSocket->ReadyState();
 
   MOZ_ASSERT(readyState != WebSocket::CONNECTING,
              "Received server close before connected?");
   MOZ_ASSERT(readyState != WebSocket::CLOSED,
              "Received server close after already closed!");
 
   // store code/string for onclose DOM event
   mCloseEventCode = aCode;
@@ -802,17 +731,17 @@ WebSocketImpl::OnServerClose(nsISupports
 // WebSocketImpl::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult)
 {
   AssertIsOnMainThread();
 
-  if (ReadyState() == WebSocket::CLOSED) {
+  if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
     return NS_ERROR_FAILURE;
   }
 
   if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
       aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
     nsresult rv;
     nsIScriptContext* sc = mWebSocket->GetContextForEventHandlers(&rv);
     nsCOMPtr<nsIDocument> doc =
@@ -836,16 +765,20 @@ WebSocketImpl::GetInterface(const nsIID&
 // WebSocket
 ////////////////////////////////////////////////////////////////////////////////
 
 WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
   : DOMEventTargetHelper(aOwnerWindow)
   , mWorkerPrivate(nullptr)
   , mKeepingAlive(false)
   , mCheckMustKeepAlive(true)
+  , mOutgoingBufferedAmount(0)
+  , mBinaryType(dom::BinaryType::Blob)
+  , mMutex("WebSocketImpl::mMutex")
+  , mReadyState(CONNECTING)
 {
   mImpl = new WebSocketImpl(this);
   mWorkerPrivate = mImpl->mWorkerPrivate;
 }
 
 WebSocket::~WebSocket()
 {
 }
@@ -1485,17 +1418,17 @@ WebSocketImpl::InitializeConnection()
 
   return NS_OK;
 }
 
 void
 WebSocketImpl::DispatchConnectionCloseEvents()
 {
   AssertIsOnTargetThread();
-  SetReadyState(WebSocket::CLOSED);
+  mWebSocket->SetReadyState(WebSocket::CLOSED);
 
   // Call 'onerror' if needed
   if (mFailed) {
     nsresult rv =
       mWebSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to dispatch the error event");
     }
@@ -1511,17 +1444,17 @@ WebSocketImpl::DispatchConnectionCloseEv
   mWebSocket->UpdateMustKeepAlive();
   Disconnect();
 }
 
 nsresult
 WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName)
 {
   MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  AssertIsOnTargetThread();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIDOMEvent> event;
   rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
@@ -1536,17 +1469,17 @@ WebSocket::CreateAndDispatchSimpleEvent(
   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
 nsresult
 WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
                                          bool aIsBinary)
 {
   MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  AssertIsOnTargetThread();
 
   AutoJSAPI jsapi;
 
   if (NS_IsMainThread()) {
     if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
       return NS_ERROR_FAILURE;
     }
   } else {
@@ -1560,31 +1493,31 @@ WebSocket::CreateAndDispatchMessageEvent
 }
 
 nsresult
 WebSocket::CreateAndDispatchMessageEvent(JSContext* aCx,
                                          const nsACString& aData,
                                          bool aIsBinary)
 {
   MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  AssertIsOnTargetThread();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
   // Create appropriate JS object for message
   JS::Rooted<JS::Value> jsData(aCx);
   if (aIsBinary) {
-    if (mImpl->mBinaryType == dom::BinaryType::Blob) {
+    if (mBinaryType == dom::BinaryType::Blob) {
       nsresult rv = nsContentUtils::CreateBlobBuffer(aCx, GetOwner(), aData,
                                                      &jsData);
       NS_ENSURE_SUCCESS(rv, rv);
-    } else if (mImpl->mBinaryType == dom::BinaryType::Arraybuffer) {
+    } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
       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;
@@ -1620,17 +1553,17 @@ WebSocket::CreateAndDispatchMessageEvent
 }
 
 nsresult
 WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
                                        uint16_t aCode,
                                        const nsAString &aReason)
 {
   MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  AssertIsOnTargetThread();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
   CloseEventInit init;
   init.mBubbles = false;
@@ -1762,17 +1695,17 @@ WebSocketImpl::ParseURL(const nsAString&
   uint32_t i;
   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;
+  mWebSocket->mOriginalURL = aURL;
 
   rv = parsedURL->GetSpec(mURI);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
@@ -1790,42 +1723,42 @@ WebSocket::UpdateMustKeepAlive()
 
   if (!mCheckMustKeepAlive || !mImpl) {
     return;
   }
 
   bool shouldKeepAlive = false;
 
   if (mListenerManager) {
-    switch (mImpl->ReadyState())
+    switch (ReadyState())
     {
-      case WebSocket::CONNECTING:
+      case CONNECTING:
       {
         if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
           shouldKeepAlive = true;
         }
       }
       break;
 
-      case WebSocket::OPEN:
-      case WebSocket::CLOSING:
+      case OPEN:
+      case CLOSING:
       {
         if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
             mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
-            mImpl->mOutgoingBufferedAmount != 0) {
+            mOutgoingBufferedAmount != 0) {
           shouldKeepAlive = true;
         }
       }
       break;
 
-      case WebSocket::CLOSED:
+      case CLOSED:
       {
         shouldKeepAlive = false;
       }
     }
   }
 
   if (mKeepingAlive && !shouldKeepAlive) {
     mKeepingAlive = false;
@@ -1935,17 +1868,17 @@ WebSocketImpl::UpdateURI()
 {
   AssertIsOnTargetThread();
 
   // Check for Redirections
   nsRefPtr<BaseWebSocketChannel> channel;
   channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
   MOZ_ASSERT(channel);
 
-  channel->GetEffectiveURL(mEffectiveURL);
+  channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
   mSecure = channel->IsEncrypted();
 
   return NS_OK;
 }
 
 void
 WebSocket::EventListenerAdded(nsIAtom* aType)
 {
@@ -1961,89 +1894,98 @@ WebSocket::EventListenerRemoved(nsIAtom*
 }
 
 //-----------------------------------------------------------------------------
 // WebSocket - methods
 //-----------------------------------------------------------------------------
 
 // webIDL: readonly attribute unsigned short readyState;
 uint16_t
-WebSocket::ReadyState() const
+WebSocket::ReadyState()
 {
-  MOZ_ASSERT(mImpl);
-  return mImpl->ReadyState();
+  MutexAutoLock lock(mMutex);
+  return mReadyState;
+}
+
+void
+WebSocket::SetReadyState(uint16_t aReadyState)
+{
+  MutexAutoLock lock(mMutex);
+  mReadyState = aReadyState;
 }
 
 // webIDL: readonly attribute unsigned long bufferedAmount;
 uint32_t
 WebSocket::BufferedAmount() const
 {
-  MOZ_ASSERT(mImpl);
-  return mImpl->BufferedAmount();
+  AssertIsOnTargetThread();
+  return mOutgoingBufferedAmount;
 }
 
 // webIDL: attribute BinaryType binaryType;
 dom::BinaryType
 WebSocket::BinaryType() const
 {
-  MOZ_ASSERT(mImpl);
-  return mImpl->BinaryType();
+  AssertIsOnTargetThread();
+  return mBinaryType;
 }
 
 // webIDL: attribute BinaryType binaryType;
 void
 WebSocket::SetBinaryType(dom::BinaryType aData)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->SetBinaryType(aData);
+  AssertIsOnTargetThread();
+  mBinaryType = aData;
 }
 
 // webIDL: readonly attribute DOMString url
 void
 WebSocket::GetUrl(nsAString& aURL)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->GetUrl(aURL);
+  AssertIsOnTargetThread();
+
+  if (mEffectiveURL.IsEmpty()) {
+    aURL = mOriginalURL;
+  } else {
+    aURL = mEffectiveURL;
+  }
 }
 
 // webIDL: readonly attribute DOMString extensions;
 void
 WebSocket::GetExtensions(nsAString& aExtensions)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
-
-  CopyUTF8toUTF16(mImpl->mEstablishedExtensions, aExtensions);
+  AssertIsOnTargetThread();
+  CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
 }
 
 // webIDL: readonly attribute DOMString protocol;
 void
 WebSocket::GetProtocol(nsAString& aProtocol)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
-
-  CopyUTF8toUTF16(mImpl->mEstablishedProtocol, aProtocol);
+  AssertIsOnTargetThread();
+  CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
 }
 
 // webIDL: void send(DOMString data);
 void
 WebSocket::Send(const nsAString& aData,
                 ErrorResult& aRv)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  AssertIsOnTargetThread();
 
   NS_ConvertUTF16toUTF8 msgString(aData);
-  mImpl->Send(nullptr, msgString, msgString.Length(), false, aRv);
+  Send(nullptr, msgString, msgString.Length(), false, aRv);
 }
 
 void
 WebSocket::Send(File& aData, ErrorResult& aRv)
 {
+  AssertIsOnTargetThread();
+
   nsCOMPtr<nsIInputStream> msgStream;
   nsresult rv = aData.GetInternalStream(getter_AddRefs(msgStream));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   uint64_t msgLength;
@@ -2053,115 +1995,105 @@ WebSocket::Send(File& aData, ErrorResult
     return;
   }
 
   if (msgLength > UINT32_MAX) {
     aRv.Throw(NS_ERROR_FILE_TOO_BIG);
     return;
   }
 
-  mImpl->Send(msgStream, EmptyCString(), msgLength, true, aRv);
+  Send(msgStream, EmptyCString(), msgLength, true, aRv);
 }
 
 void
 WebSocket::Send(const ArrayBuffer& aData,
                 ErrorResult& aRv)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  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);
+  Send(nullptr, msgString, len, true, aRv);
 }
 
 void
 WebSocket::Send(const ArrayBufferView& aData,
                 ErrorResult& aRv)
 {
-  MOZ_ASSERT(mImpl);
-  mImpl->AssertIsOnTargetThread();
+  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);
+  Send(nullptr, msgString, len, true, aRv);
 }
 
 void
-WebSocketImpl::Send(nsIInputStream* aMsgStream,
-                    const nsACString& aMsgString,
-                    uint32_t aMsgLength,
-                    bool aIsBinary,
-                    ErrorResult& aRv)
+WebSocket::Send(nsIInputStream* aMsgStream,
+                const nsACString& aMsgString,
+                uint32_t aMsgLength,
+                bool aIsBinary,
+                ErrorResult& aRv)
 {
   AssertIsOnTargetThread();
 
   int64_t readyState = ReadyState();
-  if (readyState == WebSocket::CONNECTING) {
+  if (readyState == CONNECTING) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Always increment outgoing buffer len, even if closed
   mOutgoingBufferedAmount += aMsgLength;
 
-  if (readyState == WebSocket::CLOSING ||
-      readyState == WebSocket::CLOSED) {
+  if (readyState == CLOSING ||
+      readyState == CLOSED) {
     return;
   }
 
-  MOZ_ASSERT(readyState == WebSocket::OPEN,
-             "Unknown state in WebSocket::Send");
+  // We must have mImpl when connected.
+  MOZ_ASSERT(mImpl);
+  MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
 
   nsresult rv;
   if (aMsgStream) {
-    rv = mChannel->SendBinaryStream(aMsgStream, aMsgLength);
+    rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
   } else {
     if (aIsBinary) {
-      rv = mChannel->SendBinaryMsg(aMsgString);
+      rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
     } else {
-      rv = mChannel->SendMsg(aMsgString);
+      rv = mImpl->mChannel->SendMsg(aMsgString);
     }
   }
 
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
-  mWebSocket->UpdateMustKeepAlive();
+  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)
-{
   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;
@@ -2176,42 +2108,45 @@ 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;
     }
   }
 
   int64_t readyState = ReadyState();
-  if (readyState == WebSocket::CLOSING ||
-      readyState == WebSocket::CLOSED) {
+  if (readyState == CLOSING ||
+      readyState == CLOSED) {
     return;
   }
 
-  if (readyState == WebSocket::CONNECTING) {
-    FailConnection(closeCode, closeReason);
+  // If the webSocket is not closed we MUST have a mImpl.
+  MOZ_ASSERT(mImpl);
+
+  if (readyState == CONNECTING) {
+    mImpl->FailConnection(closeCode, closeReason);
     return;
   }
 
-  MOZ_ASSERT(readyState == WebSocket::OPEN);
-  CloseConnection(closeCode, closeReason);
+  MOZ_ASSERT(readyState == OPEN);
+  mImpl->CloseConnection(closeCode, closeReason);
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl::nsIObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 WebSocketImpl::Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const char16_t* aData)
 {
   AssertIsOnMainThread();
 
-  int64_t readyState = ReadyState();
+  int64_t readyState = mWebSocket->ReadyState();
   if ((readyState == WebSocket::CLOSING) ||
       (readyState == WebSocket::CLOSED)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
   if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
     return NS_OK;
@@ -2230,26 +2165,26 @@ WebSocketImpl::Observe(nsISupports* aSub
 // WebSocketImpl::nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 WebSocketImpl::GetName(nsACString& aName)
 {
   AssertIsOnMainThread();
 
-  CopyUTF16toUTF8(mOriginalURL, aName);
+  CopyUTF16toUTF8(mWebSocket->mOriginalURL, aName);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::IsPending(bool* aValue)
 {
   AssertIsOnTargetThread();
 
-  int64_t readyState = ReadyState();
+  int64_t readyState = mWebSocket->ReadyState();
   *aValue = (readyState != WebSocket::CLOSED);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebSocketImpl::GetStatus(nsresult* aStatus)
 {
   AssertIsOnTargetThread();
@@ -2318,17 +2253,17 @@ WebSocketImpl::Cancel(nsresult aStatus)
   return CancelInternal();
 }
 
 nsresult
 WebSocketImpl::CancelInternal()
 {
   AssertIsOnTargetThread();
 
-  int64_t readyState = ReadyState();
+  int64_t readyState = mWebSocket->ReadyState();
   if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
   ConsoleError();
 
   return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
 }
@@ -2479,10 +2414,16 @@ WebSocketImpl::IsOnCurrentThread(bool* a
 }
 
 bool
 WebSocketImpl::IsTargetThread() const
 {
   return NS_IsMainThread() == !mWorkerPrivate;
 }
 
+void
+WebSocket::AssertIsOnTargetThread() const
+{
+  MOZ_ASSERT(NS_IsMainThread() == !mWorkerPrivate);
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/content/base/src/WebSocket.h
+++ b/content/base/src/WebSocket.h
@@ -18,16 +18,18 @@
 #include "nsISupports.h"
 #include "nsISupportsUtils.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 
 #define DEFAULT_WS_SCHEME_PORT  80
 #define DEFAULT_WSS_SCHEME_PORT 443
 
+class nsIInputStream;
+
 namespace mozilla {
 namespace dom {
 
 namespace workers {
 class WorkerPrivate;
 }
 
 class File;
@@ -83,17 +85,17 @@ public: // WebIDL interface:
                                                  const nsAString& aUrl,
                                                  const Sequence<nsString>& aProtocols,
                                                  ErrorResult& rv);
 
   // webIDL: readonly attribute DOMString url
   void GetUrl(nsAString& aResult);
 
   // webIDL: readonly attribute unsigned short readyState;
-  uint16_t ReadyState() const;
+  uint16_t ReadyState();
 
   // webIDL: readonly attribute unsigned long bufferedAmount;
   uint32_t BufferedAmount() const;
 
   // webIDL: attribute Function? onopen;
   IMPL_EVENT_HANDLER(open)
 
   // webIDL: attribute Function? onerror;
@@ -129,16 +131,18 @@ public: // WebIDL interface:
             ErrorResult& aRv);
   void Send(const ArrayBufferView& aData,
             ErrorResult& aRv);
 
 private: // constructor && distructor
   explicit WebSocket(nsPIDOMWindow* aOwnerWindow);
   virtual ~WebSocket();
 
+  void SetReadyState(uint16_t aReadyState);
+
   // These methods actually do the dispatch for various events.
   nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
   nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
                                          bool aIsBinary);
   nsresult CreateAndDispatchMessageEvent(JSContext* aCx,
                                          const nsACString& aData,
                                          bool aIsBinary);
   nsresult CreateAndDispatchCloseEvent(bool aWasClean,
@@ -152,24 +156,47 @@ private: // constructor && distructor
   // 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;
 
+  void Send(nsIInputStream* aMsgStream,
+            const nsACString& aMsgString,
+            uint32_t aMsgLength,
+            bool aIsBinary,
+            ErrorResult& aRv);
+
+  void AssertIsOnTargetThread() const;
+
   // 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;
+
+  uint32_t mOutgoingBufferedAmount;
+
+  // related to the WebSocket constructor steps
+  nsString mOriginalURL;
+  nsString mEffectiveURL;   // after redirects
+  nsCString mEstablishedExtensions;
+  nsCString mEstablishedProtocol;
+
+  dom::BinaryType mBinaryType;
+
+  // This mutex protects mReadyState that is the only variable that is used in
+  // different threads.
+  mozilla::Mutex mMutex;
+
+  // This value should not be used directly but use ReadyState() instead.
+  uint16_t mReadyState;
 };
 
 } //namespace dom
 } //namespace mozilla
 
 #endif
--- a/content/base/test/mochitest.ini
+++ b/content/base/test/mochitest.ini
@@ -654,8 +654,10 @@ disabled = Disabled due to making the ha
 support-files = file_bug503473-frame.sjs
 [test_bug1011748.html]
 skip-if = buildapp == 'b2g' || e10s
 support-files = file_bug1011748_redirect.sjs file_bug1011748_OK.sjs
 [test_bug1025933.html]
 [test_element.matches.html]
 [test_user_select.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g'
+[test_bug1081686.html]
+skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s
copy from content/base/test/test_websocket_hello.html
copy to content/base/test/test_bug1081686.html
--- a/content/base/test/test_websocket_hello.html
+++ b/content/base/test/test_bug1081686.html
@@ -1,42 +1,63 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 -->
 <head>
-  <title>Basic websocket test</title>
+  <title>bug 1081686</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="testWebSocket()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472529">Mozilla Bug </a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 var ws;
 
+function forcegc()
+{
+  SpecialPowers.forceGC();
+  SpecialPowers.gc();
+  setTimeout(function()
+  {
+    SpecialPowers.gc();
+  }, 0);
+}
+
 function testWebSocket () {
   ws = new WebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket_hello");
   ws.onopen = function(e) {
     ws.send("data");
   }
   ws.onclose = function(e) {
+    forcegc();
+    setTimeout(function() {
+      is(ws.readyState, 3, 'WebSocket is closed');
+      is(ws.bufferedAmount, 0, 'WebSocket.bufferedAmount should be empty.');
+      is(ws.binaryType, 'blob', 'WebSocket.binaryType is blob');
+      ws.binaryType = 'arraybuffer';
+      is(ws.binaryType, 'arraybuffer', 'WebSocket.binaryType is arraybuffer');
+      is(ws.url, 'ws://mochi.test:8888/tests/content/base/test/file_websocket_hello', 'WebSocket.url is correct');
+      ws.close();
+      ws.send('foobar');
+      SimpleTest.finish();
+    }, 1000);
   }
+
   ws.onerror = function(e) {
     ok(false, "onerror called!");
     SimpleTest.finish();
   }
   ws.onmessage = function(e) {
     is(e.data, "Hello world!", "Wrong data");
     ws.close();
-    SimpleTest.finish();
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
 <div>