Bug 1874684 - Part 20: Tag stack classes with MOZ_STACK_CLASS. r=allstarschh
[gecko.git] / widget / windows / nsWindow.cpp
blobed986f7ee21fc1cf334363a14cf5cfec6e90c6c2
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
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 /*
8 * nsWindow - Native window management and event handling.
10 * nsWindow is organized into a set of major blocks and
11 * block subsections. The layout is as follows:
13 * Includes
14 * Variables
15 * nsIWidget impl.
16 * nsIWidget methods and utilities
17 * nsSwitchToUIThread impl.
18 * nsSwitchToUIThread methods and utilities
19 * Moz events
20 * Event initialization
21 * Event dispatching
22 * Native events
23 * Wndproc(s)
24 * Event processing
25 * OnEvent event handlers
26 * IME management and accessibility
27 * Transparency
28 * Popup hook handling
29 * Misc. utilities
30 * Child window impl.
32 * Search for "BLOCK:" to find major blocks.
33 * Search for "SECTION:" to find specific sections.
35 * Blocks should be split out into separate files if they
36 * become unmanageable.
38 * Notable related sources:
40 * nsWindowDefs.h - Definitions, macros, structs, enums
41 * and general setup.
42 * nsWindowDbg.h/.cpp - Debug related code and directives.
43 * nsWindowGfx.h/.cpp - Graphics and painting.
47 /**************************************************************
48 **************************************************************
50 ** BLOCK: Includes
52 ** Include headers.
54 **************************************************************
55 **************************************************************/
57 #include "gfx2DGlue.h"
58 #include "gfxEnv.h"
59 #include "gfxPlatform.h"
61 #include "mozilla/AppShutdown.h"
62 #include "mozilla/AutoRestore.h"
63 #include "mozilla/Likely.h"
64 #include "mozilla/PreXULSkeletonUI.h"
65 #include "mozilla/Logging.h"
66 #include "mozilla/MathAlgorithms.h"
67 #include "mozilla/MiscEvents.h"
68 #include "mozilla/MouseEvents.h"
69 #include "mozilla/PresShell.h"
70 #include "mozilla/ScopeExit.h"
71 #include "mozilla/StaticPrefs_browser.h"
72 #include "mozilla/SwipeTracker.h"
73 #include "mozilla/TouchEvents.h"
74 #include "mozilla/TimeStamp.h"
76 #include "mozilla/ipc/MessageChannel.h"
77 #include <algorithm>
78 #include <limits>
80 #include "mozilla/widget/WinMessages.h"
81 #include "nsLookAndFeel.h"
82 #include "nsWindow.h"
83 #include "nsWindowTaskbarConcealer.h"
84 #include "nsAppRunner.h"
86 #include <shellapi.h>
87 #include <windows.h>
88 #include <wtsapi32.h>
89 #include <process.h>
90 #include <commctrl.h>
91 #include <dbt.h>
92 #include <unknwn.h>
93 #include <psapi.h>
94 #include <rpc.h>
95 #include <propvarutil.h>
96 #include <propkey.h>
98 #include "mozilla/Logging.h"
99 #include "prtime.h"
100 #include "prenv.h"
102 #include "mozilla/WidgetTraceEvent.h"
103 #include "nsContentUtils.h"
104 #include "nsISupportsPrimitives.h"
105 #include "nsITheme.h"
106 #include "nsIObserverService.h"
107 #include "nsIScreenManager.h"
108 #include "imgIContainer.h"
109 #include "nsIFile.h"
110 #include "nsIRollupListener.h"
111 #include "nsIClipboard.h"
112 #include "WinMouseScrollHandler.h"
113 #include "nsFontMetrics.h"
114 #include "nsIFontEnumerator.h"
115 #include "nsFont.h"
116 #include "nsRect.h"
117 #include "nsThreadUtils.h"
118 #include "nsNativeCharsetUtils.h"
119 #include "nsGkAtoms.h"
120 #include "nsCRT.h"
121 #include "nsAppDirectoryServiceDefs.h"
122 #include "nsWidgetsCID.h"
123 #include "nsTHashtable.h"
124 #include "nsHashKeys.h"
125 #include "nsString.h"
126 #include "mozilla/Components.h"
127 #include "nsNativeThemeWin.h"
128 #include "nsXULPopupManager.h"
129 #include "nsWindowsDllInterceptor.h"
130 #include "nsLayoutUtils.h"
131 #include "nsView.h"
132 #include "nsWindowGfx.h"
133 #include "gfxWindowsPlatform.h"
134 #include "gfxDWriteFonts.h"
135 #include "nsPrintfCString.h"
136 #include "mozilla/Preferences.h"
137 #include "SystemTimeConverter.h"
138 #include "WinTaskbar.h"
139 #include "WidgetUtils.h"
140 #include "WinWindowOcclusionTracker.h"
141 #include "nsIWidgetListener.h"
142 #include "mozilla/dom/Document.h"
143 #include "mozilla/dom/MouseEventBinding.h"
144 #include "mozilla/dom/Touch.h"
145 #include "mozilla/gfx/2D.h"
146 #include "mozilla/gfx/GPUProcessManager.h"
147 #include "mozilla/intl/LocaleService.h"
148 #include "mozilla/layers/WebRenderLayerManager.h"
149 #include "mozilla/WindowsVersion.h"
150 #include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
151 #include "mozilla/TextEventDispatcherListener.h"
152 #include "mozilla/widget/nsAutoRollup.h"
153 #include "mozilla/widget/PlatformWidgetTypes.h"
154 #include "mozilla/widget/Screen.h"
155 #include "nsStyleConsts.h"
156 #include "nsBidiKeyboard.h"
157 #include "nsStyleConsts.h"
158 #include "gfxConfig.h"
159 #include "InProcessWinCompositorWidget.h"
160 #include "InputDeviceUtils.h"
161 #include "ScreenHelperWin.h"
162 #include "mozilla/StaticPrefs_apz.h"
163 #include "mozilla/StaticPrefs_dom.h"
164 #include "mozilla/StaticPrefs_gfx.h"
165 #include "mozilla/StaticPrefs_layout.h"
166 #include "mozilla/StaticPrefs_ui.h"
167 #include "mozilla/StaticPrefs_widget.h"
168 #include "nsNativeAppSupportWin.h"
169 #include "mozilla/browser/NimbusFeatures.h"
171 #include "nsIGfxInfo.h"
172 #include "nsUXThemeConstants.h"
173 #include "KeyboardLayout.h"
174 #include "nsNativeDragTarget.h"
175 #include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
176 #include <zmouse.h>
177 #include <richedit.h>
179 #ifdef ACCESSIBILITY
180 # ifdef DEBUG
181 # include "mozilla/a11y/Logging.h"
182 # endif
183 # include "mozilla/a11y/Compatibility.h"
184 # include "oleidl.h"
185 # include <uiautomation.h>
186 # include <winuser.h>
187 # include "nsAccessibilityService.h"
188 # include "mozilla/a11y/DocAccessible.h"
189 # include "mozilla/a11y/LazyInstantiator.h"
190 # include "mozilla/a11y/Platform.h"
191 # include "mozilla/StaticPrefs_accessibility.h"
192 # if !defined(WINABLEAPI)
193 # include <winable.h>
194 # endif // !defined(WINABLEAPI)
195 #endif
197 #include "WindowsUIUtils.h"
199 #include "nsWindowDefs.h"
201 #include "nsCrashOnException.h"
203 #include "nsIContent.h"
205 #include "mozilla/BackgroundHangMonitor.h"
206 #include "WinIMEHandler.h"
208 #include "npapi.h"
210 #include <d3d11.h>
212 // ERROR from wingdi.h (below) gets undefined by some code.
213 // #define ERROR 0
214 // #define RGN_ERROR ERROR
215 #define ERROR 0
217 #if !defined(SM_CONVERTIBLESLATEMODE)
218 # define SM_CONVERTIBLESLATEMODE 0x2003
219 #endif
221 #include "mozilla/gfx/DeviceManagerDx.h"
222 #include "mozilla/layers/APZInputBridge.h"
223 #include "mozilla/layers/InputAPZContext.h"
224 #include "mozilla/layers/KnowsCompositor.h"
225 #include "InputData.h"
227 #include "mozilla/TaskController.h"
228 #include "mozilla/Telemetry.h"
229 #include "mozilla/webrender/WebRenderAPI.h"
230 #include "mozilla/layers/IAPZCTreeManager.h"
232 #include "DirectManipulationOwner.h"
234 using namespace mozilla;
235 using namespace mozilla::dom;
236 using namespace mozilla::gfx;
237 using namespace mozilla::layers;
238 using namespace mozilla::widget;
239 using namespace mozilla::plugins;
241 /**************************************************************
242 **************************************************************
244 ** BLOCK: Variables
246 ** nsWindow Class static initializations and global variables.
248 **************************************************************
249 **************************************************************/
251 /**************************************************************
253 * SECTION: nsWindow statics
255 **************************************************************/
256 static const wchar_t kUser32LibName[] = L"user32.dll";
258 uint32_t nsWindow::sInstanceCount = 0;
259 bool nsWindow::sIsOleInitialized = false;
260 nsIWidget::Cursor nsWindow::sCurrentCursor = {};
261 nsWindow* nsWindow::sCurrentWindow = nullptr;
262 bool nsWindow::sJustGotDeactivate = false;
263 bool nsWindow::sJustGotActivate = false;
264 bool nsWindow::sIsInMouseCapture = false;
266 // Urgent-message reentrancy depth for the static `WindowProc` callback.
268 // Three unfortunate facts collide:
270 // 𝛼) Some messages must be processed promptly. If not, Windows will leave the
271 // receiving window in an intermediate, and potentially unusable, state until
272 // the WindowProc invocation that is handling it returns.
274 // 𝛽) Some messages have indefinitely long processing time. These are mostly
275 // messages which may cause us to enter a nested modal loop (via
276 // `SpinEventLoopUntil` or similar).
278 // 𝛾) Sometimes, messages skip the queue entirely. Our `WindowProc` may be
279 // reentrantly reinvoked from the kernel while we're blocking _on_ the
280 // kernel, even briefly, during processing of other messages. (Relevant
281 // search term: `KeUserModeCallback`.)
283 // The nightmare scenario, then, is that during processing of an 𝛼-message, we
284 // briefly become blocked (e.g., by calling `::SendMessageW()`), and the kernel
285 // takes that opportunity to use 𝛾 to hand us a 𝛽-message. (Concretely, see
286 // bug 1842170.)
288 // There is little we can do to prevent the first half of this scenario. 𝛼) and
289 // 𝛾) are effectively immutable facts of Windows, and we sometimes legitimately
290 // need to make blocking calls to process 𝛼-messages. (We may not even be aware
291 // that we're making such calls, if they're undocumented implementation details
292 // of another API.)
294 // In an ideal world, WindowProc would always return promptly (or at least in
295 // bounded time), and 𝛽-messages would not _per se_ exist; long-running modal
296 // states would instead be implemented in async fashion. In practice, that's far
297 // easier said than done -- replacing existing uses of `SpinEventLoopUntil` _et
298 // al._ with asynchronous mechanisms is a collection of mostly-unrelated cross-
299 // cutting architectural tasks, each of potentially unbounded scope. For now,
300 // and for the foreseeable future, we're stuck with them.
302 // We therefore simply punt. More specifically: if a known 𝛽-message jumps the
303 // queue to come in while we're in the middle of processing a known 𝛼-message,
304 // we:
305 // * properly queue the message for processing later;
306 // * respond to the 𝛽-message as though we actually had processed it; and
307 // * just hope that it can wait until we get around to it.
309 // The word "known" requires a bit of justification. There is no canonical set
310 // of 𝛼-messages, nor is the set of 𝛽-messages fixed (or even demarcable). We
311 // can't safely assume that all messages are 𝛼-messages, as that could cause
312 // 𝛽-messages to be arbitrarily and surprisingly delayed whenever any nested
313 // event loop is active. We also can't assume all messages are 𝛽-messages,
314 // since one 𝛼-message jumping the queue while processing another 𝛼-message is
315 // part of normal and required operation for windowed Windows applications.
317 // So we simply add messages to those sets as we identify them. (Or, preferably,
318 // rework the 𝛽-message's handling to make it no longer 𝛽. But see above.)
320 // ---
322 // The actual value of `sDepth` is the number of active invocations of
323 // `WindowProc` that are processing known 𝛼-messages.
324 size_t nsWindow::WndProcUrgentInvocation::sDepth = 0;
326 // Hook Data Members for Dropdowns. sProcessHook Tells the
327 // hook methods whether they should be processing the hook
328 // messages.
329 HHOOK nsWindow::sMsgFilterHook = nullptr;
330 HHOOK nsWindow::sCallProcHook = nullptr;
331 HHOOK nsWindow::sCallMouseHook = nullptr;
332 bool nsWindow::sProcessHook = false;
333 UINT nsWindow::sRollupMsgId = 0;
334 HWND nsWindow::sRollupMsgWnd = nullptr;
335 UINT nsWindow::sHookTimerId = 0;
337 // Used to prevent dispatching mouse events that do not originate from user
338 // input.
339 POINT nsWindow::sLastMouseMovePoint = {0};
341 bool nsWindow::sIsRestoringSession = false;
343 bool nsWindow::sTouchInjectInitialized = false;
344 InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
346 static SystemTimeConverter<DWORD>& TimeConverter() {
347 static SystemTimeConverter<DWORD> timeConverterSingleton;
348 return timeConverterSingleton;
351 // Global event hook for window cloaking. Never deregistered.
352 // - `Nothing` if not yet set.
353 // - `Some(nullptr)` if no attempt should be made to set it.
354 static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook = Nothing();
355 static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
357 namespace mozilla {
359 class CurrentWindowsTimeGetter {
360 public:
361 explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
363 DWORD GetCurrentTime() const { return ::GetTickCount(); }
365 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
366 DWORD currentTime = GetCurrentTime();
367 if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
368 // There's already one inflight with this timestamp. Don't
369 // send a duplicate.
370 return;
372 sBackwardsSkewStamp = Some(aNow);
373 sLastPostTime = currentTime;
374 static_assert(sizeof(WPARAM) >= sizeof(DWORD),
375 "Can't fit a DWORD in a WPARAM");
376 ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
379 static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
380 TimeStamp* aOutSkewStamp) {
381 if (aPostTime != sLastPostTime) {
382 // The SKEWFIX message is stale; we've sent a new one since then.
383 // Ignore this one.
384 return false;
386 MOZ_ASSERT(sBackwardsSkewStamp);
387 *aOutSkewStamp = sBackwardsSkewStamp.value();
388 sBackwardsSkewStamp = Nothing();
389 return true;
392 private:
393 static Maybe<TimeStamp> sBackwardsSkewStamp;
394 static DWORD sLastPostTime;
395 HWND mWnd;
398 Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
399 DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
401 } // namespace mozilla
403 /**************************************************************
405 * SECTION: globals variables
407 **************************************************************/
409 static const char* sScreenManagerContractID =
410 "@mozilla.org/gfx/screenmanager;1";
412 extern mozilla::LazyLogModule gWindowsLog;
414 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
416 // General purpose user32.dll hook object
417 static WindowsDllInterceptor sUser32Intercept;
419 // When the client area is extended out into the default window frame area,
420 // this is the minimum amount of space along the edge of resizable windows
421 // we will always display a resize cursor in, regardless of the underlying
422 // content.
423 static const int32_t kResizableBorderMinSize = 3;
425 // Getting this object from the window server can be expensive. Keep it
426 // around, also get it off the main thread. (See bug 1640852)
427 StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
428 static bool gInitializedVirtualDesktopManager = false;
430 // We should never really try to accelerate windows bigger than this. In some
431 // cases this might lead to no D3D9 acceleration where we could have had it
432 // but D3D9 does not reliably report when it supports bigger windows. 8192
433 // is as safe as we can get, we know at least D3D10 hardware always supports
434 // this, other hardware we expect to report correctly in D3D9.
435 #define MAX_ACCELERATED_DIMENSION 8192
437 // On window open (as well as after), Windows has an unfortunate habit of
438 // sending rather a lot of WM_NCHITTEST messages. Because we have to do point
439 // to DOM target conversions for these, we cache responses for a given
440 // coordinate this many milliseconds:
441 #define HITTEST_CACHE_LIFETIME_MS 50
443 #if defined(ACCESSIBILITY)
445 namespace mozilla {
448 * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
449 * injecting tiptsf.dll. The touchscreen process then posts registered messages
450 * to our main thread. The tiptsf hook picks up those registered messages and
451 * uses them as commands, some of which call into UIA, which then calls into
452 * MSAA, which then sends WM_GETOBJECT to us.
454 * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
455 * hook. Since thread-local hooks are called ahead of global hooks, we will
456 * see these registered messages before tiptsf does. At this point we can then
457 * raise a flag that blocks a11y before invoking CallNextHookEx which will then
458 * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
459 * flag by calling TIPMessageHandler::IsA11yBlocked().
461 * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
462 * function that also calls into UIA.
464 class TIPMessageHandler {
465 public:
466 ~TIPMessageHandler() {
467 if (mHook) {
468 ::UnhookWindowsHookEx(mHook);
472 static void Initialize() {
473 if (sInstance) {
474 return;
477 sInstance = new TIPMessageHandler();
478 ClearOnShutdown(&sInstance);
481 static bool IsA11yBlocked() {
482 if (!sInstance) {
483 return false;
486 return sInstance->mA11yBlockCount > 0;
489 private:
490 TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
491 MOZ_ASSERT(NS_IsMainThread());
493 // Registered messages used by tiptsf
494 mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
495 mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
496 mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
497 mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
498 mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
499 mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
500 mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
502 mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
503 ::GetCurrentThreadId());
504 MOZ_ASSERT(mHook);
506 if (!sSendMessageTimeoutWStub) {
507 sUser32Intercept.Init("user32.dll");
508 DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
509 sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
510 MOZ_ASSERT(hooked);
514 class MOZ_RAII A11yInstantiationBlocker {
515 public:
516 A11yInstantiationBlocker() {
517 if (!TIPMessageHandler::sInstance) {
518 return;
520 ++TIPMessageHandler::sInstance->mA11yBlockCount;
523 ~A11yInstantiationBlocker() {
524 if (!TIPMessageHandler::sInstance) {
525 return;
527 MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
528 --TIPMessageHandler::sInstance->mA11yBlockCount;
532 friend class A11yInstantiationBlocker;
534 static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
535 if (aCode < 0 || !sInstance) {
536 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
539 MSG* msg = reinterpret_cast<MSG*>(aLParam);
540 UINT& msgCode = msg->message;
542 for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
543 if (msgCode == sInstance->mMessages[i]) {
544 A11yInstantiationBlocker block;
545 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
549 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
552 static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
553 WPARAM aWParam, LPARAM aLParam,
554 UINT aFlags, UINT aTimeout,
555 PDWORD_PTR aMsgResult) {
556 // We don't want to handle this unless the message is a WM_GETOBJECT that we
557 // want to block, and the aHwnd is a nsWindow that belongs to the current
558 // (i.e., main) thread.
559 if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
560 static_cast<LONG>(aLParam) != OBJID_CLIENT || !::NS_IsMainThread() ||
561 !WinUtils::GetNSWindowPtr(aHwnd) || !IsA11yBlocked()) {
562 return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
563 aTimeout, aMsgResult);
566 // In this case we want to fake the result that would happen if we had
567 // decided not to handle WM_GETOBJECT in our WndProc. We hand the message
568 // off to DefWindowProc to accomplish this.
569 *aMsgResult = static_cast<DWORD_PTR>(
570 ::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
572 return static_cast<LRESULT>(TRUE);
575 static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
576 sSendMessageTimeoutWStub;
577 static StaticAutoPtr<TIPMessageHandler> sInstance;
579 HHOOK mHook;
580 UINT mMessages[7];
581 uint32_t mA11yBlockCount;
584 WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
585 TIPMessageHandler::sSendMessageTimeoutWStub;
586 StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
588 } // namespace mozilla
590 #endif // defined(ACCESSIBILITY)
592 namespace mozilla {
594 // This task will get the VirtualDesktopManager from the generic thread pool
595 // since doing this on the main thread on startup causes performance issues.
597 // See bug 1640852.
599 // This should be fine and should not require any locking, as when the main
600 // thread will access it, if it races with this function it will either find
601 // it to be null or to have a valid value.
602 class InitializeVirtualDesktopManagerTask : public Task {
603 public:
604 InitializeVirtualDesktopManagerTask()
605 : Task(Kind::OffMainThreadOnly, kDefaultPriorityValue) {}
607 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
608 bool GetName(nsACString& aName) override {
609 aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
610 return true;
612 #endif
614 virtual TaskResult Run() override {
615 RefPtr<IVirtualDesktopManager> desktopManager;
616 HRESULT hr = ::CoCreateInstance(
617 CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
618 __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
619 if (FAILED(hr)) {
620 return TaskResult::Complete;
623 gVirtualDesktopManager = desktopManager;
624 return TaskResult::Complete;
628 // Ground-truth query: does Windows claim the window is cloaked right now?
629 static bool IsCloaked(HWND hwnd) {
630 DWORD cloakedState;
631 HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
632 sizeof(cloakedState));
634 if (FAILED(hr)) {
635 MOZ_LOG(sCloakingLog, LogLevel::Warning,
636 ("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
637 return false;
640 return cloakedState != 0;
643 } // namespace mozilla
645 /**************************************************************
646 **************************************************************
648 ** BLOCK: nsIWidget impl.
650 ** nsIWidget interface implementation, broken down into
651 ** sections.
653 **************************************************************
654 **************************************************************/
656 /**************************************************************
658 * SECTION: nsWindow construction and destruction
660 **************************************************************/
662 nsWindow::nsWindow(bool aIsChildWindow)
663 : nsBaseWidget(BorderStyle::Default),
664 mBrush(::CreateSolidBrush(NSRGB_2_COLOREF(::GetSysColor(COLOR_BTNFACE)))),
665 mFrameState(std::in_place, this),
666 mIsChildWindow(aIsChildWindow),
667 mLastPaintEndTime(TimeStamp::Now()),
668 mCachedHitTestTime(TimeStamp::Now()),
669 mSizeConstraintsScale(GetDefaultScale().scale),
670 mDesktopId("DesktopIdMutex") {
671 MOZ_ASSERT(mWindowType == WindowType::Child);
673 if (!gInitializedVirtualDesktopManager) {
674 TaskController::Get()->AddTask(
675 MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
676 gInitializedVirtualDesktopManager = true;
679 // Global initialization
680 if (!sInstanceCount) {
681 // Global app registration id for Win7 and up. See
682 // WinTaskbar.cpp for details.
683 // MSIX packages explicitly do not support setting the appid from within
684 // the app, as it is set in the package manifest instead.
685 if (!WinUtils::HasPackageIdentity()) {
686 mozilla::widget::WinTaskbar::RegisterAppUserModelID();
688 if (!StaticPrefs::ui_key_layout_load_when_first_needed()) {
689 KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
691 #if defined(ACCESSIBILITY)
692 mozilla::TIPMessageHandler::Initialize();
693 #endif // defined(ACCESSIBILITY)
694 if (SUCCEEDED(::OleInitialize(nullptr))) {
695 sIsOleInitialized = true;
697 NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
698 MouseScrollHandler::Initialize();
699 // Init theme data
700 nsUXThemeData::UpdateNativeThemeInfo();
701 RedirectedKeyDownMessageManager::Forget();
702 } // !sInstanceCount
704 sInstanceCount++;
707 nsWindow::~nsWindow() {
708 mInDtor = true;
710 // If the widget was released without calling Destroy() then the native window
711 // still exists, and we need to destroy it. Destroy() will early-return if it
712 // was already called. In any case it is important to call it before
713 // destroying mPresentLock (cf. 1156182).
714 Destroy();
716 // Free app icon resources. This must happen after `OnDestroy` (see bug
717 // 708033).
718 if (mIconSmall) ::DestroyIcon(mIconSmall);
720 if (mIconBig) ::DestroyIcon(mIconBig);
722 sInstanceCount--;
724 // Global shutdown
725 if (sInstanceCount == 0) {
726 IMEHandler::Terminate();
727 sCurrentCursor = {};
728 if (sIsOleInitialized) {
729 ::OleFlushClipboard();
730 ::OleUninitialize();
731 sIsOleInitialized = false;
735 NS_IF_RELEASE(mNativeDragTarget);
738 /**************************************************************
740 * SECTION: nsIWidget::Create, nsIWidget::Destroy
742 * Creating and destroying windows for this widget.
744 **************************************************************/
746 // Allow Derived classes to modify the height that is passed
747 // when the window is created or resized.
748 int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
750 void nsWindow::SendAnAPZEvent(InputData& aEvent) {
751 LRESULT popupHandlingResult;
752 if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
753 // We need to consume the event after using it to roll up the popup(s).
754 return;
757 if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
758 // Give the swipe tracker a first pass at the event. If a new pan gesture
759 // has been started since the beginning of the swipe, the swipe tracker
760 // will know to ignore the event.
761 nsEventStatus status =
762 mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
763 if (status == nsEventStatus_eConsumeNoDefault) {
764 return;
768 APZEventResult result;
769 if (mAPZC) {
770 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
772 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
773 return;
776 MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
777 aEvent.mInputType == PINCHGESTURE_INPUT);
779 if (aEvent.mInputType == PANGESTURE_INPUT) {
780 PanGestureInput& panInput = aEvent.AsPanGestureInput();
781 WidgetWheelEvent event = panInput.ToWidgetEvent(this);
782 if (!mAPZC) {
783 if (MayStartSwipeForNonAPZ(panInput)) {
784 return;
786 } else {
787 event = MayStartSwipeForAPZ(panInput, result);
790 ProcessUntransformedAPZEvent(&event, result);
792 return;
795 PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
796 WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
797 ProcessUntransformedAPZEvent(&event, result);
800 void nsWindow::RecreateDirectManipulationIfNeeded() {
801 DestroyDirectManipulation();
803 if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
804 return;
807 if (!(StaticPrefs::apz_allow_zooming() ||
808 StaticPrefs::apz_windows_use_direct_manipulation()) ||
809 StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
810 return;
813 mDmOwner = MakeUnique<DirectManipulationOwner>(this);
815 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
816 GetHeight(mBounds.Height()));
817 mDmOwner->Init(bounds);
820 void nsWindow::ResizeDirectManipulationViewport() {
821 if (mDmOwner) {
822 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
823 GetHeight(mBounds.Height()));
824 mDmOwner->ResizeViewport(bounds);
828 void nsWindow::DestroyDirectManipulation() {
829 if (mDmOwner) {
830 mDmOwner->Destroy();
831 mDmOwner.reset();
835 // Create the proper widget
836 nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
837 const LayoutDeviceIntRect& aRect,
838 widget::InitData* aInitData) {
839 // Historical note: there was once some belief and/or intent that nsWindows
840 // could be created on arbitrary threads, and this may still be reflected in
841 // some comments.
842 MOZ_ASSERT(NS_IsMainThread());
844 widget::InitData defaultInitData;
845 if (!aInitData) aInitData = &defaultInitData;
847 nsIWidget* baseParent =
848 aInitData->mWindowType == WindowType::Dialog ||
849 aInitData->mWindowType == WindowType::TopLevel ||
850 aInitData->mWindowType == WindowType::Invisible
851 ? nullptr
852 : aParent;
854 mIsTopWidgetWindow = (nullptr == baseParent);
855 mBounds = aRect;
857 // Ensure that the toolkit is created.
858 nsToolkit::GetToolkit();
860 BaseCreate(baseParent, aInitData);
862 HWND parent;
863 if (aParent) { // has a nsIWidget parent
864 parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
865 mParent = aParent;
866 } else { // has a nsNative parent
867 parent = (HWND)aNativeParent;
868 mParent =
869 aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
872 mIsRTL = aInitData->mRTL;
873 mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
874 mAlwaysOnTop = aInitData->mAlwaysOnTop;
875 mIsAlert = aInitData->mIsAlert;
876 mResizable = aInitData->mResizable;
878 DWORD style = WindowStyle();
879 DWORD extendedStyle = WindowExStyle();
881 if (mWindowType == WindowType::Popup) {
882 if (!aParent) {
883 parent = nullptr;
885 } else if (mWindowType == WindowType::Invisible) {
886 // Make sure CreateWindowEx succeeds at creating a toplevel window
887 style &= ~0x40000000; // WS_CHILDWINDOW
888 } else {
889 // See if the caller wants to explictly set clip children and clip siblings
890 if (aInitData->mClipChildren) {
891 style |= WS_CLIPCHILDREN;
892 } else {
893 style &= ~WS_CLIPCHILDREN;
895 if (aInitData->mClipSiblings) {
896 style |= WS_CLIPSIBLINGS;
900 const wchar_t* className = ChooseWindowClass(mWindowType);
902 // Take specific actions when creating the first top-level window
903 static bool sFirstTopLevelWindowCreated = false;
904 if (aInitData->mWindowType == WindowType::TopLevel && !aParent &&
905 !sFirstTopLevelWindowCreated) {
906 sFirstTopLevelWindowCreated = true;
907 mWnd = ConsumePreXULSkeletonUIHandle();
908 auto skeletonUIError = GetPreXULSkeletonUIErrorReason();
909 if (skeletonUIError) {
910 nsAutoString errorString(
911 GetPreXULSkeletonUIErrorString(skeletonUIError.value()));
912 Telemetry::ScalarSet(
913 Telemetry::ScalarID::STARTUP_SKELETON_UI_DISABLED_REASON,
914 errorString);
916 if (mWnd) {
917 MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
918 "The skeleton UI window style should match the expected "
919 "style for the first window created");
920 MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
921 "The skeleton UI window extended style should match the "
922 "expected extended style for the first window created");
923 MOZ_ASSERT(
924 ::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
925 "The skeleton UI window should be created on the same thread as "
926 "other windows");
927 mIsShowingPreXULSkeletonUI = true;
929 // If we successfully consumed the pre-XUL skeleton UI, just update
930 // our internal state to match what is currently being displayed.
931 mIsVisible = true;
932 mIsCloaked = mozilla::IsCloaked(mWnd);
933 mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
935 // These match the margins set in browser-tabsintitlebar.js with
936 // default prefs on Windows. Bug 1673092 tracks lining this up with
937 // that more correctly instead of hard-coding it.
938 SetNonClientMargins(LayoutDeviceIntMargin(0, 2, 2, 2));
940 // Reset the WNDPROC for this window and its whole class, as we had
941 // to use our own WNDPROC when creating the the skeleton UI window.
942 ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
943 reinterpret_cast<LONG_PTR>(
944 WinUtils::NonClientDpiScalingDefWindowProcW));
945 ::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
946 reinterpret_cast<LONG_PTR>(
947 WinUtils::NonClientDpiScalingDefWindowProcW));
951 if (!mWnd) {
952 mWnd =
953 ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
954 aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
955 parent, nullptr, nsToolkit::mDllInstance, nullptr);
958 if (!mWnd) {
959 NS_WARNING("nsWindow CreateWindowEx failed.");
960 return NS_ERROR_FAILURE;
963 if (!sWinCloakEventHook) {
964 MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
966 // C++03 lambda approximation until P2173R1 is available (-std=c++2b)
967 struct StdcallLambda {
968 static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
969 DWORD event, HWND hwnd,
970 LONG idObject, LONG idChild,
971 DWORD idEventThread,
972 DWORD dwmsEventTime) {
973 const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
974 nsWindow::OnCloakEvent(hwnd, isCloaked);
978 const HWINEVENTHOOK hook = ::SetWinEventHook(
979 EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
980 &StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
981 ::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
982 sWinCloakEventHook = Some(hook);
984 if (!hook) {
985 const DWORD err = ::GetLastError();
986 MOZ_LOG(sCloakingLog, LogLevel::Error,
987 ("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
988 err));
992 if (aInitData->mIsPrivate) {
993 if (NimbusFeatures::GetBool("majorRelease2022"_ns,
994 "feltPrivacyWindowSeparation"_ns, true) &&
995 // Although permanent Private Browsing mode is indeed Private Browsing,
996 // we choose to make it look like regular Firefox in terms of the icon
997 // it uses (which also means we shouldn't use the Private Browsing
998 // AUMID).
999 !StaticPrefs::browser_privatebrowsing_autostart()) {
1000 RefPtr<IPropertyStore> pPropStore;
1001 if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
1002 getter_AddRefs(pPropStore)))) {
1003 PROPVARIANT pv;
1004 nsAutoString aumid;
1005 // make sure we're using the private browsing AUMID so that taskbar
1006 // grouping works properly
1007 Unused << NS_WARN_IF(
1008 !mozilla::widget::WinTaskbar::GenerateAppUserModelID(aumid, true));
1009 if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
1010 if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
1011 pPropStore->Commit();
1014 PropVariantClear(&pv);
1017 HICON icon = ::LoadIconW(::GetModuleHandleW(nullptr),
1018 MAKEINTRESOURCEW(IDI_PBMODE));
1019 SetBigIcon(icon);
1020 SetSmallIcon(icon);
1024 mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
1026 // If mDefaultScale is set before mWnd has been set, it will have the scale of
1027 // the primary monitor, rather than the monitor that the window is actually
1028 // on. For non-popup windows this gets corrected by the WM_DPICHANGED message
1029 // which resets mDefaultScale, but for popup windows we don't reset
1030 // mDefaultScale on that message. In order to ensure that popup windows
1031 // spawned on a non-primary monitor end up with the correct scale, we reset
1032 // mDefaultScale here so that it gets recomputed using the correct monitor now
1033 // that we have a mWnd.
1034 mDefaultScale = -1.0;
1036 if (mIsRTL) {
1037 DWORD dwAttribute = TRUE;
1038 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1039 sizeof dwAttribute);
1042 UpdateDarkModeToolbar();
1044 if (mOpeningAnimationSuppressed) {
1045 SuppressAnimation(true);
1048 if (mAlwaysOnTop) {
1049 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
1050 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1053 if (mWindowType != WindowType::Invisible &&
1054 MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
1055 // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
1057 // We create two zero-sized windows as descendants of the top-level window,
1058 // like so:
1060 // Top-level window (MozillaWindowClass)
1061 // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
1062 // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
1064 // We need to have the middle window, otherwise the Trackpoint driver
1065 // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
1066 // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
1067 // window hierarchy until they are handled by nsWindow::WindowProc.
1068 // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
1069 // but these do not propagate automatically, so we have the window
1070 // procedure pretend that they were dispatched to the top-level window
1071 // instead.
1073 // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
1074 // is given below so that it catches the Trackpoint driver's heuristics.
1075 HWND scrollContainerWnd = ::CreateWindowW(
1076 className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
1077 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1078 HWND scrollableWnd = ::CreateWindowW(
1079 className, L"FAKETRACKPOINTSCROLLABLE",
1080 WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
1081 scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1083 // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
1084 // WindowProcInternal can distinguish it from the top-level window
1085 // easily.
1086 ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
1088 // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
1089 // old window procedure in its "user data".
1090 WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
1091 scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
1092 ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
1095 // We will start receiving native events after associating with our native
1096 // window. We will also become the output of WinUtils::GetNSWindowPtr for that
1097 // window.
1098 if (!AssociateWithNativeWindow()) {
1099 return NS_ERROR_FAILURE;
1102 // Starting with Windows XP, a process always runs within a terminal services
1103 // session. In order to play nicely with RDP, fast user switching, and the
1104 // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
1105 // our HWND in order to receive this message.
1106 DebugOnly<BOOL> wtsRegistered =
1107 ::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
1108 NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
1110 mDefaultIMC.Init(this);
1111 IMEHandler::InitInputContext(this, mInputContext);
1113 static bool a11yPrimed = false;
1114 if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
1115 a11yPrimed = true;
1116 if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
1117 ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
1121 RecreateDirectManipulationIfNeeded();
1123 return NS_OK;
1126 void nsWindow::LocalesChanged() {
1127 bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
1128 if (mIsRTL != isRTL) {
1129 DWORD dwAttribute = isRTL;
1130 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1131 sizeof dwAttribute);
1132 mIsRTL = isRTL;
1136 // Close this nsWindow
1137 void nsWindow::Destroy() {
1138 // WM_DESTROY has already fired, avoid calling it twice
1139 if (mOnDestroyCalled) return;
1141 // Don't destroy windows that have file pickers open, we'll tear these down
1142 // later once the picker is closed.
1143 mDestroyCalled = true;
1144 if (mPickerDisplayCount) return;
1146 // During the destruction of all of our children, make sure we don't get
1147 // deleted.
1148 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1150 DestroyDirectManipulation();
1153 * On windows the LayerManagerOGL destructor wants the widget to be around for
1154 * cleanup. It also would like to have the HWND intact, so we nullptr it here.
1156 DestroyLayerManager();
1158 InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
1159 mDeviceNotifyHandle = nullptr;
1161 // The DestroyWindow function destroys the specified window. The function
1162 // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
1163 // and remove the keyboard focus from it. The function also destroys the
1164 // window's menu, flushes the thread message queue, destroys timers, removes
1165 // clipboard ownership, and breaks the clipboard viewer chain (if the window
1166 // is at the top of the viewer chain).
1168 // If the specified window is a parent or owner window, DestroyWindow
1169 // automatically destroys the associated child or owned windows when it
1170 // destroys the parent or owner window. The function first destroys child or
1171 // owned windows, and then it destroys the parent or owner window.
1172 VERIFY(::DestroyWindow(mWnd));
1174 // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
1175 // OnDestroy() didn't get called, call it now.
1176 if (false == mOnDestroyCalled) {
1177 MSGResult msgResult;
1178 mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
1179 OnDestroy();
1183 /**************************************************************
1185 * SECTION: Window class utilities
1187 * Utilities for calculating the proper window class name for
1188 * Create window.
1190 **************************************************************/
1192 /* static */
1193 const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
1194 UINT aExtraStyle, LPWSTR aIconID) {
1195 WNDCLASSW wc;
1196 if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
1197 // already registered
1198 return aClassName;
1201 wc.style = CS_DBLCLKS | aExtraStyle;
1202 wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
1203 wc.cbClsExtra = 0;
1204 wc.cbWndExtra = 0;
1205 wc.hInstance = nsToolkit::mDllInstance;
1206 wc.hIcon =
1207 aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
1208 wc.hCursor = nullptr;
1209 wc.hbrBackground = nullptr;
1210 wc.lpszMenuName = nullptr;
1211 wc.lpszClassName = aClassName;
1213 if (!::RegisterClassW(&wc)) {
1214 // For older versions of Win32 (i.e., not XP), the registration may
1215 // fail with aExtraStyle, so we have to re-register without it.
1216 wc.style = CS_DBLCLKS;
1217 ::RegisterClassW(&wc);
1219 return aClassName;
1222 static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
1224 /* static */
1225 const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) {
1226 const wchar_t* className = [aWindowType] {
1227 switch (aWindowType) {
1228 case WindowType::Invisible:
1229 return kClassNameHidden;
1230 case WindowType::Dialog:
1231 return kClassNameDialog;
1232 case WindowType::Popup:
1233 return kClassNameDropShadow;
1234 default:
1235 return GetMainWindowClass();
1237 }();
1238 return RegisterWindowClass(className, 0, gStockApplicationIcon);
1241 /**************************************************************
1243 * SECTION: Window styles utilities
1245 * Return the proper windows styles and extended styles.
1247 **************************************************************/
1249 const DWORD kTitlebarItemsWindowStyles =
1250 WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
1251 const DWORD kAllBorderStyles =
1252 kTitlebarItemsWindowStyles | WS_THICKFRAME | WS_DLGFRAME;
1254 static DWORD WindowStylesRemovedForBorderStyle(BorderStyle aStyle) {
1255 if (aStyle == BorderStyle::Default || aStyle == BorderStyle::All) {
1256 return 0;
1258 if (aStyle == BorderStyle::None) {
1259 return kAllBorderStyles;
1261 DWORD toRemove = 0;
1262 if (!(aStyle & BorderStyle::Border)) {
1263 toRemove |= WS_BORDER;
1265 if (!(aStyle & BorderStyle::Title)) {
1266 toRemove |= WS_DLGFRAME;
1268 if (!(aStyle & (BorderStyle::Menu | BorderStyle::Close))) {
1269 // Looks like getting rid of the system menu also does away with the close
1270 // box. So, we only get rid of the system menu and the close box if you
1271 // want neither. How does the Windows "Dialog" window class get just
1272 // closebox and no sysmenu? Who knows.
1273 toRemove |= WS_SYSMENU;
1275 if (!(aStyle & BorderStyle::ResizeH)) {
1276 toRemove |= WS_THICKFRAME;
1278 if (!(aStyle & BorderStyle::Minimize)) {
1279 toRemove |= WS_MINIMIZEBOX;
1281 if (!(aStyle & BorderStyle::Maximize)) {
1282 toRemove |= WS_MAXIMIZEBOX;
1284 return toRemove;
1287 // Return nsWindow styles
1288 DWORD nsWindow::WindowStyle() {
1289 DWORD style;
1290 switch (mWindowType) {
1291 case WindowType::Child:
1292 style = WS_OVERLAPPED;
1293 break;
1295 case WindowType::Dialog:
1296 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
1297 DS_MODALFRAME | WS_CLIPCHILDREN;
1298 if (mBorderStyle != BorderStyle::Default) {
1299 style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
1301 break;
1303 case WindowType::Popup:
1304 style = WS_OVERLAPPED | WS_POPUP;
1305 break;
1307 default:
1308 NS_ERROR("unknown border style");
1309 [[fallthrough]];
1311 case WindowType::TopLevel:
1312 case WindowType::Invisible:
1313 style = WS_OVERLAPPED | WS_CLIPCHILDREN | WS_DLGFRAME | WS_BORDER |
1314 WS_THICKFRAME | kTitlebarItemsWindowStyles;
1315 break;
1318 style &= ~WindowStylesRemovedForBorderStyle(mBorderStyle);
1320 if (mIsChildWindow) {
1321 style |= WS_CLIPCHILDREN;
1322 if (!(style & WS_POPUP)) {
1323 style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
1327 VERIFY_WINDOW_STYLE(style);
1328 return style;
1331 // Return nsWindow extended styles
1332 DWORD nsWindow::WindowExStyle() {
1333 switch (mWindowType) {
1334 case WindowType::Child:
1335 return 0;
1336 case WindowType::Popup: {
1337 DWORD extendedStyle = WS_EX_TOOLWINDOW;
1338 if (mPopupLevel == PopupLevel::Top) {
1339 extendedStyle |= WS_EX_TOPMOST;
1341 return extendedStyle;
1343 case WindowType::Dialog:
1344 case WindowType::TopLevel:
1345 case WindowType::Invisible:
1346 break;
1348 if (mIsAlert) {
1349 MOZ_ASSERT(mWindowType == WindowType::Dialog,
1350 "Expect alert windows to have type=dialog");
1351 return WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
1353 return WS_EX_WINDOWEDGE;
1356 /**************************************************************
1358 * SECTION: Native window association utilities
1360 * Used in Create and Destroy. A nsWindow can associate with its
1361 * underlying native window mWnd. Once a native window is
1362 * associated with a nsWindow, its native events will be handled
1363 * by the static member function nsWindow::WindowProc. Moreover,
1364 * the association will be registered in the WinUtils association
1365 * list, that is, calling WinUtils::GetNSWindowPtr on the native
1366 * window will return the associated nsWindow. This is used in
1367 * nsWindow::WindowProc to correctly dispatch native events to
1368 * the handler methods defined in nsWindow, even though it is a
1369 * static member function.
1371 * After dissociation, the native events of the native window will
1372 * no longer be handled by nsWindow::WindowProc, and will thus not
1373 * be dispatched to the nsWindow native event handler methods.
1374 * Moreover, the association will no longer be registered in the
1375 * WinUtils association list, so calling WinUtils::GetNSWindowPtr
1376 * on the native window will return nullptr.
1378 **************************************************************/
1380 bool nsWindow::AssociateWithNativeWindow() {
1381 if (!mWnd || !IsWindow(mWnd)) {
1382 NS_ERROR("Invalid window handle");
1383 return false;
1386 // Connect the this pointer to the native window handle.
1387 // This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
1388 // uses WinUtils::GetNSWindowPtr internally.
1389 WinUtils::SetNSWindowPtr(mWnd, this);
1391 ::SetLastError(ERROR_SUCCESS);
1392 const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1393 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
1394 if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
1395 NS_ERROR("Failure in SetWindowLongPtrW");
1396 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1397 return false;
1400 mPrevWndProc.emplace(prevWndProc);
1401 return true;
1404 void nsWindow::DissociateFromNativeWindow() {
1405 if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
1406 return;
1409 DebugOnly<WNDPROC> wndProcBeforeDissociate =
1410 reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1411 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
1412 NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
1413 "Unstacked an unexpected native window procedure");
1415 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1416 mPrevWndProc.reset();
1419 /**************************************************************
1421 * SECTION: nsIWidget::SetParent, nsIWidget::GetParent
1423 * Set or clear the parent widgets using window properties, and
1424 * handles calculating native parent handles.
1426 **************************************************************/
1428 // Get and set parent widgets
1429 void nsWindow::SetParent(nsIWidget* aNewParent) {
1430 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1431 nsIWidget* parent = GetParent();
1432 if (parent) {
1433 parent->RemoveChild(this);
1436 mParent = aNewParent;
1438 if (aNewParent) {
1439 ReparentNativeWidget(aNewParent);
1440 aNewParent->AddChild(this);
1441 return;
1443 if (mWnd) {
1444 // If we have no parent, SetParent should return the desktop.
1445 VERIFY(::SetParent(mWnd, nullptr));
1446 RecreateDirectManipulationIfNeeded();
1450 void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
1451 MOZ_ASSERT(aNewParent, "null widget");
1453 mParent = aNewParent;
1454 if (mWindowType == WindowType::Popup) {
1455 return;
1457 HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
1458 NS_ASSERTION(newParent, "Parent widget has a null native window handle");
1459 if (newParent && mWnd) {
1460 ::SetParent(mWnd, newParent);
1461 RecreateDirectManipulationIfNeeded();
1465 nsIWidget* nsWindow::GetParent(void) {
1466 if (mIsTopWidgetWindow) {
1467 return nullptr;
1469 if (mInDtor || mOnDestroyCalled) {
1470 return nullptr;
1472 return mParent;
1475 static int32_t RoundDown(double aDouble) {
1476 return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
1477 : static_cast<int32_t>(ceil(aDouble));
1480 float nsWindow::GetDPI() {
1481 float dpi = 96.0f;
1482 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
1483 if (screen) {
1484 screen->GetDpi(&dpi);
1486 return dpi;
1489 double nsWindow::GetDefaultScaleInternal() {
1490 if (mDefaultScale <= 0.0) {
1491 mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
1493 return mDefaultScale;
1496 int32_t nsWindow::LogToPhys(double aValue) {
1497 return WinUtils::LogToPhys(
1498 ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
1501 nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
1502 return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
1505 nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
1506 if (mIsTopWidgetWindow) {
1507 // Must use a flag instead of mWindowType to tell if the window is the
1508 // owned by the topmost widget, because a child window can be embedded
1509 // inside a HWND which is not associated with a nsIWidget.
1510 return nullptr;
1513 // If this widget has already been destroyed, pretend we have no parent.
1514 // This corresponds to code in Destroy which removes the destroyed
1515 // widget from its parent's child list.
1516 if (mInDtor || mOnDestroyCalled) return nullptr;
1518 // aIncludeOwner set to true implies walking the parent chain to retrieve the
1519 // root owner. aIncludeOwner set to false implies the search will stop at the
1520 // true parent (default).
1521 nsWindow* widget = nullptr;
1522 if (mWnd) {
1523 HWND parent = nullptr;
1524 if (aIncludeOwner)
1525 parent = ::GetParent(mWnd);
1526 else
1527 parent = ::GetAncestor(mWnd, GA_PARENT);
1529 if (parent) {
1530 widget = WinUtils::GetNSWindowPtr(parent);
1531 if (widget) {
1532 // If the widget is in the process of being destroyed then
1533 // do NOT return it
1534 if (widget->mInDtor) {
1535 widget = nullptr;
1541 return widget;
1544 /**************************************************************
1546 * SECTION: nsIWidget::Show
1548 * Hide or show this component.
1550 **************************************************************/
1552 void nsWindow::Show(bool bState) {
1553 if (bState && mIsShowingPreXULSkeletonUI) {
1554 // The first time we decide to actually show the window is when we decide
1555 // that we've taken over the window from the skeleton UI, and we should
1556 // no longer treat resizes / moves specially.
1557 mIsShowingPreXULSkeletonUI = false;
1558 #if defined(ACCESSIBILITY)
1559 // If our HWND has focus and the a11y engine hasn't started yet, fire a
1560 // focus win event. Windows already did this when the skeleton UI appeared,
1561 // but a11y wouldn't have been able to start at that point even if a client
1562 // responded. Firing this now gives clients the chance to respond with
1563 // WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
1564 // this if the a11y engine has already started because it has probably
1565 // already fired focus on a descendant.
1566 if (::GetFocus() == mWnd && !GetAccService()) {
1567 ::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
1569 #endif // defined(ACCESSIBILITY)
1572 if (mWindowType == WindowType::Popup) {
1573 MOZ_ASSERT(ChooseWindowClass(mWindowType) == kClassNameDropShadow);
1574 // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
1575 // some popup menus to become invisible.
1576 LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
1577 if (exStyle & WS_EX_LAYERED) {
1578 ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
1582 bool syncInvalidate = false;
1584 bool wasVisible = mIsVisible;
1585 // Set the status now so that anyone asking during ShowWindow or
1586 // SetWindowPos would get the correct answer.
1587 mIsVisible = bState;
1589 if (mWnd) {
1590 if (bState) {
1591 if (!wasVisible && mWindowType == WindowType::TopLevel) {
1592 // speed up the initial paint after show for
1593 // top level windows:
1594 syncInvalidate = true;
1596 // Set the cursor before showing the window to avoid the default wait
1597 // cursor.
1598 SetCursor(Cursor{eCursor_standard});
1600 switch (mFrameState->GetSizeMode()) {
1601 case nsSizeMode_Fullscreen:
1602 ::ShowWindow(mWnd, SW_SHOW);
1603 break;
1604 case nsSizeMode_Maximized:
1605 ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
1606 break;
1607 case nsSizeMode_Minimized:
1608 ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
1609 break;
1610 default:
1611 if (CanTakeFocus() && !mAlwaysOnTop) {
1612 ::ShowWindow(mWnd, SW_SHOWNORMAL);
1613 } else {
1614 ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
1615 // Don't flicker the window if we're restoring session
1616 if (!sIsRestoringSession) {
1617 Unused << GetAttention(2);
1620 break;
1622 } else {
1623 DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
1624 if (wasVisible) {
1625 flags |= SWP_NOZORDER;
1627 if (mAlwaysOnTop || mIsAlert) {
1628 flags |= SWP_NOACTIVATE;
1631 if (mWindowType == WindowType::Popup) {
1632 // ensure popups are the topmost of the TOPMOST
1633 // layer. Remember not to set the SWP_NOZORDER
1634 // flag as that might allow the taskbar to overlap
1635 // the popup.
1636 flags |= SWP_NOACTIVATE;
1637 HWND owner = ::GetWindow(mWnd, GW_OWNER);
1638 if (owner) {
1639 // PopupLevel::Top popups should be above all else. All other
1640 // types should be placed in front of their owner, without
1641 // changing the owner's z-level relative to other windows.
1642 if (mPopupLevel != PopupLevel::Top) {
1643 ::SetWindowPos(mWnd, owner, 0, 0, 0, 0, flags);
1644 ::SetWindowPos(owner, mWnd, 0, 0, 0, 0,
1645 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1646 } else {
1647 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1649 } else {
1650 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
1652 } else {
1653 if (mWindowType == WindowType::Dialog && !CanTakeFocus())
1654 flags |= SWP_NOACTIVATE;
1656 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1659 } else {
1660 // Clear contents to avoid ghosting of old content if we display
1661 // this window again.
1662 if (wasVisible && mTransparencyMode == TransparencyMode::Transparent) {
1663 if (mCompositorWidgetDelegate) {
1664 mCompositorWidgetDelegate->ClearTransparentWindow();
1667 if (mWindowType != WindowType::Dialog) {
1668 ::ShowWindow(mWnd, SW_HIDE);
1669 } else {
1670 ::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
1671 SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
1672 SWP_NOACTIVATE);
1677 if (!wasVisible && bState) {
1678 Invalidate();
1679 if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
1680 ::UpdateWindow(mWnd);
1684 if (mOpeningAnimationSuppressed) {
1685 SuppressAnimation(false);
1689 /**************************************************************
1691 * SECTION: nsIWidget::IsVisible
1693 * Returns the visibility state.
1695 **************************************************************/
1697 // Return true if the component is visible, false otherwise.
1699 // This does not take cloaking into account.
1700 bool nsWindow::IsVisible() const { return mIsVisible; }
1702 /**************************************************************
1704 * SECTION: Touch and APZ-related functions
1706 **************************************************************/
1708 void nsWindow::RegisterTouchWindow() {
1709 mTouchWindow = true;
1710 ::RegisterTouchWindow(mWnd, TWF_WANTPALM);
1711 ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
1714 BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
1715 nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
1716 if (win) {
1717 ::RegisterTouchWindow(aWnd, TWF_WANTPALM);
1719 return TRUE;
1722 void nsWindow::LockAspectRatio(bool aShouldLock) {
1723 if (aShouldLock) {
1724 mAspectRatio = (float)mBounds.Width() / (float)mBounds.Height();
1725 } else {
1726 mAspectRatio = 0.0;
1730 /**************************************************************
1732 * SECTION: nsIWidget::SetInputRegion
1734 * Sets whether the window should ignore mouse events.
1736 **************************************************************/
1737 void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
1738 mInputRegion = aInputRegion;
1741 /**************************************************************
1743 * SECTION: nsIWidget::Move, nsIWidget::Resize, nsIWidget::Size
1745 * Repositioning and sizing a window.
1747 **************************************************************/
1749 void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1750 SizeConstraints c = aConstraints;
1752 if (mWindowType != WindowType::Popup && mResizable) {
1753 c.mMinSize.width =
1754 std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
1755 c.mMinSize.height =
1756 std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
1759 if (mMaxTextureSize > 0) {
1760 // We can't make ThebesLayers bigger than this anyway.. no point it letting
1761 // a window grow bigger as we won't be able to draw content there in
1762 // general.
1763 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
1764 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
1767 mSizeConstraintsScale = GetDefaultScale().scale;
1769 nsBaseWidget::SetSizeConstraints(c);
1772 const SizeConstraints nsWindow::GetSizeConstraints() {
1773 double scale = GetDefaultScale().scale;
1774 if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
1775 return mSizeConstraints;
1777 scale /= mSizeConstraintsScale;
1778 SizeConstraints c = mSizeConstraints;
1779 if (c.mMinSize.width != NS_MAXSIZE) {
1780 c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
1782 if (c.mMinSize.height != NS_MAXSIZE) {
1783 c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
1785 if (c.mMaxSize.width != NS_MAXSIZE) {
1786 c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
1788 if (c.mMaxSize.height != NS_MAXSIZE) {
1789 c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
1791 return c;
1794 // Move this component
1795 void nsWindow::Move(double aX, double aY) {
1796 if (mWindowType == WindowType::TopLevel ||
1797 mWindowType == WindowType::Dialog) {
1798 SetSizeMode(nsSizeMode_Normal);
1801 // for top-level windows only, convert coordinates from desktop pixels
1802 // (the "parent" coordinate space) to the window's device pixel space
1803 double scale =
1804 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1805 int32_t x = NSToIntRound(aX * scale);
1806 int32_t y = NSToIntRound(aY * scale);
1808 // Check to see if window needs to be moved first
1809 // to avoid a costly call to SetWindowPos. This check
1810 // can not be moved to the calling code in nsView, because
1811 // some platforms do not position child windows correctly
1813 // Only perform this check for non-popup windows, since the positioning can
1814 // in fact change even when the x/y do not. We always need to perform the
1815 // check. See bug #97805 for details.
1816 if (mWindowType != WindowType::Popup && mBounds.IsEqualXY(x, y)) {
1817 // Nothing to do, since it is already positioned correctly.
1818 return;
1821 mBounds.MoveTo(x, y);
1823 if (mWnd) {
1824 #ifdef DEBUG
1825 // complain if a window is moved offscreen (legal, but potentially
1826 // worrisome)
1827 if (mIsTopWidgetWindow) { // only a problem for top-level windows
1828 // Make sure this window is actually on the screen before we move it
1829 // XXX: Needs multiple monitor support
1830 HDC dc = ::GetDC(mWnd);
1831 if (dc) {
1832 if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
1833 RECT workArea;
1834 ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
1835 // no annoying assertions. just mention the issue.
1836 if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) {
1837 MOZ_LOG(gWindowsLog, LogLevel::Info,
1838 ("window moved to offscreen position\n"));
1841 ::ReleaseDC(mWnd, dc);
1844 #endif
1846 // Normally, when the skeleton UI is disabled, we resize+move the window
1847 // before showing it in order to ensure that it restores to the correct
1848 // position when the user un-maximizes it. However, when we are using the
1849 // skeleton UI, this results in the skeleton UI window being moved around
1850 // undesirably before being locked back into the maximized position. To
1851 // avoid this, we simply set the placement to restore to via
1852 // SetWindowPlacement. It's a little bit more of a dance, though, since we
1853 // need to convert the workspace coords that SetWindowPlacement uses to the
1854 // screen space coordinates we normally use with SetWindowPos.
1855 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1856 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1857 VERIFY(::GetWindowPlacement(mWnd, &pl));
1859 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
1860 if (NS_WARN_IF(!monitor)) {
1861 return;
1863 MONITORINFO mi = {sizeof(MONITORINFO)};
1864 VERIFY(::GetMonitorInfo(monitor, &mi));
1866 int32_t deltaX =
1867 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
1868 int32_t deltaY =
1869 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
1870 pl.rcNormalPosition.left += deltaX;
1871 pl.rcNormalPosition.right += deltaX;
1872 pl.rcNormalPosition.top += deltaY;
1873 pl.rcNormalPosition.bottom += deltaY;
1874 VERIFY(::SetWindowPlacement(mWnd, &pl));
1875 } else {
1876 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
1877 double oldScale = mDefaultScale;
1878 mResizeState = IN_SIZEMOVE;
1879 VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
1880 mResizeState = NOT_RESIZING;
1881 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
1882 ChangedDPI();
1886 ResizeDirectManipulationViewport();
1890 // Resize this component
1891 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
1892 // for top-level windows only, convert coordinates from desktop pixels
1893 // (the "parent" coordinate space) to the window's device pixel space
1894 double scale =
1895 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1896 int32_t width = NSToIntRound(aWidth * scale);
1897 int32_t height = NSToIntRound(aHeight * scale);
1899 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
1900 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
1901 if (width < 0 || height < 0) {
1902 gfxCriticalNoteOnce << "Negative passed to Resize(" << width << ", "
1903 << height << ") repaint: " << aRepaint;
1906 ConstrainSize(&width, &height);
1908 // Avoid unnecessary resizing calls
1909 if (mBounds.IsEqualSize(width, height)) {
1910 if (aRepaint) {
1911 Invalidate();
1913 return;
1916 // Set cached value for lightweight and printing
1917 bool wasLocking = mAspectRatio != 0.0;
1918 mBounds.SizeTo(width, height);
1919 if (wasLocking) {
1920 LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
1923 if (mWnd) {
1924 // Refer to the comment above a similar check in nsWindow::Move
1925 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1926 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1927 VERIFY(::GetWindowPlacement(mWnd, &pl));
1928 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
1929 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
1930 mResizeState = RESIZING;
1931 VERIFY(::SetWindowPlacement(mWnd, &pl));
1932 mResizeState = NOT_RESIZING;
1933 } else {
1934 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
1936 if (!aRepaint) {
1937 flags |= SWP_NOREDRAW;
1940 double oldScale = mDefaultScale;
1941 mResizeState = RESIZING;
1942 VERIFY(
1943 ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags));
1945 mResizeState = NOT_RESIZING;
1946 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
1947 ChangedDPI();
1951 ResizeDirectManipulationViewport();
1954 if (aRepaint) Invalidate();
1957 // Resize this component
1958 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
1959 bool aRepaint) {
1960 // for top-level windows only, convert coordinates from desktop pixels
1961 // (the "parent" coordinate space) to the window's device pixel space
1962 double scale =
1963 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1964 int32_t x = NSToIntRound(aX * scale);
1965 int32_t y = NSToIntRound(aY * scale);
1966 int32_t width = NSToIntRound(aWidth * scale);
1967 int32_t height = NSToIntRound(aHeight * scale);
1969 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
1970 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
1971 if (width < 0 || height < 0) {
1972 gfxCriticalNoteOnce << "Negative passed to Resize(" << x << " ," << y
1973 << ", " << width << ", " << height
1974 << ") repaint: " << aRepaint;
1977 ConstrainSize(&width, &height);
1979 // Avoid unnecessary resizing calls
1980 if (mBounds.IsEqualRect(x, y, width, height)) {
1981 if (aRepaint) {
1982 Invalidate();
1984 return;
1987 // Set cached value for lightweight and printing
1988 mBounds.SetRect(x, y, width, height);
1990 if (mWnd) {
1991 // Refer to the comment above a similar check in nsWindow::Move
1992 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
1993 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
1994 VERIFY(::GetWindowPlacement(mWnd, &pl));
1996 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
1997 if (NS_WARN_IF(!monitor)) {
1998 return;
2000 MONITORINFO mi = {sizeof(MONITORINFO)};
2001 VERIFY(::GetMonitorInfo(monitor, &mi));
2003 int32_t deltaX =
2004 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
2005 int32_t deltaY =
2006 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
2007 pl.rcNormalPosition.left += deltaX;
2008 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
2009 pl.rcNormalPosition.top += deltaY;
2010 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
2011 VERIFY(::SetWindowPlacement(mWnd, &pl));
2012 } else {
2013 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
2014 if (!aRepaint) {
2015 flags |= SWP_NOREDRAW;
2018 double oldScale = mDefaultScale;
2019 mResizeState = RESIZING;
2020 VERIFY(
2021 ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags));
2022 mResizeState = NOT_RESIZING;
2023 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
2024 ChangedDPI();
2027 if (mTransitionWnd) {
2028 // If we have a fullscreen transition window, we need to make
2029 // it topmost again, otherwise the taskbar may be raised by
2030 // the system unexpectedly when we leave fullscreen state.
2031 ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
2032 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
2036 ResizeDirectManipulationViewport();
2039 if (aRepaint) Invalidate();
2042 /**************************************************************
2044 * SECTION: Window Z-order and state.
2046 * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode,
2047 * nsIWidget::ConstrainPosition
2049 * Z-order, positioning, restore, minimize, and maximize.
2051 **************************************************************/
2053 // Position the window behind the given window
2054 void nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
2055 nsIWidget* aWidget, bool aActivate) {
2056 HWND behind = HWND_TOP;
2057 if (aPlacement == eZPlacementBottom)
2058 behind = HWND_BOTTOM;
2059 else if (aPlacement == eZPlacementBelow && aWidget)
2060 behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
2061 UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE;
2062 if (!aActivate) flags |= SWP_NOACTIVATE;
2064 if (!CanTakeFocus() && behind == HWND_TOP) {
2065 // Can't place the window to top so place it behind the foreground window
2066 // (as long as it is not topmost)
2067 HWND wndAfter = ::GetForegroundWindow();
2068 if (!wndAfter)
2069 behind = HWND_BOTTOM;
2070 else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST))
2071 behind = wndAfter;
2072 flags |= SWP_NOACTIVATE;
2075 ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
2078 static UINT GetCurrentShowCmd(HWND aWnd) {
2079 WINDOWPLACEMENT pl;
2080 pl.length = sizeof(pl);
2081 ::GetWindowPlacement(aWnd, &pl);
2082 return pl.showCmd;
2085 // Maximize, minimize or restore the window.
2086 void nsWindow::SetSizeMode(nsSizeMode aMode) {
2087 // If we are still displaying a maximized pre-XUL skeleton UI, ignore the
2088 // noise of sizemode changes. Once we have "shown" the window for the first
2089 // time (called nsWindow::Show(true), even though the window is already
2090 // technically displayed), we will again accept sizemode changes.
2091 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2092 return;
2095 mFrameState->EnsureSizeMode(aMode);
2098 nsSizeMode nsWindow::SizeMode() { return mFrameState->GetSizeMode(); }
2100 void DoGetWorkspaceID(HWND aWnd, nsAString* aWorkspaceID) {
2101 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2102 if (!desktopManager || !aWnd) {
2103 return;
2106 GUID desktop;
2107 HRESULT hr = desktopManager->GetWindowDesktopId(aWnd, &desktop);
2108 if (FAILED(hr)) {
2109 return;
2112 RPC_WSTR workspaceIDStr = nullptr;
2113 if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
2114 aWorkspaceID->Assign((wchar_t*)workspaceIDStr);
2115 RpcStringFreeW(&workspaceIDStr);
2119 void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
2120 // If we have a value cached, use that, but also make sure it is
2121 // scheduled to be updated. If we don't yet have a value, get
2122 // one synchronously.
2123 auto desktop = mDesktopId.Lock();
2124 if (desktop->mID.IsEmpty()) {
2125 DoGetWorkspaceID(mWnd, &desktop->mID);
2126 desktop->mUpdateIsQueued = false;
2127 } else {
2128 AsyncUpdateWorkspaceID(*desktop);
2131 workspaceID = desktop->mID;
2134 void nsWindow::AsyncUpdateWorkspaceID(Desktop& aDesktop) {
2135 struct UpdateWorkspaceIdTask : public Task {
2136 explicit UpdateWorkspaceIdTask(nsWindow* aSelf)
2137 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
2138 mSelf(aSelf) {}
2140 TaskResult Run() override {
2141 auto desktop = mSelf->mDesktopId.Lock();
2142 if (desktop->mUpdateIsQueued) {
2143 DoGetWorkspaceID(mSelf->mWnd, &desktop->mID);
2144 desktop->mUpdateIsQueued = false;
2146 return TaskResult::Complete;
2149 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
2150 bool GetName(nsACString& aName) override {
2151 aName.AssignLiteral("UpdateWorkspaceIdTask");
2152 return true;
2154 #endif
2156 RefPtr<nsWindow> mSelf;
2159 if (aDesktop.mUpdateIsQueued) {
2160 return;
2163 aDesktop.mUpdateIsQueued = true;
2164 TaskController::Get()->AddTask(MakeAndAddRef<UpdateWorkspaceIdTask>(this));
2167 void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
2168 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2169 if (!desktopManager) {
2170 return;
2173 GUID desktop;
2174 const nsString flat = PromiseFlatString(workspaceID);
2175 RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
2176 if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
2177 if (SUCCEEDED(desktopManager->MoveWindowToDesktop(mWnd, desktop))) {
2178 auto desktop = mDesktopId.Lock();
2179 desktop->mID = workspaceID;
2184 void nsWindow::SuppressAnimation(bool aSuppress) {
2185 DWORD dwAttribute = aSuppress ? TRUE : FALSE;
2186 DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
2187 sizeof dwAttribute);
2190 // Constrain a potential move to fit onscreen
2191 // Position (aX, aY) is specified in Windows screen (logical) pixels,
2192 // except when using per-monitor DPI, in which case it's device pixels.
2193 void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
2194 if (!mIsTopWidgetWindow) // only a problem for top-level windows
2195 return;
2197 double dpiScale = GetDesktopToDeviceScale().scale;
2199 // We need to use the window size in the kind of pixels used for window-
2200 // manipulation APIs.
2201 int32_t logWidth =
2202 std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
2203 int32_t logHeight =
2204 std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
2206 /* get our playing field. use the current screen, or failing that
2207 for any reason, use device caps for the default screen. */
2208 RECT screenRect;
2210 nsCOMPtr<nsIScreenManager> screenmgr =
2211 do_GetService(sScreenManagerContractID);
2212 if (!screenmgr) {
2213 return;
2215 nsCOMPtr<nsIScreen> screen;
2216 int32_t left, top, width, height;
2218 screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
2219 getter_AddRefs(screen));
2220 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
2221 // For normalized windows, use the desktop work area.
2222 nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
2223 if (NS_FAILED(rv)) {
2224 return;
2226 } else {
2227 // For full screen windows, use the desktop.
2228 nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height);
2229 if (NS_FAILED(rv)) {
2230 return;
2233 screenRect.left = left;
2234 screenRect.right = left + width;
2235 screenRect.top = top;
2236 screenRect.bottom = top + height;
2238 if (aPoint.x < screenRect.left)
2239 aPoint.x = screenRect.left;
2240 else if (aPoint.x >= screenRect.right - logWidth)
2241 aPoint.x = screenRect.right - logWidth;
2243 if (aPoint.y < screenRect.top)
2244 aPoint.y = screenRect.top;
2245 else if (aPoint.y >= screenRect.bottom - logHeight)
2246 aPoint.y = screenRect.bottom - logHeight;
2249 /**************************************************************
2251 * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
2253 * Enabling and disabling the widget.
2255 **************************************************************/
2257 // Enable/disable this component
2258 void nsWindow::Enable(bool bState) {
2259 if (mWnd) {
2260 ::EnableWindow(mWnd, bState);
2264 // Return the current enable state
2265 bool nsWindow::IsEnabled() const {
2266 return !mWnd || (::IsWindowEnabled(mWnd) &&
2267 ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
2270 /**************************************************************
2272 * SECTION: nsIWidget::SetFocus
2274 * Give the focus to this widget.
2276 **************************************************************/
2278 void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2279 if (mWnd) {
2280 #ifdef WINSTATE_DEBUG_OUTPUT
2281 if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
2282 MOZ_LOG(gWindowsLog, LogLevel::Info,
2283 ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
2284 } else {
2285 MOZ_LOG(gWindowsLog, LogLevel::Info,
2286 ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
2288 #endif
2289 // Uniconify, if necessary
2290 HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
2291 if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
2292 ::ShowWindow(toplevelWnd, SW_RESTORE);
2294 ::SetFocus(mWnd);
2298 /**************************************************************
2300 * SECTION: Bounds
2302 * GetBounds, GetClientBounds, GetScreenBounds,
2303 * GetRestoredBounds, GetClientOffset, SetNonClientMargins
2305 * Bound calculations.
2307 **************************************************************/
2309 // Return the window's full dimensions in screen coordinates.
2310 // If the window has a parent, converts the origin to an offset
2311 // of the parent's screen origin.
2312 LayoutDeviceIntRect nsWindow::GetBounds() {
2313 if (!mWnd) {
2314 return mBounds;
2317 RECT r;
2318 VERIFY(::GetWindowRect(mWnd, &r));
2320 LayoutDeviceIntRect rect;
2322 // assign size
2323 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2325 // popup window bounds' are in screen coordinates, not relative to parent
2326 // window
2327 if (mWindowType == WindowType::Popup) {
2328 rect.MoveTo(r.left, r.top);
2329 return rect;
2332 // chrome on parent:
2333 // ___ 5,5 (chrome start)
2334 // | ____ 10,10 (client start)
2335 // | | ____ 20,20 (child start)
2336 // | | |
2337 // 20,20 - 5,5 = 15,15 (??)
2338 // minus GetClientOffset:
2339 // 15,15 - 5,5 = 10,10
2341 // no chrome on parent:
2342 // ______ 10,10 (win start)
2343 // | ____ 20,20 (child start)
2344 // | |
2345 // 20,20 - 10,10 = 10,10
2347 // walking the chain:
2348 // ___ 5,5 (chrome start)
2349 // | ___ 10,10 (client start)
2350 // | | ___ 20,20 (child start)
2351 // | | | __ 30,30 (child start)
2352 // | | | |
2353 // 30,30 - 20,20 = 10,10 (offset from second child to first)
2354 // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
2355 // minus GetClientOffset:
2356 // 25,25 - 5,5 = 20,20 (offset from second child to parent client)
2358 // convert coordinates if parent exists
2359 HWND parent = ::GetParent(mWnd);
2360 if (parent) {
2361 RECT pr;
2362 VERIFY(::GetWindowRect(parent, &pr));
2363 r.left -= pr.left;
2364 r.top -= pr.top;
2365 // adjust for chrome
2366 nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
2367 if (pWidget && pWidget->IsTopLevelWidget()) {
2368 LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
2369 r.left -= clientOffset.x;
2370 r.top -= clientOffset.y;
2373 rect.MoveTo(r.left, r.top);
2374 if (mCompositorSession &&
2375 !wr::WindowSizeSanityCheck(rect.width, rect.height)) {
2376 gfxCriticalNoteOnce << "Invalid size" << rect << " size mode "
2377 << mFrameState->GetSizeMode();
2380 return rect;
2383 // Get this component dimension
2384 LayoutDeviceIntRect nsWindow::GetClientBounds() {
2385 if (!mWnd) {
2386 return LayoutDeviceIntRect(0, 0, 0, 0);
2389 RECT r;
2390 if (!::GetClientRect(mWnd, &r)) {
2391 MOZ_ASSERT_UNREACHABLE("unexpected to be called");
2392 gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
2393 return mBounds;
2396 LayoutDeviceIntRect bounds = GetBounds();
2397 LayoutDeviceIntRect rect;
2398 rect.MoveTo(bounds.TopLeft() + GetClientOffset());
2399 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2400 return rect;
2403 // Like GetBounds, but don't offset by the parent
2404 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
2405 if (!mWnd) {
2406 return mBounds;
2409 RECT r;
2410 VERIFY(::GetWindowRect(mWnd, &r));
2412 return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2415 nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
2416 if (SizeMode() == nsSizeMode_Normal) {
2417 aRect = GetScreenBounds();
2418 return NS_OK;
2420 if (!mWnd) {
2421 return NS_ERROR_FAILURE;
2424 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2425 VERIFY(::GetWindowPlacement(mWnd, &pl));
2426 const RECT& r = pl.rcNormalPosition;
2428 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2429 if (!monitor) {
2430 return NS_ERROR_FAILURE;
2432 MONITORINFO mi = {sizeof(MONITORINFO)};
2433 VERIFY(::GetMonitorInfo(monitor, &mi));
2435 aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2436 aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
2437 mi.rcWork.top - mi.rcMonitor.top);
2438 return NS_OK;
2441 // Return the x,y offset of the client area from the origin of the window. If
2442 // the window is borderless returns (0,0).
2443 LayoutDeviceIntPoint nsWindow::GetClientOffset() {
2444 if (!mWnd) {
2445 return LayoutDeviceIntPoint(0, 0);
2448 RECT r1;
2449 GetWindowRect(mWnd, &r1);
2450 LayoutDeviceIntPoint pt = WidgetToScreenOffset();
2451 return LayoutDeviceIntPoint(pt.x - LayoutDeviceIntCoord(r1.left),
2452 pt.y - LayoutDeviceIntCoord(r1.top));
2455 void nsWindow::ResetLayout() {
2456 // This will trigger a frame changed event, triggering
2457 // nc calc size and a sizemode gecko event.
2458 SetWindowPos(mWnd, 0, 0, 0, 0, 0,
2459 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
2460 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
2462 // If hidden, just send the frame changed event for now.
2463 if (!mIsVisible) {
2464 return;
2467 // Send a gecko size event to trigger reflow.
2468 RECT clientRc = {0};
2469 GetClientRect(mWnd, &clientRc);
2470 OnResize(WinUtils::ToIntRect(clientRc).Size());
2472 // Invalidate and update
2473 Invalidate();
2476 // Internally track the caption status via a window property. Required
2477 // due to our internal handling of WM_NCACTIVATE when custom client
2478 // margins are set.
2479 static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty";
2480 typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
2481 static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr>
2482 sGetWindowInfoPtrStub;
2484 BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) {
2485 if (!sGetWindowInfoPtrStub) {
2486 NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!");
2487 return FALSE;
2489 int windowStatus =
2490 reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty));
2491 // No property set, return the default data.
2492 if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi);
2493 // Call GetWindowInfo and update dwWindowStatus with our
2494 // internally tracked value.
2495 BOOL result = sGetWindowInfoPtrStub(hWnd, pwi);
2496 if (result && pwi)
2497 pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION);
2498 return result;
2501 void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) {
2502 if (!mWnd) return;
2504 sUser32Intercept.Init("user32.dll");
2505 sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo",
2506 &GetWindowInfoHook);
2507 if (!sGetWindowInfoPtrStub) {
2508 return;
2511 // Update our internally tracked caption status
2512 SetPropW(mWnd, kManageWindowInfoProperty,
2513 reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
2516 #define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
2517 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
2519 void nsWindow::UpdateDarkModeToolbar() {
2520 PreferenceSheet::EnsureInitialized();
2521 BOOL dark = PreferenceSheet::ColorSchemeForChrome() == ColorScheme::Dark;
2522 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &dark,
2523 sizeof dark);
2524 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
2525 sizeof dark);
2528 LayoutDeviceIntMargin nsWindow::NormalWindowNonClientOffset() const {
2529 LayoutDeviceIntMargin nonClientOffset;
2531 // We're dealing with a "normal" window (not maximized, minimized, or
2532 // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset`
2533 // accordingly.
2535 // Setting `mNonClientOffset` to 0 has the effect of leaving the default
2536 // frame intact. Setting it to a value greater than 0 reduces the frame
2537 // size by that amount.
2539 if (mNonClientMargins.top > 0) {
2540 nonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
2541 } else if (mNonClientMargins.top == 0) {
2542 nonClientOffset.top = mCaptionHeight;
2543 } else {
2544 nonClientOffset.top = 0;
2547 if (mNonClientMargins.bottom > 0) {
2548 nonClientOffset.bottom =
2549 std::min(mVertResizeMargin, mNonClientMargins.bottom);
2550 } else if (mNonClientMargins.bottom == 0) {
2551 nonClientOffset.bottom = mVertResizeMargin;
2552 } else {
2553 nonClientOffset.bottom = 0;
2556 if (mNonClientMargins.left > 0) {
2557 nonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left);
2558 } else if (mNonClientMargins.left == 0) {
2559 nonClientOffset.left = mHorResizeMargin;
2560 } else {
2561 nonClientOffset.left = 0;
2564 if (mNonClientMargins.right > 0) {
2565 nonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right);
2566 } else if (mNonClientMargins.right == 0) {
2567 nonClientOffset.right = mHorResizeMargin;
2568 } else {
2569 nonClientOffset.right = 0;
2571 return nonClientOffset;
2575 * Called when the window layout changes: full screen mode transitions,
2576 * theme changes, and composition changes. Calculates the new non-client
2577 * margins and fires off a frame changed event, which triggers an nc calc
2578 * size windows event, kicking the changes in.
2580 * The offsets calculated here are based on the value of `mNonClientMargins`
2581 * which is specified in the "chromemargins" attribute of the window. For
2582 * each margin, the value specified has the following meaning:
2583 * -1 - leave the default frame in place
2584 * 0 - remove the frame
2585 * >0 - frame size equals min(0, (default frame size - margin value))
2587 * This function calculates and populates `mNonClientOffset`.
2588 * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
2589 * as (default frame size - offset). For example, if the left frame should
2590 * be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
2591 * will equal 1.
2593 * For maximized, fullscreen, and minimized windows, the values stored in
2594 * `mNonClientMargins` are ignored, and special processing takes place.
2596 * For non-glass windows, we only allow frames to be their default size
2597 * or removed entirely.
2599 bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) {
2600 if (!mCustomNonClient) {
2601 return false;
2604 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
2606 bool hasCaption =
2607 bool(mBorderStyle & (BorderStyle::All | BorderStyle::Title |
2608 BorderStyle::Menu | BorderStyle::Default));
2610 float dpi = GetDPI();
2612 // mCaptionHeight is the default size of the NC area at
2613 // the top of the window. If the window has a caption,
2614 // the size is calculated as the sum of:
2615 // SM_CYFRAME - The thickness of the sizing border
2616 // around a resizable window
2617 // SM_CXPADDEDBORDER - The amount of border padding
2618 // for captioned windows
2619 // SM_CYCAPTION - The height of the caption area
2621 // If the window does not have a caption, mCaptionHeight will be equal to
2622 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2623 mCaptionHeight =
2624 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2625 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +
2626 WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2627 : 0);
2628 if (!mUseResizeMarginOverrides) {
2629 // mHorResizeMargin is the size of the default NC areas on the
2630 // left and right sides of our window. It is calculated as
2631 // the sum of:
2632 // SM_CXFRAME - The thickness of the sizing border
2633 // SM_CXPADDEDBORDER - The amount of border padding
2634 // for captioned windows
2636 // If the window does not have a caption, mHorResizeMargin will be equal to
2637 // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
2638 mHorResizeMargin =
2639 WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
2640 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2641 : 0);
2643 // mVertResizeMargin is the size of the default NC area at the
2644 // bottom of the window. It is calculated as the sum of:
2645 // SM_CYFRAME - The thickness of the sizing border
2646 // SM_CXPADDEDBORDER - The amount of border padding
2647 // for captioned windows.
2649 // If the window does not have a caption, mVertResizeMargin will be equal to
2650 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2651 mVertResizeMargin =
2652 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2653 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2654 : 0);
2657 if (sizeMode == nsSizeMode_Minimized) {
2658 // Use default frame size for minimized windows
2659 mNonClientOffset.top = 0;
2660 mNonClientOffset.left = 0;
2661 mNonClientOffset.right = 0;
2662 mNonClientOffset.bottom = 0;
2663 } else if (sizeMode == nsSizeMode_Fullscreen) {
2664 // Remove the default frame from the top of our fullscreen window. This
2665 // makes the whole caption part of our client area, allowing us to draw
2666 // in the whole caption area. Additionally remove the default frame from
2667 // the left, right, and bottom.
2668 mNonClientOffset.top = mCaptionHeight;
2669 mNonClientOffset.bottom = mVertResizeMargin;
2670 mNonClientOffset.left = mHorResizeMargin;
2671 mNonClientOffset.right = mHorResizeMargin;
2672 } else if (sizeMode == nsSizeMode_Maximized) {
2673 // We make the entire frame part of the client area. We leave the default
2674 // frame sizes for left, right and bottom since Windows will automagically
2675 // position the edges "offscreen" for maximized windows.
2676 int verticalResize =
2677 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2678 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2679 : 0);
2681 mNonClientOffset.top = mCaptionHeight - verticalResize;
2682 mNonClientOffset.bottom = 0;
2683 mNonClientOffset.left = 0;
2684 mNonClientOffset.right = 0;
2686 mozilla::Maybe<UINT> maybeEdge = GetHiddenTaskbarEdge();
2687 if (maybeEdge) {
2688 auto edge = maybeEdge.value();
2689 if (ABE_LEFT == edge) {
2690 mNonClientOffset.left -= kHiddenTaskbarSize;
2691 } else if (ABE_RIGHT == edge) {
2692 mNonClientOffset.right -= kHiddenTaskbarSize;
2693 } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
2694 mNonClientOffset.bottom -= kHiddenTaskbarSize;
2697 // When we are drawing the non-client region, we need
2698 // to clear the portion of the NC region that is exposed by the
2699 // hidden taskbar. As above, we clear the bottom of the NC region
2700 // when the taskbar is at the top of the screen.
2701 UINT clearEdge = (edge == ABE_TOP) ? ABE_BOTTOM : edge;
2702 mClearNCEdge = Some(clearEdge);
2704 } else {
2705 mNonClientOffset = NormalWindowNonClientOffset();
2708 if (aReflowWindow) {
2709 // Force a reflow of content based on the new client
2710 // dimensions.
2711 ResetLayout();
2714 return true;
2717 nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) {
2718 if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None) {
2719 return NS_ERROR_INVALID_ARG;
2722 if (mHideChrome) {
2723 mFutureMarginsOnceChromeShows = margins;
2724 mFutureMarginsToUse = true;
2725 return NS_OK;
2727 mFutureMarginsToUse = false;
2729 // Request for a reset
2730 if (margins.top == -1 && margins.left == -1 && margins.right == -1 &&
2731 margins.bottom == -1) {
2732 mCustomNonClient = false;
2733 mNonClientMargins = margins;
2734 // Force a reflow of content based on the new client
2735 // dimensions.
2736 ResetLayout();
2738 int windowStatus =
2739 reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty));
2740 if (windowStatus) {
2741 ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0);
2744 return NS_OK;
2747 if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 ||
2748 margins.right < -1) {
2749 return NS_ERROR_INVALID_ARG;
2752 mNonClientMargins = margins;
2753 mCustomNonClient = true;
2754 if (!UpdateNonClientMargins()) {
2755 NS_WARNING("UpdateNonClientMargins failed!");
2756 return NS_OK;
2759 return NS_OK;
2762 void nsWindow::SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) {
2763 mUseResizeMarginOverrides = true;
2764 mHorResizeMargin = aResizeMargin;
2765 mVertResizeMargin = aResizeMargin;
2766 UpdateNonClientMargins();
2769 void nsWindow::InvalidateNonClientRegion() {
2770 // +-+-----------------------+-+
2771 // | | app non-client chrome | |
2772 // | +-----------------------+ |
2773 // | | app client chrome | | }
2774 // | +-----------------------+ | }
2775 // | | app content | | } area we don't want to invalidate
2776 // | +-----------------------+ | }
2777 // | | app client chrome | | }
2778 // | +-----------------------+ |
2779 // +---------------------------+ <
2780 // ^ ^ windows non-client chrome
2781 // client area = app *
2782 RECT rect;
2783 GetWindowRect(mWnd, &rect);
2784 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2785 HRGN winRgn = CreateRectRgnIndirect(&rect);
2787 // Subtract app client chrome and app content leaving
2788 // windows non-client chrome and app non-client chrome
2789 // in winRgn.
2790 GetWindowRect(mWnd, &rect);
2791 rect.top += mCaptionHeight;
2792 rect.right -= mHorResizeMargin;
2793 rect.bottom -= mVertResizeMargin;
2794 rect.left += mHorResizeMargin;
2795 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2796 HRGN clientRgn = CreateRectRgnIndirect(&rect);
2797 CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF);
2798 DeleteObject(clientRgn);
2800 // triggers ncpaint and paint events for the two areas
2801 RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE);
2802 DeleteObject(winRgn);
2805 /**************************************************************
2807 * SECTION: nsIWidget::SetBackgroundColor
2809 * Sets the window background paint color.
2811 **************************************************************/
2813 void nsWindow::SetBackgroundColor(const nscolor& aColor) {
2814 if (mBrush) ::DeleteObject(mBrush);
2816 mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor));
2817 if (mWnd != nullptr) {
2818 ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush);
2822 /**************************************************************
2824 * SECTION: nsIWidget::SetCursor
2826 * SetCursor and related utilities for manging cursor state.
2828 **************************************************************/
2830 // Set this component cursor
2831 static HCURSOR CursorFor(nsCursor aCursor) {
2832 switch (aCursor) {
2833 case eCursor_select:
2834 return ::LoadCursor(nullptr, IDC_IBEAM);
2835 case eCursor_wait:
2836 return ::LoadCursor(nullptr, IDC_WAIT);
2837 case eCursor_hyperlink:
2838 return ::LoadCursor(nullptr, IDC_HAND);
2839 case eCursor_standard:
2840 case eCursor_context_menu: // XXX See bug 258960.
2841 return ::LoadCursor(nullptr, IDC_ARROW);
2843 case eCursor_n_resize:
2844 case eCursor_s_resize:
2845 return ::LoadCursor(nullptr, IDC_SIZENS);
2847 case eCursor_w_resize:
2848 case eCursor_e_resize:
2849 return ::LoadCursor(nullptr, IDC_SIZEWE);
2851 case eCursor_nw_resize:
2852 case eCursor_se_resize:
2853 return ::LoadCursor(nullptr, IDC_SIZENWSE);
2855 case eCursor_ne_resize:
2856 case eCursor_sw_resize:
2857 return ::LoadCursor(nullptr, IDC_SIZENESW);
2859 case eCursor_crosshair:
2860 return ::LoadCursor(nullptr, IDC_CROSS);
2862 case eCursor_move:
2863 return ::LoadCursor(nullptr, IDC_SIZEALL);
2865 case eCursor_help:
2866 return ::LoadCursor(nullptr, IDC_HELP);
2868 case eCursor_copy: // CSS3
2869 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
2871 case eCursor_alias:
2872 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
2874 case eCursor_cell:
2875 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
2876 case eCursor_grab:
2877 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
2879 case eCursor_grabbing:
2880 return ::LoadCursor(nsToolkit::mDllInstance,
2881 MAKEINTRESOURCE(IDC_GRABBING));
2883 case eCursor_spinning:
2884 return ::LoadCursor(nullptr, IDC_APPSTARTING);
2886 case eCursor_zoom_in:
2887 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
2889 case eCursor_zoom_out:
2890 return ::LoadCursor(nsToolkit::mDllInstance,
2891 MAKEINTRESOURCE(IDC_ZOOMOUT));
2893 case eCursor_not_allowed:
2894 case eCursor_no_drop:
2895 return ::LoadCursor(nullptr, IDC_NO);
2897 case eCursor_col_resize:
2898 return ::LoadCursor(nsToolkit::mDllInstance,
2899 MAKEINTRESOURCE(IDC_COLRESIZE));
2901 case eCursor_row_resize:
2902 return ::LoadCursor(nsToolkit::mDllInstance,
2903 MAKEINTRESOURCE(IDC_ROWRESIZE));
2905 case eCursor_vertical_text:
2906 return ::LoadCursor(nsToolkit::mDllInstance,
2907 MAKEINTRESOURCE(IDC_VERTICALTEXT));
2909 case eCursor_all_scroll:
2910 // XXX not 100% appropriate perhaps
2911 return ::LoadCursor(nullptr, IDC_SIZEALL);
2913 case eCursor_nesw_resize:
2914 return ::LoadCursor(nullptr, IDC_SIZENESW);
2916 case eCursor_nwse_resize:
2917 return ::LoadCursor(nullptr, IDC_SIZENWSE);
2919 case eCursor_ns_resize:
2920 return ::LoadCursor(nullptr, IDC_SIZENS);
2922 case eCursor_ew_resize:
2923 return ::LoadCursor(nullptr, IDC_SIZEWE);
2925 case eCursor_none:
2926 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
2928 default:
2929 NS_ERROR("Invalid cursor type");
2930 return nullptr;
2934 static HCURSOR CursorForImage(const nsIWidget::Cursor& aCursor,
2935 CSSToLayoutDeviceScale aScale) {
2936 if (!aCursor.IsCustom()) {
2937 return nullptr;
2940 nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
2942 // Reject cursors greater than 128 pixels in either direction, to prevent
2943 // spoofing.
2944 // XXX ideally we should rescale. Also, we could modify the API to
2945 // allow trusted content to set larger cursors.
2946 if (size.width > 128 || size.height > 128) {
2947 return nullptr;
2950 LayoutDeviceIntSize layoutSize =
2951 RoundedToInt(CSSIntSize(size.width, size.height) * aScale);
2952 LayoutDeviceIntPoint hotspot =
2953 RoundedToInt(CSSIntPoint(aCursor.mHotspotX, aCursor.mHotspotY) * aScale);
2954 HCURSOR cursor;
2955 nsresult rv = nsWindowGfx::CreateIcon(aCursor.mContainer, true, hotspot,
2956 layoutSize, &cursor);
2957 if (NS_FAILED(rv)) {
2958 return nullptr;
2961 return cursor;
2964 void nsWindow::SetCursor(const Cursor& aCursor) {
2965 static HCURSOR sCurrentHCursor = nullptr;
2966 static bool sCurrentHCursorIsCustom = false;
2968 mCursor = aCursor;
2970 if (sCurrentCursor == aCursor && sCurrentHCursor && !mUpdateCursor) {
2971 // Cursors in windows are global, so even if our mUpdateCursor flag is
2972 // false we always need to make sure the Windows cursor is up-to-date,
2973 // since stuff like native drag and drop / resizers code can mutate it
2974 // outside of this method.
2975 ::SetCursor(sCurrentHCursor);
2976 return;
2979 mUpdateCursor = false;
2981 if (sCurrentHCursorIsCustom) {
2982 ::DestroyIcon(sCurrentHCursor);
2984 sCurrentHCursor = nullptr;
2985 sCurrentHCursorIsCustom = false;
2986 sCurrentCursor = aCursor;
2988 HCURSOR cursor = nullptr;
2989 if (mCustomCursorAllowed) {
2990 cursor = CursorForImage(aCursor, GetDefaultScale());
2992 bool custom = false;
2993 if (cursor) {
2994 custom = true;
2995 } else {
2996 cursor = CursorFor(aCursor.mDefaultCursor);
2999 if (!cursor) {
3000 return;
3003 sCurrentHCursor = cursor;
3004 sCurrentHCursorIsCustom = custom;
3005 ::SetCursor(cursor);
3008 /**************************************************************
3010 * SECTION: nsIWidget::Get/SetTransparencyMode
3012 * Manage the transparency mode of the window containing this
3013 * widget. Only works for popup and dialog windows when the
3014 * Desktop Window Manager compositor is not enabled.
3016 **************************************************************/
3018 TransparencyMode nsWindow::GetTransparencyMode() {
3019 return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
3022 void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
3023 nsWindow* window = GetTopLevelWindow(true);
3024 MOZ_ASSERT(window);
3026 if (!window || window->DestroyCalled()) {
3027 return;
3030 window->SetWindowTranslucencyInner(aMode);
3033 /**************************************************************
3035 * SECTION: nsIWidget::UpdateWindowDraggingRegion
3037 * For setting the draggable titlebar region from CSS
3038 * with -moz-window-dragging: drag.
3040 **************************************************************/
3042 void nsWindow::UpdateWindowDraggingRegion(
3043 const LayoutDeviceIntRegion& aRegion) {
3044 mDraggableRegion = aRegion;
3047 /**************************************************************
3049 * SECTION: nsIWidget::HideWindowChrome
3051 * Show or hide window chrome.
3053 **************************************************************/
3055 void nsWindow::HideWindowChrome(bool aShouldHide) {
3056 HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
3057 if (!WinUtils::GetNSWindowPtr(hwnd)) {
3058 NS_WARNING("Trying to hide window decorations in an embedded context");
3059 return;
3062 if (mHideChrome == aShouldHide) return;
3064 // Data manipulation: styles + ex-styles, and bitmasking operations thereupon.
3065 struct Styles {
3066 LONG_PTR style, ex;
3067 constexpr Styles operator|(Styles const& that) const {
3068 return Styles{.style = style | that.style, .ex = ex | that.ex};
3070 constexpr Styles operator&(Styles const& that) const {
3071 return Styles{.style = style & that.style, .ex = ex & that.ex};
3073 constexpr Styles operator~() const {
3074 return Styles{.style = ~style, .ex = ~ex};
3077 // Compute a style-set which matches `zero` where the bits of `this` are 0
3078 // and `one` where the bits of `this` are 1.
3079 constexpr Styles merge(Styles zero, Styles one) const {
3080 Styles const& mask = *this;
3081 return (~mask & zero) | (mask & one);
3084 // The dual of `merge`, above: returns a pair [zero, one] satisfying
3085 // `a.merge(a.split(b)...) == b`. (Or its equivalent in valid C++.)
3086 constexpr std::tuple<Styles, Styles> split(Styles data) const {
3087 Styles const& mask = *this;
3088 return {~mask & data, mask & data};
3092 // Get styles from an HWND.
3093 constexpr auto const GetStyles = [](HWND hwnd) {
3094 return Styles{.style = ::GetWindowLongPtrW(hwnd, GWL_STYLE),
3095 .ex = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE)};
3097 constexpr auto const SetStyles = [](HWND hwnd, Styles styles) {
3098 VERIFY_WINDOW_STYLE(styles.style);
3099 ::SetWindowLongPtrW(hwnd, GWL_STYLE, styles.style);
3100 ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, styles.ex);
3103 // Get styles from *this.
3104 auto const GetCachedStyles = [&]() {
3105 return mOldStyles.map([](auto const& m) {
3106 return Styles{.style = m.style, .ex = m.exStyle};
3109 auto const SetCachedStyles = [&](Styles styles) {
3110 using WStyles = nsWindow::WindowStyles;
3111 mOldStyles = Some(WStyles{.style = styles.style, .exStyle = styles.ex});
3114 // The mask describing the "chrome" which this function is supposed to remove
3115 // (or restore, as the case may be). Other style-flags will be left untouched.
3116 constexpr static const Styles kChromeMask{
3117 .style = WS_CAPTION | WS_THICKFRAME,
3118 .ex = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE |
3119 WS_EX_STATICEDGE};
3121 // The desired style-flagset for fullscreen windows. (This happens to be all
3122 // zeroes, but we don't need to rely on that.)
3123 constexpr static const Styles kFullscreenChrome{.style = 0, .ex = 0};
3125 auto const [chromeless, currentChrome] = kChromeMask.split(GetStyles(hwnd));
3126 Styles newChrome{}, oldChrome{};
3128 mHideChrome = aShouldHide;
3129 if (aShouldHide) {
3130 newChrome = kFullscreenChrome;
3131 oldChrome = currentChrome;
3132 } else {
3133 // if there's nothing to "restore" it to, just use what's there now
3134 oldChrome = GetCachedStyles().refOr(currentChrome);
3135 newChrome = oldChrome;
3136 if (mFutureMarginsToUse) {
3137 SetNonClientMargins(mFutureMarginsOnceChromeShows);
3141 SetCachedStyles(oldChrome);
3142 SetStyles(hwnd, kChromeMask.merge(chromeless, newChrome));
3145 /**************************************************************
3147 * SECTION: nsWindow::Invalidate
3149 * Invalidate an area of the client for painting.
3151 **************************************************************/
3153 // Invalidate this component visible area
3154 void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
3155 bool aIncludeChildren) {
3156 if (!mWnd) {
3157 return;
3160 #ifdef WIDGET_DEBUG_OUTPUT
3161 debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
3162 #endif // WIDGET_DEBUG_OUTPUT
3164 DWORD flags = RDW_INVALIDATE;
3165 if (aEraseBackground) {
3166 flags |= RDW_ERASE;
3168 if (aUpdateNCArea) {
3169 flags |= RDW_FRAME;
3171 if (aIncludeChildren) {
3172 flags |= RDW_ALLCHILDREN;
3175 VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
3178 // Invalidate this component visible area
3179 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
3180 if (mWnd) {
3181 #ifdef WIDGET_DEBUG_OUTPUT
3182 debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
3183 #endif // WIDGET_DEBUG_OUTPUT
3185 RECT rect;
3187 rect.left = aRect.X();
3188 rect.top = aRect.Y();
3189 rect.right = aRect.XMost();
3190 rect.bottom = aRect.YMost();
3192 VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
3196 static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
3197 WPARAM wParam,
3198 LPARAM lParam) {
3199 switch (uMsg) {
3200 case WM_FULLSCREEN_TRANSITION_BEFORE:
3201 case WM_FULLSCREEN_TRANSITION_AFTER: {
3202 DWORD duration = (DWORD)lParam;
3203 DWORD flags = AW_BLEND;
3204 if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
3205 flags |= AW_HIDE;
3207 ::AnimateWindow(hWnd, duration, flags);
3208 // The message sender should have added ref for us.
3209 NS_DispatchToMainThread(
3210 already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
3211 break;
3213 case WM_DESTROY:
3214 ::PostQuitMessage(0);
3215 break;
3216 default:
3217 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
3219 return 0;
3222 struct FullscreenTransitionInitData {
3223 LayoutDeviceIntRect mBounds;
3224 HANDLE mSemaphore;
3225 HANDLE mThread;
3226 HWND mWnd;
3228 FullscreenTransitionInitData()
3229 : mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
3231 ~FullscreenTransitionInitData() {
3232 if (mSemaphore) {
3233 ::CloseHandle(mSemaphore);
3235 if (mThread) {
3236 ::CloseHandle(mThread);
3241 static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
3242 // Initialize window class
3243 static bool sInitialized = false;
3244 if (!sInitialized) {
3245 WNDCLASSW wc = {};
3246 wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
3247 wc.hInstance = nsToolkit::mDllInstance;
3248 wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
3249 wc.lpszClassName = kClassNameTransition;
3250 ::RegisterClassW(&wc);
3251 sInitialized = true;
3254 auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
3255 HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
3256 nullptr, nsToolkit::mDllInstance, nullptr);
3257 if (!wnd) {
3258 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3259 return 0;
3262 // Since AnimateWindow blocks the thread of the transition window,
3263 // we need to hide the cursor for that window, otherwise the system
3264 // would show the busy pointer to the user.
3265 ::ShowCursor(false);
3266 ::SetWindowLongW(wnd, GWL_STYLE, 0);
3267 ::SetWindowLongW(
3268 wnd, GWL_EXSTYLE,
3269 WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
3270 ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
3271 data->mBounds.Width(), data->mBounds.Height(), 0);
3272 data->mWnd = wnd;
3273 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3274 // The initialization data may no longer be valid
3275 // after we release the semaphore.
3276 data = nullptr;
3278 MSG msg;
3279 while (::GetMessageW(&msg, nullptr, 0, 0)) {
3280 ::TranslateMessage(&msg);
3281 ::DispatchMessage(&msg);
3283 ::ShowCursor(true);
3284 ::DestroyWindow(wnd);
3285 return 0;
3288 class FullscreenTransitionData final : public nsISupports {
3289 public:
3290 NS_DECL_ISUPPORTS
3292 explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
3293 MOZ_ASSERT(NS_IsMainThread(),
3294 "FullscreenTransitionData "
3295 "should be constructed in the main thread");
3298 const HWND mWnd;
3300 private:
3301 ~FullscreenTransitionData() {
3302 MOZ_ASSERT(NS_IsMainThread(),
3303 "FullscreenTransitionData "
3304 "should be deconstructed in the main thread");
3305 ::PostMessageW(mWnd, WM_DESTROY, 0, 0);
3309 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
3311 /* virtual */
3312 bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
3313 FullscreenTransitionInitData initData;
3314 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
3315 const DesktopIntRect rect = screen->GetRectDisplayPix();
3316 MOZ_ASSERT(BoundsUseDesktopPixels(),
3317 "Should only be called on top-level window");
3318 initData.mBounds =
3319 LayoutDeviceIntRect::Round(rect * GetDesktopToDeviceScale());
3321 // Create a semaphore for synchronizing the window handle which will
3322 // be created by the transition thread and used by the main thread for
3323 // posting the transition messages.
3324 initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
3325 if (initData.mSemaphore) {
3326 initData.mThread = ::CreateThread(
3327 nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
3328 if (initData.mThread) {
3329 ::WaitForSingleObject(initData.mSemaphore, INFINITE);
3332 if (!initData.mWnd) {
3333 return false;
3336 mTransitionWnd = initData.mWnd;
3338 auto data = new FullscreenTransitionData(initData.mWnd);
3339 *aData = data;
3340 NS_ADDREF(data);
3341 return true;
3344 /* virtual */
3345 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
3346 uint16_t aDuration,
3347 nsISupports* aData,
3348 nsIRunnable* aCallback) {
3349 auto data = static_cast<FullscreenTransitionData*>(aData);
3350 nsCOMPtr<nsIRunnable> callback = aCallback;
3351 UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
3352 : WM_FULLSCREEN_TRANSITION_AFTER;
3353 WPARAM wparam = (WPARAM)callback.forget().take();
3354 ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
3357 /* virtual */
3358 void nsWindow::CleanupFullscreenTransition() {
3359 MOZ_ASSERT(NS_IsMainThread(),
3360 "CleanupFullscreenTransition "
3361 "should only run on the main thread");
3363 mTransitionWnd = nullptr;
3366 void nsWindow::TryDwmResizeHack() {
3367 // The "DWM resize hack", aka the "fullscreen resize hack", is a workaround
3368 // for DWM's occasional and not-entirely-predictable failure to update its
3369 // internal state when the client area of a window changes without changing
3370 // the window size. The effect of this is that DWM will clip the content of
3371 // the window to its former client area.
3373 // It is not known under what circumstances the bug will trigger. Windows 11
3374 // is known to be required, but many Windows 11 machines do not exhibit the
3375 // issue. Even machines that _do_ exhibit it will sometimes not do so when
3376 // apparently-irrelevant changes are made to the configuration. (See bug
3377 // 1763981.)
3379 // The bug is triggered by Firefox when a maximized window (which has window
3380 // decorations) becomes fullscreen (which doesn't). To work around this, if we
3381 // think it may occur, we "flicker-resize" the relevant window -- that is, we
3382 // reduce its height by 1px, then restore it. This causes DWM to acquire the
3383 // new client-area metrics.
3385 // Note that, in particular, this bug will not occur when using a separate
3386 // compositor window, as our compositor windows never have any nonclient area.
3388 // This is admittedly a sledgehammer where a screwdriver should suffice.
3390 // ---------------------------------------------------------------------------
3392 // Regardless of preferences or heuristics, only apply the hack if this is the
3393 // first time we've entered fullscreen across the entire Firefox session.
3394 // (Subsequent transitions to fullscreen, even with different windows, don't
3395 // appear to induce the bug.)
3397 // (main thread only; `atomic` not needed)
3398 static bool sIsFirstFullscreenEntry = true;
3399 bool isFirstFullscreenEntry = sIsFirstFullscreenEntry;
3400 sIsFirstFullscreenEntry = false;
3401 if (MOZ_LIKELY(!isFirstFullscreenEntry)) {
3402 return;
3404 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3405 ("%s: first fullscreen entry", __PRETTY_FUNCTION__));
3408 // Check whether to try to apply the DWM resize hack, based on the override
3409 // pref and/or some internal heuristics.
3411 const auto hackApplicationHeuristics = [&]() -> bool {
3412 // The bug has only been seen under Windows 11. (At time of writing, this
3413 // is the latest version of Windows.)
3414 if (!IsWin11OrLater()) {
3415 return false;
3418 KnowsCompositor const* const kc = mWindowRenderer->AsKnowsCompositor();
3419 // This should never happen...
3420 MOZ_ASSERT(kc);
3421 // ... so if it does, we are in uncharted territory: don't apply the hack.
3422 if (!kc) {
3423 return false;
3426 // The bug doesn't occur when we're using a separate compositor window
3427 // (since the compositor window always comprises exactly its client area,
3428 // with no non-client border).
3429 if (kc->GetUseCompositorWnd()) {
3430 return false;
3433 // Otherwise, apply the hack.
3434 return true;
3437 // Figure out whether or not we should perform the hack, and -- arguably
3438 // more importantly -- log that decision.
3439 bool const shouldApplyHack = [&]() {
3440 enum Reason : bool { Pref, Heuristics };
3441 auto const msg = [&](bool decision, Reason reason) -> bool {
3442 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3443 ("%s %s per %s", decision ? "applying" : "skipping",
3444 "DWM resize hack", reason == Pref ? "pref" : "heuristics"));
3445 return decision;
3447 switch (StaticPrefs::widget_windows_apply_dwm_resize_hack()) {
3448 case 0:
3449 return msg(false, Pref);
3450 case 1:
3451 return msg(true, Pref);
3452 default: // treat all other values as `auto`
3453 return msg(hackApplicationHeuristics(), Heuristics);
3455 }();
3457 if (!shouldApplyHack) {
3458 return;
3462 // The DWM bug is believed to involve a race condition: some users have
3463 // reported that setting a custom theme or adding unused command-line
3464 // parameters sometimes causes the bug to vanish.
3466 // Out of an abundance of caution, we therefore apply the hack in a later
3467 // event, rather than inline.
3468 NS_DispatchToMainThread(NS_NewRunnableFunction(
3469 "nsWindow::TryFullscreenResizeHack", [self = RefPtr(this)]() {
3470 HWND const hwnd = self->GetWindowHandle();
3472 if (self->mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
3473 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info,
3474 ("DWM resize hack: window no longer fullscreen; aborting"));
3475 return;
3478 RECT origRect;
3479 if (!::GetWindowRect(hwnd, &origRect)) {
3480 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Error,
3481 ("DWM resize hack: could not get window size?!"));
3482 return;
3484 LONG const x = origRect.left;
3485 LONG const y = origRect.top;
3486 LONG const width = origRect.right - origRect.left;
3487 LONG const height = origRect.bottom - origRect.top;
3489 MOZ_DIAGNOSTIC_ASSERT(!self->mIsPerformingDwmFlushHack);
3490 auto const onExit =
3491 MakeScopeExit([&, oldVal = self->mIsPerformingDwmFlushHack]() {
3492 self->mIsPerformingDwmFlushHack = oldVal;
3494 self->mIsPerformingDwmFlushHack = true;
3496 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3497 ("beginning DWM resize hack for HWND %08" PRIXPTR,
3498 uintptr_t(hwnd)));
3499 ::MoveWindow(hwnd, x, y, width, height - 1, FALSE);
3500 ::MoveWindow(hwnd, x, y, width, height, TRUE);
3501 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3502 ("concluded DWM resize hack for HWND %08" PRIXPTR,
3503 uintptr_t(hwnd)));
3504 }));
3507 void nsWindow::OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen) {
3508 MOZ_ASSERT((aOldSizeMode != nsSizeMode_Fullscreen) == aFullScreen);
3510 // HACK: Potentially flicker-resize the window, to force DWM to get the right
3511 // client-area information.
3512 if (aFullScreen) {
3513 TryDwmResizeHack();
3516 // Hide chrome and reposition window. Note this will also cache dimensions for
3517 // restoration, so it should only be called once per fullscreen request.
3519 // Don't do this when minimized, since our bounds make no sense then, nor when
3520 // coming back from that state.
3521 const bool toOrFromMinimized =
3522 mFrameState->GetSizeMode() == nsSizeMode_Minimized ||
3523 aOldSizeMode == nsSizeMode_Minimized;
3524 if (!toOrFromMinimized) {
3525 InfallibleMakeFullScreen(aFullScreen);
3528 // Possibly notify the taskbar that we have changed our fullscreen mode.
3529 TaskbarConcealer::OnFullscreenChanged(this, aFullScreen);
3532 nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
3533 mFrameState->EnsureFullscreenMode(aFullScreen);
3534 return NS_OK;
3537 /**************************************************************
3539 * SECTION: Native data storage
3541 * nsIWidget::GetNativeData
3542 * nsIWidget::FreeNativeData
3544 * Set or clear native data based on a constant.
3546 **************************************************************/
3548 // Return some native data according to aDataType
3549 void* nsWindow::GetNativeData(uint32_t aDataType) {
3550 switch (aDataType) {
3551 case NS_NATIVE_WIDGET:
3552 case NS_NATIVE_WINDOW:
3553 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
3554 return (void*)mWnd;
3555 case NS_NATIVE_GRAPHIC:
3556 MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
3557 return nullptr;
3558 case NS_RAW_NATIVE_IME_CONTEXT: {
3559 void* pseudoIMEContext = GetPseudoIMEContext();
3560 if (pseudoIMEContext) {
3561 return pseudoIMEContext;
3563 [[fallthrough]];
3565 case NS_NATIVE_TSF_THREAD_MGR:
3566 case NS_NATIVE_TSF_CATEGORY_MGR:
3567 case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
3568 return IMEHandler::GetNativeData(this, aDataType);
3570 default:
3571 break;
3574 return nullptr;
3577 // Free some native data according to aDataType
3578 void nsWindow::FreeNativeData(void* data, uint32_t aDataType) {
3579 switch (aDataType) {
3580 case NS_NATIVE_GRAPHIC:
3581 case NS_NATIVE_WIDGET:
3582 case NS_NATIVE_WINDOW:
3583 break;
3584 default:
3585 break;
3589 /**************************************************************
3591 * SECTION: nsIWidget::SetTitle
3593 * Set the main windows title text.
3595 **************************************************************/
3597 nsresult nsWindow::SetTitle(const nsAString& aTitle) {
3598 const nsString& strTitle = PromiseFlatString(aTitle);
3599 AutoRestore<bool> sendingText(mSendingSetText);
3600 mSendingSetText = true;
3601 ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
3602 return NS_OK;
3605 /**************************************************************
3607 * SECTION: nsIWidget::SetIcon
3609 * Set the main windows icon.
3611 **************************************************************/
3613 void nsWindow::SetBigIcon(HICON aIcon) {
3614 HICON icon =
3615 (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
3616 if (icon) {
3617 ::DestroyIcon(icon);
3620 mIconBig = aIcon;
3623 void nsWindow::SetSmallIcon(HICON aIcon) {
3624 HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
3625 (LPARAM)aIcon);
3626 if (icon) {
3627 ::DestroyIcon(icon);
3630 mIconSmall = aIcon;
3633 void nsWindow::SetIcon(const nsAString& aIconSpec) {
3634 // Assume the given string is a local identifier for an icon file.
3636 nsCOMPtr<nsIFile> iconFile;
3637 ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
3638 if (!iconFile) return;
3640 nsAutoString iconPath;
3641 iconFile->GetPath(iconPath);
3643 // XXX this should use MZLU (see bug 239279)
3645 ::SetLastError(0);
3647 HICON bigIcon =
3648 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3649 ::GetSystemMetrics(SM_CXICON),
3650 ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
3651 HICON smallIcon =
3652 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3653 ::GetSystemMetrics(SM_CXSMICON),
3654 ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
3656 if (bigIcon) {
3657 SetBigIcon(bigIcon);
3659 #ifdef DEBUG_SetIcon
3660 else {
3661 NS_LossyConvertUTF16toASCII cPath(iconPath);
3662 MOZ_LOG(gWindowsLog, LogLevel::Info,
3663 ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3664 ::GetLastError()));
3666 #endif
3667 if (smallIcon) {
3668 SetSmallIcon(smallIcon);
3670 #ifdef DEBUG_SetIcon
3671 else {
3672 NS_LossyConvertUTF16toASCII cPath(iconPath);
3673 MOZ_LOG(gWindowsLog, LogLevel::Info,
3674 ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3675 ::GetLastError()));
3677 #endif
3680 void nsWindow::SetBigIconNoData() {
3681 HICON bigIcon =
3682 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3683 SetBigIcon(bigIcon);
3686 void nsWindow::SetSmallIconNoData() {
3687 HICON smallIcon =
3688 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3689 SetSmallIcon(smallIcon);
3692 /**************************************************************
3694 * SECTION: nsIWidget::WidgetToScreenOffset
3696 * Return this widget's origin in screen coordinates.
3698 **************************************************************/
3700 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
3701 POINT point;
3702 point.x = 0;
3703 point.y = 0;
3704 ::ClientToScreen(mWnd, &point);
3705 return LayoutDeviceIntPoint(point.x, point.y);
3708 LayoutDeviceIntMargin nsWindow::ClientToWindowMargin() {
3709 if (mWindowType == WindowType::Popup) {
3710 return {};
3713 if (mCustomNonClient) {
3714 return NonClientSizeMargin(NormalWindowNonClientOffset());
3717 // Just use a dummy 200x200 at (200, 200) client rect as the rect.
3718 RECT clientRect;
3719 clientRect.left = 200;
3720 clientRect.top = 200;
3721 clientRect.right = 400;
3722 clientRect.bottom = 400;
3724 auto ToRect = [](const RECT& aRect) -> LayoutDeviceIntRect {
3725 return {aRect.left, aRect.top, aRect.right - aRect.left,
3726 aRect.bottom - aRect.top};
3729 RECT windowRect = clientRect;
3730 ::AdjustWindowRectEx(&windowRect, WindowStyle(), false, WindowExStyle());
3732 return ToRect(windowRect) - ToRect(clientRect);
3735 /**************************************************************
3737 * SECTION: nsIWidget::EnableDragDrop
3739 * Enables/Disables drag and drop of files on this widget.
3741 **************************************************************/
3743 void nsWindow::EnableDragDrop(bool aEnable) {
3744 if (!mWnd) {
3745 // Return early if the window already closed
3746 return;
3749 if (aEnable) {
3750 if (!mNativeDragTarget) {
3751 mNativeDragTarget = new nsNativeDragTarget(this);
3752 mNativeDragTarget->AddRef();
3753 ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
3755 } else {
3756 if (mWnd && mNativeDragTarget) {
3757 ::RevokeDragDrop(mWnd);
3758 mNativeDragTarget->DragCancel();
3759 NS_RELEASE(mNativeDragTarget);
3764 /**************************************************************
3766 * SECTION: nsIWidget::CaptureMouse
3768 * Enables/Disables system mouse capture.
3770 **************************************************************/
3772 void nsWindow::CaptureMouse(bool aCapture) {
3773 TRACKMOUSEEVENT mTrack;
3774 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
3775 mTrack.dwHoverTime = 0;
3776 mTrack.hwndTrack = mWnd;
3777 if (aCapture) {
3778 mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
3779 ::SetCapture(mWnd);
3780 } else {
3781 mTrack.dwFlags = TME_LEAVE;
3782 ::ReleaseCapture();
3784 sIsInMouseCapture = aCapture;
3785 TrackMouseEvent(&mTrack);
3788 /**************************************************************
3790 * SECTION: nsIWidget::CaptureRollupEvents
3792 * Dealing with event rollup on destroy for popups. Enables &
3793 * Disables system capture of any and all events that would
3794 * cause a dropdown to be rolled up.
3796 **************************************************************/
3798 void nsWindow::CaptureRollupEvents(bool aDoCapture) {
3799 if (aDoCapture) {
3800 if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
3801 RegisterSpecialDropdownHooks();
3803 sProcessHook = true;
3804 } else {
3805 sProcessHook = false;
3806 UnregisterSpecialDropdownHooks();
3810 /**************************************************************
3812 * SECTION: nsIWidget::GetAttention
3814 * Bring this window to the user's attention.
3816 **************************************************************/
3818 // Draw user's attention to this window until it comes to foreground.
3819 nsresult nsWindow::GetAttention(int32_t aCycleCount) {
3820 // Got window?
3821 if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
3823 HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
3824 HWND fgWnd = ::GetForegroundWindow();
3825 // Don't flash if the flash count is 0 or if the foreground window is our
3826 // window handle or that of our owned-most window.
3827 if (aCycleCount == 0 || flashWnd == fgWnd ||
3828 flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
3829 return NS_OK;
3832 DWORD defaultCycleCount = 0;
3833 ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
3835 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
3836 aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
3837 ::FlashWindowEx(&flashInfo);
3839 return NS_OK;
3842 void nsWindow::StopFlashing() {
3843 HWND flashWnd = mWnd;
3844 while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
3845 flashWnd = ownerWnd;
3848 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
3849 ::FlashWindowEx(&flashInfo);
3852 /**************************************************************
3854 * SECTION: nsIWidget::HasPendingInputEvent
3856 * Ask whether there user input events pending. All input events are
3857 * included, including those not targeted at this nsIwidget instance.
3859 **************************************************************/
3861 bool nsWindow::HasPendingInputEvent() {
3862 // If there is pending input or the user is currently
3863 // moving the window then return true.
3864 // Note: When the user is moving the window WIN32 spins
3865 // a separate event loop and input events are not
3866 // reported to the application.
3867 if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
3868 GUITHREADINFO guiInfo;
3869 guiInfo.cbSize = sizeof(GUITHREADINFO);
3870 if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
3871 return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
3874 /**************************************************************
3876 * SECTION: nsIWidget::GetWindowRenderer
3878 * Get the window renderer associated with this widget.
3880 **************************************************************/
3882 WindowRenderer* nsWindow::GetWindowRenderer() {
3883 if (mWindowRenderer) {
3884 return mWindowRenderer;
3887 if (!mLocalesChangedObserver) {
3888 mLocalesChangedObserver = new LocalesChangedObserver(this);
3891 // Try OMTC first.
3892 if (!mWindowRenderer && ShouldUseOffMainThreadCompositing()) {
3893 gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
3894 CreateCompositor();
3897 if (!mWindowRenderer) {
3898 MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
3899 MOZ_ASSERT(!mCompositorWidgetDelegate);
3901 // Ensure we have a widget proxy even if we're not using the compositor,
3902 // since all our transparent window handling lives there.
3903 WinCompositorWidgetInitData initData(
3904 reinterpret_cast<uintptr_t>(mWnd),
3905 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
3906 mTransparencyMode, mFrameState->GetSizeMode());
3907 // If we're not using the compositor, the options don't actually matter.
3908 CompositorOptions options(false, false);
3909 mBasicLayersSurface =
3910 new InProcessWinCompositorWidget(initData, options, this);
3911 mCompositorWidgetDelegate = mBasicLayersSurface;
3912 mWindowRenderer = CreateFallbackRenderer();
3915 NS_ASSERTION(mWindowRenderer, "Couldn't provide a valid window renderer.");
3917 if (mWindowRenderer) {
3918 // Update the size constraints now that the layer manager has been
3919 // created.
3920 KnowsCompositor* knowsCompositor = mWindowRenderer->AsKnowsCompositor();
3921 if (knowsCompositor) {
3922 SizeConstraints c = mSizeConstraints;
3923 mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
3924 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
3925 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
3926 nsBaseWidget::SetSizeConstraints(c);
3930 return mWindowRenderer;
3933 /**************************************************************
3935 * SECTION: nsBaseWidget::SetCompositorWidgetDelegate
3937 * Called to connect the nsWindow to the delegate providing
3938 * platform compositing API access.
3940 **************************************************************/
3942 void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
3943 if (delegate) {
3944 mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
3945 MOZ_ASSERT(mCompositorWidgetDelegate,
3946 "nsWindow::SetCompositorWidgetDelegate called with a "
3947 "non-PlatformCompositorWidgetDelegate");
3948 } else {
3949 mCompositorWidgetDelegate = nullptr;
3953 /**************************************************************
3955 * SECTION: nsIWidget::OnDefaultButtonLoaded
3957 * Called after the dialog is loaded and it has a default button.
3959 **************************************************************/
3961 nsresult nsWindow::OnDefaultButtonLoaded(
3962 const LayoutDeviceIntRect& aButtonRect) {
3963 if (aButtonRect.IsEmpty()) return NS_OK;
3965 // Don't snap when we are not active.
3966 HWND activeWnd = ::GetActiveWindow();
3967 if (activeWnd != ::GetForegroundWindow() ||
3968 WinUtils::GetTopLevelHWND(mWnd, true) !=
3969 WinUtils::GetTopLevelHWND(activeWnd, true)) {
3970 return NS_OK;
3973 bool isAlwaysSnapCursor =
3974 Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
3976 if (!isAlwaysSnapCursor) {
3977 BOOL snapDefaultButton;
3978 if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
3979 0) ||
3980 !snapDefaultButton)
3981 return NS_OK;
3984 LayoutDeviceIntRect widgetRect = GetScreenBounds();
3985 LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
3987 LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
3988 buttonRect.Y() + buttonRect.Height() / 2);
3989 // The center of the button can be outside of the widget.
3990 // E.g., it could be hidden by scrolling.
3991 if (!widgetRect.Contains(centerOfButton)) {
3992 return NS_OK;
3995 if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
3996 NS_ERROR("SetCursorPos failed");
3997 return NS_ERROR_FAILURE;
3999 return NS_OK;
4002 uint32_t nsWindow::GetMaxTouchPoints() const {
4003 return WinUtils::GetMaxTouchPoints();
4006 void nsWindow::SetWindowClass(const nsAString& xulWinType,
4007 const nsAString& xulWinClass,
4008 const nsAString& xulWinName) {
4009 mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank");
4012 /**************************************************************
4013 **************************************************************
4015 ** BLOCK: Moz Events
4017 ** Moz GUI event management.
4019 **************************************************************
4020 **************************************************************/
4022 /**************************************************************
4024 * SECTION: Mozilla event initialization
4026 * Helpers for initializing moz events.
4028 **************************************************************/
4030 // Event initialization
4031 void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
4032 if (nullptr == aPoint) { // use the point from the event
4033 // get the message position in client coordinates
4034 if (mWnd != nullptr) {
4035 DWORD pos = ::GetMessagePos();
4036 POINT cpos;
4038 cpos.x = GET_X_LPARAM(pos);
4039 cpos.y = GET_Y_LPARAM(pos);
4041 ::ScreenToClient(mWnd, &cpos);
4042 event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
4043 } else {
4044 event.mRefPoint = LayoutDeviceIntPoint(0, 0);
4046 } else {
4047 // use the point override if provided
4048 event.mRefPoint = *aPoint;
4051 event.AssignEventTime(CurrentMessageWidgetEventTime());
4054 WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
4055 LONG messageTime = ::GetMessageTime();
4056 return WidgetEventTime(GetMessageTimeStamp(messageTime));
4059 /**************************************************************
4061 * SECTION: Moz event dispatch helpers
4063 * Helpers for dispatching different types of moz events.
4065 **************************************************************/
4067 // Main event dispatch. Invokes callback and ProcessEvent method on
4068 // Event Listener object. Part of nsIWidget.
4069 nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
4070 nsEventStatus& aStatus) {
4071 #ifdef WIDGET_DEBUG_OUTPUT
4072 debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
4073 #endif // WIDGET_DEBUG_OUTPUT
4075 aStatus = nsEventStatus_eIgnore;
4077 // Top level windows can have a view attached which requires events be sent
4078 // to the underlying base window and the view. Added when we combined the
4079 // base chrome window with the main content child for nc client area (title
4080 // bar) rendering.
4081 if (mAttachedWidgetListener) {
4082 aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
4083 } else if (mWidgetListener) {
4084 aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
4087 // the window can be destroyed during processing of seemingly innocuous events
4088 // like, say, mousedowns due to the magic of scripting. mousedowns will return
4089 // nsEventStatus_eIgnore, which causes problems with the deleted window.
4090 // therefore:
4091 if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
4092 return NS_OK;
4095 bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
4096 WidgetGUIEvent event(true, aMsg, this);
4097 InitEvent(event);
4099 bool result = DispatchWindowEvent(event);
4100 return result;
4103 bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
4104 nsEventStatus status = DispatchInputEvent(event).mContentStatus;
4105 return ConvertStatus(status);
4108 bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
4109 nsEventStatus status;
4110 DispatchEvent(aEvent, status);
4111 return ConvertStatus(status);
4114 bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
4115 nsEventStatus status =
4116 DispatchInputEvent(aEvent->AsInputEvent()).mContentStatus;
4117 return ConvertStatus(status);
4120 // Recursively dispatch synchronous paints for nsIWidget
4121 // descendants with invalidated rectangles.
4122 BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
4123 LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
4124 if (proc == (LONG_PTR)&nsWindow::WindowProc) {
4125 // its one of our windows so check to see if it has a
4126 // invalidated rect. If it does. Dispatch a synchronous
4127 // paint.
4128 if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
4130 return TRUE;
4133 // Check for pending paints and dispatch any pending paint
4134 // messages for any nsIWidget which is a descendant of the
4135 // top-level window that *this* window is embedded within.
4137 // Note: We do not dispatch pending paint messages for non
4138 // nsIWidget managed windows.
4139 void nsWindow::DispatchPendingEvents() {
4140 // We need to ensure that reflow events do not get starved.
4141 // At the same time, we don't want to recurse through here
4142 // as that would prevent us from dispatching starved paints.
4143 static int recursionBlocker = 0;
4144 if (recursionBlocker++ == 0) {
4145 NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
4146 --recursionBlocker;
4149 // Quickly check to see if there are any paint events pending,
4150 // but only dispatch them if it has been long enough since the
4151 // last paint completed.
4152 if (::GetQueueStatus(QS_PAINT) &&
4153 ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
4154 // Find the top level window.
4155 HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
4157 // Dispatch pending paints for topWnd and all its descendant windows.
4158 // Note: EnumChildWindows enumerates all descendant windows not just
4159 // the children (but not the window itself).
4160 nsWindow::DispatchStarvedPaints(topWnd, 0);
4161 ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
4165 void nsWindow::DispatchCustomEvent(const nsString& eventName) {
4166 if (Document* doc = GetDocument()) {
4167 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
4168 win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
4173 bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
4174 LayoutDeviceIntPoint aEventPoint) {
4175 // Allow users to start dragging by double-tapping.
4176 if (aEventMessage == eMouseDoubleClick) {
4177 return true;
4180 // In chrome UI, allow touchdownstartsdrag attributes
4181 // to cause any touchdown event to trigger a drag.
4182 if (aEventMessage == eMouseDown) {
4183 WidgetMouseEvent hittest(true, eMouseHitTest, this,
4184 WidgetMouseEvent::eReal);
4185 hittest.mRefPoint = aEventPoint;
4186 hittest.mIgnoreRootScrollFrame = true;
4187 hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
4188 DispatchInputEvent(&hittest);
4190 if (EventTarget* target = hittest.GetDOMEventTarget()) {
4191 if (nsIContent* content = nsIContent::FromEventTarget(target)) {
4192 // Check if the element or any parent element has the
4193 // attribute we're looking for.
4194 for (Element* element = content->GetAsElementOrParentElement(); element;
4195 element = element->GetParentElement()) {
4196 nsAutoString startDrag;
4197 element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
4198 if (!startDrag.IsEmpty()) {
4199 return true;
4206 return false;
4209 // Deal with all sort of mouse event
4210 bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
4211 LPARAM lParam, bool aIsContextMenuKey,
4212 int16_t aButton, uint16_t aInputSource,
4213 WinPointerInfo* aPointerInfo,
4214 bool aIgnoreAPZ) {
4215 ContextMenuPreventer contextMenuPreventer(this);
4216 bool result = false;
4218 UserActivity();
4220 if (!mWidgetListener) {
4221 return result;
4224 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
4225 LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
4227 // Suppress mouse moves caused by widget creation. Make sure to do this early
4228 // so that we update sLastMouseMovePoint even for touch-induced mousemove
4229 // events.
4230 if (aEventMessage == eMouseMove) {
4231 if ((sLastMouseMovePoint.x == mpScreen.x.value) &&
4232 (sLastMouseMovePoint.y == mpScreen.y.value)) {
4233 return result;
4235 sLastMouseMovePoint.x = mpScreen.x;
4236 sLastMouseMovePoint.y = mpScreen.y;
4239 if (!aIgnoreAPZ && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
4240 if (mTouchWindow) {
4241 // If mTouchWindow is true, then we must have APZ enabled and be
4242 // feeding it raw touch events. In that case we only want to
4243 // send touch-generated mouse events to content if they should
4244 // start a touch-based drag-and-drop gesture, such as on
4245 // double-tapping or when tapping elements marked with the
4246 // touchdownstartsdrag attribute in chrome UI.
4247 MOZ_ASSERT(mAPZC);
4248 if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
4249 aEventMessage = eMouseTouchDrag;
4250 } else {
4251 return result;
4256 uint32_t pointerId =
4257 aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
4259 switch (aEventMessage) {
4260 case eMouseDown:
4261 CaptureMouse(true);
4262 break;
4264 // eMouseMove and eMouseExitFromWidget are here because we need to make
4265 // sure capture flag isn't left on after a drag where we wouldn't see a
4266 // button up message (see bug 324131).
4267 case eMouseUp:
4268 case eMouseMove:
4269 case eMouseExitFromWidget:
4270 if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
4271 sIsInMouseCapture)
4272 CaptureMouse(false);
4273 break;
4275 default:
4276 break;
4278 } // switch
4280 WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal,
4281 aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
4282 : WidgetMouseEvent::eNormal);
4283 if (aEventMessage == eContextMenu && aIsContextMenuKey) {
4284 LayoutDeviceIntPoint zero(0, 0);
4285 InitEvent(event, &zero);
4286 } else {
4287 InitEvent(event, &eventPoint);
4290 ModifierKeyState modifierKeyState;
4291 modifierKeyState.InitInputEvent(event);
4293 // eContextMenu with Shift state is special. It won't fire "contextmenu"
4294 // event in the web content for blocking web content to prevent its default.
4295 // However, Shift+F10 is a standard shortcut key on Windows. Therefore,
4296 // this should not block web page to prevent its default. I.e., it should
4297 // behave same as ContextMenu key without Shift key.
4298 // XXX Should we allow to block web page to prevent its default with
4299 // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
4300 if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() &&
4301 NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
4302 NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
4303 event.mModifiers &= ~MODIFIER_SHIFT;
4306 event.mButton = aButton;
4307 event.mInputSource = aInputSource;
4308 if (aPointerInfo) {
4309 // Mouse events from Windows WM_POINTER*. Fill more information in
4310 // WidgetMouseEvent.
4311 event.AssignPointerHelperData(*aPointerInfo);
4312 event.mPressure = aPointerInfo->mPressure;
4313 event.mButtons = aPointerInfo->mButtons;
4314 } else {
4315 // If we get here the mouse events must be from non-touch sources, so
4316 // convert it to pointer events as well
4317 event.convertToPointer = true;
4318 event.pointerId = pointerId;
4321 // Static variables used to distinguish simple-, double- and triple-clicks.
4322 static POINT sLastMousePoint = {0};
4323 static LONG sLastMouseDownTime = 0L;
4324 static LONG sLastClickCount = 0L;
4325 static BYTE sLastMouseButton = 0;
4327 bool insideMovementThreshold =
4328 (DeprecatedAbs(sLastMousePoint.x - eventPoint.x.value) <
4329 (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
4330 (DeprecatedAbs(sLastMousePoint.y - eventPoint.y.value) <
4331 (short)::GetSystemMetrics(SM_CYDOUBLECLK));
4333 BYTE eventButton;
4334 switch (aButton) {
4335 case MouseButton::ePrimary:
4336 eventButton = VK_LBUTTON;
4337 break;
4338 case MouseButton::eMiddle:
4339 eventButton = VK_MBUTTON;
4340 break;
4341 case MouseButton::eSecondary:
4342 eventButton = VK_RBUTTON;
4343 break;
4344 default:
4345 eventButton = 0;
4346 break;
4349 // Doubleclicks are used to set the click count, then changed to mousedowns
4350 // We're going to time double-clicks from mouse *up* to next mouse *down*
4351 LONG curMsgTime = ::GetMessageTime();
4353 switch (aEventMessage) {
4354 case eMouseDoubleClick:
4355 event.mMessage = eMouseDown;
4356 event.mButton = aButton;
4357 sLastClickCount = 2;
4358 sLastMouseDownTime = curMsgTime;
4359 break;
4360 case eMouseUp:
4361 // remember when this happened for the next mouse down
4362 sLastMousePoint.x = eventPoint.x;
4363 sLastMousePoint.y = eventPoint.y;
4364 sLastMouseButton = eventButton;
4365 break;
4366 case eMouseDown:
4367 // now look to see if we want to convert this to a double- or triple-click
4368 if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
4369 insideMovementThreshold && eventButton == sLastMouseButton) {
4370 sLastClickCount++;
4371 } else {
4372 // reset the click count, to count *this* click
4373 sLastClickCount = 1;
4375 // Set last Click time on MouseDown only
4376 sLastMouseDownTime = curMsgTime;
4377 break;
4378 case eMouseMove:
4379 if (!insideMovementThreshold) {
4380 sLastClickCount = 0;
4382 break;
4383 case eMouseExitFromWidget:
4384 event.mExitFrom =
4385 Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
4386 : WidgetMouseEvent::ePlatformChild);
4387 break;
4388 default:
4389 break;
4391 event.mClickCount = sLastClickCount;
4393 #ifdef NS_DEBUG_XX
4394 MOZ_LOG(gWindowsLog, LogLevel::Info,
4395 ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount));
4396 #endif
4398 // call the event callback
4399 if (mWidgetListener) {
4400 if (aEventMessage == eMouseMove) {
4401 LayoutDeviceIntRect rect = GetBounds();
4402 rect.MoveTo(0, 0);
4404 if (rect.Contains(event.mRefPoint)) {
4405 if (sCurrentWindow == nullptr || sCurrentWindow != this) {
4406 if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
4407 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4408 sCurrentWindow->DispatchMouseEvent(
4409 eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
4410 aInputSource, aPointerInfo);
4412 sCurrentWindow = this;
4413 if (!mInDtor) {
4414 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4415 sCurrentWindow->DispatchMouseEvent(
4416 eMouseEnterIntoWidget, wParam, pos, false,
4417 MouseButton::ePrimary, aInputSource, aPointerInfo);
4421 } else if (aEventMessage == eMouseExitFromWidget) {
4422 if (sCurrentWindow == this) {
4423 sCurrentWindow = nullptr;
4427 nsIWidget::ContentAndAPZEventStatus eventStatus =
4428 DispatchInputEvent(&event);
4429 contextMenuPreventer.Update(event, eventStatus);
4430 return ConvertStatus(eventStatus.mContentStatus);
4433 return result;
4436 HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
4437 // retrieve the toplevel window or dialogue
4438 HWND toplevelWnd = nullptr;
4439 while (aCurWnd) {
4440 toplevelWnd = aCurWnd;
4441 nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
4442 if (win) {
4443 if (win->mWindowType == WindowType::TopLevel ||
4444 win->mWindowType == WindowType::Dialog) {
4445 break;
4449 aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
4451 return toplevelWnd;
4454 void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
4455 if (aIsActivate && mPickerDisplayCount) {
4456 // We disable the root window when a picker opens. See PickerOpen. When the
4457 // picker closes (but before PickerClosed is called), our window will get
4458 // focus, but it will still be disabled. This confuses the focus system.
4459 // Therefore, we ignore this focus and explicitly call this function once
4460 // we re-enable the window. Rarely, the picker seems to re-enable our root
4461 // window before we do, but for simplicity, we always ignore focus before
4462 // the final call to PickerClosed. See bug 1883568 for further details.
4463 return;
4466 if (aIsActivate) {
4467 sJustGotActivate = false;
4469 sJustGotDeactivate = false;
4470 mLastKillFocusWindow = nullptr;
4472 HWND toplevelWnd = GetTopLevelForFocus(mWnd);
4474 if (toplevelWnd) {
4475 nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
4476 if (win && win->mWidgetListener) {
4477 if (aIsActivate) {
4478 win->mWidgetListener->WindowActivated();
4479 } else {
4480 win->mWidgetListener->WindowDeactivated();
4486 HWND nsWindow::WindowAtMouse() {
4487 DWORD pos = ::GetMessagePos();
4488 POINT mp;
4489 mp.x = GET_X_LPARAM(pos);
4490 mp.y = GET_Y_LPARAM(pos);
4491 return ::WindowFromPoint(mp);
4494 bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
4495 HWND mouseWnd = WindowAtMouse();
4497 // WinUtils::GetTopLevelHWND() will return a HWND for the window frame
4498 // (which includes the non-client area). If the mouse has moved into
4499 // the non-client area, we should treat it as a top-level exit.
4500 HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
4501 if (mouseWnd == mouseTopLevel) return true;
4503 return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
4506 /**************************************************************
4508 * SECTION: IPC
4510 * IPC related helpers.
4512 **************************************************************/
4514 // static
4515 bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
4516 switch (aMsg) {
4517 case WM_SETFOCUS:
4518 case WM_KILLFOCUS:
4519 case WM_ENABLE:
4520 case WM_WINDOWPOSCHANGING:
4521 case WM_WINDOWPOSCHANGED:
4522 case WM_PARENTNOTIFY:
4523 case WM_ACTIVATEAPP:
4524 case WM_NCACTIVATE:
4525 case WM_ACTIVATE:
4526 case WM_CHILDACTIVATE:
4527 case WM_IME_SETCONTEXT:
4528 case WM_IME_NOTIFY:
4529 case WM_SHOWWINDOW:
4530 case WM_CANCELMODE:
4531 case WM_MOUSEACTIVATE:
4532 case WM_CONTEXTMENU:
4533 aResult = 0;
4534 return true;
4536 case WM_SETTINGCHANGE:
4537 case WM_SETCURSOR:
4538 return false;
4541 #ifdef DEBUG
4542 char szBuf[200];
4543 sprintf(szBuf,
4544 "An unhandled ISMEX_SEND message was received during spin loop! (%X)",
4545 aMsg);
4546 NS_WARNING(szBuf);
4547 #endif
4549 return false;
4552 void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
4553 MOZ_ASSERT_IF(
4554 msg != WM_GETOBJECT,
4555 !mozilla::ipc::MessageChannel::IsPumpingMessages() ||
4556 mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
4558 // Modal UI being displayed in windowless plugins.
4559 if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
4560 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4561 LRESULT res;
4562 if (IsAsyncResponseEvent(msg, res)) {
4563 ReplyMessage(res);
4565 return;
4568 // Handle certain sync plugin events sent to the parent which
4569 // trigger ipc calls that result in deadlocks.
4571 DWORD dwResult = 0;
4572 bool handled = false;
4574 switch (msg) {
4575 // Windowless flash sending WM_ACTIVATE events to the main window
4576 // via calls to ShowWindow.
4577 case WM_ACTIVATE:
4578 if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
4579 IsWindow((HWND)lParam)) {
4580 // Check for Adobe Reader X sync activate message from their
4581 // helper window and ignore. Fixes an annoying focus problem.
4582 if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
4583 ISMEX_SEND) {
4584 wchar_t szClass[10];
4585 HWND focusWnd = (HWND)lParam;
4586 if (IsWindowVisible(focusWnd) &&
4587 GetClassNameW(focusWnd, szClass,
4588 sizeof(szClass) / sizeof(char16_t)) &&
4589 !wcscmp(szClass, L"Edit") &&
4590 !WinUtils::IsOurProcessWindow(focusWnd)) {
4591 break;
4594 handled = true;
4596 break;
4597 // Plugins taking or losing focus triggering focus app messages.
4598 case WM_SETFOCUS:
4599 case WM_KILLFOCUS:
4600 // Windowed plugins that pass sys key events to defwndproc generate
4601 // WM_SYSCOMMAND events to the main window.
4602 case WM_SYSCOMMAND:
4603 // Windowed plugins that fire context menu selection events to parent
4604 // windows.
4605 case WM_CONTEXTMENU:
4606 // IME events fired as a result of synchronous focus changes
4607 case WM_IME_SETCONTEXT:
4608 handled = true;
4609 break;
4612 if (handled &&
4613 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4614 ReplyMessage(dwResult);
4618 /**************************************************************
4619 **************************************************************
4621 ** BLOCK: Native events
4623 ** Main Windows message handlers and OnXXX handlers for
4624 ** Windows event handling.
4626 **************************************************************
4627 **************************************************************/
4629 /**************************************************************
4631 * SECTION: Wind proc.
4633 * The main Windows event procedures and associated
4634 * message processing methods.
4636 **************************************************************/
4638 static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
4639 int32_t x, int32_t y) {
4640 HMENU hMenu = GetSystemMenu(hWnd, FALSE);
4641 if (hMenu) {
4642 MENUITEMINFO mii;
4643 mii.cbSize = sizeof(MENUITEMINFO);
4644 mii.fMask = MIIM_STATE;
4645 mii.fType = 0;
4647 // update the options
4648 mii.fState = MF_ENABLED;
4649 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4650 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4651 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4652 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4653 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4655 mii.fState = MF_GRAYED;
4656 switch (sizeMode) {
4657 case nsSizeMode_Fullscreen:
4658 // intentional fall through
4659 case nsSizeMode_Maximized:
4660 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4661 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4662 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4663 break;
4664 case nsSizeMode_Minimized:
4665 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4666 break;
4667 case nsSizeMode_Normal:
4668 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4669 break;
4670 case nsSizeMode_Invalid:
4671 NS_ASSERTION(false, "Did the argument come from invalid IPC?");
4672 break;
4673 default:
4674 MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected");
4675 break;
4677 LPARAM cmd = TrackPopupMenu(
4678 hMenu,
4679 (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN |
4680 (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
4681 x, y, 0, hWnd, nullptr);
4682 if (cmd) {
4683 PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
4684 return true;
4687 return false;
4690 // The WndProc procedure for all nsWindows in this toolkit. This merely catches
4691 // SEH exceptions and passes the real work to WindowProcInternal. See bug 587406
4692 // and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
4693 LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
4694 LPARAM lParam) {
4695 mozilla::ipc::CancelCPOWs();
4697 BackgroundHangMonitor().NotifyActivity();
4699 return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
4700 wParam, lParam);
4703 LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
4704 WPARAM wParam, LPARAM lParam) {
4705 if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
4706 // This message was sent to the FAKETRACKPOINTSCROLLABLE.
4707 if (msg == WM_HSCROLL) {
4708 // Route WM_HSCROLL messages to the main window.
4709 hWnd = ::GetParent(::GetParent(hWnd));
4710 } else {
4711 // Handle all other messages with its original window procedure.
4712 WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
4713 return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
4717 if (msg == MOZ_WM_TRACE) {
4718 // This is a tracer event for measuring event loop latency.
4719 // See WidgetTraceEvent.cpp for more details.
4720 mozilla::SignalTracerThread();
4721 return 0;
4724 // Get the window which caused the event and ask it to process the message
4725 nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
4726 NS_ASSERTION(targetWindow, "nsWindow* is null!");
4727 if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
4729 // Hold the window for the life of this method, in case it gets
4730 // destroyed during processing, unless we're in the dtor already.
4731 nsCOMPtr<nsIWidget> kungFuDeathGrip;
4732 if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
4734 targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
4736 // Create this here so that we store the last rolled up popup until after
4737 // the event has been processed.
4738 nsAutoRollup autoRollup;
4740 LRESULT popupHandlingResult;
4741 if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult))
4742 return popupHandlingResult;
4744 // Call ProcessMessage
4745 LRESULT retValue;
4746 if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
4747 return retValue;
4750 LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
4751 wParam, lParam);
4753 return res;
4756 const char16_t* GetQuitType() {
4757 if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
4758 DWORD cchCmdLine = 0;
4759 HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
4760 &cchCmdLine, nullptr);
4761 if (rc == S_OK) {
4762 return u"os-restart";
4765 return nullptr;
4768 bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
4769 LPARAM& aLParam,
4770 MSGResult& aResult) {
4771 if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
4772 return true;
4775 if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
4776 return true;
4779 if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
4780 aResult)) {
4781 return true;
4784 return false;
4787 // The main windows message processing method. Wraps ProcessMessageInternal so
4788 // we can log aRetValue.
4789 bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
4790 LRESULT* aRetValue) {
4791 // For some events we might change the parameter values, so log
4792 // before and after we process them.
4793 NativeEventLogger eventLogger("nsWindow", mWnd, msg, wParam, lParam);
4794 bool result = ProcessMessageInternal(msg, wParam, lParam, aRetValue);
4795 eventLogger.SetResult(*aRetValue, result);
4797 return result;
4800 // The main windows message processing method. Called by ProcessMessage.
4801 bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
4802 LRESULT* aRetValue) {
4803 MSGResult msgResult(aRetValue);
4804 if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
4805 return (msgResult.mConsumed || !mWnd);
4808 bool result = false; // call the default nsWindow proc
4809 *aRetValue = 0;
4811 // The DWM resize hack (see bug 1763981) causes us to process a number of
4812 // messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
4813 // would ordinarily result in a whole lot of internal state being updated.
4815 // Since we're supposed to end in the same state we started in (and since the
4816 // content shouldn't know about any of this nonsense), just discard any
4817 // messages synchronously dispatched from within the hack.
4818 if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
4819 return true;
4822 // Glass hit testing w/custom transparent margins.
4824 // FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
4825 // now.
4826 LRESULT dwmHitResult;
4827 if (mCustomNonClient &&
4828 DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
4829 *aRetValue = dwmHitResult;
4830 return true;
4833 // The preference whether to use a different keyboard layout for each
4834 // window is cached, and updating it will not take effect until the
4835 // next restart. We read the preference here and not upon WM_ACTIVATE to make
4836 // sure that this behavior is consistent. Otherwise, if the user changed the
4837 // preference before having ever lowered the window, the preference would take
4838 // effect immediately.
4839 static const bool sSwitchKeyboardLayout =
4840 Preferences::GetBool("intl.keyboard.per_window_layout", false);
4841 AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
4843 // (Large blocks of code should be broken out into OnEvent handlers.)
4844 switch (msg) {
4845 // WM_QUERYENDSESSION must be handled by all windows.
4846 // Otherwise Windows thinks the window can just be killed at will.
4847 case WM_QUERYENDSESSION: {
4848 // Ask around if it's ok to quit.
4849 nsCOMPtr<nsIObserverService> obsServ =
4850 mozilla::services::GetObserverService();
4851 nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
4852 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
4853 cancelQuitWrapper->SetData(false);
4855 const char16_t* quitType = GetQuitType();
4856 obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
4857 quitType);
4859 bool shouldCancelQuit;
4860 cancelQuitWrapper->GetData(&shouldCancelQuit);
4861 *aRetValue = !shouldCancelQuit;
4862 result = true;
4863 } break;
4865 case MOZ_WM_STARTA11Y:
4866 #if defined(ACCESSIBILITY)
4867 Unused << GetAccessible();
4868 result = true;
4869 #else
4870 result = false;
4871 #endif
4872 break;
4874 case WM_ENDSESSION: {
4875 // For WM_ENDSESSION, wParam indicates whether we need to shutdown
4876 // (TRUE) or not (FALSE).
4877 if (!wParam) {
4878 result = true;
4879 break;
4881 // According to WM_ENDSESSION lParam documentation:
4882 // 0 -> OS shutdown or restart (no way to distinguish)
4883 // ENDSESSION_LOGOFF -> User is logging off
4884 // ENDSESSION_CLOSEAPP -> Application must shutdown
4885 // ENDSESSION_CRITICAL -> Application is forced to shutdown
4886 // The difference of the last two is not very clear.
4887 if (lParam == 0) {
4888 shutdownReason = AppShutdownReason::OSShutdown;
4889 } else if (lParam & ENDSESSION_LOGOFF) {
4890 shutdownReason = AppShutdownReason::OSSessionEnd;
4891 } else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
4892 shutdownReason = AppShutdownReason::OSForceClose;
4893 } else {
4894 MOZ_DIAGNOSTIC_ASSERT(false,
4895 "Received WM_ENDSESSION with unknown flags.");
4896 shutdownReason = AppShutdownReason::OSForceClose;
4899 [[fallthrough]];
4900 case MOZ_WM_APP_QUIT: {
4901 if (shutdownReason == AppShutdownReason::Unknown) {
4902 // TODO: We do not expect that these days anybody sends us
4903 // MOZ_WM_APP_QUIT, see bug 1827807.
4904 shutdownReason = AppShutdownReason::WinUnexpectedMozQuit;
4906 // Let's fake a shutdown sequence without actually closing windows etc.
4907 // to avoid Windows killing us in the middle. A proper shutdown would
4908 // require having a chance to pump some messages. Unfortunately
4909 // Windows won't let us do that. Bug 212316.
4910 nsCOMPtr<nsIObserverService> obsServ =
4911 mozilla::services::GetObserverService();
4912 const char16_t* syncShutdown = u"syncShutdown";
4913 const char16_t* quitType = GetQuitType();
4915 AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
4917 obsServ->NotifyObservers(nullptr, "quit-application-granted",
4918 syncShutdown);
4919 obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
4921 AppShutdown::OnShutdownConfirmed();
4923 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
4924 quitType);
4925 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
4926 nullptr);
4927 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
4928 nullptr);
4929 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
4930 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
4931 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
4932 nullptr);
4934 AppShutdown::DoImmediateExit();
4935 MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
4936 } break;
4938 case WM_SYSCOLORCHANGE:
4939 // No need to invalidate layout for system color changes, but we need to
4940 // invalidate style.
4941 NotifyThemeChanged(widget::ThemeChangeKind::Style);
4942 break;
4944 case WM_THEMECHANGED: {
4945 // Update non-client margin offsets
4946 UpdateNonClientMargins();
4947 nsUXThemeData::UpdateNativeThemeInfo();
4949 // We assume pretty much everything could've changed here.
4950 NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
4952 UpdateDarkModeToolbar();
4954 // Invalidate the window so that the repaint will
4955 // pick up the new theme.
4956 Invalidate(true, true, true);
4957 } break;
4959 case WM_WTSSESSION_CHANGE: {
4960 switch (wParam) {
4961 case WTS_CONSOLE_CONNECT:
4962 case WTS_REMOTE_CONNECT:
4963 case WTS_SESSION_UNLOCK:
4964 // When a session becomes visible, we should invalidate.
4965 Invalidate(true, true, true);
4966 break;
4967 default:
4968 break;
4970 } break;
4972 case WM_FONTCHANGE: {
4973 // We only handle this message for the hidden window,
4974 // as we only need to update the (global) font list once
4975 // for any given change, not once per window!
4976 if (mWindowType != WindowType::Invisible) {
4977 break;
4980 // update the global font list
4981 gfxPlatform::GetPlatform()->UpdateFontList();
4982 } break;
4984 case WM_SETTINGCHANGE: {
4985 if (wParam == SPI_SETCLIENTAREAANIMATION ||
4986 wParam == SPI_SETKEYBOARDDELAY || wParam == SPI_SETMOUSEVANISH ||
4987 wParam == MOZ_SPI_SETCURSORSIZE) {
4988 // These need to update LookAndFeel cached values.
4989 // They affect reduced motion settings / caret blink count / show
4990 // pointer while typing / tooltip offset, so no need to invalidate style
4991 // / layout.
4992 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
4993 break;
4995 if (wParam == SPI_SETFONTSMOOTHING ||
4996 wParam == SPI_SETFONTSMOOTHINGTYPE) {
4997 gfxDWriteFont::UpdateSystemTextVars();
4998 break;
5000 if (wParam == SPI_SETWORKAREA) {
5001 // NB: We also refresh screens on WM_DISPLAYCHANGE but the rcWork
5002 // values are sometimes wrong at that point. This message then
5003 // arrives soon afterward, when we can get the right rcWork values.
5004 ScreenHelperWin::RefreshScreens();
5005 break;
5007 if (auto lParamString = reinterpret_cast<const wchar_t*>(lParam)) {
5008 if (!wcscmp(lParamString, L"ImmersiveColorSet")) {
5009 // This affects system colors (-moz-win-accentcolor), so gotta pass
5010 // the style flag.
5011 NotifyThemeChanged(widget::ThemeChangeKind::Style);
5012 break;
5015 // UserInteractionMode, ConvertibleSlateMode, SystemDockMode may cause
5016 // @media(pointer) queries to change, which layout needs to know about
5018 // (WM_SETTINGCHANGE will be sent to all top-level windows, so we
5019 // only respond to the hidden top-level window to avoid hammering
5020 // layout with a bunch of NotifyThemeChanged() calls)
5022 if (mWindowType == WindowType::Invisible) {
5023 if (!wcscmp(lParamString, L"UserInteractionMode") ||
5024 !wcscmp(lParamString, L"ConvertibleSlateMode") ||
5025 !wcscmp(lParamString, L"SystemDockMode")) {
5026 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5027 WindowsUIUtils::UpdateInTabletMode();
5031 } break;
5033 case WM_DEVICECHANGE: {
5034 if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
5035 DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
5036 // Check dbch_devicetype explicitly since we will get other device types
5037 // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify
5038 // DBT_DEVTYP_DEVICEINTERFACE in the filter for
5039 // RegisterDeviceNotification.
5040 if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
5041 // This can only change media queries (any-hover/any-pointer).
5042 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5045 } break;
5047 case WM_NCCALCSIZE: {
5048 // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
5049 // will need to be kept in sync.
5050 if (mCustomNonClient) {
5051 // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
5052 // the proposed window rectangle for our window. During our
5053 // processing of the `WM_NCCALCSIZE` message, we are expected to
5054 // modify the `RECT` that `lParam` points to, so that its value upon
5055 // our return is the new client area. We must return 0 if `wParam`
5056 // is `FALSE`.
5058 // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
5059 // struct. This struct contains an array of 3 `RECT`s, the first of
5060 // which has the exact same meaning as the `RECT` that is pointed to
5061 // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
5062 // conjunction with our return value, can
5063 // be used to specify portions of the source and destination window
5064 // rectangles that are valid and should be preserved. We opt not to
5065 // implement an elaborate client-area preservation technique, and
5066 // simply return 0, which means "preserve the entire old client area
5067 // and align it with the upper-left corner of our new client area".
5068 RECT* clientRect =
5069 wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
5070 : (reinterpret_cast<RECT*>(lParam));
5071 auto margin = NonClientSizeMargin();
5072 clientRect->top += margin.top;
5073 clientRect->left += margin.left;
5074 clientRect->right -= margin.right;
5075 clientRect->bottom -= margin.bottom;
5076 // Make client rect's width and height more than 0 to
5077 // avoid problems of webrender and angle.
5078 clientRect->right = std::max(clientRect->right, clientRect->left + 1);
5079 clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
5081 result = true;
5082 *aRetValue = 0;
5084 break;
5087 case WM_GETTITLEBARINFOEX: {
5088 if (!mCustomNonClient) {
5089 break;
5091 auto* info = reinterpret_cast<TITLEBARINFOEX*>(lParam);
5092 const LayoutDeviceIntPoint origin = WidgetToScreenOffset();
5093 auto GeckoClientToWinScreenRect =
5094 [&origin](LayoutDeviceIntRect aRect) -> RECT {
5095 aRect.MoveBy(origin);
5096 return {
5097 .left = aRect.x,
5098 .top = aRect.y,
5099 .right = aRect.XMost(),
5100 .bottom = aRect.YMost(),
5103 auto SetButton = [&](size_t aIndex, WindowButtonType aType) {
5104 info->rgrect[aIndex] =
5105 GeckoClientToWinScreenRect(mWindowBtnRect[aType]);
5106 DWORD& state = info->rgstate[aIndex];
5107 if (mWindowBtnRect[aType].IsEmpty()) {
5108 state = STATE_SYSTEM_INVISIBLE;
5109 } else {
5110 state = STATE_SYSTEM_FOCUSABLE;
5113 info->rgrect[0] = info->rcTitleBar =
5114 GeckoClientToWinScreenRect(mDraggableRegion.GetBounds());
5115 info->rgstate[0] = 0;
5116 SetButton(2, WindowButtonType::Minimize);
5117 SetButton(3, WindowButtonType::Maximize);
5118 SetButton(5, WindowButtonType::Close);
5119 // We don't have a help button.
5120 info->rgstate[4] = STATE_SYSTEM_INVISIBLE;
5121 info->rgrect[4] = {0, 0, 0, 0};
5122 result = true;
5123 } break;
5125 case WM_NCHITTEST: {
5126 if (mInputRegion.mFullyTransparent) {
5127 // Treat this window as transparent.
5128 *aRetValue = HTTRANSPARENT;
5129 result = true;
5130 break;
5133 if (mInputRegion.mMargin) {
5134 const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
5135 GET_Y_LPARAM(lParam));
5136 LayoutDeviceIntRect screenRect = GetScreenBounds();
5137 screenRect.Deflate(mInputRegion.mMargin);
5138 if (!screenRect.Contains(screenPoint)) {
5139 *aRetValue = HTTRANSPARENT;
5140 result = true;
5141 break;
5146 * If an nc client area margin has been moved, we are responsible
5147 * for calculating where the resize margins are and returning the
5148 * appropriate set of hit test constants. DwmDefWindowProc (above)
5149 * will handle hit testing on it's command buttons if we are on a
5150 * composited desktop.
5153 if (!mCustomNonClient) {
5154 break;
5157 *aRetValue =
5158 ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5159 result = true;
5160 break;
5163 case WM_SETTEXT:
5165 * WM_SETTEXT paints the titlebar area. Avoid this if we have a
5166 * custom titlebar we paint ourselves, or if we're the ones
5167 * sending the message with an updated title
5170 if (mSendingSetText || !mCustomNonClient || mNonClientMargins.top == -1)
5171 break;
5174 // From msdn, the way around this is to disable the visible state
5175 // temporarily. We need the text to be set but we don't want the
5176 // redraw to occur. However, we need to make sure that we don't
5177 // do this at the same time that a Present is happening.
5179 // To do this we take mPresentLock in nsWindow::PreRender and
5180 // if that lock is taken we wait before doing WM_SETTEXT
5181 if (mCompositorWidgetDelegate) {
5182 mCompositorWidgetDelegate->EnterPresentLock();
5184 DWORD style = GetWindowLong(mWnd, GWL_STYLE);
5185 SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
5186 *aRetValue =
5187 CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
5188 SetWindowLong(mWnd, GWL_STYLE, style);
5189 if (mCompositorWidgetDelegate) {
5190 mCompositorWidgetDelegate->LeavePresentLock();
5193 return true;
5196 case WM_NCACTIVATE: {
5198 * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting
5199 * through WM_NCPAINT via InvalidateNonClientRegion.
5201 UpdateGetWindowInfoCaptionStatus(FALSE != wParam);
5203 if (!mCustomNonClient) {
5204 break;
5207 // There is a case that rendered result is not kept. Bug 1237617
5208 if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
5209 NS_DispatchToMainThread(NewRunnableMethod(
5210 "nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
5213 // let the dwm handle nc painting on glass
5214 // Never allow native painting if we are on fullscreen
5215 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) break;
5217 if (wParam == TRUE) {
5218 // going active
5219 *aRetValue = FALSE; // ignored
5220 result = true;
5221 // invalidate to trigger a paint
5222 InvalidateNonClientRegion();
5223 break;
5224 } else {
5225 // going inactive
5226 *aRetValue = TRUE; // go ahead and deactive
5227 result = true;
5228 // invalidate to trigger a paint
5229 InvalidateNonClientRegion();
5230 break;
5234 case WM_NCPAINT: {
5236 * ClearType changes often don't send a WM_SETTINGCHANGE message. But they
5237 * do seem to always send a WM_NCPAINT message, so let's update on that.
5239 gfxDWriteFont::UpdateSystemTextVars();
5240 } break;
5242 case WM_POWERBROADCAST:
5243 switch (wParam) {
5244 case PBT_APMSUSPEND:
5245 PostSleepWakeNotification(true);
5246 break;
5247 case PBT_APMRESUMEAUTOMATIC:
5248 case PBT_APMRESUMECRITICAL:
5249 case PBT_APMRESUMESUSPEND:
5250 PostSleepWakeNotification(false);
5251 break;
5253 break;
5255 case WM_CLOSE: // close request
5256 if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
5257 result = true; // abort window closure
5258 break;
5260 case WM_DESTROY:
5261 // clean up.
5262 DestroyLayerManager();
5263 OnDestroy();
5264 result = true;
5265 break;
5267 case WM_PAINT:
5268 *aRetValue = (int)OnPaint(0);
5269 result = true;
5270 break;
5272 case WM_HOTKEY:
5273 result = OnHotKey(wParam, lParam);
5274 break;
5276 case WM_SYSCHAR:
5277 case WM_CHAR: {
5278 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5279 result = ProcessCharMessage(nativeMsg, nullptr);
5280 DispatchPendingEvents();
5281 } break;
5283 case WM_SYSKEYUP:
5284 case WM_KEYUP: {
5285 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5286 nativeMsg.time = ::GetMessageTime();
5287 result = ProcessKeyUpMessage(nativeMsg, nullptr);
5288 DispatchPendingEvents();
5289 } break;
5291 case WM_SYSKEYDOWN:
5292 case WM_KEYDOWN: {
5293 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5294 result = ProcessKeyDownMessage(nativeMsg, nullptr);
5295 DispatchPendingEvents();
5296 } break;
5298 // Say we've dealt with erasing the background. (This is actually handled in
5299 // WM_PAINT, where necessary.)
5300 case WM_ERASEBKGND: {
5301 *aRetValue = 1;
5302 result = true;
5303 } break;
5305 case WM_MOUSEMOVE: {
5306 LPARAM lParamScreen = lParamToScreen(lParam);
5307 mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
5308 GET_Y_LPARAM(lParamScreen));
5310 if (!mMousePresent && !sIsInMouseCapture) {
5311 // First MOUSEMOVE over the client area. Ask for MOUSELEAVE
5312 TRACKMOUSEEVENT mTrack;
5313 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5314 mTrack.dwFlags = TME_LEAVE;
5315 mTrack.dwHoverTime = 0;
5316 mTrack.hwndTrack = mWnd;
5317 TrackMouseEvent(&mTrack);
5319 mMousePresent = true;
5321 // Suppress dispatch of pending events
5322 // when mouse moves are generated by widget
5323 // creation instead of user input.
5324 POINT mp;
5325 mp.x = GET_X_LPARAM(lParamScreen);
5326 mp.y = GET_Y_LPARAM(lParamScreen);
5327 bool userMovedMouse = false;
5328 if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
5329 userMovedMouse = true;
5332 if (userMovedMouse) {
5333 result = DispatchMouseEvent(
5334 eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
5335 MOUSE_INPUT_SOURCE(),
5336 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5337 DispatchPendingEvents();
5339 } break;
5341 case WM_NCMOUSEMOVE: {
5342 LPARAM lParamClient = lParamToClient(lParam);
5343 if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
5344 if (!sIsInMouseCapture) {
5345 TRACKMOUSEEVENT mTrack;
5346 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5347 mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
5348 mTrack.dwHoverTime = 0;
5349 mTrack.hwndTrack = mWnd;
5350 TrackMouseEvent(&mTrack);
5352 // If we noticed the mouse moving in our draggable region, forward the
5353 // message as a normal WM_MOUSEMOVE.
5354 SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
5355 } else {
5356 // We've transitioned from a draggable area to somewhere else within
5357 // the non-client area - perhaps one of the edges of the window for
5358 // resizing.
5359 mSimulatedClientArea = false;
5362 if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
5363 SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
5365 } break;
5367 case WM_LBUTTONDOWN: {
5368 result =
5369 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5370 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5371 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5372 DispatchPendingEvents();
5373 } break;
5375 case WM_LBUTTONUP: {
5376 result =
5377 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5378 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5379 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5380 DispatchPendingEvents();
5381 } break;
5383 case WM_NCMOUSELEAVE: {
5384 mSimulatedClientArea = false;
5386 if (EventIsInsideWindow(this)) {
5387 // If we're handling WM_NCMOUSELEAVE and the mouse is still over the
5388 // window, then by process of elimination, the mouse has moved from the
5389 // non-client to client area, so no need to fall-through to the
5390 // WM_MOUSELEAVE handler. We also need to re-register for the
5391 // WM_MOUSELEAVE message, since according to the documentation at [1],
5392 // all tracking requested via TrackMouseEvent is cleared once
5393 // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
5394 // [1]:
5395 // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
5396 TRACKMOUSEEVENT mTrack;
5397 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5398 mTrack.dwFlags = TME_LEAVE;
5399 mTrack.dwHoverTime = 0;
5400 mTrack.hwndTrack = mWnd;
5401 TrackMouseEvent(&mTrack);
5402 break;
5404 // We've transitioned from non-client to outside of the window, so
5405 // fall-through to the WM_MOUSELEAVE handler.
5406 [[fallthrough]];
5408 case WM_MOUSELEAVE: {
5409 if (!mMousePresent) break;
5410 if (mSimulatedClientArea) break;
5411 mMousePresent = false;
5413 // Check if the mouse is over the fullscreen transition window, if so
5414 // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
5415 // transition window disappears will not be ignored, even if the mouse
5416 // hasn't moved.
5417 if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
5418 sLastMouseMovePoint = {0};
5421 // We need to check mouse button states and put them in for
5422 // wParam.
5423 WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
5424 (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
5425 (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
5426 // Synthesize an event position because we don't get one from
5427 // WM_MOUSELEAVE.
5428 LPARAM pos = lParamToClient(::GetMessagePos());
5429 DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
5430 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5431 } break;
5433 case WM_CONTEXTMENU: {
5434 // If the context menu is brought up by a touch long-press, then
5435 // the APZ code is responsible for dealing with this, so we don't
5436 // need to do anything.
5437 if (mTouchWindow &&
5438 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
5439 MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
5440 result = true;
5441 break;
5444 // If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
5445 // event in overscroll gutter, we shouldn't open context menu.
5446 if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
5447 mNeedsToPreventContextMenu) {
5448 result = true;
5449 break;
5452 // if the context menu is brought up from the keyboard, |lParam|
5453 // will be -1.
5454 LPARAM pos;
5455 bool contextMenukey = false;
5456 if (lParam == -1) {
5457 contextMenukey = true;
5458 pos = lParamToClient(GetMessagePos());
5459 } else {
5460 pos = lParamToClient(lParam);
5463 result = DispatchMouseEvent(
5464 eContextMenu, wParam, pos, contextMenukey,
5465 contextMenukey ? MouseButton::ePrimary : MouseButton::eSecondary,
5466 MOUSE_INPUT_SOURCE());
5467 if (lParam != -1 && !result && mCustomNonClient &&
5468 mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
5469 // Blank area hit, throw up the system menu.
5470 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5471 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5472 result = true;
5474 } break;
5476 case WM_POINTERLEAVE:
5477 case WM_POINTERDOWN:
5478 case WM_POINTERUP:
5479 case WM_POINTERUPDATE:
5480 result = OnPointerEvents(msg, wParam, lParam);
5481 if (result) {
5482 DispatchPendingEvents();
5484 break;
5486 case DM_POINTERHITTEST:
5487 if (mDmOwner) {
5488 UINT contactId = GET_POINTERID_WPARAM(wParam);
5489 POINTER_INPUT_TYPE pointerType;
5490 if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
5491 pointerType == PT_TOUCHPAD) {
5492 mDmOwner->SetContact(contactId);
5495 break;
5497 case WM_LBUTTONDBLCLK:
5498 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5499 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5500 DispatchPendingEvents();
5501 break;
5503 case WM_MBUTTONDOWN:
5504 result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5505 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5506 DispatchPendingEvents();
5507 break;
5509 case WM_MBUTTONUP:
5510 result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5511 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5512 DispatchPendingEvents();
5513 break;
5515 case WM_MBUTTONDBLCLK:
5516 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5517 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5518 DispatchPendingEvents();
5519 break;
5521 case WM_NCMBUTTONDOWN:
5522 result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5523 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5524 DispatchPendingEvents();
5525 break;
5527 case WM_NCMBUTTONUP:
5528 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5529 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5530 DispatchPendingEvents();
5531 break;
5533 case WM_NCMBUTTONDBLCLK:
5534 result =
5535 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5536 false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5537 DispatchPendingEvents();
5538 break;
5540 case WM_RBUTTONDOWN:
5541 result =
5542 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5543 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5544 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5545 DispatchPendingEvents();
5546 break;
5548 case WM_RBUTTONUP:
5549 result =
5550 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5551 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5552 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5553 DispatchPendingEvents();
5554 break;
5556 case WM_RBUTTONDBLCLK:
5557 result =
5558 DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5559 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5560 DispatchPendingEvents();
5561 break;
5563 case WM_NCRBUTTONDOWN:
5564 result =
5565 DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5566 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5567 DispatchPendingEvents();
5568 break;
5570 case WM_NCRBUTTONUP:
5571 result =
5572 DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5573 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5574 DispatchPendingEvents();
5575 break;
5577 case WM_NCRBUTTONDBLCLK:
5578 result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5579 false, MouseButton::eSecondary,
5580 MOUSE_INPUT_SOURCE());
5581 DispatchPendingEvents();
5582 break;
5584 // Windows doesn't provide to customize the behavior of 4th nor 5th button
5585 // of mouse. If 5-button mouse works with standard mouse deriver of
5586 // Windows, users cannot disable 4th button (browser back) nor 5th button
5587 // (browser forward). We should allow to do it with our prefs since we can
5588 // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
5589 // messages are not sent to DefWindowProc.
5590 case WM_XBUTTONDOWN:
5591 case WM_XBUTTONUP:
5592 case WM_NCXBUTTONDOWN:
5593 case WM_NCXBUTTONUP:
5594 *aRetValue = TRUE;
5595 switch (GET_XBUTTON_WPARAM(wParam)) {
5596 case XBUTTON1:
5597 result = !Preferences::GetBool("mousebutton.4th.enabled", true);
5598 break;
5599 case XBUTTON2:
5600 result = !Preferences::GetBool("mousebutton.5th.enabled", true);
5601 break;
5602 default:
5603 break;
5605 break;
5607 case WM_SIZING: {
5608 if (mAspectRatio > 0) {
5609 LPRECT rect = (LPRECT)lParam;
5610 int32_t newWidth, newHeight;
5612 // The following conditions and switch statement borrow heavily from the
5613 // Chromium source code from
5614 // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
5615 if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
5616 wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
5617 newWidth = rect->right - rect->left;
5618 newHeight = newWidth / mAspectRatio;
5619 if (newHeight < mSizeConstraints.mMinSize.height) {
5620 newHeight = mSizeConstraints.mMinSize.height;
5621 newWidth = newHeight * mAspectRatio;
5622 } else if (newHeight > mSizeConstraints.mMaxSize.height) {
5623 newHeight = mSizeConstraints.mMaxSize.height;
5624 newWidth = newHeight * mAspectRatio;
5626 } else {
5627 newHeight = rect->bottom - rect->top;
5628 newWidth = newHeight * mAspectRatio;
5629 if (newWidth < mSizeConstraints.mMinSize.width) {
5630 newWidth = mSizeConstraints.mMinSize.width;
5631 newHeight = newWidth / mAspectRatio;
5632 } else if (newWidth > mSizeConstraints.mMaxSize.width) {
5633 newWidth = mSizeConstraints.mMaxSize.width;
5634 newHeight = newWidth / mAspectRatio;
5638 switch (wParam) {
5639 case WMSZ_RIGHT:
5640 case WMSZ_BOTTOM:
5641 rect->right = newWidth + rect->left;
5642 rect->bottom = rect->top + newHeight;
5643 break;
5644 case WMSZ_TOP:
5645 rect->right = newWidth + rect->left;
5646 rect->top = rect->bottom - newHeight;
5647 break;
5648 case WMSZ_LEFT:
5649 case WMSZ_TOPLEFT:
5650 rect->left = rect->right - newWidth;
5651 rect->top = rect->bottom - newHeight;
5652 break;
5653 case WMSZ_TOPRIGHT:
5654 rect->right = rect->left + newWidth;
5655 rect->top = rect->bottom - newHeight;
5656 break;
5657 case WMSZ_BOTTOMLEFT:
5658 rect->left = rect->right - newWidth;
5659 rect->bottom = rect->top + newHeight;
5660 break;
5661 case WMSZ_BOTTOMRIGHT:
5662 rect->right = rect->left + newWidth;
5663 rect->bottom = rect->top + newHeight;
5664 break;
5668 // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
5669 // resize or move event. Instead we wait for first VM_SIZING message
5670 // within a ENTERSIZEMOVE to consider this a live resize event.
5671 if (mResizeState == IN_SIZEMOVE) {
5672 mResizeState = RESIZING;
5673 NotifyLiveResizeStarted();
5675 break;
5678 case WM_MOVING:
5679 FinishLiveResizing(MOVING);
5680 if (WinUtils::IsPerMonitorDPIAware()) {
5681 // Sometimes, we appear to miss a WM_DPICHANGED message while moving
5682 // a window around. Therefore, call ChangedDPI and ResetLayout here
5683 // if it appears that the window's scaling is not what we expect.
5684 // This causes the prescontext and appshell window management code to
5685 // check the appUnitsPerDevPixel value and current widget size, and
5686 // refresh them if necessary. If nothing has changed, these calls will
5687 // return without actually triggering any extra reflow or painting.
5688 if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
5689 ChangedDPI();
5690 ResetLayout();
5691 if (mWidgetListener) {
5692 mWidgetListener->UIResolutionChanged();
5696 break;
5698 case WM_ENTERSIZEMOVE: {
5699 if (mResizeState == NOT_RESIZING) {
5700 mResizeState = IN_SIZEMOVE;
5702 break;
5705 case WM_EXITSIZEMOVE: {
5706 FinishLiveResizing(NOT_RESIZING);
5708 if (!sIsInMouseCapture) {
5709 NotifySizeMoveDone();
5712 // Windows spins a separate hidden event loop when moving a window so we
5713 // don't hear mouse events during this time and WM_EXITSIZEMOVE is fired
5714 // when the hidden event loop exits. We set mDraggingWindowWithMouse to
5715 // true in WM_NCLBUTTONDOWN when we started moving the window with the
5716 // mouse so we know that if mDraggingWindowWithMouse is true, we can send
5717 // a mouse up event.
5718 if (mDraggingWindowWithMouse) {
5719 mDraggingWindowWithMouse = false;
5720 result = DispatchMouseEvent(
5721 eMouseUp, wParam, lParam, false, MouseButton::ePrimary,
5722 MOUSE_INPUT_SOURCE(),
5723 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5726 break;
5729 case WM_DISPLAYCHANGE: {
5730 ScreenHelperWin::RefreshScreens();
5731 if (mWidgetListener) {
5732 mWidgetListener->UIResolutionChanged();
5734 break;
5737 case WM_NCLBUTTONDBLCLK:
5738 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
5739 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5740 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5741 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5742 DispatchPendingEvents();
5743 break;
5745 case WM_NCLBUTTONDOWN: {
5746 // Dispatch a custom event when this happens in the draggable region, so
5747 // that non-popup-based panels can react to it. This doesn't send an
5748 // actual mousedown event because that would break dragging or interfere
5749 // with other mousedown handling in the caption area.
5750 if (ClientMarginHitTestPoint(GET_X_LPARAM(lParam),
5751 GET_Y_LPARAM(lParam)) == HTCAPTION) {
5752 DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
5753 mDraggingWindowWithMouse = true;
5756 if (IsWindowButton(wParam) && mCustomNonClient) {
5757 DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
5758 lParamToClient(lParam), false, MouseButton::ePrimary,
5759 MOUSE_INPUT_SOURCE(), nullptr, true);
5760 DispatchPendingEvents();
5761 result = true;
5763 break;
5766 case WM_APPCOMMAND: {
5767 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5768 result = HandleAppCommandMsg(nativeMsg, aRetValue);
5769 break;
5772 // The WM_ACTIVATE event is fired when a window is raised or lowered,
5773 // and the loword of wParam specifies which. But we don't want to tell
5774 // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
5775 // events are fired. Instead, set either the sJustGotActivate or
5776 // gJustGotDeactivate flags and activate/deactivate once the focus
5777 // events arrive.
5778 case WM_ACTIVATE: {
5779 int32_t fActive = LOWORD(wParam);
5780 if (mWidgetListener) {
5781 if (WA_INACTIVE == fActive) {
5782 // when minimizing a window, the deactivation and focus events will
5783 // be fired in the reverse order. Instead, just deactivate right away.
5784 // This can also happen when a modal system dialog is opened, so check
5785 // if the last window to receive the WM_KILLFOCUS message was this one
5786 // or a child of this one.
5787 if (HIWORD(wParam) ||
5788 (mLastKillFocusWindow &&
5789 (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
5790 DispatchFocusToTopLevelWindow(false);
5791 } else {
5792 sJustGotDeactivate = true;
5794 if (mIsTopWidgetWindow) {
5795 mLastKeyboardLayout = KeyboardLayout::GetLayout();
5797 } else {
5798 StopFlashing();
5800 sJustGotActivate = true;
5801 WidgetMouseEvent event(true, eMouseActivate, this,
5802 WidgetMouseEvent::eReal);
5803 InitEvent(event);
5804 ModifierKeyState modifierKeyState;
5805 modifierKeyState.InitInputEvent(event);
5806 DispatchInputEvent(&event);
5807 if (sSwitchKeyboardLayout && mLastKeyboardLayout)
5808 ActivateKeyboardLayout(mLastKeyboardLayout, 0);
5810 #ifdef ACCESSIBILITY
5811 a11y::LazyInstantiator::ResetUiaDetectionCache();
5812 #endif
5815 } break;
5817 case WM_ACTIVATEAPP: {
5818 // Bug 1851991: Sometimes this can be called before gfxPlatform::Init
5819 // when a window is created very early. In that case we just forego
5820 // setting this and accept the GPU process might briefly run at a lower
5821 // priority.
5822 if (GPUProcessManager::Get()) {
5823 GPUProcessManager::Get()->SetAppInForeground(wParam);
5825 } break;
5827 case WM_MOUSEACTIVATE:
5828 // A popup with a parent owner should not be activated when clicked but
5829 // should still allow the mouse event to be fired, so the return value
5830 // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
5831 // just use default processing so that the window is activated.
5832 if (IsPopup() && IsOwnerForegroundWindow()) {
5833 *aRetValue = MA_NOACTIVATE;
5834 result = true;
5836 break;
5838 case WM_WINDOWPOSCHANGING: {
5839 LPWINDOWPOS info = (LPWINDOWPOS)lParam;
5840 OnWindowPosChanging(info);
5841 result = true;
5842 } break;
5844 // Workaround for race condition in explorer.exe.
5845 case MOZ_WM_FULLSCREEN_STATE_UPDATE: {
5846 TaskbarConcealer::OnAsyncStateUpdateRequest(mWnd);
5847 result = true;
5848 } break;
5850 case WM_GETMINMAXINFO: {
5851 MINMAXINFO* mmi = (MINMAXINFO*)lParam;
5852 // Set the constraints. The minimum size should also be constrained to the
5853 // default window maximum size so that it fits on screen.
5854 mmi->ptMinTrackSize.x =
5855 std::min((int32_t)mmi->ptMaxTrackSize.x,
5856 std::max((int32_t)mmi->ptMinTrackSize.x,
5857 mSizeConstraints.mMinSize.width));
5858 mmi->ptMinTrackSize.y =
5859 std::min((int32_t)mmi->ptMaxTrackSize.y,
5860 std::max((int32_t)mmi->ptMinTrackSize.y,
5861 mSizeConstraints.mMinSize.height));
5862 mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
5863 mSizeConstraints.mMaxSize.width);
5864 mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
5865 mSizeConstraints.mMaxSize.height);
5866 } break;
5868 case WM_SETFOCUS: {
5869 WndProcUrgentInvocation::Marker _marker;
5871 // If previous focused window isn't ours, it must have received the
5872 // redirected message. So, we should forget it.
5873 if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
5874 RedirectedKeyDownMessageManager::Forget();
5876 if (sJustGotActivate) {
5877 DispatchFocusToTopLevelWindow(true);
5879 TaskbarConcealer::OnFocusAcquired(this);
5880 } break;
5882 case WM_KILLFOCUS:
5883 if (sJustGotDeactivate) {
5884 DispatchFocusToTopLevelWindow(false);
5885 } else {
5886 mLastKillFocusWindow = mWnd;
5888 break;
5890 case WM_WINDOWPOSCHANGED: {
5891 WINDOWPOS* wp = (LPWINDOWPOS)lParam;
5892 OnWindowPosChanged(wp);
5893 TaskbarConcealer::OnWindowPosChanged(this);
5894 result = true;
5895 } break;
5897 case WM_INPUTLANGCHANGEREQUEST:
5898 *aRetValue = TRUE;
5899 result = false;
5900 break;
5902 case WM_INPUTLANGCHANGE:
5903 KeyboardLayout::GetInstance()->OnLayoutChange(
5904 reinterpret_cast<HKL>(lParam));
5905 nsBidiKeyboard::OnLayoutChange();
5906 result = false; // always pass to child window
5907 break;
5909 case WM_DESTROYCLIPBOARD: {
5910 nsIClipboard* clipboard;
5911 nsresult rv = CallGetService(kCClipboardCID, &clipboard);
5912 if (NS_SUCCEEDED(rv)) {
5913 clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
5914 NS_RELEASE(clipboard);
5916 } break;
5918 #ifdef ACCESSIBILITY
5919 case WM_GETOBJECT: {
5920 *aRetValue = 0;
5921 // Do explicit casting to make it working on 64bit systems (see bug 649236
5922 // for details).
5923 int32_t objId = static_cast<DWORD>(lParam);
5924 if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
5925 RefPtr<IAccessible> root(
5926 a11y::LazyInstantiator::GetRootAccessible(mWnd));
5927 if (root) {
5928 *aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
5929 a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
5930 result = true;
5932 } else if (objId == UiaRootObjectId &&
5933 StaticPrefs::accessibility_uia_enable()) {
5934 if (a11y::LocalAccessible* acc = GetAccessible()) {
5935 RefPtr<IAccessible> ia;
5936 acc->GetNativeInterface(getter_AddRefs(ia));
5937 MOZ_ASSERT(ia);
5938 RefPtr<IRawElementProviderSimple> uia;
5939 ia->QueryInterface(IID_IRawElementProviderSimple,
5940 getter_AddRefs(uia));
5941 if (uia) {
5942 *aRetValue = UiaReturnRawElementProvider(mWnd, wParam, lParam, uia);
5943 result = true;
5947 } break;
5948 #endif
5950 case WM_SYSCOMMAND: {
5951 WPARAM const filteredWParam = (wParam & 0xFFF0);
5953 // SC_CLOSE may trigger a synchronous confirmation prompt. If we're in the
5954 // middle of something important, put off responding to it.
5955 if (filteredWParam == SC_CLOSE && WndProcUrgentInvocation::IsActive()) {
5956 ::PostMessageW(mWnd, msg, wParam, lParam);
5957 result = true;
5958 break;
5961 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
5962 filteredWParam == SC_RESTORE &&
5963 GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
5964 mFrameState->EnsureFullscreenMode(false);
5965 result = true;
5968 // Handle the system menu manually when we're in full screen mode
5969 // so we can set the appropriate options.
5970 if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE &&
5971 mFrameState->GetSizeMode() == nsSizeMode_Fullscreen) {
5972 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5973 MOZ_SYSCONTEXT_X_POS, MOZ_SYSCONTEXT_Y_POS);
5974 result = true;
5976 } break;
5978 case WM_DPICHANGED: {
5979 LPRECT rect = (LPRECT)lParam;
5980 OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
5981 rect->bottom - rect->top);
5982 break;
5985 /* Gesture support events */
5986 case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
5987 // According to MS samples, this must be handled to enable
5988 // rotational support in multi-touch drivers.
5989 result = true;
5990 *aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
5991 break;
5993 case WM_TOUCH:
5994 result = OnTouch(wParam, lParam);
5995 if (result) {
5996 *aRetValue = 0;
5998 break;
6000 case WM_GESTURE:
6001 result = OnGesture(wParam, lParam);
6002 break;
6004 case WM_GESTURENOTIFY: {
6005 if (mWindowType != WindowType::Invisible) {
6006 // A GestureNotify event is dispatched to decide which single-finger
6007 // panning direction should be active (including none) and if pan
6008 // feedback should be displayed. Java and plugin windows can make their
6009 // own calls.
6011 GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
6012 nsPointWin touchPoint;
6013 touchPoint = gestureinfo->ptsLocation;
6014 touchPoint.ScreenToClient(mWnd);
6015 WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
6016 gestureNotifyEvent.mRefPoint =
6017 LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
6018 nsEventStatus status;
6019 DispatchEvent(&gestureNotifyEvent, status);
6020 mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
6021 if (!mTouchWindow)
6022 mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
6024 result = false; // should always bubble to DefWindowProc
6025 } break;
6027 case WM_CLEAR: {
6028 WidgetContentCommandEvent command(true, eContentCommandDelete, this);
6029 DispatchWindowEvent(command);
6030 result = true;
6031 } break;
6033 case WM_CUT: {
6034 WidgetContentCommandEvent command(true, eContentCommandCut, this);
6035 DispatchWindowEvent(command);
6036 result = true;
6037 } break;
6039 case WM_COPY: {
6040 WidgetContentCommandEvent command(true, eContentCommandCopy, this);
6041 DispatchWindowEvent(command);
6042 result = true;
6043 } break;
6045 case WM_PASTE: {
6046 WidgetContentCommandEvent command(true, eContentCommandPaste, this);
6047 DispatchWindowEvent(command);
6048 result = true;
6049 } break;
6051 case EM_UNDO: {
6052 WidgetContentCommandEvent command(true, eContentCommandUndo, this);
6053 DispatchWindowEvent(command);
6054 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6055 result = true;
6056 } break;
6058 case EM_REDO: {
6059 WidgetContentCommandEvent command(true, eContentCommandRedo, this);
6060 DispatchWindowEvent(command);
6061 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6062 result = true;
6063 } break;
6065 case EM_CANPASTE: {
6066 // Support EM_CANPASTE message only when wParam isn't specified or
6067 // is plain text format.
6068 if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
6069 WidgetContentCommandEvent command(true, eContentCommandPaste, this,
6070 true);
6071 DispatchWindowEvent(command);
6072 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6073 result = true;
6075 } break;
6077 case EM_CANUNDO: {
6078 WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
6079 DispatchWindowEvent(command);
6080 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6081 result = true;
6082 } break;
6084 case EM_CANREDO: {
6085 WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
6086 DispatchWindowEvent(command);
6087 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6088 result = true;
6089 } break;
6091 case MOZ_WM_SKEWFIX: {
6092 TimeStamp skewStamp;
6093 if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
6094 &skewStamp)) {
6095 TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
6096 skewStamp);
6098 } break;
6100 default: {
6101 if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
6102 SetHasTaskbarIconBeenCreated();
6104 } break;
6107 //*aRetValue = result;
6108 if (mWnd) {
6109 return result;
6110 } else {
6111 // Events which caused mWnd destruction and aren't consumed
6112 // will crash during the Windows default processing.
6113 return true;
6117 void nsWindow::FinishLiveResizing(ResizeState aNewState) {
6118 if (mResizeState == RESIZING) {
6119 NotifyLiveResizeStopped();
6121 mResizeState = aNewState;
6122 ForcePresent();
6125 /**************************************************************
6127 * SECTION: Event processing helpers
6129 * Special processing for certain event types and
6130 * synthesized events.
6132 **************************************************************/
6134 LayoutDeviceIntMargin nsWindow::NonClientSizeMargin(
6135 const LayoutDeviceIntMargin& aNonClientOffset) const {
6136 return LayoutDeviceIntMargin(mCaptionHeight - aNonClientOffset.top,
6137 mHorResizeMargin - aNonClientOffset.right,
6138 mVertResizeMargin - aNonClientOffset.bottom,
6139 mHorResizeMargin - aNonClientOffset.left);
6142 int32_t nsWindow::ClientMarginHitTestPoint(int32_t aX, int32_t aY) {
6143 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
6144 if (sizeMode == nsSizeMode_Minimized || sizeMode == nsSizeMode_Fullscreen) {
6145 return HTCLIENT;
6148 // Calculations are done in screen coords
6149 const LayoutDeviceIntRect winRect = GetScreenBounds();
6150 const LayoutDeviceIntPoint point(aX, aY);
6152 // hit return constants:
6153 // HTBORDER - non-resizable border
6154 // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
6155 // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
6156 // HTTOPLEFT, HTTOPRIGHT - resizable corner
6157 // HTCAPTION - general title bar area
6158 // HTCLIENT - area considered the client
6159 // HTCLOSE - hovering over the close button
6160 // HTMAXBUTTON - maximize button
6161 // HTMINBUTTON - minimize button
6163 int32_t testResult = HTCLIENT;
6164 const bool isResizable =
6165 sizeMode != nsSizeMode_Maximized &&
6166 (mBorderStyle &
6167 (BorderStyle::All | BorderStyle::ResizeH | BorderStyle::Default));
6169 LayoutDeviceIntMargin nonClientSizeMargin = NonClientSizeMargin();
6171 // Ensure being accessible to borders of window. Even if contents are in
6172 // this area, the area must behave as border.
6173 nonClientSizeMargin.EnsureAtLeast(
6174 LayoutDeviceIntMargin(kResizableBorderMinSize, kResizableBorderMinSize,
6175 kResizableBorderMinSize, kResizableBorderMinSize));
6177 LayoutDeviceIntRect clientRect = winRect;
6178 clientRect.Deflate(nonClientSizeMargin);
6180 const bool allowContentOverride =
6181 sizeMode == nsSizeMode_Maximized || clientRect.Contains(point);
6183 // The border size. If there is no content under mouse cursor, the border
6184 // size should be larger than the values in system settings. Otherwise,
6185 // contents under the mouse cursor should be able to override the behavior.
6186 // E.g., user must expect that Firefox button always opens the popup menu
6187 // even when the user clicks on the above edge of it.
6188 LayoutDeviceIntMargin borderSize = nonClientSizeMargin;
6189 borderSize.EnsureAtLeast(
6190 LayoutDeviceIntMargin(mVertResizeMargin, mHorResizeMargin,
6191 mVertResizeMargin, mHorResizeMargin));
6193 bool top = false;
6194 bool bottom = false;
6195 bool left = false;
6196 bool right = false;
6198 if (point.y >= winRect.y && point.y < winRect.y + borderSize.top) {
6199 top = true;
6200 } else if (point.y <= winRect.YMost() &&
6201 point.y > winRect.YMost() - borderSize.bottom) {
6202 bottom = true;
6205 // (the 2x case here doubles the resize area for corners)
6206 int multiplier = (top || bottom) ? 2 : 1;
6207 if (point.x >= winRect.x &&
6208 point.x < winRect.x + (multiplier * borderSize.left)) {
6209 left = true;
6210 } else if (point.x <= winRect.XMost() &&
6211 point.x > winRect.XMost() - (multiplier * borderSize.right)) {
6212 right = true;
6215 bool inResizeRegion = false;
6216 if (isResizable) {
6217 if (top) {
6218 testResult = HTTOP;
6219 if (left) {
6220 testResult = HTTOPLEFT;
6221 } else if (right) {
6222 testResult = HTTOPRIGHT;
6224 } else if (bottom) {
6225 testResult = HTBOTTOM;
6226 if (left) {
6227 testResult = HTBOTTOMLEFT;
6228 } else if (right) {
6229 testResult = HTBOTTOMRIGHT;
6231 } else {
6232 if (left) {
6233 testResult = HTLEFT;
6235 if (right) {
6236 testResult = HTRIGHT;
6239 inResizeRegion = (testResult != HTCLIENT);
6240 } else {
6241 if (top) {
6242 testResult = HTCAPTION;
6243 } else if (bottom || left || right) {
6244 testResult = HTBORDER;
6248 if (!sIsInMouseCapture && allowContentOverride) {
6250 POINT pt = {aX, aY};
6251 ::ScreenToClient(mWnd, &pt);
6253 if (pt.x == mCachedHitTestPoint.x.value &&
6254 pt.y == mCachedHitTestPoint.y.value &&
6255 TimeStamp::Now() - mCachedHitTestTime <
6256 TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
6257 return mCachedHitTestResult;
6260 mCachedHitTestPoint = {pt.x, pt.y};
6261 mCachedHitTestTime = TimeStamp::Now();
6264 auto pt = mCachedHitTestPoint;
6266 if (mWindowBtnRect[WindowButtonType::Minimize].Contains(pt)) {
6267 testResult = HTMINBUTTON;
6268 } else if (mWindowBtnRect[WindowButtonType::Maximize].Contains(pt)) {
6269 #ifdef ACCESSIBILITY
6270 a11y::Compatibility::SuppressA11yForSnapLayouts();
6271 #endif
6272 testResult = HTMAXBUTTON;
6273 } else if (mWindowBtnRect[WindowButtonType::Close].Contains(pt)) {
6274 testResult = HTCLOSE;
6275 } else if (!inResizeRegion) {
6276 // If we're in the resize region, avoid overriding that with either a
6277 // drag or a client result; resize takes priority over either (but not
6278 // over the window controls, which is why we check this after those).
6279 if (mDraggableRegion.Contains(pt)) {
6280 testResult = HTCAPTION;
6281 } else {
6282 testResult = HTCLIENT;
6286 mCachedHitTestResult = testResult;
6289 return testResult;
6292 bool nsWindow::IsSimulatedClientArea(int32_t screenX, int32_t screenY) {
6293 int32_t testResult = ClientMarginHitTestPoint(screenX, screenY);
6294 return testResult == HTCAPTION || IsWindowButton(testResult);
6297 bool nsWindow::IsWindowButton(int32_t hitTestResult) {
6298 return hitTestResult == HTMINBUTTON || hitTestResult == HTMAXBUTTON ||
6299 hitTestResult == HTCLOSE;
6302 TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
6303 CurrentWindowsTimeGetter getCurrentTime(mWnd);
6304 return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
6307 void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
6308 // Retain the previous mode that was notified to observers
6309 static bool sWasSleepMode = false;
6311 // Only notify observers if mode changed
6312 if (aIsSleepMode == sWasSleepMode) return;
6314 sWasSleepMode = aIsSleepMode;
6316 nsCOMPtr<nsIObserverService> observerService =
6317 mozilla::services::GetObserverService();
6318 if (observerService)
6319 observerService->NotifyObservers(nullptr,
6320 aIsSleepMode
6321 ? NS_WIDGET_SLEEP_OBSERVER_TOPIC
6322 : NS_WIDGET_WAKE_OBSERVER_TOPIC,
6323 nullptr);
6326 LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
6327 if (IMEHandler::IsComposingOn(this)) {
6328 IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
6330 // These must be checked here too as a lone WM_CHAR could be received
6331 // if a child window didn't handle it (for example Alt+Space in a content
6332 // window)
6333 ModifierKeyState modKeyState;
6334 NativeKey nativeKey(this, aMsg, modKeyState);
6335 return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
6338 LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
6339 ModifierKeyState modKeyState;
6340 NativeKey nativeKey(this, aMsg, modKeyState);
6341 bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
6342 if (aMsg.wParam == VK_F10) {
6343 // Bug 1382199: Windows default behavior will trigger the System menu bar
6344 // when F10 is released. Among other things, this causes the System menu bar
6345 // to appear when a web page overrides the contextmenu event. We *never*
6346 // want this default behavior, so eat this key (never pass it to Windows).
6347 return true;
6349 return result;
6352 LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
6353 bool* aEventDispatched) {
6354 // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
6355 // must clean up the redirected message information itself. For more
6356 // information, see above comment of
6357 // RedirectedKeyDownMessageManager::AutoFlusher class definition in
6358 // KeyboardLayout.h.
6359 RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
6361 ModifierKeyState modKeyState;
6363 NativeKey nativeKey(this, aMsg, modKeyState);
6364 LRESULT result =
6365 static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
6366 // HandleKeyDownMessage cleaned up the redirected message information
6367 // itself, so, we should do nothing.
6368 redirectedMsgFlusher.Cancel();
6370 if (aMsg.wParam == VK_MENU ||
6371 (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
6372 // We need to let Windows handle this keypress,
6373 // by returning false, if there's a native menu
6374 // bar somewhere in our containing window hierarchy.
6375 // Otherwise we handle the keypress and don't pass
6376 // it on to Windows, by returning true.
6377 bool hasNativeMenu = false;
6378 HWND hWnd = mWnd;
6379 while (hWnd) {
6380 if (::GetMenu(hWnd)) {
6381 hasNativeMenu = true;
6382 break;
6384 hWnd = ::GetParent(hWnd);
6386 result = !hasNativeMenu;
6389 return result;
6392 nsresult nsWindow::SynthesizeNativeKeyEvent(
6393 int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
6394 uint32_t aModifierFlags, const nsAString& aCharacters,
6395 const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
6396 AutoObserverNotifier notifier(aObserver, "keyevent");
6398 KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
6399 return keyboardLayout->SynthesizeNativeKeyEvent(
6400 this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
6401 aUnmodifiedCharacters);
6404 nsresult nsWindow::SynthesizeNativeMouseEvent(
6405 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
6406 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
6407 nsIObserver* aObserver) {
6408 AutoObserverNotifier notifier(aObserver, "mouseevent");
6410 INPUT input;
6411 memset(&input, 0, sizeof(input));
6413 // TODO (bug 1693240):
6414 // Now, we synthesize native mouse events asynchronously since we want to
6415 // synthesize the event on the front window at the point. However, Windows
6416 // does not provide a way to set modifier only while a mouse message is
6417 // being handled, and MOUSEEVENTF_MOVE may be coalesced by Windows. So, we
6418 // need a trick for handling it.
6420 switch (aNativeMessage) {
6421 case NativeMouseMessage::Move:
6422 input.mi.dwFlags = MOUSEEVENTF_MOVE;
6423 // Reset sLastMouseMovePoint so that even if we're moving the mouse
6424 // to the position it's already at, we still dispatch a mousemove
6425 // event, because the callers of this function expect that.
6426 sLastMouseMovePoint = {0};
6427 break;
6428 case NativeMouseMessage::ButtonDown:
6429 case NativeMouseMessage::ButtonUp: {
6430 const bool isDown = aNativeMessage == NativeMouseMessage::ButtonDown;
6431 switch (aButton) {
6432 case MouseButton::ePrimary:
6433 input.mi.dwFlags = isDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
6434 break;
6435 case MouseButton::eMiddle:
6436 input.mi.dwFlags =
6437 isDown ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
6438 break;
6439 case MouseButton::eSecondary:
6440 input.mi.dwFlags =
6441 isDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
6442 break;
6443 case MouseButton::eX1:
6444 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6445 input.mi.mouseData = XBUTTON1;
6446 break;
6447 case MouseButton::eX2:
6448 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6449 input.mi.mouseData = XBUTTON2;
6450 break;
6451 default:
6452 return NS_ERROR_INVALID_ARG;
6454 break;
6456 case NativeMouseMessage::EnterWindow:
6457 case NativeMouseMessage::LeaveWindow:
6458 MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Windows");
6459 return NS_ERROR_INVALID_ARG;
6462 input.type = INPUT_MOUSE;
6463 ::SetCursorPos(aPoint.x, aPoint.y);
6464 ::SendInput(1, &input, sizeof(INPUT));
6466 return NS_OK;
6469 nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
6470 LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
6471 double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
6472 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
6473 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
6474 return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
6475 this, aPoint, aNativeMessage,
6476 (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
6477 ? static_cast<int32_t>(aDeltaY)
6478 : static_cast<int32_t>(aDeltaX),
6479 aModifierFlags, aAdditionalFlags);
6482 nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
6483 LayoutDeviceIntPoint aPoint,
6484 double aDeltaX, double aDeltaY,
6485 int32_t aModifierFlags,
6486 nsIObserver* aObserver) {
6487 AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
6488 DirectManipulationOwner::SynthesizeNativeTouchpadPan(
6489 this, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
6490 return NS_OK;
6493 static void MaybeLogPosChanged(HWND aWnd, WINDOWPOS* wp) {
6494 #ifdef WINSTATE_DEBUG_OUTPUT
6495 if (aWnd == WinUtils::GetTopLevelHWND(aWnd)) {
6496 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
6497 } else {
6498 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
6500 MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
6501 if (wp->flags & SWP_FRAMECHANGED) {
6502 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
6504 if (wp->flags & SWP_SHOWWINDOW) {
6505 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
6507 if (wp->flags & SWP_NOSIZE) {
6508 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
6510 if (wp->flags & SWP_HIDEWINDOW) {
6511 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
6513 if (wp->flags & SWP_NOZORDER) {
6514 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
6516 if (wp->flags & SWP_NOACTIVATE) {
6517 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
6519 MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
6520 #endif
6523 /**************************************************************
6525 * SECTION: OnXXX message handlers
6527 * For message handlers that need to be broken out or
6528 * implemented in specific platform code.
6530 **************************************************************/
6532 void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
6533 if (!wp) {
6534 return;
6537 MaybeLogPosChanged(mWnd, wp);
6539 // Handle window size mode changes
6540 if (wp->flags & SWP_FRAMECHANGED) {
6541 // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
6542 // windows when fullscreen games disable desktop composition. If we're
6543 // minimized and not being activated, ignore the event and let windows
6544 // handle it.
6545 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized &&
6546 (wp->flags & SWP_NOACTIVATE)) {
6547 return;
6550 mFrameState->OnFrameChanged();
6552 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
6553 // Skip window size change events below on minimization.
6554 return;
6558 // Notify visibility change when window is activated.
6559 if (!(wp->flags & SWP_NOACTIVATE) && NeedsToTrackWindowOcclusionState()) {
6560 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
6561 this, mFrameState->GetSizeMode() != nsSizeMode_Minimized);
6564 // Handle window position changes
6565 if (!(wp->flags & SWP_NOMOVE)) {
6566 mBounds.MoveTo(wp->x, wp->y);
6567 NotifyWindowMoved(wp->x, wp->y);
6570 // Handle window size changes
6571 if (!(wp->flags & SWP_NOSIZE)) {
6572 RECT r;
6573 int32_t newWidth, newHeight;
6575 ::GetWindowRect(mWnd, &r);
6577 newWidth = r.right - r.left;
6578 newHeight = r.bottom - r.top;
6580 if (newWidth > mLastSize.width) {
6581 RECT drect;
6583 // getting wider
6584 drect.left = wp->x + mLastSize.width;
6585 drect.top = wp->y;
6586 drect.right = drect.left + (newWidth - mLastSize.width);
6587 drect.bottom = drect.top + newHeight;
6589 ::RedrawWindow(mWnd, &drect, nullptr,
6590 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6591 RDW_ERASENOW | RDW_ALLCHILDREN);
6593 if (newHeight > mLastSize.height) {
6594 RECT drect;
6596 // getting taller
6597 drect.left = wp->x;
6598 drect.top = wp->y + mLastSize.height;
6599 drect.right = drect.left + newWidth;
6600 drect.bottom = drect.top + (newHeight - mLastSize.height);
6602 ::RedrawWindow(mWnd, &drect, nullptr,
6603 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6604 RDW_ERASENOW | RDW_ALLCHILDREN);
6607 mBounds.SizeTo(newWidth, newHeight);
6608 mLastSize.width = newWidth;
6609 mLastSize.height = newHeight;
6611 #ifdef WINSTATE_DEBUG_OUTPUT
6612 MOZ_LOG(gWindowsLog, LogLevel::Info,
6613 ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
6614 newHeight));
6615 #endif
6617 if (mAspectRatio > 0) {
6618 // It's possible (via Windows Aero Snap) that the size of the window
6619 // has changed such that it violates the aspect ratio constraint. If so,
6620 // queue up an event to enforce the aspect ratio constraint and repaint.
6621 // When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
6622 float newAspectRatio = (float)newWidth / newHeight;
6623 if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
6624 // Hold a reference to self alive and pass it into the lambda to make
6625 // sure this nsIWidget stays alive long enough to run this function.
6626 nsCOMPtr<nsIWidget> self(this);
6627 NS_DispatchToMainThread(NS_NewRunnableFunction(
6628 "EnforceAspectRatio", [self, this, newWidth]() -> void {
6629 if (mWnd) {
6630 Resize(newWidth, newWidth / mAspectRatio, true);
6632 }));
6636 // If a maximized window is resized, recalculate the non-client margins.
6637 if (mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
6638 if (UpdateNonClientMargins(true)) {
6639 // gecko resize event already sent by UpdateNonClientMargins.
6640 return;
6645 // Notify the widget listener for size change of client area for gecko
6646 // events. This needs to be done when either window size is changed,
6647 // or window frame is changed. They may not happen together.
6648 // However, we don't invoke that for popup when window frame changes,
6649 // because popups may trigger frame change before size change via
6650 // {Set,Clear}ThemeRegion they invoke in Resize. That would make the
6651 // code below call OnResize with a wrong client size first, which can
6652 // lead to flickerling for some popups.
6653 if (!(wp->flags & SWP_NOSIZE) ||
6654 ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
6655 RECT r;
6656 LayoutDeviceIntSize clientSize;
6657 if (::GetClientRect(mWnd, &r)) {
6658 clientSize = WinUtils::ToIntRect(r).Size();
6659 } else {
6660 clientSize = mBounds.Size();
6662 // Send a gecko resize event
6663 OnResize(clientSize);
6667 void nsWindow::OnWindowPosChanging(WINDOWPOS* info) {
6668 // Update non-client margins if the frame size is changing, and let the
6669 // browser know we are changing size modes, so alternative css can kick in.
6670 // If we're going into fullscreen mode, ignore this, since it'll reset
6671 // margins to normal mode.
6672 if (info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) {
6673 mFrameState->OnFrameChanging();
6676 // Force fullscreen. This works around a bug in Windows 10 1809 where
6677 // using fullscreen when a window is "snapped" causes a spurious resize
6678 // smaller than the full screen, see bug 1482920.
6679 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
6680 !(info->flags & SWP_NOMOVE) && !(info->flags & SWP_NOSIZE)) {
6681 nsCOMPtr<nsIScreenManager> screenmgr =
6682 do_GetService(sScreenManagerContractID);
6683 if (screenmgr) {
6684 LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
6685 DesktopIntRect deskBounds =
6686 RoundedToInt(bounds / GetDesktopToDeviceScale());
6687 nsCOMPtr<nsIScreen> screen;
6688 screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
6689 deskBounds.Width(), deskBounds.Height(),
6690 getter_AddRefs(screen));
6692 if (screen) {
6693 auto rect = screen->GetRect();
6694 info->x = rect.x;
6695 info->y = rect.y;
6696 info->cx = rect.width;
6697 info->cy = rect.height;
6702 // enforce local z-order rules
6703 if (!(info->flags & SWP_NOZORDER)) {
6704 HWND hwndAfter = info->hwndInsertAfter;
6706 nsWindow* aboveWindow = 0;
6707 nsWindowZ placement;
6709 if (hwndAfter == HWND_BOTTOM)
6710 placement = nsWindowZBottom;
6711 else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST ||
6712 hwndAfter == HWND_NOTOPMOST)
6713 placement = nsWindowZTop;
6714 else {
6715 placement = nsWindowZRelative;
6716 aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter);
6719 if (mWidgetListener) {
6720 nsCOMPtr<nsIWidget> actualBelow = nullptr;
6721 if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow,
6722 getter_AddRefs(actualBelow))) {
6723 if (placement == nsWindowZBottom)
6724 info->hwndInsertAfter = HWND_BOTTOM;
6725 else if (placement == nsWindowZTop)
6726 info->hwndInsertAfter = HWND_TOP;
6727 else {
6728 info->hwndInsertAfter =
6729 (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW);
6734 // prevent rude external programs from making hidden window visible
6735 if (mWindowType == WindowType::Invisible) info->flags &= ~SWP_SHOWWINDOW;
6737 // When waking from sleep or switching out of tablet mode, Windows 10
6738 // Version 1809 will reopen popup windows that should be hidden. Detect
6739 // this case and refuse to show the window.
6740 static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
6741 if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
6742 mWindowType == WindowType::Popup && mWidgetListener &&
6743 mWidgetListener->ShouldNotBeVisible()) {
6744 info->flags &= ~SWP_SHOWWINDOW;
6748 void nsWindow::UserActivity() {
6749 // Check if we have the idle service, if not we try to get it.
6750 if (!mIdleService) {
6751 mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
6754 // Check that we now have the idle service.
6755 if (mIdleService) {
6756 mIdleService->ResetIdleTimeOut(0);
6760 // Helper function for TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT,
6761 // uint32_t).
6762 static bool TouchDeviceNeedsPanGestureConversion(HANDLE aSource) {
6763 std::string deviceName;
6764 UINT dataSize = 0;
6765 // The first call just queries how long the name string will be.
6766 GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, nullptr, &dataSize);
6767 if (!dataSize || dataSize > 0x10000) {
6768 return false;
6770 deviceName.resize(dataSize);
6771 // The second call actually populates the string.
6772 UINT result = GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, &deviceName[0],
6773 &dataSize);
6774 if (result == UINT_MAX) {
6775 return false;
6777 // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
6778 // needs to be escaped with another one.
6779 std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
6780 // For some reason, the dataSize returned by the first call is double the
6781 // actual length of the device name (as if it were returning the size of a
6782 // wide-character string in bytes) even though we are using the narrow
6783 // version of the API. For the comparison against the expected device name
6784 // to pass, we truncate the buffer to be no longer tha the expected device
6785 // name.
6786 if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
6787 return false;
6790 RID_DEVICE_INFO deviceInfo;
6791 deviceInfo.cbSize = sizeof(deviceInfo);
6792 dataSize = sizeof(deviceInfo);
6793 result =
6794 GetRawInputDeviceInfoA(aSource, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
6795 if (result == UINT_MAX) {
6796 return false;
6798 // The device identifiers that we check for here come from bug 1355162
6799 // comment 1 (see also bug 1511901 comment 35).
6800 return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
6801 deviceInfo.hid.dwProductId == 0 &&
6802 deviceInfo.hid.dwVersionNumber == 1 &&
6803 deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
6806 // Determine if the touch device that originated |aOSEvent| needs to have
6807 // touch events representing a two-finger gesture converted to pan
6808 // gesture events.
6809 // We only do this for touch devices with a specific name and identifiers.
6810 static bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
6811 uint32_t aTouchCount) {
6812 if (!StaticPrefs::apz_windows_check_for_pan_gesture_conversion()) {
6813 return false;
6815 if (aTouchCount == 0) {
6816 return false;
6818 HANDLE source = aOSEvent[0].hSource;
6820 // Cache the result of this computation for each touch device.
6821 // Touch devices are identified by the HANDLE stored in the hSource
6822 // field of TOUCHINPUT.
6823 static std::map<HANDLE, bool> sResultCache;
6824 auto [iter, inserted] = sResultCache.emplace(source, false);
6825 if (inserted) {
6826 iter->second = TouchDeviceNeedsPanGestureConversion(source);
6828 return iter->second;
6831 Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
6832 const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
6833 // Checks if the touch device that originated the touch event is one
6834 // for which we want to convert the touch events to pang gesture events.
6835 bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
6836 aOSEvent, aTouchInput.mTouches.Length());
6837 if (!shouldConvert) {
6838 return Nothing();
6841 // Only two-finger gestures need conversion.
6842 if (aTouchInput.mTouches.Length() != 2) {
6843 return Nothing();
6846 PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
6847 if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
6848 eventType = PanGestureInput::PANGESTURE_START;
6849 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
6850 eventType = PanGestureInput::PANGESTURE_END;
6851 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
6852 eventType = PanGestureInput::PANGESTURE_CANCELLED;
6855 // Use the midpoint of the two touches as the start point of the pan gesture.
6856 ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
6857 aTouchInput.mTouches[1].mScreenPoint) /
6859 // To compute the displacement of the pan gesture, we keep track of the
6860 // location of the previous event.
6861 ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
6862 ? ScreenPoint(0, 0)
6863 : (focusPoint - mLastPanGestureFocus);
6864 mLastPanGestureFocus = focusPoint;
6866 // We need to negate the displacement because for a touch event, moving the
6867 // fingers down results in scrolling up, but for a touchpad gesture, we want
6868 // moving the fingers down to result in scrolling down.
6869 PanGestureInput result(eventType, aTouchInput.mTimeStamp, focusPoint,
6870 -displacement, aTouchInput.modifiers);
6871 result.mSimulateMomentum = true;
6873 return Some(result);
6876 // Dispatch an event that originated as an OS touch event.
6877 // Usually, we want to dispatch it as a touch event, but some touchpads
6878 // produce touch events for two-finger scrolling, which need to be converted
6879 // to pan gesture events for correct behaviour.
6880 void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
6881 PTOUCHINPUT aOSEvent) {
6882 if (Maybe<PanGestureInput> panInput =
6883 ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
6884 DispatchPanGestureInput(*panInput);
6885 return;
6888 DispatchTouchInput(aTouchInput);
6891 bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
6892 uint32_t cInputs = LOWORD(wParam);
6893 PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
6895 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
6896 sizeof(TOUCHINPUT))) {
6897 MultiTouchInput touchInput, touchEndInput;
6899 // Walk across the touch point array processing each contact point.
6900 for (uint32_t i = 0; i < cInputs; i++) {
6901 bool addToEvent = false, addToEndEvent = false;
6903 // N.B.: According with MS documentation
6904 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
6905 // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
6906 // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
6907 // TOUCHEVENTF_UP can be combined together.
6909 if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
6910 if (touchInput.mTimeStamp.IsNull()) {
6911 // Initialize a touch event to send.
6912 touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
6913 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6914 ModifierKeyState modifierKeyState;
6915 touchInput.modifiers = modifierKeyState.GetModifiers();
6917 // Pres shell expects this event to be a eTouchStart
6918 // if any new contact points have been added since the last event sent.
6919 if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
6920 touchInput.mType = MultiTouchInput::MULTITOUCH_START;
6922 addToEvent = true;
6924 if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
6925 // Pres shell expects removed contacts points to be delivered in a
6926 // separate eTouchEnd event containing only the contact points that were
6927 // removed.
6928 if (touchEndInput.mTimeStamp.IsNull()) {
6929 // Initialize a touch event to send.
6930 touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
6931 touchEndInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
6932 ModifierKeyState modifierKeyState;
6933 touchEndInput.modifiers = modifierKeyState.GetModifiers();
6935 addToEndEvent = true;
6937 if (!addToEvent && !addToEndEvent) {
6938 // Filter out spurious Windows events we don't understand, like palm
6939 // contact.
6940 continue;
6943 // Setup the touch point we'll append to the touch event array.
6944 nsPointWin touchPoint;
6945 touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
6946 touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
6947 touchPoint.ScreenToClient(mWnd);
6949 // Initialize the touch data.
6950 SingleTouchData touchData(
6951 pInputs[i].dwID, // aIdentifier
6952 ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
6953 // The contact area info cannot be trusted even when
6954 // TOUCHINPUTMASKF_CONTACTAREA is set when the input source is pen,
6955 // which somehow violates the API docs. (bug 1710509) Ultimately the
6956 // dwFlags check will become redundant since we want to migrate to
6957 // WM_POINTER for pens. (bug 1707075)
6958 (pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA) &&
6959 !(pInputs[i].dwFlags & TOUCHEVENTF_PEN)
6960 ? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
6961 TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
6962 : ScreenSize(1, 1), // aRadius
6963 0.0f, // aRotationAngle
6964 0.0f); // aForce
6966 // Append touch data to the appropriate event.
6967 if (addToEvent) {
6968 touchInput.mTouches.AppendElement(touchData);
6970 if (addToEndEvent) {
6971 touchEndInput.mTouches.AppendElement(touchData);
6975 // Dispatch touch start and touch move event if we have one.
6976 if (!touchInput.mTimeStamp.IsNull()) {
6977 DispatchTouchOrPanGestureInput(touchInput, pInputs);
6979 // Dispatch touch end event if we have one.
6980 if (!touchEndInput.mTimeStamp.IsNull()) {
6981 DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
6985 delete[] pInputs;
6986 CloseTouchInputHandle((HTOUCHINPUT)lParam);
6987 return true;
6990 // Gesture event processing. Handles WM_GESTURE events.
6991 bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
6992 // Treatment for pan events which translate into scroll events:
6993 if (mGesture.IsPanEvent(lParam)) {
6994 if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
6995 return false; // ignore
6997 nsEventStatus status;
6999 WidgetWheelEvent wheelEvent(true, eWheel, this);
7001 ModifierKeyState modifierKeyState;
7002 modifierKeyState.InitInputEvent(wheelEvent);
7004 wheelEvent.mButton = 0;
7005 wheelEvent.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
7006 wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
7008 bool endFeedback = true;
7010 if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
7011 DispatchEvent(&wheelEvent, status);
7014 if (mDisplayPanFeedback) {
7015 mGesture.UpdatePanFeedbackX(
7016 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
7017 endFeedback);
7018 mGesture.UpdatePanFeedbackY(
7019 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
7020 endFeedback);
7021 mGesture.PanFeedbackFinalize(mWnd, endFeedback);
7024 CloseGestureInfoHandle((HGESTUREINFO)lParam);
7026 return true;
7029 // Other gestures translate into simple gesture events:
7030 WidgetSimpleGestureEvent event(true, eVoidEvent, this);
7031 if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
7032 return false; // fall through to DefWndProc
7035 // Polish up and send off the new event
7036 ModifierKeyState modifierKeyState;
7037 modifierKeyState.InitInputEvent(event);
7038 event.mButton = 0;
7039 event.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
7040 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
7042 nsEventStatus status;
7043 DispatchEvent(&event, status);
7044 if (status == nsEventStatus_eIgnore) {
7045 return false; // Ignored, fall through
7048 // Only close this if we process and return true.
7049 CloseGestureInfoHandle((HGESTUREINFO)lParam);
7051 return true; // Handled
7054 // WM_DESTROY event handler
7055 void nsWindow::OnDestroy() {
7056 mOnDestroyCalled = true;
7058 // If this is a toplevel window, notify the taskbar concealer to clean up any
7059 // relevant state.
7060 if (!mParent) {
7061 TaskbarConcealer::OnWindowDestroyed(mWnd);
7064 // Make sure we don't get destroyed in the process of tearing down.
7065 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
7067 // Dispatch the destroy notification.
7068 if (!mInDtor) NotifyWindowDestroyed();
7070 // Prevent the widget from sending additional events.
7071 mWidgetListener = nullptr;
7072 mAttachedWidgetListener = nullptr;
7074 DestroyDirectManipulation();
7076 if (mWnd == mLastKillFocusWindow) {
7077 mLastKillFocusWindow = nullptr;
7079 // Unregister notifications from terminal services
7080 ::WTSUnRegisterSessionNotification(mWnd);
7082 // We will stop receiving native events after dissociating from our native
7083 // window. We will also disappear from the output of WinUtils::GetNSWindowPtr
7084 // for that window.
7085 DissociateFromNativeWindow();
7087 // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
7088 // can be cleared. (It's used in tracking windows for mouse events.)
7089 if (sCurrentWindow == this) sCurrentWindow = nullptr;
7091 // Disconnects us from our parent, will call our GetParent().
7092 nsBaseWidget::Destroy();
7094 // Release references to children, device context, toolkit, and app shell.
7095 nsBaseWidget::OnDestroy();
7097 // Clear our native parent handle.
7098 // XXX Windows will take care of this in the proper order, and
7099 // SetParent(nullptr)'s remove child on the parent already took place in
7100 // nsBaseWidget's Destroy call above.
7101 // SetParent(nullptr);
7102 mParent = nullptr;
7104 // We have to destroy the native drag target before we null out our window
7105 // pointer.
7106 EnableDragDrop(false);
7108 // If we're going away and for some reason we're still the rollup widget,
7109 // rollup and turn off capture.
7110 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7111 nsCOMPtr<nsIWidget> rollupWidget;
7112 if (rollupListener) {
7113 rollupWidget = rollupListener->GetRollupWidget();
7115 if (this == rollupWidget) {
7116 rollupListener->Rollup({});
7117 CaptureRollupEvents(false);
7120 IMEHandler::OnDestroyWindow(this);
7122 // Free GDI window class objects
7123 if (mBrush) {
7124 VERIFY(::DeleteObject(mBrush));
7125 mBrush = nullptr;
7128 // Destroy any custom cursor resources.
7129 if (mCursor.IsCustom()) {
7130 SetCursor(Cursor{eCursor_standard});
7133 if (mCompositorWidgetDelegate) {
7134 mCompositorWidgetDelegate->OnDestroyWindow();
7136 mBasicLayersSurface = nullptr;
7138 // Finalize panning feedback to possibly restore window displacement
7139 mGesture.PanFeedbackFinalize(mWnd, true);
7141 // Clear the main HWND.
7142 mWnd = nullptr;
7145 // Send a resize message to the listener
7146 bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
7147 if (mCompositorWidgetDelegate &&
7148 !mCompositorWidgetDelegate->OnWindowResize(aSize)) {
7149 return false;
7152 bool result = false;
7153 if (mWidgetListener) {
7154 result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
7157 // If there is an attached view, inform it as well as the normal widget
7158 // listener.
7159 if (mAttachedWidgetListener) {
7160 return mAttachedWidgetListener->WindowResized(this, aSize.width,
7161 aSize.height);
7164 return result;
7167 void nsWindow::OnSizeModeChange() {
7168 const nsSizeMode mode = mFrameState->GetSizeMode();
7170 MOZ_LOG(gWindowsLog, LogLevel::Info,
7171 ("nsWindow::OnSizeModeChange() sizeMode %d", mode));
7173 if (NeedsToTrackWindowOcclusionState()) {
7174 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
7175 this, mode != nsSizeMode_Minimized);
7177 wr::DebugFlags flags{0};
7178 flags._0 = gfx::gfxVars::WebRenderDebugFlags();
7179 bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
7180 if (debugEnabled && mCompositorWidgetDelegate) {
7181 mCompositorWidgetDelegate->NotifyVisibilityUpdated(mode,
7182 mIsFullyOccluded);
7186 if (mCompositorWidgetDelegate) {
7187 mCompositorWidgetDelegate->OnWindowModeChange(mode);
7190 if (mWidgetListener) {
7191 mWidgetListener->SizeModeChanged(mode);
7195 bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
7197 bool nsWindow::IsPopup() { return mWindowType == WindowType::Popup; }
7199 bool nsWindow::ShouldUseOffMainThreadCompositing() {
7200 if (mWindowType == WindowType::Popup && mPopupType == PopupType::Tooltip) {
7201 return false;
7204 // Content rendering of popup is always done by child window.
7205 // See nsDocumentViewer::ShouldAttachToTopLevel().
7206 if (mWindowType == WindowType::Popup && !mIsChildWindow) {
7207 MOZ_ASSERT(!mParent);
7208 return false;
7211 return nsBaseWidget::ShouldUseOffMainThreadCompositing();
7214 void nsWindow::WindowUsesOMTC() {
7215 ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
7216 if (!style) {
7217 NS_WARNING("Could not get window class style");
7218 return;
7220 style |= CS_HREDRAW | CS_VREDRAW;
7221 DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
7222 NS_WARNING_ASSERTION(result, "Could not reset window class style");
7225 void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
7226 int32_t height) {
7227 // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
7228 // they remain tied to their original parent's resolution.
7229 if (mWindowType == WindowType::Popup) {
7230 return;
7232 if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
7233 return;
7235 mDefaultScale = -1.0; // force recomputation of scale factor
7237 if (mResizeState != RESIZING &&
7238 mFrameState->GetSizeMode() == nsSizeMode_Normal) {
7239 // Limit the position (if not in the middle of a drag-move) & size,
7240 // if it would overflow the destination screen
7241 nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID);
7242 if (sm) {
7243 nsCOMPtr<nsIScreen> screen;
7244 sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
7245 if (screen) {
7246 int32_t availLeft, availTop, availWidth, availHeight;
7247 screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
7248 if (mResizeState != MOVING) {
7249 x = std::max(x, availLeft);
7250 y = std::max(y, availTop);
7252 width = std::min(width, availWidth);
7253 height = std::min(height, availHeight);
7257 Resize(x, y, width, height, true);
7259 UpdateNonClientMargins();
7260 ChangedDPI();
7261 ResetLayout();
7264 // Callback to generate OnCloakChanged pseudo-events.
7265 /* static */
7266 void nsWindow::OnCloakEvent(HWND aWnd, bool aCloaked) {
7267 MOZ_ASSERT(NS_IsMainThread());
7269 const char* const kEventName = aCloaked ? "CLOAKED" : "UNCLOAKED";
7270 nsWindow* pWin = WinUtils::GetNSWindowPtr(aWnd);
7271 if (!pWin) {
7272 MOZ_LOG(
7273 sCloakingLog, LogLevel::Debug,
7274 ("Received %s event for HWND %p (not an nsWindow)", kEventName, aWnd));
7275 return;
7278 const char* const kWasCloakedStr = pWin->mIsCloaked ? "cloaked" : "uncloaked";
7279 if (mozilla::IsCloaked(aWnd) == pWin->mIsCloaked) {
7280 MOZ_LOG(sCloakingLog, LogLevel::Debug,
7281 ("Received redundant %s event for %s HWND %p; discarding",
7282 kEventName, kWasCloakedStr, aWnd));
7283 return;
7286 MOZ_LOG(
7287 sCloakingLog, LogLevel::Info,
7288 ("Received %s event for %s HWND %p", kEventName, kWasCloakedStr, aWnd));
7290 // Cloaking events like the one we've just received are sent asynchronously.
7291 // Rather than process them one-by-one, we jump the gun a bit and perform
7292 // updates on all newly cloaked/uncloaked nsWindows at once. This also lets us
7293 // batch operations that consider more than one window's state.
7294 struct Item {
7295 nsWindow* win;
7296 bool nowCloaked;
7298 nsTArray<Item> changedWindows;
7300 mozilla::EnumerateThreadWindows([&](HWND hwnd) {
7301 nsWindow* pWin = WinUtils::GetNSWindowPtr(hwnd);
7302 if (!pWin) {
7303 return;
7306 const bool isCloaked = mozilla::IsCloaked(hwnd);
7307 if (isCloaked != pWin->mIsCloaked) {
7308 changedWindows.AppendElement(Item{pWin, isCloaked});
7312 if (changedWindows.IsEmpty()) {
7313 return;
7316 for (const Item& item : changedWindows) {
7317 item.win->OnCloakChanged(item.nowCloaked);
7320 nsWindow::TaskbarConcealer::OnCloakChanged();
7323 void nsWindow::OnCloakChanged(bool aCloaked) {
7324 MOZ_LOG(sCloakingLog, LogLevel::Info,
7325 ("Calling OnCloakChanged(): HWND %p, aCloaked %s", mWnd,
7326 aCloaked ? "true" : "false"));
7327 mIsCloaked = aCloaked;
7330 /**************************************************************
7331 **************************************************************
7333 ** BLOCK: IME management and accessibility
7335 ** Handles managing IME input and accessibility.
7337 **************************************************************
7338 **************************************************************/
7340 void nsWindow::SetInputContext(const InputContext& aContext,
7341 const InputContextAction& aAction) {
7342 InputContext newInputContext = aContext;
7343 IMEHandler::SetInputContext(this, newInputContext, aAction);
7344 mInputContext = newInputContext;
7347 InputContext nsWindow::GetInputContext() {
7348 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7349 if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
7350 mInputContext.mIMEState.mOpen = IMEState::OPEN;
7351 } else {
7352 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7354 return mInputContext;
7357 TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
7358 return IMEHandler::GetNativeTextEventDispatcherListener();
7361 #ifdef ACCESSIBILITY
7362 # ifdef DEBUG
7363 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
7364 if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
7365 printf( \
7366 "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
7367 "%p,\n", \
7368 aHwnd, ::GetParent(aHwnd), aWnd); \
7369 printf(" acc: %p", aAcc); \
7370 if (aAcc) { \
7371 nsAutoString name; \
7372 aAcc->Name(name); \
7373 printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
7375 printf("\n }\n"); \
7378 # else
7379 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
7380 # endif
7382 a11y::LocalAccessible* nsWindow::GetAccessible() {
7383 // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
7384 if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
7385 return nullptr;
7387 if (mInDtor || mOnDestroyCalled || mWindowType == WindowType::Invisible) {
7388 return nullptr;
7391 // In case of popup window return a popup accessible.
7392 nsView* view = nsView::GetViewFor(this);
7393 if (view) {
7394 nsIFrame* frame = view->GetFrame();
7395 if (frame && nsLayoutUtils::IsPopup(frame)) {
7396 nsAccessibilityService* accService = GetOrCreateAccService();
7397 if (accService) {
7398 a11y::DocAccessible* docAcc =
7399 GetAccService()->GetDocAccessible(frame->PresShell());
7400 if (docAcc) {
7401 NS_LOG_WMGETOBJECT(
7402 this, mWnd,
7403 docAcc->GetAccessibleOrDescendant(frame->GetContent()));
7404 return docAcc->GetAccessibleOrDescendant(frame->GetContent());
7410 // otherwise root document accessible.
7411 NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
7412 return GetRootAccessible();
7414 #endif
7416 /**************************************************************
7417 **************************************************************
7419 ** BLOCK: Transparency
7421 ** Window transparency helpers.
7423 **************************************************************
7424 **************************************************************/
7426 void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) {
7427 if (aMode == mTransparencyMode) {
7428 return;
7431 // stop on dialogs and popups!
7432 HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
7433 nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd);
7435 if (!parent) {
7436 NS_WARNING("Trying to use transparent chrome in an embedded context");
7437 return;
7440 if (parent != this) {
7441 NS_WARNING(
7442 "Setting SetWindowTranslucencyInner on a parent this is not us!");
7445 LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE),
7446 exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
7448 if (parent->mIsVisible) {
7449 style |= WS_VISIBLE;
7450 if (parent->mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
7451 style |= WS_MAXIMIZE;
7452 } else if (parent->mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
7453 style |= WS_MINIMIZE;
7457 if (aMode == TransparencyMode::Transparent) {
7458 exStyle |= WS_EX_LAYERED;
7459 } else {
7460 exStyle &= ~WS_EX_LAYERED;
7463 VERIFY_WINDOW_STYLE(style);
7464 ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
7465 ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
7467 mTransparencyMode = aMode;
7469 if (mCompositorWidgetDelegate) {
7470 mCompositorWidgetDelegate->UpdateTransparency(aMode);
7474 /**************************************************************
7475 **************************************************************
7477 ** BLOCK: Popup rollup hooks
7479 ** Deals with CaptureRollup on popup windows.
7481 **************************************************************
7482 **************************************************************/
7484 // Schedules a timer for a window, so we can rollup after processing the hook
7485 // event
7486 void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
7487 // In some cases multiple hooks may be scheduled
7488 // so ignore any other requests once one timer is scheduled
7489 if (sHookTimerId == 0) {
7490 // Remember the window handle and the message ID to be used later
7491 sRollupMsgId = aMsgId;
7492 sRollupMsgWnd = aWnd;
7493 // Schedule native timer for doing the rollup after
7494 // this event is done being processed
7495 sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
7496 NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
7500 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7501 int gLastMsgCode = 0;
7502 extern MSGFEventMsgInfo gMSGFEvents[];
7503 #endif
7505 // Process Menu messages, rollup when popup is clicked.
7506 LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
7507 LPARAM lParam) {
7508 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7509 if (sProcessHook) {
7510 MSG* pMsg = (MSG*)lParam;
7512 int inx = 0;
7513 while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
7514 inx++;
7516 if (code != gLastMsgCode) {
7517 if (gMSGFEvents[inx].mId == code) {
7518 # ifdef DEBUG
7519 MOZ_LOG(gWindowsLog, LogLevel::Info,
7520 ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
7521 gMSGFEvents[inx].mStr, pMsg->hwnd));
7522 # endif
7523 } else {
7524 # ifdef DEBUG
7525 MOZ_LOG(gWindowsLog, LogLevel::Info,
7526 ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
7527 gMSGFEvents[inx].mId, pMsg->hwnd));
7528 # endif
7530 gLastMsgCode = code;
7532 PrintEvent(pMsg->message, FALSE, FALSE);
7534 #endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7536 if (sProcessHook && code == MSGF_MENU) {
7537 MSG* pMsg = (MSG*)lParam;
7538 ScheduleHookTimer(pMsg->hwnd, pMsg->message);
7541 return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
7544 // Process all mouse messages. Roll up when a click is in a native window
7545 // that doesn't have an nsIWidget.
7546 LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
7547 LPARAM lParam) {
7548 if (sProcessHook) {
7549 switch (WinUtils::GetNativeMessage(wParam)) {
7550 case WM_LBUTTONDOWN:
7551 case WM_RBUTTONDOWN:
7552 case WM_MBUTTONDOWN:
7553 case WM_MOUSEWHEEL:
7554 case WM_MOUSEHWHEEL: {
7555 MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
7556 nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
7557 if (!mozWin) {
7558 ScheduleHookTimer(ms->hwnd, (UINT)wParam);
7560 break;
7564 return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
7567 // Process all messages. Roll up when the window is moving, or
7568 // is resizing or when maximized or mininized.
7569 LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
7570 LPARAM lParam) {
7571 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7572 if (sProcessHook) {
7573 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7574 PrintEvent(cwpt->message, FALSE, FALSE);
7576 #endif
7578 if (sProcessHook) {
7579 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7580 if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
7581 cwpt->message == WM_GETMINMAXINFO) {
7582 ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
7586 return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
7589 // Register the special "hooks" for dropdown processing.
7590 void nsWindow::RegisterSpecialDropdownHooks() {
7591 NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
7592 NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
7594 DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
7596 // Install msg hook for moving the window and resizing
7597 if (!sMsgFilterHook) {
7598 DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
7599 sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
7600 nullptr, GetCurrentThreadId());
7601 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7602 if (!sMsgFilterHook) {
7603 MOZ_LOG(gWindowsLog, LogLevel::Info,
7604 ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
7606 #endif
7609 // Install msg hook for menus
7610 if (!sCallProcHook) {
7611 DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
7612 sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
7613 GetCurrentThreadId());
7614 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7615 if (!sCallProcHook) {
7616 MOZ_LOG(
7617 gWindowsLog, LogLevel::Info,
7618 ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
7620 #endif
7623 // Install msg hook for the mouse
7624 if (!sCallMouseHook) {
7625 DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
7626 sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
7627 GetCurrentThreadId());
7628 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7629 if (!sCallMouseHook) {
7630 MOZ_LOG(gWindowsLog, LogLevel::Info,
7631 ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
7633 #endif
7637 // Unhook special message hooks for dropdowns.
7638 void nsWindow::UnregisterSpecialDropdownHooks() {
7639 DISPLAY_NMM_PRT(
7640 "***************** De-installing Msg Hooks ***************\n");
7642 if (sCallProcHook) {
7643 DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
7644 if (!::UnhookWindowsHookEx(sCallProcHook)) {
7645 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
7647 sCallProcHook = nullptr;
7650 if (sMsgFilterHook) {
7651 DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
7652 if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
7653 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
7655 sMsgFilterHook = nullptr;
7658 if (sCallMouseHook) {
7659 DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
7660 if (!::UnhookWindowsHookEx(sCallMouseHook)) {
7661 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
7663 sCallMouseHook = nullptr;
7667 // This timer is designed to only fire one time at most each time a "hook"
7668 // function is used to rollup the dropdown. In some cases, the timer may be
7669 // scheduled from the hook, but that hook event or a subsequent event may roll
7670 // up the dropdown before this timer function is executed.
7672 // For example, if an MFC control takes focus, the combobox will lose focus and
7673 // rollup before this function fires.
7674 VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
7675 DWORD dwTime) {
7676 if (sHookTimerId != 0) {
7677 // if the window is nullptr then we need to use the ID to kill the timer
7678 DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
7679 NS_ASSERTION(status, "Hook Timer was not killed.");
7680 sHookTimerId = 0;
7683 if (sRollupMsgId != 0) {
7684 // Note: DealWithPopups does the check to make sure that the rollup widget
7685 // is set.
7686 LRESULT popupHandlingResult;
7687 nsAutoRollup autoRollup;
7688 DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
7689 sRollupMsgId = 0;
7690 sRollupMsgWnd = nullptr;
7694 static bool IsDifferentThreadWindow(HWND aWnd) {
7695 return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
7698 // static
7699 bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
7700 Maybe<POINT> aEventPoint) {
7701 RECT r;
7702 ::GetWindowRect(aWindow->mWnd, &r);
7703 POINT mp;
7704 if (aEventPoint) {
7705 mp = *aEventPoint;
7706 } else {
7707 DWORD pos = ::GetMessagePos();
7708 mp.x = GET_X_LPARAM(pos);
7709 mp.y = GET_Y_LPARAM(pos);
7712 auto margin = aWindow->mInputRegion.mMargin;
7713 if (margin > 0) {
7714 r.top += margin;
7715 r.bottom -= margin;
7716 r.left += margin;
7717 r.right -= margin;
7720 // was the event inside this window?
7721 return static_cast<bool>(::PtInRect(&r, mp));
7724 // static
7725 bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
7726 uint32_t* aPopupsToRollup,
7727 Maybe<POINT> aEventPoint) {
7728 // If we're dealing with menus, we probably have submenus and we don't want
7729 // to rollup some of them if the click is in a parent menu of the current
7730 // submenu.
7731 *aPopupsToRollup = UINT32_MAX;
7732 AutoTArray<nsIWidget*, 5> widgetChain;
7733 uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
7734 for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
7735 nsIWidget* widget = widgetChain[i];
7736 if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
7737 // Don't roll up if the mouse event occurred within a menu of the
7738 // same type. If the mouse event occurred in a menu higher than that,
7739 // roll up, but pass the number of popups to Rollup so that only those
7740 // of the same type close up.
7741 if (i < sameTypeCount) {
7742 return false;
7745 *aPopupsToRollup = sameTypeCount;
7746 break;
7749 return true;
7752 // static
7753 bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) {
7754 // While popup is open, popup window might be activated by other application.
7755 // At this time, we need to take back focus to the previous window but it
7756 // causes flickering its nonclient area because WM_NCACTIVATE comes before
7757 // WM_ACTIVATE and we cannot know which window will take focus at receiving
7758 // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
7760 // If non-popup window receives WM_NCACTIVATE at deactivating, default
7761 // wndproc shouldn't handle it as deactivating. Instead, at receiving
7762 // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
7763 // This returns true if the window needs to handle WM_NCACTIVATE later.
7765 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7766 return window && !window->IsPopup();
7769 static bool IsTouchSupportEnabled(HWND aWnd) {
7770 nsWindow* topWindow =
7771 WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
7772 return topWindow ? topWindow->IsTouchWindow() : false;
7775 static Maybe<POINT> GetSingleTouch(WPARAM wParam, LPARAM lParam) {
7776 Maybe<POINT> ret;
7777 uint32_t cInputs = LOWORD(wParam);
7778 if (cInputs != 1) {
7779 return ret;
7781 TOUCHINPUT input;
7782 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, &input,
7783 sizeof(TOUCHINPUT))) {
7784 ret.emplace();
7785 ret->x = TOUCH_COORD_TO_PIXEL(input.x);
7786 ret->y = TOUCH_COORD_TO_PIXEL(input.y);
7788 // Note that we don't call CloseTouchInputHandle here because we need
7789 // to read the touch input info again in OnTouch later.
7790 return ret;
7793 // static
7794 bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
7795 LPARAM aLParam, LRESULT* aResult) {
7796 NS_ASSERTION(aResult, "Bad outResult");
7798 // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
7799 *aResult = MA_NOACTIVATE;
7801 if (!::IsWindowVisible(aWnd)) {
7802 return false;
7805 if (MOZ_UNLIKELY(aMessage == WM_KILLFOCUS)) {
7806 // NOTE: We deal with this here rather than on the switch below because we
7807 // want to do this even if there are no menus to rollup (tooltips don't set
7808 // the rollup listener etc).
7809 if (RefPtr pm = nsXULPopupManager::GetInstance()) {
7810 pm->RollupTooltips();
7814 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7815 NS_ENSURE_TRUE(rollupListener, false);
7817 nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
7818 if (!popup) {
7819 return false;
7822 static bool sSendingNCACTIVATE = false;
7823 static bool sPendingNCACTIVATE = false;
7824 uint32_t popupsToRollup = UINT32_MAX;
7826 bool consumeRollupEvent = false;
7827 Maybe<POINT> touchPoint; // In screen coords.
7829 // If we rollup with animations but get occluded right away, we might not
7830 // advance the refresh driver enough for the animation to finish.
7831 auto allowAnimations = nsIRollupListener::AllowAnimations::Yes;
7832 nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
7833 UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
7834 switch (nativeMessage) {
7835 case WM_TOUCH:
7836 if (!IsTouchSupportEnabled(aWnd)) {
7837 // If APZ is disabled, don't allow touch inputs to dismiss popups. The
7838 // compatibility mouse events will do it instead.
7839 return false;
7841 touchPoint = GetSingleTouch(aWParam, aLParam);
7842 if (!touchPoint) {
7843 return false;
7845 [[fallthrough]];
7846 case WM_LBUTTONDOWN:
7847 case WM_RBUTTONDOWN:
7848 case WM_MBUTTONDOWN:
7849 case WM_NCLBUTTONDOWN:
7850 case WM_NCRBUTTONDOWN:
7851 case WM_NCMBUTTONDOWN:
7852 if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
7853 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
7854 // If any of these mouse events are really compatibility events that
7855 // Windows is sending for touch inputs, then don't allow them to dismiss
7856 // popups when APZ is enabled (instead we do the dismissing as part of
7857 // WM_TOUCH handling which is more correct).
7858 // If we don't do this, then when the user lifts their finger after a
7859 // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
7860 // us will dismiss the contextmenu popup that we displayed as part of
7861 // handling the long-tap-up.
7862 return false;
7864 if (!EventIsInsideWindow(popupWindow, touchPoint) &&
7865 GetPopupsToRollup(rollupListener, &popupsToRollup, touchPoint)) {
7866 break;
7868 return false;
7869 case WM_POINTERDOWN: {
7870 WinPointerEvents pointerEvents;
7871 if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) {
7872 return false;
7874 POINT pt;
7875 pt.x = GET_X_LPARAM(aLParam);
7876 pt.y = GET_Y_LPARAM(aLParam);
7877 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
7878 return false;
7880 if (EventIsInsideWindow(popupWindow, Some(pt))) {
7881 // Don't roll up if the event is inside the popup window.
7882 return false;
7884 } break;
7885 case MOZ_WM_DMANIP: {
7886 POINT pt;
7887 ::GetCursorPos(&pt);
7888 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
7889 return false;
7891 if (EventIsInsideWindow(popupWindow, Some(pt))) {
7892 // Don't roll up if the event is inside the popup window
7893 return false;
7895 } break;
7896 case WM_MOUSEWHEEL:
7897 case WM_MOUSEHWHEEL:
7898 // We need to check if the popup thinks that it should cause closing
7899 // itself when mouse wheel events are fired outside the rollup widget.
7900 if (!EventIsInsideWindow(popupWindow)) {
7901 // Check if we should consume this event even if we don't roll-up:
7902 consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
7903 *aResult = MA_ACTIVATE;
7904 if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
7905 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7906 break;
7909 return consumeRollupEvent;
7911 case WM_ACTIVATEAPP:
7912 allowAnimations = nsIRollupListener::AllowAnimations::No;
7913 break;
7915 case WM_ACTIVATE: {
7916 WndProcUrgentInvocation::Marker _marker;
7918 // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
7919 // because we cannot distinguish it's caused by mouse or not.
7920 if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
7921 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7922 if (window && (window->IsPopup() || window->mIsAlert)) {
7923 // Cancel notifying widget listeners of deactivating the previous
7924 // active window (see WM_KILLFOCUS case in ProcessMessage()).
7925 sJustGotDeactivate = false;
7926 // Reactivate the window later.
7927 ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
7928 return true;
7930 // Don't rollup the popup when focus moves back to the parent window
7931 // from a popup because such case is caused by strange mouse drivers.
7932 nsWindow* prevWindow =
7933 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
7934 if (prevWindow && prevWindow->IsPopup()) {
7935 // Consume this message here since previous window must not have
7936 // been inactivated since we've already stopped accepting the
7937 // inactivation below.
7938 return true;
7940 } else if (LOWORD(aWParam) == WA_INACTIVE) {
7941 nsWindow* activeWindow =
7942 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
7943 if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) {
7944 // If focus moves to non-popup widget or focusable popup, the window
7945 // needs to update its nonclient area.
7946 if (!activeWindow || !activeWindow->IsPopup()) {
7947 sSendingNCACTIVATE = true;
7948 ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
7949 sSendingNCACTIVATE = false;
7951 sPendingNCACTIVATE = false;
7953 // If focus moves from/to popup, we don't need to rollup the popup
7954 // because such case is caused by strange mouse drivers. And in
7955 // such case, we should consume the message here since we need to
7956 // hide this odd focus move from our content. (If we didn't consume
7957 // the message here, ProcessMessage() will notify widget listener of
7958 // inactivation and that causes unnecessary reflow for supporting
7959 // -moz-window-inactive pseudo class.
7960 if (activeWindow) {
7961 if (activeWindow->IsPopup()) {
7962 return true;
7964 nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
7965 if (deactiveWindow && deactiveWindow->IsPopup()) {
7966 return true;
7969 } else if (LOWORD(aWParam) == WA_CLICKACTIVE) {
7970 // If the WM_ACTIVATE message is caused by a click in a popup,
7971 // we should not rollup any popups.
7972 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7973 if ((window && window->IsPopup()) ||
7974 !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
7975 return false;
7978 allowAnimations = nsIRollupListener::AllowAnimations::No;
7979 } break;
7981 case MOZ_WM_REACTIVATE:
7982 // The previous active window should take back focus.
7983 if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
7984 // FYI: Even without this API call, you see expected result (e.g., the
7985 // owner window of the popup keeps active without flickering
7986 // the non-client area). And also this causes initializing
7987 // TSF and it causes using CPU time a lot. However, even if we
7988 // consume WM_ACTIVE messages, native focus change has already
7989 // been occurred. I.e., a popup window is active now. Therefore,
7990 // you'll see some odd behavior if we don't reactivate the owner
7991 // window here. For example, if you do:
7992 // 1. Turn wheel on a bookmark panel.
7993 // 2. Turn wheel on another window.
7994 // then, you'll see that the another window becomes active but the
7995 // owner window of the bookmark panel looks still active and the
7996 // bookmark panel keeps open. The reason is that the first wheel
7997 // operation gives focus to the bookmark panel. Therefore, when
7998 // the next operation gives focus to the another window, previous
7999 // focus window is the bookmark panel (i.e., a popup window).
8000 // So, in this case, our hack around here prevents to inactivate
8001 // the owner window and roll up the bookmark panel.
8002 ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
8004 return true;
8006 case WM_NCACTIVATE:
8007 if (!aWParam && !sSendingNCACTIVATE &&
8008 NeedsToHandleNCActivateDelayed(aWnd)) {
8009 // Don't just consume WM_NCACTIVATE. It doesn't handle only the
8010 // nonclient area state change.
8011 ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
8012 // Accept the deactivating because it's necessary to receive following
8013 // WM_ACTIVATE.
8014 *aResult = TRUE;
8015 sPendingNCACTIVATE = true;
8016 return true;
8018 return false;
8020 case WM_MOUSEACTIVATE:
8021 if (!EventIsInsideWindow(popupWindow) &&
8022 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
8023 // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
8024 // of TweakUI is enabled. Then, check if the popup should be rolled up
8025 // with rollup listener. If not, just consume the message.
8026 if (HIWORD(aLParam) == WM_MOUSEMOVE &&
8027 !rollupListener->ShouldRollupOnMouseActivate()) {
8028 return true;
8030 // Otherwise, it should be handled by wndproc.
8031 return false;
8034 // Prevent the click inside the popup from causing a change in window
8035 // activation. Since the popup is shown non-activated, we need to eat any
8036 // requests to activate the window while it is displayed. Windows will
8037 // automatically activate the popup on the mousedown otherwise.
8038 return true;
8040 case WM_SHOWWINDOW:
8041 // If the window is being minimized, close popups.
8042 if (aLParam == SW_PARENTCLOSING) {
8043 allowAnimations = nsIRollupListener::AllowAnimations::No;
8044 break;
8046 return false;
8048 case WM_KILLFOCUS:
8049 // If focus moves to other window created in different process/thread,
8050 // e.g., a plugin window, popups should be rolled up.
8051 if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
8052 allowAnimations = nsIRollupListener::AllowAnimations::No;
8053 break;
8055 return false;
8057 case WM_MOVING:
8058 case WM_MENUSELECT:
8059 break;
8061 default:
8062 return false;
8065 // Only need to deal with the last rollup for left mouse down events.
8066 NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
8068 nsIRollupListener::RollupOptions rollupOptions{
8069 popupsToRollup,
8070 nsIRollupListener::FlushViews::Yes,
8071 /* mPoint = */ nullptr,
8072 allowAnimations,
8075 if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
8076 nativeMessage == WM_POINTERDOWN) {
8077 LayoutDeviceIntPoint pos;
8078 if (nativeMessage == WM_TOUCH) {
8079 pos.x = touchPoint->x;
8080 pos.y = touchPoint->y;
8081 } else {
8082 POINT pt;
8083 pt.x = GET_X_LPARAM(aLParam);
8084 pt.y = GET_Y_LPARAM(aLParam);
8085 // POINTERDOWN is already in screen coords.
8086 if (nativeMessage == WM_LBUTTONDOWN) {
8087 ::ClientToScreen(aWnd, &pt);
8089 pos = LayoutDeviceIntPoint(pt.x, pt.y);
8092 rollupOptions.mPoint = &pos;
8093 nsIContent* lastRollup = nullptr;
8094 consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
8095 nsAutoRollup::SetLastRollup(lastRollup);
8096 } else {
8097 consumeRollupEvent = rollupListener->Rollup(rollupOptions);
8100 // Tell hook to stop processing messages
8101 sProcessHook = false;
8102 sRollupMsgId = 0;
8103 sRollupMsgWnd = nullptr;
8105 // If we are NOT supposed to be consuming events, let it go through
8106 if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) {
8107 *aResult = MA_ACTIVATE;
8108 return true;
8111 return false;
8114 /**************************************************************
8115 **************************************************************
8117 ** BLOCK: Misc. utility methods and functions.
8119 ** General use.
8121 **************************************************************
8122 **************************************************************/
8124 // Note that the result of GetTopLevelWindow method can be different from the
8125 // result of WinUtils::GetTopLevelHWND(). The result can be non-floating
8126 // window. Because our top level window may be contained in another window
8127 // which is not managed by us.
8128 nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
8129 nsWindow* curWindow = this;
8131 while (true) {
8132 if (aStopOnDialogOrPopup) {
8133 switch (curWindow->mWindowType) {
8134 case WindowType::Dialog:
8135 case WindowType::Popup:
8136 return curWindow;
8137 default:
8138 break;
8142 // Retrieve the top level parent or owner window
8143 nsWindow* parentWindow = curWindow->GetParentWindow(true);
8145 if (!parentWindow) return curWindow;
8147 curWindow = parentWindow;
8151 // Set a flag if hwnd is a (non-popup) visible window from this process,
8152 // and bail out of the enumeration. Otherwise leave the flag unmodified
8153 // and continue the enumeration.
8154 // lParam must be a bool* pointing at the flag to be set.
8155 static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
8156 DWORD pid;
8157 ::GetWindowThreadProcessId(hwnd, &pid);
8158 if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
8159 // Don't count popups as visible windows, since they don't take focus,
8160 // in case we only have a popup visible (see bug 1554490 where the gfx
8161 // test window is an offscreen popup).
8162 nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
8163 if (!window || !window->IsPopup()) {
8164 bool* windowsVisible = reinterpret_cast<bool*>(lParam);
8165 *windowsVisible = true;
8166 return FALSE;
8169 return TRUE;
8172 // Determine if it would be ok to activate a window, taking focus.
8173 // We want to avoid stealing focus from another app (bug 225305).
8174 bool nsWindow::CanTakeFocus() {
8175 HWND fgWnd = ::GetForegroundWindow();
8176 if (!fgWnd) {
8177 // There is no foreground window, so don't worry about stealing focus.
8178 return true;
8180 // We can take focus if the current foreground window is already from
8181 // this process.
8182 DWORD pid;
8183 ::GetWindowThreadProcessId(fgWnd, &pid);
8184 if (pid == ::GetCurrentProcessId()) {
8185 return true;
8188 bool windowsVisible = false;
8189 ::EnumWindows(EnumVisibleWindowsProc,
8190 reinterpret_cast<LPARAM>(&windowsVisible));
8192 if (!windowsVisible) {
8193 // We're probably creating our first visible window, allow that to
8194 // take focus.
8195 return true;
8197 return false;
8200 /* static */ const wchar_t* nsWindow::GetMainWindowClass() {
8201 static const wchar_t* sMainWindowClass = nullptr;
8202 if (!sMainWindowClass) {
8203 nsAutoString className;
8204 Preferences::GetString("ui.window_class_override", className);
8205 if (!className.IsEmpty()) {
8206 sMainWindowClass = wcsdup(className.get());
8207 } else {
8208 sMainWindowClass = kClassNameGeneral;
8211 return sMainWindowClass;
8214 LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
8215 POINT pt;
8216 pt.x = GET_X_LPARAM(lParam);
8217 pt.y = GET_Y_LPARAM(lParam);
8218 ::ClientToScreen(mWnd, &pt);
8219 return MAKELPARAM(pt.x, pt.y);
8222 LPARAM nsWindow::lParamToClient(LPARAM lParam) {
8223 POINT pt;
8224 pt.x = GET_X_LPARAM(lParam);
8225 pt.y = GET_Y_LPARAM(lParam);
8226 ::ScreenToClient(mWnd, &pt);
8227 return MAKELPARAM(pt.x, pt.y);
8230 WPARAM nsWindow::wParamFromGlobalMouseState() {
8231 WPARAM result = 0;
8233 if (!!::GetKeyState(VK_CONTROL)) {
8234 result |= MK_CONTROL;
8237 if (!!::GetKeyState(VK_SHIFT)) {
8238 result |= MK_SHIFT;
8241 if (!!::GetKeyState(VK_LBUTTON)) {
8242 result |= MK_LBUTTON;
8245 if (!!::GetKeyState(VK_MBUTTON)) {
8246 result |= MK_MBUTTON;
8249 if (!!::GetKeyState(VK_RBUTTON)) {
8250 result |= MK_RBUTTON;
8253 if (!!::GetKeyState(VK_XBUTTON1)) {
8254 result |= MK_XBUTTON1;
8257 if (!!::GetKeyState(VK_XBUTTON2)) {
8258 result |= MK_XBUTTON2;
8261 return result;
8264 // WORKAROUND FOR UNDOCUMENTED BEHAVIOR: `IFileDialog::Show` disables the
8265 // top-level ancestor of its provided owner-window. If the modal window's
8266 // container process crashes, it will never get a chance to undo that.
8268 // For simplicity's sake we simply unconditionally perform both the disabling
8269 // and reenabling here, synchronously, on the main thread, rather than leaving
8270 // it to happen in our asynchronously-operated IFileDialog.
8272 void nsWindow::PickerOpen() {
8273 AssertIsOnMainThread();
8275 // Disable the root-level window synchronously before any file-dialogs get a
8276 // chance to fight over doing it asynchronously.
8277 if (!mPickerDisplayCount) {
8278 ::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), FALSE);
8281 mPickerDisplayCount++;
8284 void nsWindow::PickerClosed() {
8285 AssertIsOnMainThread();
8286 NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
8287 if (!mPickerDisplayCount) return;
8288 mPickerDisplayCount--;
8290 // Once all the file-dialogs are gone, reenable the root-level window.
8291 if (!mPickerDisplayCount) {
8292 ::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), TRUE);
8293 DispatchFocusToTopLevelWindow(true);
8296 if (!mPickerDisplayCount && mDestroyCalled) {
8297 Destroy();
8301 bool nsWindow::WidgetTypeSupportsAcceleration() {
8302 // We don't currently support using an accelerated layer manager with
8303 // transparent windows so don't even try. I'm also not sure if we even
8304 // want to support this case. See bug 593471.
8306 // Windows' support for transparent accelerated surfaces isn't great.
8307 // Some possible approaches:
8308 // - Readback the data and update it using
8309 // UpdateLayeredWindow/UpdateLayeredWindowIndirect
8310 // This is what WPF does. See
8311 // CD3DDeviceLevel1::PresentWithGDI/CD3DSwapChainWithSwDC in WpfGfx. The
8312 // rationale for not using IDirect3DSurface9::GetDC is explained here:
8313 // https://web.archive.org/web/20160521191104/https://blogs.msdn.microsoft.com/dwayneneed/2008/09/08/transparent-windows-in-wpf/
8314 // - Use D3D11_RESOURCE_MISC_GDI_COMPATIBLE, IDXGISurface1::GetDC(),
8315 // and UpdateLayeredWindowIndirect.
8316 // This is suggested here:
8317 // https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/december/windows-with-c-layered-windows-with-direct2d
8318 // but might have the same problem that IDirect3DSurface9::GetDC has.
8319 // - Creating the window with the WS_EX_NOREDIRECTIONBITMAP flag and use
8320 // DirectComposition.
8321 // Not supported on Win7.
8322 // - Using DwmExtendFrameIntoClientArea with negative margins and something
8323 // to turn off the glass effect.
8324 // This doesn't work when the DWM is not running (Win7)
8326 // Also see bug 1150376, D3D11 composition can cause issues on some devices
8327 // on Windows 7 where presentation fails randomly for windows with drop
8328 // shadows.
8329 return mTransparencyMode != TransparencyMode::Transparent &&
8330 !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
8333 bool nsWindow::DispatchTouchEventFromWMPointer(
8334 UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo,
8335 mozilla::MouseButton aButton) {
8336 MultiTouchInput::MultiTouchType touchType;
8337 switch (msg) {
8338 case WM_POINTERDOWN:
8339 touchType = MultiTouchInput::MULTITOUCH_START;
8340 break;
8341 case WM_POINTERUPDATE:
8342 if (aPointerInfo.mPressure == 0) {
8343 return false; // hover
8345 touchType = MultiTouchInput::MULTITOUCH_MOVE;
8346 break;
8347 case WM_POINTERUP:
8348 touchType = MultiTouchInput::MULTITOUCH_END;
8349 break;
8350 default:
8351 return false;
8354 nsPointWin touchPoint;
8355 touchPoint.x = GET_X_LPARAM(aLParam);
8356 touchPoint.y = GET_Y_LPARAM(aLParam);
8357 touchPoint.ScreenToClient(mWnd);
8359 SingleTouchData touchData(static_cast<int32_t>(aPointerInfo.pointerId),
8360 ScreenIntPoint::FromUnknownPoint(touchPoint),
8361 ScreenSize(1, 1), // pixel size radius for pen
8362 0.0f, // no radius rotation
8363 aPointerInfo.mPressure);
8364 touchData.mTiltX = aPointerInfo.tiltX;
8365 touchData.mTiltY = aPointerInfo.tiltY;
8366 touchData.mTwist = aPointerInfo.twist;
8368 MultiTouchInput touchInput;
8369 touchInput.mType = touchType;
8370 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
8371 touchInput.mTouches.AppendElement(touchData);
8372 touchInput.mButton = aButton;
8373 touchInput.mButtons = aPointerInfo.mButtons;
8375 // POINTER_INFO.dwKeyStates can't be used as it only supports Shift and Ctrl
8376 ModifierKeyState modifierKeyState;
8377 touchInput.modifiers = modifierKeyState.GetModifiers();
8379 DispatchTouchInput(touchInput, MouseEvent_Binding::MOZ_SOURCE_PEN);
8380 return true;
8383 static MouseButton PenFlagsToMouseButton(PEN_FLAGS aPenFlags) {
8384 // Theoretically flags can be set together but they do not
8385 if (aPenFlags & PEN_FLAG_BARREL) {
8386 return MouseButton::eSecondary;
8388 if (aPenFlags & PEN_FLAG_ERASER) {
8389 return MouseButton::eEraser;
8391 return MouseButton::ePrimary;
8394 bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
8395 if (!mAPZC) {
8396 // APZ is not available on context menu. Follow the behavior of touch input
8397 // which fallbacks to WM_LBUTTON* and WM_GESTURE, to keep consistency.
8398 return false;
8400 if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) {
8401 return false;
8403 if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
8404 // We have to handle WM_POINTER* to fetch and cache pen related information
8405 // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
8406 // handler. This is because Windows doesn't support ::DoDragDrop in the
8407 // touch or pen message handlers.
8408 mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam);
8409 // Don't consume the Windows WM_POINTER* messages
8410 return false;
8413 uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
8414 POINTER_PEN_INFO penInfo{};
8415 if (!mPointerEvents.GetPointerPenInfo(pointerId, &penInfo)) {
8416 return false;
8419 // When dispatching mouse events with pen, there may be some
8420 // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
8421 // small movements. Those events will reset sLastMousePoint and reset
8422 // sLastClickCount. To prevent that, we keep the last pen down position
8423 // and compare it with the subsequent WM_POINTERUPDATE. If the movement is
8424 // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
8425 // eMouseMove for WM_POINTERUPDATE.
8426 static POINT sLastPointerDownPoint = {0};
8428 // We don't support chorded buttons for pen. Keep the button at
8429 // WM_POINTERDOWN.
8430 static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
8431 static bool sPointerDown = false;
8433 EventMessage message;
8434 mozilla::MouseButton button = MouseButton::ePrimary;
8435 switch (msg) {
8436 case WM_POINTERDOWN: {
8437 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8438 GET_Y_LPARAM(aLParam));
8439 sLastPointerDownPoint.x = eventPoint.x;
8440 sLastPointerDownPoint.y = eventPoint.y;
8441 message = eMouseDown;
8442 button = PenFlagsToMouseButton(penInfo.penFlags);
8443 sLastPenDownButton = button;
8444 sPointerDown = true;
8445 } break;
8446 case WM_POINTERUP:
8447 message = eMouseUp;
8448 MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
8449 button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
8450 sPointerDown = false;
8451 break;
8452 case WM_POINTERUPDATE:
8453 message = eMouseMove;
8454 if (sPointerDown) {
8455 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8456 GET_Y_LPARAM(aLParam));
8457 int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
8458 ? sLastPointerDownPoint.x - eventPoint.x.value
8459 : eventPoint.x.value - sLastPointerDownPoint.x;
8460 int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
8461 ? sLastPointerDownPoint.y - eventPoint.y.value
8462 : eventPoint.y.value - sLastPointerDownPoint.y;
8463 bool insideMovementThreshold =
8464 movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
8465 movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
8467 if (insideMovementThreshold) {
8468 // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
8469 // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
8470 return false;
8472 button = sLastPenDownButton;
8474 break;
8475 case WM_POINTERLEAVE:
8476 message = eMouseExitFromWidget;
8477 break;
8478 default:
8479 return false;
8482 // Windows defines the pen pressure is normalized to a range between 0 and
8483 // 1024. Convert it to float.
8484 float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
8485 int16_t buttons = sPointerDown
8486 ? nsContentUtils::GetButtonsFlagForButton(button)
8487 : MouseButtonsFlag::eNoButtons;
8488 WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure,
8489 buttons);
8490 // Per
8491 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_pen_info,
8492 // the rotation is normalized in a range of 0 to 359.
8493 MOZ_ASSERT(penInfo.rotation <= 359);
8494 pointerInfo.twist = (int32_t)penInfo.rotation;
8496 // Fire touch events but not when the barrel button is pressed.
8497 if (button != MouseButton::eSecondary &&
8498 StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled() &&
8499 DispatchTouchEventFromWMPointer(msg, aLParam, pointerInfo, button)) {
8500 return true;
8503 // The aLParam of WM_POINTER* is the screen location. Convert it to client
8504 // location
8505 LPARAM newLParam = lParamToClient(aLParam);
8506 DispatchMouseEvent(message, aWParam, newLParam, false, button,
8507 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8509 if (button == MouseButton::eSecondary && message == eMouseUp) {
8510 // Fire eContextMenu manually since consuming WM_POINTER* blocks
8511 // WM_CONTEXTMENU
8512 DispatchMouseEvent(eContextMenu, aWParam, newLParam, false, button,
8513 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8515 // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
8516 // WM_MOUSEMOVE.
8517 return true;
8520 void nsWindow::GetCompositorWidgetInitData(
8521 mozilla::widget::CompositorWidgetInitData* aInitData) {
8522 *aInitData = WinCompositorWidgetInitData(
8523 reinterpret_cast<uintptr_t>(mWnd),
8524 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
8525 mTransparencyMode, mFrameState->GetSizeMode());
8528 bool nsWindow::SynchronouslyRepaintOnResize() { return false; }
8530 void nsWindow::MaybeDispatchInitialFocusEvent() {
8531 if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
8532 DispatchFocusToTopLevelWindow(true);
8536 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
8537 nsCOMPtr<nsIWidget> window = new nsWindow();
8538 return window.forget();
8541 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
8542 nsCOMPtr<nsIWidget> window = new nsWindow(true);
8543 return window.forget();
8546 // static
8547 bool nsWindow::InitTouchInjection() {
8548 if (!sTouchInjectInitialized) {
8549 // Initialize touch injection on the first call
8550 HMODULE hMod = LoadLibraryW(kUser32LibName);
8551 if (!hMod) {
8552 return false;
8555 InitializeTouchInjectionPtr func =
8556 (InitializeTouchInjectionPtr)GetProcAddress(hMod,
8557 "InitializeTouchInjection");
8558 if (!func) {
8559 WinUtils::Log("InitializeTouchInjection not available.");
8560 return false;
8563 if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
8564 WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
8565 GetLastError());
8566 return false;
8569 sInjectTouchFuncPtr =
8570 (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
8571 if (!sInjectTouchFuncPtr) {
8572 WinUtils::Log("InjectTouchInput not available.");
8573 return false;
8575 sTouchInjectInitialized = true;
8577 return true;
8580 bool nsWindow::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
8581 POINTER_FLAGS aFlags, uint32_t aPressure,
8582 uint32_t aOrientation) {
8583 if (aId > TOUCH_INJECT_MAX_POINTS) {
8584 WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
8585 return false;
8588 POINTER_TOUCH_INFO info{};
8590 info.touchFlags = TOUCH_FLAG_NONE;
8591 info.touchMask =
8592 TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
8593 info.pressure = aPressure;
8594 info.orientation = aOrientation;
8596 info.pointerInfo.pointerFlags = aFlags;
8597 info.pointerInfo.pointerType = PT_TOUCH;
8598 info.pointerInfo.pointerId = aId;
8599 info.pointerInfo.ptPixelLocation.x = aPoint.x;
8600 info.pointerInfo.ptPixelLocation.y = aPoint.y;
8602 info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
8603 info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
8604 info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
8605 info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
8607 for (int i = 0; i < 3; i++) {
8608 if (sInjectTouchFuncPtr(1, &info)) {
8609 break;
8611 DWORD error = GetLastError();
8612 if (error == ERROR_NOT_READY && i < 2) {
8613 // We sent it too quickly after the previous injection (see bug 1535140
8614 // comment 10). On the first loop iteration we just yield (via Sleep(0))
8615 // and try again. If it happens again on the second loop iteration we
8616 // explicitly Sleep(1) and try again. If that doesn't work either we just
8617 // error out.
8618 ::Sleep(i);
8619 continue;
8621 WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
8622 return false;
8624 return true;
8627 void nsWindow::ChangedDPI() {
8628 if (mWidgetListener) {
8629 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
8630 presShell->BackingScaleFactorChanged();
8633 NotifyAPZOfDPIChange();
8636 static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
8637 nsWindow::TouchPointerState aPointerState, bool isUpdate) {
8638 bool hover = aPointerState & nsWindow::TOUCH_HOVER;
8639 bool contact = aPointerState & nsWindow::TOUCH_CONTACT;
8640 bool remove = aPointerState & nsWindow::TOUCH_REMOVE;
8641 bool cancel = aPointerState & nsWindow::TOUCH_CANCEL;
8643 POINTER_FLAGS flags;
8644 if (isUpdate) {
8645 // We know about this pointer, send an update
8646 flags = POINTER_FLAG_UPDATE;
8647 if (hover) {
8648 flags |= POINTER_FLAG_INRANGE;
8649 } else if (contact) {
8650 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
8651 } else if (remove) {
8652 flags = POINTER_FLAG_UP;
8655 if (cancel) {
8656 flags |= POINTER_FLAG_CANCELED;
8658 } else {
8659 // Missing init state, error out
8660 if (remove || cancel) {
8661 return Err(NS_ERROR_INVALID_ARG);
8664 // Create a new pointer
8665 flags = POINTER_FLAG_INRANGE;
8666 if (contact) {
8667 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
8670 return flags;
8673 nsresult nsWindow::SynthesizeNativeTouchPoint(
8674 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8675 LayoutDeviceIntPoint aPoint, double aPointerPressure,
8676 uint32_t aPointerOrientation, nsIObserver* aObserver) {
8677 AutoObserverNotifier notifier(aObserver, "touchpoint");
8679 if (StaticPrefs::apz_test_fails_with_native_injection() ||
8680 !InitTouchInjection()) {
8681 // If we don't have touch injection from the OS, or if we are running a test
8682 // that cannot properly inject events to satisfy the OS requirements (see
8683 // bug 1313170) we can just fake it and synthesize the events from here.
8684 MOZ_ASSERT(NS_IsMainThread());
8685 if (aPointerState == TOUCH_HOVER) {
8686 return NS_ERROR_UNEXPECTED;
8689 if (!mSynthesizedTouchInput) {
8690 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
8693 WidgetEventTime time = CurrentMessageWidgetEventTime();
8694 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8695 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
8696 mSynthesizedTouchInput.get(), time.mTimeStamp, aPointerId,
8697 aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
8698 DispatchTouchInput(inputToDispatch);
8699 return NS_OK;
8702 // win api expects a value from 0 to 1024. aPointerPressure is a value
8703 // from 0.0 to 1.0.
8704 uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
8706 // If we already know about this pointer id get it's record
8707 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8708 POINTER_FLAGS flags;
8709 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8710 auto result = PointerStateToFlag(aPointerState, !!entry);
8711 if (result.isOk()) {
8712 flags = result.unwrap();
8713 } else {
8714 return result.unwrapErr();
8717 if (!entry) {
8718 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8719 PointerInfo::PointerType::TOUCH));
8720 } else {
8721 if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
8722 return NS_ERROR_UNEXPECTED;
8724 if (aPointerState & TOUCH_REMOVE) {
8725 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8726 // so shouldn't leak.
8727 entry.Remove();
8731 return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
8732 aPointerOrientation)
8733 ? NS_ERROR_UNEXPECTED
8734 : NS_OK;
8738 nsresult nsWindow::ClearNativeTouchSequence(nsIObserver* aObserver) {
8739 AutoObserverNotifier notifier(aObserver, "cleartouch");
8740 if (!sTouchInjectInitialized) {
8741 return NS_OK;
8744 // cancel all input points
8745 for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
8746 auto* info = iter.UserData();
8747 if (info->mType != PointerInfo::PointerType::TOUCH) {
8748 continue;
8750 InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
8751 iter.Remove();
8754 nsBaseWidget::ClearNativeTouchSequence(nullptr);
8756 return NS_OK;
8759 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8760 static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
8761 static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
8762 static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
8763 #endif
8764 static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
8766 static bool InitPenInjection() {
8767 if (sSyntheticPenDevice) {
8768 return true;
8770 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8771 HMODULE hMod = LoadLibraryW(kUser32LibName);
8772 if (!hMod) {
8773 return false;
8775 CreateSyntheticPointerDevice =
8776 (CreateSyntheticPointerDevicePtr)GetProcAddress(
8777 hMod, "CreateSyntheticPointerDevice");
8778 if (!CreateSyntheticPointerDevice) {
8779 WinUtils::Log("CreateSyntheticPointerDevice not available.");
8780 return false;
8782 DestroySyntheticPointerDevice =
8783 (DestroySyntheticPointerDevicePtr)GetProcAddress(
8784 hMod, "DestroySyntheticPointerDevice");
8785 if (!DestroySyntheticPointerDevice) {
8786 WinUtils::Log("DestroySyntheticPointerDevice not available.");
8787 return false;
8789 InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
8790 hMod, "InjectSyntheticPointerInput");
8791 if (!InjectSyntheticPointerInput) {
8792 WinUtils::Log("InjectSyntheticPointerInput not available.");
8793 return false;
8795 #endif
8796 sSyntheticPenDevice =
8797 CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
8798 return !!sSyntheticPenDevice;
8801 nsresult nsWindow::SynthesizeNativePenInput(
8802 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8803 LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
8804 int32_t aTiltX, int32_t aTiltY, int32_t aButton, nsIObserver* aObserver) {
8805 AutoObserverNotifier notifier(aObserver, "peninput");
8806 if (!InitPenInjection()) {
8807 return NS_ERROR_UNEXPECTED;
8810 // win api expects a value from 0 to 1024. aPointerPressure is a value
8811 // from 0.0 to 1.0.
8812 uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
8814 // If we already know about this pointer id get it's record
8815 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8816 POINTER_FLAGS flags;
8817 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8818 auto result = PointerStateToFlag(aPointerState, !!entry);
8819 if (result.isOk()) {
8820 flags = result.unwrap();
8821 } else {
8822 return result.unwrapErr();
8825 if (!entry) {
8826 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8827 PointerInfo::PointerType::PEN));
8828 } else {
8829 if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
8830 return NS_ERROR_UNEXPECTED;
8832 if (aPointerState & TOUCH_REMOVE) {
8833 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8834 // so shouldn't leak.
8835 entry.Remove();
8839 POINTER_TYPE_INFO info{};
8841 info.type = PT_PEN;
8842 info.penInfo.pointerInfo.pointerType = PT_PEN;
8843 info.penInfo.pointerInfo.pointerFlags = flags;
8844 info.penInfo.pointerInfo.pointerId = aPointerId;
8845 info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
8846 info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
8848 info.penInfo.penFlags = PEN_FLAG_NONE;
8849 // PEN_FLAG_ERASER is not supported this way, unfortunately.
8850 if (aButton == 2) {
8851 info.penInfo.penFlags |= PEN_FLAG_BARREL;
8853 info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
8854 PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
8855 info.penInfo.pressure = pressure;
8856 info.penInfo.rotation = aRotation;
8857 info.penInfo.tiltX = aTiltX;
8858 info.penInfo.tiltY = aTiltY;
8860 return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
8861 ? NS_OK
8862 : NS_ERROR_UNEXPECTED;
8866 bool nsWindow::HandleAppCommandMsg(const MSG& aAppCommandMsg,
8867 LRESULT* aRetValue) {
8868 ModifierKeyState modKeyState;
8869 NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
8870 bool consumed = nativeKey.HandleAppCommandMessage();
8871 *aRetValue = consumed ? 1 : 0;
8872 return consumed;
8875 #ifdef DEBUG
8876 nsresult nsWindow::SetHiDPIMode(bool aHiDPI) {
8877 return WinUtils::SetHiDPIMode(aHiDPI);
8880 nsresult nsWindow::RestoreHiDPIMode() { return WinUtils::RestoreHiDPIMode(); }
8881 #endif
8883 mozilla::Maybe<UINT> nsWindow::GetHiddenTaskbarEdge() {
8884 HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
8886 // Check all four sides of our monitor for an appbar. Skip any that aren't
8887 // the system taskbar.
8888 MONITORINFO mi;
8889 mi.cbSize = sizeof(MONITORINFO);
8890 ::GetMonitorInfo(windowMonitor, &mi);
8892 APPBARDATA appBarData;
8893 appBarData.cbSize = sizeof(appBarData);
8894 appBarData.rc = mi.rcMonitor;
8895 const auto kEdges = {ABE_BOTTOM, ABE_TOP, ABE_LEFT, ABE_RIGHT};
8896 for (auto edge : kEdges) {
8897 appBarData.uEdge = edge;
8898 HWND appBarHwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &appBarData);
8899 if (appBarHwnd) {
8900 nsAutoString className;
8901 if (WinUtils::GetClassName(appBarHwnd, className)) {
8902 if (className.Equals(L"Shell_TrayWnd") ||
8903 className.Equals(L"Shell_SecondaryTrayWnd")) {
8904 return Some(edge);
8910 return Nothing();
8913 static nsSizeMode GetSizeModeForWindowFrame(HWND aWnd, bool aFullscreenMode) {
8914 WINDOWPLACEMENT pl;
8915 pl.length = sizeof(pl);
8916 ::GetWindowPlacement(aWnd, &pl);
8918 if (pl.showCmd == SW_SHOWMINIMIZED) {
8919 return nsSizeMode_Minimized;
8920 } else if (aFullscreenMode) {
8921 return nsSizeMode_Fullscreen;
8922 } else if (pl.showCmd == SW_SHOWMAXIMIZED) {
8923 return nsSizeMode_Maximized;
8924 } else {
8925 return nsSizeMode_Normal;
8929 static void ShowWindowWithMode(HWND aWnd, nsSizeMode aMode) {
8930 // This will likely cause a callback to
8931 // nsWindow::FrameState::{OnFrameChanging() and OnFrameChanged()}
8932 switch (aMode) {
8933 case nsSizeMode_Fullscreen:
8934 ::ShowWindow(aWnd, SW_SHOW);
8935 break;
8937 case nsSizeMode_Maximized:
8938 ::ShowWindow(aWnd, SW_MAXIMIZE);
8939 break;
8941 case nsSizeMode_Minimized:
8942 ::ShowWindow(aWnd, SW_MINIMIZE);
8943 break;
8945 default:
8946 // Don't call ::ShowWindow if we're trying to "restore" a window that is
8947 // already in a normal state. Prevents a bug where snapping to one side
8948 // of the screen and then minimizing would cause Windows to forget our
8949 // window's correct restored position/size.
8950 if (GetCurrentShowCmd(aWnd) != SW_SHOWNORMAL) {
8951 ::ShowWindow(aWnd, SW_RESTORE);
8956 nsWindow::FrameState::FrameState(nsWindow* aWindow) : mWindow(aWindow) {}
8958 nsSizeMode nsWindow::FrameState::GetSizeMode() const { return mSizeMode; }
8960 void nsWindow::FrameState::CheckInvariant() const {
8961 MOZ_ASSERT(mSizeMode >= 0 && mSizeMode < nsSizeMode_Invalid);
8962 MOZ_ASSERT(mLastSizeMode >= 0 && mLastSizeMode < nsSizeMode_Invalid);
8963 MOZ_ASSERT(mPreFullscreenSizeMode >= 0 &&
8964 mPreFullscreenSizeMode < nsSizeMode_Invalid);
8965 MOZ_ASSERT(mWindow);
8967 // We should never observe fullscreen sizemode unless fullscreen is enabled
8968 MOZ_ASSERT_IF(mSizeMode == nsSizeMode_Fullscreen, mFullscreenMode);
8969 MOZ_ASSERT_IF(!mFullscreenMode, mSizeMode != nsSizeMode_Fullscreen);
8971 // Something went wrong if we somehow saved fullscreen mode when we are
8972 // changing into fullscreen mode
8973 MOZ_ASSERT(mPreFullscreenSizeMode != nsSizeMode_Fullscreen);
8976 void nsWindow::FrameState::ConsumePreXULSkeletonState(bool aWasMaximized) {
8977 mSizeMode = aWasMaximized ? nsSizeMode_Maximized : nsSizeMode_Normal;
8980 void nsWindow::FrameState::EnsureSizeMode(nsSizeMode aMode,
8981 DoShowWindow aDoShowWindow) {
8982 if (mSizeMode == aMode) {
8983 return;
8986 if (StaticPrefs::widget_windows_fullscreen_remind_taskbar()) {
8987 // If we're unminimizing a window, asynchronously notify the taskbar after
8988 // the message has been processed. This redundant notification works around
8989 // a race condition in explorer.exe. (See bug 1835851, or comments in
8990 // TaskbarConcealer.)
8992 // Note that we notify regardless of `aMode`: unminimizing a non-fullscreen
8993 // window can also affect the correct taskbar state, yet fail to affect the
8994 // current taskbar state.
8995 if (mSizeMode == nsSizeMode_Minimized) {
8996 ::PostMessage(mWindow->mWnd, MOZ_WM_FULLSCREEN_STATE_UPDATE, 0, 0);
9000 if (aMode == nsSizeMode_Fullscreen) {
9001 EnsureFullscreenMode(true, aDoShowWindow);
9002 MOZ_ASSERT(mSizeMode == nsSizeMode_Fullscreen);
9003 } else if (mSizeMode == nsSizeMode_Fullscreen && aMode == nsSizeMode_Normal) {
9004 // If we are in fullscreen mode, minimize should work like normal and
9005 // return us to fullscreen mode when unminimized. Maximize isn't really
9006 // available and won't do anything. "Restore" should do the same thing as
9007 // requesting to end fullscreen.
9008 EnsureFullscreenMode(false, aDoShowWindow);
9009 } else {
9010 SetSizeModeInternal(aMode, aDoShowWindow);
9014 void nsWindow::FrameState::EnsureFullscreenMode(bool aFullScreen,
9015 DoShowWindow aDoShowWindow) {
9016 const bool changed = aFullScreen != mFullscreenMode;
9017 if (changed && aFullScreen) {
9018 // Save the size mode from before fullscreen.
9019 mPreFullscreenSizeMode = mSizeMode;
9021 mFullscreenMode = aFullScreen;
9022 if (changed || aFullScreen) {
9023 // NOTE(emilio): When minimizing a fullscreen window we remain with
9024 // mFullscreenMode = true, but mSizeMode = nsSizeMode_Minimized. We need to
9025 // make sure to call SetSizeModeInternal even if mFullscreenMode didn't
9026 // change, to ensure we actually end up with a fullscreen sizemode when
9027 // restoring a window from that state.
9028 SetSizeModeInternal(
9029 aFullScreen ? nsSizeMode_Fullscreen : mPreFullscreenSizeMode,
9030 aDoShowWindow);
9034 void nsWindow::FrameState::OnFrameChanging() {
9035 const nsSizeMode newSizeMode =
9036 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
9037 EnsureSizeMode(newSizeMode);
9038 mWindow->UpdateNonClientMargins(false);
9041 void nsWindow::FrameState::OnFrameChanged() {
9042 // We don't want to perform the ShowWindow ourselves if we're on the frame
9043 // changed message. Windows has done the frame change for us, and we take care
9044 // of activating as needed. We also don't want to potentially trigger
9045 // more focus / restore. Among other things, this addresses a bug on Win7
9046 // related to window docking. (bug 489258)
9047 const auto newSizeMode =
9048 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
9049 EnsureSizeMode(newSizeMode, DoShowWindow::No);
9051 // If window was restored, activate the window now to get correct attributes.
9052 if (mWindow->mIsVisible && mWindow->IsForegroundWindow() &&
9053 mLastSizeMode == nsSizeMode_Minimized &&
9054 mSizeMode != nsSizeMode_Minimized) {
9055 mWindow->DispatchFocusToTopLevelWindow(true);
9057 mLastSizeMode = mSizeMode;
9060 static void MaybeLogSizeMode(nsSizeMode aMode) {
9061 #ifdef WINSTATE_DEBUG_OUTPUT
9062 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** SizeMode: %d\n", int(aMode)));
9063 #endif
9066 void nsWindow::FrameState::SetSizeModeInternal(nsSizeMode aMode,
9067 DoShowWindow aDoShowWindow) {
9068 if (mSizeMode == aMode) {
9069 return;
9072 const auto oldSizeMode = mSizeMode;
9073 const bool fullscreenChange =
9074 mSizeMode == nsSizeMode_Fullscreen || aMode == nsSizeMode_Fullscreen;
9075 const bool fullscreen = aMode == nsSizeMode_Fullscreen;
9077 mLastSizeMode = mSizeMode;
9078 mSizeMode = aMode;
9080 MaybeLogSizeMode(mSizeMode);
9082 if (bool(aDoShowWindow) && mWindow->mIsVisible) {
9083 ShowWindowWithMode(mWindow->mWnd, aMode);
9086 mWindow->UpdateNonClientMargins(false);
9088 if (fullscreenChange) {
9089 mWindow->OnFullscreenChanged(oldSizeMode, fullscreen);
9092 mWindow->OnSizeModeChange();
9095 void nsWindow::ContextMenuPreventer::Update(
9096 const WidgetMouseEvent& aEvent,
9097 const nsIWidget::ContentAndAPZEventStatus& aEventStatus) {
9098 mNeedsToPreventContextMenu =
9099 aEvent.mMessage == eMouseUp &&
9100 aEvent.mButton == MouseButton::eSecondary &&
9101 aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
9102 aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault;