Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / widget / windows / WinEventObserver.cpp
blob16e383b2db2f643b1c140aff009ff1ff878e75e8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <windows.h>
8 #include <winternl.h>
9 #include <winuser.h>
10 #include <wtsapi32.h>
11 #include <dbt.h>
13 #include "WinEventObserver.h"
15 #include "InputDeviceUtils.h"
16 #include "ScreenHelperWin.h"
17 #include "WindowsUIUtils.h"
18 #include "WinWindowOcclusionTracker.h"
20 #include "gfxDWriteFonts.h"
21 #include "gfxPlatform.h"
22 #include "mozilla/Assertions.h"
23 #include "mozilla/ClearOnShutdown.h"
24 #include "mozilla/Logging.h"
25 #include "mozilla/LookAndFeel.h"
26 #include "mozilla/WindowsVersion.h"
27 #include "nsLookAndFeel.h"
28 #include "nsStringFwd.h"
29 #include "nsWindowDbg.h"
30 #include "nsdefs.h"
31 #include "nsXULAppAPI.h"
33 // borrowed from devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483, by way
34 // of the Chromium sandboxing code's "current_module.h"
35 extern "C" IMAGE_DOS_HEADER __ImageBase;
36 #define CURRENT_MODULE() reinterpret_cast<HMODULE>(&__ImageBase)
38 namespace mozilla::widget {
40 LazyLogModule gWinEventWindowLog("WinEventWindow");
41 #define OBS_LOG(...) \
42 MOZ_LOG(gWinEventWindowLog, ::mozilla::LogLevel::Info, (__VA_ARGS__))
44 namespace {
45 namespace evtwin_details {
46 static HWND sHiddenWindow = nullptr;
47 static bool sHiddenWindowShutdown = false;
48 HDEVNOTIFY sDeviceNotifyHandle = nullptr;
49 } // namespace evtwin_details
50 } // namespace
52 /* static */
53 void WinEventWindow::Ensure() {
54 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
55 MOZ_RELEASE_ASSERT(NS_IsMainThread());
57 using namespace evtwin_details;
59 if (sHiddenWindow) return;
60 if (sHiddenWindowShutdown) return;
62 HMODULE const hSelf = CURRENT_MODULE();
63 WNDCLASSW const wc = {.lpfnWndProc = WinEventWindow::WndProc,
64 .hInstance = hSelf,
65 .lpszClassName = kClassNameHidden};
66 ATOM const atom = ::RegisterClassW(&wc);
67 if (!atom) {
68 // This is known to be possible when the atom table no longer has free
69 // entries, which unfortunately happens more often than one might expect.
70 // See bug 1571516.
71 auto volatile const err [[maybe_unused]] = ::GetLastError();
72 MOZ_CRASH("could not register broadcast-receiver window-class");
75 sHiddenWindow =
76 ::CreateWindowW((LPCWSTR)(uintptr_t)atom, L"WinEventWindow", 0, 0, 0, 0,
77 0, nullptr, nullptr, hSelf, nullptr);
79 if (!sHiddenWindow) {
80 MOZ_CRASH("could not create broadcast-receiver window");
83 sDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(sHiddenWindow);
85 // It should be harmless to leak this window until destruction -- but other
86 // parts of Gecko may expect all windows to be destroyed, so do that.
87 mozilla::RunOnShutdown([]() {
88 InputDeviceUtils::UnregisterNotification(sDeviceNotifyHandle);
90 sHiddenWindowShutdown = true;
91 ::DestroyWindow(sHiddenWindow);
92 sHiddenWindow = nullptr;
93 });
96 /* static */
97 HWND WinEventWindow::GetHwndForTestingOnly() {
98 return evtwin_details::sHiddenWindow;
101 // Callbacks for individual event types. These are private and internal
102 // implementation details of WinEventWindow.
103 namespace {
104 namespace evtwin_details {
106 static void NotifyThemeChanged(ThemeChangeKind aKind) {
107 LookAndFeel::NotifyChangedAllWindows(aKind);
110 static void OnSessionChange(WPARAM wParam, LPARAM lParam) {
111 if (wParam == WTS_SESSION_LOCK || wParam == WTS_SESSION_UNLOCK) {
112 DWORD currentSessionId;
113 BOOL const rv =
114 ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
115 if (!rv) {
116 // A process should always have the relevant access privileges for itself,
117 // but the above call could still fail if, e.g., someone's playing games
118 // with function imports. If so, just assert and/or skip out.
120 // Should this turn out to somehow be a real concern, we could do
121 // ```
122 // DWORD const currentSessionId =
123 // ::NtCurrentTeb()->ProcessEnvironmentBlock->SessionId;
124 // ```
125 // instead, which is actually documented (albeit abjured against).
126 MOZ_ASSERT(false, "::ProcessIdToSessionId() failed");
127 return;
130 OBS_LOG("WinEventWindow OnSessionChange(): wParam=%zu lParam=%" PRIdLPTR
131 " currentSessionId=%lu",
132 wParam, lParam, currentSessionId);
134 // Ignore lock/unlock messages for other sessions -- which Windows actually
135 // _does_ send in some scenarios; see review of Chromium changeset 1929489:
137 // https://chromium-review.googlesource.com/c/chromium/src/+/1929489
138 if (currentSessionId != (DWORD)lParam) {
139 return;
142 if (auto* wwot = WinWindowOcclusionTracker::Get()) {
143 wwot->OnSessionChange(wParam);
148 static void OnPowerBroadcast(WPARAM wParam, LPARAM lParam) {
149 if (wParam == PBT_POWERSETTINGCHANGE) {
150 POWERBROADCAST_SETTING* setting = (POWERBROADCAST_SETTING*)lParam;
151 MOZ_ASSERT(setting);
153 if (::IsEqualGUID(setting->PowerSetting, GUID_SESSION_DISPLAY_STATUS) &&
154 setting->DataLength == sizeof(DWORD)) {
155 MONITOR_DISPLAY_STATE state{};
156 errno_t const err =
157 ::memcpy_s(&state, sizeof(state), setting->Data, setting->DataLength);
158 if (err) {
159 MOZ_ASSERT(false, "bad data in POWERBROADCAST_SETTING in lParam");
160 return;
163 bool const displayOn = MONITOR_DISPLAY_STATE::PowerMonitorOff != state;
165 OBS_LOG("WinEventWindow OnPowerBroadcast(): displayOn=%d",
166 int(displayOn ? 1 : 0));
168 if (auto* wwot = WinWindowOcclusionTracker::Get()) {
169 wwot->OnDisplayStateChanged(displayOn);
175 static void OnSettingsChange(WPARAM wParam, LPARAM lParam) {
176 switch (wParam) {
177 case SPI_SETCLIENTAREAANIMATION:
178 case SPI_SETKEYBOARDDELAY:
179 case SPI_SETMOUSEVANISH:
180 case MOZ_SPI_SETCURSORSIZE:
181 // These need to update LookAndFeel cached values.
183 // They affect reduced motion settings / caret blink count / show pointer
184 // while typing / tooltip offset, so no need to invalidate style / layout.
185 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
186 return;
188 case SPI_SETFONTSMOOTHING:
189 case SPI_SETFONTSMOOTHINGTYPE:
190 gfxDWriteFont::UpdateSystemTextVars();
191 return;
193 case SPI_SETWORKAREA:
194 // NB: We also refresh screens on WM_DISPLAYCHANGE, but the rcWork
195 // values are sometimes wrong at that point. This message then arrives
196 // soon afterward, when we can get the right rcWork values.
197 ScreenHelperWin::RefreshScreens();
198 return;
200 default:
201 break;
204 if (lParam == 0) {
205 return;
207 nsDependentString lParamString{reinterpret_cast<const wchar_t*>(lParam)};
209 if (lParamString == u"ImmersiveColorSet"_ns) {
210 // This affects system colors (-moz-win-accentcolor), so gotta pass the
211 // style flag.
212 NotifyThemeChanged(widget::ThemeChangeKind::Style);
213 return;
216 // UserInteractionMode, ConvertibleSlateMode, and SystemDockMode may cause
217 // @media(pointer) queries to change, which layout needs to know about.
219 // The former two of those also imply that the current tablet-mode state needs
220 // to be updated.
222 if (lParamString == u"UserInteractionMode"_ns) {
223 // Documentation implies, and testing shows, that this is seen on Win10
224 // only.
225 Unused << NS_WARN_IF(mozilla::IsWin11OrLater());
226 WindowsUIUtils::UpdateInWin10TabletMode();
227 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
228 return;
231 if (lParamString == u"ConvertibleSlateMode"_ns) {
232 // Documentation implies, and testing shows, that this is not seen on Win10.
233 Unused << NS_WARN_IF(!mozilla::IsWin11OrLater());
234 WindowsUIUtils::UpdateInWin11TabletMode();
235 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
236 return;
239 if (lParamString == u"SystemDockMode"_ns) {
240 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
241 return;
245 static void OnDeviceChange(WPARAM wParam, LPARAM lParam) {
246 if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
247 DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
248 // Check dbch_devicetype explicitly since we will get other device types
249 // (e.g. DBT_DEVTYP_VOLUME) for some reason, even if we specify
250 // DBT_DEVTYP_DEVICEINTERFACE in the filter for RegisterDeviceNotification.
251 if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
252 // This can only change media queries (any-hover/any-pointer).
253 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
258 } // namespace evtwin_details
259 } // namespace
261 // static
262 LRESULT CALLBACK WinEventWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam,
263 LPARAM lParam) {
264 NativeEventLogger eventLogger("WinEventWindow", hwnd, msg, wParam, lParam);
266 switch (msg) {
267 case WM_WINDOWPOSCHANGING: {
268 // prevent rude external programs from making hidden window visible
269 LPWINDOWPOS info = (LPWINDOWPOS)lParam;
270 info->flags &= ~SWP_SHOWWINDOW;
271 } break;
273 case WM_WTSSESSION_CHANGE: {
274 evtwin_details::OnSessionChange(wParam, lParam);
275 } break;
277 case WM_POWERBROADCAST: {
278 evtwin_details::OnPowerBroadcast(wParam, lParam);
279 } break;
281 case WM_SYSCOLORCHANGE: {
282 // No need to invalidate layout for system color changes, but we need to
283 // invalidate style.
284 evtwin_details::NotifyThemeChanged(widget::ThemeChangeKind::Style);
285 } break;
287 case WM_THEMECHANGED: {
288 // We assume pretty much everything could've changed here.
289 evtwin_details::NotifyThemeChanged(
290 widget::ThemeChangeKind::StyleAndLayout);
291 } break;
293 case WM_FONTCHANGE: {
294 // update the global font list
295 gfxPlatform::GetPlatform()->UpdateFontList();
296 } break;
298 case WM_SETTINGCHANGE: {
299 evtwin_details::OnSettingsChange(wParam, lParam);
300 } break;
302 case WM_DEVICECHANGE: {
303 evtwin_details::OnDeviceChange(wParam, lParam);
304 } break;
306 case WM_DISPLAYCHANGE: {
307 ScreenHelperWin::RefreshScreens();
308 break;
312 LRESULT const ret = ::DefWindowProcW(hwnd, msg, wParam, lParam);
313 eventLogger.SetResult(ret, false);
314 return ret;
317 #undef OBS_LOG
319 } // namespace mozilla::widget