gecko-dev/widget/windows/nsWindowLoggedMessages.cpp
Ray Kraesig 00dd01290c Bug 1915665 - [0/2] Clear out NativeEventLogger's static data on shutdown r=win-reviewers,gstoll
If a HWND whose messages are logged is not destroyed before application
shutdown, any static data in NativeEventLogger's persistent store will
not be cleared out. This can show up as a memory leak during testing.

Set up a RunOnShutdown hook to ensure that everything is actually
destroyed during shutdown, even if some other module is ill-behaved.

Differential Revision: https://phabricator.services.mozilla.com/D222382
2024-09-17 19:16:45 +00:00

341 lines
13 KiB
C++

/* -*- 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 <windef.h>
#include <winuser.h>
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Likely.h"
#include "mozilla/StaticPrefs_storage.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsWindowLoggedMessages.h"
#include "nsWindow.h"
#include "WinUtils.h"
#include <map>
#include <algorithm>
namespace mozilla::widget {
// NCCALCSIZE_PARAMS and WINDOWPOS are relatively large structures, so store
// them as a pointer to save memory
using NcCalcSizeVariantData =
Variant<UniquePtr<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>>, RECT>;
// to save memory, hold the raw data and only convert to string
// when requested
using MessageSpecificData =
Variant<std::pair<WPARAM, LPARAM>, // WM_SIZE, WM_MOVE
WINDOWPOS, // WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED
std::pair<WPARAM, RECT>, // WM_SIZING, WM_DPICHANGED, WM_MOVING
std::pair<WPARAM, nsString>, // WM_SETTINGCHANGE
std::pair<bool, NcCalcSizeVariantData>, // WM_NCCALCSIZE
MINMAXINFO // WM_GETMINMAXINFO
>;
struct WindowMessageData {
long mEventCounter;
bool mIsPreEvent;
MessageSpecificData mSpecificData;
mozilla::Maybe<bool> mResult;
LRESULT mRetValue;
WindowMessageData(long eventCounter, bool isPreEvent,
MessageSpecificData&& specificData,
mozilla::Maybe<bool> result, LRESULT retValue)
: mEventCounter(eventCounter),
mIsPreEvent(isPreEvent),
mSpecificData(std::move(specificData)),
mResult(result),
mRetValue(retValue) {}
// Disallow copy constructor/operator since MessageSpecificData has a
// UniquePtr
WindowMessageData(const WindowMessageData&) = delete;
WindowMessageData& operator=(const WindowMessageData&) = delete;
WindowMessageData(WindowMessageData&&) = default;
WindowMessageData& operator=(WindowMessageData&&) = default;
};
struct WindowMessageDataSortKey {
long mEventCounter;
bool mIsPreEvent;
explicit WindowMessageDataSortKey(const WindowMessageData& data)
: mEventCounter(data.mEventCounter), mIsPreEvent(data.mIsPreEvent) {}
bool operator<(const WindowMessageDataSortKey& other) const {
if (mEventCounter < other.mEventCounter) {
return true;
}
if (other.mEventCounter < mEventCounter) {
return false;
}
if (mIsPreEvent && !other.mIsPreEvent) {
return true;
}
if (other.mIsPreEvent && !mIsPreEvent) {
return false;
}
// they're equal
return false;
}
};
struct CircularMessageBuffer {
// Only used when the vector is at its maximum size
size_t mNextFreeIndex = 0;
std::vector<WindowMessageData> mMessages;
};
using WindowMessageMap = std::map<HWND, std::map<UINT, CircularMessageBuffer>>;
static WindowMessageMap* GetStaticMessageBuffer() {
MOZ_ASSERT(NS_IsMainThread());
static StaticAutoPtr<WindowMessageMap> gWindowMessages;
static bool gWindowMessagesShutdown = false;
if (MOZ_LIKELY(gWindowMessages)) {
return gWindowMessages.get();
}
if (gWindowMessagesShutdown) {
return nullptr;
}
gWindowMessages = new WindowMessageMap();
RunOnShutdown([]() {
MOZ_ASSERT(NS_IsMainThread());
gWindowMessages = nullptr;
gWindowMessagesShutdown = true;
});
return gWindowMessages.get();
}
static HWND GetHwndFromWidget(nsIWidget* windowWidget) {
nsWindow* window = static_cast<nsWindow*>(windowWidget);
return window->GetWindowHandle();
}
MessageSpecificData MakeMessageSpecificData(UINT event, WPARAM wParam,
LPARAM lParam) {
// Since we store this data for every message we log, make sure it's of a
// reasonable size. Keep in mind we're storing up to 10 (number of message
// types)
// * 6 (default number of messages per type to keep) of these messages per
// window.
static_assert(sizeof(MessageSpecificData) <= 48);
switch (event) {
case WM_SIZE:
case WM_MOVE:
return MessageSpecificData(std::make_pair(wParam, lParam));
case WM_WINDOWPOSCHANGING:
case WM_WINDOWPOSCHANGED: {
LPWINDOWPOS windowPosPtr = reinterpret_cast<LPWINDOWPOS>(lParam);
WINDOWPOS windowPos = *windowPosPtr;
return MessageSpecificData(std::move(windowPos));
}
case WM_SIZING:
case WM_DPICHANGED:
case WM_MOVING: {
LPRECT rectPtr = reinterpret_cast<LPRECT>(lParam);
RECT rect = *rectPtr;
return MessageSpecificData(std::make_pair(wParam, std::move(rect)));
}
case WM_SETTINGCHANGE: {
LPCWSTR wideStrPtr = reinterpret_cast<LPCWSTR>(lParam);
nsString str(wideStrPtr);
return MessageSpecificData(std::make_pair(wParam, std::move(str)));
}
case WM_NCCALCSIZE: {
bool shouldIndicateValidArea = wParam == TRUE;
if (shouldIndicateValidArea) {
LPNCCALCSIZE_PARAMS ncCalcSizeParamsPtr =
reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam);
NCCALCSIZE_PARAMS ncCalcSizeParams = *ncCalcSizeParamsPtr;
WINDOWPOS windowPos = *ncCalcSizeParams.lppos;
UniquePtr<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>> ncCalcSizeData =
MakeUnique<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>>(
std::pair(std::move(ncCalcSizeParams), std::move(windowPos)));
return MessageSpecificData(
std::make_pair(shouldIndicateValidArea,
NcCalcSizeVariantData(std::move(ncCalcSizeData))));
} else {
LPRECT rectPtr = reinterpret_cast<LPRECT>(lParam);
RECT rect = *rectPtr;
return MessageSpecificData(std::make_pair(
shouldIndicateValidArea, NcCalcSizeVariantData(std::move(rect))));
}
}
case WM_GETMINMAXINFO: {
PMINMAXINFO minMaxInfoPtr = reinterpret_cast<PMINMAXINFO>(lParam);
MINMAXINFO minMaxInfo = *minMaxInfoPtr;
return MessageSpecificData(std::move(minMaxInfo));
}
default:
MOZ_ASSERT_UNREACHABLE(
"Unhandled message type in MakeMessageSpecificData");
return MessageSpecificData(std::make_pair(wParam, lParam));
}
}
void AppendFriendlyMessageSpecificData(nsCString& str, UINT event,
bool isPreEvent,
const MessageSpecificData& data) {
switch (event) {
case WM_SIZE: {
const auto& params = data.as<std::pair<WPARAM, LPARAM>>();
nsAutoCString tempStr =
WmSizeParamInfo(params.first, params.second, isPreEvent);
str.AppendASCII(tempStr);
break;
}
case WM_MOVE: {
const auto& params = data.as<std::pair<WPARAM, LPARAM>>();
XLowWordYHighWordParamInfo(str, params.second, "upperLeft", isPreEvent);
break;
}
case WM_WINDOWPOSCHANGING:
case WM_WINDOWPOSCHANGED: {
const auto& params = data.as<WINDOWPOS>();
WindowPosParamInfo(str, reinterpret_cast<uint64_t>(&params),
"newSizeAndPos", isPreEvent);
break;
}
case WM_SIZING: {
const auto& params = data.as<std::pair<WPARAM, RECT>>();
WindowEdgeParamInfo(str, params.first, "edge", isPreEvent);
str.AppendASCII(" ");
RectParamInfo(str, reinterpret_cast<uint64_t>(&params.second), "rect",
isPreEvent);
break;
}
case WM_DPICHANGED: {
const auto& params = data.as<std::pair<WPARAM, RECT>>();
XLowWordYHighWordParamInfo(str, params.first, "newDPI", isPreEvent);
str.AppendASCII(" ");
RectParamInfo(str, reinterpret_cast<uint64_t>(&params.second),
"suggestedSizeAndPos", isPreEvent);
break;
}
case WM_MOVING: {
const auto& params = data.as<std::pair<WPARAM, RECT>>();
RectParamInfo(str, reinterpret_cast<uint64_t>(&params.second), "rect",
isPreEvent);
break;
}
case WM_SETTINGCHANGE: {
const auto& params = data.as<std::pair<WPARAM, nsString>>();
UiActionParamInfo(str, params.first, "uiAction", isPreEvent);
str.AppendASCII(" ");
WideStringParamInfo(
str,
reinterpret_cast<uint64_t>((const wchar_t*)(params.second.Data())),
"paramChanged", isPreEvent);
break;
}
case WM_NCCALCSIZE: {
const auto& params = data.as<std::pair<bool, NcCalcSizeVariantData>>();
bool shouldIndicateValidArea = params.first;
if (shouldIndicateValidArea) {
const auto& validAreaParams =
params.second
.as<UniquePtr<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>>>();
// Make pointer point to the cached data
validAreaParams->first.lppos = &validAreaParams->second;
nsAutoCString tempStr = WmNcCalcSizeParamInfo(
TRUE, reinterpret_cast<uint64_t>(&validAreaParams->first),
isPreEvent);
str.AppendASCII(tempStr);
} else {
RECT rect = params.second.as<RECT>();
nsAutoCString tempStr = WmNcCalcSizeParamInfo(
FALSE, reinterpret_cast<uint64_t>(&rect), isPreEvent);
str.AppendASCII(tempStr);
}
break;
}
case WM_GETMINMAXINFO: {
const auto& params = data.as<MINMAXINFO>();
MinMaxInfoParamInfo(str, reinterpret_cast<uint64_t>(&params), "",
isPreEvent);
break;
}
default:
MOZ_ASSERT(false,
"Unhandled message type in AppendFriendlyMessageSpecificData");
str.AppendASCII("???");
}
}
nsCString MakeFriendlyMessage(UINT event, bool isPreEvent, long eventCounter,
const MessageSpecificData& data,
mozilla::Maybe<bool> result, LRESULT retValue) {
nsCString str;
const char* eventName = mozilla::widget::WinUtils::WinEventToEventName(event);
MOZ_ASSERT(eventName, "Unknown event name in MakeFriendlyMessage");
eventName = eventName ? eventName : "(unknown)";
str.AppendPrintf("%6ld %04x (%s) - ", eventCounter, event, eventName);
AppendFriendlyMessageSpecificData(str, event, isPreEvent, data);
const char* resultMsg =
result.isSome() ? (result.value() ? "true" : "false") : "initial call";
str.AppendPrintf(" 0x%08llX (%s)",
result.isSome() ? static_cast<uint64_t>(retValue) : 0,
resultMsg);
return str;
}
void WindowClosed(HWND hwnd) {
if (auto* msgs = GetStaticMessageBuffer()) {
msgs->erase(hwnd);
}
}
void LogWindowMessage(HWND hwnd, UINT event, bool isPreEvent, long eventCounter,
WPARAM wParam, LPARAM lParam, mozilla::Maybe<bool> result,
LRESULT retValue) {
auto* messageBuf = GetStaticMessageBuffer();
if (!messageBuf) return;
auto& hwndMessages = (*messageBuf)[hwnd];
auto& hwndWindowMessages = hwndMessages[event];
WindowMessageData messageData = {
eventCounter, isPreEvent, MakeMessageSpecificData(event, wParam, lParam),
result, retValue};
uint32_t numberOfMessagesToKeep =
StaticPrefs::widget_windows_messages_to_log();
if (hwndWindowMessages.mMessages.size() < numberOfMessagesToKeep) {
// haven't reached limit yet
hwndWindowMessages.mMessages.push_back(std::move(messageData));
} else {
hwndWindowMessages.mMessages[hwndWindowMessages.mNextFreeIndex] =
std::move(messageData);
}
hwndWindowMessages.mNextFreeIndex =
(hwndWindowMessages.mNextFreeIndex + 1) % numberOfMessagesToKeep;
}
void GetLatestWindowMessages(nsIWidget* windowWidget,
nsTArray<nsCString>& messages) {
auto* messageBuf = GetStaticMessageBuffer();
if (!messageBuf) return;
HWND hwnd = GetHwndFromWidget(windowWidget);
const auto& rawMessages = (*messageBuf)[hwnd];
nsTArray<std::pair<WindowMessageDataSortKey, nsCString>>
sortKeyAndMessageArray;
sortKeyAndMessageArray.SetCapacity(
rawMessages.size() * StaticPrefs::widget_windows_messages_to_log());
for (const auto& eventAndMessage : rawMessages) {
for (const auto& messageData : eventAndMessage.second.mMessages) {
nsCString message = MakeFriendlyMessage(
eventAndMessage.first, messageData.mIsPreEvent,
messageData.mEventCounter, messageData.mSpecificData,
messageData.mResult, messageData.mRetValue);
WindowMessageDataSortKey sortKey(messageData);
sortKeyAndMessageArray.AppendElement(
std::make_pair(sortKey, std::move(message)));
}
}
std::sort(sortKeyAndMessageArray.begin(), sortKeyAndMessageArray.end());
messages.SetCapacity(sortKeyAndMessageArray.Length());
for (std::pair<WindowMessageDataSortKey, nsCString>& entry :
sortKeyAndMessageArray) {
messages.AppendElement(std::move(entry.second));
}
}
} // namespace mozilla::widget