/* -*- 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 #include "mozilla/StaticPrefs_storage.h" #include "nsWindowLoggedMessages.h" #include "nsWindow.h" #include "WinUtils.h" #include #include namespace mozilla::widget { // NCCALCSIZE_PARAMS and WINDOWPOS are relatively large structures, so store // them as a pointer to save memory using NcCalcSizeVariantData = Variant>, RECT>; // to save memory, hold the raw data and only convert to string // when requested using MessageSpecificData = Variant, // WM_SIZE, WM_MOVE WINDOWPOS, // WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED std::pair, // WM_SIZING, WM_DPICHANGED, WM_MOVING std::pair, // WM_SETTINGCHANGE std::pair, // WM_NCCALCSIZE MINMAXINFO // WM_GETMINMAXINFO >; struct WindowMessageData { long mEventCounter; bool mIsPreEvent; MessageSpecificData mSpecificData; mozilla::Maybe mResult; LRESULT mRetValue; WindowMessageData(long eventCounter, bool isPreEvent, MessageSpecificData&& specificData, mozilla::Maybe 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 mMessages; }; static std::map> gWindowMessages; static HWND GetHwndFromWidget(nsIWidget* windowWidget) { nsWindow* window = static_cast(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(lParam); WINDOWPOS windowPos = *windowPosPtr; return MessageSpecificData(std::move(windowPos)); } case WM_SIZING: case WM_DPICHANGED: case WM_MOVING: { LPRECT rectPtr = reinterpret_cast(lParam); RECT rect = *rectPtr; return MessageSpecificData(std::make_pair(wParam, std::move(rect))); } case WM_SETTINGCHANGE: { LPCWSTR wideStrPtr = reinterpret_cast(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(lParam); NCCALCSIZE_PARAMS ncCalcSizeParams = *ncCalcSizeParamsPtr; WINDOWPOS windowPos = *ncCalcSizeParams.lppos; UniquePtr> ncCalcSizeData = MakeUnique>( std::pair(std::move(ncCalcSizeParams), std::move(windowPos))); return MessageSpecificData( std::make_pair(shouldIndicateValidArea, NcCalcSizeVariantData(std::move(ncCalcSizeData)))); } else { LPRECT rectPtr = reinterpret_cast(lParam); RECT rect = *rectPtr; return MessageSpecificData(std::make_pair( shouldIndicateValidArea, NcCalcSizeVariantData(std::move(rect)))); } } case WM_GETMINMAXINFO: { PMINMAXINFO minMaxInfoPtr = reinterpret_cast(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>(); nsAutoCString tempStr = WmSizeParamInfo(params.first, params.second, isPreEvent); str.AppendASCII(tempStr); break; } case WM_MOVE: { const auto& params = data.as>(); XLowWordYHighWordParamInfo(str, params.second, "upperLeft", isPreEvent); break; } case WM_WINDOWPOSCHANGING: case WM_WINDOWPOSCHANGED: { const auto& params = data.as(); WindowPosParamInfo(str, reinterpret_cast(¶ms), "newSizeAndPos", isPreEvent); break; } case WM_SIZING: { const auto& params = data.as>(); WindowEdgeParamInfo(str, params.first, "edge", isPreEvent); str.AppendASCII(" "); RectParamInfo(str, reinterpret_cast(¶ms.second), "rect", isPreEvent); break; } case WM_DPICHANGED: { const auto& params = data.as>(); XLowWordYHighWordParamInfo(str, params.first, "newDPI", isPreEvent); str.AppendASCII(" "); RectParamInfo(str, reinterpret_cast(¶ms.second), "suggestedSizeAndPos", isPreEvent); break; } case WM_MOVING: { const auto& params = data.as>(); RectParamInfo(str, reinterpret_cast(¶ms.second), "rect", isPreEvent); break; } case WM_SETTINGCHANGE: { const auto& params = data.as>(); UiActionParamInfo(str, params.first, "uiAction", isPreEvent); str.AppendASCII(" "); WideStringParamInfo( str, reinterpret_cast((const wchar_t*)(params.second.Data())), "paramChanged", isPreEvent); break; } case WM_NCCALCSIZE: { const auto& params = data.as>(); bool shouldIndicateValidArea = params.first; if (shouldIndicateValidArea) { const auto& validAreaParams = params.second .as>>(); // Make pointer point to the cached data validAreaParams->first.lppos = &validAreaParams->second; nsAutoCString tempStr = WmNcCalcSizeParamInfo( TRUE, reinterpret_cast(&validAreaParams->first), isPreEvent); str.AppendASCII(tempStr); } else { RECT rect = params.second.as(); nsAutoCString tempStr = WmNcCalcSizeParamInfo( FALSE, reinterpret_cast(&rect), isPreEvent); str.AppendASCII(tempStr); } break; } case WM_GETMINMAXINFO: { const auto& params = data.as(); MinMaxInfoParamInfo(str, reinterpret_cast(¶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 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(retValue) : 0, resultMsg); return str; } void WindowClosed(HWND hwnd) { gWindowMessages.erase(hwnd); } void LogWindowMessage(HWND hwnd, UINT event, bool isPreEvent, long eventCounter, WPARAM wParam, LPARAM lParam, mozilla::Maybe result, LRESULT retValue) { auto& hwndMessages = gWindowMessages[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(RefPtr windowWidget, nsTArray& messages) { HWND hwnd = GetHwndFromWidget(windowWidget); const auto& rawMessages = gWindowMessages[hwnd]; nsTArray> 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 (const std::pair& entry : sortKeyAndMessageArray) { messages.AppendElement(std::move(entry.second)); } } } // namespace mozilla::widget