From 80872a0825a93af4a9a2875f90f0b833d0e0f6c2 Mon Sep 17 00:00:00 2001 From: Jason Duell Date: Tue, 21 Feb 2012 14:57:10 -0800 Subject: [PATCH] Bug 696085: WebSocket connection opening across page loads r=smaug --- content/base/src/nsWebSocket.cpp | 75 +++++++++++++++++-- content/base/src/nsWebSocket.h | 6 ++ .../protocol/websocket/WebSocketChannel.cpp | 9 +-- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/content/base/src/nsWebSocket.cpp b/content/base/src/nsWebSocket.cpp index b5b53594db3..408d3621fe0 100644 --- a/content/base/src/nsWebSocket.cpp +++ b/content/base/src/nsWebSocket.cpp @@ -84,6 +84,7 @@ #include "nsDOMFile.h" #include "nsWrapperCacheInlines.h" #include "nsDOMEventTargetHelper.h" +#include "nsIObserverService.h" using namespace mozilla; @@ -92,7 +93,7 @@ using namespace mozilla; #define TRUE_OR_FAIL_WEBSOCKET(x, ret) \ PR_BEGIN_MACRO \ if (NS_UNLIKELY(!(x))) { \ - NS_WARNING("ENSURE_TRUE_AND_FAIL_IF_FAILED(" #x ") failed"); \ + NS_WARNING("TRUE_OR_FAIL_WEBSOCKET(" #x ") failed"); \ FailConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR); \ return ret; \ } \ @@ -227,10 +228,9 @@ nsWebSocket::FailConnection(PRUint16 aReasonCode, NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); ConsoleError(); - nsresult rv = CloseConnection(aReasonCode, aReasonString); - NS_ENSURE_SUCCESS(rv, rv); + CloseConnection(aReasonCode, aReasonString); - rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error")); + nsresult rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error")); if (NS_FAILED(rv)) NS_WARNING("Failed to dispatch the error event"); @@ -250,6 +250,12 @@ nsWebSocket::Disconnect() if (loadGroup) loadGroup->RemoveRequest(this, nsnull, NS_OK); + nsCOMPtr 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 kungfuDeathGrip = this; @@ -307,6 +313,13 @@ nsWebSocket::OnStart(nsISupports *aContext) if (mDisconnected) return NS_OK; + // Attempt to kill "ghost" websocket: but usually too early for check to fail + nsresult rv = CheckInnerWindowCorrectness(); + if (NS_FAILED(rv)) { + FailConnectionQuietly(); + return rv; + } + if (!mRequestedProtocolList.IsEmpty()) { mChannel->GetProtocol(mEstablishedProtocol); } @@ -405,7 +418,7 @@ nsWebSocket::GetInterface(const nsIID &aIID, void **aResult) return wwatch->GetPrompt(outerWindow, aIID, aResult); } - return NS_ERROR_UNEXPECTED; + return QueryInterface(aIID, aResult); } //////////////////////////////////////////////////////////////////////////////// @@ -499,6 +512,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsWebSocket) NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIRequest) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(WebSocket) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) @@ -1085,6 +1100,14 @@ nsWebSocket::DontKeepAliveAnyMore() mCheckMustKeepAlive = false; } +void +nsWebSocket::FailConnectionQuietly() +{ + // Fail without console error or JS onerror message: onmessage/onclose will + // also be blocked so long as CheckInnerWindowCorrectness is failing. + CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); +} + nsresult nsWebSocket::UpdateURI() { @@ -1496,6 +1519,19 @@ nsWebSocket::Init(nsIPrincipal* aPrincipal, mOwner = nsnull; } + // Attempt to kill "ghost" websocket: but usually too early for check to fail + rv = CheckInnerWindowCorrectness(); + NS_ENSURE_SUCCESS(rv, rv); + + // Shut down websocket if window is frozen or destroyed (only needed for + // "ghost" websockets--see bug 696085) + nsCOMPtr 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); + nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1"); JSContext* cx = nsnull; @@ -1571,6 +1607,35 @@ nsWebSocket::Init(nsIPrincipal* aPrincipal, return NS_OK; } +//----------------------------------------------------------------------------- +// nsWebSocket::nsIObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsWebSocket::Observe(nsISupports* aSubject, + const char* aTopic, + const PRUnichar* aData) +{ + if ((mReadyState == nsIWebSocket::CLOSING) || + (mReadyState == nsIWebSocket::CLOSED)) { + return NS_OK; + } + + nsCOMPtr window = do_QueryInterface(aSubject); + if (!mOwner || window != mOwner) { + return NS_OK; + } + + if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) || + (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)) + { + FailConnectionQuietly(); + } + + return NS_OK; +} + + //----------------------------------------------------------------------------- // nsWebSocket::nsIRequest //----------------------------------------------------------------------------- diff --git a/content/base/src/nsWebSocket.h b/content/base/src/nsWebSocket.h index bc4e56ebda4..c0e60d3b064 100644 --- a/content/base/src/nsWebSocket.h +++ b/content/base/src/nsWebSocket.h @@ -54,7 +54,9 @@ #include "nsIInterfaceRequestor.h" #include "nsIWebSocketChannel.h" #include "nsIWebSocketListener.h" +#include "nsIObserver.h" #include "nsIRequest.h" +#include "nsWeakReference.h" #define DEFAULT_WS_SCHEME_PORT 80 #define DEFAULT_WSS_SCHEME_PORT 443 @@ -74,6 +76,8 @@ class nsWebSocket: public nsDOMEventTargetHelper, public nsIJSNativeInitializer, public nsIInterfaceRequestor, public nsIWebSocketListener, + public nsIObserver, + public nsSupportsWeakReference, public nsIRequest { friend class nsWSCloseEvent; @@ -88,6 +92,7 @@ public: NS_DECL_NSIWEBSOCKET NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIWEBSOCKETLISTENER + NS_DECL_NSIOBSERVER NS_DECL_NSIREQUEST // nsIJSNativeInitializer @@ -114,6 +119,7 @@ protected: // These methods when called can release the WebSocket object nsresult FailConnection(PRUint16 reasonCode, const nsACString& aReasonString = EmptyCString()); + void FailConnectionQuietly(); nsresult CloseConnection(PRUint16 reasonCode, const nsACString& aReasonString = EmptyCString()); nsresult Disconnect(); diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index d3a3f18b207..35788663d57 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -2319,17 +2319,16 @@ WebSocketChannel::Close(PRUint16 code, const nsACString & reason) LOG(("WebSocketChannel::Close() %p\n", this)); NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); + if (mRequestedClose) { + return NS_OK; + } + if (!mTransport) { LOG(("WebSocketChannel::Close() without transport - aborting.")); AbortSession(NS_ERROR_NOT_CONNECTED); return NS_ERROR_NOT_CONNECTED; } - if (mRequestedClose) { - LOG(("WebSocketChannel:: Double close error\n")); - return NS_ERROR_UNEXPECTED; - } - // The API requires the UTF-8 string to be 123 or less bytes if (reason.Length() > 123) return NS_ERROR_ILLEGAL_VALUE;