Bug 959114 - Remove websockify dependency from LayerScope. r=vlad, r=cku
authorMorris Tseng <mtseng@mozilla.com>
Mon, 10 Feb 2014 08:45:15 -0500
changeset 167850 5f8d42207dfdb4f71ed97a7761bde10944dfc21a
parent 167849 77bb6be4adb4c3d5b4ff51c9b95c8ec56de4f589
child 167851 db980acda4f1cdef680b71e00eb1d18d01e2975a
push id26190
push userryanvm@gmail.com
push dateMon, 10 Feb 2014 20:37:53 +0000
treeherdermozilla-central@07739c5c874f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvlad, cku
bugs959114
milestone30.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 959114 - Remove websockify dependency from LayerScope. r=vlad, r=cku
gfx/layers/LayerScope.cpp
--- a/gfx/layers/LayerScope.cpp
+++ b/gfx/layers/LayerScope.cpp
@@ -5,16 +5,17 @@
 
 /* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */
 #include "LayerScope.h"
 
 #include "Composer2D.h"
 #include "Effects.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Endian.h"
 #include "TexturePoolOGL.h"
 #include "mozilla/layers/TextureHostOGL.h"
 
 #include "gfxColor.h"
 #include "gfxContext.h"
 #include "gfxUtils.h"
 #include "gfxPlatform.h"
 #include "nsIWidget.h"
@@ -24,21 +25,26 @@
 #include "GLReadTexImageHelper.h"
 
 #include "nsIServiceManager.h"
 #include "nsIConsoleService.h"
 
 #include <memory>
 #include "mozilla/Compression.h"
 #include "mozilla/LinkedList.h"
+#include "mozilla/Base64.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticPtr.h"
 #include "nsThreadUtils.h"
 #include "nsISocketTransport.h"
 #include "nsIServerSocket.h"
+#include "nsReadLine.h"
 #include "nsNetCID.h"
 #include "nsIOutputStream.h"
+#include "nsIAsyncInputStream.h"
 #include "nsIEventTarget.h"
 #include "nsProxyRelease.h"
 
 #ifdef __GNUC__
 #define PACKED_STRUCT __attribute__((packed))
 #else
 #define PACKED_STRUCT
 #endif
@@ -47,23 +53,277 @@ namespace mozilla {
 namespace layers {
 
 using namespace mozilla::Compression;
 using namespace mozilla::gfx;
 using namespace mozilla::gl;
 using namespace mozilla;
 
 class DebugDataSender;
+class DebugGLData;
 
-static bool gDebugConnected = false;
-static nsCOMPtr<nsIServerSocket> gDebugServerSocket;
-static nsCOMPtr<nsIThread> gDebugSenderThread;
-static nsCOMPtr<nsISocketTransport> gDebugSenderTransport;
-static nsCOMPtr<nsIOutputStream> gDebugStream;
-static nsCOMPtr<DebugDataSender> gCurrentSender;
+/* This class handle websocket protocol which included
+ * handshake and data frame's header
+ */
+class LayerScopeWebSocketHandler : public nsIInputStreamCallback {
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    enum SocketStateType {
+        NoHandshake,
+        HandshakeSuccess,
+        HandshakeFailed
+    };
+
+    LayerScopeWebSocketHandler()
+        : mState(NoHandshake)
+    { }
+
+    virtual ~LayerScopeWebSocketHandler()
+    {
+        if (mTransport) {
+            mTransport->Close(NS_OK);
+        }
+    }
+
+    void OpenStream(nsISocketTransport* aTransport) {
+        MOZ_ASSERT(aTransport);
+
+        mTransport = aTransport;
+        mTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING,
+                                     0,
+                                     0,
+                                     getter_AddRefs(mOutputStream));
+
+        nsCOMPtr<nsIInputStream> debugInputStream;
+        mTransport->OpenInputStream(0,
+                                    0,
+                                    0,
+                                    getter_AddRefs(debugInputStream));
+        mInputStream = do_QueryInterface(debugInputStream);
+        mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+    }
+
+    bool WriteToStream(void *ptr, uint32_t size) {
+        if (mState == NoHandshake) {
+            // Not yet handshake, just return true in case of
+            // LayerScope remove this handle
+            return true;
+        } else if (mState == HandshakeFailed) {
+            return false;
+        }
+
+        // Generate WebSocket header
+        uint8_t wsHeader[10];
+        int wsHeaderSize = 0;
+        const uint8_t opcode = 0x2;
+        wsHeader[0] = 0x80 | (opcode & 0x0f); // FIN + opcode;
+        if (size <= 125) {
+            wsHeaderSize = 2;
+            wsHeader[1] = size;
+        } else if (size < 65536) {
+            wsHeaderSize = 4;
+            wsHeader[1] = 0x7E;
+            NetworkEndian::writeUint16(wsHeader + 2, size);
+        } else {
+            wsHeaderSize = 10;
+            wsHeader[1] = 0x7F;
+            NetworkEndian::writeUint64(wsHeader + 2, size);
+        }
+
+        // Send WebSocket header
+        nsresult rv;
+        uint32_t cnt;
+        rv = mOutputStream->Write(reinterpret_cast<char*>(wsHeader),
+                                 wsHeaderSize, &cnt);
+        if (NS_FAILED(rv))
+            return false;
+
+        uint32_t written = 0;
+        while (written < size) {
+            uint32_t cnt;
+            rv = mOutputStream->Write(reinterpret_cast<char*>(ptr) + written,
+                                     size - written, &cnt);
+            if (NS_FAILED(rv))
+                return false;
+
+            written += cnt;
+        }
+
+        return true;
+    }
+
+    // nsIInputStreamCallback
+    NS_IMETHODIMP OnInputStreamReady(nsIAsyncInputStream *stream) MOZ_OVERRIDE
+    {
+        nsTArray<nsCString> protocolString;
+        ReadInputStreamData(protocolString);
+
+        if (WebSocketHandshake(protocolString)) {
+            mState = HandshakeSuccess;
+        } else {
+            mState = HandshakeFailed;
+        }
+        return NS_OK;
+    }
+private:
+    void ReadInputStreamData(nsTArray<nsCString>& aProtocolString)
+    {
+        nsLineBuffer<char> lineBuffer;
+        nsCString line;
+        bool more = true;
+        do {
+            NS_ReadLine(mInputStream.get(), &lineBuffer, line, &more);
+
+            if (line.Length() > 0) {
+                aProtocolString.AppendElement(line);
+            }
+        } while (more && line.Length() > 0);
+    }
+
+    bool WebSocketHandshake(nsTArray<nsCString>& aProtocolString)
+    {
+        nsresult rv;
+        bool isWebSocket = false;
+        nsCString version;
+        nsCString wsKey;
+        nsCString protocol;
+
+        // Validate WebSocket client request.
+        if (aProtocolString.Length() == 0)
+            return false;
+
+        // Check that the HTTP method is GET
+        const char* HTTP_METHOD = "GET ";
+        if (strncmp(aProtocolString[0].get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) {
+            return false;
+        }
+
+        for (uint32_t i = 1; i < aProtocolString.Length(); ++i) {
+            const char* line = aProtocolString[i].get();
+            const char* prop_pos = strchr(line, ':');
+            if (prop_pos != nullptr) {
+                nsCString key(line, prop_pos - line);
+                nsCString value(prop_pos + 2);
+                if (key.EqualsIgnoreCase("upgrade") &&
+                    value.EqualsIgnoreCase("websocket")) {
+                    isWebSocket = true;
+                } else if (key.EqualsIgnoreCase("sec-websocket-version")) {
+                    version = value;
+                } else if (key.EqualsIgnoreCase("sec-websocket-key")) {
+                    wsKey = value;
+                } else if (key.EqualsIgnoreCase("sec-websocket-protocol")) {
+                    protocol = value;
+                }
+            }
+        }
+
+        if (!isWebSocket) {
+            return false;
+        }
+
+        if (!(version.Equals("7") || version.Equals("8") || version.Equals("13"))) {
+            return false;
+        }
+
+        if (!(protocol.EqualsIgnoreCase("binary"))) {
+            return false;
+        }
+
+        // Client request is valid. Start to generate and send server response.
+        nsAutoCString guid("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+        nsAutoCString res;
+        SHA1Sum sha1;
+        nsCString combined(wsKey + guid);
+        sha1.update(combined.get(), combined.Length());
+        uint8_t digest[SHA1Sum::HashSize]; // SHA1 digests are 20 bytes long.
+        sha1.finish(digest);
+        nsCString newString(reinterpret_cast<char*>(digest), SHA1Sum::HashSize);
+        Base64Encode(newString, res);
+
+        nsCString response("HTTP/1.1 101 Switching Protocols\r\n");
+        response.Append("Upgrade: websocket\r\n");
+        response.Append("Connection: Upgrade\r\n");
+        response.Append(nsCString("Sec-WebSocket-Accept: ") + res + nsCString("\r\n"));
+        response.Append("Sec-WebSocket-Protocol: binary\r\n\r\n");
+        uint32_t written = 0;
+        uint32_t size = response.Length();
+        while (written < size) {
+            uint32_t cnt;
+            rv = mOutputStream->Write(const_cast<char*>(response.get()) + written,
+                                     size - written, &cnt);
+            if (NS_FAILED(rv))
+                return false;
+
+            written += cnt;
+        }
+        mOutputStream->Flush();
+
+        return true;
+    }
+
+    nsCOMPtr<nsIOutputStream> mOutputStream;
+    nsCOMPtr<nsIAsyncInputStream> mInputStream;
+    nsCOMPtr<nsISocketTransport> mTransport;
+    SocketStateType mState;
+};
+
+NS_IMPL_ISUPPORTS1(LayerScopeWebSocketHandler, nsIInputStreamCallback);
+
+class LayerScopeWebSocketManager {
+public:
+    LayerScopeWebSocketManager();
+    ~LayerScopeWebSocketManager();
+
+    void AddConnection(nsISocketTransport *aTransport)
+    {
+        MOZ_ASSERT(aTransport);
+        nsRefPtr<LayerScopeWebSocketHandler> temp = new LayerScopeWebSocketHandler();
+        temp->OpenStream(aTransport);
+        mHandlers.AppendElement(temp.get());
+    }
+
+    void RemoveConnection(uint32_t aIndex)
+    {
+        MOZ_ASSERT(aIndex < mHandlers.Length());
+        mHandlers.RemoveElementAt(aIndex);
+    }
+
+    void RemoveAllConnections()
+    {
+        mHandlers.Clear();
+    }
+
+    bool WriteAll(void *ptr, uint32_t size)
+    {
+        for (int32_t i = mHandlers.Length() - 1; i >= 0; --i) {
+            if (!mHandlers[i]->WriteToStream(ptr, size)) {
+                // Send failed, remove this handler
+                RemoveConnection(i);
+            }
+        }
+
+        return true;
+    }
+
+    bool IsConnected()
+    {
+        return (mHandlers.Length() != 0) ? true : false;
+    }
+
+    void AppendDebugData(DebugGLData *aDebugData);
+    void DispatchDebugData();
+private:
+    nsTArray<nsRefPtr<LayerScopeWebSocketHandler> > mHandlers;
+    nsCOMPtr<nsIThread> mDebugSenderThread;
+    nsCOMPtr<DebugDataSender> mCurrentSender;
+    nsCOMPtr<nsIServerSocket> mServerSocket;
+};
+
+static StaticAutoPtr<LayerScopeWebSocketManager> gLayerScopeWebSocketManager;
 
 class DebugGLData : public LinkedListElement<DebugGLData> {
 public:
     typedef enum {
         FrameStart,
         FrameEnd,
         TextureData,
         ColorData
@@ -106,29 +366,19 @@ public:
         packet.type = mDataType;
         packet.ptr = static_cast<uint64_t>(mContextAddress);
         packet.value = mValue;
 
         return WriteToStream(&packet, sizeof(packet));
     }
 
     static bool WriteToStream(void *ptr, uint32_t size) {
-        uint32_t written = 0;
-        nsresult rv;
-        while (written < size) {
-            uint32_t cnt;
-            rv = gDebugStream->Write(reinterpret_cast<char*>(ptr) + written,
-                                     size - written, &cnt);
-            if (NS_FAILED(rv))
-                return false;
-
-            written += cnt;
-        }
-
-        return true;
+        if (!gLayerScopeWebSocketManager)
+            return true;
+        return gLayerScopeWebSocketManager->WriteAll(ptr, size);
     }
 
 protected:
     DataType mDataType;
     intptr_t mContextAddress;
     int64_t mValue;
 
 public:
@@ -279,51 +529,44 @@ protected:
     void *mLayerRef;
     uint32_t mColor;
     nsIntSize mSize;
 };
 
 static bool
 CheckSender()
 {
-    if (!gDebugConnected)
+    if (!gLayerScopeWebSocketManager)
         return false;
 
-    // At some point we may want to support sending
-    // data in between frames.
-#if 1
-    if (!gCurrentSender)
+    if (!gLayerScopeWebSocketManager->IsConnected())
         return false;
-#else
-    if (!gCurrentSender)
-        gCurrentSender = new DebugDataSender();
-#endif
 
     return true;
 }
 
-
 class DebugListener : public nsIServerSocketListener
 {
 public:
 
     NS_DECL_THREADSAFE_ISUPPORTS
 
     DebugListener() { }
     virtual ~DebugListener() { }
 
     /* nsIServerSocketListener */
 
     NS_IMETHODIMP OnSocketAccepted(nsIServerSocket *aServ,
                                    nsISocketTransport *aTransport)
     {
+        if (!gLayerScopeWebSocketManager)
+            return NS_OK;
+
         printf_stderr("*** LayerScope: Accepted connection\n");
-        gDebugConnected = true;
-        gDebugSenderTransport = aTransport;
-        aTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(gDebugStream));
+        gLayerScopeWebSocketManager->AddConnection(aTransport);
         return NS_OK;
     }
 
     NS_IMETHODIMP OnStopListening(nsIServerSocket *aServ,
                                   nsresult aStatus)
     {
         return NS_OK;
     }
@@ -363,38 +606,28 @@ public:
     }
 
     /* nsIRunnable impl; send the data */
 
     NS_IMETHODIMP Run() {
         DebugGLData *d;
         nsresult rv = NS_OK;
 
-        // If we got closed while trying to write some stuff earlier, just
-        // throw away everything.
-        if (!gDebugStream) {
-            Cleanup();
-            return NS_OK;
-        }
-
         while ((d = mList->popFirst()) != nullptr) {
             std::auto_ptr<DebugGLData> cleaner(d);
             if (!d->Write()) {
                 rv = NS_ERROR_FAILURE;
                 break;
             }
         }
 
         Cleanup();
 
         if (NS_FAILED(rv)) {
-            gDebugSenderTransport->Close(rv);
-            gDebugConnected = false;
-            gDebugStream = nullptr;
-            gDebugServerSocket = nullptr;
+            LayerScope::DestroyServerSocket();
         }
 
         return NS_OK;
     }
 
 protected:
     LinkedList<DebugGLData> *mList;
 };
@@ -403,72 +636,66 @@ NS_IMPL_ISUPPORTS1(DebugDataSender, nsIR
 
 void
 LayerScope::CreateServerSocket()
 {
     if (!Preferences::GetBool("gfx.layerscope.enabled", false)) {
         return;
     }
 
-    if (!gDebugSenderThread) {
-        NS_NewThread(getter_AddRefs(gDebugSenderThread));
-    }
-
-    if (!gDebugServerSocket) {
-        gDebugServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID);
-        int port = Preferences::GetInt("gfx.layerscope.port", 23456);
-        gDebugServerSocket->Init(port, false, -1);
-        gDebugServerSocket->AsyncListen(new DebugListener);
+    if (!gLayerScopeWebSocketManager) {
+        gLayerScopeWebSocketManager = new LayerScopeWebSocketManager();
     }
 }
 
 void
 LayerScope::DestroyServerSocket()
 {
-    gDebugConnected = false;
-    gDebugStream = nullptr;
-    gDebugServerSocket = nullptr;
+    if (gLayerScopeWebSocketManager) {
+        gLayerScopeWebSocketManager->RemoveAllConnections();
+    }
 }
 
 void
 LayerScope::BeginFrame(GLContext* aGLContext, int64_t aFrameStamp)
 {
-    if (!gDebugConnected)
+    if (!gLayerScopeWebSocketManager)
+        return;
+
+    if (!gLayerScopeWebSocketManager->IsConnected())
         return;
 
 #if 0
     // if we're sending data in between frames, flush the list down the socket,
     // and start a new one
     if (gCurrentSender) {
         gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL);
     }
 #endif
 
-    gCurrentSender = new DebugDataSender();
-    gCurrentSender->Append(new DebugGLData(DebugGLData::FrameStart, aGLContext, aFrameStamp));
+    gLayerScopeWebSocketManager->AppendDebugData(new DebugGLData(DebugGLData::FrameStart, aGLContext, aFrameStamp));
 }
 
 void
 LayerScope::EndFrame(GLContext* aGLContext)
 {
     if (!CheckSender())
         return;
 
-    gCurrentSender->Append(new DebugGLData(DebugGLData::FrameEnd, aGLContext));
-    gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL);
-    gCurrentSender = nullptr;
+    gLayerScopeWebSocketManager->AppendDebugData(new DebugGLData(DebugGLData::FrameEnd, aGLContext));
+    gLayerScopeWebSocketManager->DispatchDebugData();
 }
 
 static void
 SendColor(void* aLayerRef, const gfxRGBA& aColor, int aWidth, int aHeight)
 {
     if (!CheckSender())
         return;
 
-    gCurrentSender->Append(
+    gLayerScopeWebSocketManager->AppendDebugData(
         new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight));
 }
 
 static void
 SendTextureSource(GLContext* aGLContext,
                   void* aLayerRef,
                   TextureSourceOGL* aSource,
                   bool aFlipY)
@@ -495,17 +722,17 @@ SendTextureSource(GLContext* aGLContext,
 
     // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding
     // texture correctly. textureId is used for tracking in DebugGLTextureData.
     nsRefPtr<gfxImageSurface> img =
         aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget,
                                                        gfxIntSize(size.width, size.height),
                                                        shaderProgram, aFlipY);
 
-    gCurrentSender->Append(
+    gLayerScopeWebSocketManager->AppendDebugData(
         new DebugGLTextureData(aGLContext, aLayerRef, textureTarget,
                                textureId, img));
 }
 
 static void
 SendTexturedEffect(GLContext* aGLContext,
                    void* aLayerRef,
                    const TexturedEffect* aEffect)
@@ -580,10 +807,39 @@ LayerScope::SendEffectChain(GLContext* a
     default:
         break;
     }
 
     //const Effect* secondaryEffect = aEffectChain.mSecondaryEffects[EFFECT_MASK];
     // TODO:
 }
 
+LayerScopeWebSocketManager::LayerScopeWebSocketManager()
+{
+    NS_NewThread(getter_AddRefs(mDebugSenderThread));
+
+    mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID);
+    int port = Preferences::GetInt("gfx.layerscope.port", 23456);
+    mServerSocket->Init(port, false, -1);
+    mServerSocket->AsyncListen(new DebugListener);
+}
+
+LayerScopeWebSocketManager::~LayerScopeWebSocketManager()
+{
+}
+
+void LayerScopeWebSocketManager::AppendDebugData(DebugGLData *aDebugData)
+{
+    if (!mCurrentSender) {
+        mCurrentSender = new DebugDataSender();
+    }
+
+    mCurrentSender->Append(aDebugData);
+}
+
+void LayerScopeWebSocketManager::DispatchDebugData()
+{
+    mDebugSenderThread->Dispatch(mCurrentSender, NS_DISPATCH_NORMAL);
+    mCurrentSender = nullptr;
+}
+
 } /* layers */
 } /* mozilla */