bug 674716 - websockets API close reason codes and messages r=sicking r=biesi sr=bz

--HG--
extra : rebase_source : f7a0eb6b310f29f8e697c26ff5e0ef9d2fde559a
This commit is contained in:
Patrick McManus 2011-08-03 15:15:25 -04:00
parent 2adea969f2
commit c9a6067c84
17 changed files with 427 additions and 93 deletions

View File

@ -58,7 +58,7 @@ class nsString;
* http://dev.w3.org/html5/websockets/
*
*/
[scriptable, uuid(d50eb158-30a1-4692-8664-fdd479fa24b3)]
[scriptable, uuid(8fa080c7-934a-4040-90cb-31055798b35d)]
interface nsIMozWebSocket : nsISupports
{
readonly attribute DOMString url;
@ -92,7 +92,8 @@ interface nsIMozWebSocket : nsISupports
* Closes the Web Socket connection or connection attempt, if any.
* If the connection is already closed, it does nothing.
*/
void close();
[optional_argc] void close([optional] in unsigned short code,
[optional] in DOMString reason);
/**
* Initialize the object for use from C++ code with the principal, script

View File

@ -390,7 +390,8 @@ nsWebSocketEstablishedConnection::Close()
return NS_OK;
}
return mWebSocketChannel->Close();
return mWebSocketChannel->Close(mOwner->mClientReasonCode,
mOwner->mClientReason);
}
nsresult
@ -561,9 +562,15 @@ nsWebSocketEstablishedConnection::OnAcknowledge(nsISupports *aContext,
}
NS_IMETHODIMP
nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext)
nsWebSocketEstablishedConnection::OnServerClose(nsISupports *aContext,
PRUint16 aCode,
const nsACString &aReason)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
if (mOwner) {
mOwner->mServerReasonCode = aCode;
CopyUTF8toUTF16(aReason, mOwner->mServerReason);
}
Close(); /* reciprocate! */
return NS_OK;
@ -611,6 +618,8 @@ nsWebSocketEstablishedConnection::GetInterface(const nsIID &aIID,
nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE),
mCheckMustKeepAlive(PR_TRUE),
mTriggeredCloseEvent(PR_FALSE),
mClientReasonCode(0),
mServerReasonCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
mReadyState(nsIMozWebSocket::CONNECTING),
mOutgoingBufferedAmount(0),
mScriptLine(0),
@ -806,7 +815,7 @@ nsWebSocket::EstablishConnection()
rv = conn->Init(this);
mConnection = conn;
if (NS_FAILED(rv)) {
Close();
Close(0, EmptyString(), 0);
mConnection = nsnull;
return rv;
}
@ -817,14 +826,18 @@ nsWebSocket::EstablishConnection()
class nsWSCloseEvent : public nsRunnable
{
public:
nsWSCloseEvent(nsWebSocket *aWebSocket, PRBool aWasClean)
nsWSCloseEvent(nsWebSocket *aWebSocket, PRBool aWasClean,
PRUint16 aCode, const nsString &aReason)
: mWebSocket(aWebSocket),
mWasClean(aWasClean)
mWasClean(aWasClean),
mCode(aCode),
mReason(aReason)
{}
NS_IMETHOD Run()
{
nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean);
nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean,
mCode, mReason);
mWebSocket->UpdateMustKeepAlive();
return rv;
}
@ -832,6 +845,8 @@ public:
private:
nsRefPtr<nsWebSocket> mWebSocket;
PRBool mWasClean;
PRUint16 mCode;
nsString mReason;
};
nsresult
@ -919,7 +934,9 @@ nsWebSocket::CreateAndDispatchMessageEvent(const nsACString& aData)
}
nsresult
nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean)
nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean,
PRUint16 aCode,
const nsString &aReason)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
nsresult rv;
@ -941,7 +958,7 @@ nsWebSocket::CreateAndDispatchCloseEvent(PRBool aWasClean)
nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event);
rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"),
PR_FALSE, PR_FALSE,
aWasClean);
aWasClean, aCode, aReason);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
@ -999,7 +1016,10 @@ nsWebSocket::SetReadyState(PRUint16 aNewReadyState)
if (mConnection) {
// The close event must be dispatched asynchronously.
nsCOMPtr<nsIRunnable> event =
new nsWSCloseEvent(this, mConnection->ClosedCleanly());
new nsWSCloseEvent(this,
mConnection->ClosedCleanly(),
mServerReasonCode,
mServerReason);
mOutgoingBufferedAmount += mConnection->GetOutgoingBufferedAmount();
mConnection = nsnull; // this is no longer necessary
@ -1257,6 +1277,26 @@ NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(error, mOnErrorListener)
NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(message, mOnMessageListener)
NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(close, mOnCloseListener)
static PRBool
ContainsUnpairedSurrogates(const nsAString& aData)
{
// Check for unpaired surrogates.
PRUint32 i, length = aData.Length();
for (i = 0; i < length; ++i) {
if (NS_IS_LOW_SURROGATE(aData[i])) {
return PR_TRUE;
}
if (NS_IS_HIGH_SURROGATE(aData[i])) {
++i;
if (i == length || !NS_IS_LOW_SURROGATE(aData[i])) {
return PR_TRUE;
}
continue;
}
}
return PR_FALSE;
}
NS_IMETHODIMP
nsWebSocket::Send(const nsAString& aData)
{
@ -1266,20 +1306,8 @@ nsWebSocket::Send(const nsAString& aData)
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
// Check for unpaired surrogates.
PRUint32 i, length = aData.Length();
for (i = 0; i < length; ++i) {
if (NS_IS_LOW_SURROGATE(aData[i])) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
if (NS_IS_HIGH_SURROGATE(aData[i])) {
if (i + 1 == length || !NS_IS_LOW_SURROGATE(aData[i + 1])) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
++i;
continue;
}
}
if (ContainsUnpairedSurrogates(aData))
return NS_ERROR_DOM_SYNTAX_ERR;
if (mReadyState == nsIMozWebSocket::CLOSING ||
mReadyState == nsIMozWebSocket::CLOSED) {
@ -1293,9 +1321,34 @@ nsWebSocket::Send(const nsAString& aData)
}
NS_IMETHODIMP
nsWebSocket::Close()
nsWebSocket::Close(PRUint16 code, const nsAString & reason, PRUint8 argc)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
// the reason code is optional, but if provided it must be in a specific range
if (argc >= 1) {
if (code != 1000 && (code < 3000 || code > 4999))
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
nsCAutoString utf8Reason;
if (argc >= 2) {
if (ContainsUnpairedSurrogates(reason))
return NS_ERROR_DOM_SYNTAX_ERR;
CopyUTF16toUTF8(reason, utf8Reason);
// The API requires the UTF-8 string to be 123 or less bytes
if (utf8Reason.Length() > 123)
return NS_ERROR_DOM_SYNTAX_ERR;
}
// Format checks for reason and code both passed, they can now be assigned.
if (argc >= 1)
mClientReasonCode = code;
if (argc >= 2)
mClientReason = utf8Reason;
if (mReadyState == nsIMozWebSocket::CLOSING ||
mReadyState == nsIMozWebSocket::CLOSED) {
return NS_OK;

View File

@ -109,7 +109,8 @@ protected:
nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
nsresult CreateAndDispatchMessageEvent(const nsACString& aData);
nsresult CreateAndDispatchCloseEvent(PRBool aWasClean);
nsresult CreateAndDispatchCloseEvent(PRBool aWasClean, PRUint16 aCode,
const nsString &aReason);
// called from mConnection accordingly to the situation
void SetReadyState(PRUint16 aNewReadyState);
@ -136,6 +137,11 @@ protected:
PRPackedBool mCheckMustKeepAlive;
PRPackedBool mTriggeredCloseEvent;
nsCString mClientReason;
PRUint16 mClientReasonCode;
nsString mServerReason;
PRUint16 mServerReasonCode;
nsCString mAsciiHost; // hostname
PRUint32 mPort;
nsCString mResource; // [filepath[?query]]

View File

@ -96,5 +96,25 @@ def web_socket_transfer_data(request):
elif request.ws_protocol == "test-20":
msgutil.send_message(request, "server data")
msgutil.close_connection(request)
elif request.ws_protocol == "test-34":
request.ws_stream.close_connection(1001, "going away now")
elif request.ws_protocol == "test-35a":
while not request.client_terminated:
msgutil.receive_message(request)
global test35code
test35code = request.ws_close_code
global test35reason
test35reason = request.ws_close_reason
elif request.ws_protocol == "test-35b":
request.ws_stream.close_connection(test35code + 1, test35reason)
elif request.ws_protocol == "test-37b":
while not request.client_terminated:
msgutil.receive_message(request)
global test37code
test37code = request.ws_close_code
global test37reason
test37reason = request.ws_close_reason
elif request.ws_protocol == "test-37c":
request.ws_stream.close_connection(test37code, test37reason)
while not request.client_terminated:
msgutil.receive_message(request)

View File

@ -56,10 +56,15 @@
* 31. ctor using valid 2 element sub-protocol array with 1 element server
* will reject and one server will accept.
* 32. ctor using invalid sub-protocol array that contains duplicate items
* 33. default close code test
* 34. test for receiving custom close code and reason
* 35. test for sending custom close code and reason
* 36. negative test for sending out of range close code
* 37. negative test for too long of a close reason
*/
var first_test = 1;
var last_test = 32;
var last_test = 37;
var current_test = first_test;
@ -884,6 +889,173 @@ function test32()
doTest(33);
}
function test33()
{
var prots=["test33"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 33 open");
ws.close();
};
ws.onclose = function(e)
{
ok(true, "test 33 close");
ok(e.wasClean, "test 33 closed cleanly");
ok(e.code == 1000, "test 33 had normal 1000 error code");
doTest(34);
};
}
function test34()
{
var prots=["test-34"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 34 open");
ws.close();
};
ws.onclose = function(e)
{
ok(true, "test 34 close");
ok(e.wasClean, "test 34 closed cleanly");
ok(e.code == 1001, "test 34 custom server code");
ok(e.reason == "going away now", "test 34 custom server reason");
doTest(35);
};
}
function test35()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-35a");
ws.onopen = function(e)
{
ok(true, "test 35a open");
ws.close(3500, "my code");
};
ws.onclose = function(e)
{
ok(true, "test 35a close");
ok(e.wasClean, "test 35a closed cleanly");
current_test--; // CreateTestWS for 35a incremented this
var wsb = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-35b");
wsb.onopen = function(e)
{
ok(true, "test 35b open");
wsb.close();
};
wsb.onclose = function(e)
{
ok(true, "test 35b close");
ok(e.wasClean, "test 35b closed cleanly");
ok(e.code == 3501, "test 35 custom server code");
ok(e.reason == "my code", "test 35 custom server reason");
doTest(36);
};
}
}
function test36()
{
var prots=["test-36"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 36 open");
try {
ws.close(13200);
ok(false, "testing custom close code out of range");
}
catch (e) {
ok(true, "testing custom close code out of range");
ws.close(3200);
}
};
ws.onclose = function(e)
{
ok(true, "test 36 close");
ok(e.wasClean, "test 36 closed cleanly");
doTest(37);
};
}
function test37()
{
var prots=["test-37"];
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", prots);
ws.onopen = function(e)
{
ok(true, "test 37 open");
try {
ws.close(3100,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
ok(false, "testing custom close reason out of range");
}
catch (e) {
ok(true, "testing custom close reason out of range");
ws.close(3100,"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012");
}
};
ws.onclose = function(e)
{
ok(true, "test 37 close");
ok(e.wasClean, "test 37 closed cleanly");
current_test--; // CreateTestWS for 37 incremented this
var wsb = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-37b");
wsb.onopen = function(e)
{
// now test that a rejected close code and reason dont persist
ok(true, "test 37b open");
try {
wsb.close(3101,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
ok(false, "testing custom close reason out of range 37b");
}
catch (e) {
ok(true, "testing custom close reason out of range 37b");
wsb.close();
}
}
wsb.onclose = function(e)
{
ok(true, "test 37b close");
ok(e.wasClean, "test 37b closed cleanly");
current_test--; // CreateTestWS for 37 incremented this
var wsc = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test-37c");
wsc.onopen = function(e)
{
ok(true, "test 37c open");
wsc.close();
}
wsc.onclose = function(e)
{
ok(e.code != 3101, "test 37c custom server code not present");
ok(e.reason == "", "test 37c custom server reason not present");
doTest(38);
}
}
}
}
var ranAllTests = false;
function maybeFinished()

View File

@ -56,16 +56,34 @@ nsDOMCloseEvent::GetWasClean(PRBool *aWasClean)
return NS_OK;
}
NS_IMETHODIMP
nsDOMCloseEvent::GetCode(PRUint16 *aCode)
{
*aCode = mReasonCode;
return NS_OK;
}
NS_IMETHODIMP
nsDOMCloseEvent::GetReason(nsAString & aReason)
{
aReason = mReason;
return NS_OK;
}
NS_IMETHODIMP
nsDOMCloseEvent::InitCloseEvent(const nsAString& aType,
PRBool aCanBubble,
PRBool aCancelable,
PRBool aWasClean)
PRBool aWasClean,
PRUint16 aReasonCode,
const nsAString &aReason)
{
nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable);
NS_ENSURE_SUCCESS(rv, rv);
mWasClean = aWasClean;
mReasonCode = aReasonCode;
mReason = aReason;
return NS_OK;
}

View File

@ -53,9 +53,9 @@ class nsDOMCloseEvent : public nsDOMEvent,
{
public:
nsDOMCloseEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent), mWasClean(PR_FALSE)
{
}
: nsDOMEvent(aPresContext, aEvent),
mWasClean(PR_FALSE),
mReasonCode(1005) {}
NS_DECL_ISUPPORTS_INHERITED
@ -66,6 +66,8 @@ public:
private:
PRBool mWasClean;
PRUint16 mReasonCode;
nsString mReason;
};
#endif // nsDOMCloseEvent_h__

View File

@ -45,13 +45,17 @@
* For more information on this interface, please see
* http://dev.w3.org/html5/websockets/#closeevent
*/
[scriptable, uuid(a94d4379-eba2-45f4-be3a-6cc2fa1453a8)]
[scriptable, uuid(f83d9d6d-6c0c-418c-b12a-438e76d5866b)]
interface nsIDOMCloseEvent : nsIDOMEvent
{
readonly attribute boolean wasClean;
readonly attribute unsigned short code;
readonly attribute DOMString reason;
void initCloseEvent(in DOMString aType,
in boolean aCanBubble,
in boolean aCancelable,
in boolean aWasClean);
in boolean aCanBubble,
in boolean aCancelable,
in boolean aWasClean,
in unsigned short aReasonCode,
in DOMString aReason);
};

View File

@ -55,7 +55,7 @@ async protocol PWebSocket
parent:
// Forwarded methods corresponding to methods on nsIWebSocketChannel
AsyncOpen(URI aURI, nsCString aOrigin, nsCString aProtocol, bool aSecure);
Close();
Close(PRUint16 code, nsCString reason);
SendMsg(nsCString aMsg);
SendBinaryMsg(nsCString aMsg);
@ -68,7 +68,7 @@ child:
OnMessageAvailable(nsCString aMsg);
OnBinaryMessageAvailable(nsCString aMsg);
OnAcknowledge(PRUint32 aSize);
OnServerClose();
OnServerClose(PRUint16 code, nsCString aReason);
__delete__();

View File

@ -179,13 +179,17 @@ public:
NS_DECL_ISUPPORTS
CallOnServerClose(nsIWebSocketListener *aListener,
nsISupports *aContext)
nsISupports *aContext,
PRUint16 aCode,
nsCString &aReason)
: mListener(aListener),
mContext(aContext) {}
mContext(aContext),
mCode(aCode),
mReason(aReason) {}
NS_SCRIPTABLE NS_IMETHOD Run()
{
mListener->OnServerClose(mContext);
mListener->OnServerClose(mContext, mCode, mReason);
return NS_OK;
}
@ -194,6 +198,8 @@ private:
nsCOMPtr<nsIWebSocketListener> mListener;
nsCOMPtr<nsISupports> mContext;
PRUint16 mCode;
nsCString mReason;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnServerClose, nsIRunnable)
@ -502,7 +508,8 @@ WebSocketChannel::WebSocketChannel() :
mTCPClosed(0),
mMaxMessageSize(16000000),
mStopOnClose(NS_OK),
mCloseCode(kCloseAbnormal),
mServerCloseCode(CLOSE_ABNORMAL),
mScriptCloseCode(0),
mFragmentOpcode(0),
mFragmentAccumulator(0),
mBuffered(0),
@ -869,26 +876,29 @@ WebSocketChannel::ProcessInput(PRUint8 *buffer, PRUint32 count)
LOG(("WebSocketChannel:: close received\n"));
mServerClosed = 1;
mCloseCode = kCloseNoStatus;
mServerCloseCode = CLOSE_NO_STATUS;
if (payloadLength >= 2) {
memcpy(&mCloseCode, payload, 2);
mCloseCode = PR_ntohs(mCloseCode);
LOG(("WebSocketChannel:: close recvd code %u\n", mCloseCode));
memcpy(&mServerCloseCode, payload, 2);
mServerCloseCode = PR_ntohs(mServerCloseCode);
LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
PRUint16 msglen = payloadLength - 2;
if (msglen > 0) {
nsCString utf8Data((const char *)payload + 2, msglen);
mServerCloseReason.SetLength(msglen);
memcpy(mServerCloseReason.BeginWriting(),
(const char *)payload + 2, msglen);
// section 8.1 says to replace received non utf-8 sequences
// (which are non-conformant to send) with u+fffd,
// but secteam feels that silently rewriting messages is
// inappropriate - so we will fail the connection instead.
if (!IsUTF8(utf8Data)) {
if (!IsUTF8(mServerCloseReason)) {
LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
LOG(("WebSocketChannel:: close msg %s\n", utf8Data.get()));
LOG(("WebSocketChannel:: close msg %s\n",
mServerCloseReason.get()));
}
}
@ -897,7 +907,9 @@ WebSocketChannel::ProcessInput(PRUint8 *buffer, PRUint32 count)
mCloseTimer = nsnull;
}
if (mListener)
NS_DispatchToMainThread(new CallOnServerClose(mListener, mContext));
NS_DispatchToMainThread(
new CallOnServerClose(mListener, mContext,
mServerCloseCode, mServerCloseReason));
if (mClientClosed)
ReleaseSession();
@ -1058,16 +1070,16 @@ PRUint16
WebSocketChannel::ResultToCloseCode(nsresult resultCode)
{
if (NS_SUCCEEDED(resultCode))
return kCloseNormal;
return CLOSE_NORMAL;
if (resultCode == NS_ERROR_FILE_TOO_BIG)
return kCloseTooLarge;
return CLOSE_TOO_LARGE;
if (resultCode == NS_BASE_STREAM_CLOSED ||
resultCode == NS_ERROR_NET_TIMEOUT ||
resultCode == NS_ERROR_CONNECTION_REFUSED) {
return kCloseAbnormal;
return CLOSE_ABNORMAL;
}
return kCloseProtocolError;
return CLOSE_PROTOCOL_ERROR;
}
void
@ -1107,16 +1119,34 @@ WebSocketChannel::PrimeNewOutgoingMessage()
LOG(("WebSocketChannel:: PrimeNewOutgoingMessage() found close request\n"));
mClientClosed = 1;
mOutHeader[0] = kFinalFragBit | kClose;
mOutHeader[1] = 0x02; // payload len = 2
mOutHeader[1] = 0x02; // payload len = 2, maybe more for reason
mOutHeader[1] |= kMaskBit;
// payload is offset 6 including 4 for the mask
payload = mOutHeader + 6;
// The close reason code sits in the first 2 bytes of payload
*((PRUint16 *)payload) = PR_htons(ResultToCloseCode(mStopOnClose));
// length is 8 plus any reason information
mHdrOutToSend = 8;
// The close reason code sits in the first 2 bytes of payload
// If the channel user provided a code and reason during Close()
// and there isn't an internal error, use that.
if (NS_SUCCEEDED(mStopOnClose) && mScriptCloseCode) {
*((PRUint16 *)payload) = PR_htons(mScriptCloseCode);
if (!mScriptCloseReason.IsEmpty()) {
NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123,
"Close Reason Too Long");
mOutHeader[1] += mScriptCloseReason.Length();
mHdrOutToSend += mScriptCloseReason.Length();
memcpy (payload + 2,
mScriptCloseReason.BeginReading(),
mScriptCloseReason.Length());
}
}
else {
*((PRUint16 *)payload) = PR_htons(ResultToCloseCode(mStopOnClose));
}
if (mServerClosed) {
/* bidi close complete */
mReleaseOnTransmit = 1;
@ -1984,7 +2014,7 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
}
NS_IMETHODIMP
WebSocketChannel::Close()
WebSocketChannel::Close(PRUint16 code, const nsACString & reason)
{
LOG(("WebSocketChannel::Close() %p\n", this));
NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
@ -2000,8 +2030,14 @@ WebSocketChannel::Close()
return NS_ERROR_UNEXPECTED;
}
mRequestedClose = 1;
// The API requires the UTF-8 string to be 123 or less bytes
if (reason.Length() > 123)
return NS_ERROR_ILLEGAL_VALUE;
mRequestedClose = 1;
mScriptCloseReason = reason;
mScriptCloseCode = code;
return mSocketThread->Dispatch(new nsPostMessage(this, kFinMessage, -1),
nsIEventTarget::DISPATCH_NORMAL);
}

View File

@ -99,7 +99,7 @@ public:
const nsACString &aOrigin,
nsIWebSocketListener *aListener,
nsISupports *aContext);
NS_IMETHOD Close();
NS_IMETHOD Close(PRUint16 aCode, const nsACString & aReason);
NS_IMETHOD SendMsg(const nsACString &aMsg);
NS_IMETHOD SendBinaryMsg(const nsACString &aMsg);
NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
@ -123,15 +123,6 @@ public:
const static PRUint8 kMaskBit = 0x80;
const static PRUint8 kFinalFragBit = 0x80;
// section 7.4.1 defines these
const static PRUint16 kCloseNormal = 1000;
const static PRUint16 kCloseGoingAway = 1001;
const static PRUint16 kCloseProtocolError = 1002;
const static PRUint16 kCloseUnsupported = 1003;
const static PRUint16 kCloseTooLarge = 1004;
const static PRUint16 kCloseNoStatus = 1005;
const static PRUint16 kCloseAbnormal = 1006;
protected:
virtual ~WebSocketChannel();
@ -252,7 +243,10 @@ private:
PRInt32 mMaxMessageSize;
nsresult mStopOnClose;
PRUint16 mCloseCode;
PRUint16 mServerCloseCode;
nsCString mServerCloseReason;
PRUint16 mScriptCloseCode;
nsCString mScriptCloseReason;
// These are for the read buffers
PRUint8 *mFramePtr;

View File

@ -291,36 +291,44 @@ WebSocketChannelChild::OnAcknowledge(const PRUint32& aSize)
class ServerCloseEvent : public ChannelEvent
{
public:
ServerCloseEvent(WebSocketChannelChild* aChild)
ServerCloseEvent(WebSocketChannelChild* aChild,
const PRUint16 aCode,
const nsCString &aReason)
: mChild(aChild)
, mCode(aCode)
, mReason(aReason)
{}
void Run()
{
mChild->OnServerClose();
mChild->OnServerClose(mCode, mReason);
}
private:
WebSocketChannelChild* mChild;
PRUint16 mCode;
nsCString mReason;
};
bool
WebSocketChannelChild::RecvOnServerClose()
WebSocketChannelChild::RecvOnServerClose(const PRUint16& aCode,
const nsCString& aReason)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new ServerCloseEvent(this));
mEventQ.Enqueue(new ServerCloseEvent(this, aCode, aReason));
} else {
OnServerClose();
OnServerClose(aCode, aReason);
}
return true;
}
void
WebSocketChannelChild::OnServerClose()
WebSocketChannelChild::OnServerClose(const PRUint16& aCode,
const nsCString& aReason)
{
LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this));
if (mListener) {
AutoEventEnqueuer ensureSerialDispatch(mEventQ);;
mListener->OnServerClose(mContext);
mListener->OnServerClose(mContext, aCode, aReason);
}
}
@ -361,11 +369,11 @@ WebSocketChannelChild::AsyncOpen(nsIURI *aURI,
}
NS_IMETHODIMP
WebSocketChannelChild::Close()
WebSocketChannelChild::Close(PRUint16 code, const nsACString & reason)
{
LOG(("WebSocketChannelChild::Close() %p\n", this));
if (!mIPCOpen || !SendClose())
if (!mIPCOpen || !SendClose(code, nsCString(reason)))
return NS_ERROR_UNEXPECTED;
return NS_OK;
}

View File

@ -64,7 +64,7 @@ class WebSocketChannelChild : public BaseWebSocketChannel,
const nsACString &aOrigin,
nsIWebSocketListener *aListener,
nsISupports *aContext);
NS_SCRIPTABLE NS_IMETHOD Close();
NS_SCRIPTABLE NS_IMETHOD Close(PRUint16 code, const nsACString & reason);
NS_SCRIPTABLE NS_IMETHOD SendMsg(const nsACString &aMsg);
NS_SCRIPTABLE NS_IMETHOD SendBinaryMsg(const nsACString &aMsg);
NS_SCRIPTABLE NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
@ -78,7 +78,7 @@ class WebSocketChannelChild : public BaseWebSocketChannel,
bool RecvOnMessageAvailable(const nsCString& aMsg);
bool RecvOnBinaryMessageAvailable(const nsCString& aMsg);
bool RecvOnAcknowledge(const PRUint32& aSize);
bool RecvOnServerClose();
bool RecvOnServerClose(const PRUint16& aCode, const nsCString &aReason);
bool RecvAsyncOpenFailed();
void OnStart(const nsCString& aProtocol);
@ -86,7 +86,7 @@ class WebSocketChannelChild : public BaseWebSocketChannel,
void OnMessageAvailable(const nsCString& aMsg);
void OnBinaryMessageAvailable(const nsCString& aMsg);
void OnAcknowledge(const PRUint32& aSize);
void OnServerClose();
void OnServerClose(const PRUint16& aCode, const nsCString& aReason);
void AsyncOpenFailed();
ChannelEventQueue mEventQ;

View File

@ -105,11 +105,11 @@ fail:
}
bool
WebSocketChannelParent::RecvClose()
WebSocketChannelParent::RecvClose(const PRUint16& code, const nsCString& reason)
{
LOG(("WebSocketChannelParent::RecvClose() %p\n", this));
if (mChannel) {
nsresult rv = mChannel->Close();
nsresult rv = mChannel->Close(code, reason);
NS_ENSURE_SUCCESS(rv, true);
}
return true;
@ -203,10 +203,11 @@ WebSocketChannelParent::OnAcknowledge(nsISupports *aContext, PRUint32 aSize)
}
NS_IMETHODIMP
WebSocketChannelParent::OnServerClose(nsISupports *aContext)
WebSocketChannelParent::OnServerClose(nsISupports *aContext,
PRUint16 code, const nsACString & reason)
{
LOG(("WebSocketChannelParent::OnServerClose() %p\n", this));
if (!mIPCOpen || !SendOnServerClose()) {
if (!mIPCOpen || !SendOnServerClose(code, nsCString(reason))) {
return NS_ERROR_FAILURE;
}
return NS_OK;

View File

@ -67,7 +67,7 @@ class WebSocketChannelParent : public PWebSocketParent,
const nsCString& aOrigin,
const nsCString& aProtocol,
const bool& aSecure);
bool RecvClose();
bool RecvClose(const PRUint16 & code, const nsCString & reason);
bool RecvSendMsg(const nsCString& aMsg);
bool RecvSendBinaryMsg(const nsCString& aMsg);
bool RecvDeleteSelf();

View File

@ -44,7 +44,7 @@ interface nsIWebSocketListener;
#include "nsISupports.idl"
[scriptable, uuid(398a2460-a46d-11e0-8264-0800200c9a66)]
[scriptable, uuid(783029cf-75b5-439e-8e47-86b390202b4c)]
interface nsIWebSocketChannel : nsISupports
{
/**
@ -106,9 +106,22 @@ interface nsIWebSocketChannel : nsISupports
* Close the websocket connection for writing - no more calls to sendMsg
* or sendBinaryMsg should be made after calling this. The listener object
* may receive more messages if a server close has not yet been received.
*
* @param aCode the websocket closing handshake close code. Set to 0 if
* you are not providing a code.
* @param aReason the websocket closing handshake close reason
*/
void close();
void close(in unsigned short aCode, in AUTF8String aReason);
// section 7.4.1 defines these close codes
const unsigned short CLOSE_NORMAL = 1000;
const unsigned short CLOSE_GOING_AWAY = 1001;
const unsigned short CLOSE_PROTOCOL_ERROR = 1002;
const unsigned short CLOSE_UNSUPPORTED = 1003;
const unsigned short CLOSE_TOO_LARGE = 1004;
const unsigned short CLOSE_NO_STATUS = 1005;
const unsigned short CLOSE_ABNORMAL = 1006;
/**
* Use to send text message down the connection to WebSocket peer.
*

View File

@ -43,7 +43,7 @@
* nsIWebSocketListener: passed to nsIWebSocketChannel::AsyncOpen. Receives
* websocket traffic events as they arrive.
*/
[scriptable, uuid(b0c27050-31e9-42e5-bc59-499d54b52f99)]
[scriptable, uuid(d74c96b2-65b3-4e39-9e39-c577de5d7a73)]
interface nsIWebSocketListener : nsISupports
{
/**
@ -101,8 +101,14 @@ interface nsIWebSocketListener : nsISupports
* onBinaryMessageAvailable() or onAcknowledge() will be delievered
* to the listener after onServerClose(), though outgoing messages can still
* be sent through the nsIWebSocketChannel connection.
*
* @param aContext user defined context
* @param aCode the websocket closing handshake close code.
* @param aReason the websocket closing handshake close reason
*/
void onServerClose(in nsISupports aContext);
void onServerClose(in nsISupports aContext, in unsigned short aCode,
in AUTF8String aReason);
};