From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- widget/windows/RemoteBackbuffer.cpp | 675 ++++++++++++++++++++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100644 widget/windows/RemoteBackbuffer.cpp (limited to 'widget/windows/RemoteBackbuffer.cpp') diff --git a/widget/windows/RemoteBackbuffer.cpp b/widget/windows/RemoteBackbuffer.cpp new file mode 100644 index 0000000000..08b420affb --- /dev/null +++ b/widget/windows/RemoteBackbuffer.cpp @@ -0,0 +1,675 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "RemoteBackbuffer.h" +#include "mozilla/Span.h" +#include +#include + +namespace mozilla { +namespace widget { +namespace remote_backbuffer { + +// This number can be adjusted as a time-memory tradeoff +constexpr uint8_t kMaxDirtyRects = 8; + +struct IpcSafeRect { + explicit IpcSafeRect(const gfx::IntRect& aRect) + : x(aRect.x), y(aRect.y), width(aRect.width), height(aRect.height) {} + int32_t x; + int32_t y; + int32_t width; + int32_t height; +}; + +enum class ResponseResult { + Unknown, + Error, + BorrowSuccess, + BorrowSameBuffer, + PresentSuccess +}; + +enum class SharedDataType { + BorrowRequest, + BorrowRequestAllowSameBuffer, + BorrowResponse, + PresentRequest, + PresentResponse +}; + +struct BorrowResponseData { + ResponseResult result; + int32_t width; + int32_t height; + HANDLE fileMapping; +}; + +struct PresentRequestData { + uint8_t lenDirtyRects; + IpcSafeRect dirtyRects[kMaxDirtyRects]; +}; + +struct PresentResponseData { + ResponseResult result; +}; + +struct SharedData { + SharedDataType dataType; + union { + BorrowResponseData borrowResponse; + PresentRequestData presentRequest; + PresentResponseData presentResponse; + } data; +}; + +static_assert(std::is_trivially_copyable::value && + std::is_standard_layout::value, + "SharedData must be safe to pass over IPC boundaries"); + +class SharedImage { + public: + SharedImage() + : mWidth(0), mHeight(0), mFileMapping(nullptr), mPixelData(nullptr) {} + + ~SharedImage() { + if (mPixelData) { + MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mPixelData)); + } + + if (mFileMapping) { + MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping)); + } + } + + bool Initialize(int32_t aWidth, int32_t aHeight) { + MOZ_ASSERT(aWidth > 0); + MOZ_ASSERT(aHeight > 0); + + mWidth = aWidth; + mHeight = aHeight; + + DWORD bufferSize = static_cast(mHeight * GetStride()); + + mFileMapping = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE, + 0 /*sizeHigh*/, bufferSize, nullptr /*name*/); + if (!mFileMapping) { + return false; + } + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mPixelData = reinterpret_cast(mappedFilePtr); + + return true; + } + + bool InitializeRemote(int32_t aWidth, int32_t aHeight, HANDLE aFileMapping) { + MOZ_ASSERT(aWidth > 0); + MOZ_ASSERT(aHeight > 0); + MOZ_ASSERT(aFileMapping); + + mWidth = aWidth; + mHeight = aHeight; + mFileMapping = aFileMapping; + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mPixelData = reinterpret_cast(mappedFilePtr); + + return true; + } + + HBITMAP CreateDIBSection() { + BITMAPINFO bitmapInfo = {}; + bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader); + bitmapInfo.bmiHeader.biWidth = mWidth; + bitmapInfo.bmiHeader.biHeight = -mHeight; + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + void* dummy = nullptr; + return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo, + DIB_RGB_COLORS, &dummy, mFileMapping, + 0 /*offset*/); + } + + HANDLE CreateRemoteFileMapping(DWORD aTargetProcessId) { + MOZ_ASSERT(aTargetProcessId); + + HANDLE fileMapping = nullptr; + if (!ipc::DuplicateHandle(mFileMapping, aTargetProcessId, &fileMapping, + 0 /*desiredAccess*/, DUPLICATE_SAME_ACCESS)) { + return nullptr; + } + return fileMapping; + } + + already_AddRefed CreateDrawTarget() { + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::CAIRO, mPixelData, IntSize(mWidth, mHeight), + GetStride(), gfx::SurfaceFormat::B8G8R8A8); + } + + void CopyPixelsFrom(const SharedImage& other) { + const unsigned char* src = other.mPixelData; + unsigned char* dst = mPixelData; + + int32_t width = std::min(mWidth, other.mWidth); + int32_t height = std::min(mHeight, other.mHeight); + + for (int32_t row = 0; row < height; ++row) { + memcpy(dst, src, static_cast(width * kBytesPerPixel)); + src += other.GetStride(); + dst += GetStride(); + } + } + + int32_t GetWidth() { return mWidth; } + + int32_t GetHeight() { return mHeight; } + + SharedImage(const SharedImage&) = delete; + SharedImage(SharedImage&&) = delete; + SharedImage& operator=(const SharedImage&) = delete; + SharedImage& operator=(SharedImage&&) = delete; + + private: + static constexpr int32_t kBytesPerPixel = 4; + + int32_t GetStride() const { + // DIB requires 32-bit row alignment + return (((mWidth * kBytesPerPixel) + 3) / 4) * 4; + } + + int32_t mWidth; + int32_t mHeight; + HANDLE mFileMapping; + unsigned char* mPixelData; +}; + +class PresentableSharedImage { + public: + PresentableSharedImage() + : mSharedImage(), + mDeviceContext(nullptr), + mDIBSection(nullptr), + mSavedObject(nullptr) {} + + ~PresentableSharedImage() { + if (mSavedObject) { + MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext, mSavedObject)); + } + + if (mDIBSection) { + MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection)); + } + + if (mDeviceContext) { + MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext)); + } + } + + bool Initialize(int32_t aWidth, int32_t aHeight) { + if (!mSharedImage.Initialize(aWidth, aHeight)) { + return false; + } + + mDeviceContext = ::CreateCompatibleDC(nullptr); + if (!mDeviceContext) { + return false; + } + + mDIBSection = mSharedImage.CreateDIBSection(); + if (!mDIBSection) { + return false; + } + + mSavedObject = ::SelectObject(mDeviceContext, mDIBSection); + if (!mSavedObject) { + return false; + } + + return true; + } + + bool PresentToWindow(HWND aWindowHandle, nsTransparencyMode aTransparencyMode, + Span aDirtyRects) { + if (aTransparencyMode == eTransparencyTransparent) { + // If our window is a child window or a child-of-a-child, the window + // that needs to be updated is the top level ancestor of the tree + HWND topLevelWindow = WinUtils::GetTopLevelHWND(aWindowHandle, true); + MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow, GWL_EXSTYLE) & + WS_EX_LAYERED); + + BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + SIZE winSize = {mSharedImage.GetWidth(), mSharedImage.GetHeight()}; + POINT srcPos = {0, 0}; + return !!::UpdateLayeredWindow( + topLevelWindow, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize, + mDeviceContext, &srcPos, 0 /*colorKey*/, &bf, ULW_ALPHA); + } + + IntRect sharedImageRect{0, 0, mSharedImage.GetWidth(), + mSharedImage.GetHeight()}; + + bool result = true; + + HDC windowDC = ::GetDC(aWindowHandle); + if (!windowDC) { + return false; + } + + for (auto& ipcDirtyRect : aDirtyRects) { + IntRect dirtyRect{ipcDirtyRect.x, ipcDirtyRect.y, ipcDirtyRect.width, + ipcDirtyRect.height}; + IntRect bltRect = dirtyRect.Intersect(sharedImageRect); + + if (!::BitBlt(windowDC, bltRect.x /*dstX*/, bltRect.y /*dstY*/, + bltRect.width, bltRect.height, mDeviceContext, + bltRect.x /*srcX*/, bltRect.y /*srcY*/, SRCCOPY)) { + result = false; + break; + } + } + + MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle, windowDC)); + + return result; + } + + HANDLE CreateRemoteFileMapping(DWORD aTargetProcessId) { + return mSharedImage.CreateRemoteFileMapping(aTargetProcessId); + } + + already_AddRefed CreateDrawTarget() { + return mSharedImage.CreateDrawTarget(); + } + + void CopyPixelsFrom(const PresentableSharedImage& other) { + mSharedImage.CopyPixelsFrom(other.mSharedImage); + } + + int32_t GetWidth() { return mSharedImage.GetWidth(); } + + int32_t GetHeight() { return mSharedImage.GetHeight(); } + + PresentableSharedImage(const PresentableSharedImage&) = delete; + PresentableSharedImage(PresentableSharedImage&&) = delete; + PresentableSharedImage& operator=(const PresentableSharedImage&) = delete; + PresentableSharedImage& operator=(PresentableSharedImage&&) = delete; + + private: + SharedImage mSharedImage; + HDC mDeviceContext; + HBITMAP mDIBSection; + HGDIOBJ mSavedObject; +}; + +Provider::Provider() + : mWindowHandle(nullptr), + mTargetProcessId(0), + mFileMapping(nullptr), + mRequestReadyEvent(nullptr), + mResponseReadyEvent(nullptr), + mSharedDataPtr(nullptr), + mStopServiceThread(false), + mServiceThread(), + mBackbuffer() {} + +Provider::~Provider() { + mBackbuffer.reset(); + + if (mServiceThread.joinable()) { + mStopServiceThread = true; + MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent)); + mServiceThread.join(); + } + + if (mSharedDataPtr) { + MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr)); + } + + if (mResponseReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent)); + } + + if (mRequestReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent)); + } + + if (mFileMapping) { + MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping)); + } +} + +bool Provider::Initialize(HWND aWindowHandle, DWORD aTargetProcessId, + nsTransparencyMode aTransparencyMode) { + MOZ_ASSERT(aWindowHandle); + MOZ_ASSERT(aTargetProcessId); + + mWindowHandle = aWindowHandle; + mTargetProcessId = aTargetProcessId; + + mFileMapping = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE, 0 /*sizeHigh*/, + static_cast(sizeof(SharedData)), nullptr /*name*/); + if (!mFileMapping) { + return false; + } + + mRequestReadyEvent = + ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/, + FALSE /*initialState*/, nullptr /*name*/); + if (!mRequestReadyEvent) { + return false; + } + + mResponseReadyEvent = + ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/, + FALSE /*initialState*/, nullptr /*name*/); + if (!mResponseReadyEvent) { + return false; + } + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mSharedDataPtr = reinterpret_cast(mappedFilePtr); + + mStopServiceThread = false; + + mServiceThread = std::thread([this] { this->ThreadMain(); }); + + mTransparencyMode = aTransparencyMode; + + return true; +} + +Maybe Provider::CreateRemoteHandles() { + HANDLE fileMapping = nullptr; + if (!ipc::DuplicateHandle(mFileMapping, mTargetProcessId, &fileMapping, + 0 /*desiredAccess*/, DUPLICATE_SAME_ACCESS)) { + return Nothing(); + } + + HANDLE requestReadyEvent = nullptr; + if (!ipc::DuplicateHandle(mRequestReadyEvent, mTargetProcessId, + &requestReadyEvent, 0 /*desiredAccess*/, + DUPLICATE_SAME_ACCESS)) { + return Nothing(); + } + + HANDLE responseReadyEvent = nullptr; + if (!ipc::DuplicateHandle(mResponseReadyEvent, mTargetProcessId, + &responseReadyEvent, 0 /*desiredAccess*/, + DUPLICATE_SAME_ACCESS)) { + return Nothing(); + } + + return Some(RemoteBackbufferHandles( + reinterpret_cast(fileMapping), + reinterpret_cast(requestReadyEvent), + reinterpret_cast(responseReadyEvent))); +} + +void Provider::UpdateTransparencyMode(nsTransparencyMode aTransparencyMode) { + mTransparencyMode = aTransparencyMode; +} + +void Provider::ThreadMain() { + while (true) { + MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent, INFINITE) == + WAIT_OBJECT_0); + + if (mStopServiceThread) { + break; + } + + switch (mSharedDataPtr->dataType) { + case SharedDataType::BorrowRequest: + case SharedDataType::BorrowRequestAllowSameBuffer: { + BorrowResponseData responseData = {}; + + HandleBorrowRequest(&responseData, + mSharedDataPtr->dataType == + SharedDataType::BorrowRequestAllowSameBuffer); + + mSharedDataPtr->dataType = SharedDataType::BorrowResponse; + mSharedDataPtr->data.borrowResponse = responseData; + + MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent)); + + break; + } + case SharedDataType::PresentRequest: { + PresentRequestData requestData = mSharedDataPtr->data.presentRequest; + PresentResponseData responseData = {}; + + HandlePresentRequest(requestData, &responseData); + + mSharedDataPtr->dataType = SharedDataType::PresentResponse; + mSharedDataPtr->data.presentResponse = responseData; + + MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent)); + + break; + } + default: + break; + }; + } +} + +void Provider::HandleBorrowRequest(BorrowResponseData* aResponseData, + bool aAllowSameBuffer) { + MOZ_ASSERT(aResponseData); + + aResponseData->result = ResponseResult::Error; + + RECT clientRect = {}; + if (!::GetClientRect(mWindowHandle, &clientRect)) { + return; + } + + MOZ_ASSERT(clientRect.left == 0); + MOZ_ASSERT(clientRect.top == 0); + + int32_t width = clientRect.right ? clientRect.right : 1; + int32_t height = clientRect.bottom ? clientRect.bottom : 1; + + bool needNewBackbuffer = !aAllowSameBuffer || !mBackbuffer || + (mBackbuffer->GetWidth() != width) || + (mBackbuffer->GetHeight() != height); + + if (!needNewBackbuffer) { + aResponseData->result = ResponseResult::BorrowSameBuffer; + return; + } + + auto newBackbuffer = std::make_unique(); + if (!newBackbuffer->Initialize(width, height)) { + return; + } + + // Preserve the contents of the old backbuffer (if it exists) + if (mBackbuffer) { + newBackbuffer->CopyPixelsFrom(*mBackbuffer); + mBackbuffer.reset(); + } + + HANDLE remoteFileMapping = + newBackbuffer->CreateRemoteFileMapping(mTargetProcessId); + if (!remoteFileMapping) { + return; + } + + aResponseData->result = ResponseResult::BorrowSuccess; + aResponseData->width = width; + aResponseData->height = height; + aResponseData->fileMapping = remoteFileMapping; + + mBackbuffer = std::move(newBackbuffer); +} + +void Provider::HandlePresentRequest(const PresentRequestData& aRequestData, + PresentResponseData* aResponseData) { + MOZ_ASSERT(aResponseData); + + Span rectSpan(aRequestData.dirtyRects, kMaxDirtyRects); + + aResponseData->result = ResponseResult::Error; + + if (!mBackbuffer) { + return; + } + + if (!mBackbuffer->PresentToWindow( + mWindowHandle, mTransparencyMode, + rectSpan.First(aRequestData.lenDirtyRects))) { + return; + } + + aResponseData->result = ResponseResult::PresentSuccess; +} + +Client::Client() + : mFileMapping(nullptr), + mRequestReadyEvent(nullptr), + mResponseReadyEvent(nullptr), + mSharedDataPtr(nullptr), + mBackbuffer() {} + +Client::~Client() { + mBackbuffer.reset(); + + if (mSharedDataPtr) { + MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr)); + } + + if (mResponseReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent)); + } + + if (mRequestReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent)); + } + + if (mFileMapping) { + MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping)); + } +} + +bool Client::Initialize(const RemoteBackbufferHandles& aRemoteHandles) { + MOZ_ASSERT(aRemoteHandles.fileMapping()); + MOZ_ASSERT(aRemoteHandles.requestReadyEvent()); + MOZ_ASSERT(aRemoteHandles.responseReadyEvent()); + + mFileMapping = reinterpret_cast(aRemoteHandles.fileMapping()); + mRequestReadyEvent = + reinterpret_cast(aRemoteHandles.requestReadyEvent()); + mResponseReadyEvent = + reinterpret_cast(aRemoteHandles.responseReadyEvent()); + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mSharedDataPtr = reinterpret_cast(mappedFilePtr); + + return true; +} + +already_AddRefed Client::BorrowDrawTarget() { + mSharedDataPtr->dataType = mBackbuffer + ? SharedDataType::BorrowRequestAllowSameBuffer + : SharedDataType::BorrowRequest; + + MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent)); + MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) == + WAIT_OBJECT_0); + + if (mSharedDataPtr->dataType != SharedDataType::BorrowResponse) { + return nullptr; + } + + BorrowResponseData responseData = mSharedDataPtr->data.borrowResponse; + + if ((responseData.result != ResponseResult::BorrowSameBuffer) && + (responseData.result != ResponseResult::BorrowSuccess)) { + return nullptr; + } + + if (responseData.result == ResponseResult::BorrowSuccess) { + mBackbuffer.reset(); + + auto newBackbuffer = std::make_unique(); + if (!newBackbuffer->InitializeRemote(responseData.width, + responseData.height, + responseData.fileMapping)) { + return nullptr; + } + + mBackbuffer = std::move(newBackbuffer); + } + + MOZ_ASSERT(mBackbuffer); + + return mBackbuffer->CreateDrawTarget(); +} + +bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion) { + mSharedDataPtr->dataType = SharedDataType::PresentRequest; + + // Simplify the region until it has <= kMaxDirtyRects + aDirtyRegion.SimplifyOutward(kMaxDirtyRects); + + Span rectSpan(mSharedDataPtr->data.presentRequest.dirtyRects, kMaxDirtyRects); + + uint8_t rectIndex = 0; + for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { + rectSpan[rectIndex] = IpcSafeRect(iter.Get()); + ++rectIndex; + } + + mSharedDataPtr->data.presentRequest.lenDirtyRects = rectIndex; + + MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent)); + MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) == + WAIT_OBJECT_0); + + if (mSharedDataPtr->dataType != SharedDataType::PresentResponse) { + return false; + } + + if (mSharedDataPtr->data.presentResponse.result != + ResponseResult::PresentSuccess) { + return false; + } + + return true; +} + +} // namespace remote_backbuffer +} // namespace widget +} // namespace mozilla -- cgit v1.2.3