mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
00dd01290c
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
341 lines
13 KiB
C++
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>(¶ms),
|
|
"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>(¶ms.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>(¶ms.second),
|
|
"suggestedSizeAndPos", isPreEvent);
|
|
break;
|
|
}
|
|
case WM_MOVING: {
|
|
const auto& params = data.as<std::pair<WPARAM, RECT>>();
|
|
RectParamInfo(str, reinterpret_cast<uint64_t>(¶ms.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>(¶ms), "",
|
|
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
|