[WebSocket] Add extension attribute support
authorbashi@chromium.org <bashi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Feb 2012 02:11:46 +0000 (02:11 +0000)
committerbashi@chromium.org <bashi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Feb 2012 02:11:46 +0000 (02:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=78557

Source/WebCore:

Implement WebSocket "extensions" attribute that holds a list of
extension the server accepted. No change in behavior at this time
because we don't send any extension on handshake.

Reviewed by Kent Tamura.

No new tests. http/tests/websocket/tests/hybi/extensions.html checks the value of this attribute.

* websockets/ThreadableWebSocketChannel.h: Add extensions().
(ThreadableWebSocketChannel):
* websockets/ThreadableWebSocketChannelClientWrapper.cpp:
(WebCore::ThreadableWebSocketChannelClientWrapper::extensions): Added.
(WebCore):
(WebCore::ThreadableWebSocketChannelClientWrapper::setExtensions): Added.
* websockets/ThreadableWebSocketChannelClientWrapper.h:
(ThreadableWebSocketChannelClientWrapper):
* websockets/WebSocket.cpp: Added m_extensions member variable.
(WebCore::WebSocket::WebSocket):
(WebCore::WebSocket::extensions): Returns m_extensions.
* websockets/WebSocket.h:
* websockets/WebSocketChannel.cpp:
(WebCore::WebSocketChannel::extensions): Added.
(WebCore):
* websockets/WebSocketChannel.h:
(WebSocketChannel):
* websockets/WebSocketExtensionDispatcher.cpp:
(WebCore::WebSocketExtensionDispatcher::fail): Added.
(WebCore::WebSocketExtensionDispatcher::processHeaderValue): Stores accepted extensions.
(WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
(WebCore):
(WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
* websockets/WebSocketExtensionDispatcher.h:
(WebSocketExtensionDispatcher):
* websockets/WebSocketHandshake.cpp:
(WebCore::WebSocketHandshake::acceptedExtensions): Added.
(WebCore):
* websockets/WebSocketHandshake.h:
* websockets/WorkerThreadableWebSocketChannel.cpp:
(WebCore::WorkerThreadableWebSocketChannel::extensions): Added.
(WebCore):
(WebCore::workerContextDidConnect): Calls ThreadableWebSocketChannelClientWrapper::setExtensions().
(WebCore::WorkerThreadableWebSocketChannel::Peer::didConnect): Passes extensions as an argument.
* websockets/WorkerThreadableWebSocketChannel.h:
(WorkerThreadableWebSocketChannel):

Source/WebKit/chromium:

Add WebSocketExtensionDispatcher::acceptedExtensions() checks.

Reviewed by Kent Tamura.

* tests/WebSocketExtensionDispatcherTest.cpp:
(WebCore::TEST_F):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@107769 268f45cc-cd09-0410-ab3c-d52691b4dbfc

16 files changed:
Source/WebCore/ChangeLog
Source/WebCore/websockets/ThreadableWebSocketChannel.h
Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.cpp
Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.h
Source/WebCore/websockets/WebSocket.cpp
Source/WebCore/websockets/WebSocket.h
Source/WebCore/websockets/WebSocketChannel.cpp
Source/WebCore/websockets/WebSocketChannel.h
Source/WebCore/websockets/WebSocketExtensionDispatcher.cpp
Source/WebCore/websockets/WebSocketExtensionDispatcher.h
Source/WebCore/websockets/WebSocketHandshake.cpp
Source/WebCore/websockets/WebSocketHandshake.h
Source/WebCore/websockets/WorkerThreadableWebSocketChannel.cpp
Source/WebCore/websockets/WorkerThreadableWebSocketChannel.h
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/tests/WebSocketExtensionDispatcherTest.cpp

index 4592f46..9975c00 100644 (file)
@@ -1,3 +1,53 @@
+2012-02-14  Kenichi Ishibashi  <bashi@chromium.org>
+
+        [WebSocket] Add extension attribute support
+        https://bugs.webkit.org/show_bug.cgi?id=78557
+
+        Implement WebSocket "extensions" attribute that holds a list of
+        extension the server accepted. No change in behavior at this time
+        because we don't send any extension on handshake.
+
+        Reviewed by Kent Tamura.
+
+        No new tests. http/tests/websocket/tests/hybi/extensions.html checks the value of this attribute.
+
+        * websockets/ThreadableWebSocketChannel.h: Add extensions().
+        (ThreadableWebSocketChannel):
+        * websockets/ThreadableWebSocketChannelClientWrapper.cpp:
+        (WebCore::ThreadableWebSocketChannelClientWrapper::extensions): Added.
+        (WebCore):
+        (WebCore::ThreadableWebSocketChannelClientWrapper::setExtensions): Added.
+        * websockets/ThreadableWebSocketChannelClientWrapper.h:
+        (ThreadableWebSocketChannelClientWrapper):
+        * websockets/WebSocket.cpp: Added m_extensions member variable.
+        (WebCore::WebSocket::WebSocket):
+        (WebCore::WebSocket::extensions): Returns m_extensions.
+        * websockets/WebSocket.h:
+        * websockets/WebSocketChannel.cpp:
+        (WebCore::WebSocketChannel::extensions): Added.
+        (WebCore):
+        * websockets/WebSocketChannel.h:
+        (WebSocketChannel):
+        * websockets/WebSocketExtensionDispatcher.cpp:
+        (WebCore::WebSocketExtensionDispatcher::fail): Added.
+        (WebCore::WebSocketExtensionDispatcher::processHeaderValue): Stores accepted extensions.
+        (WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
+        (WebCore):
+        (WebCore::WebSocketExtensionDispatcher::acceptedExtensions): Added.
+        * websockets/WebSocketExtensionDispatcher.h:
+        (WebSocketExtensionDispatcher):
+        * websockets/WebSocketHandshake.cpp:
+        (WebCore::WebSocketHandshake::acceptedExtensions): Added.
+        (WebCore):
+        * websockets/WebSocketHandshake.h:
+        * websockets/WorkerThreadableWebSocketChannel.cpp:
+        (WebCore::WorkerThreadableWebSocketChannel::extensions): Added.
+        (WebCore):
+        (WebCore::workerContextDidConnect): Calls ThreadableWebSocketChannelClientWrapper::setExtensions().
+        (WebCore::WorkerThreadableWebSocketChannel::Peer::didConnect): Passes extensions as an argument.
+        * websockets/WorkerThreadableWebSocketChannel.h:
+        (WorkerThreadableWebSocketChannel):
+
 2012-02-14  Kentaro Hara  <haraken@chromium.org>
 
         Rename [JSGenerateToJS] to [JSGenerateToJSObject]
index e93cf86..a14582a 100644 (file)
@@ -53,6 +53,7 @@ public:
     virtual bool useHixie76Protocol() = 0;
     virtual void connect(const KURL&, const String& protocol) = 0;
     virtual String subprotocol() = 0; // Will be available after didConnect() callback is invoked.
+    virtual String extensions() = 0; // Will be available after didConnect() callback is invoked.
     virtual bool send(const String& message) = 0;
     virtual bool send(const ArrayBuffer&) = 0;
     virtual bool send(const Blob&) = 0;
index 3be04a9..0db708f 100644 (file)
@@ -95,6 +95,21 @@ void ThreadableWebSocketChannelClientWrapper::setSubprotocol(const String& subpr
         memcpy(m_subprotocol.data(), subprotocol.characters(), sizeof(UChar) * length);
 }
 
+String ThreadableWebSocketChannelClientWrapper::extensions() const
+{
+    if (m_extensions.isEmpty())
+        return String("");
+    return String(m_extensions);
+}
+
+void ThreadableWebSocketChannelClientWrapper::setExtensions(const String& extensions)
+{
+    unsigned length = extensions.length();
+    m_extensions.resize(length);
+    if (length)
+        memcpy(m_extensions.data(), extensions.characters(), sizeof(UChar) * length);
+}
+
 bool ThreadableWebSocketChannelClientWrapper::sendRequestResult() const
 {
     return m_sendRequestResult;
index ec9397c..e83a80a 100644 (file)
@@ -59,9 +59,11 @@ public:
     bool useHixie76Protocol() const;
     void setUseHixie76Protocol(bool);
 
-    // Subprotocol is cached too. Will be available when didConnect() callback is invoked.
+    // Subprotocol and extensions are cached too. Will be available when didConnect() callback is invoked.
     String subprotocol() const;
     void setSubprotocol(const String&);
+    String extensions() const;
+    void setExtensions(const String&);
 
     bool sendRequestResult() const;
     void setSendRequestResult(bool);
@@ -95,7 +97,9 @@ protected:
     WebSocketChannelClient* m_client;
     bool m_syncMethodDone;
     bool m_useHixie76Protocol;
-    Vector<UChar> m_subprotocol; // ThreadSafeRefCounted must not have a String member variable.
+    // ThreadSafeRefCounted must not have String member variables.
+    Vector<UChar> m_subprotocol;
+    Vector<UChar> m_extensions;
     bool m_sendRequestResult;
     unsigned long m_bufferedAmount;
     bool m_suspended;
index 3010ea3..36dbb6f 100644 (file)
@@ -155,6 +155,7 @@ WebSocket::WebSocket(ScriptExecutionContext* context)
     , m_binaryType(BinaryTypeBlob)
     , m_useHixie76Protocol(true)
     , m_subprotocol("")
+    , m_extensions("")
 {
 }
 
@@ -387,8 +388,7 @@ String WebSocket::extensions() const
 {
     if (m_useHixie76Protocol)
         return String();
-    // WebSocket protocol extension is not supported yet.
-    return "";
+    return m_extensions;
 }
 
 String WebSocket::binaryType() const
index cae57d2..1e44245 100644 (file)
@@ -136,6 +136,7 @@ private:
     BinaryType m_binaryType;
     bool m_useHixie76Protocol;
     String m_subprotocol;
+    String m_extensions;
 };
 
 } // namespace WebCore
index 53436e3..18f4351 100644 (file)
@@ -153,6 +153,17 @@ String WebSocketChannel::subprotocol()
     return serverProtocol;
 }
 
+String WebSocketChannel::extensions()
+{
+    LOG(Network, "WebSocketChannel %p extensions", this);
+    if (!m_handshake || m_handshake->mode() != WebSocketHandshake::Connected)
+        return "";
+    String extensions = m_handshake->acceptedExtensions();
+    if (extensions.isNull())
+        return "";
+    return extensions;
+}
+
 bool WebSocketChannel::send(const String& message)
 {
     LOG(Network, "WebSocketChannel %p send %s", this, message.utf8().data());
index 9f9bab8..8bdb47f 100644 (file)
@@ -68,6 +68,7 @@ public:
     virtual bool useHixie76Protocol() OVERRIDE;
     virtual void connect(const KURL&, const String& protocol) OVERRIDE;
     virtual String subprotocol() OVERRIDE;
+    virtual String extensions() OVERRIDE;
     virtual bool send(const String& message) OVERRIDE;
     virtual bool send(const ArrayBuffer&) OVERRIDE;
     virtual bool send(const Blob&) OVERRIDE;
index d85092a..7c592ad 100644 (file)
@@ -35,8 +35,8 @@
 #include "WebSocketExtensionDispatcher.h"
 
 #include <wtf/ASCIICType.h>
+#include <wtf/HashMap.h>
 #include <wtf/text/CString.h>
-#include <wtf/text/StringBuilder.h>
 #include <wtf/text/StringHash.h>
 
 namespace WebCore {
@@ -173,6 +173,28 @@ const String WebSocketExtensionDispatcher::createHeaderValue() const
     return builder.toString();
 }
 
+void WebSocketExtensionDispatcher::appendAcceptedExtension(const String& extensionToken, HashMap<String, String>& extensionParameters)
+{
+    if (!m_acceptedExtensionsBuilder.isEmpty())
+        m_acceptedExtensionsBuilder.append(", ");
+    m_acceptedExtensionsBuilder.append(extensionToken);
+    // FIXME: Should use ListHashSet to keep the order of the parameters.
+    for (HashMap<String, String>::const_iterator iterator = extensionParameters.begin(); iterator != extensionParameters.end(); ++iterator) {
+        m_acceptedExtensionsBuilder.append("; ");
+        m_acceptedExtensionsBuilder.append(iterator->first);
+        if (!iterator->second.isNull()) {
+            m_acceptedExtensionsBuilder.append("=");
+            m_acceptedExtensionsBuilder.append(iterator->second);
+        }
+    }
+}
+
+void WebSocketExtensionDispatcher::fail(const String& reason)
+{
+    m_failureReason = reason;
+    m_acceptedExtensionsBuilder.clear();
+}
+
 bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
 {
     if (!headerValue.length())
@@ -180,17 +202,16 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
 
     // If we don't send Sec-WebSocket-Extensions header, the server should not return the header.
     if (!m_processors.size()) {
-        m_failureReason = "Received unexpected Sec-WebSocket-Extensions header";
+        fail("Received unexpected Sec-WebSocket-Extensions header");
         return false;
     }
 
     const CString headerValueData = headerValue.utf8();
     ExtensionParser parser(headerValueData.data(), headerValueData.data() + headerValueData.length());
-
     while (!parser.finished()) {
         // Parse extension-token.
         if (!parser.consumeToken()) {
-            m_failureReason = "Sec-WebSocket-Extensions header is invalid";
+            fail("Sec-WebSocket-Extensions header is invalid");
             return false;
         }
         String extensionToken = parser.currentToken();
@@ -199,7 +220,7 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
         HashMap<String, String> extensionParameters;
         while (parser.consumeCharacter(';')) {
             if (!parser.consumeToken()) {
-                m_failureReason = "Sec-WebSocket-Extensions header is invalid";
+                fail("Sec-WebSocket-Extensions header is invalid");
                 return false;
             }
 
@@ -208,14 +229,14 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
                 if (parser.consumeQuotedStringOrToken())
                     extensionParameters.add(parameterToken, parser.currentToken());
                 else {
-                    m_failureReason = "Sec-WebSocket-Extensions header is invalid";
+                    fail("Sec-WebSocket-Extensions header is invalid");
                     return false;
                 }
             } else
                 extensionParameters.add(parameterToken, String());
         }
         if (!parser.finished() && !parser.consumeCharacter(',')) {
-            m_failureReason = "Sec-WebSocket-Extensions header is invalid";
+            fail("Sec-WebSocket-Extensions header is invalid");
             return false;
         }
 
@@ -223,21 +244,30 @@ bool WebSocketExtensionDispatcher::processHeaderValue(const String& headerValue)
         for (index = 0; index < m_processors.size(); ++index) {
             WebSocketExtensionProcessor* processor = m_processors[index].get();
             if (extensionToken == processor->extensionToken()) {
-                if (processor->processResponse(extensionParameters))
+                if (processor->processResponse(extensionParameters)) {
+                    appendAcceptedExtension(extensionToken, extensionParameters);
                     break;
-                m_failureReason = processor->failureReason();
+                }
+                fail(processor->failureReason());
                 return false;
             }
         }
         // There is no extension which can process the response.
         if (index == m_processors.size()) {
-            m_failureReason = "Received unexpected extension: " + extensionToken;
+            fail("Received unexpected extension: " + extensionToken);
             return false;
         }
     }
     return parser.parsedSuccessfully();
 }
 
+String WebSocketExtensionDispatcher::acceptedExtensions() const
+{
+    if (m_acceptedExtensionsBuilder.isEmpty())
+        return String();
+    return m_acceptedExtensionsBuilder.toStringPreserveCapacity();
+}
+
 String WebSocketExtensionDispatcher::failureReason() const
 {
     return m_failureReason;
index f882bb9..35cd769 100644 (file)
@@ -37,6 +37,7 @@
 #include <wtf/OwnPtr.h>
 #include <wtf/PassOwnPtr.h>
 #include <wtf/Vector.h>
+#include <wtf/text/StringBuilder.h>
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
@@ -50,10 +51,15 @@ public:
     const String createHeaderValue() const;
 
     bool processHeaderValue(const String&);
+    String acceptedExtensions() const;
     String failureReason() const;
 
 private:
+    void appendAcceptedExtension(const String& extensionToken, HashMap<String, String>& extensionParameters);
+    void fail(const String& reason);
+
     Vector<OwnPtr<WebSocketExtensionProcessor> > m_processors;
+    StringBuilder m_acceptedExtensionsBuilder;
     String m_failureReason;
 };
 
index b261557..8cc509e 100644 (file)
@@ -476,6 +476,11 @@ String WebSocketHandshake::serverWebSocketAccept() const
     return m_response.headerFields().get("sec-websocket-accept");
 }
 
+String WebSocketHandshake::acceptedExtensions() const
+{
+    return m_extensionDispatcher.acceptedExtensions();
+}
+
 const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const
 {
     return m_response;
index 9777fb9..b025f0c 100644 (file)
@@ -84,6 +84,7 @@ public:
     String serverUpgrade() const;
     String serverConnection() const;
     String serverWebSocketAccept() const; // Only for hybi-10 handshake.
+    String acceptedExtensions() const;
 
     const WebSocketHandshakeResponse& serverHandshakeResponse() const;
 
index f0e63e1..af03a4e 100644 (file)
@@ -82,6 +82,12 @@ String WorkerThreadableWebSocketChannel::subprotocol()
     return m_workerClientWrapper->subprotocol();
 }
 
+String WorkerThreadableWebSocketChannel::extensions()
+{
+    ASSERT(m_workerClientWrapper);
+    return m_workerClientWrapper->extensions();
+}
+
 bool WorkerThreadableWebSocketChannel::send(const String& message)
 {
     if (!m_bridge)
@@ -262,17 +268,18 @@ void WorkerThreadableWebSocketChannel::Peer::resume()
     m_mainWebSocketChannel->resume();
 }
 
-static void workerContextDidConnect(ScriptExecutionContext* context, PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& subprotocol)
+static void workerContextDidConnect(ScriptExecutionContext* context, PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& subprotocol, const String& extensions)
 {
     ASSERT_UNUSED(context, context->isWorkerContext());
     workerClientWrapper->setSubprotocol(subprotocol);
+    workerClientWrapper->setExtensions(extensions);
     workerClientWrapper->didConnect();
 }
 
 void WorkerThreadableWebSocketChannel::Peer::didConnect()
 {
     ASSERT(isMainThread());
-    m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidConnect, m_workerClientWrapper, m_mainWebSocketChannel->subprotocol()), m_taskMode);
+    m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidConnect, m_workerClientWrapper, m_mainWebSocketChannel->subprotocol(), m_mainWebSocketChannel->extensions()), m_taskMode);
 }
 
 static void workerContextDidReceiveMessage(ScriptExecutionContext* context, PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& message)
index 2b1fa38..317fce9 100644 (file)
@@ -64,6 +64,7 @@ public:
     virtual bool useHixie76Protocol() OVERRIDE;
     virtual void connect(const KURL&, const String& protocol) OVERRIDE;
     virtual String subprotocol() OVERRIDE;
+    virtual String extensions() OVERRIDE;
     virtual bool send(const String& message) OVERRIDE;
     virtual bool send(const ArrayBuffer&) OVERRIDE;
     virtual bool send(const Blob&) OVERRIDE;
index d2048eb..dc17ecb 100644 (file)
@@ -1,5 +1,17 @@
 2012-02-14  Kenichi Ishibashi  <bashi@chromium.org>
 
+        [WebSocket] Add extension attribute support
+        https://bugs.webkit.org/show_bug.cgi?id=78557
+
+        Add WebSocketExtensionDispatcher::acceptedExtensions() checks.
+
+        Reviewed by Kent Tamura.
+
+        * tests/WebSocketExtensionDispatcherTest.cpp:
+        (WebCore::TEST_F):
+
+2012-02-14  Kenichi Ishibashi  <bashi@chromium.org>
+
         [WebSocket] Add deflater/inflater classes.
         https://bugs.webkit.org/show_bug.cgi?id=78449
 
index fa0b7d7..358983b 100644 (file)
@@ -90,6 +90,7 @@ TEST_F(WebSocketExtensionDispatcherTest, TestSingle)
     EXPECT_TRUE(m_extensions.processHeaderValue("deflate-frame"));
     EXPECT_EQ(1UL, m_parsedExtensionTokens.size());
     EXPECT_EQ("deflate-frame", m_parsedExtensionTokens[0]);
+    EXPECT_EQ("deflate-frame", m_extensions.acceptedExtensions());
     EXPECT_EQ(0, m_parsedParameters[0].size());
 }
 
@@ -122,6 +123,8 @@ TEST_F(WebSocketExtensionDispatcherTest, TestMultiple)
     addMockProcessor("mux");
     addMockProcessor("deflate-frame");
     EXPECT_TRUE(m_extensions.processHeaderValue("mux ;  max-channels =4;flow-control, deflate-frame  "));
+    EXPECT_TRUE(m_extensions.acceptedExtensions().find("mux") != notFound);
+    EXPECT_TRUE(m_extensions.acceptedExtensions().find("deflate-frame") != notFound);
     for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) {
         EXPECT_EQ(expected[i].token, m_parsedExtensionTokens[i]);
         const HashMap<String, String>& expectedParameters = expected[i].parameters;
@@ -169,6 +172,7 @@ TEST_F(WebSocketExtensionDispatcherTest, TestInvalid)
         addMockProcessor("x-foo");
         addMockProcessor("x-bar");
         EXPECT_FALSE(m_extensions.processHeaderValue(inputs[i]));
+        EXPECT_TRUE(m_extensions.acceptedExtensions().isNull());
     }
 }