author | Morris Tseng <mtseng@mozilla.com> |
Mon, 10 Feb 2014 08:45:15 -0500 | |
changeset 167850 | 5f8d42207dfdb4f71ed97a7761bde10944dfc21a |
parent 167849 | 77bb6be4adb4c3d5b4ff51c9b95c8ec56de4f589 |
child 167851 | db980acda4f1cdef680b71e00eb1d18d01e2975a |
push id | 26190 |
push user | ryanvm@gmail.com |
push date | Mon, 10 Feb 2014 20:37:53 +0000 |
treeherder | mozilla-central@07739c5c874f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | vlad, cku |
bugs | 959114 |
milestone | 30.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
|
--- 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 */