bug 674716 - websockets API close reason codes and messages r=sicking r=biesi sr=bz
authorPatrick McManus <mcmanus@ducksong.com>
Wed, 03 Aug 2011 15:15:25 -0400
changeset 74812 e054dec99f3e9559772ec78a46b6cf957754d4f2
parent 74811 a8045a08cbc9fb935d5039b928772717c9d577d0
child 74813 3cca53b77c110c9c406c376e6be6ff2d9018fb07
push id67
push userclegnitto@mozilla.com
push dateFri, 04 Nov 2011 22:39:41 +0000
treeherdermozilla-release@04778346a3b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking, biesi, bz
bugs674716
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 674716 - websockets API close reason codes and messages r=sicking r=biesi sr=bz
content/base/public/nsIMozWebSocket.idl
content/base/src/nsWebSocket.cpp
content/base/src/nsWebSocket.h
content/base/test/file_websocket_wsh.py
content/base/test/test_websocket.html
content/events/src/nsDOMCloseEvent.cpp
content/events/src/nsDOMCloseEvent.h
dom/interfaces/events/nsIDOMCloseEvent.idl
netwerk/protocol/websocket/PWebSocket.ipdl
netwerk/protocol/websocket/WebSocketChannel.cpp
netwerk/protocol/websocket/WebSocketChannel.h
netwerk/protocol/websocket/WebSocketChannelChild.cpp
netwerk/protocol/websocket/WebSocketChannelChild.h
netwerk/protocol/websocket/WebSocketChannelParent.cpp
netwerk/protocol/websocket/WebSocketChannelParent.h
netwerk/protocol/websocket/nsIWebSocketChannel.idl
netwerk/protocol/websocket/nsIWebSocketListener.idl
--- a/content/base/public/nsIMozWebSocket.idl
+++ b/content/base/public/nsIMozWebSocket.idl
@@ -53,17 +53,17 @@ class nsString;
 
 /**
  * The nsIMozWebSocket interface enables Web applications to maintain
  * bidirectional communications with server-side processes as described in:
  *
  * 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;
   readonly attribute DOMString protocol;
 
   //ready state
   const unsigned short CONNECTING = 0;
   const unsigned short OPEN = 1;
@@ -87,17 +87,18 @@ interface nsIMozWebSocket : nsISupports
    *         sent successfully).
    */
   void send(in DOMString data);
 
   /**
    * 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
    * context, and owner window that should be used.
    *
    * @param principal The principal to use for the request. This must not be
    *                  null.
    * @param scriptContext The script context to use for the request. May be
--- a/content/base/src/nsWebSocket.cpp
+++ b/content/base/src/nsWebSocket.cpp
@@ -385,17 +385,18 @@ nsWebSocketEstablishedConnection::Close(
   mOwner->SetReadyState(nsIMozWebSocket::CLOSING);
 
   if (mStatus == CONN_CLOSED) {
     mOwner->SetReadyState(nsIMozWebSocket::CLOSED);
     Disconnect();
     return NS_OK;
   }
 
-  return mWebSocketChannel->Close();
+  return mWebSocketChannel->Close(mOwner->mClientReasonCode,
+                                  mOwner->mClientReason);
 }
 
 nsresult
 nsWebSocketEstablishedConnection::ConsoleError()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
   if (!mOwner) return NS_OK;
@@ -556,19 +557,25 @@ nsWebSocketEstablishedConnection::OnAckn
   if (aSize > mOutgoingBufferedAmount)
     return NS_ERROR_UNEXPECTED;
   
   mOutgoingBufferedAmount -= aSize;
   return NS_OK;
 }
 
 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;
 }
 
 //-----------------------------------------------------------------------------
 // nsWebSocketEstablishedConnection::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
@@ -606,16 +613,18 @@ nsWebSocketEstablishedConnection::GetInt
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsWebSocket
 ////////////////////////////////////////////////////////////////////////////////
 
 nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE),
                              mCheckMustKeepAlive(PR_TRUE),
                              mTriggeredCloseEvent(PR_FALSE),
+                             mClientReasonCode(0),
+                             mServerReasonCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
                              mReadyState(nsIMozWebSocket::CONNECTING),
                              mOutgoingBufferedAmount(0),
                              mScriptLine(0),
                              mWindowID(0)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 }
 
@@ -801,42 +810,48 @@ nsWebSocket::EstablishConnection()
   nsresult rv;
 
   nsRefPtr<nsWebSocketEstablishedConnection> conn =
     new nsWebSocketEstablishedConnection();
 
   rv = conn->Init(this);
   mConnection = conn;
   if (NS_FAILED(rv)) {
-    Close();
+    Close(0, EmptyString(), 0);
     mConnection = nsnull;
     return rv;
   }
 
   return NS_OK;
 }
 
 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;
   }
 
 private:
   nsRefPtr<nsWebSocket> mWebSocket;
   PRBool mWasClean;
+  PRUint16 mCode;
+  nsString mReason;
 };
 
 nsresult
 nsWebSocket::CreateAndDispatchSimpleEvent(const nsString& aName)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
@@ -914,17 +929,19 @@ nsWebSocket::CreateAndDispatchMessageEve
   nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
   rv = privateEvent->SetTrusted(PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchDOMEvent(nsnull, event, nsnull, nsnull);
 }
 
 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;
 
   mTriggeredCloseEvent = PR_TRUE;
 
   rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
@@ -936,17 +953,17 @@ nsWebSocket::CreateAndDispatchCloseEvent
 
   nsCOMPtr<nsIDOMEvent> event;
   rv = NS_NewDOMCloseEvent(getter_AddRefs(event), nsnull, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
   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);
   rv = privateEvent->SetTrusted(PR_TRUE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchDOMEvent(nsnull, event, nsnull, nsnull);
 }
@@ -994,17 +1011,20 @@ nsWebSocket::SetReadyState(PRUint16 aNew
   }
 
   if (aNewReadyState == nsIMozWebSocket::CLOSED) {
     mReadyState = 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
 
       rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
       if (NS_FAILED(rv)) {
         NS_WARNING("Failed to dispatch the close event");
         mTriggeredCloseEvent = PR_TRUE;
         UpdateMustKeepAlive();
@@ -1252,55 +1272,88 @@ nsWebSocket::GetBufferedAmount(PRUint32 
                                   _eventlistener, aEventListener);             \
   }
 
 NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(open, mOnOpenListener)
 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)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mReadyState == nsIMozWebSocket::CONNECTING) {
     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) {
     mOutgoingBufferedAmount += NS_ConvertUTF16toUTF8(aData).Length();
     return NS_OK;
   }
 
   mConnection->PostMessage(PromiseFlatString(aData));
 
   return NS_OK;
 }
 
 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;
   }
 
   if (mReadyState == nsIMozWebSocket::CONNECTING) {
     // FailConnection() can release the object, so we keep a reference
     // before calling it
--- a/content/base/src/nsWebSocket.h
+++ b/content/base/src/nsWebSocket.h
@@ -104,17 +104,18 @@ public:
   const PRUint32 GetScriptLine() const { return mScriptLine; }
 
 protected:
   nsresult ParseURL(const nsString& aURL);
   nsresult EstablishConnection();
 
   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);
 
   // if there are "strong event listeners" (see comment in nsWebSocket.cpp) or
   // outgoing not sent messages then this method keeps the object alive
   // when js doesn't have strong references to it.
   void UpdateMustKeepAlive();
@@ -131,16 +132,21 @@ protected:
   nsString mOriginalURL;
   PRPackedBool mSecure; // if true it is using SSL and the wss scheme,
                         // otherwise it is using the ws scheme with no SSL
 
   PRPackedBool mKeepingAlive;
   PRPackedBool mCheckMustKeepAlive;
   PRPackedBool mTriggeredCloseEvent;
 
+  nsCString mClientReason;
+  PRUint16  mClientReasonCode;
+  nsString  mServerReason;
+  PRUint16  mServerReasonCode;
+
   nsCString mAsciiHost;  // hostname
   PRUint32  mPort;
   nsCString mResource; // [filepath[?query]]
   nsString  mUTF16Origin;
   
   nsCOMPtr<nsIURI> mURI;
   nsCString mRequestedProtocolList;
   nsCString mEstablishedProtocol;
--- a/content/base/test/file_websocket_wsh.py
+++ b/content/base/test/file_websocket_wsh.py
@@ -91,10 +91,30 @@ def web_socket_transfer_data(request):
     if msgutil.receive_message(request) == "client data":
       resp = "server data"
     msgutil.send_message(request, resp.decode('utf-8'))
     time.sleep(2)
     msgutil.close_connection(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)
--- a/content/base/test/test_websocket.html
+++ b/content/base/test/test_websocket.html
@@ -51,20 +51,25 @@
  * 27. ctor with invalid sub-protocol array containing an empty element in list
  * 28. ctor using valid 1 element sub-protocol array
  * 29. ctor using all valid 5 element sub-protocol array
  * 30. ctor using valid 1 element sub-protocol array with element server will
  *     reject
  * 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;
 
 var all_ws = [];
 
 function shouldNotOpen(e)
 {
   var ws = e.target;
@@ -879,16 +884,183 @@ function test32()
     ok(false, "testing duplicated element sub protocol array");
   }
   catch (e) {
     ok(true, "testing duplicated sub element protocol array");
   }
   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()
 {
   if (!ranAllTests)
     return;
 
   if (waitTest2Part1 || waitTest2Part2 || waitTest9 || waitTest10 ||
--- a/content/events/src/nsDOMCloseEvent.cpp
+++ b/content/events/src/nsDOMCloseEvent.cpp
@@ -52,25 +52,43 @@ NS_INTERFACE_MAP_END_INHERITING(nsDOMEve
 NS_IMETHODIMP
 nsDOMCloseEvent::GetWasClean(PRBool *aWasClean)
 {
   *aWasClean = mWasClean;
   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;
 }
 
 nsresult
 NS_NewDOMCloseEvent(nsIDOMEvent** aInstancePtrResult,
                     nsPresContext* aPresContext,
                     nsEvent* aEvent) 
--- a/content/events/src/nsDOMCloseEvent.h
+++ b/content/events/src/nsDOMCloseEvent.h
@@ -48,24 +48,26 @@
  *
  * See http://dev.w3.org/html5/websockets/#closeevent for further details.
  */
 class nsDOMCloseEvent : public nsDOMEvent,
                         public nsIDOMCloseEvent
 {
 public:
   nsDOMCloseEvent(nsPresContext* aPresContext, nsEvent* aEvent)
-    : nsDOMEvent(aPresContext, aEvent), mWasClean(PR_FALSE)
-  {
-  }
+    : nsDOMEvent(aPresContext, aEvent),
+    mWasClean(PR_FALSE),
+    mReasonCode(1005) {}
                      
   NS_DECL_ISUPPORTS_INHERITED
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMEVENT
 
   NS_DECL_NSIDOMCLOSEEVENT
 
 private:
   PRBool mWasClean;
+  PRUint16 mReasonCode;
+  nsString mReason;
 };
 
 #endif // nsDOMCloseEvent_h__
--- a/dom/interfaces/events/nsIDOMCloseEvent.idl
+++ b/dom/interfaces/events/nsIDOMCloseEvent.idl
@@ -40,18 +40,22 @@
 
 /**
  * The nsIDOMCloseEvent interface is the interface to the event
  * close on a WebSocket object.
  *
  * 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);
 };
--- a/netwerk/protocol/websocket/PWebSocket.ipdl
+++ b/netwerk/protocol/websocket/PWebSocket.ipdl
@@ -50,29 +50,29 @@ namespace net {
 
 async protocol PWebSocket
 {
   manager PNecko;
 
 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);
 
   DeleteSelf();
 
 child:
   // Forwarded notifications corresponding to the nsIWebSocketListener interface
   OnStart(nsCString aProtocol);
   OnStop(nsresult aStatusCode);
   OnMessageAvailable(nsCString aMsg);
   OnBinaryMessageAvailable(nsCString aMsg);
   OnAcknowledge(PRUint32 aSize);
-  OnServerClose();
+  OnServerClose(PRUint16 code, nsCString aReason);
 
   __delete__();
 
 };
 
 } //namespace net
 } //namespace mozilla
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -174,31 +174,37 @@ private:
 NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnStop, nsIRunnable)
 
 class CallOnServerClose : public nsIRunnable
 {
 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;
   }
 
 private:
   ~CallOnServerClose() {}
 
   nsCOMPtr<nsIWebSocketListener>    mListener;
   nsCOMPtr<nsISupports>             mContext;
+  PRUint16                          mCode;
+  nsCString                         mReason;
 };
 NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnServerClose, nsIRunnable)
 
 class CallAcknowledge : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
 
@@ -497,17 +503,18 @@ WebSocketChannel::WebSocketChannel() :
   mCalledOnStop(0),
   mPingOutstanding(0),
   mAllowCompression(1),
   mAutoFollowRedirects(0),
   mReleaseOnTransmit(0),
   mTCPClosed(0),
   mMaxMessageSize(16000000),
   mStopOnClose(NS_OK),
-  mCloseCode(kCloseAbnormal),
+  mServerCloseCode(CLOSE_ABNORMAL),
+  mScriptCloseCode(0),
   mFragmentOpcode(0),
   mFragmentAccumulator(0),
   mBuffered(0),
   mBufferSize(16384),
   mCurrentOut(nsnull),
   mCurrentOutSent(0),
   mCompressor(nsnull),
   mDynamicOutputSize(0),
@@ -864,45 +871,50 @@ WebSocketChannel::ProcessInput(PRUint8 *
         AbortSession(NS_ERROR_ILLEGAL_VALUE);
         return NS_ERROR_ILLEGAL_VALUE;
       }
 
       if (opcode == kClose) {
         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()));
           }
         }
 
         if (mCloseTimer) {
           mCloseTimer->Cancel();
           mCloseTimer = nsnull;
         }
         if (mListener)
-          NS_DispatchToMainThread(new CallOnServerClose(mListener, mContext));
+          NS_DispatchToMainThread(
+            new CallOnServerClose(mListener, mContext,
+                                  mServerCloseCode, mServerCloseReason));
 
         if (mClientClosed)
           ReleaseSession();
       } else if (opcode == kPing) {
         LOG(("WebSocketChannel:: ping received\n"));
         GeneratePong(payload, payloadLength);
       } else {
         // opcode kPong: the mere act of receiving the packet is all we need
@@ -1053,26 +1065,26 @@ WebSocketChannel::SendMsgInternal(nsCStr
   }
   OnOutputStreamReady(mSocketOut);
 }
 
 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
 WebSocketChannel::PrimeNewOutgoingMessage()
 {
   LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
   NS_ABORT_IF_FALSE(!mCurrentOut, "Current message in progress");
@@ -1102,26 +1114,44 @@ WebSocketChannel::PrimeNewOutgoingMessag
     if (mClientClosed) {
       PrimeNewOutgoingMessage();
       return;
     }
 
     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;
 
+    // length is 8 plus any reason information
+    mHdrOutToSend = 8;
+
     // The close reason code sits in the first 2 bytes of payload
-    *((PRUint16 *)payload) = PR_htons(ResultToCloseCode(mStopOnClose));
-
-    mHdrOutToSend = 8;
+    // 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;
     } else if (NS_FAILED(mStopOnClose)) {
       /* result of abort session - give up */
       StopSession(mStopOnClose);
     } else {
       /* wait for reciprocal close from server */
@@ -1979,34 +2009,40 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI
   rv = SetupRequest();
   if (NS_FAILED(rv))
     return rv;
 
   return ApplyForAdmission();
 }
 
 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");
 
   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;
+
   mRequestedClose = 1;
-
+  mScriptCloseReason = reason;
+  mScriptCloseCode = code;
+    
   return mSocketThread->Dispatch(new nsPostMessage(this, kFinMessage, -1),
                                  nsIEventTarget::DISPATCH_NORMAL);
 }
 
 NS_IMETHODIMP
 WebSocketChannel::SendMsg(const nsACString &aMsg)
 {
   LOG(("WebSocketChannel::SendMsg() %p\n", this));
--- a/netwerk/protocol/websocket/WebSocketChannel.h
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -94,17 +94,17 @@ public:
   NS_DECL_NSICHANNELEVENTSINK
 
   // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
   //
   NS_IMETHOD AsyncOpen(nsIURI *aURI,
                        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);
 
   WebSocketChannel();
   static void Shutdown();
 
   enum {
@@ -118,25 +118,16 @@ public:
     kPing =         0x9,
     kPong =         0xA
   };
 
   const static PRUint32 kControlFrameMask   = 0x8;
   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();
 
 private:
   friend class nsPostMessage;
   friend class nsWSAdmissionManager;
 
   void SendMsgInternal(nsCString *aMsg, PRInt32 datalen);
@@ -247,17 +238,20 @@ private:
   PRUint32                        mPingOutstanding           : 1;
   PRUint32                        mAllowCompression          : 1;
   PRUint32                        mAutoFollowRedirects       : 1;
   PRUint32                        mReleaseOnTransmit         : 1;
   PRUint32                        mTCPClosed                 : 1;
 
   PRInt32                         mMaxMessageSize;
   nsresult                        mStopOnClose;
-  PRUint16                        mCloseCode;
+  PRUint16                        mServerCloseCode;
+  nsCString                       mServerCloseReason;
+  PRUint16                        mScriptCloseCode;
+  nsCString                       mScriptCloseReason;
 
   // These are for the read buffers
   PRUint8                        *mFramePtr;
   PRUint8                        *mBuffer;
   PRUint8                         mFragmentOpcode;
   PRUint32                        mFragmentAccumulator;
   PRUint32                        mBuffered;
   PRUint32                        mBufferSize;
--- a/netwerk/protocol/websocket/WebSocketChannelChild.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -286,46 +286,54 @@ WebSocketChannelChild::OnAcknowledge(con
     AutoEventEnqueuer ensureSerialDispatch(mEventQ);;
     mListener->OnAcknowledge(mContext, 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);
   }
 }
 
 NS_IMETHODIMP
 WebSocketChannelChild::AsyncOpen(nsIURI *aURI,
                                  const nsACString &aOrigin,
                                  nsIWebSocketListener *aListener,
                                  nsISupports *aContext)
@@ -356,21 +364,21 @@ WebSocketChannelChild::AsyncOpen(nsIURI 
   mListener = aListener;
   mContext = aContext;
   mOrigin = aOrigin;
 
   return NS_OK;
 }
 
 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;
 }
 
 NS_IMETHODIMP
 WebSocketChannelChild::SendMsg(const nsACString &aMsg)
 {
   LOG(("WebSocketChannelChild::SendMsg() %p\n", this));
--- a/netwerk/protocol/websocket/WebSocketChannelChild.h
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -59,39 +59,39 @@ class WebSocketChannelChild : public Bas
   NS_DECL_ISUPPORTS
 
   // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
   //
   NS_SCRIPTABLE NS_IMETHOD AsyncOpen(nsIURI *aURI,
                                      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);
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
  private:
   bool RecvOnStart(const nsCString& aProtocol);
   bool RecvOnStop(const nsresult& aStatusCode);
   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);
   void OnStop(const nsresult& aStatusCode);
   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;
   bool mIPCOpen;
 
   friend class StartEvent;
   friend class StopEvent;
   friend class MessageEvent;
--- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -100,21 +100,21 @@ WebSocketChannelParent::RecvAsyncOpen(co
   return true;
 
 fail:
   mChannel = nsnull;
   return SendOnStop(rv);
 }
 
 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;
 }
 
 bool
 WebSocketChannelParent::RecvSendMsg(const nsCString& aMsg)
 {
@@ -198,20 +198,21 @@ WebSocketChannelParent::OnAcknowledge(ns
   LOG(("WebSocketChannelParent::OnAcknowledge() %p\n", this));
   if (!mIPCOpen || !SendOnAcknowledge(aSize)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 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;
 }
 
 void
 WebSocketChannelParent::ActorDestroy(ActorDestroyReason why)
 {
--- a/netwerk/protocol/websocket/WebSocketChannelParent.h
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -62,17 +62,17 @@ class WebSocketChannelParent : public PW
 
   WebSocketChannelParent(nsIAuthPromptProvider* aAuthProvider);
 
  private:
   bool RecvAsyncOpen(const IPC::URI& aURI,
                      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();
 
   void ActorDestroy(ActorDestroyReason why);
 
   nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
   nsCOMPtr<nsIWebSocketChannel> mChannel;
--- a/netwerk/protocol/websocket/nsIWebSocketChannel.idl
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -39,17 +39,17 @@
 
 interface nsIURI;
 interface nsIInterfaceRequestor;
 interface nsILoadGroup;
 interface nsIWebSocketListener;
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(398a2460-a46d-11e0-8264-0800200c9a66)]
+[scriptable, uuid(783029cf-75b5-439e-8e47-86b390202b4c)]
 interface nsIWebSocketChannel : nsISupports
 {
     /**
      * The original URI used to construct the protocol connection. This is used
      * in the case of a redirect or URI "resolution" (e.g. resolving a
      * resource: URI to a file: URI) so that the original pre-redirect
      * URI can still be obtained.  This is never null.
      */
@@ -101,19 +101,32 @@ interface nsIWebSocketChannel : nsISuppo
                    in ACString aOrigin,
                    in nsIWebSocketListener aListener,
                    in nsISupports aContext);
 
     /*
      * 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.
      *
      * @param aMsg the utf8 string to send
      */
     void sendMsg(in AUTF8String aMsg);
 
     /**
--- a/netwerk/protocol/websocket/nsIWebSocketListener.idl
+++ b/netwerk/protocol/websocket/nsIWebSocketListener.idl
@@ -38,17 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 /**
  * 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
 {
     /**
      * Called to signify the establishment of the message stream.
      * Any listener that receives onStart will also receive OnStop.
      *
      * @param aContext user defined context
      */
@@ -96,14 +96,20 @@ interface nsIWebSocketListener : nsISupp
      * Called to inform receipt of WebSocket Close message from server.
      * In the case of errors onStop() can be called without ever
      * receiving server close.
      *
      * No additional messages through onMessageAvailable(),
      * 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);
     
 };