summaryrefslogtreecommitdiffstats
path: root/gfx/layers/LayerScope.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/layers/LayerScope.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/LayerScope.cpp')
-rw-r--r--gfx/layers/LayerScope.cpp1633
1 files changed, 1633 insertions, 0 deletions
diff --git a/gfx/layers/LayerScope.cpp b/gfx/layers/LayerScope.cpp
new file mode 100644
index 0000000000..69c0e9eaff
--- /dev/null
+++ b/gfx/layers/LayerScope.cpp
@@ -0,0 +1,1633 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */
+#include "LayerScope.h"
+
+#include "nsAppRunner.h"
+#include "Effects.h"
+#include "Layers.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/TextureHostOGL.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+
+#include "nsIWidget.h"
+
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "GLReadTexImageHelper.h"
+
+#include <memory>
+#include "mozilla/LinkedList.h"
+#include "mozilla/Base64.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/StaticPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsISocketTransport.h"
+#include "nsIServerSocket.h"
+#include "nsReadLine.h"
+#include "nsNetCID.h"
+#include "nsIOutputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsProxyRelease.h"
+#include <list>
+
+// Undo the damage done by mozzconf.h
+#undef compress
+#include "mozilla/Compression.h"
+
+// Undo the damage done by X11
+#ifdef Status
+# undef Status
+#endif
+// Protocol buffer (generated automatically)
+#include "protobuf/LayerScopePacket.pb.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace mozilla::Compression;
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+using namespace mozilla;
+using namespace layerscope;
+
+class DebugDataSender;
+class DebugGLData;
+
+/*
+ * Manage Websocket connections
+ */
+class LayerScopeWebSocketManager {
+ public:
+ LayerScopeWebSocketManager();
+ ~LayerScopeWebSocketManager();
+
+ void RemoveAllConnections() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(mHandlerMutex);
+ 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() {
+ // This funtion can be called in both main thread and compositor thread.
+ MutexAutoLock lock(mHandlerMutex);
+ return (mHandlers.Length() != 0) ? true : false;
+ }
+
+ void AppendDebugData(DebugGLData* aDebugData);
+ void CleanDebugData();
+ void DispatchDebugData();
+
+ private:
+ void AddConnection(nsISocketTransport* aTransport) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTransport);
+
+ MutexAutoLock lock(mHandlerMutex);
+
+ RefPtr<SocketHandler> temp = new SocketHandler();
+ temp->OpenStream(aTransport);
+ mHandlers.AppendElement(temp.get());
+ }
+
+ void RemoveConnection(uint32_t aIndex) {
+ // TBD: RemoveConnection is executed on the compositor thread and
+ // AddConntection is executed on the main thread, which might be
+ // a problem if a user disconnect and connect readlly quickly at
+ // viewer side.
+
+ // We should dispatch RemoveConnection onto main thead.
+ MOZ_ASSERT(aIndex < mHandlers.Length());
+
+ MutexAutoLock lock(mHandlerMutex);
+ mHandlers.RemoveElementAt(aIndex);
+ }
+
+ friend class SocketListener;
+ class SocketListener : public nsIServerSocketListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SocketListener() = default;
+
+ /* nsIServerSocketListener */
+ NS_IMETHOD OnSocketAccepted(nsIServerSocket* aServ,
+ nsISocketTransport* aTransport) override;
+ NS_IMETHOD OnStopListening(nsIServerSocket* aServ,
+ nsresult aStatus) override {
+ return NS_OK;
+ }
+
+ private:
+ virtual ~SocketListener() = default;
+ };
+
+ /*
+ * This class handle websocket protocol which included
+ * handshake and data frame's header
+ */
+ class SocketHandler : public nsIInputStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SocketHandler() : mState(NoHandshake), mConnected(false) {}
+
+ void OpenStream(nsISocketTransport* aTransport);
+ bool WriteToStream(void* aPtr, uint32_t aSize);
+
+ // nsIInputStreamCallback
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aStream) override;
+
+ private:
+ virtual ~SocketHandler() { CloseConnection(); }
+
+ void ReadInputStreamData(nsTArray<nsCString>& aProtocolString);
+ bool WebSocketHandshake(nsTArray<nsCString>& aProtocolString);
+ void ApplyMask(uint32_t aMask, uint8_t* aData, uint64_t aLen);
+ bool HandleDataFrame(uint8_t* aData, uint32_t aSize);
+ void CloseConnection();
+
+ nsresult HandleSocketMessage(nsIAsyncInputStream* aStream);
+ nsresult ProcessInput(uint8_t* aBuffer, uint32_t aCount);
+
+ private:
+ enum SocketStateType { NoHandshake, HandshakeSuccess, HandshakeFailed };
+ SocketStateType mState;
+
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ nsCOMPtr<nsIAsyncInputStream> mInputStream;
+ nsCOMPtr<nsISocketTransport> mTransport;
+ bool mConnected;
+ };
+
+ nsTArray<RefPtr<SocketHandler> > mHandlers;
+ nsCOMPtr<nsIThread> mDebugSenderThread;
+ RefPtr<DebugDataSender> mCurrentSender;
+ nsCOMPtr<nsIServerSocket> mServerSocket;
+
+ // Keep mHandlers accessing thread safe.
+ Mutex mHandlerMutex;
+};
+
+NS_IMPL_ISUPPORTS(LayerScopeWebSocketManager::SocketListener,
+ nsIServerSocketListener);
+NS_IMPL_ISUPPORTS(LayerScopeWebSocketManager::SocketHandler,
+ nsIInputStreamCallback);
+
+class DrawSession {
+ public:
+ DrawSession() : mOffsetX(0.0), mOffsetY(0.0), mRects(0) {}
+
+ float mOffsetX;
+ float mOffsetY;
+ gfx::Matrix4x4 mMVMatrix;
+ size_t mRects;
+ gfx::Rect mLayerRects[4];
+ gfx::Rect mTextureRects[4];
+ std::list<GLuint> mTexIDs;
+};
+
+class ContentMonitor {
+ public:
+ using THArray = nsTArray<const TextureHost*>;
+
+ // Notify the content of a TextureHost was changed.
+ void SetChangedHost(const TextureHost* host) {
+ if (THArray::NoIndex == mChangedHosts.IndexOf(host)) {
+ mChangedHosts.AppendElement(host);
+ }
+ }
+
+ // Clear changed flag of a host.
+ void ClearChangedHost(const TextureHost* host) {
+ if (THArray::NoIndex != mChangedHosts.IndexOf(host)) {
+ mChangedHosts.RemoveElement(host);
+ }
+ }
+
+ // Return true iff host is a new one or the content of it had been changed.
+ bool IsChangedOrNew(const TextureHost* host) {
+ if (THArray::NoIndex == mSeenHosts.IndexOf(host)) {
+ mSeenHosts.AppendElement(host);
+ return true;
+ }
+
+ if (decltype(mChangedHosts)::NoIndex != mChangedHosts.IndexOf(host)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void Empty() {
+ mSeenHosts.SetLength(0);
+ mChangedHosts.SetLength(0);
+ }
+
+ private:
+ THArray mSeenHosts;
+ THArray mChangedHosts;
+};
+
+/*
+ * Hold all singleton objects used by LayerScope.
+ */
+class LayerScopeManager {
+ public:
+ void CreateServerSocket() {
+ // WebSocketManager must be created on the main thread.
+ if (NS_IsMainThread()) {
+ mWebSocketManager = mozilla::MakeUnique<LayerScopeWebSocketManager>();
+ } else {
+ // Dispatch creation to main thread, and make sure we
+ // dispatch this only once after booting
+ static bool dispatched = false;
+ if (dispatched) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv =
+ NS_DispatchToMainThread(new CreateServerSocketRunnable(this));
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Failed to dispatch WebSocket Creation to main thread");
+ dispatched = true;
+ }
+ }
+
+ void DestroyServerSocket() {
+ // Destroy Web Server Socket
+ if (mWebSocketManager) {
+ mWebSocketManager->RemoveAllConnections();
+ }
+ }
+
+ LayerScopeWebSocketManager* GetSocketManager() {
+ return mWebSocketManager.get();
+ }
+
+ ContentMonitor* GetContentMonitor() {
+ if (!mContentMonitor.get()) {
+ mContentMonitor = mozilla::MakeUnique<ContentMonitor>();
+ }
+
+ return mContentMonitor.get();
+ }
+
+ void NewDrawSession() { mSession = mozilla::MakeUnique<DrawSession>(); }
+
+ DrawSession& CurrentSession() { return *mSession; }
+
+ void SetPixelScale(double scale) { mScale = scale; }
+
+ double GetPixelScale() const { return mScale; }
+
+ LayerScopeManager() : mScale(1.0) {}
+
+ private:
+ friend class CreateServerSocketRunnable;
+ class CreateServerSocketRunnable : public Runnable {
+ public:
+ explicit CreateServerSocketRunnable(LayerScopeManager* aLayerScopeManager)
+ : Runnable("layers::LayerScopeManager::CreateServerSocketRunnable"),
+ mLayerScopeManager(aLayerScopeManager) {}
+ NS_IMETHOD Run() override {
+ mLayerScopeManager->mWebSocketManager =
+ mozilla::MakeUnique<LayerScopeWebSocketManager>();
+ return NS_OK;
+ }
+
+ private:
+ LayerScopeManager* mLayerScopeManager;
+ };
+
+ mozilla::UniquePtr<LayerScopeWebSocketManager> mWebSocketManager;
+ mozilla::UniquePtr<DrawSession> mSession;
+ mozilla::UniquePtr<ContentMonitor> mContentMonitor;
+ double mScale;
+};
+
+LayerScopeManager gLayerScopeManager;
+
+/*
+ * The static helper functions that set data into the packet
+ * 1. DumpRect
+ * 2. DumpFilter
+ */
+template <typename T>
+static void DumpRect(T* aPacketRect, const Rect& aRect) {
+ aPacketRect->set_x(aRect.X());
+ aPacketRect->set_y(aRect.Y());
+ aPacketRect->set_w(aRect.Width());
+ aPacketRect->set_h(aRect.Height());
+}
+
+static void DumpFilter(TexturePacket* aTexturePacket,
+ const SamplingFilter aSamplingFilter) {
+ switch (aSamplingFilter) {
+ case SamplingFilter::GOOD:
+ aTexturePacket->set_mfilter(TexturePacket::GOOD);
+ break;
+ case SamplingFilter::LINEAR:
+ aTexturePacket->set_mfilter(TexturePacket::LINEAR);
+ break;
+ case SamplingFilter::POINT:
+ aTexturePacket->set_mfilter(TexturePacket::POINT);
+ break;
+ default:
+ MOZ_ASSERT(false,
+ "Can't dump unexpected mSamplingFilter to texture packet!");
+ break;
+ }
+}
+
+/*
+ * DebugGLData is the base class of
+ * 1. DebugGLFrameStatusData (Frame start/end packet)
+ * 2. DebugGLColorData (Color data packet)
+ * 3. DebugGLTextureData (Texture data packet)
+ * 4. DebugGLLayersData (Layers Tree data packet)
+ * 5. DebugGLMetaData (Meta data packet)
+ */
+class DebugGLData : public LinkedListElement<DebugGLData> {
+ public:
+ explicit DebugGLData(Packet::DataType aDataType) : mDataType(aDataType) {}
+
+ virtual ~DebugGLData() = default;
+
+ virtual bool Write() = 0;
+
+ protected:
+ static bool WriteToStream(Packet& aPacket) {
+ if (!gLayerScopeManager.GetSocketManager()) return true;
+
+ size_t size = aPacket.ByteSizeLong();
+ auto data = MakeUnique<uint8_t[]>(size);
+ aPacket.SerializeToArray(data.get(), size);
+ return gLayerScopeManager.GetSocketManager()->WriteAll(data.get(), size);
+ }
+
+ Packet::DataType mDataType;
+};
+
+class DebugGLFrameStatusData final : public DebugGLData {
+ public:
+ DebugGLFrameStatusData(Packet::DataType aDataType, int64_t aValue)
+ : DebugGLData(aDataType), mFrameStamp(aValue) {}
+
+ explicit DebugGLFrameStatusData(Packet::DataType aDataType)
+ : DebugGLData(aDataType), mFrameStamp(0) {}
+
+ bool Write() override {
+ Packet packet;
+ packet.set_type(mDataType);
+
+ FramePacket* fp = packet.mutable_frame();
+ fp->set_value(static_cast<uint64_t>(mFrameStamp));
+
+ fp->set_scale(gLayerScopeManager.GetPixelScale());
+
+ return WriteToStream(packet);
+ }
+
+ protected:
+ int64_t mFrameStamp;
+};
+
+class DebugGLTextureData final : public DebugGLData {
+ public:
+ DebugGLTextureData(GLContext* cx, void* layerRef, GLenum target, GLuint name,
+ DataSourceSurface* img, bool aIsMask,
+ UniquePtr<Packet> aPacket)
+ : DebugGLData(Packet::TEXTURE),
+ mLayerRef(reinterpret_cast<uint64_t>(layerRef)),
+ mTarget(target),
+ mName(name),
+ mContextAddress(reinterpret_cast<intptr_t>(cx)),
+ mDatasize(0),
+ mIsMask(aIsMask),
+ mPacket(std::move(aPacket)) {
+ // pre-packing
+ // DataSourceSurface may have locked buffer,
+ // so we should compress now, and then it could
+ // be unlocked outside.
+ pack(img);
+ }
+
+ bool Write() override { return WriteToStream(*mPacket); }
+
+ private:
+ void pack(DataSourceSurface* aImage) {
+ mPacket->set_type(mDataType);
+
+ TexturePacket* tp = mPacket->mutable_texture();
+ tp->set_layerref(mLayerRef);
+ tp->set_name(mName);
+ tp->set_target(mTarget);
+ tp->set_dataformat(LOCAL_GL_RGBA);
+ tp->set_glcontext(static_cast<uint64_t>(mContextAddress));
+ tp->set_ismask(mIsMask);
+
+ if (aImage) {
+ DataSourceSurface::ScopedMap map(aImage, DataSourceSurface::READ);
+ tp->set_width(aImage->GetSize().width);
+ tp->set_height(aImage->GetSize().height);
+ tp->set_stride(map.GetStride());
+
+ mDatasize = aImage->GetSize().height * map.GetStride();
+
+ auto compresseddata =
+ MakeUnique<char[]>(LZ4::maxCompressedSize(mDatasize));
+ if (compresseddata) {
+ int ndatasize = LZ4::compress((char*)map.GetData(), mDatasize,
+ compresseddata.get());
+ if (ndatasize > 0) {
+ mDatasize = ndatasize;
+ tp->set_dataformat((1 << 16 | tp->dataformat()));
+ tp->set_data(compresseddata.get(), mDatasize);
+ } else {
+ NS_WARNING("Compress data failed");
+ tp->set_data(map.GetData(), mDatasize);
+ }
+ } else {
+ NS_WARNING("Couldn't new compressed data.");
+ tp->set_data(map.GetData(), mDatasize);
+ }
+ } else {
+ tp->set_width(0);
+ tp->set_height(0);
+ tp->set_stride(0);
+ }
+ }
+
+ protected:
+ uint64_t mLayerRef;
+ GLenum mTarget;
+ GLuint mName;
+ intptr_t mContextAddress;
+ uint32_t mDatasize;
+ bool mIsMask;
+
+ // Packet data
+ UniquePtr<Packet> mPacket;
+};
+
+class DebugGLColorData final : public DebugGLData {
+ public:
+ DebugGLColorData(void* layerRef, const DeviceColor& color, int width,
+ int height)
+ : DebugGLData(Packet::COLOR),
+ mLayerRef(reinterpret_cast<uint64_t>(layerRef)),
+ mColor(color.ToABGR()),
+ mSize(width, height) {}
+
+ bool Write() override {
+ Packet packet;
+ packet.set_type(mDataType);
+
+ ColorPacket* cp = packet.mutable_color();
+ cp->set_layerref(mLayerRef);
+ cp->set_color(mColor);
+ cp->set_width(mSize.width);
+ cp->set_height(mSize.height);
+
+ return WriteToStream(packet);
+ }
+
+ protected:
+ uint64_t mLayerRef;
+ uint32_t mColor;
+ IntSize mSize;
+};
+
+class DebugGLLayersData final : public DebugGLData {
+ public:
+ explicit DebugGLLayersData(UniquePtr<Packet> aPacket)
+ : DebugGLData(Packet::LAYERS), mPacket(std::move(aPacket)) {}
+
+ bool Write() override {
+ mPacket->set_type(mDataType);
+ return WriteToStream(*mPacket);
+ }
+
+ protected:
+ UniquePtr<Packet> mPacket;
+};
+
+class DebugGLMetaData final : public DebugGLData {
+ public:
+ DebugGLMetaData(Packet::DataType aDataType, bool aValue)
+ : DebugGLData(aDataType), mComposedByHwc(aValue) {}
+
+ explicit DebugGLMetaData(Packet::DataType aDataType)
+ : DebugGLData(aDataType), mComposedByHwc(false) {}
+
+ bool Write() override {
+ Packet packet;
+ packet.set_type(mDataType);
+
+ MetaPacket* mp = packet.mutable_meta();
+ mp->set_composedbyhwc(mComposedByHwc);
+
+ return WriteToStream(packet);
+ }
+
+ protected:
+ bool mComposedByHwc;
+};
+
+class DebugGLDrawData final : public DebugGLData {
+ public:
+ DebugGLDrawData(float aOffsetX, float aOffsetY,
+ const gfx::Matrix4x4& aMVMatrix, size_t aRects,
+ const gfx::Rect* aLayerRects, const gfx::Rect* aTextureRects,
+ const std::list<GLuint>& aTexIDs, void* aLayerRef)
+ : DebugGLData(Packet::DRAW),
+ mOffsetX(aOffsetX),
+ mOffsetY(aOffsetY),
+ mMVMatrix(aMVMatrix),
+ mRects(aRects),
+ mTexIDs(aTexIDs),
+ mLayerRef(reinterpret_cast<uint64_t>(aLayerRef)) {
+ for (size_t i = 0; i < mRects; i++) {
+ mLayerRects[i] = aLayerRects[i];
+ mTextureRects[i] = aTextureRects[i];
+ }
+ }
+
+ bool Write() override {
+ Packet packet;
+ packet.set_type(mDataType);
+
+ DrawPacket* dp = packet.mutable_draw();
+ dp->set_layerref(mLayerRef);
+
+ dp->set_offsetx(mOffsetX);
+ dp->set_offsety(mOffsetY);
+
+ auto element = reinterpret_cast<Float*>(&mMVMatrix);
+ for (int i = 0; i < 16; i++) {
+ dp->add_mvmatrix(*element++);
+ }
+ dp->set_totalrects(mRects);
+
+ MOZ_ASSERT(mRects > 0 && mRects < 4);
+ for (size_t i = 0; i < mRects; i++) {
+ // Vertex
+ DumpRect(dp->add_layerrect(), mLayerRects[i]);
+ // UV
+ DumpRect(dp->add_texturerect(), mTextureRects[i]);
+ }
+
+ for (GLuint texId : mTexIDs) {
+ dp->add_texids(texId);
+ }
+
+ return WriteToStream(packet);
+ }
+
+ protected:
+ float mOffsetX;
+ float mOffsetY;
+ gfx::Matrix4x4 mMVMatrix;
+ size_t mRects;
+ gfx::Rect mLayerRects[4];
+ gfx::Rect mTextureRects[4];
+ std::list<GLuint> mTexIDs;
+ uint64_t mLayerRef;
+};
+
+class DebugDataSender {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DebugDataSender)
+
+ // Append a DebugData into mList on mThread
+ class AppendTask : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ AppendTask(DebugDataSender* host, DebugGLData* d) : mData(d), mHost(host) {}
+
+ NS_IMETHOD Run() override {
+ mHost->mList.insertBack(mData);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~AppendTask() = default;
+
+ DebugGLData* mData;
+ // Keep a strong reference to DebugDataSender to prevent this object
+ // accessing mHost on mThread, when it's been destroyed on the main
+ // thread.
+ RefPtr<DebugDataSender> mHost;
+ };
+
+ // Clear all DebugData in mList on mThead.
+ class ClearTask : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ explicit ClearTask(DebugDataSender* host) : mHost(host) {}
+
+ NS_IMETHOD Run() override {
+ mHost->RemoveData();
+ return NS_OK;
+ }
+
+ private:
+ virtual ~ClearTask() = default;
+
+ RefPtr<DebugDataSender> mHost;
+ };
+
+ // Send all DebugData in mList via websocket, and then, clean up
+ // mList on mThread.
+ class SendTask : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit SendTask(DebugDataSender* host) : mHost(host) {}
+
+ NS_IMETHOD Run() override {
+ // Sendout all appended debug data.
+ DebugGLData* d = nullptr;
+ while ((d = mHost->mList.popFirst()) != nullptr) {
+ UniquePtr<DebugGLData> cleaner(d);
+ if (!d->Write()) {
+ gLayerScopeManager.DestroyServerSocket();
+ break;
+ }
+ }
+
+ // Cleanup.
+ mHost->RemoveData();
+ return NS_OK;
+ }
+
+ private:
+ virtual ~SendTask() = default;
+
+ RefPtr<DebugDataSender> mHost;
+ };
+
+ explicit DebugDataSender(nsIThread* thread) : mThread(thread) {}
+
+ void Append(DebugGLData* d) {
+ mThread->Dispatch(new AppendTask(this, d), NS_DISPATCH_NORMAL);
+ }
+
+ void Cleanup() { mThread->Dispatch(new ClearTask(this), NS_DISPATCH_NORMAL); }
+
+ void Send() { mThread->Dispatch(new SendTask(this), NS_DISPATCH_NORMAL); }
+
+ protected:
+ virtual ~DebugDataSender() = default;
+ void RemoveData() {
+ MOZ_ASSERT(mThread->SerialEventTarget()->IsOnCurrentThread());
+ if (mList.isEmpty()) return;
+
+ DebugGLData* d;
+ while ((d = mList.popFirst()) != nullptr) delete d;
+ }
+
+ // We can only modify or aceess mList on mThread.
+ LinkedList<DebugGLData> mList;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS(DebugDataSender::AppendTask, nsIRunnable);
+NS_IMPL_ISUPPORTS(DebugDataSender::ClearTask, nsIRunnable);
+NS_IMPL_ISUPPORTS(DebugDataSender::SendTask, nsIRunnable);
+
+/*
+ * LayerScope SendXXX Structure
+ * 1. SendLayer
+ * 2. SendEffectChain
+ * 1. SendTexturedEffect
+ * -> SendTextureSource
+ * 2. SendMaskEffect
+ * -> SendTextureSource
+ * 3. SendYCbCrEffect
+ * -> SendTextureSource
+ * 4. SendColor
+ */
+class SenderHelper {
+ // Sender public APIs
+ public:
+ static void SendLayer(LayerComposite* aLayer, int aWidth, int aHeight);
+
+ static void SendEffectChain(gl::GLContext* aGLContext,
+ const EffectChain& aEffectChain, int aWidth = 0,
+ int aHeight = 0);
+
+ static void SetLayersTreeSendable(bool aSet) { sLayersTreeSendable = aSet; }
+
+ static void SetLayersBufferSendable(bool aSet) {
+ sLayersBufferSendable = aSet;
+ }
+
+ static bool GetLayersTreeSendable() { return sLayersTreeSendable; }
+
+ static void ClearSentTextureIds();
+
+ // Sender private functions
+ private:
+ static void SendColor(void* aLayerRef, const DeviceColor& aColor, int aWidth,
+ int aHeight);
+ static void SendTextureSource(GLContext* aGLContext, void* aLayerRef,
+ TextureSourceOGL* aSource, bool aFlipY,
+ bool aIsMask, UniquePtr<Packet> aPacket);
+ static void SetAndSendTexture(GLContext* aGLContext, void* aLayerRef,
+ TextureSourceOGL* aSource,
+ const TexturedEffect* aEffect);
+ static void SendTexturedEffect(GLContext* aGLContext, void* aLayerRef,
+ const TexturedEffect* aEffect);
+ static void SendMaskEffect(GLContext* aGLContext, void* aLayerRef,
+ const EffectMask* aEffect);
+ static void SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef,
+ const EffectYCbCr* aEffect);
+ static GLuint GetTextureID(GLContext* aGLContext, TextureSourceOGL* aSource);
+ static bool HasTextureIdBeenSent(GLuint aTextureId);
+ // Data fields
+ private:
+ static bool sLayersTreeSendable;
+ static bool sLayersBufferSendable;
+ static std::vector<GLuint> sSentTextureIds;
+};
+
+bool SenderHelper::sLayersTreeSendable = true;
+bool SenderHelper::sLayersBufferSendable = true;
+std::vector<GLuint> SenderHelper::sSentTextureIds;
+
+// ----------------------------------------------
+// SenderHelper implementation
+// ----------------------------------------------
+void SenderHelper::ClearSentTextureIds() { sSentTextureIds.clear(); }
+
+bool SenderHelper::HasTextureIdBeenSent(GLuint aTextureId) {
+ return std::find(sSentTextureIds.begin(), sSentTextureIds.end(),
+ aTextureId) != sSentTextureIds.end();
+}
+
+void SenderHelper::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) {
+ MOZ_ASSERT(aLayer && aLayer->GetLayer());
+ if (!aLayer || !aLayer->GetLayer()) {
+ return;
+ }
+
+ switch (aLayer->GetLayer()->GetType()) {
+ case Layer::TYPE_COLOR: {
+ EffectChain effect;
+ aLayer->GenEffectChain(effect);
+
+ LayerScope::DrawBegin();
+ LayerScope::DrawEnd(nullptr, effect, aWidth, aHeight);
+ break;
+ }
+ case Layer::TYPE_IMAGE:
+ case Layer::TYPE_CANVAS:
+ case Layer::TYPE_PAINTED: {
+ // Get CompositableHost and Compositor
+ CompositableHost* compHost = aLayer->GetCompositableHost();
+ TextureSourceProvider* provider = compHost->GetTextureSourceProvider();
+ Compositor* comp = provider->AsCompositor();
+ // Send EffectChain only for CompositorOGL
+ if (LayersBackend::LAYERS_OPENGL == comp->GetBackendType()) {
+ CompositorOGL* compOGL = comp->AsCompositorOGL();
+ EffectChain effect;
+ // Generate primary effect (lock and gen)
+ AutoLockCompositableHost lock(compHost);
+ aLayer->GenEffectChain(effect);
+
+ LayerScope::DrawBegin();
+ LayerScope::DrawEnd(compOGL->gl(), effect, aWidth, aHeight);
+ }
+ break;
+ }
+ case Layer::TYPE_CONTAINER:
+ default:
+ break;
+ }
+}
+
+void SenderHelper::SendColor(void* aLayerRef, const DeviceColor& aColor,
+ int aWidth, int aHeight) {
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight));
+}
+
+GLuint SenderHelper::GetTextureID(GLContext* aGLContext,
+ TextureSourceOGL* aSource) {
+ GLenum textureTarget = aSource->GetTextureTarget();
+ aSource->BindTexture(LOCAL_GL_TEXTURE0, gfx::SamplingFilter::LINEAR);
+
+ GLuint texID = 0;
+ // This is horrid hack. It assumes that aGLContext matches the context
+ // aSource has bound to.
+ if (textureTarget == LOCAL_GL_TEXTURE_2D) {
+ aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &texID);
+ } else if (textureTarget == LOCAL_GL_TEXTURE_EXTERNAL) {
+ aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &texID);
+ } else if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE) {
+ aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &texID);
+ }
+
+ return texID;
+}
+
+void SenderHelper::SendTextureSource(GLContext* aGLContext, void* aLayerRef,
+ TextureSourceOGL* aSource, bool aFlipY,
+ bool aIsMask, UniquePtr<Packet> aPacket) {
+ MOZ_ASSERT(aGLContext);
+ if (!aGLContext) {
+ return;
+ }
+ GLuint texID = GetTextureID(aGLContext, aSource);
+ if (HasTextureIdBeenSent(texID)) {
+ return;
+ }
+
+ GLenum textureTarget = aSource->GetTextureTarget();
+ ShaderConfigOGL config =
+ ShaderConfigFromTargetAndFormat(textureTarget, aSource->GetFormat());
+ int shaderConfig = config.mFeatures;
+
+ gfx::IntSize size = aSource->GetSize();
+
+ // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding
+ // texture correctly. texID is used for tracking in DebugGLTextureData.
+ RefPtr<DataSourceSurface> img =
+ aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget, size,
+ shaderConfig, aFlipY);
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, texID, img,
+ aIsMask, std::move(aPacket)));
+
+ sSentTextureIds.push_back(texID);
+ gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID);
+}
+
+void SenderHelper::SetAndSendTexture(GLContext* aGLContext, void* aLayerRef,
+ TextureSourceOGL* aSource,
+ const TexturedEffect* aEffect) {
+ // Expose packet creation here, so we could dump primary texture effect
+ // attributes.
+ auto packet = MakeUnique<layerscope::Packet>();
+ layerscope::TexturePacket* texturePacket = packet->mutable_texture();
+ texturePacket->set_mpremultiplied(aEffect->mPremultiplied);
+ DumpFilter(texturePacket, aEffect->mSamplingFilter);
+ DumpRect(texturePacket->mutable_mtexturecoords(), aEffect->mTextureCoords);
+ SendTextureSource(aGLContext, aLayerRef, aSource, false, false,
+ std::move(packet));
+}
+
+void SenderHelper::SendTexturedEffect(GLContext* aGLContext, void* aLayerRef,
+ const TexturedEffect* aEffect) {
+ TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL();
+ if (!source) {
+ return;
+ }
+
+ // Fallback texture sending path.
+ SetAndSendTexture(aGLContext, aLayerRef, source, aEffect);
+}
+
+void SenderHelper::SendMaskEffect(GLContext* aGLContext, void* aLayerRef,
+ const EffectMask* aEffect) {
+ TextureSourceOGL* source = aEffect->mMaskTexture->AsSourceOGL();
+ if (!source) {
+ return;
+ }
+
+ // Expose packet creation here, so we could dump secondary mask effect
+ // attributes.
+ auto packet = MakeUnique<layerscope::Packet>();
+ TexturePacket::EffectMask* mask = packet->mutable_texture()->mutable_mask();
+ mask->mutable_msize()->set_w(aEffect->mSize.width);
+ mask->mutable_msize()->set_h(aEffect->mSize.height);
+ auto element = reinterpret_cast<const Float*>(&(aEffect->mMaskTransform));
+ for (int i = 0; i < 16; i++) {
+ mask->mutable_mmasktransform()->add_m(*element++);
+ }
+
+ SendTextureSource(aGLContext, aLayerRef, source, false, true,
+ std::move(packet));
+}
+
+void SenderHelper::SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef,
+ const EffectYCbCr* aEffect) {
+ TextureSource* sourceYCbCr = aEffect->mTexture;
+ if (!sourceYCbCr) return;
+
+ const int Y = 0, Cb = 1, Cr = 2;
+ TextureSourceOGL* sources[] = {sourceYCbCr->GetSubSource(Y)->AsSourceOGL(),
+ sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(),
+ sourceYCbCr->GetSubSource(Cr)->AsSourceOGL()};
+
+ for (auto source : sources) {
+ SetAndSendTexture(aGLContext, aLayerRef, source, aEffect);
+ }
+}
+
+void SenderHelper::SendEffectChain(GLContext* aGLContext,
+ const EffectChain& aEffectChain, int aWidth,
+ int aHeight) {
+ if (!sLayersBufferSendable) return;
+
+ const Effect* primaryEffect = aEffectChain.mPrimaryEffect;
+ MOZ_ASSERT(primaryEffect);
+
+ if (!primaryEffect) {
+ return;
+ }
+
+ switch (primaryEffect->mType) {
+ case EffectTypes::RGB: {
+ const TexturedEffect* texturedEffect =
+ static_cast<const TexturedEffect*>(primaryEffect);
+ SendTexturedEffect(aGLContext, aEffectChain.mLayerRef, texturedEffect);
+ break;
+ }
+ case EffectTypes::YCBCR: {
+ const EffectYCbCr* yCbCrEffect =
+ static_cast<const EffectYCbCr*>(primaryEffect);
+ SendYCbCrEffect(aGLContext, aEffectChain.mLayerRef, yCbCrEffect);
+ break;
+ }
+ case EffectTypes::SOLID_COLOR: {
+ const EffectSolidColor* solidColorEffect =
+ static_cast<const EffectSolidColor*>(primaryEffect);
+ SendColor(aEffectChain.mLayerRef, solidColorEffect->mColor, aWidth,
+ aHeight);
+ break;
+ }
+ case EffectTypes::COMPONENT_ALPHA:
+ case EffectTypes::RENDER_TARGET:
+ default:
+ break;
+ }
+
+ if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) {
+ const EffectMask* effectMask = static_cast<const EffectMask*>(
+ aEffectChain.mSecondaryEffects[EffectTypes::MASK].get());
+ SendMaskEffect(aGLContext, aEffectChain.mLayerRef, effectMask);
+ }
+}
+
+void LayerScope::ContentChanged(TextureHost* host) {
+ if (!CheckSendable()) {
+ return;
+ }
+
+ gLayerScopeManager.GetContentMonitor()->SetChangedHost(host);
+}
+
+// ----------------------------------------------
+// SocketHandler implementation
+// ----------------------------------------------
+void LayerScopeWebSocketManager::SocketHandler::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, GetCurrentEventTarget());
+}
+
+bool LayerScopeWebSocketManager::SocketHandler::WriteToStream(void* aPtr,
+ uint32_t aSize) {
+ if (mState == NoHandshake) {
+ // Not yet handshake, just return true in case of
+ // LayerScope remove this handle
+ return true;
+ } else if (mState == HandshakeFailed) {
+ return false;
+ }
+
+ if (!mOutputStream) {
+ 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 (aSize <= 125) {
+ wsHeaderSize = 2;
+ wsHeader[1] = aSize;
+ } else if (aSize < 65536) {
+ wsHeaderSize = 4;
+ wsHeader[1] = 0x7E;
+ NetworkEndian::writeUint16(wsHeader + 2, aSize);
+ } else {
+ wsHeaderSize = 10;
+ wsHeader[1] = 0x7F;
+ NetworkEndian::writeUint64(wsHeader + 2, aSize);
+ }
+
+ // 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 < aSize) {
+ uint32_t cnt;
+ rv = mOutputStream->Write(reinterpret_cast<char*>(aPtr) + written,
+ aSize - written, &cnt);
+ if (NS_FAILED(rv)) return false;
+
+ written += cnt;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+LayerScopeWebSocketManager::SocketHandler::OnInputStreamReady(
+ nsIAsyncInputStream* aStream) {
+ MOZ_ASSERT(mInputStream);
+
+ if (!mInputStream) {
+ return NS_OK;
+ }
+
+ if (!mConnected) {
+ nsTArray<nsCString> protocolString;
+ ReadInputStreamData(protocolString);
+
+ if (WebSocketHandshake(protocolString)) {
+ mState = HandshakeSuccess;
+ mConnected = true;
+ mInputStream->AsyncWait(this, 0, 0, GetCurrentEventTarget());
+ } else {
+ mState = HandshakeFailed;
+ }
+ return NS_OK;
+ } else {
+ return HandleSocketMessage(aStream);
+ }
+}
+
+void LayerScopeWebSocketManager::SocketHandler::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 LayerScopeWebSocketManager::SocketHandler::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.EqualsLiteral("7") || version.EqualsLiteral("8") ||
+ version.EqualsLiteral("13"))) {
+ return false;
+ }
+
+ if (!(protocol.EqualsIgnoreCase("binary"))) {
+ return false;
+ }
+
+ if (!mOutputStream) {
+ 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::kHashSize]; // SHA1 digests are 20 bytes long.
+ sha1.finish(digest);
+ nsCString newString(reinterpret_cast<char*>(digest), SHA1Sum::kHashSize);
+ nsCString response("HTTP/1.1 101 Switching Protocols\r\n");
+ response.AppendLiteral("Upgrade: websocket\r\n");
+ response.AppendLiteral("Connection: Upgrade\r\n");
+ response.AppendLiteral("Sec-WebSocket-Accept: ");
+ rv = Base64EncodeAppend(newString, response);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ response.AppendLiteral("\r\n");
+ response.AppendLiteral("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;
+}
+
+nsresult LayerScopeWebSocketManager::SocketHandler::HandleSocketMessage(
+ nsIAsyncInputStream* aStream) {
+ // The reading and parsing of this input stream is customized for layer
+ // viewer.
+ const uint32_t cPacketSize = 1024;
+ char buffer[cPacketSize];
+ uint32_t count = 0;
+ nsresult rv = NS_OK;
+
+ do {
+ rv = mInputStream->Read((char*)buffer, cPacketSize, &count);
+
+ // TODO: combine packets if we have to read more than once
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mInputStream->AsyncWait(this, 0, 0, GetCurrentEventTarget());
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ if (count == 0) {
+ // NS_BASE_STREAM_CLOSED
+ CloseConnection();
+ break;
+ }
+
+ rv = ProcessInput(reinterpret_cast<uint8_t*>(buffer), count);
+ } while (NS_SUCCEEDED(rv) && mInputStream);
+ return rv;
+}
+
+nsresult LayerScopeWebSocketManager::SocketHandler::ProcessInput(
+ uint8_t* aBuffer, uint32_t aCount) {
+ uint32_t avail = aCount;
+
+ // Decode Websocket data frame
+ if (avail <= 2) {
+ NS_WARNING("Packet size is less than 2 bytes");
+ return NS_OK;
+ }
+
+ // First byte, data type, only care the opcode
+ // rsvBits: aBuffer[0] & 0x70 (0111 0000)
+ uint8_t finBit = aBuffer[0] & 0x80; // 1000 0000
+ uint8_t opcode = aBuffer[0] & 0x0F; // 0000 1111
+
+ if (!finBit) {
+ NS_WARNING(
+ "We cannot handle multi-fragments messages in Layerscope websocket "
+ "parser.");
+ return NS_OK;
+ }
+
+ // Second byte, data length
+ uint8_t maskBit = aBuffer[1] & 0x80; // 1000 0000
+ int64_t payloadLength64 = aBuffer[1] & 0x7F; // 0111 1111
+
+ if (!maskBit) {
+ NS_WARNING("Client to Server should set the mask bit");
+ return NS_OK;
+ }
+
+ uint32_t framingLength = 2 + 4; // 4 for masks
+
+ if (payloadLength64 < 126) {
+ if (avail < framingLength) return NS_OK;
+ } else if (payloadLength64 == 126) {
+ // 16 bit length field
+ framingLength += 2;
+ if (avail < framingLength) {
+ return NS_OK;
+ }
+
+ payloadLength64 = aBuffer[2] << 8 | aBuffer[3];
+ } else {
+ // 64 bit length
+ framingLength += 8;
+ if (avail < framingLength) {
+ return NS_OK;
+ }
+
+ if (aBuffer[2] & 0x80) {
+ // Section 4.2 says that the most significant bit MUST be
+ // 0. (i.e. this is really a 63 bit value)
+ NS_WARNING("High bit of 64 bit length set");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // copy this in case it is unaligned
+ payloadLength64 = NetworkEndian::readInt64(aBuffer + 2);
+ }
+
+ uint8_t* payload = aBuffer + framingLength;
+ avail -= framingLength;
+
+ uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
+ if (avail < payloadLength) {
+ NS_WARNING("Packet size mismatch the payload length");
+ return NS_OK;
+ }
+
+ // Apply mask
+ uint32_t mask = NetworkEndian::readUint32(payload - 4);
+ ApplyMask(mask, payload, payloadLength);
+
+ if (opcode == 0x8) {
+ // opcode == 0x8 means connection close
+ CloseConnection();
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!HandleDataFrame(payload, payloadLength)) {
+ NS_WARNING("Cannot decode payload data by the protocol buffer");
+ }
+
+ return NS_OK;
+}
+
+void LayerScopeWebSocketManager::SocketHandler::ApplyMask(uint32_t aMask,
+ uint8_t* aData,
+ uint64_t aLen) {
+ if (!aData || aLen == 0) {
+ return;
+ }
+
+ // Optimally we want to apply the mask 32 bits at a time,
+ // but the buffer might not be alligned. So we first deal with
+ // 0 to 3 bytes of preamble individually
+ while (aLen && (reinterpret_cast<uintptr_t>(aData) & 3)) {
+ *aData ^= aMask >> 24;
+ aMask = RotateLeft(aMask, 8);
+ aData++;
+ aLen--;
+ }
+
+ // perform mask on full words of data
+ uint32_t* iData = reinterpret_cast<uint32_t*>(aData);
+ uint32_t* end = iData + (aLen >> 2);
+ NetworkEndian::writeUint32(&aMask, aMask);
+ for (; iData < end; iData++) {
+ *iData ^= aMask;
+ }
+ aMask = NetworkEndian::readUint32(&aMask);
+ aData = (uint8_t*)iData;
+ aLen = aLen % 4;
+
+ // There maybe up to 3 trailing bytes that need to be dealt with
+ // individually
+ while (aLen) {
+ *aData ^= aMask >> 24;
+ aMask = RotateLeft(aMask, 8);
+ aData++;
+ aLen--;
+ }
+}
+
+bool LayerScopeWebSocketManager::SocketHandler::HandleDataFrame(
+ uint8_t* aData, uint32_t aSize) {
+ // Handle payload data by protocol buffer
+ auto p = MakeUnique<CommandPacket>();
+ p->ParseFromArray(static_cast<void*>(aData), aSize);
+
+ if (!p->has_type()) {
+ MOZ_ASSERT(false, "Protocol buffer decoding failed or cannot recongize it");
+ return false;
+ }
+
+ switch (p->type()) {
+ case CommandPacket::LAYERS_TREE:
+ if (p->has_value()) {
+ SenderHelper::SetLayersTreeSendable(p->value());
+ }
+ break;
+
+ case CommandPacket::LAYERS_BUFFER:
+ if (p->has_value()) {
+ SenderHelper::SetLayersBufferSendable(p->value());
+ }
+ break;
+
+ case CommandPacket::NO_OP:
+ default:
+ NS_WARNING("Invalid message type");
+ break;
+ }
+ return true;
+}
+
+void LayerScopeWebSocketManager::SocketHandler::CloseConnection() {
+ gLayerScopeManager.GetSocketManager()->CleanDebugData();
+ if (mInputStream) {
+ mInputStream->AsyncWait(nullptr, 0, 0, nullptr);
+ mInputStream = nullptr;
+ }
+ if (mOutputStream) {
+ mOutputStream = nullptr;
+ }
+ if (mTransport) {
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+ mConnected = false;
+}
+
+// ----------------------------------------------
+// LayerScopeWebSocketManager implementation
+// ----------------------------------------------
+LayerScopeWebSocketManager::LayerScopeWebSocketManager()
+ : mHandlerMutex("LayerScopeWebSocketManager::mHandlerMutex") {
+ NS_NewNamedThread("LayerScope", getter_AddRefs(mDebugSenderThread));
+
+ mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID);
+ int port = StaticPrefs::gfx_layerscope_port();
+ mServerSocket->Init(port, false, -1);
+ mServerSocket->AsyncListen(new SocketListener);
+}
+
+LayerScopeWebSocketManager::~LayerScopeWebSocketManager() {
+ mServerSocket->Close();
+}
+
+void LayerScopeWebSocketManager::AppendDebugData(DebugGLData* aDebugData) {
+ if (!mCurrentSender) {
+ mCurrentSender = new DebugDataSender(mDebugSenderThread);
+ }
+
+ mCurrentSender->Append(aDebugData);
+}
+
+void LayerScopeWebSocketManager::CleanDebugData() {
+ if (mCurrentSender) {
+ mCurrentSender->Cleanup();
+ }
+}
+
+void LayerScopeWebSocketManager::DispatchDebugData() {
+ MOZ_ASSERT(mCurrentSender.get() != nullptr);
+
+ mCurrentSender->Send();
+ mCurrentSender = nullptr;
+}
+
+NS_IMETHODIMP LayerScopeWebSocketManager::SocketListener::OnSocketAccepted(
+ nsIServerSocket* aServ, nsISocketTransport* aTransport) {
+ if (!gLayerScopeManager.GetSocketManager()) return NS_OK;
+
+ printf_stderr("*** LayerScope: Accepted connection\n");
+ gLayerScopeManager.GetSocketManager()->AddConnection(aTransport);
+ gLayerScopeManager.GetContentMonitor()->Empty();
+ return NS_OK;
+}
+
+// ----------------------------------------------
+// LayerScope implementation
+// ----------------------------------------------
+/*static*/
+void LayerScope::Init() {
+ if (!StaticPrefs::gfx_layerscope_enabled() || XRE_IsGPUProcess()) {
+ return;
+ }
+
+ gLayerScopeManager.CreateServerSocket();
+}
+
+/*static*/
+void LayerScope::DrawBegin() {
+ if (!CheckSendable()) {
+ return;
+ }
+
+ gLayerScopeManager.NewDrawSession();
+}
+
+/*static*/
+void LayerScope::SetRenderOffset(float aX, float aY) {
+ if (!CheckSendable()) {
+ return;
+ }
+
+ gLayerScopeManager.CurrentSession().mOffsetX = aX;
+ gLayerScopeManager.CurrentSession().mOffsetY = aY;
+}
+
+/*static*/
+void LayerScope::SetLayerTransform(const gfx::Matrix4x4& aMatrix) {
+ if (!CheckSendable()) {
+ return;
+ }
+
+ gLayerScopeManager.CurrentSession().mMVMatrix = aMatrix;
+}
+
+/*static*/
+void LayerScope::SetDrawRects(size_t aRects, const gfx::Rect* aLayerRects,
+ const gfx::Rect* aTextureRects) {
+ if (!CheckSendable()) {
+ return;
+ }
+
+ MOZ_ASSERT(aRects > 0 && aRects <= 4);
+ MOZ_ASSERT(aLayerRects);
+
+ gLayerScopeManager.CurrentSession().mRects = aRects;
+
+ for (size_t i = 0; i < aRects; i++) {
+ gLayerScopeManager.CurrentSession().mLayerRects[i] = aLayerRects[i];
+ gLayerScopeManager.CurrentSession().mTextureRects[i] = aTextureRects[i];
+ }
+}
+
+/*static*/
+void LayerScope::DrawEnd(gl::GLContext* aGLContext,
+ const EffectChain& aEffectChain, int aWidth,
+ int aHeight) {
+ // Protect this public function
+ if (!CheckSendable()) {
+ return;
+ }
+
+ // 1. Send textures.
+ SenderHelper::SendEffectChain(aGLContext, aEffectChain, aWidth, aHeight);
+
+ // 2. Send parameters of draw call, such as uniforms and attributes of
+ // vertex adnd fragment shader.
+ DrawSession& draws = gLayerScopeManager.CurrentSession();
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLDrawData(draws.mOffsetX, draws.mOffsetY, draws.mMVMatrix,
+ draws.mRects, draws.mLayerRects, draws.mTextureRects,
+ draws.mTexIDs, aEffectChain.mLayerRef));
+}
+
+/*static*/
+void LayerScope::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) {
+ // Protect this public function
+ if (!CheckSendable()) {
+ return;
+ }
+ SenderHelper::SendLayer(aLayer, aWidth, aHeight);
+}
+
+/*static*/
+void LayerScope::SendLayerDump(UniquePtr<Packet> aPacket) {
+ // Protect this public function
+ if (!CheckSendable() || !SenderHelper::GetLayersTreeSendable()) {
+ return;
+ }
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLLayersData(std::move(aPacket)));
+}
+
+/*static*/
+bool LayerScope::CheckSendable() {
+ // Only compositor threads check LayerScope status
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || gIsGtest);
+
+ if (!StaticPrefs::gfx_layerscope_enabled()) {
+ return false;
+ }
+ if (!gLayerScopeManager.GetSocketManager()) {
+ Init();
+ return false;
+ }
+ if (!gLayerScopeManager.GetSocketManager()->IsConnected()) {
+ return false;
+ }
+ return true;
+}
+
+/*static*/
+void LayerScope::CleanLayer() {
+ if (CheckSendable()) {
+ gLayerScopeManager.GetSocketManager()->CleanDebugData();
+ }
+}
+
+/*static*/
+void LayerScope::SetHWComposed() {
+ if (CheckSendable()) {
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLMetaData(Packet::META, true));
+ }
+}
+
+/*static*/
+void LayerScope::SetPixelScale(double devPixelsPerCSSPixel) {
+ gLayerScopeManager.SetPixelScale(devPixelsPerCSSPixel);
+}
+
+// ----------------------------------------------
+// LayerScopeAutoFrame implementation
+// ----------------------------------------------
+LayerScopeAutoFrame::LayerScopeAutoFrame(int64_t aFrameStamp) {
+ // Do Begin Frame
+ BeginFrame(aFrameStamp);
+}
+
+LayerScopeAutoFrame::~LayerScopeAutoFrame() {
+ // Do End Frame
+ EndFrame();
+}
+
+void LayerScopeAutoFrame::BeginFrame(int64_t aFrameStamp) {
+ if (!LayerScope::CheckSendable()) {
+ return;
+ }
+ SenderHelper::ClearSentTextureIds();
+
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLFrameStatusData(Packet::FRAMESTART, aFrameStamp));
+}
+
+void LayerScopeAutoFrame::EndFrame() {
+ if (!LayerScope::CheckSendable()) {
+ return;
+ }
+
+ gLayerScopeManager.GetSocketManager()->AppendDebugData(
+ new DebugGLFrameStatusData(Packet::FRAMEEND));
+ gLayerScopeManager.GetSocketManager()->DispatchDebugData();
+}
+
+} // namespace layers
+} // namespace mozilla