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 73794 e054dec99f3e9559772ec78a46b6cf957754d4f2
parent 73793 a8045a08cbc9fb935d5039b928772717c9d577d0
child 73795 3cca53b77c110c9c406c376e6be6ff2d9018fb07
push idunknown
push userunknown
push dateunknown
reviewerssicking, biesi, bz
bugs674716
milestone8.0a1
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);
     
 };