Bug 1838234 - Implement Quarantine controls in extensions panel and context menus...
[gecko.git] / widget / windows / nsWindow.cpp
blob6b88076137354819ef54261608305349d6659d8e
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 "nsWindow.h"
81 #include "nsWindowTaskbarConcealer.h"
82 #include "nsAppRunner.h"
84 #include <shellapi.h>
85 #include <windows.h>
86 #include <wtsapi32.h>
87 #include <process.h>
88 #include <commctrl.h>
89 #include <dbt.h>
90 #include <unknwn.h>
91 #include <psapi.h>
92 #include <rpc.h>
93 #include <propvarutil.h>
94 #include <propkey.h>
96 #include "mozilla/Logging.h"
97 #include "prtime.h"
98 #include "prenv.h"
100 #include "mozilla/WidgetTraceEvent.h"
101 #include "nsContentUtils.h"
102 #include "nsISupportsPrimitives.h"
103 #include "nsITheme.h"
104 #include "nsIObserverService.h"
105 #include "nsIScreenManager.h"
106 #include "imgIContainer.h"
107 #include "nsIFile.h"
108 #include "nsIRollupListener.h"
109 #include "nsIClipboard.h"
110 #include "WinMouseScrollHandler.h"
111 #include "nsFontMetrics.h"
112 #include "nsIFontEnumerator.h"
113 #include "nsFont.h"
114 #include "nsRect.h"
115 #include "nsThreadUtils.h"
116 #include "nsNativeCharsetUtils.h"
117 #include "nsGkAtoms.h"
118 #include "nsCRT.h"
119 #include "nsAppDirectoryServiceDefs.h"
120 #include "nsWidgetsCID.h"
121 #include "nsTHashtable.h"
122 #include "nsHashKeys.h"
123 #include "nsString.h"
124 #include "mozilla/Components.h"
125 #include "nsNativeThemeWin.h"
126 #include "nsXULPopupManager.h"
127 #include "nsWindowsDllInterceptor.h"
128 #include "nsLayoutUtils.h"
129 #include "nsView.h"
130 #include "nsWindowGfx.h"
131 #include "gfxWindowsPlatform.h"
132 #include "gfxDWriteFonts.h"
133 #include "nsPrintfCString.h"
134 #include "mozilla/Preferences.h"
135 #include "SystemTimeConverter.h"
136 #include "WinTaskbar.h"
137 #include "WidgetUtils.h"
138 #include "WinWindowOcclusionTracker.h"
139 #include "nsIWidgetListener.h"
140 #include "mozilla/dom/Document.h"
141 #include "mozilla/dom/MouseEventBinding.h"
142 #include "mozilla/dom/Touch.h"
143 #include "mozilla/gfx/2D.h"
144 #include "mozilla/gfx/GPUProcessManager.h"
145 #include "mozilla/intl/LocaleService.h"
146 #include "mozilla/layers/WebRenderLayerManager.h"
147 #include "mozilla/WindowsVersion.h"
148 #include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
149 #include "mozilla/TextEventDispatcherListener.h"
150 #include "mozilla/widget/nsAutoRollup.h"
151 #include "mozilla/widget/PlatformWidgetTypes.h"
152 #include "mozilla/widget/Screen.h"
153 #include "nsStyleConsts.h"
154 #include "nsBidiKeyboard.h"
155 #include "nsStyleConsts.h"
156 #include "gfxConfig.h"
157 #include "InProcessWinCompositorWidget.h"
158 #include "InputDeviceUtils.h"
159 #include "ScreenHelperWin.h"
160 #include "mozilla/StaticPrefs_apz.h"
161 #include "mozilla/StaticPrefs_dom.h"
162 #include "mozilla/StaticPrefs_gfx.h"
163 #include "mozilla/StaticPrefs_layout.h"
164 #include "mozilla/StaticPrefs_widget.h"
165 #include "nsNativeAppSupportWin.h"
166 #include "mozilla/browser/NimbusFeatures.h"
168 #include "nsIGfxInfo.h"
169 #include "nsUXThemeConstants.h"
170 #include "KeyboardLayout.h"
171 #include "nsNativeDragTarget.h"
172 #include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
173 #include <zmouse.h>
174 #include <richedit.h>
176 #if defined(ACCESSIBILITY)
178 # ifdef DEBUG
179 # include "mozilla/a11y/Logging.h"
180 # endif
182 # include "oleidl.h"
183 # include <winuser.h>
184 # include "nsAccessibilityService.h"
185 # include "mozilla/a11y/DocAccessible.h"
186 # include "mozilla/a11y/LazyInstantiator.h"
187 # include "mozilla/a11y/Platform.h"
188 # if !defined(WINABLEAPI)
189 # include <winable.h>
190 # endif // !defined(WINABLEAPI)
191 #endif // defined(ACCESSIBILITY)
193 #include "WindowsUIUtils.h"
195 #include "nsWindowDefs.h"
197 #include "nsCrashOnException.h"
199 #include "nsIContent.h"
201 #include "mozilla/BackgroundHangMonitor.h"
202 #include "WinIMEHandler.h"
204 #include "npapi.h"
206 #include <d3d11.h>
208 #include "InkCollector.h"
210 // ERROR from wingdi.h (below) gets undefined by some code.
211 // #define ERROR 0
212 // #define RGN_ERROR ERROR
213 #define ERROR 0
215 #if !defined(SM_CONVERTIBLESLATEMODE)
216 # define SM_CONVERTIBLESLATEMODE 0x2003
217 #endif
219 // Win 8.1+ (_WIN32_WINNT_WINBLUE)
220 #if !defined(WM_DPICHANGED)
221 # define WM_DPICHANGED 0x02E0
222 #endif
224 // Win 8+ (_WIN32_WINNT_WIN8)
225 #if !defined(EVENT_OBJECT_CLOAKED)
226 # define EVENT_OBJECT_CLOAKED 0x8017
227 # define EVENT_OBJECT_UNCLOAKED 0x8018
228 #endif
230 #include "mozilla/gfx/DeviceManagerDx.h"
231 #include "mozilla/layers/APZInputBridge.h"
232 #include "mozilla/layers/InputAPZContext.h"
233 #include "mozilla/layers/KnowsCompositor.h"
234 #include "InputData.h"
236 #include "mozilla/TaskController.h"
237 #include "mozilla/Telemetry.h"
238 #include "mozilla/webrender/WebRenderAPI.h"
239 #include "mozilla/layers/IAPZCTreeManager.h"
241 #include "DirectManipulationOwner.h"
243 using namespace mozilla;
244 using namespace mozilla::dom;
245 using namespace mozilla::gfx;
246 using namespace mozilla::layers;
247 using namespace mozilla::widget;
248 using namespace mozilla::plugins;
250 /**************************************************************
251 **************************************************************
253 ** BLOCK: Variables
255 ** nsWindow Class static initializations and global variables.
257 **************************************************************
258 **************************************************************/
260 /**************************************************************
262 * SECTION: nsWindow statics
264 **************************************************************/
265 static const wchar_t kUser32LibName[] = L"user32.dll";
267 uint32_t nsWindow::sInstanceCount = 0;
268 bool nsWindow::sIsOleInitialized = false;
269 nsIWidget::Cursor nsWindow::sCurrentCursor = {};
270 nsWindow* nsWindow::sCurrentWindow = nullptr;
271 bool nsWindow::sJustGotDeactivate = false;
272 bool nsWindow::sJustGotActivate = false;
273 bool nsWindow::sIsInMouseCapture = false;
275 // Hook Data Members for Dropdowns. sProcessHook Tells the
276 // hook methods whether they should be processing the hook
277 // messages.
278 HHOOK nsWindow::sMsgFilterHook = nullptr;
279 HHOOK nsWindow::sCallProcHook = nullptr;
280 HHOOK nsWindow::sCallMouseHook = nullptr;
281 bool nsWindow::sProcessHook = false;
282 UINT nsWindow::sRollupMsgId = 0;
283 HWND nsWindow::sRollupMsgWnd = nullptr;
284 UINT nsWindow::sHookTimerId = 0;
286 // Used to prevent dispatching mouse events that do not originate from user
287 // input.
288 POINT nsWindow::sLastMouseMovePoint = {0};
290 bool nsWindow::sIsRestoringSession = false;
292 bool nsWindow::sTouchInjectInitialized = false;
293 InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
295 static SystemTimeConverter<DWORD>& TimeConverter() {
296 static SystemTimeConverter<DWORD> timeConverterSingleton;
297 return timeConverterSingleton;
300 // Global event hook for window cloaking. Never deregistered.
301 // - `Nothing` if not yet set.
302 // - `Some(nullptr)` if no attempt should be made to set it.
303 static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook =
304 IsWin8OrLater() ? Nothing() : Some(HWINEVENTHOOK(nullptr));
305 static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
307 namespace mozilla {
309 class CurrentWindowsTimeGetter {
310 public:
311 explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
313 DWORD GetCurrentTime() const { return ::GetTickCount(); }
315 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
316 DWORD currentTime = GetCurrentTime();
317 if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
318 // There's already one inflight with this timestamp. Don't
319 // send a duplicate.
320 return;
322 sBackwardsSkewStamp = Some(aNow);
323 sLastPostTime = currentTime;
324 static_assert(sizeof(WPARAM) >= sizeof(DWORD),
325 "Can't fit a DWORD in a WPARAM");
326 ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
329 static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
330 TimeStamp* aOutSkewStamp) {
331 if (aPostTime != sLastPostTime) {
332 // The SKEWFIX message is stale; we've sent a new one since then.
333 // Ignore this one.
334 return false;
336 MOZ_ASSERT(sBackwardsSkewStamp);
337 *aOutSkewStamp = sBackwardsSkewStamp.value();
338 sBackwardsSkewStamp = Nothing();
339 return true;
342 private:
343 static Maybe<TimeStamp> sBackwardsSkewStamp;
344 static DWORD sLastPostTime;
345 HWND mWnd;
348 Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
349 DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
351 } // namespace mozilla
353 /**************************************************************
355 * SECTION: globals variables
357 **************************************************************/
359 static const char* sScreenManagerContractID =
360 "@mozilla.org/gfx/screenmanager;1";
362 extern mozilla::LazyLogModule gWindowsLog;
364 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
366 // General purpose user32.dll hook object
367 static WindowsDllInterceptor sUser32Intercept;
369 // When the client area is extended out into the default window frame area,
370 // this is the minimum amount of space along the edge of resizable windows
371 // we will always display a resize cursor in, regardless of the underlying
372 // content.
373 static const int32_t kResizableBorderMinSize = 3;
375 // Getting this object from the window server can be expensive. Keep it
376 // around, also get it off the main thread. (See bug 1640852)
377 StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
378 static bool gInitializedVirtualDesktopManager = false;
380 // We should never really try to accelerate windows bigger than this. In some
381 // cases this might lead to no D3D9 acceleration where we could have had it
382 // but D3D9 does not reliably report when it supports bigger windows. 8192
383 // is as safe as we can get, we know at least D3D10 hardware always supports
384 // this, other hardware we expect to report correctly in D3D9.
385 #define MAX_ACCELERATED_DIMENSION 8192
387 // On window open (as well as after), Windows has an unfortunate habit of
388 // sending rather a lot of WM_NCHITTEST messages. Because we have to do point
389 // to DOM target conversions for these, we cache responses for a given
390 // coordinate this many milliseconds:
391 #define HITTEST_CACHE_LIFETIME_MS 50
393 #if defined(ACCESSIBILITY)
395 namespace mozilla {
398 * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
399 * injecting tiptsf.dll. The touchscreen process then posts registered messages
400 * to our main thread. The tiptsf hook picks up those registered messages and
401 * uses them as commands, some of which call into UIA, which then calls into
402 * MSAA, which then sends WM_GETOBJECT to us.
404 * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
405 * hook. Since thread-local hooks are called ahead of global hooks, we will
406 * see these registered messages before tiptsf does. At this point we can then
407 * raise a flag that blocks a11y before invoking CallNextHookEx which will then
408 * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
409 * flag by calling TIPMessageHandler::IsA11yBlocked().
411 * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
412 * function that also calls into UIA.
414 class TIPMessageHandler {
415 public:
416 ~TIPMessageHandler() {
417 if (mHook) {
418 ::UnhookWindowsHookEx(mHook);
422 static void Initialize() {
423 if (!IsWin8OrLater()) {
424 return;
427 if (sInstance) {
428 return;
431 sInstance = new TIPMessageHandler();
432 ClearOnShutdown(&sInstance);
435 static bool IsA11yBlocked() {
436 if (!sInstance) {
437 return false;
440 return sInstance->mA11yBlockCount > 0;
443 private:
444 TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
445 MOZ_ASSERT(NS_IsMainThread());
447 // Registered messages used by tiptsf
448 mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
449 mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
450 mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
451 mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
452 mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
453 mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
454 mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
456 mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
457 ::GetCurrentThreadId());
458 MOZ_ASSERT(mHook);
460 // On touchscreen devices, tiptsf.dll will have been loaded when STA COM was
461 // first initialized.
462 if (!IsWin10OrLater() && GetModuleHandle(L"tiptsf.dll") &&
463 !sProcessCaretEventsStub) {
464 sTipTsfInterceptor.Init("tiptsf.dll");
465 DebugOnly<bool> ok = sProcessCaretEventsStub.Set(
466 sTipTsfInterceptor, "ProcessCaretEvents", &ProcessCaretEventsHook);
467 MOZ_ASSERT(ok);
470 if (!sSendMessageTimeoutWStub) {
471 sUser32Intercept.Init("user32.dll");
472 DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
473 sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
474 MOZ_ASSERT(hooked);
478 class MOZ_RAII A11yInstantiationBlocker {
479 public:
480 A11yInstantiationBlocker() {
481 if (!TIPMessageHandler::sInstance) {
482 return;
484 ++TIPMessageHandler::sInstance->mA11yBlockCount;
487 ~A11yInstantiationBlocker() {
488 if (!TIPMessageHandler::sInstance) {
489 return;
491 MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
492 --TIPMessageHandler::sInstance->mA11yBlockCount;
496 friend class A11yInstantiationBlocker;
498 static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
499 if (aCode < 0 || !sInstance) {
500 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
503 MSG* msg = reinterpret_cast<MSG*>(aLParam);
504 UINT& msgCode = msg->message;
506 for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
507 if (msgCode == sInstance->mMessages[i]) {
508 A11yInstantiationBlocker block;
509 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
513 return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
516 static void CALLBACK ProcessCaretEventsHook(HWINEVENTHOOK aWinEventHook,
517 DWORD aEvent, HWND aHwnd,
518 LONG aObjectId, LONG aChildId,
519 DWORD aGeneratingTid,
520 DWORD aEventTime) {
521 A11yInstantiationBlocker block;
522 sProcessCaretEventsStub(aWinEventHook, aEvent, aHwnd, aObjectId, aChildId,
523 aGeneratingTid, aEventTime);
526 static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
527 WPARAM aWParam, LPARAM aLParam,
528 UINT aFlags, UINT aTimeout,
529 PDWORD_PTR aMsgResult) {
530 // We don't want to handle this unless the message is a WM_GETOBJECT that we
531 // want to block, and the aHwnd is a nsWindow that belongs to the current
532 // (i.e., main) thread.
533 if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
534 static_cast<LONG>(aLParam) != OBJID_CLIENT || !::NS_IsMainThread() ||
535 !WinUtils::GetNSWindowPtr(aHwnd) || !IsA11yBlocked()) {
536 return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
537 aTimeout, aMsgResult);
540 // In this case we want to fake the result that would happen if we had
541 // decided not to handle WM_GETOBJECT in our WndProc. We hand the message
542 // off to DefWindowProc to accomplish this.
543 *aMsgResult = static_cast<DWORD_PTR>(
544 ::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
546 return static_cast<LRESULT>(TRUE);
549 static WindowsDllInterceptor sTipTsfInterceptor;
550 static WindowsDllInterceptor::FuncHookType<WINEVENTPROC>
551 sProcessCaretEventsStub;
552 static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
553 sSendMessageTimeoutWStub;
554 static StaticAutoPtr<TIPMessageHandler> sInstance;
556 HHOOK mHook;
557 UINT mMessages[7];
558 uint32_t mA11yBlockCount;
561 WindowsDllInterceptor TIPMessageHandler::sTipTsfInterceptor;
562 WindowsDllInterceptor::FuncHookType<WINEVENTPROC>
563 TIPMessageHandler::sProcessCaretEventsStub;
564 WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
565 TIPMessageHandler::sSendMessageTimeoutWStub;
566 StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
568 } // namespace mozilla
570 #endif // defined(ACCESSIBILITY)
572 namespace mozilla {
574 // This task will get the VirtualDesktopManager from the generic thread pool
575 // since doing this on the main thread on startup causes performance issues.
577 // See bug 1640852.
579 // This should be fine and should not require any locking, as when the main
580 // thread will access it, if it races with this function it will either find
581 // it to be null or to have a valid value.
582 class InitializeVirtualDesktopManagerTask : public Task {
583 public:
584 InitializeVirtualDesktopManagerTask() : Task(false, kDefaultPriorityValue) {}
586 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
587 bool GetName(nsACString& aName) override {
588 aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
589 return true;
591 #endif
593 virtual bool Run() override {
594 if (!IsWin10OrLater()) {
595 return true;
598 RefPtr<IVirtualDesktopManager> desktopManager;
599 HRESULT hr = ::CoCreateInstance(
600 CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
601 __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
602 if (FAILED(hr)) {
603 return true;
606 gVirtualDesktopManager = desktopManager;
607 return true;
611 static bool GetMouseVanishSystemPref(bool aShouldUpdate) {
612 static Maybe<bool> sCachedMouseVanishSystemPref;
614 if (aShouldUpdate) {
615 sCachedMouseVanishSystemPref.reset();
618 if (sCachedMouseVanishSystemPref.isNothing()) {
619 BOOL mouseVanishSystemPref;
620 BOOL ok = ::SystemParametersInfo(SPI_GETMOUSEVANISH, 0,
621 &mouseVanishSystemPref, 0);
622 // If getting system pref failed, just use user pref.
623 sCachedMouseVanishSystemPref.emplace(
624 ok ? mouseVanishSystemPref
625 : StaticPrefs::widget_windows_hide_cursor_when_typing());
628 return *sCachedMouseVanishSystemPref;
631 static bool IsMouseVanishKey(WPARAM aVirtKey) {
632 switch (aVirtKey) {
633 case VK_SHIFT:
634 case VK_LSHIFT:
635 case VK_RSHIFT:
636 case VK_CONTROL:
637 case VK_LCONTROL:
638 case VK_RCONTROL:
639 case VK_MENU:
640 case VK_LMENU:
641 case VK_RMENU:
642 case VK_LWIN:
643 case VK_RWIN:
644 case VK_INSERT:
645 case VK_DELETE:
646 case VK_HOME:
647 case VK_END:
648 case VK_ESCAPE:
649 case VK_PRINT:
650 case VK_UP:
651 case VK_DOWN:
652 case VK_LEFT:
653 case VK_RIGHT:
654 case VK_PRIOR: // PgUp
655 case VK_NEXT: // PgDn
656 case 0xff: // Undefined. May be sent for Fn key.
657 return false;
658 default:
659 // Vanish unless Ctrl or Alt is also pressed, or if a key in
660 // a relevant range is pressed.
661 // The range between VK_F1 and VK_LAUNCH_APP2 includes control,
662 // function, browser, volume and media keys, all of which we ignore.
663 return (GetKeyState(VK_CONTROL) & 0x8000) != 0x8000 &&
664 (GetKeyState(VK_MENU) & 0x8000) != 0x8000 &&
665 (aVirtKey < VK_F1 || aVirtKey > VK_LAUNCH_APP2);
670 * Hide/unhide the cursor if the correct Windows and Firefox settings are set.
672 static void MaybeHideCursor(bool aShouldHide) {
673 static bool sMouseExists = [] {
674 // Before the first call to ShowCursor, the visibility count is 0
675 // if there is a mouse installed and -1 if not.
676 int count = ::ShowCursor(FALSE);
677 ::ShowCursor(TRUE);
678 return count == -1;
679 }();
681 if (!sMouseExists) {
682 return;
685 static bool sIsHidden = false;
686 bool shouldHide = aShouldHide &&
687 StaticPrefs::widget_windows_hide_cursor_when_typing() &&
688 GetMouseVanishSystemPref(false);
690 if (shouldHide != sIsHidden) {
691 [[maybe_unused]] int count = ::ShowCursor(aShouldHide ? FALSE : TRUE);
692 MOZ_ASSERT(count == (aShouldHide ? -1 : 0));
693 sIsHidden = shouldHide;
697 // Ground-truth query: does Windows claim the window is cloaked right now?
698 static bool IsCloaked(HWND hwnd) {
699 DWORD cloakedState;
700 HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
701 sizeof(cloakedState));
703 if (FAILED(hr)) {
704 MOZ_LOG(sCloakingLog, LogLevel::Warning,
705 ("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
706 return false;
709 return cloakedState != 0;
712 } // namespace mozilla
714 /**************************************************************
715 **************************************************************
717 ** BLOCK: nsIWidget impl.
719 ** nsIWidget interface implementation, broken down into
720 ** sections.
722 **************************************************************
723 **************************************************************/
725 /**************************************************************
727 * SECTION: nsWindow construction and destruction
729 **************************************************************/
731 nsWindow::nsWindow(bool aIsChildWindow)
732 : nsBaseWidget(BorderStyle::Default),
733 mBrush(::CreateSolidBrush(NSRGB_2_COLOREF(::GetSysColor(COLOR_BTNFACE)))),
734 mFrameState(std::in_place, this),
735 mIsChildWindow(aIsChildWindow),
736 mLastPaintEndTime(TimeStamp::Now()),
737 mCachedHitTestTime(TimeStamp::Now()),
738 mSizeConstraintsScale(GetDefaultScale().scale),
739 mDesktopId("DesktopIdMutex") {
740 MOZ_ASSERT(mWindowType == WindowType::Child);
742 if (!gInitializedVirtualDesktopManager) {
743 TaskController::Get()->AddTask(
744 MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
745 gInitializedVirtualDesktopManager = true;
748 // Global initialization
749 if (!sInstanceCount) {
750 // Global app registration id for Win7 and up. See
751 // WinTaskbar.cpp for details.
752 // MSIX packages explicitly do not support setting the appid from within
753 // the app, as it is set in the package manifest instead.
754 if (!WinUtils::HasPackageIdentity()) {
755 mozilla::widget::WinTaskbar::RegisterAppUserModelID();
757 KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
758 #if defined(ACCESSIBILITY)
759 mozilla::TIPMessageHandler::Initialize();
760 #endif // defined(ACCESSIBILITY)
761 if (SUCCEEDED(::OleInitialize(nullptr))) {
762 sIsOleInitialized = true;
764 NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
765 MouseScrollHandler::Initialize();
766 // Init theme data
767 nsUXThemeData::UpdateNativeThemeInfo();
768 RedirectedKeyDownMessageManager::Forget();
769 if (mPointerEvents.ShouldEnableInkCollector()) {
770 InkCollector::sInkCollector = new InkCollector();
772 } // !sInstanceCount
774 sInstanceCount++;
777 nsWindow::~nsWindow() {
778 mInDtor = true;
780 // If the widget was released without calling Destroy() then the native window
781 // still exists, and we need to destroy it. Destroy() will early-return if it
782 // was already called. In any case it is important to call it before
783 // destroying mPresentLock (cf. 1156182).
784 Destroy();
786 // Free app icon resources. This must happen after `OnDestroy` (see bug
787 // 708033).
788 if (mIconSmall) ::DestroyIcon(mIconSmall);
790 if (mIconBig) ::DestroyIcon(mIconBig);
792 sInstanceCount--;
794 // Global shutdown
795 if (sInstanceCount == 0) {
796 if (InkCollector::sInkCollector) {
797 InkCollector::sInkCollector->Shutdown();
798 InkCollector::sInkCollector = nullptr;
800 IMEHandler::Terminate();
801 sCurrentCursor = {};
802 if (sIsOleInitialized) {
803 ::OleFlushClipboard();
804 ::OleUninitialize();
805 sIsOleInitialized = false;
809 NS_IF_RELEASE(mNativeDragTarget);
812 /**************************************************************
814 * SECTION: nsIWidget::Create, nsIWidget::Destroy
816 * Creating and destroying windows for this widget.
818 **************************************************************/
820 // Allow Derived classes to modify the height that is passed
821 // when the window is created or resized.
822 int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
824 void nsWindow::SendAnAPZEvent(InputData& aEvent) {
825 LRESULT popupHandlingResult;
826 if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
827 // We need to consume the event after using it to roll up the popup(s).
828 return;
831 if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
832 // Give the swipe tracker a first pass at the event. If a new pan gesture
833 // has been started since the beginning of the swipe, the swipe tracker
834 // will know to ignore the event.
835 nsEventStatus status =
836 mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
837 if (status == nsEventStatus_eConsumeNoDefault) {
838 return;
842 APZEventResult result;
843 if (mAPZC) {
844 result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
846 if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
847 return;
850 MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
851 aEvent.mInputType == PINCHGESTURE_INPUT);
853 if (aEvent.mInputType == PANGESTURE_INPUT) {
854 PanGestureInput& panInput = aEvent.AsPanGestureInput();
855 WidgetWheelEvent event = panInput.ToWidgetEvent(this);
856 if (!mAPZC) {
857 if (MayStartSwipeForNonAPZ(panInput)) {
858 return;
860 } else {
861 event = MayStartSwipeForAPZ(panInput, result);
864 ProcessUntransformedAPZEvent(&event, result);
866 return;
869 PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
870 WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
871 ProcessUntransformedAPZEvent(&event, result);
874 void nsWindow::RecreateDirectManipulationIfNeeded() {
875 DestroyDirectManipulation();
877 if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
878 return;
881 if (!(StaticPrefs::apz_allow_zooming() ||
882 StaticPrefs::apz_windows_use_direct_manipulation()) ||
883 StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
884 return;
887 if (!IsWin10OrLater()) {
888 // Chrome source said the Windows Direct Manipulation implementation had
889 // important bugs until Windows 10 (although IE on Windows 8.1 seems to use
890 // Direct Manipulation).
891 return;
894 mDmOwner = MakeUnique<DirectManipulationOwner>(this);
896 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
897 GetHeight(mBounds.Height()));
898 mDmOwner->Init(bounds);
901 void nsWindow::ResizeDirectManipulationViewport() {
902 if (mDmOwner) {
903 LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
904 GetHeight(mBounds.Height()));
905 mDmOwner->ResizeViewport(bounds);
909 void nsWindow::DestroyDirectManipulation() {
910 if (mDmOwner) {
911 mDmOwner->Destroy();
912 mDmOwner.reset();
916 // Create the proper widget
917 nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
918 const LayoutDeviceIntRect& aRect,
919 widget::InitData* aInitData) {
920 // Historical note: there was once some belief and/or intent that nsWindows
921 // could be created on arbitrary threads, and this may still be reflected in
922 // some comments.
923 MOZ_ASSERT(NS_IsMainThread());
925 widget::InitData defaultInitData;
926 if (!aInitData) aInitData = &defaultInitData;
928 nsIWidget* baseParent =
929 aInitData->mWindowType == WindowType::Dialog ||
930 aInitData->mWindowType == WindowType::TopLevel ||
931 aInitData->mWindowType == WindowType::Invisible
932 ? nullptr
933 : aParent;
935 mIsTopWidgetWindow = (nullptr == baseParent);
936 mBounds = aRect;
938 // Ensure that the toolkit is created.
939 nsToolkit::GetToolkit();
941 BaseCreate(baseParent, aInitData);
943 HWND parent;
944 if (aParent) { // has a nsIWidget parent
945 parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
946 mParent = aParent;
947 } else { // has a nsNative parent
948 parent = (HWND)aNativeParent;
949 mParent =
950 aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
953 mIsRTL = aInitData->mRTL;
954 mForMenupopupFrame = aInitData->mForMenupopupFrame;
955 mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
956 mAlwaysOnTop = aInitData->mAlwaysOnTop;
957 mResizable = aInitData->mResizable;
959 DWORD style = WindowStyle();
960 DWORD extendedStyle = WindowExStyle();
962 // When window is PiP window on Windows7, WS_EX_COMPOSITED is set to suppress
963 // flickering during resizing with hardware acceleration.
964 bool isPIPWindow = aInitData && aInitData->mPIPWindow;
965 if (isPIPWindow && !IsWin8OrLater() &&
966 gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) &&
967 WidgetTypeSupportsAcceleration()) {
968 extendedStyle |= WS_EX_COMPOSITED;
971 if (mWindowType == WindowType::Popup) {
972 if (!aParent) {
973 parent = nullptr;
976 if (!IsWin8OrLater() && HasBogusPopupsDropShadowOnMultiMonitor() &&
977 ShouldUseOffMainThreadCompositing()) {
978 extendedStyle |= WS_EX_COMPOSITED;
980 } else if (mWindowType == WindowType::Invisible) {
981 // Make sure CreateWindowEx succeeds at creating a toplevel window
982 style &= ~0x40000000; // WS_CHILDWINDOW
983 } else {
984 // See if the caller wants to explictly set clip children and clip siblings
985 if (aInitData->mClipChildren) {
986 style |= WS_CLIPCHILDREN;
987 } else {
988 style &= ~WS_CLIPCHILDREN;
990 if (aInitData->mClipSiblings) {
991 style |= WS_CLIPSIBLINGS;
995 const wchar_t* className = ChooseWindowClass(mWindowType, mForMenupopupFrame);
997 // Take specific actions when creating the first top-level window
998 static bool sFirstTopLevelWindowCreated = false;
999 if (aInitData->mWindowType == WindowType::TopLevel && !aParent &&
1000 !sFirstTopLevelWindowCreated) {
1001 sFirstTopLevelWindowCreated = true;
1002 mWnd = ConsumePreXULSkeletonUIHandle();
1003 auto skeletonUIError = GetPreXULSkeletonUIErrorReason();
1004 if (skeletonUIError) {
1005 nsAutoString errorString(
1006 GetPreXULSkeletonUIErrorString(skeletonUIError.value()));
1007 Telemetry::ScalarSet(
1008 Telemetry::ScalarID::STARTUP_SKELETON_UI_DISABLED_REASON,
1009 errorString);
1011 if (mWnd) {
1012 MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
1013 "The skeleton UI window style should match the expected "
1014 "style for the first window created");
1015 MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
1016 "The skeleton UI window extended style should match the "
1017 "expected extended style for the first window created");
1018 MOZ_ASSERT(
1019 ::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
1020 "The skeleton UI window should be created on the same thread as "
1021 "other windows");
1022 mIsShowingPreXULSkeletonUI = true;
1024 // If we successfully consumed the pre-XUL skeleton UI, just update
1025 // our internal state to match what is currently being displayed.
1026 mIsVisible = true;
1027 mIsCloaked = mozilla::IsCloaked(mWnd);
1028 mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
1030 // These match the margins set in browser-tabsintitlebar.js with
1031 // default prefs on Windows. Bug 1673092 tracks lining this up with
1032 // that more correctly instead of hard-coding it.
1033 SetNonClientMargins(LayoutDeviceIntMargin(0, 2, 2, 2));
1035 // Reset the WNDPROC for this window and its whole class, as we had
1036 // to use our own WNDPROC when creating the the skeleton UI window.
1037 ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
1038 reinterpret_cast<LONG_PTR>(
1039 WinUtils::NonClientDpiScalingDefWindowProcW));
1040 ::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
1041 reinterpret_cast<LONG_PTR>(
1042 WinUtils::NonClientDpiScalingDefWindowProcW));
1046 if (!mWnd) {
1047 mWnd =
1048 ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
1049 aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
1050 parent, nullptr, nsToolkit::mDllInstance, nullptr);
1053 if (!mWnd) {
1054 NS_WARNING("nsWindow CreateWindowEx failed.");
1055 return NS_ERROR_FAILURE;
1058 if (!sWinCloakEventHook) {
1059 MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
1061 // C++03 lambda approximation until P2173R1 is available (-std=c++2b)
1062 struct StdcallLambda {
1063 static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
1064 DWORD event, HWND hwnd,
1065 LONG idObject, LONG idChild,
1066 DWORD idEventThread,
1067 DWORD dwmsEventTime) {
1068 const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
1069 nsWindow::OnCloakEvent(hwnd, isCloaked);
1073 const HWINEVENTHOOK hook = ::SetWinEventHook(
1074 EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
1075 &StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
1076 ::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
1077 sWinCloakEventHook = Some(hook);
1079 if (!hook) {
1080 const DWORD err = ::GetLastError();
1081 MOZ_LOG(sCloakingLog, LogLevel::Error,
1082 ("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
1083 err));
1087 if (aInitData->mIsPrivate) {
1088 if (NimbusFeatures::GetBool("majorRelease2022"_ns,
1089 "feltPrivacyWindowSeparation"_ns, true) &&
1090 // Although permanent Private Browsing mode is indeed Private Browsing,
1091 // we choose to make it look like regular Firefox in terms of the icon
1092 // it uses (which also means we shouldn't use the Private Browsing
1093 // AUMID).
1094 !StaticPrefs::browser_privatebrowsing_autostart()) {
1095 RefPtr<IPropertyStore> pPropStore;
1096 if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
1097 getter_AddRefs(pPropStore)))) {
1098 PROPVARIANT pv;
1099 nsAutoString aumid;
1100 // make sure we're using the private browsing AUMID so that taskbar
1101 // grouping works properly
1102 Unused << NS_WARN_IF(
1103 !mozilla::widget::WinTaskbar::GenerateAppUserModelID(aumid, true));
1104 if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
1105 if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
1106 pPropStore->Commit();
1109 PropVariantClear(&pv);
1112 HICON icon = ::LoadIconW(::GetModuleHandleW(nullptr),
1113 MAKEINTRESOURCEW(IDI_PBMODE));
1114 SetBigIcon(icon);
1115 SetSmallIcon(icon);
1119 mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
1121 // If mDefaultScale is set before mWnd has been set, it will have the scale of
1122 // the primary monitor, rather than the monitor that the window is actually
1123 // on. For non-popup windows this gets corrected by the WM_DPICHANGED message
1124 // which resets mDefaultScale, but for popup windows we don't reset
1125 // mDefaultScale on that message. In order to ensure that popup windows
1126 // spawned on a non-primary monitor end up with the correct scale, we reset
1127 // mDefaultScale here so that it gets recomputed using the correct monitor now
1128 // that we have a mWnd.
1129 mDefaultScale = -1.0;
1131 if (mIsRTL) {
1132 DWORD dwAttribute = TRUE;
1133 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1134 sizeof dwAttribute);
1137 UpdateDarkModeToolbar();
1139 if (mOpeningAnimationSuppressed) {
1140 SuppressAnimation(true);
1143 if (mAlwaysOnTop) {
1144 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
1145 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1148 if (mWindowType != WindowType::Invisible &&
1149 MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
1150 // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
1152 // We create two zero-sized windows as descendants of the top-level window,
1153 // like so:
1155 // Top-level window (MozillaWindowClass)
1156 // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
1157 // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
1159 // We need to have the middle window, otherwise the Trackpoint driver
1160 // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
1161 // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
1162 // window hierarchy until they are handled by nsWindow::WindowProc.
1163 // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
1164 // but these do not propagate automatically, so we have the window
1165 // procedure pretend that they were dispatched to the top-level window
1166 // instead.
1168 // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
1169 // is given below so that it catches the Trackpoint driver's heuristics.
1170 HWND scrollContainerWnd = ::CreateWindowW(
1171 className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
1172 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1173 HWND scrollableWnd = ::CreateWindowW(
1174 className, L"FAKETRACKPOINTSCROLLABLE",
1175 WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
1176 scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
1178 // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
1179 // WindowProcInternal can distinguish it from the top-level window
1180 // easily.
1181 ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
1183 // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
1184 // old window procedure in its "user data".
1185 WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
1186 scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
1187 ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
1190 // We will start receiving native events after associating with our native
1191 // window. We will also become the output of WinUtils::GetNSWindowPtr for that
1192 // window.
1193 if (!AssociateWithNativeWindow()) {
1194 return NS_ERROR_FAILURE;
1197 // Starting with Windows XP, a process always runs within a terminal services
1198 // session. In order to play nicely with RDP, fast user switching, and the
1199 // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
1200 // our HWND in order to receive this message.
1201 DebugOnly<BOOL> wtsRegistered =
1202 ::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
1203 NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
1205 mDefaultIMC.Init(this);
1206 IMEHandler::InitInputContext(this, mInputContext);
1208 static bool a11yPrimed = false;
1209 if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
1210 a11yPrimed = true;
1211 if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
1212 ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
1216 RecreateDirectManipulationIfNeeded();
1218 return NS_OK;
1221 void nsWindow::LocalesChanged() {
1222 bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
1223 if (mIsRTL != isRTL) {
1224 DWORD dwAttribute = isRTL;
1225 DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
1226 sizeof dwAttribute);
1227 mIsRTL = isRTL;
1231 // Close this nsWindow
1232 void nsWindow::Destroy() {
1233 // WM_DESTROY has already fired, avoid calling it twice
1234 if (mOnDestroyCalled) return;
1236 // Don't destroy windows that have file pickers open, we'll tear these down
1237 // later once the picker is closed.
1238 mDestroyCalled = true;
1239 if (mPickerDisplayCount) return;
1241 // During the destruction of all of our children, make sure we don't get
1242 // deleted.
1243 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1245 DestroyDirectManipulation();
1248 * On windows the LayerManagerOGL destructor wants the widget to be around for
1249 * cleanup. It also would like to have the HWND intact, so we nullptr it here.
1251 DestroyLayerManager();
1253 InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
1254 mDeviceNotifyHandle = nullptr;
1256 // The DestroyWindow function destroys the specified window. The function
1257 // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
1258 // and remove the keyboard focus from it. The function also destroys the
1259 // window's menu, flushes the thread message queue, destroys timers, removes
1260 // clipboard ownership, and breaks the clipboard viewer chain (if the window
1261 // is at the top of the viewer chain).
1263 // If the specified window is a parent or owner window, DestroyWindow
1264 // automatically destroys the associated child or owned windows when it
1265 // destroys the parent or owner window. The function first destroys child or
1266 // owned windows, and then it destroys the parent or owner window.
1267 VERIFY(::DestroyWindow(mWnd));
1269 // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
1270 // OnDestroy() didn't get called, call it now.
1271 if (false == mOnDestroyCalled) {
1272 MSGResult msgResult;
1273 mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
1274 OnDestroy();
1278 /**************************************************************
1280 * SECTION: Window class utilities
1282 * Utilities for calculating the proper window class name for
1283 * Create window.
1285 **************************************************************/
1287 /* static */
1288 const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
1289 UINT aExtraStyle, LPWSTR aIconID) {
1290 WNDCLASSW wc;
1291 if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
1292 // already registered
1293 return aClassName;
1296 wc.style = CS_DBLCLKS | aExtraStyle;
1297 wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
1298 wc.cbClsExtra = 0;
1299 wc.cbWndExtra = 0;
1300 wc.hInstance = nsToolkit::mDllInstance;
1301 wc.hIcon =
1302 aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
1303 wc.hCursor = nullptr;
1304 wc.hbrBackground = nullptr;
1305 wc.lpszMenuName = nullptr;
1306 wc.lpszClassName = aClassName;
1308 if (!::RegisterClassW(&wc)) {
1309 // For older versions of Win32 (i.e., not XP), the registration may
1310 // fail with aExtraStyle, so we have to re-register without it.
1311 wc.style = CS_DBLCLKS;
1312 ::RegisterClassW(&wc);
1314 return aClassName;
1317 static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
1319 /* static */
1320 const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType,
1321 bool aForMenupopupFrame) {
1322 MOZ_ASSERT_IF(aForMenupopupFrame, aWindowType == WindowType::Popup);
1323 switch (aWindowType) {
1324 case WindowType::Invisible:
1325 return RegisterWindowClass(kClassNameHidden, 0, gStockApplicationIcon);
1326 case WindowType::Dialog:
1327 return RegisterWindowClass(kClassNameDialog, 0, 0);
1328 case WindowType::Popup:
1329 if (aForMenupopupFrame) {
1330 return RegisterWindowClass(kClassNameDropShadow, CS_DROPSHADOW,
1331 gStockApplicationIcon);
1333 [[fallthrough]];
1334 default:
1335 return RegisterWindowClass(GetMainWindowClass(), 0,
1336 gStockApplicationIcon);
1340 /**************************************************************
1342 * SECTION: Window styles utilities
1344 * Return the proper windows styles and extended styles.
1346 **************************************************************/
1348 // Return nsWindow styles
1349 DWORD nsWindow::WindowStyle() {
1350 DWORD style;
1352 switch (mWindowType) {
1353 case WindowType::Child:
1354 style = WS_OVERLAPPED;
1355 break;
1357 case WindowType::Dialog:
1358 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
1359 DS_MODALFRAME | WS_CLIPCHILDREN;
1360 if (mBorderStyle != BorderStyle::Default)
1361 style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
1362 break;
1364 case WindowType::Popup:
1365 style = WS_POPUP | WS_OVERLAPPED;
1366 break;
1368 default:
1369 NS_ERROR("unknown border style");
1370 [[fallthrough]];
1372 case WindowType::TopLevel:
1373 case WindowType::Invisible:
1374 style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
1375 WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
1376 break;
1379 if (mBorderStyle != BorderStyle::Default &&
1380 mBorderStyle != BorderStyle::All) {
1381 if (mBorderStyle == BorderStyle::None ||
1382 !(mBorderStyle & BorderStyle::Border))
1383 style &= ~WS_BORDER;
1385 if (mBorderStyle == BorderStyle::None ||
1386 !(mBorderStyle & BorderStyle::Title)) {
1387 style &= ~WS_DLGFRAME;
1390 if (mBorderStyle == BorderStyle::None ||
1391 !(mBorderStyle & BorderStyle::Close))
1392 style &= ~0;
1393 // XXX The close box can only be removed by changing the window class,
1394 // as far as I know --- roc+moz@cs.cmu.edu
1396 if (mBorderStyle == BorderStyle::None ||
1397 !(mBorderStyle & (BorderStyle::Menu | BorderStyle::Close)))
1398 style &= ~WS_SYSMENU;
1399 // Looks like getting rid of the system menu also does away with the
1400 // close box. So, we only get rid of the system menu if you want neither it
1401 // nor the close box. How does the Windows "Dialog" window class get just
1402 // closebox and no sysmenu? Who knows.
1404 if (mBorderStyle == BorderStyle::None ||
1405 !(mBorderStyle & BorderStyle::ResizeH))
1406 style &= ~WS_THICKFRAME;
1408 if (mBorderStyle == BorderStyle::None ||
1409 !(mBorderStyle & BorderStyle::Minimize))
1410 style &= ~WS_MINIMIZEBOX;
1412 if (mBorderStyle == BorderStyle::None ||
1413 !(mBorderStyle & BorderStyle::Maximize))
1414 style &= ~WS_MAXIMIZEBOX;
1416 if (IsPopupWithTitleBar()) {
1417 style |= WS_CAPTION;
1418 if (mBorderStyle & BorderStyle::Close) {
1419 style |= WS_SYSMENU;
1424 if (mIsChildWindow) {
1425 style |= WS_CLIPCHILDREN;
1426 if (!(style & WS_POPUP)) {
1427 style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
1431 VERIFY_WINDOW_STYLE(style);
1432 return style;
1435 // Return nsWindow extended styles
1436 DWORD nsWindow::WindowExStyle() {
1437 switch (mWindowType) {
1438 case WindowType::Child:
1439 return 0;
1441 case WindowType::Dialog:
1442 return WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME;
1444 case WindowType::Popup: {
1445 DWORD extendedStyle = WS_EX_TOOLWINDOW;
1446 if (mPopupLevel == PopupLevel::Top) extendedStyle |= WS_EX_TOPMOST;
1447 return extendedStyle;
1449 default:
1450 NS_ERROR("unknown border style");
1451 [[fallthrough]];
1453 case WindowType::TopLevel:
1454 case WindowType::Invisible:
1455 return WS_EX_WINDOWEDGE;
1459 /**************************************************************
1461 * SECTION: Native window association utilities
1463 * Used in Create and Destroy. A nsWindow can associate with its
1464 * underlying native window mWnd. Once a native window is
1465 * associated with a nsWindow, its native events will be handled
1466 * by the static member function nsWindow::WindowProc. Moreover,
1467 * the association will be registered in the WinUtils association
1468 * list, that is, calling WinUtils::GetNSWindowPtr on the native
1469 * window will return the associated nsWindow. This is used in
1470 * nsWindow::WindowProc to correctly dispatch native events to
1471 * the handler methods defined in nsWindow, even though it is a
1472 * static member function.
1474 * After dissociation, the native events of the native window will
1475 * no longer be handled by nsWindow::WindowProc, and will thus not
1476 * be dispatched to the nsWindow native event handler methods.
1477 * Moreover, the association will no longer be registered in the
1478 * WinUtils association list, so calling WinUtils::GetNSWindowPtr
1479 * on the native window will return nullptr.
1481 **************************************************************/
1483 bool nsWindow::AssociateWithNativeWindow() {
1484 if (!mWnd || !IsWindow(mWnd)) {
1485 NS_ERROR("Invalid window handle");
1486 return false;
1489 // Connect the this pointer to the native window handle.
1490 // This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
1491 // uses WinUtils::GetNSWindowPtr internally.
1492 WinUtils::SetNSWindowPtr(mWnd, this);
1494 ::SetLastError(ERROR_SUCCESS);
1495 const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1496 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
1497 if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
1498 NS_ERROR("Failure in SetWindowLongPtrW");
1499 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1500 return false;
1503 mPrevWndProc.emplace(prevWndProc);
1504 return true;
1507 void nsWindow::DissociateFromNativeWindow() {
1508 if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
1509 return;
1512 DebugOnly<WNDPROC> wndProcBeforeDissociate =
1513 reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
1514 mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
1515 NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
1516 "Unstacked an unexpected native window procedure");
1518 WinUtils::SetNSWindowPtr(mWnd, nullptr);
1519 mPrevWndProc.reset();
1522 /**************************************************************
1524 * SECTION: nsIWidget::SetParent, nsIWidget::GetParent
1526 * Set or clear the parent widgets using window properties, and
1527 * handles calculating native parent handles.
1529 **************************************************************/
1531 // Get and set parent widgets
1532 void nsWindow::SetParent(nsIWidget* aNewParent) {
1533 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
1534 nsIWidget* parent = GetParent();
1535 if (parent) {
1536 parent->RemoveChild(this);
1539 mParent = aNewParent;
1541 if (aNewParent) {
1542 ReparentNativeWidget(aNewParent);
1543 aNewParent->AddChild(this);
1544 return;
1546 if (mWnd) {
1547 // If we have no parent, SetParent should return the desktop.
1548 VERIFY(::SetParent(mWnd, nullptr));
1549 RecreateDirectManipulationIfNeeded();
1553 void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
1554 MOZ_ASSERT(aNewParent, "null widget");
1556 mParent = aNewParent;
1557 if (mWindowType == WindowType::Popup) {
1558 return;
1560 HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
1561 NS_ASSERTION(newParent, "Parent widget has a null native window handle");
1562 if (newParent && mWnd) {
1563 ::SetParent(mWnd, newParent);
1564 RecreateDirectManipulationIfNeeded();
1568 nsIWidget* nsWindow::GetParent(void) {
1569 if (mIsTopWidgetWindow) {
1570 return nullptr;
1572 if (mInDtor || mOnDestroyCalled) {
1573 return nullptr;
1575 return mParent;
1578 static int32_t RoundDown(double aDouble) {
1579 return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
1580 : static_cast<int32_t>(ceil(aDouble));
1583 float nsWindow::GetDPI() {
1584 float dpi = 96.0f;
1585 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
1586 if (screen) {
1587 screen->GetDpi(&dpi);
1589 return dpi;
1592 double nsWindow::GetDefaultScaleInternal() {
1593 if (mDefaultScale <= 0.0) {
1594 mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
1596 return mDefaultScale;
1599 int32_t nsWindow::LogToPhys(double aValue) {
1600 return WinUtils::LogToPhys(
1601 ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
1604 nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
1605 return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
1608 nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
1609 if (mIsTopWidgetWindow) {
1610 // Must use a flag instead of mWindowType to tell if the window is the
1611 // owned by the topmost widget, because a child window can be embedded
1612 // inside a HWND which is not associated with a nsIWidget.
1613 return nullptr;
1616 // If this widget has already been destroyed, pretend we have no parent.
1617 // This corresponds to code in Destroy which removes the destroyed
1618 // widget from its parent's child list.
1619 if (mInDtor || mOnDestroyCalled) return nullptr;
1621 // aIncludeOwner set to true implies walking the parent chain to retrieve the
1622 // root owner. aIncludeOwner set to false implies the search will stop at the
1623 // true parent (default).
1624 nsWindow* widget = nullptr;
1625 if (mWnd) {
1626 HWND parent = nullptr;
1627 if (aIncludeOwner)
1628 parent = ::GetParent(mWnd);
1629 else
1630 parent = ::GetAncestor(mWnd, GA_PARENT);
1632 if (parent) {
1633 widget = WinUtils::GetNSWindowPtr(parent);
1634 if (widget) {
1635 // If the widget is in the process of being destroyed then
1636 // do NOT return it
1637 if (widget->mInDtor) {
1638 widget = nullptr;
1644 return widget;
1647 /**************************************************************
1649 * SECTION: nsIWidget::Show
1651 * Hide or show this component.
1653 **************************************************************/
1655 void nsWindow::Show(bool bState) {
1656 if (bState && mIsShowingPreXULSkeletonUI) {
1657 // The first time we decide to actually show the window is when we decide
1658 // that we've taken over the window from the skeleton UI, and we should
1659 // no longer treat resizes / moves specially.
1660 mIsShowingPreXULSkeletonUI = false;
1661 #if defined(ACCESSIBILITY)
1662 // If our HWND has focus and the a11y engine hasn't started yet, fire a
1663 // focus win event. Windows already did this when the skeleton UI appeared,
1664 // but a11y wouldn't have been able to start at that point even if a client
1665 // responded. Firing this now gives clients the chance to respond with
1666 // WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
1667 // this if the a11y engine has already started because it has probably
1668 // already fired focus on a descendant.
1669 if (::GetFocus() == mWnd && !GetAccService()) {
1670 ::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
1672 #endif // defined(ACCESSIBILITY)
1675 if (mForMenupopupFrame) {
1676 MOZ_ASSERT(ChooseWindowClass(mWindowType, mForMenupopupFrame) ==
1677 kClassNameDropShadow);
1678 const bool shouldUseDropShadow = [&] {
1679 if (mTransparencyMode == TransparencyMode::Transparent) {
1680 return false;
1682 if (HasBogusPopupsDropShadowOnMultiMonitor() &&
1683 WinUtils::GetMonitorCount() > 1 &&
1684 !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
1685 // See bug 603793. When we try to draw D3D9/10 windows with a drop
1686 // shadow without the DWM on a secondary monitor, windows fails to
1687 // composite our windows correctly. We therefor switch off the drop
1688 // shadow for pop-up windows when the DWM is disabled and two monitors
1689 // are connected.
1690 return false;
1692 return true;
1693 }();
1695 static bool sShadowEnabled = true;
1696 if (sShadowEnabled != shouldUseDropShadow) {
1697 ::SetClassLongA(mWnd, GCL_STYLE, shouldUseDropShadow ? CS_DROPSHADOW : 0);
1698 sShadowEnabled = shouldUseDropShadow;
1701 // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
1702 // some popup menus to become invisible.
1703 LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
1704 if (exStyle & WS_EX_LAYERED) {
1705 ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
1709 bool syncInvalidate = false;
1711 bool wasVisible = mIsVisible;
1712 // Set the status now so that anyone asking during ShowWindow or
1713 // SetWindowPos would get the correct answer.
1714 mIsVisible = bState;
1716 // We may have cached an out of date visible state. This can happen
1717 // when session restore sets the full screen mode.
1718 if (mIsVisible)
1719 mOldStyle |= WS_VISIBLE;
1720 else
1721 mOldStyle &= ~WS_VISIBLE;
1723 if (mWnd) {
1724 if (bState) {
1725 if (!wasVisible && mWindowType == WindowType::TopLevel) {
1726 // speed up the initial paint after show for
1727 // top level windows:
1728 syncInvalidate = true;
1730 // Set the cursor before showing the window to avoid the default wait
1731 // cursor.
1732 SetCursor(Cursor{eCursor_standard});
1734 switch (mFrameState->GetSizeMode()) {
1735 case nsSizeMode_Fullscreen:
1736 ::ShowWindow(mWnd, SW_SHOW);
1737 break;
1738 case nsSizeMode_Maximized:
1739 ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
1740 break;
1741 case nsSizeMode_Minimized:
1742 ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
1743 break;
1744 default:
1745 if (CanTakeFocus() && !mAlwaysOnTop) {
1746 ::ShowWindow(mWnd, SW_SHOWNORMAL);
1747 } else {
1748 ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
1749 // Don't flicker the window if we're restoring session
1750 if (!sIsRestoringSession) {
1751 Unused << GetAttention(2);
1754 break;
1756 } else {
1757 DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
1758 if (wasVisible) flags |= SWP_NOZORDER;
1759 if (mAlwaysOnTop) flags |= SWP_NOACTIVATE;
1761 if (mWindowType == WindowType::Popup) {
1762 // ensure popups are the topmost of the TOPMOST
1763 // layer. Remember not to set the SWP_NOZORDER
1764 // flag as that might allow the taskbar to overlap
1765 // the popup.
1766 flags |= SWP_NOACTIVATE;
1767 HWND owner = ::GetWindow(mWnd, GW_OWNER);
1768 if (owner) {
1769 // PopupLevel::Top popups should be above all else. All other
1770 // types should be placed in front of their owner, without
1771 // changing the owner's z-level relative to other windows.
1772 if (mPopupLevel != PopupLevel::Top) {
1773 ::SetWindowPos(mWnd, owner, 0, 0, 0, 0, flags);
1774 ::SetWindowPos(owner, mWnd, 0, 0, 0, 0,
1775 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1776 } else {
1777 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1779 } else {
1780 ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
1782 } else {
1783 if (mWindowType == WindowType::Dialog && !CanTakeFocus())
1784 flags |= SWP_NOACTIVATE;
1786 ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
1789 } else {
1790 // Clear contents to avoid ghosting of old content if we display
1791 // this window again.
1792 if (wasVisible && mTransparencyMode == TransparencyMode::Transparent) {
1793 if (mCompositorWidgetDelegate) {
1794 mCompositorWidgetDelegate->ClearTransparentWindow();
1797 if (mWindowType != WindowType::Dialog) {
1798 ::ShowWindow(mWnd, SW_HIDE);
1799 } else {
1800 ::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
1801 SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
1802 SWP_NOACTIVATE);
1807 if (!wasVisible && bState) {
1808 Invalidate();
1809 if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
1810 ::UpdateWindow(mWnd);
1814 if (mOpeningAnimationSuppressed) {
1815 SuppressAnimation(false);
1819 /**************************************************************
1821 * SECTION: nsIWidget::IsVisible
1823 * Returns the visibility state.
1825 **************************************************************/
1827 // Return true if the component is visible, false otherwise.
1829 // This does not take cloaking into account.
1830 bool nsWindow::IsVisible() const { return mIsVisible; }
1832 /**************************************************************
1834 * SECTION: Window clipping utilities
1836 * Used in Size and Move operations for setting the proper
1837 * window clipping regions for window transparency.
1839 **************************************************************/
1841 // XP and Vista visual styles sometimes require window clipping regions to be
1842 // applied for proper transparency. These routines are called on size and move
1843 // operations.
1844 // XXX this is apparently still needed in Windows 7 and later
1845 void nsWindow::ClearThemeRegion() {
1846 if (mWindowType == WindowType::Popup && !IsPopupWithTitleBar() &&
1847 (mPopupType == PopupType::Tooltip || mPopupType == PopupType::Panel)) {
1848 SetWindowRgn(mWnd, nullptr, false);
1852 /**************************************************************
1854 * SECTION: Touch and APZ-related functions
1856 **************************************************************/
1858 void nsWindow::RegisterTouchWindow() {
1859 mTouchWindow = true;
1860 ::RegisterTouchWindow(mWnd, TWF_WANTPALM);
1861 ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
1864 BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
1865 nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
1866 if (win) {
1867 ::RegisterTouchWindow(aWnd, TWF_WANTPALM);
1869 return TRUE;
1872 void nsWindow::LockAspectRatio(bool aShouldLock) {
1873 if (aShouldLock) {
1874 mAspectRatio = (float)mBounds.Width() / (float)mBounds.Height();
1875 } else {
1876 mAspectRatio = 0.0;
1880 /**************************************************************
1882 * SECTION: nsIWidget::SetInputRegion
1884 * Sets whether the window should ignore mouse events.
1886 **************************************************************/
1887 void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
1888 mInputRegion = aInputRegion;
1891 /**************************************************************
1893 * SECTION: nsIWidget::Move, nsIWidget::Resize, nsIWidget::Size
1895 * Repositioning and sizing a window.
1897 **************************************************************/
1899 void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
1900 SizeConstraints c = aConstraints;
1902 if (mWindowType != WindowType::Popup && mResizable) {
1903 c.mMinSize.width =
1904 std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
1905 c.mMinSize.height =
1906 std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
1909 if (mMaxTextureSize > 0) {
1910 // We can't make ThebesLayers bigger than this anyway.. no point it letting
1911 // a window grow bigger as we won't be able to draw content there in
1912 // general.
1913 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
1914 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
1917 mSizeConstraintsScale = GetDefaultScale().scale;
1919 nsBaseWidget::SetSizeConstraints(c);
1922 const SizeConstraints nsWindow::GetSizeConstraints() {
1923 double scale = GetDefaultScale().scale;
1924 if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
1925 return mSizeConstraints;
1927 scale /= mSizeConstraintsScale;
1928 SizeConstraints c = mSizeConstraints;
1929 if (c.mMinSize.width != NS_MAXSIZE) {
1930 c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
1932 if (c.mMinSize.height != NS_MAXSIZE) {
1933 c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
1935 if (c.mMaxSize.width != NS_MAXSIZE) {
1936 c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
1938 if (c.mMaxSize.height != NS_MAXSIZE) {
1939 c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
1941 return c;
1944 // Move this component
1945 void nsWindow::Move(double aX, double aY) {
1946 if (mWindowType == WindowType::TopLevel ||
1947 mWindowType == WindowType::Dialog) {
1948 SetSizeMode(nsSizeMode_Normal);
1951 // for top-level windows only, convert coordinates from desktop pixels
1952 // (the "parent" coordinate space) to the window's device pixel space
1953 double scale =
1954 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
1955 int32_t x = NSToIntRound(aX * scale);
1956 int32_t y = NSToIntRound(aY * scale);
1958 // Check to see if window needs to be moved first
1959 // to avoid a costly call to SetWindowPos. This check
1960 // can not be moved to the calling code in nsView, because
1961 // some platforms do not position child windows correctly
1963 // Only perform this check for non-popup windows, since the positioning can
1964 // in fact change even when the x/y do not. We always need to perform the
1965 // check. See bug #97805 for details.
1966 if (mWindowType != WindowType::Popup && mBounds.IsEqualXY(x, y)) {
1967 // Nothing to do, since it is already positioned correctly.
1968 return;
1971 mBounds.MoveTo(x, y);
1973 if (mWnd) {
1974 #ifdef DEBUG
1975 // complain if a window is moved offscreen (legal, but potentially
1976 // worrisome)
1977 if (mIsTopWidgetWindow) { // only a problem for top-level windows
1978 // Make sure this window is actually on the screen before we move it
1979 // XXX: Needs multiple monitor support
1980 HDC dc = ::GetDC(mWnd);
1981 if (dc) {
1982 if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
1983 RECT workArea;
1984 ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
1985 // no annoying assertions. just mention the issue.
1986 if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) {
1987 MOZ_LOG(gWindowsLog, LogLevel::Info,
1988 ("window moved to offscreen position\n"));
1991 ::ReleaseDC(mWnd, dc);
1994 #endif
1996 // Normally, when the skeleton UI is disabled, we resize+move the window
1997 // before showing it in order to ensure that it restores to the correct
1998 // position when the user un-maximizes it. However, when we are using the
1999 // skeleton UI, this results in the skeleton UI window being moved around
2000 // undesirably before being locked back into the maximized position. To
2001 // avoid this, we simply set the placement to restore to via
2002 // SetWindowPlacement. It's a little bit more of a dance, though, since we
2003 // need to convert the workspace coords that SetWindowPlacement uses to the
2004 // screen space coordinates we normally use with SetWindowPos.
2005 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2006 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2007 VERIFY(::GetWindowPlacement(mWnd, &pl));
2009 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2010 if (NS_WARN_IF(!monitor)) {
2011 return;
2013 MONITORINFO mi = {sizeof(MONITORINFO)};
2014 VERIFY(::GetMonitorInfo(monitor, &mi));
2016 int32_t deltaX =
2017 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
2018 int32_t deltaY =
2019 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
2020 pl.rcNormalPosition.left += deltaX;
2021 pl.rcNormalPosition.right += deltaX;
2022 pl.rcNormalPosition.top += deltaY;
2023 pl.rcNormalPosition.bottom += deltaY;
2024 VERIFY(::SetWindowPlacement(mWnd, &pl));
2025 } else {
2026 ClearThemeRegion();
2028 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
2029 double oldScale = mDefaultScale;
2030 mResizeState = IN_SIZEMOVE;
2031 VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
2032 mResizeState = NOT_RESIZING;
2033 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
2034 ChangedDPI();
2038 ResizeDirectManipulationViewport();
2042 // Resize this component
2043 void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
2044 // for top-level windows only, convert coordinates from desktop pixels
2045 // (the "parent" coordinate space) to the window's device pixel space
2046 double scale =
2047 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
2048 int32_t width = NSToIntRound(aWidth * scale);
2049 int32_t height = NSToIntRound(aHeight * scale);
2051 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
2052 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
2053 if (width < 0 || height < 0) {
2054 gfxCriticalNoteOnce << "Negative passed to Resize(" << width << ", "
2055 << height << ") repaint: " << aRepaint;
2058 ConstrainSize(&width, &height);
2060 // Avoid unnecessary resizing calls
2061 if (mBounds.IsEqualSize(width, height)) {
2062 if (aRepaint) {
2063 Invalidate();
2065 return;
2068 // Set cached value for lightweight and printing
2069 bool wasLocking = mAspectRatio != 0.0;
2070 mBounds.SizeTo(width, height);
2071 if (wasLocking) {
2072 LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
2075 if (mWnd) {
2076 // Refer to the comment above a similar check in nsWindow::Move
2077 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2078 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2079 VERIFY(::GetWindowPlacement(mWnd, &pl));
2080 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
2081 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
2082 mResizeState = RESIZING;
2083 VERIFY(::SetWindowPlacement(mWnd, &pl));
2084 mResizeState = NOT_RESIZING;
2085 } else {
2086 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
2088 if (!aRepaint) {
2089 flags |= SWP_NOREDRAW;
2092 ClearThemeRegion();
2093 double oldScale = mDefaultScale;
2094 mResizeState = RESIZING;
2095 VERIFY(
2096 ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags));
2098 mResizeState = NOT_RESIZING;
2099 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
2100 ChangedDPI();
2104 ResizeDirectManipulationViewport();
2107 if (aRepaint) Invalidate();
2110 // Resize this component
2111 void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
2112 bool aRepaint) {
2113 // for top-level windows only, convert coordinates from desktop pixels
2114 // (the "parent" coordinate space) to the window's device pixel space
2115 double scale =
2116 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
2117 int32_t x = NSToIntRound(aX * scale);
2118 int32_t y = NSToIntRound(aY * scale);
2119 int32_t width = NSToIntRound(aWidth * scale);
2120 int32_t height = NSToIntRound(aHeight * scale);
2122 NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
2123 NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
2124 if (width < 0 || height < 0) {
2125 gfxCriticalNoteOnce << "Negative passed to Resize(" << x << " ," << y
2126 << ", " << width << ", " << height
2127 << ") repaint: " << aRepaint;
2130 ConstrainSize(&width, &height);
2132 // Avoid unnecessary resizing calls
2133 if (mBounds.IsEqualRect(x, y, width, height)) {
2134 if (aRepaint) {
2135 Invalidate();
2137 return;
2140 // Set cached value for lightweight and printing
2141 mBounds.SetRect(x, y, width, height);
2143 if (mWnd) {
2144 // Refer to the comment above a similar check in nsWindow::Move
2145 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2146 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2147 VERIFY(::GetWindowPlacement(mWnd, &pl));
2149 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2150 if (NS_WARN_IF(!monitor)) {
2151 return;
2153 MONITORINFO mi = {sizeof(MONITORINFO)};
2154 VERIFY(::GetMonitorInfo(monitor, &mi));
2156 int32_t deltaX =
2157 x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
2158 int32_t deltaY =
2159 y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
2160 pl.rcNormalPosition.left += deltaX;
2161 pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
2162 pl.rcNormalPosition.top += deltaY;
2163 pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
2164 VERIFY(::SetWindowPlacement(mWnd, &pl));
2165 } else {
2166 UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
2167 if (!aRepaint) {
2168 flags |= SWP_NOREDRAW;
2171 ClearThemeRegion();
2173 double oldScale = mDefaultScale;
2174 mResizeState = RESIZING;
2175 VERIFY(
2176 ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags));
2177 mResizeState = NOT_RESIZING;
2178 if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
2179 ChangedDPI();
2182 if (mTransitionWnd) {
2183 // If we have a fullscreen transition window, we need to make
2184 // it topmost again, otherwise the taskbar may be raised by
2185 // the system unexpectedly when we leave fullscreen state.
2186 ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
2187 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
2191 ResizeDirectManipulationViewport();
2194 if (aRepaint) Invalidate();
2197 mozilla::Maybe<bool> nsWindow::IsResizingNativeWidget() {
2198 if (mResizeState == RESIZING) {
2199 return Some(true);
2201 return Some(false);
2204 /**************************************************************
2206 * SECTION: Window Z-order and state.
2208 * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode,
2209 * nsIWidget::ConstrainPosition
2211 * Z-order, positioning, restore, minimize, and maximize.
2213 **************************************************************/
2215 // Position the window behind the given window
2216 void nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
2217 nsIWidget* aWidget, bool aActivate) {
2218 HWND behind = HWND_TOP;
2219 if (aPlacement == eZPlacementBottom)
2220 behind = HWND_BOTTOM;
2221 else if (aPlacement == eZPlacementBelow && aWidget)
2222 behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
2223 UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE;
2224 if (!aActivate) flags |= SWP_NOACTIVATE;
2226 if (!CanTakeFocus() && behind == HWND_TOP) {
2227 // Can't place the window to top so place it behind the foreground window
2228 // (as long as it is not topmost)
2229 HWND wndAfter = ::GetForegroundWindow();
2230 if (!wndAfter)
2231 behind = HWND_BOTTOM;
2232 else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST))
2233 behind = wndAfter;
2234 flags |= SWP_NOACTIVATE;
2237 ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
2240 static UINT GetCurrentShowCmd(HWND aWnd) {
2241 WINDOWPLACEMENT pl;
2242 pl.length = sizeof(pl);
2243 ::GetWindowPlacement(aWnd, &pl);
2244 return pl.showCmd;
2247 // Maximize, minimize or restore the window.
2248 void nsWindow::SetSizeMode(nsSizeMode aMode) {
2249 // If we are still displaying a maximized pre-XUL skeleton UI, ignore the
2250 // noise of sizemode changes. Once we have "shown" the window for the first
2251 // time (called nsWindow::Show(true), even though the window is already
2252 // technically displayed), we will again accept sizemode changes.
2253 if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
2254 return;
2257 mFrameState->EnsureSizeMode(aMode);
2260 nsSizeMode nsWindow::SizeMode() { return mFrameState->GetSizeMode(); }
2262 void DoGetWorkspaceID(HWND aWnd, nsAString* aWorkspaceID) {
2263 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2264 if (!desktopManager || !aWnd) {
2265 return;
2268 GUID desktop;
2269 HRESULT hr = desktopManager->GetWindowDesktopId(aWnd, &desktop);
2270 if (FAILED(hr)) {
2271 return;
2274 RPC_WSTR workspaceIDStr = nullptr;
2275 if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
2276 aWorkspaceID->Assign((wchar_t*)workspaceIDStr);
2277 RpcStringFreeW(&workspaceIDStr);
2281 void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
2282 // If we have a value cached, use that, but also make sure it is
2283 // scheduled to be updated. If we don't yet have a value, get
2284 // one synchronously.
2285 auto desktop = mDesktopId.Lock();
2286 if (desktop->mID.IsEmpty()) {
2287 DoGetWorkspaceID(mWnd, &desktop->mID);
2288 desktop->mUpdateIsQueued = false;
2289 } else {
2290 AsyncUpdateWorkspaceID(*desktop);
2293 workspaceID = desktop->mID;
2296 void nsWindow::AsyncUpdateWorkspaceID(Desktop& aDesktop) {
2297 struct UpdateWorkspaceIdTask : public Task {
2298 explicit UpdateWorkspaceIdTask(nsWindow* aSelf)
2299 : Task(false /* mainThread */, EventQueuePriority::Normal),
2300 mSelf(aSelf) {}
2302 bool Run() override {
2303 auto desktop = mSelf->mDesktopId.Lock();
2304 if (desktop->mUpdateIsQueued) {
2305 DoGetWorkspaceID(mSelf->mWnd, &desktop->mID);
2306 desktop->mUpdateIsQueued = false;
2308 return true;
2311 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
2312 bool GetName(nsACString& aName) override {
2313 aName.AssignLiteral("UpdateWorkspaceIdTask");
2314 return true;
2316 #endif
2318 RefPtr<nsWindow> mSelf;
2321 if (aDesktop.mUpdateIsQueued) {
2322 return;
2325 aDesktop.mUpdateIsQueued = true;
2326 TaskController::Get()->AddTask(MakeAndAddRef<UpdateWorkspaceIdTask>(this));
2329 void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
2330 RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
2331 if (!desktopManager) {
2332 return;
2335 GUID desktop;
2336 const nsString flat = PromiseFlatString(workspaceID);
2337 RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
2338 if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
2339 if (SUCCEEDED(desktopManager->MoveWindowToDesktop(mWnd, desktop))) {
2340 auto desktop = mDesktopId.Lock();
2341 desktop->mID = workspaceID;
2346 void nsWindow::SuppressAnimation(bool aSuppress) {
2347 DWORD dwAttribute = aSuppress ? TRUE : FALSE;
2348 DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
2349 sizeof dwAttribute);
2352 // Constrain a potential move to fit onscreen
2353 // Position (aX, aY) is specified in Windows screen (logical) pixels,
2354 // except when using per-monitor DPI, in which case it's device pixels.
2355 void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
2356 if (!mIsTopWidgetWindow) // only a problem for top-level windows
2357 return;
2359 double dpiScale = GetDesktopToDeviceScale().scale;
2361 // We need to use the window size in the kind of pixels used for window-
2362 // manipulation APIs.
2363 int32_t logWidth =
2364 std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
2365 int32_t logHeight =
2366 std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
2368 /* get our playing field. use the current screen, or failing that
2369 for any reason, use device caps for the default screen. */
2370 RECT screenRect;
2372 nsCOMPtr<nsIScreenManager> screenmgr =
2373 do_GetService(sScreenManagerContractID);
2374 if (!screenmgr) {
2375 return;
2377 nsCOMPtr<nsIScreen> screen;
2378 int32_t left, top, width, height;
2380 screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
2381 getter_AddRefs(screen));
2382 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
2383 // For normalized windows, use the desktop work area.
2384 nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
2385 if (NS_FAILED(rv)) {
2386 return;
2388 } else {
2389 // For full screen windows, use the desktop.
2390 nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height);
2391 if (NS_FAILED(rv)) {
2392 return;
2395 screenRect.left = left;
2396 screenRect.right = left + width;
2397 screenRect.top = top;
2398 screenRect.bottom = top + height;
2400 if (aPoint.x < screenRect.left)
2401 aPoint.x = screenRect.left;
2402 else if (aPoint.x >= screenRect.right - logWidth)
2403 aPoint.x = screenRect.right - logWidth;
2405 if (aPoint.y < screenRect.top)
2406 aPoint.y = screenRect.top;
2407 else if (aPoint.y >= screenRect.bottom - logHeight)
2408 aPoint.y = screenRect.bottom - logHeight;
2411 /**************************************************************
2413 * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
2415 * Enabling and disabling the widget.
2417 **************************************************************/
2419 // Enable/disable this component
2420 void nsWindow::Enable(bool bState) {
2421 if (mWnd) {
2422 ::EnableWindow(mWnd, bState);
2426 // Return the current enable state
2427 bool nsWindow::IsEnabled() const {
2428 return !mWnd || (::IsWindowEnabled(mWnd) &&
2429 ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
2432 /**************************************************************
2434 * SECTION: nsIWidget::SetFocus
2436 * Give the focus to this widget.
2438 **************************************************************/
2440 void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
2441 if (mWnd) {
2442 #ifdef WINSTATE_DEBUG_OUTPUT
2443 if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
2444 MOZ_LOG(gWindowsLog, LogLevel::Info,
2445 ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
2446 } else {
2447 MOZ_LOG(gWindowsLog, LogLevel::Info,
2448 ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
2450 #endif
2451 // Uniconify, if necessary
2452 HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
2453 if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
2454 ::ShowWindow(toplevelWnd, SW_RESTORE);
2456 ::SetFocus(mWnd);
2460 /**************************************************************
2462 * SECTION: Bounds
2464 * GetBounds, GetClientBounds, GetScreenBounds,
2465 * GetRestoredBounds, GetClientOffset
2466 * SetDrawsInTitlebar, SetNonClientMargins
2468 * Bound calculations.
2470 **************************************************************/
2472 // Return the window's full dimensions in screen coordinates.
2473 // If the window has a parent, converts the origin to an offset
2474 // of the parent's screen origin.
2475 LayoutDeviceIntRect nsWindow::GetBounds() {
2476 if (!mWnd) {
2477 return mBounds;
2480 RECT r;
2481 VERIFY(::GetWindowRect(mWnd, &r));
2483 LayoutDeviceIntRect rect;
2485 // assign size
2486 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2488 // popup window bounds' are in screen coordinates, not relative to parent
2489 // window
2490 if (mWindowType == WindowType::Popup) {
2491 rect.MoveTo(r.left, r.top);
2492 return rect;
2495 // chrome on parent:
2496 // ___ 5,5 (chrome start)
2497 // | ____ 10,10 (client start)
2498 // | | ____ 20,20 (child start)
2499 // | | |
2500 // 20,20 - 5,5 = 15,15 (??)
2501 // minus GetClientOffset:
2502 // 15,15 - 5,5 = 10,10
2504 // no chrome on parent:
2505 // ______ 10,10 (win start)
2506 // | ____ 20,20 (child start)
2507 // | |
2508 // 20,20 - 10,10 = 10,10
2510 // walking the chain:
2511 // ___ 5,5 (chrome start)
2512 // | ___ 10,10 (client start)
2513 // | | ___ 20,20 (child start)
2514 // | | | __ 30,30 (child start)
2515 // | | | |
2516 // 30,30 - 20,20 = 10,10 (offset from second child to first)
2517 // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
2518 // minus GetClientOffset:
2519 // 25,25 - 5,5 = 20,20 (offset from second child to parent client)
2521 // convert coordinates if parent exists
2522 HWND parent = ::GetParent(mWnd);
2523 if (parent) {
2524 RECT pr;
2525 VERIFY(::GetWindowRect(parent, &pr));
2526 r.left -= pr.left;
2527 r.top -= pr.top;
2528 // adjust for chrome
2529 nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
2530 if (pWidget && pWidget->IsTopLevelWidget()) {
2531 LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
2532 r.left -= clientOffset.x;
2533 r.top -= clientOffset.y;
2536 rect.MoveTo(r.left, r.top);
2537 if (mCompositorSession &&
2538 !wr::WindowSizeSanityCheck(rect.width, rect.height)) {
2539 gfxCriticalNoteOnce << "Invalid size" << rect << " size mode "
2540 << mFrameState->GetSizeMode();
2543 return rect;
2546 // Get this component dimension
2547 LayoutDeviceIntRect nsWindow::GetClientBounds() {
2548 if (!mWnd) {
2549 return LayoutDeviceIntRect(0, 0, 0, 0);
2552 RECT r;
2553 if (!::GetClientRect(mWnd, &r)) {
2554 MOZ_ASSERT_UNREACHABLE("unexpected to be called");
2555 gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
2556 return mBounds;
2559 LayoutDeviceIntRect bounds = GetBounds();
2560 LayoutDeviceIntRect rect;
2561 rect.MoveTo(bounds.TopLeft() + GetClientOffset());
2562 rect.SizeTo(r.right - r.left, r.bottom - r.top);
2563 return rect;
2566 // Like GetBounds, but don't offset by the parent
2567 LayoutDeviceIntRect nsWindow::GetScreenBounds() {
2568 if (!mWnd) {
2569 return mBounds;
2572 RECT r;
2573 VERIFY(::GetWindowRect(mWnd, &r));
2575 return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2578 nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
2579 if (SizeMode() == nsSizeMode_Normal) {
2580 aRect = GetScreenBounds();
2581 return NS_OK;
2583 if (!mWnd) {
2584 return NS_ERROR_FAILURE;
2587 WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
2588 VERIFY(::GetWindowPlacement(mWnd, &pl));
2589 const RECT& r = pl.rcNormalPosition;
2591 HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
2592 if (!monitor) {
2593 return NS_ERROR_FAILURE;
2595 MONITORINFO mi = {sizeof(MONITORINFO)};
2596 VERIFY(::GetMonitorInfo(monitor, &mi));
2598 aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
2599 aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
2600 mi.rcWork.top - mi.rcMonitor.top);
2601 return NS_OK;
2604 // Return the x,y offset of the client area from the origin of the window. If
2605 // the window is borderless returns (0,0).
2606 LayoutDeviceIntPoint nsWindow::GetClientOffset() {
2607 if (!mWnd) {
2608 return LayoutDeviceIntPoint(0, 0);
2611 RECT r1;
2612 GetWindowRect(mWnd, &r1);
2613 LayoutDeviceIntPoint pt = WidgetToScreenOffset();
2614 return LayoutDeviceIntPoint(pt.x - LayoutDeviceIntCoord(r1.left),
2615 pt.y - LayoutDeviceIntCoord(r1.top));
2618 void nsWindow::SetDrawsInTitlebar(bool aState) {
2619 nsWindow* window = GetTopLevelWindow(true);
2620 if (window && window != this) {
2621 return window->SetDrawsInTitlebar(aState);
2624 // top, right, bottom, left
2625 SetNonClientMargins(aState ? LayoutDeviceIntMargin(0, -1, -1, -1)
2626 : LayoutDeviceIntMargin(-1, -1, -1, -1));
2629 void nsWindow::ResetLayout() {
2630 // This will trigger a frame changed event, triggering
2631 // nc calc size and a sizemode gecko event.
2632 SetWindowPos(mWnd, 0, 0, 0, 0, 0,
2633 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
2634 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
2636 // If hidden, just send the frame changed event for now.
2637 if (!mIsVisible) {
2638 return;
2641 // Send a gecko size event to trigger reflow.
2642 RECT clientRc = {0};
2643 GetClientRect(mWnd, &clientRc);
2644 OnResize(WinUtils::ToIntRect(clientRc).Size());
2646 // Invalidate and update
2647 Invalidate();
2650 // Internally track the caption status via a window property. Required
2651 // due to our internal handling of WM_NCACTIVATE when custom client
2652 // margins are set.
2653 static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty";
2654 typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
2655 static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr>
2656 sGetWindowInfoPtrStub;
2658 BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) {
2659 if (!sGetWindowInfoPtrStub) {
2660 NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!");
2661 return FALSE;
2663 int windowStatus =
2664 reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty));
2665 // No property set, return the default data.
2666 if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi);
2667 // Call GetWindowInfo and update dwWindowStatus with our
2668 // internally tracked value.
2669 BOOL result = sGetWindowInfoPtrStub(hWnd, pwi);
2670 if (result && pwi)
2671 pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION);
2672 return result;
2675 void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) {
2676 if (!mWnd) return;
2678 sUser32Intercept.Init("user32.dll");
2679 sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo",
2680 &GetWindowInfoHook);
2681 if (!sGetWindowInfoPtrStub) {
2682 return;
2685 // Update our internally tracked caption status
2686 SetPropW(mWnd, kManageWindowInfoProperty,
2687 reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
2690 #define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
2691 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
2693 void nsWindow::UpdateDarkModeToolbar() {
2694 if (!IsWin10OrLater()) {
2695 return;
2697 LookAndFeel::EnsureColorSchemesInitialized();
2698 BOOL dark = LookAndFeel::ColorSchemeForChrome() == ColorScheme::Dark;
2699 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &dark,
2700 sizeof dark);
2701 DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
2702 sizeof dark);
2705 LayoutDeviceIntMargin nsWindow::NormalWindowNonClientOffset() const {
2706 bool glass = gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
2708 LayoutDeviceIntMargin nonClientOffset;
2710 // We're dealing with a "normal" window (not maximized, minimized, or
2711 // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset`
2712 // accordingly.
2714 // Setting `mNonClientOffset` to 0 has the effect of leaving the default
2715 // frame intact. Setting it to a value greater than 0 reduces the frame
2716 // size by that amount.
2718 if (mNonClientMargins.top > 0 && glass) {
2719 nonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
2720 } else if (mNonClientMargins.top == 0) {
2721 nonClientOffset.top = mCaptionHeight;
2722 } else {
2723 nonClientOffset.top = 0;
2726 if (mNonClientMargins.bottom > 0 && glass) {
2727 nonClientOffset.bottom =
2728 std::min(mVertResizeMargin, mNonClientMargins.bottom);
2729 } else if (mNonClientMargins.bottom == 0) {
2730 nonClientOffset.bottom = mVertResizeMargin;
2731 } else {
2732 nonClientOffset.bottom = 0;
2735 if (mNonClientMargins.left > 0 && glass) {
2736 nonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left);
2737 } else if (mNonClientMargins.left == 0) {
2738 nonClientOffset.left = mHorResizeMargin;
2739 } else {
2740 nonClientOffset.left = 0;
2743 if (mNonClientMargins.right > 0 && glass) {
2744 nonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right);
2745 } else if (mNonClientMargins.right == 0) {
2746 nonClientOffset.right = mHorResizeMargin;
2747 } else {
2748 nonClientOffset.right = 0;
2750 return nonClientOffset;
2754 * Called when the window layout changes: full screen mode transitions,
2755 * theme changes, and composition changes. Calculates the new non-client
2756 * margins and fires off a frame changed event, which triggers an nc calc
2757 * size windows event, kicking the changes in.
2759 * The offsets calculated here are based on the value of `mNonClientMargins`
2760 * which is specified in the "chromemargins" attribute of the window. For
2761 * each margin, the value specified has the following meaning:
2762 * -1 - leave the default frame in place
2763 * 0 - remove the frame
2764 * >0 - frame size equals min(0, (default frame size - margin value))
2766 * This function calculates and populates `mNonClientOffset`.
2767 * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
2768 * as (default frame size - offset). For example, if the left frame should
2769 * be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
2770 * will equal 1.
2772 * For maximized, fullscreen, and minimized windows, the values stored in
2773 * `mNonClientMargins` are ignored, and special processing takes place.
2775 * For non-glass windows, we only allow frames to be their default size
2776 * or removed entirely.
2778 bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) {
2779 if (!mCustomNonClient) {
2780 return false;
2783 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
2785 bool hasCaption =
2786 bool(mBorderStyle & (BorderStyle::All | BorderStyle::Title |
2787 BorderStyle::Menu | BorderStyle::Default));
2789 float dpi = GetDPI();
2791 // mCaptionHeight is the default size of the NC area at
2792 // the top of the window. If the window has a caption,
2793 // the size is calculated as the sum of:
2794 // SM_CYFRAME - The thickness of the sizing border
2795 // around a resizable window
2796 // SM_CXPADDEDBORDER - The amount of border padding
2797 // for captioned windows
2798 // SM_CYCAPTION - The height of the caption area
2800 // If the window does not have a caption, mCaptionHeight will be equal to
2801 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2802 mCaptionHeight =
2803 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2804 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +
2805 WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2806 : 0);
2807 if (!mUseResizeMarginOverrides) {
2808 // mHorResizeMargin is the size of the default NC areas on the
2809 // left and right sides of our window. It is calculated as
2810 // the sum of:
2811 // SM_CXFRAME - The thickness of the sizing border
2812 // SM_CXPADDEDBORDER - The amount of border padding
2813 // for captioned windows
2815 // If the window does not have a caption, mHorResizeMargin will be equal to
2816 // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
2817 mHorResizeMargin =
2818 WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
2819 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2820 : 0);
2822 // mVertResizeMargin is the size of the default NC area at the
2823 // bottom of the window. It is calculated as the sum of:
2824 // SM_CYFRAME - The thickness of the sizing border
2825 // SM_CXPADDEDBORDER - The amount of border padding
2826 // for captioned windows.
2828 // If the window does not have a caption, mVertResizeMargin will be equal to
2829 // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
2830 mVertResizeMargin =
2831 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2832 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2833 : 0);
2836 if (sizeMode == nsSizeMode_Minimized) {
2837 // Use default frame size for minimized windows
2838 mNonClientOffset.top = 0;
2839 mNonClientOffset.left = 0;
2840 mNonClientOffset.right = 0;
2841 mNonClientOffset.bottom = 0;
2842 } else if (sizeMode == nsSizeMode_Fullscreen) {
2843 // Remove the default frame from the top of our fullscreen window. This
2844 // makes the whole caption part of our client area, allowing us to draw
2845 // in the whole caption area. Additionally remove the default frame from
2846 // the left, right, and bottom.
2847 mNonClientOffset.top = mCaptionHeight;
2848 mNonClientOffset.bottom = mVertResizeMargin;
2849 mNonClientOffset.left = mHorResizeMargin;
2850 mNonClientOffset.right = mHorResizeMargin;
2851 } else if (sizeMode == nsSizeMode_Maximized) {
2852 // We make the entire frame part of the client area. We leave the default
2853 // frame sizes for left, right and bottom since Windows will automagically
2854 // position the edges "offscreen" for maximized windows.
2855 int verticalResize =
2856 WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
2857 (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
2858 : 0);
2860 mNonClientOffset.top = mCaptionHeight - verticalResize;
2861 mNonClientOffset.bottom = 0;
2862 mNonClientOffset.left = 0;
2863 mNonClientOffset.right = 0;
2865 mozilla::Maybe<UINT> maybeEdge = GetHiddenTaskbarEdge();
2866 if (maybeEdge) {
2867 auto edge = maybeEdge.value();
2868 if (ABE_LEFT == edge) {
2869 mNonClientOffset.left -= kHiddenTaskbarSize;
2870 } else if (ABE_RIGHT == edge) {
2871 mNonClientOffset.right -= kHiddenTaskbarSize;
2872 } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
2873 mNonClientOffset.bottom -= kHiddenTaskbarSize;
2876 // On Windows 10+, when we are drawing the non-client region, we need
2877 // to clear the portion of the NC region that is exposed by the
2878 // hidden taskbar. As above, we clear the bottom of the NC region
2879 // when the taskbar is at the top of the screen.
2880 if (IsWin10OrLater()) {
2881 UINT clearEdge = (edge == ABE_TOP) ? ABE_BOTTOM : edge;
2882 mClearNCEdge = Some(clearEdge);
2885 } else {
2886 mNonClientOffset = NormalWindowNonClientOffset();
2889 if (aReflowWindow) {
2890 // Force a reflow of content based on the new client
2891 // dimensions.
2892 ResetLayout();
2895 return true;
2898 nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) {
2899 if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None)
2900 return NS_ERROR_INVALID_ARG;
2902 if (mHideChrome) {
2903 mFutureMarginsOnceChromeShows = margins;
2904 mFutureMarginsToUse = true;
2905 return NS_OK;
2907 mFutureMarginsToUse = false;
2909 // Request for a reset
2910 if (margins.top == -1 && margins.left == -1 && margins.right == -1 &&
2911 margins.bottom == -1) {
2912 mCustomNonClient = false;
2913 mNonClientMargins = margins;
2914 // Force a reflow of content based on the new client
2915 // dimensions.
2916 ResetLayout();
2918 int windowStatus =
2919 reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty));
2920 if (windowStatus) {
2921 ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0);
2924 return NS_OK;
2927 if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 ||
2928 margins.right < -1)
2929 return NS_ERROR_INVALID_ARG;
2931 mNonClientMargins = margins;
2932 mCustomNonClient = true;
2933 if (!UpdateNonClientMargins()) {
2934 NS_WARNING("UpdateNonClientMargins failed!");
2935 return NS_OK;
2938 return NS_OK;
2941 void nsWindow::SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) {
2942 mUseResizeMarginOverrides = true;
2943 mHorResizeMargin = aResizeMargin;
2944 mVertResizeMargin = aResizeMargin;
2945 UpdateNonClientMargins();
2948 void nsWindow::InvalidateNonClientRegion() {
2949 // +-+-----------------------+-+
2950 // | | app non-client chrome | |
2951 // | +-----------------------+ |
2952 // | | app client chrome | | }
2953 // | +-----------------------+ | }
2954 // | | app content | | } area we don't want to invalidate
2955 // | +-----------------------+ | }
2956 // | | app client chrome | | }
2957 // | +-----------------------+ |
2958 // +---------------------------+ <
2959 // ^ ^ windows non-client chrome
2960 // client area = app *
2961 RECT rect;
2962 GetWindowRect(mWnd, &rect);
2963 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2964 HRGN winRgn = CreateRectRgnIndirect(&rect);
2966 // Subtract app client chrome and app content leaving
2967 // windows non-client chrome and app non-client chrome
2968 // in winRgn.
2969 GetWindowRect(mWnd, &rect);
2970 rect.top += mCaptionHeight;
2971 rect.right -= mHorResizeMargin;
2972 rect.bottom -= mVertResizeMargin;
2973 rect.left += mHorResizeMargin;
2974 MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
2975 HRGN clientRgn = CreateRectRgnIndirect(&rect);
2976 CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF);
2977 DeleteObject(clientRgn);
2979 // triggers ncpaint and paint events for the two areas
2980 RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE);
2981 DeleteObject(winRgn);
2984 HRGN nsWindow::ExcludeNonClientFromPaintRegion(HRGN aRegion) {
2985 RECT rect;
2986 HRGN rgn = nullptr;
2987 if (aRegion == (HRGN)1) { // undocumented value indicating a full refresh
2988 GetWindowRect(mWnd, &rect);
2989 rgn = CreateRectRgnIndirect(&rect);
2990 } else {
2991 rgn = aRegion;
2993 GetClientRect(mWnd, &rect);
2994 MapWindowPoints(mWnd, nullptr, (LPPOINT)&rect, 2);
2995 HRGN nonClientRgn = CreateRectRgnIndirect(&rect);
2996 CombineRgn(rgn, rgn, nonClientRgn, RGN_DIFF);
2997 DeleteObject(nonClientRgn);
2998 return rgn;
3001 /**************************************************************
3003 * SECTION: nsIWidget::SetBackgroundColor
3005 * Sets the window background paint color.
3007 **************************************************************/
3009 void nsWindow::SetBackgroundColor(const nscolor& aColor) {
3010 if (mBrush) ::DeleteObject(mBrush);
3012 mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor));
3013 if (mWnd != nullptr) {
3014 ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush);
3018 /**************************************************************
3020 * SECTION: nsIWidget::SetCursor
3022 * SetCursor and related utilities for manging cursor state.
3024 **************************************************************/
3026 // Set this component cursor
3027 static HCURSOR CursorFor(nsCursor aCursor) {
3028 switch (aCursor) {
3029 case eCursor_select:
3030 return ::LoadCursor(nullptr, IDC_IBEAM);
3031 case eCursor_wait:
3032 return ::LoadCursor(nullptr, IDC_WAIT);
3033 case eCursor_hyperlink:
3034 return ::LoadCursor(nullptr, IDC_HAND);
3035 case eCursor_standard:
3036 case eCursor_context_menu: // XXX See bug 258960.
3037 return ::LoadCursor(nullptr, IDC_ARROW);
3039 case eCursor_n_resize:
3040 case eCursor_s_resize:
3041 return ::LoadCursor(nullptr, IDC_SIZENS);
3043 case eCursor_w_resize:
3044 case eCursor_e_resize:
3045 return ::LoadCursor(nullptr, IDC_SIZEWE);
3047 case eCursor_nw_resize:
3048 case eCursor_se_resize:
3049 return ::LoadCursor(nullptr, IDC_SIZENWSE);
3051 case eCursor_ne_resize:
3052 case eCursor_sw_resize:
3053 return ::LoadCursor(nullptr, IDC_SIZENESW);
3055 case eCursor_crosshair:
3056 return ::LoadCursor(nullptr, IDC_CROSS);
3058 case eCursor_move:
3059 return ::LoadCursor(nullptr, IDC_SIZEALL);
3061 case eCursor_help:
3062 return ::LoadCursor(nullptr, IDC_HELP);
3064 case eCursor_copy: // CSS3
3065 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
3067 case eCursor_alias:
3068 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
3070 case eCursor_cell:
3071 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
3072 case eCursor_grab:
3073 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
3075 case eCursor_grabbing:
3076 return ::LoadCursor(nsToolkit::mDllInstance,
3077 MAKEINTRESOURCE(IDC_GRABBING));
3079 case eCursor_spinning:
3080 return ::LoadCursor(nullptr, IDC_APPSTARTING);
3082 case eCursor_zoom_in:
3083 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
3085 case eCursor_zoom_out:
3086 return ::LoadCursor(nsToolkit::mDllInstance,
3087 MAKEINTRESOURCE(IDC_ZOOMOUT));
3089 case eCursor_not_allowed:
3090 case eCursor_no_drop:
3091 return ::LoadCursor(nullptr, IDC_NO);
3093 case eCursor_col_resize:
3094 return ::LoadCursor(nsToolkit::mDllInstance,
3095 MAKEINTRESOURCE(IDC_COLRESIZE));
3097 case eCursor_row_resize:
3098 return ::LoadCursor(nsToolkit::mDllInstance,
3099 MAKEINTRESOURCE(IDC_ROWRESIZE));
3101 case eCursor_vertical_text:
3102 return ::LoadCursor(nsToolkit::mDllInstance,
3103 MAKEINTRESOURCE(IDC_VERTICALTEXT));
3105 case eCursor_all_scroll:
3106 // XXX not 100% appropriate perhaps
3107 return ::LoadCursor(nullptr, IDC_SIZEALL);
3109 case eCursor_nesw_resize:
3110 return ::LoadCursor(nullptr, IDC_SIZENESW);
3112 case eCursor_nwse_resize:
3113 return ::LoadCursor(nullptr, IDC_SIZENWSE);
3115 case eCursor_ns_resize:
3116 return ::LoadCursor(nullptr, IDC_SIZENS);
3118 case eCursor_ew_resize:
3119 return ::LoadCursor(nullptr, IDC_SIZEWE);
3121 case eCursor_none:
3122 return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
3124 default:
3125 NS_ERROR("Invalid cursor type");
3126 return nullptr;
3130 static HCURSOR CursorForImage(const nsIWidget::Cursor& aCursor,
3131 CSSToLayoutDeviceScale aScale) {
3132 if (!aCursor.IsCustom()) {
3133 return nullptr;
3136 nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
3138 // Reject cursors greater than 128 pixels in either direction, to prevent
3139 // spoofing.
3140 // XXX ideally we should rescale. Also, we could modify the API to
3141 // allow trusted content to set larger cursors.
3142 if (size.width > 128 || size.height > 128) {
3143 return nullptr;
3146 LayoutDeviceIntSize layoutSize =
3147 RoundedToInt(CSSIntSize(size.width, size.height) * aScale);
3148 LayoutDeviceIntPoint hotspot =
3149 RoundedToInt(CSSIntPoint(aCursor.mHotspotX, aCursor.mHotspotY) * aScale);
3150 HCURSOR cursor;
3151 nsresult rv = nsWindowGfx::CreateIcon(aCursor.mContainer, true, hotspot,
3152 layoutSize, &cursor);
3153 if (NS_FAILED(rv)) {
3154 return nullptr;
3157 return cursor;
3160 void nsWindow::SetCursor(const Cursor& aCursor) {
3161 static HCURSOR sCurrentHCursor = nullptr;
3162 static bool sCurrentHCursorIsCustom = false;
3164 mCursor = aCursor;
3166 if (sCurrentCursor == aCursor && sCurrentHCursor && !mUpdateCursor) {
3167 // Cursors in windows are global, so even if our mUpdateCursor flag is
3168 // false we always need to make sure the Windows cursor is up-to-date,
3169 // since stuff like native drag and drop / resizers code can mutate it
3170 // outside of this method.
3171 ::SetCursor(sCurrentHCursor);
3172 return;
3175 mUpdateCursor = false;
3177 if (sCurrentHCursorIsCustom) {
3178 ::DestroyIcon(sCurrentHCursor);
3180 sCurrentHCursor = nullptr;
3181 sCurrentHCursorIsCustom = false;
3182 sCurrentCursor = aCursor;
3184 HCURSOR cursor = CursorForImage(aCursor, GetDefaultScale());
3185 bool custom = false;
3186 if (cursor) {
3187 custom = true;
3188 } else {
3189 cursor = CursorFor(aCursor.mDefaultCursor);
3192 if (!cursor) {
3193 return;
3196 sCurrentHCursor = cursor;
3197 sCurrentHCursorIsCustom = custom;
3198 ::SetCursor(cursor);
3201 /**************************************************************
3203 * SECTION: nsIWidget::Get/SetTransparencyMode
3205 * Manage the transparency mode of the window containing this
3206 * widget. Only works for popup and dialog windows when the
3207 * Desktop Window Manager compositor is not enabled.
3209 **************************************************************/
3211 TransparencyMode nsWindow::GetTransparencyMode() {
3212 return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
3215 void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
3216 nsWindow* window = GetTopLevelWindow(true);
3217 MOZ_ASSERT(window);
3219 if (!window || window->DestroyCalled()) {
3220 return;
3223 if (WindowType::TopLevel == window->mWindowType &&
3224 mTransparencyMode != aMode &&
3225 !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
3226 NS_WARNING("Cannot set transparency mode on top-level windows.");
3227 return;
3230 window->SetWindowTranslucencyInner(aMode);
3233 /**************************************************************
3235 * SECTION: nsIWidget::UpdateWindowDraggingRegion
3237 * For setting the draggable titlebar region from CSS
3238 * with -moz-window-dragging: drag.
3240 **************************************************************/
3242 void nsWindow::UpdateWindowDraggingRegion(
3243 const LayoutDeviceIntRegion& aRegion) {
3244 if (mDraggableRegion != aRegion) {
3245 mDraggableRegion = aRegion;
3249 /**************************************************************
3251 * SECTION: nsIWidget::HideWindowChrome
3253 * Show or hide window chrome.
3255 **************************************************************/
3257 void nsWindow::HideWindowChrome(bool aShouldHide) {
3258 HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
3259 if (!WinUtils::GetNSWindowPtr(hwnd)) {
3260 NS_WARNING("Trying to hide window decorations in an embedded context");
3261 return;
3264 if (mHideChrome == aShouldHide) return;
3266 DWORD_PTR style, exStyle;
3267 mHideChrome = aShouldHide;
3268 if (aShouldHide) {
3269 DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
3270 DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
3272 style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME);
3273 exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
3274 WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
3276 mOldStyle = tempStyle;
3277 mOldExStyle = tempExStyle;
3278 } else {
3279 if (!mOldStyle || !mOldExStyle) {
3280 mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
3281 mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
3284 style = mOldStyle;
3285 exStyle = mOldExStyle;
3286 if (mFutureMarginsToUse) {
3287 SetNonClientMargins(mFutureMarginsOnceChromeShows);
3291 VERIFY_WINDOW_STYLE(style);
3292 ::SetWindowLongPtrW(hwnd, GWL_STYLE, style);
3293 ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle);
3296 /**************************************************************
3298 * SECTION: nsWindow::Invalidate
3300 * Invalidate an area of the client for painting.
3302 **************************************************************/
3304 // Invalidate this component visible area
3305 void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
3306 bool aIncludeChildren) {
3307 if (!mWnd) {
3308 return;
3311 #ifdef WIDGET_DEBUG_OUTPUT
3312 debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
3313 #endif // WIDGET_DEBUG_OUTPUT
3315 DWORD flags = RDW_INVALIDATE;
3316 if (aEraseBackground) {
3317 flags |= RDW_ERASE;
3319 if (aUpdateNCArea) {
3320 flags |= RDW_FRAME;
3322 if (aIncludeChildren) {
3323 flags |= RDW_ALLCHILDREN;
3326 VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
3329 // Invalidate this component visible area
3330 void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
3331 if (mWnd) {
3332 #ifdef WIDGET_DEBUG_OUTPUT
3333 debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
3334 #endif // WIDGET_DEBUG_OUTPUT
3336 RECT rect;
3338 rect.left = aRect.X();
3339 rect.top = aRect.Y();
3340 rect.right = aRect.XMost();
3341 rect.bottom = aRect.YMost();
3343 VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
3347 static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
3348 WPARAM wParam,
3349 LPARAM lParam) {
3350 switch (uMsg) {
3351 case WM_FULLSCREEN_TRANSITION_BEFORE:
3352 case WM_FULLSCREEN_TRANSITION_AFTER: {
3353 DWORD duration = (DWORD)lParam;
3354 DWORD flags = AW_BLEND;
3355 if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
3356 flags |= AW_HIDE;
3358 ::AnimateWindow(hWnd, duration, flags);
3359 // The message sender should have added ref for us.
3360 NS_DispatchToMainThread(
3361 already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
3362 break;
3364 case WM_DESTROY:
3365 ::PostQuitMessage(0);
3366 break;
3367 default:
3368 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
3370 return 0;
3373 struct FullscreenTransitionInitData {
3374 LayoutDeviceIntRect mBounds;
3375 HANDLE mSemaphore;
3376 HANDLE mThread;
3377 HWND mWnd;
3379 FullscreenTransitionInitData()
3380 : mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
3382 ~FullscreenTransitionInitData() {
3383 if (mSemaphore) {
3384 ::CloseHandle(mSemaphore);
3386 if (mThread) {
3387 ::CloseHandle(mThread);
3392 static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
3393 // Initialize window class
3394 static bool sInitialized = false;
3395 if (!sInitialized) {
3396 WNDCLASSW wc = {};
3397 wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
3398 wc.hInstance = nsToolkit::mDllInstance;
3399 wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
3400 wc.lpszClassName = kClassNameTransition;
3401 ::RegisterClassW(&wc);
3402 sInitialized = true;
3405 auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
3406 HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
3407 nullptr, nsToolkit::mDllInstance, nullptr);
3408 if (!wnd) {
3409 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3410 return 0;
3413 // Since AnimateWindow blocks the thread of the transition window,
3414 // we need to hide the cursor for that window, otherwise the system
3415 // would show the busy pointer to the user.
3416 ::ShowCursor(false);
3417 ::SetWindowLongW(wnd, GWL_STYLE, 0);
3418 ::SetWindowLongW(
3419 wnd, GWL_EXSTYLE,
3420 WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
3421 ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
3422 data->mBounds.Width(), data->mBounds.Height(), 0);
3423 data->mWnd = wnd;
3424 ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
3425 // The initialization data may no longer be valid
3426 // after we release the semaphore.
3427 data = nullptr;
3429 MSG msg;
3430 while (::GetMessageW(&msg, nullptr, 0, 0)) {
3431 ::TranslateMessage(&msg);
3432 ::DispatchMessage(&msg);
3434 ::ShowCursor(true);
3435 ::DestroyWindow(wnd);
3436 return 0;
3439 class FullscreenTransitionData final : public nsISupports {
3440 public:
3441 NS_DECL_ISUPPORTS
3443 explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
3444 MOZ_ASSERT(NS_IsMainThread(),
3445 "FullscreenTransitionData "
3446 "should be constructed in the main thread");
3449 const HWND mWnd;
3451 private:
3452 ~FullscreenTransitionData() {
3453 MOZ_ASSERT(NS_IsMainThread(),
3454 "FullscreenTransitionData "
3455 "should be deconstructed in the main thread");
3456 ::PostMessageW(mWnd, WM_DESTROY, 0, 0);
3460 NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
3462 /* virtual */
3463 bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
3464 // We don't support fullscreen transition when composition is not
3465 // enabled, which could make the transition broken and annoying.
3466 // See bug 1184201.
3467 if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
3468 return false;
3471 FullscreenTransitionInitData initData;
3472 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
3473 const DesktopIntRect rect = screen->GetRectDisplayPix();
3474 MOZ_ASSERT(BoundsUseDesktopPixels(),
3475 "Should only be called on top-level window");
3476 initData.mBounds =
3477 LayoutDeviceIntRect::Round(rect * GetDesktopToDeviceScale());
3479 // Create a semaphore for synchronizing the window handle which will
3480 // be created by the transition thread and used by the main thread for
3481 // posting the transition messages.
3482 initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
3483 if (initData.mSemaphore) {
3484 initData.mThread = ::CreateThread(
3485 nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
3486 if (initData.mThread) {
3487 ::WaitForSingleObject(initData.mSemaphore, INFINITE);
3490 if (!initData.mWnd) {
3491 return false;
3494 mTransitionWnd = initData.mWnd;
3496 auto data = new FullscreenTransitionData(initData.mWnd);
3497 *aData = data;
3498 NS_ADDREF(data);
3499 return true;
3502 /* virtual */
3503 void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
3504 uint16_t aDuration,
3505 nsISupports* aData,
3506 nsIRunnable* aCallback) {
3507 auto data = static_cast<FullscreenTransitionData*>(aData);
3508 nsCOMPtr<nsIRunnable> callback = aCallback;
3509 UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
3510 : WM_FULLSCREEN_TRANSITION_AFTER;
3511 WPARAM wparam = (WPARAM)callback.forget().take();
3512 ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
3515 /* virtual */
3516 void nsWindow::CleanupFullscreenTransition() {
3517 MOZ_ASSERT(NS_IsMainThread(),
3518 "CleanupFullscreenTransition "
3519 "should only run on the main thread");
3521 mTransitionWnd = nullptr;
3524 void nsWindow::TryDwmResizeHack() {
3525 // The "DWM resize hack", aka the "fullscreen resize hack", is a workaround
3526 // for DWM's occasional and not-entirely-predictable failure to update its
3527 // internal state when the client area of a window changes without changing
3528 // the window size. The effect of this is that DWM will clip the content of
3529 // the window to its former client area.
3531 // It is not known under what circumstances the bug will trigger. Windows 11
3532 // is known to be required, but many Windows 11 machines do not exhibit the
3533 // issue. Even machines that _do_ exhibit it will sometimes not do so when
3534 // apparently-irrelevant changes are made to the configuration. (See bug
3535 // 1763981.)
3537 // The bug is triggered by Firefox when a maximized window (which has window
3538 // decorations) becomes fullscreen (which doesn't). To work around this, if we
3539 // think it may occur, we "flicker-resize" the relevant window -- that is, we
3540 // reduce its height by 1px, then restore it. This causes DWM to acquire the
3541 // new client-area metrics.
3543 // Note that, in particular, this bug will not occur when using a separate
3544 // compositor window, as our compositor windows never have any nonclient area.
3546 // This is admittedly a sledgehammer where a screwdriver should suffice.
3548 // ---------------------------------------------------------------------------
3550 // Regardless of preferences or heuristics, only apply the hack if this is the
3551 // first time we've entered fullscreen across the entire Firefox session.
3552 // (Subsequent transitions to fullscreen, even with different windows, don't
3553 // appear to induce the bug.)
3555 // (main thread only; `atomic` not needed)
3556 static bool sIsFirstFullscreenEntry = true;
3557 bool isFirstFullscreenEntry = sIsFirstFullscreenEntry;
3558 sIsFirstFullscreenEntry = false;
3559 if (MOZ_LIKELY(!isFirstFullscreenEntry)) {
3560 return;
3562 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3563 ("%s: first fullscreen entry", __PRETTY_FUNCTION__));
3566 // Check whether to try to apply the DWM resize hack, based on the override
3567 // pref and/or some internal heuristics.
3569 const auto hackApplicationHeuristics = [&]() -> bool {
3570 // The bug has only been seen under Windows 11. (At time of writing, this
3571 // is the latest version of Windows.)
3572 if (!IsWin11OrLater()) {
3573 return false;
3576 KnowsCompositor const* const kc = mWindowRenderer->AsKnowsCompositor();
3577 // This should never happen...
3578 MOZ_ASSERT(kc);
3579 // ... so if it does, we are in uncharted territory: don't apply the hack.
3580 if (!kc) {
3581 return false;
3584 // The bug doesn't occur when we're using a separate compositor window
3585 // (since the compositor window always comprises exactly its client area,
3586 // with no non-client border).
3587 if (kc->GetUseCompositorWnd()) {
3588 return false;
3591 // Otherwise, apply the hack.
3592 return true;
3595 // Figure out whether or not we should perform the hack, and -- arguably
3596 // more importantly -- log that decision.
3597 bool const shouldApplyHack = [&]() {
3598 enum Reason : bool { Pref, Heuristics };
3599 auto const msg = [&](bool decision, Reason reason) -> bool {
3600 MOZ_LOG(gWindowsLog, LogLevel::Verbose,
3601 ("%s %s per %s", decision ? "applying" : "skipping",
3602 "DWM resize hack", reason == Pref ? "pref" : "heuristics"));
3603 return decision;
3605 switch (StaticPrefs::widget_windows_apply_dwm_resize_hack()) {
3606 case 0:
3607 return msg(false, Pref);
3608 case 1:
3609 return msg(true, Pref);
3610 default: // treat all other values as `auto`
3611 return msg(hackApplicationHeuristics(), Heuristics);
3613 }();
3615 if (!shouldApplyHack) {
3616 return;
3620 // The DWM bug is believed to involve a race condition: some users have
3621 // reported that setting a custom theme or adding unused command-line
3622 // parameters sometimes causes the bug to vanish.
3624 // Out of an abundance of caution, we therefore apply the hack in a later
3625 // event, rather than inline.
3626 NS_DispatchToMainThread(NS_NewRunnableFunction(
3627 "nsWindow::TryFullscreenResizeHack", [self = RefPtr(this)]() {
3628 HWND const hwnd = self->GetWindowHandle();
3630 if (self->mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
3631 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info,
3632 ("DWM resize hack: window no longer fullscreen; aborting"));
3633 return;
3636 RECT origRect;
3637 if (!::GetWindowRect(hwnd, &origRect)) {
3638 MOZ_LOG(gWindowsLog, mozilla::LogLevel::Error,
3639 ("DWM resize hack: could not get window size?!"));
3640 return;
3642 LONG const x = origRect.left;
3643 LONG const y = origRect.top;
3644 LONG const width = origRect.right - origRect.left;
3645 LONG const height = origRect.bottom - origRect.top;
3647 MOZ_DIAGNOSTIC_ASSERT(!self->mIsPerformingDwmFlushHack);
3648 auto const onExit =
3649 MakeScopeExit([&, oldVal = self->mIsPerformingDwmFlushHack]() {
3650 self->mIsPerformingDwmFlushHack = oldVal;
3652 self->mIsPerformingDwmFlushHack = true;
3654 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3655 ("beginning DWM resize hack for HWND %08" PRIXPTR,
3656 uintptr_t(hwnd)));
3657 ::MoveWindow(hwnd, x, y, width, height - 1, FALSE);
3658 ::MoveWindow(hwnd, x, y, width, height, TRUE);
3659 MOZ_LOG(gWindowsLog, LogLevel::Debug,
3660 ("concluded DWM resize hack for HWND %08" PRIXPTR,
3661 uintptr_t(hwnd)));
3662 }));
3665 void nsWindow::OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen) {
3666 MOZ_ASSERT((aOldSizeMode != nsSizeMode_Fullscreen) == aFullScreen);
3668 // HACK: Potentially flicker-resize the window, to force DWM to get the right
3669 // client-area information.
3670 if (aFullScreen) {
3671 TryDwmResizeHack();
3674 // Hide chrome and reposition window. Note this will also cache dimensions for
3675 // restoration, so it should only be called once per fullscreen request.
3677 // Don't do this when minimized, since our bounds make no sense then, nor when
3678 // coming back from that state.
3679 const bool toOrFromMinimized =
3680 mFrameState->GetSizeMode() == nsSizeMode_Minimized ||
3681 aOldSizeMode == nsSizeMode_Minimized;
3682 if (!toOrFromMinimized) {
3683 InfallibleMakeFullScreen(aFullScreen);
3686 // Possibly notify the taskbar that we have changed our fullscreen mode.
3687 TaskbarConcealer::OnFullscreenChanged(this, aFullScreen);
3690 nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
3691 mFrameState->EnsureFullscreenMode(aFullScreen);
3692 return NS_OK;
3695 /**************************************************************
3697 * SECTION: Native data storage
3699 * nsIWidget::GetNativeData
3700 * nsIWidget::FreeNativeData
3702 * Set or clear native data based on a constant.
3704 **************************************************************/
3706 // Return some native data according to aDataType
3707 void* nsWindow::GetNativeData(uint32_t aDataType) {
3708 switch (aDataType) {
3709 case NS_NATIVE_WIDGET:
3710 case NS_NATIVE_WINDOW:
3711 case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
3712 return (void*)mWnd;
3713 case NS_NATIVE_GRAPHIC:
3714 MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
3715 return nullptr;
3716 case NS_RAW_NATIVE_IME_CONTEXT: {
3717 void* pseudoIMEContext = GetPseudoIMEContext();
3718 if (pseudoIMEContext) {
3719 return pseudoIMEContext;
3721 [[fallthrough]];
3723 case NS_NATIVE_TSF_THREAD_MGR:
3724 case NS_NATIVE_TSF_CATEGORY_MGR:
3725 case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
3726 return IMEHandler::GetNativeData(this, aDataType);
3728 default:
3729 break;
3732 return nullptr;
3735 // Free some native data according to aDataType
3736 void nsWindow::FreeNativeData(void* data, uint32_t aDataType) {
3737 switch (aDataType) {
3738 case NS_NATIVE_GRAPHIC:
3739 case NS_NATIVE_WIDGET:
3740 case NS_NATIVE_WINDOW:
3741 break;
3742 default:
3743 break;
3747 /**************************************************************
3749 * SECTION: nsIWidget::SetTitle
3751 * Set the main windows title text.
3753 **************************************************************/
3755 nsresult nsWindow::SetTitle(const nsAString& aTitle) {
3756 const nsString& strTitle = PromiseFlatString(aTitle);
3757 AutoRestore<bool> sendingText(mSendingSetText);
3758 mSendingSetText = true;
3759 ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
3760 return NS_OK;
3763 /**************************************************************
3765 * SECTION: nsIWidget::SetIcon
3767 * Set the main windows icon.
3769 **************************************************************/
3771 void nsWindow::SetBigIcon(HICON aIcon) {
3772 HICON icon =
3773 (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
3774 if (icon) {
3775 ::DestroyIcon(icon);
3778 mIconBig = aIcon;
3781 void nsWindow::SetSmallIcon(HICON aIcon) {
3782 HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
3783 (LPARAM)aIcon);
3784 if (icon) {
3785 ::DestroyIcon(icon);
3788 mIconSmall = aIcon;
3791 void nsWindow::SetIcon(const nsAString& aIconSpec) {
3792 // Assume the given string is a local identifier for an icon file.
3794 nsCOMPtr<nsIFile> iconFile;
3795 ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
3796 if (!iconFile) return;
3798 nsAutoString iconPath;
3799 iconFile->GetPath(iconPath);
3801 // XXX this should use MZLU (see bug 239279)
3803 ::SetLastError(0);
3805 HICON bigIcon =
3806 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3807 ::GetSystemMetrics(SM_CXICON),
3808 ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
3809 HICON smallIcon =
3810 (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
3811 ::GetSystemMetrics(SM_CXSMICON),
3812 ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
3814 if (bigIcon) {
3815 SetBigIcon(bigIcon);
3817 #ifdef DEBUG_SetIcon
3818 else {
3819 NS_LossyConvertUTF16toASCII cPath(iconPath);
3820 MOZ_LOG(gWindowsLog, LogLevel::Info,
3821 ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3822 ::GetLastError()));
3824 #endif
3825 if (smallIcon) {
3826 SetSmallIcon(smallIcon);
3828 #ifdef DEBUG_SetIcon
3829 else {
3830 NS_LossyConvertUTF16toASCII cPath(iconPath);
3831 MOZ_LOG(gWindowsLog, LogLevel::Info,
3832 ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
3833 ::GetLastError()));
3835 #endif
3838 void nsWindow::SetBigIconNoData() {
3839 HICON bigIcon =
3840 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3841 SetBigIcon(bigIcon);
3844 void nsWindow::SetSmallIconNoData() {
3845 HICON smallIcon =
3846 ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
3847 SetSmallIcon(smallIcon);
3850 /**************************************************************
3852 * SECTION: nsIWidget::WidgetToScreenOffset
3854 * Return this widget's origin in screen coordinates.
3856 **************************************************************/
3858 LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
3859 POINT point;
3860 point.x = 0;
3861 point.y = 0;
3862 ::ClientToScreen(mWnd, &point);
3863 return LayoutDeviceIntPoint(point.x, point.y);
3866 LayoutDeviceIntMargin nsWindow::ClientToWindowMargin() {
3867 if (mWindowType == WindowType::Popup && !IsPopupWithTitleBar()) {
3868 return {};
3871 if (mCustomNonClient) {
3872 return NonClientSizeMargin(NormalWindowNonClientOffset());
3875 // Just use a dummy 200x200 at (200, 200) client rect as the rect.
3876 RECT clientRect;
3877 clientRect.left = 200;
3878 clientRect.top = 200;
3879 clientRect.right = 400;
3880 clientRect.bottom = 400;
3882 auto ToRect = [](const RECT& aRect) -> LayoutDeviceIntRect {
3883 return {aRect.left, aRect.top, aRect.right - aRect.left,
3884 aRect.bottom - aRect.top};
3887 RECT windowRect = clientRect;
3888 ::AdjustWindowRectEx(&windowRect, WindowStyle(), false, WindowExStyle());
3890 return ToRect(windowRect) - ToRect(clientRect);
3893 /**************************************************************
3895 * SECTION: nsIWidget::EnableDragDrop
3897 * Enables/Disables drag and drop of files on this widget.
3899 **************************************************************/
3901 void nsWindow::EnableDragDrop(bool aEnable) {
3902 if (!mWnd) {
3903 // Return early if the window already closed
3904 return;
3907 if (aEnable) {
3908 if (!mNativeDragTarget) {
3909 mNativeDragTarget = new nsNativeDragTarget(this);
3910 mNativeDragTarget->AddRef();
3911 if (SUCCEEDED(::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, TRUE,
3912 FALSE))) {
3913 ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
3916 } else {
3917 if (mWnd && mNativeDragTarget) {
3918 ::RevokeDragDrop(mWnd);
3919 ::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, FALSE, TRUE);
3920 mNativeDragTarget->DragCancel();
3921 NS_RELEASE(mNativeDragTarget);
3926 /**************************************************************
3928 * SECTION: nsIWidget::CaptureMouse
3930 * Enables/Disables system mouse capture.
3932 **************************************************************/
3934 void nsWindow::CaptureMouse(bool aCapture) {
3935 TRACKMOUSEEVENT mTrack;
3936 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
3937 mTrack.dwHoverTime = 0;
3938 mTrack.hwndTrack = mWnd;
3939 if (aCapture) {
3940 mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
3941 ::SetCapture(mWnd);
3942 } else {
3943 mTrack.dwFlags = TME_LEAVE;
3944 ::ReleaseCapture();
3946 sIsInMouseCapture = aCapture;
3947 TrackMouseEvent(&mTrack);
3950 /**************************************************************
3952 * SECTION: nsIWidget::CaptureRollupEvents
3954 * Dealing with event rollup on destroy for popups. Enables &
3955 * Disables system capture of any and all events that would
3956 * cause a dropdown to be rolled up.
3958 **************************************************************/
3960 void nsWindow::CaptureRollupEvents(bool aDoCapture) {
3961 if (aDoCapture) {
3962 if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
3963 RegisterSpecialDropdownHooks();
3965 sProcessHook = true;
3966 } else {
3967 sProcessHook = false;
3968 UnregisterSpecialDropdownHooks();
3972 /**************************************************************
3974 * SECTION: nsIWidget::GetAttention
3976 * Bring this window to the user's attention.
3978 **************************************************************/
3980 // Draw user's attention to this window until it comes to foreground.
3981 nsresult nsWindow::GetAttention(int32_t aCycleCount) {
3982 // Got window?
3983 if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
3985 HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
3986 HWND fgWnd = ::GetForegroundWindow();
3987 // Don't flash if the flash count is 0 or if the foreground window is our
3988 // window handle or that of our owned-most window.
3989 if (aCycleCount == 0 || flashWnd == fgWnd ||
3990 flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
3991 return NS_OK;
3994 DWORD defaultCycleCount = 0;
3995 ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
3997 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
3998 aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
3999 ::FlashWindowEx(&flashInfo);
4001 return NS_OK;
4004 void nsWindow::StopFlashing() {
4005 HWND flashWnd = mWnd;
4006 while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
4007 flashWnd = ownerWnd;
4010 FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
4011 ::FlashWindowEx(&flashInfo);
4014 /**************************************************************
4016 * SECTION: nsIWidget::HasPendingInputEvent
4018 * Ask whether there user input events pending. All input events are
4019 * included, including those not targeted at this nsIwidget instance.
4021 **************************************************************/
4023 bool nsWindow::HasPendingInputEvent() {
4024 // If there is pending input or the user is currently
4025 // moving the window then return true.
4026 // Note: When the user is moving the window WIN32 spins
4027 // a separate event loop and input events are not
4028 // reported to the application.
4029 if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
4030 GUITHREADINFO guiInfo;
4031 guiInfo.cbSize = sizeof(GUITHREADINFO);
4032 if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
4033 return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
4036 /**************************************************************
4038 * SECTION: nsIWidget::GetWindowRenderer
4040 * Get the window renderer associated with this widget.
4042 **************************************************************/
4044 WindowRenderer* nsWindow::GetWindowRenderer() {
4045 if (mWindowRenderer) {
4046 return mWindowRenderer;
4049 if (!mLocalesChangedObserver) {
4050 mLocalesChangedObserver = new LocalesChangedObserver(this);
4053 // Try OMTC first.
4054 if (!mWindowRenderer && ShouldUseOffMainThreadCompositing()) {
4055 gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
4056 CreateCompositor();
4059 if (!mWindowRenderer) {
4060 MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
4061 MOZ_ASSERT(!mCompositorWidgetDelegate);
4063 // Ensure we have a widget proxy even if we're not using the compositor,
4064 // since all our transparent window handling lives there.
4065 WinCompositorWidgetInitData initData(
4066 reinterpret_cast<uintptr_t>(mWnd),
4067 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
4068 mTransparencyMode, mFrameState->GetSizeMode());
4069 // If we're not using the compositor, the options don't actually matter.
4070 CompositorOptions options(false, false);
4071 mBasicLayersSurface =
4072 new InProcessWinCompositorWidget(initData, options, this);
4073 mCompositorWidgetDelegate = mBasicLayersSurface;
4074 mWindowRenderer = CreateFallbackRenderer();
4077 NS_ASSERTION(mWindowRenderer, "Couldn't provide a valid window renderer.");
4079 if (mWindowRenderer) {
4080 // Update the size constraints now that the layer manager has been
4081 // created.
4082 KnowsCompositor* knowsCompositor = mWindowRenderer->AsKnowsCompositor();
4083 if (knowsCompositor) {
4084 SizeConstraints c = mSizeConstraints;
4085 mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
4086 c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
4087 c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
4088 nsBaseWidget::SetSizeConstraints(c);
4092 return mWindowRenderer;
4095 /**************************************************************
4097 * SECTION: nsBaseWidget::SetCompositorWidgetDelegate
4099 * Called to connect the nsWindow to the delegate providing
4100 * platform compositing API access.
4102 **************************************************************/
4104 void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
4105 if (delegate) {
4106 mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
4107 MOZ_ASSERT(mCompositorWidgetDelegate,
4108 "nsWindow::SetCompositorWidgetDelegate called with a "
4109 "non-PlatformCompositorWidgetDelegate");
4110 } else {
4111 mCompositorWidgetDelegate = nullptr;
4115 /**************************************************************
4117 * SECTION: nsIWidget::OnDefaultButtonLoaded
4119 * Called after the dialog is loaded and it has a default button.
4121 **************************************************************/
4123 nsresult nsWindow::OnDefaultButtonLoaded(
4124 const LayoutDeviceIntRect& aButtonRect) {
4125 if (aButtonRect.IsEmpty()) return NS_OK;
4127 // Don't snap when we are not active.
4128 HWND activeWnd = ::GetActiveWindow();
4129 if (activeWnd != ::GetForegroundWindow() ||
4130 WinUtils::GetTopLevelHWND(mWnd, true) !=
4131 WinUtils::GetTopLevelHWND(activeWnd, true)) {
4132 return NS_OK;
4135 bool isAlwaysSnapCursor =
4136 Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
4138 if (!isAlwaysSnapCursor) {
4139 BOOL snapDefaultButton;
4140 if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
4141 0) ||
4142 !snapDefaultButton)
4143 return NS_OK;
4146 LayoutDeviceIntRect widgetRect = GetScreenBounds();
4147 LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
4149 LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
4150 buttonRect.Y() + buttonRect.Height() / 2);
4151 // The center of the button can be outside of the widget.
4152 // E.g., it could be hidden by scrolling.
4153 if (!widgetRect.Contains(centerOfButton)) {
4154 return NS_OK;
4157 if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
4158 NS_ERROR("SetCursorPos failed");
4159 return NS_ERROR_FAILURE;
4161 return NS_OK;
4164 uint32_t nsWindow::GetMaxTouchPoints() const {
4165 return WinUtils::GetMaxTouchPoints();
4168 void nsWindow::SetWindowClass(const nsAString& xulWinType,
4169 const nsAString& xulWinClass,
4170 const nsAString& xulWinName) {
4171 mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank");
4174 /**************************************************************
4175 **************************************************************
4177 ** BLOCK: Moz Events
4179 ** Moz GUI event management.
4181 **************************************************************
4182 **************************************************************/
4184 /**************************************************************
4186 * SECTION: Mozilla event initialization
4188 * Helpers for initializing moz events.
4190 **************************************************************/
4192 // Event initialization
4193 void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
4194 if (nullptr == aPoint) { // use the point from the event
4195 // get the message position in client coordinates
4196 if (mWnd != nullptr) {
4197 DWORD pos = ::GetMessagePos();
4198 POINT cpos;
4200 cpos.x = GET_X_LPARAM(pos);
4201 cpos.y = GET_Y_LPARAM(pos);
4203 ::ScreenToClient(mWnd, &cpos);
4204 event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
4205 } else {
4206 event.mRefPoint = LayoutDeviceIntPoint(0, 0);
4208 } else {
4209 // use the point override if provided
4210 event.mRefPoint = *aPoint;
4213 event.AssignEventTime(CurrentMessageWidgetEventTime());
4216 WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
4217 LONG messageTime = ::GetMessageTime();
4218 return WidgetEventTime(GetMessageTimeStamp(messageTime));
4221 /**************************************************************
4223 * SECTION: Moz event dispatch helpers
4225 * Helpers for dispatching different types of moz events.
4227 **************************************************************/
4229 // Main event dispatch. Invokes callback and ProcessEvent method on
4230 // Event Listener object. Part of nsIWidget.
4231 nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
4232 nsEventStatus& aStatus) {
4233 #ifdef WIDGET_DEBUG_OUTPUT
4234 debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
4235 #endif // WIDGET_DEBUG_OUTPUT
4237 aStatus = nsEventStatus_eIgnore;
4239 // Top level windows can have a view attached which requires events be sent
4240 // to the underlying base window and the view. Added when we combined the
4241 // base chrome window with the main content child for nc client area (title
4242 // bar) rendering.
4243 if (mAttachedWidgetListener) {
4244 aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
4245 } else if (mWidgetListener) {
4246 aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
4249 // the window can be destroyed during processing of seemingly innocuous events
4250 // like, say, mousedowns due to the magic of scripting. mousedowns will return
4251 // nsEventStatus_eIgnore, which causes problems with the deleted window.
4252 // therefore:
4253 if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
4254 return NS_OK;
4257 bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
4258 WidgetGUIEvent event(true, aMsg, this);
4259 InitEvent(event);
4261 bool result = DispatchWindowEvent(event);
4262 return result;
4265 bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
4266 nsEventStatus status = DispatchInputEvent(event).mContentStatus;
4267 return ConvertStatus(status);
4270 bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
4271 nsEventStatus status;
4272 DispatchEvent(aEvent, status);
4273 return ConvertStatus(status);
4276 bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
4277 nsEventStatus status =
4278 DispatchInputEvent(aEvent->AsInputEvent()).mContentStatus;
4279 return ConvertStatus(status);
4282 // Recursively dispatch synchronous paints for nsIWidget
4283 // descendants with invalidated rectangles.
4284 BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
4285 LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
4286 if (proc == (LONG_PTR)&nsWindow::WindowProc) {
4287 // its one of our windows so check to see if it has a
4288 // invalidated rect. If it does. Dispatch a synchronous
4289 // paint.
4290 if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
4292 return TRUE;
4295 // Check for pending paints and dispatch any pending paint
4296 // messages for any nsIWidget which is a descendant of the
4297 // top-level window that *this* window is embedded within.
4299 // Note: We do not dispatch pending paint messages for non
4300 // nsIWidget managed windows.
4301 void nsWindow::DispatchPendingEvents() {
4302 // We need to ensure that reflow events do not get starved.
4303 // At the same time, we don't want to recurse through here
4304 // as that would prevent us from dispatching starved paints.
4305 static int recursionBlocker = 0;
4306 if (recursionBlocker++ == 0) {
4307 NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
4308 --recursionBlocker;
4311 // Quickly check to see if there are any paint events pending,
4312 // but only dispatch them if it has been long enough since the
4313 // last paint completed.
4314 if (::GetQueueStatus(QS_PAINT) &&
4315 ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
4316 // Find the top level window.
4317 HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
4319 // Dispatch pending paints for topWnd and all its descendant windows.
4320 // Note: EnumChildWindows enumerates all descendant windows not just
4321 // the children (but not the window itself).
4322 nsWindow::DispatchStarvedPaints(topWnd, 0);
4323 ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
4327 void nsWindow::DispatchCustomEvent(const nsString& eventName) {
4328 if (Document* doc = GetDocument()) {
4329 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
4330 win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
4335 bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
4336 LayoutDeviceIntPoint aEventPoint) {
4337 // Allow users to start dragging by double-tapping.
4338 if (aEventMessage == eMouseDoubleClick) {
4339 return true;
4342 // In chrome UI, allow touchdownstartsdrag attributes
4343 // to cause any touchdown event to trigger a drag.
4344 if (aEventMessage == eMouseDown) {
4345 WidgetMouseEvent hittest(true, eMouseHitTest, this,
4346 WidgetMouseEvent::eReal);
4347 hittest.mRefPoint = aEventPoint;
4348 hittest.mIgnoreRootScrollFrame = true;
4349 hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
4350 DispatchInputEvent(&hittest);
4352 if (EventTarget* target = hittest.GetDOMEventTarget()) {
4353 if (nsIContent* content = nsIContent::FromEventTarget(target)) {
4354 // Check if the element or any parent element has the
4355 // attribute we're looking for.
4356 for (Element* element = content->GetAsElementOrParentElement(); element;
4357 element = element->GetParentElement()) {
4358 nsAutoString startDrag;
4359 element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
4360 if (!startDrag.IsEmpty()) {
4361 return true;
4368 return false;
4371 // Deal with all sort of mouse event
4372 bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
4373 LPARAM lParam, bool aIsContextMenuKey,
4374 int16_t aButton, uint16_t aInputSource,
4375 WinPointerInfo* aPointerInfo,
4376 bool aIgnoreAPZ) {
4377 ContextMenuPreventer contextMenuPreventer(this);
4378 bool result = false;
4380 UserActivity();
4382 if (!mWidgetListener) {
4383 return result;
4386 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
4387 LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
4389 // Suppress mouse moves caused by widget creation. Make sure to do this early
4390 // so that we update sLastMouseMovePoint even for touch-induced mousemove
4391 // events.
4392 if (aEventMessage == eMouseMove) {
4393 if ((sLastMouseMovePoint.x == mpScreen.x.value) &&
4394 (sLastMouseMovePoint.y == mpScreen.y.value)) {
4395 return result;
4397 sLastMouseMovePoint.x = mpScreen.x;
4398 sLastMouseMovePoint.y = mpScreen.y;
4401 if (!aIgnoreAPZ && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
4402 if (mTouchWindow) {
4403 // If mTouchWindow is true, then we must have APZ enabled and be
4404 // feeding it raw touch events. In that case we only want to
4405 // send touch-generated mouse events to content if they should
4406 // start a touch-based drag-and-drop gesture, such as on
4407 // double-tapping or when tapping elements marked with the
4408 // touchdownstartsdrag attribute in chrome UI.
4409 MOZ_ASSERT(mAPZC);
4410 if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
4411 aEventMessage = eMouseTouchDrag;
4412 } else {
4413 return result;
4418 uint32_t pointerId =
4419 aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
4421 // Since it is unclear whether a user will use the digitizer,
4422 // Postpone initialization until first PEN message will be found.
4423 if (MouseEvent_Binding::MOZ_SOURCE_PEN == aInputSource
4424 // Messages should be only at topLevel window.
4425 && WindowType::TopLevel == mWindowType
4426 // Currently this scheme is used only when pointer events is enabled.
4427 && InkCollector::sInkCollector) {
4428 InkCollector::sInkCollector->SetTarget(mWnd);
4429 InkCollector::sInkCollector->SetPointerId(pointerId);
4432 switch (aEventMessage) {
4433 case eMouseDown:
4434 CaptureMouse(true);
4435 break;
4437 // eMouseMove and eMouseExitFromWidget are here because we need to make
4438 // sure capture flag isn't left on after a drag where we wouldn't see a
4439 // button up message (see bug 324131).
4440 case eMouseUp:
4441 case eMouseMove:
4442 case eMouseExitFromWidget:
4443 if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
4444 sIsInMouseCapture)
4445 CaptureMouse(false);
4446 break;
4448 default:
4449 break;
4451 } // switch
4453 WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal,
4454 aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
4455 : WidgetMouseEvent::eNormal);
4456 if (aEventMessage == eContextMenu && aIsContextMenuKey) {
4457 LayoutDeviceIntPoint zero(0, 0);
4458 InitEvent(event, &zero);
4459 } else {
4460 InitEvent(event, &eventPoint);
4463 ModifierKeyState modifierKeyState;
4464 modifierKeyState.InitInputEvent(event);
4466 // eContextMenu with Shift state is special. It won't fire "contextmenu"
4467 // event in the web content for blocking web content to prevent its default.
4468 // However, Shift+F10 is a standard shortcut key on Windows. Therefore,
4469 // this should not block web page to prevent its default. I.e., it should
4470 // behave same as ContextMenu key without Shift key.
4471 // XXX Should we allow to block web page to prevent its default with
4472 // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
4473 if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() &&
4474 NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
4475 NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
4476 event.mModifiers &= ~MODIFIER_SHIFT;
4479 event.mButton = aButton;
4480 event.mInputSource = aInputSource;
4481 if (aPointerInfo) {
4482 // Mouse events from Windows WM_POINTER*. Fill more information in
4483 // WidgetMouseEvent.
4484 event.AssignPointerHelperData(*aPointerInfo);
4485 event.mPressure = aPointerInfo->mPressure;
4486 event.mButtons = aPointerInfo->mButtons;
4487 } else {
4488 // If we get here the mouse events must be from non-touch sources, so
4489 // convert it to pointer events as well
4490 event.convertToPointer = true;
4491 event.pointerId = pointerId;
4494 // Static variables used to distinguish simple-, double- and triple-clicks.
4495 static POINT sLastMousePoint = {0};
4496 static LONG sLastMouseDownTime = 0L;
4497 static LONG sLastClickCount = 0L;
4498 static BYTE sLastMouseButton = 0;
4500 bool insideMovementThreshold =
4501 (DeprecatedAbs(sLastMousePoint.x - eventPoint.x.value) <
4502 (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
4503 (DeprecatedAbs(sLastMousePoint.y - eventPoint.y.value) <
4504 (short)::GetSystemMetrics(SM_CYDOUBLECLK));
4506 BYTE eventButton;
4507 switch (aButton) {
4508 case MouseButton::ePrimary:
4509 eventButton = VK_LBUTTON;
4510 break;
4511 case MouseButton::eMiddle:
4512 eventButton = VK_MBUTTON;
4513 break;
4514 case MouseButton::eSecondary:
4515 eventButton = VK_RBUTTON;
4516 break;
4517 default:
4518 eventButton = 0;
4519 break;
4522 // Doubleclicks are used to set the click count, then changed to mousedowns
4523 // We're going to time double-clicks from mouse *up* to next mouse *down*
4524 LONG curMsgTime = ::GetMessageTime();
4526 switch (aEventMessage) {
4527 case eMouseDoubleClick:
4528 event.mMessage = eMouseDown;
4529 event.mButton = aButton;
4530 sLastClickCount = 2;
4531 sLastMouseDownTime = curMsgTime;
4532 break;
4533 case eMouseUp:
4534 // remember when this happened for the next mouse down
4535 sLastMousePoint.x = eventPoint.x;
4536 sLastMousePoint.y = eventPoint.y;
4537 sLastMouseButton = eventButton;
4538 break;
4539 case eMouseDown:
4540 // now look to see if we want to convert this to a double- or triple-click
4541 if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
4542 insideMovementThreshold && eventButton == sLastMouseButton) {
4543 sLastClickCount++;
4544 } else {
4545 // reset the click count, to count *this* click
4546 sLastClickCount = 1;
4548 // Set last Click time on MouseDown only
4549 sLastMouseDownTime = curMsgTime;
4550 break;
4551 case eMouseMove:
4552 if (!insideMovementThreshold) {
4553 sLastClickCount = 0;
4555 break;
4556 case eMouseExitFromWidget:
4557 event.mExitFrom =
4558 Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
4559 : WidgetMouseEvent::ePlatformChild);
4560 break;
4561 default:
4562 break;
4564 event.mClickCount = sLastClickCount;
4566 #ifdef NS_DEBUG_XX
4567 MOZ_LOG(gWindowsLog, LogLevel::Info,
4568 ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount));
4569 #endif
4571 // call the event callback
4572 if (mWidgetListener) {
4573 if (aEventMessage == eMouseMove) {
4574 LayoutDeviceIntRect rect = GetBounds();
4575 rect.MoveTo(0, 0);
4577 if (rect.Contains(event.mRefPoint)) {
4578 if (sCurrentWindow == nullptr || sCurrentWindow != this) {
4579 if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
4580 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4581 sCurrentWindow->DispatchMouseEvent(
4582 eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
4583 aInputSource, aPointerInfo);
4585 sCurrentWindow = this;
4586 if (!mInDtor) {
4587 LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
4588 sCurrentWindow->DispatchMouseEvent(
4589 eMouseEnterIntoWidget, wParam, pos, false,
4590 MouseButton::ePrimary, aInputSource, aPointerInfo);
4594 } else if (aEventMessage == eMouseExitFromWidget) {
4595 if (sCurrentWindow == this) {
4596 sCurrentWindow = nullptr;
4600 nsIWidget::ContentAndAPZEventStatus eventStatus =
4601 DispatchInputEvent(&event);
4602 contextMenuPreventer.Update(event, eventStatus);
4603 return ConvertStatus(eventStatus.mContentStatus);
4606 return result;
4609 HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
4610 // retrieve the toplevel window or dialogue
4611 HWND toplevelWnd = nullptr;
4612 while (aCurWnd) {
4613 toplevelWnd = aCurWnd;
4614 nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
4615 if (win) {
4616 if (win->mWindowType == WindowType::TopLevel ||
4617 win->mWindowType == WindowType::Dialog) {
4618 break;
4622 aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
4624 return toplevelWnd;
4627 void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
4628 if (aIsActivate) {
4629 sJustGotActivate = false;
4631 sJustGotDeactivate = false;
4632 mLastKillFocusWindow = nullptr;
4634 HWND toplevelWnd = GetTopLevelForFocus(mWnd);
4636 if (toplevelWnd) {
4637 nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
4638 if (win && win->mWidgetListener) {
4639 if (aIsActivate) {
4640 win->mWidgetListener->WindowActivated();
4641 } else {
4642 win->mWidgetListener->WindowDeactivated();
4648 HWND nsWindow::WindowAtMouse() {
4649 DWORD pos = ::GetMessagePos();
4650 POINT mp;
4651 mp.x = GET_X_LPARAM(pos);
4652 mp.y = GET_Y_LPARAM(pos);
4653 return ::WindowFromPoint(mp);
4656 bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
4657 HWND mouseWnd = WindowAtMouse();
4659 // WinUtils::GetTopLevelHWND() will return a HWND for the window frame
4660 // (which includes the non-client area). If the mouse has moved into
4661 // the non-client area, we should treat it as a top-level exit.
4662 HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
4663 if (mouseWnd == mouseTopLevel) return true;
4665 return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
4668 /**************************************************************
4670 * SECTION: IPC
4672 * IPC related helpers.
4674 **************************************************************/
4676 // static
4677 bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
4678 switch (aMsg) {
4679 case WM_SETFOCUS:
4680 case WM_KILLFOCUS:
4681 case WM_ENABLE:
4682 case WM_WINDOWPOSCHANGING:
4683 case WM_WINDOWPOSCHANGED:
4684 case WM_PARENTNOTIFY:
4685 case WM_ACTIVATEAPP:
4686 case WM_NCACTIVATE:
4687 case WM_ACTIVATE:
4688 case WM_CHILDACTIVATE:
4689 case WM_IME_SETCONTEXT:
4690 case WM_IME_NOTIFY:
4691 case WM_SHOWWINDOW:
4692 case WM_CANCELMODE:
4693 case WM_MOUSEACTIVATE:
4694 case WM_CONTEXTMENU:
4695 aResult = 0;
4696 return true;
4698 case WM_SETTINGCHANGE:
4699 case WM_SETCURSOR:
4700 return false;
4703 #ifdef DEBUG
4704 char szBuf[200];
4705 sprintf(szBuf,
4706 "An unhandled ISMEX_SEND message was received during spin loop! (%X)",
4707 aMsg);
4708 NS_WARNING(szBuf);
4709 #endif
4711 return false;
4714 void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
4715 MOZ_ASSERT_IF(
4716 msg != WM_GETOBJECT,
4717 !mozilla::ipc::MessageChannel::IsPumpingMessages() ||
4718 mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
4720 // Modal UI being displayed in windowless plugins.
4721 if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
4722 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4723 LRESULT res;
4724 if (IsAsyncResponseEvent(msg, res)) {
4725 ReplyMessage(res);
4727 return;
4730 // Handle certain sync plugin events sent to the parent which
4731 // trigger ipc calls that result in deadlocks.
4733 DWORD dwResult = 0;
4734 bool handled = false;
4736 switch (msg) {
4737 // Windowless flash sending WM_ACTIVATE events to the main window
4738 // via calls to ShowWindow.
4739 case WM_ACTIVATE:
4740 if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
4741 IsWindow((HWND)lParam)) {
4742 // Check for Adobe Reader X sync activate message from their
4743 // helper window and ignore. Fixes an annoying focus problem.
4744 if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
4745 ISMEX_SEND) {
4746 wchar_t szClass[10];
4747 HWND focusWnd = (HWND)lParam;
4748 if (IsWindowVisible(focusWnd) &&
4749 GetClassNameW(focusWnd, szClass,
4750 sizeof(szClass) / sizeof(char16_t)) &&
4751 !wcscmp(szClass, L"Edit") &&
4752 !WinUtils::IsOurProcessWindow(focusWnd)) {
4753 break;
4756 handled = true;
4758 break;
4759 // Plugins taking or losing focus triggering focus app messages.
4760 case WM_SETFOCUS:
4761 case WM_KILLFOCUS:
4762 // Windowed plugins that pass sys key events to defwndproc generate
4763 // WM_SYSCOMMAND events to the main window.
4764 case WM_SYSCOMMAND:
4765 // Windowed plugins that fire context menu selection events to parent
4766 // windows.
4767 case WM_CONTEXTMENU:
4768 // IME events fired as a result of synchronous focus changes
4769 case WM_IME_SETCONTEXT:
4770 handled = true;
4771 break;
4774 if (handled &&
4775 (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
4776 ReplyMessage(dwResult);
4780 /**************************************************************
4781 **************************************************************
4783 ** BLOCK: Native events
4785 ** Main Windows message handlers and OnXXX handlers for
4786 ** Windows event handling.
4788 **************************************************************
4789 **************************************************************/
4791 /**************************************************************
4793 * SECTION: Wind proc.
4795 * The main Windows event procedures and associated
4796 * message processing methods.
4798 **************************************************************/
4800 static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
4801 int32_t x, int32_t y) {
4802 HMENU hMenu = GetSystemMenu(hWnd, FALSE);
4803 if (hMenu) {
4804 MENUITEMINFO mii;
4805 mii.cbSize = sizeof(MENUITEMINFO);
4806 mii.fMask = MIIM_STATE;
4807 mii.fType = 0;
4809 // update the options
4810 mii.fState = MF_ENABLED;
4811 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4812 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4813 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4814 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4815 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4817 mii.fState = MF_GRAYED;
4818 switch (sizeMode) {
4819 case nsSizeMode_Fullscreen:
4820 // intentional fall through
4821 case nsSizeMode_Maximized:
4822 SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
4823 SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
4824 SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
4825 break;
4826 case nsSizeMode_Minimized:
4827 SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
4828 break;
4829 case nsSizeMode_Normal:
4830 SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
4831 break;
4832 case nsSizeMode_Invalid:
4833 NS_ASSERTION(false, "Did the argument come from invalid IPC?");
4834 break;
4835 default:
4836 MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected");
4837 break;
4839 LPARAM cmd = TrackPopupMenu(
4840 hMenu,
4841 (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN |
4842 (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
4843 x, y, 0, hWnd, nullptr);
4844 if (cmd) {
4845 PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
4846 return true;
4849 return false;
4852 // The WndProc procedure for all nsWindows in this toolkit. This merely catches
4853 // SEH exceptions and passes the real work to WindowProcInternal. See bug 587406
4854 // and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
4855 LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
4856 LPARAM lParam) {
4857 mozilla::ipc::CancelCPOWs();
4859 BackgroundHangMonitor().NotifyActivity();
4861 return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
4862 wParam, lParam);
4865 LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
4866 WPARAM wParam, LPARAM lParam) {
4867 if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
4868 // This message was sent to the FAKETRACKPOINTSCROLLABLE.
4869 if (msg == WM_HSCROLL) {
4870 // Route WM_HSCROLL messages to the main window.
4871 hWnd = ::GetParent(::GetParent(hWnd));
4872 } else {
4873 // Handle all other messages with its original window procedure.
4874 WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
4875 return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
4879 if (msg == MOZ_WM_TRACE) {
4880 // This is a tracer event for measuring event loop latency.
4881 // See WidgetTraceEvent.cpp for more details.
4882 mozilla::SignalTracerThread();
4883 return 0;
4886 // Get the window which caused the event and ask it to process the message
4887 nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
4888 NS_ASSERTION(targetWindow, "nsWindow* is null!");
4889 if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
4891 // Hold the window for the life of this method, in case it gets
4892 // destroyed during processing, unless we're in the dtor already.
4893 nsCOMPtr<nsIWidget> kungFuDeathGrip;
4894 if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
4896 targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
4898 // Create this here so that we store the last rolled up popup until after
4899 // the event has been processed.
4900 nsAutoRollup autoRollup;
4902 LRESULT popupHandlingResult;
4903 if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult))
4904 return popupHandlingResult;
4906 // Call ProcessMessage
4907 LRESULT retValue;
4908 if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
4909 return retValue;
4912 LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
4913 wParam, lParam);
4915 return res;
4918 const char16_t* GetQuitType() {
4919 if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
4920 DWORD cchCmdLine = 0;
4921 HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
4922 &cchCmdLine, nullptr);
4923 if (rc == S_OK) {
4924 return u"os-restart";
4927 return nullptr;
4930 bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
4931 LPARAM& aLParam,
4932 MSGResult& aResult) {
4933 if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
4934 return true;
4937 if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
4938 return true;
4941 if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
4942 aResult)) {
4943 return true;
4946 return false;
4949 // The main windows message processing method. Wraps ProcessMessageInternal so
4950 // we can log aRetValue.
4951 bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
4952 LRESULT* aRetValue) {
4953 // For some events we might change the parameter values, so log
4954 // before and after we process them.
4955 NativeEventLogger eventLogger("nsWindow", mWnd, msg, wParam, lParam);
4956 bool result = ProcessMessageInternal(msg, wParam, lParam, aRetValue);
4957 eventLogger.SetResult(*aRetValue, result);
4959 return result;
4962 // The main windows message processing method. Called by ProcessMessage.
4963 bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
4964 LRESULT* aRetValue) {
4965 MSGResult msgResult(aRetValue);
4966 if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
4967 return (msgResult.mConsumed || !mWnd);
4970 bool result = false; // call the default nsWindow proc
4971 *aRetValue = 0;
4973 // The DWM resize hack (see bug 1763981) causes us to process a number of
4974 // messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
4975 // would ordinarily result in a whole lot of internal state being updated.
4977 // Since we're supposed to end in the same state we started in (and since the
4978 // content shouldn't know about any of this nonsense), just discard any
4979 // messages synchronously dispatched from within the hack.
4980 if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
4981 return true;
4984 // Glass hit testing w/custom transparent margins.
4986 // FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
4987 // now.
4988 LRESULT dwmHitResult;
4989 if (mCustomNonClient &&
4990 DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
4991 *aRetValue = dwmHitResult;
4992 return true;
4995 // The preference whether to use a different keyboard layout for each
4996 // window is cached, and updating it will not take effect until the
4997 // next restart. We read the preference here and not upon WM_ACTIVATE to make
4998 // sure that this behavior is consistent. Otherwise, if the user changed the
4999 // preference before having ever lowered the window, the preference would take
5000 // effect immediately.
5001 static const bool sSwitchKeyboardLayout =
5002 Preferences::GetBool("intl.keyboard.per_window_layout", false);
5003 AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
5005 // (Large blocks of code should be broken out into OnEvent handlers.)
5006 switch (msg) {
5007 // WM_QUERYENDSESSION must be handled by all windows.
5008 // Otherwise Windows thinks the window can just be killed at will.
5009 case WM_QUERYENDSESSION: {
5010 // Ask around if it's ok to quit.
5011 nsCOMPtr<nsIObserverService> obsServ =
5012 mozilla::services::GetObserverService();
5013 nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
5014 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
5015 cancelQuitWrapper->SetData(false);
5017 const char16_t* quitType = GetQuitType();
5018 obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
5019 quitType);
5021 bool shouldCancelQuit;
5022 cancelQuitWrapper->GetData(&shouldCancelQuit);
5023 *aRetValue = !shouldCancelQuit;
5024 result = true;
5025 } break;
5027 case MOZ_WM_STARTA11Y:
5028 #if defined(ACCESSIBILITY)
5029 Unused << GetAccessible();
5030 result = true;
5031 #else
5032 result = false;
5033 #endif
5034 break;
5036 case WM_ENDSESSION: {
5037 // For WM_ENDSESSION, wParam indicates whether we need to shutdown
5038 // (TRUE) or not (FALSE).
5039 if (!wParam) {
5040 result = true;
5041 break;
5043 // According to WM_ENDSESSION lParam documentation:
5044 // 0 -> OS shutdown or restart (no way to distinguish)
5045 // ENDSESSION_LOGOFF -> User is logging off
5046 // ENDSESSION_CLOSEAPP -> Application must shutdown
5047 // ENDSESSION_CRITICAL -> Application is forced to shutdown
5048 // The difference of the last two is not very clear.
5049 if (lParam == 0) {
5050 shutdownReason = AppShutdownReason::OSShutdown;
5051 } else if (lParam & ENDSESSION_LOGOFF) {
5052 shutdownReason = AppShutdownReason::OSSessionEnd;
5053 } else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
5054 shutdownReason = AppShutdownReason::OSForceClose;
5055 } else {
5056 MOZ_DIAGNOSTIC_ASSERT(false,
5057 "Received WM_ENDSESSION with unknown flags.");
5058 shutdownReason = AppShutdownReason::OSForceClose;
5061 [[fallthrough]];
5062 case MOZ_WM_APP_QUIT: {
5063 if (shutdownReason == AppShutdownReason::Unknown) {
5064 // TODO: We do not expect that these days anybody sends us
5065 // MOZ_WM_APP_QUIT, see bug 1827807.
5066 shutdownReason = AppShutdownReason::WinUnexpectedMozQuit;
5068 // Let's fake a shutdown sequence without actually closing windows etc.
5069 // to avoid Windows killing us in the middle. A proper shutdown would
5070 // require having a chance to pump some messages. Unfortunately
5071 // Windows won't let us do that. Bug 212316.
5072 nsCOMPtr<nsIObserverService> obsServ =
5073 mozilla::services::GetObserverService();
5074 const char16_t* syncShutdown = u"syncShutdown";
5075 const char16_t* quitType = GetQuitType();
5077 AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
5079 obsServ->NotifyObservers(nullptr, "quit-application-granted",
5080 syncShutdown);
5081 obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
5083 AppShutdown::OnShutdownConfirmed();
5085 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
5086 quitType);
5087 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
5088 nullptr);
5089 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
5090 nullptr);
5091 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
5092 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
5093 AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
5094 nullptr);
5096 AppShutdown::DoImmediateExit();
5097 MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
5098 } break;
5100 case WM_SYSCOLORCHANGE:
5101 // No need to invalidate layout for system color changes, but we need to
5102 // invalidate style.
5103 NotifyThemeChanged(widget::ThemeChangeKind::Style);
5104 break;
5106 case WM_THEMECHANGED: {
5107 // Update non-client margin offsets
5108 UpdateNonClientMargins();
5109 nsUXThemeData::UpdateNativeThemeInfo();
5111 // We assume pretty much everything could've changed here.
5112 NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
5114 UpdateDarkModeToolbar();
5116 // Invalidate the window so that the repaint will
5117 // pick up the new theme.
5118 Invalidate(true, true, true);
5119 } break;
5121 case WM_WTSSESSION_CHANGE: {
5122 switch (wParam) {
5123 case WTS_CONSOLE_CONNECT:
5124 case WTS_REMOTE_CONNECT:
5125 case WTS_SESSION_UNLOCK:
5126 // When a session becomes visible, we should invalidate.
5127 Invalidate(true, true, true);
5128 break;
5129 default:
5130 break;
5132 } break;
5134 case WM_FONTCHANGE: {
5135 // We only handle this message for the hidden window,
5136 // as we only need to update the (global) font list once
5137 // for any given change, not once per window!
5138 if (mWindowType != WindowType::Invisible) {
5139 break;
5142 // update the global font list
5143 gfxPlatform::GetPlatform()->UpdateFontList();
5144 } break;
5146 case WM_SETTINGCHANGE: {
5147 if (wParam == SPI_SETCLIENTAREAANIMATION ||
5148 wParam == SPI_SETKEYBOARDDELAY) {
5149 // These need to update LookAndFeel cached values.
5150 // They affect reduced motion settings / caret blink count / and
5151 // keyboard cues, so no need to invalidate style / layout.
5152 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5153 break;
5155 if (wParam == SPI_SETFONTSMOOTHING ||
5156 wParam == SPI_SETFONTSMOOTHINGTYPE) {
5157 gfxDWriteFont::UpdateSystemTextVars();
5158 break;
5160 if (wParam == SPI_SETWORKAREA) {
5161 // NB: We also refresh screens on WM_DISPLAYCHANGE but the rcWork
5162 // values are sometimes wrong at that point. This message then
5163 // arrives soon afterward, when we can get the right rcWork values.
5164 ScreenHelperWin::RefreshScreens();
5165 break;
5167 if (auto lParamString = reinterpret_cast<const wchar_t*>(lParam)) {
5168 if (!wcscmp(lParamString, L"ImmersiveColorSet")) {
5169 // This affects system colors (-moz-win-accentcolor), so gotta pass
5170 // the style flag.
5171 NotifyThemeChanged(widget::ThemeChangeKind::Style);
5172 break;
5175 // UserInteractionMode, ConvertibleSlateMode, SystemDockMode may cause
5176 // @media(pointer) queries to change, which layout needs to know about
5178 // (WM_SETTINGCHANGE will be sent to all top-level windows, so we
5179 // only respond to the hidden top-level window to avoid hammering
5180 // layout with a bunch of NotifyThemeChanged() calls)
5182 if (mWindowType == WindowType::Invisible) {
5183 if (!wcscmp(lParamString, L"UserInteractionMode") ||
5184 !wcscmp(lParamString, L"ConvertibleSlateMode") ||
5185 !wcscmp(lParamString, L"SystemDockMode")) {
5186 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5187 WindowsUIUtils::UpdateInTabletMode();
5192 // SPI_GETMOUSEVANISH sends WM_SETTINGCHANGE when changed but does
5193 // not include identifiers in the parameters. WM_SETTINGCHANGE docs
5194 // recommend updating all cached settings when this message is received
5195 // anyway.
5196 GetMouseVanishSystemPref(true);
5197 } break;
5199 case WM_DEVICECHANGE: {
5200 if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
5201 DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
5202 // Check dbch_devicetype explicitly since we will get other device types
5203 // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify
5204 // DBT_DEVTYP_DEVICEINTERFACE in the filter for
5205 // RegisterDeviceNotification.
5206 if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
5207 // This can only change media queries (any-hover/any-pointer).
5208 NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
5211 } break;
5213 case WM_NCCALCSIZE: {
5214 // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
5215 // will need to be kept in sync.
5216 if (mCustomNonClient) {
5217 // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
5218 // the proposed window rectangle for our window. During our
5219 // processing of the `WM_NCCALCSIZE` message, we are expected to
5220 // modify the `RECT` that `lParam` points to, so that its value upon
5221 // our return is the new client area. We must return 0 if `wParam`
5222 // is `FALSE`.
5224 // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
5225 // struct. This struct contains an array of 3 `RECT`s, the first of
5226 // which has the exact same meaning as the `RECT` that is pointed to
5227 // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
5228 // conjunction with our return value, can
5229 // be used to specify portions of the source and destination window
5230 // rectangles that are valid and should be preserved. We opt not to
5231 // implement an elaborate client-area preservation technique, and
5232 // simply return 0, which means "preserve the entire old client area
5233 // and align it with the upper-left corner of our new client area".
5234 RECT* clientRect =
5235 wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
5236 : (reinterpret_cast<RECT*>(lParam));
5237 auto margin = NonClientSizeMargin();
5238 clientRect->top += margin.top;
5239 clientRect->left += margin.left;
5240 clientRect->right -= margin.right;
5241 clientRect->bottom -= margin.bottom;
5242 // Make client rect's width and height more than 0 to
5243 // avoid problems of webrender and angle.
5244 clientRect->right = std::max(clientRect->right, clientRect->left + 1);
5245 clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
5247 result = true;
5248 *aRetValue = 0;
5250 break;
5253 case WM_NCHITTEST: {
5254 if (mInputRegion.mFullyTransparent) {
5255 // Treat this window as transparent.
5256 *aRetValue = HTTRANSPARENT;
5257 result = true;
5258 break;
5261 if (mInputRegion.mMargin) {
5262 const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
5263 GET_Y_LPARAM(lParam));
5264 LayoutDeviceIntRect screenRect = GetScreenBounds();
5265 screenRect.Deflate(mInputRegion.mMargin);
5266 if (!screenRect.Contains(screenPoint)) {
5267 *aRetValue = HTTRANSPARENT;
5268 result = true;
5269 break;
5274 * If an nc client area margin has been moved, we are responsible
5275 * for calculating where the resize margins are and returning the
5276 * appropriate set of hit test constants. DwmDefWindowProc (above)
5277 * will handle hit testing on it's command buttons if we are on a
5278 * composited desktop.
5281 if (!mCustomNonClient) {
5282 break;
5285 *aRetValue =
5286 ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5287 result = true;
5288 break;
5291 case WM_SETTEXT:
5293 * WM_SETTEXT paints the titlebar area. Avoid this if we have a
5294 * custom titlebar we paint ourselves, or if we're the ones
5295 * sending the message with an updated title
5298 if ((mSendingSetText &&
5299 gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) ||
5300 !mCustomNonClient || mNonClientMargins.top == -1)
5301 break;
5304 // From msdn, the way around this is to disable the visible state
5305 // temporarily. We need the text to be set but we don't want the
5306 // redraw to occur. However, we need to make sure that we don't
5307 // do this at the same time that a Present is happening.
5309 // To do this we take mPresentLock in nsWindow::PreRender and
5310 // if that lock is taken we wait before doing WM_SETTEXT
5311 if (mCompositorWidgetDelegate) {
5312 mCompositorWidgetDelegate->EnterPresentLock();
5314 DWORD style = GetWindowLong(mWnd, GWL_STYLE);
5315 SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
5316 *aRetValue =
5317 CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
5318 SetWindowLong(mWnd, GWL_STYLE, style);
5319 if (mCompositorWidgetDelegate) {
5320 mCompositorWidgetDelegate->LeavePresentLock();
5323 return true;
5326 case WM_NCACTIVATE: {
5328 * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting
5329 * through WM_NCPAINT via InvalidateNonClientRegion.
5331 UpdateGetWindowInfoCaptionStatus(FALSE != wParam);
5333 if (!mCustomNonClient) {
5334 break;
5337 // There is a case that rendered result is not kept. Bug 1237617
5338 if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT() &&
5339 gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
5340 NS_DispatchToMainThread(NewRunnableMethod(
5341 "nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
5344 // let the dwm handle nc painting on glass
5345 // Never allow native painting if we are on fullscreen
5346 if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen &&
5347 gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled())
5348 break;
5350 if (wParam == TRUE) {
5351 // going active
5352 *aRetValue = FALSE; // ignored
5353 result = true;
5354 // invalidate to trigger a paint
5355 InvalidateNonClientRegion();
5356 break;
5357 } else {
5358 // going inactive
5359 *aRetValue = TRUE; // go ahead and deactive
5360 result = true;
5361 // invalidate to trigger a paint
5362 InvalidateNonClientRegion();
5363 break;
5367 case WM_NCPAINT: {
5369 * ClearType changes often don't send a WM_SETTINGCHANGE message. But they
5370 * do seem to always send a WM_NCPAINT message, so let's update on that.
5372 gfxDWriteFont::UpdateSystemTextVars();
5375 * Reset the non-client paint region so that it excludes the
5376 * non-client areas we paint manually. Then call defwndproc
5377 * to do the actual painting.
5380 if (!mCustomNonClient) break;
5382 // let the dwm handle nc painting on glass
5383 if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) break;
5385 HRGN paintRgn = ExcludeNonClientFromPaintRegion((HRGN)wParam);
5386 LRESULT res = CallWindowProcW(GetPrevWindowProc(), mWnd, msg,
5387 (WPARAM)paintRgn, lParam);
5388 if (paintRgn != (HRGN)wParam) DeleteObject(paintRgn);
5389 *aRetValue = res;
5390 result = true;
5391 } break;
5393 case WM_POWERBROADCAST:
5394 switch (wParam) {
5395 case PBT_APMSUSPEND:
5396 PostSleepWakeNotification(true);
5397 break;
5398 case PBT_APMRESUMEAUTOMATIC:
5399 case PBT_APMRESUMECRITICAL:
5400 case PBT_APMRESUMESUSPEND:
5401 PostSleepWakeNotification(false);
5402 break;
5404 break;
5406 case WM_CLOSE: // close request
5407 if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
5408 result = true; // abort window closure
5409 break;
5411 case WM_DESTROY:
5412 // clean up.
5413 DestroyLayerManager();
5414 OnDestroy();
5415 result = true;
5416 break;
5418 case WM_PAINT:
5419 *aRetValue = (int)OnPaint(nullptr, 0);
5420 result = true;
5421 break;
5423 case WM_PRINTCLIENT:
5424 result = OnPaint((HDC)wParam, 0);
5425 break;
5427 case WM_HOTKEY:
5428 result = OnHotKey(wParam, lParam);
5429 break;
5431 case WM_SYSCHAR:
5432 case WM_CHAR: {
5433 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5434 result = ProcessCharMessage(nativeMsg, nullptr);
5435 DispatchPendingEvents();
5436 } break;
5438 case WM_SYSKEYUP:
5439 case WM_KEYUP: {
5440 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5441 nativeMsg.time = ::GetMessageTime();
5442 result = ProcessKeyUpMessage(nativeMsg, nullptr);
5443 DispatchPendingEvents();
5444 } break;
5446 case WM_SYSKEYDOWN:
5447 case WM_KEYDOWN: {
5448 if (IsMouseVanishKey(wParam)) {
5449 MaybeHideCursor(true);
5452 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5453 result = ProcessKeyDownMessage(nativeMsg, nullptr);
5454 DispatchPendingEvents();
5455 } break;
5457 // Say we've dealt with erasing the background. (This is actually handled in
5458 // WM_PAINT, where necessary.)
5459 case WM_ERASEBKGND: {
5460 *aRetValue = 1;
5461 result = true;
5462 } break;
5464 case WM_MOUSEMOVE: {
5465 MaybeHideCursor(false);
5467 LPARAM lParamScreen = lParamToScreen(lParam);
5468 mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
5469 GET_Y_LPARAM(lParamScreen));
5471 if (!mMousePresent && !sIsInMouseCapture) {
5472 // First MOUSEMOVE over the client area. Ask for MOUSELEAVE
5473 TRACKMOUSEEVENT mTrack;
5474 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5475 mTrack.dwFlags = TME_LEAVE;
5476 mTrack.dwHoverTime = 0;
5477 mTrack.hwndTrack = mWnd;
5478 TrackMouseEvent(&mTrack);
5480 mMousePresent = true;
5482 // Suppress dispatch of pending events
5483 // when mouse moves are generated by widget
5484 // creation instead of user input.
5485 POINT mp;
5486 mp.x = GET_X_LPARAM(lParamScreen);
5487 mp.y = GET_Y_LPARAM(lParamScreen);
5488 bool userMovedMouse = false;
5489 if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
5490 userMovedMouse = true;
5493 if (userMovedMouse) {
5494 result = DispatchMouseEvent(
5495 eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
5496 MOUSE_INPUT_SOURCE(),
5497 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5498 DispatchPendingEvents();
5500 } break;
5502 case WM_NCMOUSEMOVE: {
5503 MaybeHideCursor(false);
5505 LPARAM lParamClient = lParamToClient(lParam);
5506 if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
5507 if (!sIsInMouseCapture) {
5508 TRACKMOUSEEVENT mTrack;
5509 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5510 mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
5511 mTrack.dwHoverTime = 0;
5512 mTrack.hwndTrack = mWnd;
5513 TrackMouseEvent(&mTrack);
5515 // If we noticed the mouse moving in our draggable region, forward the
5516 // message as a normal WM_MOUSEMOVE.
5517 SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
5518 } else {
5519 // We've transitioned from a draggable area to somewhere else within
5520 // the non-client area - perhaps one of the edges of the window for
5521 // resizing.
5522 mSimulatedClientArea = false;
5525 if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
5526 SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
5528 } break;
5530 case WM_LBUTTONDOWN: {
5531 MaybeHideCursor(false);
5533 result =
5534 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5535 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5536 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5537 DispatchPendingEvents();
5538 } break;
5540 case WM_LBUTTONUP: {
5541 result =
5542 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5543 MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
5544 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5545 DispatchPendingEvents();
5546 } break;
5548 case WM_NCMOUSELEAVE: {
5549 mSimulatedClientArea = false;
5551 if (EventIsInsideWindow(this)) {
5552 // If we're handling WM_NCMOUSELEAVE and the mouse is still over the
5553 // window, then by process of elimination, the mouse has moved from the
5554 // non-client to client area, so no need to fall-through to the
5555 // WM_MOUSELEAVE handler. We also need to re-register for the
5556 // WM_MOUSELEAVE message, since according to the documentation at [1],
5557 // all tracking requested via TrackMouseEvent is cleared once
5558 // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
5559 // [1]:
5560 // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
5561 TRACKMOUSEEVENT mTrack;
5562 mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
5563 mTrack.dwFlags = TME_LEAVE;
5564 mTrack.dwHoverTime = 0;
5565 mTrack.hwndTrack = mWnd;
5566 TrackMouseEvent(&mTrack);
5567 break;
5569 // We've transitioned from non-client to outside of the window, so
5570 // fall-through to the WM_MOUSELEAVE handler.
5571 [[fallthrough]];
5573 case WM_MOUSELEAVE: {
5574 if (!mMousePresent) break;
5575 if (mSimulatedClientArea) break;
5576 mMousePresent = false;
5578 // Check if the mouse is over the fullscreen transition window, if so
5579 // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
5580 // transition window disappears will not be ignored, even if the mouse
5581 // hasn't moved.
5582 if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
5583 sLastMouseMovePoint = {0};
5586 // We need to check mouse button states and put them in for
5587 // wParam.
5588 WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
5589 (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
5590 (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
5591 // Synthesize an event position because we don't get one from
5592 // WM_MOUSELEAVE.
5593 LPARAM pos = lParamToClient(::GetMessagePos());
5594 DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
5595 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5596 } break;
5598 case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER: {
5599 LPARAM pos = lParamToClient(::GetMessagePos());
5600 MOZ_ASSERT(InkCollector::sInkCollector);
5601 uint16_t pointerId = InkCollector::sInkCollector->GetPointerId();
5602 if (pointerId != 0) {
5603 WinPointerInfo pointerInfo;
5604 pointerInfo.pointerId = pointerId;
5605 DispatchMouseEvent(eMouseExitFromWidget, wParam, pos, false,
5606 MouseButton::ePrimary,
5607 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
5608 InkCollector::sInkCollector->ClearTarget();
5609 InkCollector::sInkCollector->ClearPointerId();
5611 } break;
5613 case WM_CONTEXTMENU: {
5614 // If the context menu is brought up by a touch long-press, then
5615 // the APZ code is responsible for dealing with this, so we don't
5616 // need to do anything.
5617 if (mTouchWindow &&
5618 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
5619 MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
5620 result = true;
5621 break;
5624 // If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
5625 // event in overscroll gutter, we shouldn't open context menu.
5626 if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
5627 mNeedsToPreventContextMenu) {
5628 result = true;
5629 break;
5632 // if the context menu is brought up from the keyboard, |lParam|
5633 // will be -1.
5634 LPARAM pos;
5635 bool contextMenukey = false;
5636 if (lParam == -1) {
5637 contextMenukey = true;
5638 pos = lParamToClient(GetMessagePos());
5639 } else {
5640 pos = lParamToClient(lParam);
5643 result = DispatchMouseEvent(
5644 eContextMenu, wParam, pos, contextMenukey,
5645 contextMenukey ? MouseButton::ePrimary : MouseButton::eSecondary,
5646 MOUSE_INPUT_SOURCE());
5647 if (lParam != -1 && !result && mCustomNonClient &&
5648 mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
5649 // Blank area hit, throw up the system menu.
5650 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
5651 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
5652 result = true;
5654 } break;
5656 case WM_POINTERLEAVE:
5657 case WM_POINTERDOWN:
5658 case WM_POINTERUP:
5659 case WM_POINTERUPDATE:
5660 MaybeHideCursor(false);
5661 result = OnPointerEvents(msg, wParam, lParam);
5662 if (result) {
5663 DispatchPendingEvents();
5665 break;
5667 case DM_POINTERHITTEST:
5668 if (mDmOwner) {
5669 UINT contactId = GET_POINTERID_WPARAM(wParam);
5670 POINTER_INPUT_TYPE pointerType;
5671 if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
5672 pointerType == PT_TOUCHPAD) {
5673 mDmOwner->SetContact(contactId);
5676 break;
5678 case WM_LBUTTONDBLCLK:
5679 MaybeHideCursor(false);
5680 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5681 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5682 DispatchPendingEvents();
5683 break;
5685 case WM_MBUTTONDOWN:
5686 MaybeHideCursor(false);
5687 result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5688 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5689 DispatchPendingEvents();
5690 break;
5692 case WM_MBUTTONUP:
5693 result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5694 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5695 DispatchPendingEvents();
5696 break;
5698 case WM_MBUTTONDBLCLK:
5699 MaybeHideCursor(false);
5700 result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5701 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5702 DispatchPendingEvents();
5703 break;
5705 case WM_NCMBUTTONDOWN:
5706 MaybeHideCursor(false);
5707 result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5708 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5709 DispatchPendingEvents();
5710 break;
5712 case WM_NCMBUTTONUP:
5713 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5714 MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5715 DispatchPendingEvents();
5716 break;
5718 case WM_NCMBUTTONDBLCLK:
5719 MaybeHideCursor(false);
5720 result =
5721 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5722 false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
5723 DispatchPendingEvents();
5724 break;
5726 case WM_RBUTTONDOWN:
5727 MaybeHideCursor(false);
5728 result =
5729 DispatchMouseEvent(eMouseDown, wParam, lParam, false,
5730 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5731 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5732 DispatchPendingEvents();
5733 break;
5735 case WM_RBUTTONUP:
5736 result =
5737 DispatchMouseEvent(eMouseUp, wParam, lParam, false,
5738 MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
5739 mPointerEvents.GetCachedPointerInfo(msg, wParam));
5740 DispatchPendingEvents();
5741 break;
5743 case WM_RBUTTONDBLCLK:
5744 MaybeHideCursor(false);
5745 result =
5746 DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
5747 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5748 DispatchPendingEvents();
5749 break;
5751 case WM_NCRBUTTONDOWN:
5752 MaybeHideCursor(false);
5753 result =
5754 DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
5755 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5756 DispatchPendingEvents();
5757 break;
5759 case WM_NCRBUTTONUP:
5760 result =
5761 DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5762 MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
5763 DispatchPendingEvents();
5764 break;
5766 case WM_NCRBUTTONDBLCLK:
5767 MaybeHideCursor(false);
5768 result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
5769 false, MouseButton::eSecondary,
5770 MOUSE_INPUT_SOURCE());
5771 DispatchPendingEvents();
5772 break;
5774 // Windows doesn't provide to customize the behavior of 4th nor 5th button
5775 // of mouse. If 5-button mouse works with standard mouse deriver of
5776 // Windows, users cannot disable 4th button (browser back) nor 5th button
5777 // (browser forward). We should allow to do it with our prefs since we can
5778 // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
5779 // messages are not sent to DefWindowProc.
5780 case WM_XBUTTONDOWN:
5781 case WM_XBUTTONUP:
5782 case WM_NCXBUTTONDOWN:
5783 case WM_NCXBUTTONUP:
5784 MaybeHideCursor(false);
5786 *aRetValue = TRUE;
5787 switch (GET_XBUTTON_WPARAM(wParam)) {
5788 case XBUTTON1:
5789 result = !Preferences::GetBool("mousebutton.4th.enabled", true);
5790 break;
5791 case XBUTTON2:
5792 result = !Preferences::GetBool("mousebutton.5th.enabled", true);
5793 break;
5794 default:
5795 break;
5797 break;
5799 case WM_SIZING: {
5800 if (mAspectRatio > 0) {
5801 LPRECT rect = (LPRECT)lParam;
5802 int32_t newWidth, newHeight;
5804 // The following conditions and switch statement borrow heavily from the
5805 // Chromium source code from
5806 // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
5807 if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
5808 wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
5809 newWidth = rect->right - rect->left;
5810 newHeight = newWidth / mAspectRatio;
5811 if (newHeight < mSizeConstraints.mMinSize.height) {
5812 newHeight = mSizeConstraints.mMinSize.height;
5813 newWidth = newHeight * mAspectRatio;
5814 } else if (newHeight > mSizeConstraints.mMaxSize.height) {
5815 newHeight = mSizeConstraints.mMaxSize.height;
5816 newWidth = newHeight * mAspectRatio;
5818 } else {
5819 newHeight = rect->bottom - rect->top;
5820 newWidth = newHeight * mAspectRatio;
5821 if (newWidth < mSizeConstraints.mMinSize.width) {
5822 newWidth = mSizeConstraints.mMinSize.width;
5823 newHeight = newWidth / mAspectRatio;
5824 } else if (newWidth > mSizeConstraints.mMaxSize.width) {
5825 newWidth = mSizeConstraints.mMaxSize.width;
5826 newHeight = newWidth / mAspectRatio;
5830 switch (wParam) {
5831 case WMSZ_RIGHT:
5832 case WMSZ_BOTTOM:
5833 rect->right = newWidth + rect->left;
5834 rect->bottom = rect->top + newHeight;
5835 break;
5836 case WMSZ_TOP:
5837 rect->right = newWidth + rect->left;
5838 rect->top = rect->bottom - newHeight;
5839 break;
5840 case WMSZ_LEFT:
5841 case WMSZ_TOPLEFT:
5842 rect->left = rect->right - newWidth;
5843 rect->top = rect->bottom - newHeight;
5844 break;
5845 case WMSZ_TOPRIGHT:
5846 rect->right = rect->left + newWidth;
5847 rect->top = rect->bottom - newHeight;
5848 break;
5849 case WMSZ_BOTTOMLEFT:
5850 rect->left = rect->right - newWidth;
5851 rect->bottom = rect->top + newHeight;
5852 break;
5853 case WMSZ_BOTTOMRIGHT:
5854 rect->right = rect->left + newWidth;
5855 rect->bottom = rect->top + newHeight;
5856 break;
5860 // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
5861 // resize or move event. Instead we wait for first VM_SIZING message
5862 // within a ENTERSIZEMOVE to consider this a live resize event.
5863 if (mResizeState == IN_SIZEMOVE) {
5864 mResizeState = RESIZING;
5865 NotifyLiveResizeStarted();
5867 break;
5870 case WM_MOVING:
5871 FinishLiveResizing(MOVING);
5872 if (WinUtils::IsPerMonitorDPIAware()) {
5873 // Sometimes, we appear to miss a WM_DPICHANGED message while moving
5874 // a window around. Therefore, call ChangedDPI and ResetLayout here
5875 // if it appears that the window's scaling is not what we expect.
5876 // This causes the prescontext and appshell window management code to
5877 // check the appUnitsPerDevPixel value and current widget size, and
5878 // refresh them if necessary. If nothing has changed, these calls will
5879 // return without actually triggering any extra reflow or painting.
5880 if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
5881 ChangedDPI();
5882 ResetLayout();
5883 if (mWidgetListener) {
5884 mWidgetListener->UIResolutionChanged();
5888 break;
5890 case WM_ENTERSIZEMOVE: {
5891 if (mResizeState == NOT_RESIZING) {
5892 mResizeState = IN_SIZEMOVE;
5894 break;
5897 case WM_EXITSIZEMOVE: {
5898 FinishLiveResizing(NOT_RESIZING);
5900 if (!sIsInMouseCapture) {
5901 NotifySizeMoveDone();
5904 break;
5907 case WM_DISPLAYCHANGE: {
5908 ScreenHelperWin::RefreshScreens();
5909 if (mWidgetListener) {
5910 mWidgetListener->UIResolutionChanged();
5912 break;
5915 case WM_NCLBUTTONDBLCLK:
5916 DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
5917 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5918 result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
5919 MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
5920 DispatchPendingEvents();
5921 break;
5923 case WM_NCLBUTTONDOWN: {
5924 // Dispatch a custom event when this happens in the draggable region, so
5925 // that non-popup-based panels can react to it. This doesn't send an
5926 // actual mousedown event because that would break dragging or interfere
5927 // with other mousedown handling in the caption area.
5928 if (ClientMarginHitTestPoint(GET_X_LPARAM(lParam),
5929 GET_Y_LPARAM(lParam)) == HTCAPTION) {
5930 DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
5933 if (IsWindowButton(wParam) && mCustomNonClient) {
5934 DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
5935 lParamToClient(lParam), false, MouseButton::ePrimary,
5936 MOUSE_INPUT_SOURCE(), nullptr, true);
5937 DispatchPendingEvents();
5938 result = true;
5940 break;
5943 case WM_APPCOMMAND: {
5944 MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
5945 result = HandleAppCommandMsg(nativeMsg, aRetValue);
5946 break;
5949 // The WM_ACTIVATE event is fired when a window is raised or lowered,
5950 // and the loword of wParam specifies which. But we don't want to tell
5951 // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
5952 // events are fired. Instead, set either the sJustGotActivate or
5953 // gJustGotDeactivate flags and activate/deactivate once the focus
5954 // events arrive.
5955 case WM_ACTIVATE: {
5956 int32_t fActive = LOWORD(wParam);
5957 if (!fActive) {
5958 MaybeHideCursor(false);
5961 if (mWidgetListener) {
5962 if (WA_INACTIVE == fActive) {
5963 // when minimizing a window, the deactivation and focus events will
5964 // be fired in the reverse order. Instead, just deactivate right away.
5965 // This can also happen when a modal system dialog is opened, so check
5966 // if the last window to receive the WM_KILLFOCUS message was this one
5967 // or a child of this one.
5968 if (HIWORD(wParam) ||
5969 (mLastKillFocusWindow &&
5970 (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
5971 DispatchFocusToTopLevelWindow(false);
5972 } else {
5973 sJustGotDeactivate = true;
5975 if (mIsTopWidgetWindow) {
5976 mLastKeyboardLayout = KeyboardLayout::GetInstance()->GetLayout();
5978 } else {
5979 StopFlashing();
5981 sJustGotActivate = true;
5982 WidgetMouseEvent event(true, eMouseActivate, this,
5983 WidgetMouseEvent::eReal);
5984 InitEvent(event);
5985 ModifierKeyState modifierKeyState;
5986 modifierKeyState.InitInputEvent(event);
5987 DispatchInputEvent(&event);
5988 if (sSwitchKeyboardLayout && mLastKeyboardLayout)
5989 ActivateKeyboardLayout(mLastKeyboardLayout, 0);
5991 #ifdef ACCESSIBILITY
5992 a11y::LazyInstantiator::ResetUiaDetectionCache();
5993 #endif
5996 } break;
5998 case WM_ACTIVATEAPP: {
5999 GPUProcessManager::Get()->SetAppInForeground(wParam);
6000 } break;
6002 case WM_MOUSEACTIVATE:
6003 // A popup with a parent owner should not be activated when clicked but
6004 // should still allow the mouse event to be fired, so the return value
6005 // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
6006 // just use default processing so that the window is activated.
6007 if (IsPopup() && IsOwnerForegroundWindow()) {
6008 *aRetValue = MA_NOACTIVATE;
6009 result = true;
6011 break;
6013 case WM_WINDOWPOSCHANGING: {
6014 LPWINDOWPOS info = (LPWINDOWPOS)lParam;
6015 OnWindowPosChanging(info);
6016 result = true;
6017 } break;
6019 case WM_GETMINMAXINFO: {
6020 MINMAXINFO* mmi = (MINMAXINFO*)lParam;
6021 // Set the constraints. The minimum size should also be constrained to the
6022 // default window maximum size so that it fits on screen.
6023 mmi->ptMinTrackSize.x =
6024 std::min((int32_t)mmi->ptMaxTrackSize.x,
6025 std::max((int32_t)mmi->ptMinTrackSize.x,
6026 mSizeConstraints.mMinSize.width));
6027 mmi->ptMinTrackSize.y =
6028 std::min((int32_t)mmi->ptMaxTrackSize.y,
6029 std::max((int32_t)mmi->ptMinTrackSize.y,
6030 mSizeConstraints.mMinSize.height));
6031 mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
6032 mSizeConstraints.mMaxSize.width);
6033 mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
6034 mSizeConstraints.mMaxSize.height);
6035 } break;
6037 case WM_SETFOCUS:
6038 // If previous focused window isn't ours, it must have received the
6039 // redirected message. So, we should forget it.
6040 if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
6041 RedirectedKeyDownMessageManager::Forget();
6043 if (sJustGotActivate) {
6044 DispatchFocusToTopLevelWindow(true);
6046 TaskbarConcealer::OnFocusAcquired(this);
6047 break;
6049 case WM_KILLFOCUS:
6050 if (sJustGotDeactivate) {
6051 DispatchFocusToTopLevelWindow(false);
6052 } else {
6053 mLastKillFocusWindow = mWnd;
6055 break;
6057 case WM_WINDOWPOSCHANGED: {
6058 WINDOWPOS* wp = (LPWINDOWPOS)lParam;
6059 OnWindowPosChanged(wp);
6060 TaskbarConcealer::OnWindowPosChanged(this);
6061 result = true;
6062 } break;
6064 case WM_INPUTLANGCHANGEREQUEST:
6065 *aRetValue = TRUE;
6066 result = false;
6067 break;
6069 case WM_INPUTLANGCHANGE:
6070 KeyboardLayout::GetInstance()->OnLayoutChange(
6071 reinterpret_cast<HKL>(lParam));
6072 nsBidiKeyboard::OnLayoutChange();
6073 result = false; // always pass to child window
6074 break;
6076 case WM_DESTROYCLIPBOARD: {
6077 nsIClipboard* clipboard;
6078 nsresult rv = CallGetService(kCClipboardCID, &clipboard);
6079 if (NS_SUCCEEDED(rv)) {
6080 clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
6081 NS_RELEASE(clipboard);
6083 } break;
6085 #ifdef ACCESSIBILITY
6086 case WM_GETOBJECT: {
6087 *aRetValue = 0;
6088 // Do explicit casting to make it working on 64bit systems (see bug 649236
6089 // for details).
6090 int32_t objId = static_cast<DWORD>(lParam);
6091 if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
6092 RefPtr<IAccessible> root(
6093 a11y::LazyInstantiator::GetRootAccessible(mWnd));
6094 if (root) {
6095 *aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
6096 a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
6097 result = true;
6100 } break;
6101 #endif
6103 case WM_SYSCOMMAND: {
6104 WPARAM filteredWParam = (wParam & 0xFFF0);
6105 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
6106 filteredWParam == SC_RESTORE &&
6107 GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
6108 mFrameState->EnsureFullscreenMode(false);
6109 result = true;
6112 // Handle the system menu manually when we're in full screen mode
6113 // so we can set the appropriate options.
6114 if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE &&
6115 mFrameState->GetSizeMode() == nsSizeMode_Fullscreen) {
6116 DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
6117 MOZ_SYSCONTEXT_X_POS, MOZ_SYSCONTEXT_Y_POS);
6118 result = true;
6120 } break;
6122 case WM_DWMCOMPOSITIONCHANGED:
6123 // Every window will get this message, but gfxVars only broadcasts
6124 // updates when the value actually changes
6125 if (XRE_IsParentProcess()) {
6126 BOOL dwmEnabled = FALSE;
6127 if (FAILED(::DwmIsCompositionEnabled(&dwmEnabled)) || !dwmEnabled) {
6128 gfxVars::SetDwmCompositionEnabled(false);
6129 } else {
6130 gfxVars::SetDwmCompositionEnabled(true);
6134 UpdateNonClientMargins();
6135 BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED);
6136 // TODO: Why is NotifyThemeChanged needed, what does it affect? And can we
6137 // make it more granular by tweaking the ChangeKind we pass?
6138 NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
6139 Invalidate(true, true, true);
6140 break;
6142 case WM_DPICHANGED: {
6143 LPRECT rect = (LPRECT)lParam;
6144 OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
6145 rect->bottom - rect->top);
6146 break;
6149 /* Gesture support events */
6150 case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
6151 // According to MS samples, this must be handled to enable
6152 // rotational support in multi-touch drivers.
6153 result = true;
6154 *aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
6155 break;
6157 case WM_TOUCH:
6158 result = OnTouch(wParam, lParam);
6159 if (result) {
6160 *aRetValue = 0;
6162 break;
6164 case WM_GESTURE:
6165 result = OnGesture(wParam, lParam);
6166 break;
6168 case WM_GESTURENOTIFY: {
6169 if (mWindowType != WindowType::Invisible) {
6170 // A GestureNotify event is dispatched to decide which single-finger
6171 // panning direction should be active (including none) and if pan
6172 // feedback should be displayed. Java and plugin windows can make their
6173 // own calls.
6175 GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
6176 nsPointWin touchPoint;
6177 touchPoint = gestureinfo->ptsLocation;
6178 touchPoint.ScreenToClient(mWnd);
6179 WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
6180 gestureNotifyEvent.mRefPoint =
6181 LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
6182 nsEventStatus status;
6183 DispatchEvent(&gestureNotifyEvent, status);
6184 mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
6185 if (!mTouchWindow)
6186 mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
6188 result = false; // should always bubble to DefWindowProc
6189 } break;
6191 case WM_CLEAR: {
6192 WidgetContentCommandEvent command(true, eContentCommandDelete, this);
6193 DispatchWindowEvent(command);
6194 result = true;
6195 } break;
6197 case WM_CUT: {
6198 WidgetContentCommandEvent command(true, eContentCommandCut, this);
6199 DispatchWindowEvent(command);
6200 result = true;
6201 } break;
6203 case WM_COPY: {
6204 WidgetContentCommandEvent command(true, eContentCommandCopy, this);
6205 DispatchWindowEvent(command);
6206 result = true;
6207 } break;
6209 case WM_PASTE: {
6210 WidgetContentCommandEvent command(true, eContentCommandPaste, this);
6211 DispatchWindowEvent(command);
6212 result = true;
6213 } break;
6215 case EM_UNDO: {
6216 WidgetContentCommandEvent command(true, eContentCommandUndo, this);
6217 DispatchWindowEvent(command);
6218 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6219 result = true;
6220 } break;
6222 case EM_REDO: {
6223 WidgetContentCommandEvent command(true, eContentCommandRedo, this);
6224 DispatchWindowEvent(command);
6225 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6226 result = true;
6227 } break;
6229 case EM_CANPASTE: {
6230 // Support EM_CANPASTE message only when wParam isn't specified or
6231 // is plain text format.
6232 if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
6233 WidgetContentCommandEvent command(true, eContentCommandPaste, this,
6234 true);
6235 DispatchWindowEvent(command);
6236 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6237 result = true;
6239 } break;
6241 case EM_CANUNDO: {
6242 WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
6243 DispatchWindowEvent(command);
6244 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6245 result = true;
6246 } break;
6248 case EM_CANREDO: {
6249 WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
6250 DispatchWindowEvent(command);
6251 *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
6252 result = true;
6253 } break;
6255 case MOZ_WM_SKEWFIX: {
6256 TimeStamp skewStamp;
6257 if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
6258 &skewStamp)) {
6259 TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
6260 skewStamp);
6262 } break;
6264 default: {
6265 if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
6266 SetHasTaskbarIconBeenCreated();
6268 } break;
6271 //*aRetValue = result;
6272 if (mWnd) {
6273 return result;
6274 } else {
6275 // Events which caused mWnd destruction and aren't consumed
6276 // will crash during the Windows default processing.
6277 return true;
6281 void nsWindow::FinishLiveResizing(ResizeState aNewState) {
6282 if (mResizeState == RESIZING) {
6283 NotifyLiveResizeStopped();
6285 mResizeState = aNewState;
6286 ForcePresent();
6289 /**************************************************************
6291 * SECTION: Broadcast messaging
6293 * Broadcast messages to all windows.
6295 **************************************************************/
6297 // Enumerate all child windows sending aMsg to each of them
6298 BOOL CALLBACK nsWindow::BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg) {
6299 WNDPROC winProc = (WNDPROC)::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
6300 if (winProc == &nsWindow::WindowProc) {
6301 // it's one of our windows so go ahead and send a message to it
6302 ::CallWindowProcW(winProc, aWnd, aMsg, 0, 0);
6304 return TRUE;
6307 // Enumerate all top level windows specifying that the children of each
6308 // top level window should be enumerated. Do *not* send the message to
6309 // each top level window since it is assumed that the toolkit will send
6310 // aMsg to them directly.
6311 BOOL CALLBACK nsWindow::BroadcastMsg(HWND aTopWindow, LPARAM aMsg) {
6312 // Iterate each of aTopWindows child windows sending the aMsg
6313 // to each of them.
6314 ::EnumChildWindows(aTopWindow, nsWindow::BroadcastMsgToChildren, aMsg);
6315 return TRUE;
6318 /**************************************************************
6320 * SECTION: Event processing helpers
6322 * Special processing for certain event types and
6323 * synthesized events.
6325 **************************************************************/
6327 LayoutDeviceIntMargin nsWindow::NonClientSizeMargin(
6328 const LayoutDeviceIntMargin& aNonClientOffset) const {
6329 return LayoutDeviceIntMargin(mCaptionHeight - aNonClientOffset.top,
6330 mHorResizeMargin - aNonClientOffset.right,
6331 mVertResizeMargin - aNonClientOffset.bottom,
6332 mHorResizeMargin - aNonClientOffset.left);
6335 int32_t nsWindow::ClientMarginHitTestPoint(int32_t aX, int32_t aY) {
6336 const nsSizeMode sizeMode = mFrameState->GetSizeMode();
6337 if (sizeMode == nsSizeMode_Minimized || sizeMode == nsSizeMode_Fullscreen) {
6338 return HTCLIENT;
6341 // Calculations are done in screen coords
6342 const LayoutDeviceIntRect winRect = GetScreenBounds();
6343 const LayoutDeviceIntPoint point(aX, aY);
6345 // hit return constants:
6346 // HTBORDER - non-resizable border
6347 // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
6348 // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
6349 // HTTOPLEFT, HTTOPRIGHT - resizable corner
6350 // HTCAPTION - general title bar area
6351 // HTCLIENT - area considered the client
6352 // HTCLOSE - hovering over the close button
6353 // HTMAXBUTTON - maximize button
6354 // HTMINBUTTON - minimize button
6356 int32_t testResult = HTCLIENT;
6357 const bool isResizable =
6358 sizeMode != nsSizeMode_Maximized &&
6359 (mBorderStyle &
6360 (BorderStyle::All | BorderStyle::ResizeH | BorderStyle::Default));
6362 LayoutDeviceIntMargin nonClientSizeMargin = NonClientSizeMargin();
6364 // Ensure being accessible to borders of window. Even if contents are in
6365 // this area, the area must behave as border.
6366 nonClientSizeMargin.EnsureAtLeast(
6367 LayoutDeviceIntMargin(kResizableBorderMinSize, kResizableBorderMinSize,
6368 kResizableBorderMinSize, kResizableBorderMinSize));
6370 LayoutDeviceIntRect clientRect = winRect;
6371 clientRect.Deflate(nonClientSizeMargin);
6373 const bool allowContentOverride =
6374 sizeMode == nsSizeMode_Maximized || clientRect.Contains(point);
6376 // The border size. If there is no content under mouse cursor, the border
6377 // size should be larger than the values in system settings. Otherwise,
6378 // contents under the mouse cursor should be able to override the behavior.
6379 // E.g., user must expect that Firefox button always opens the popup menu
6380 // even when the user clicks on the above edge of it.
6381 LayoutDeviceIntMargin borderSize = nonClientSizeMargin;
6382 borderSize.EnsureAtLeast(
6383 LayoutDeviceIntMargin(mVertResizeMargin, mHorResizeMargin,
6384 mVertResizeMargin, mHorResizeMargin));
6386 bool top = false;
6387 bool bottom = false;
6388 bool left = false;
6389 bool right = false;
6391 if (point.y >= winRect.y && point.y < winRect.y + borderSize.top) {
6392 top = true;
6393 } else if (point.y <= winRect.YMost() &&
6394 point.y > winRect.YMost() - borderSize.bottom) {
6395 bottom = true;
6398 // (the 2x case here doubles the resize area for corners)
6399 int multiplier = (top || bottom) ? 2 : 1;
6400 if (point.x >= winRect.x &&
6401 point.x < winRect.x + (multiplier * borderSize.left)) {
6402 left = true;
6403 } else if (point.x <= winRect.XMost() &&
6404 point.x > winRect.XMost() - (multiplier * borderSize.right)) {
6405 right = true;
6408 bool inResizeRegion = false;
6409 if (isResizable) {
6410 if (top) {
6411 testResult = HTTOP;
6412 if (left) {
6413 testResult = HTTOPLEFT;
6414 } else if (right) {
6415 testResult = HTTOPRIGHT;
6417 } else if (bottom) {
6418 testResult = HTBOTTOM;
6419 if (left) {
6420 testResult = HTBOTTOMLEFT;
6421 } else if (right) {
6422 testResult = HTBOTTOMRIGHT;
6424 } else {
6425 if (left) {
6426 testResult = HTLEFT;
6428 if (right) {
6429 testResult = HTRIGHT;
6432 inResizeRegion = (testResult != HTCLIENT);
6433 } else {
6434 if (top) {
6435 testResult = HTCAPTION;
6436 } else if (bottom || left || right) {
6437 testResult = HTBORDER;
6441 if (!sIsInMouseCapture && allowContentOverride) {
6443 POINT pt = {aX, aY};
6444 ::ScreenToClient(mWnd, &pt);
6446 if (pt.x == mCachedHitTestPoint.x.value &&
6447 pt.y == mCachedHitTestPoint.y.value &&
6448 TimeStamp::Now() - mCachedHitTestTime <
6449 TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
6450 return mCachedHitTestResult;
6453 mCachedHitTestPoint = {pt.x, pt.y};
6454 mCachedHitTestTime = TimeStamp::Now();
6457 auto pt = mCachedHitTestPoint;
6459 if (mWindowBtnRect[WindowButtonType::Minimize].Contains(pt)) {
6460 testResult = HTMINBUTTON;
6461 } else if (mWindowBtnRect[WindowButtonType::Maximize].Contains(pt)) {
6462 testResult = HTMAXBUTTON;
6463 } else if (mWindowBtnRect[WindowButtonType::Close].Contains(pt)) {
6464 testResult = HTCLOSE;
6465 } else if (!inResizeRegion) {
6466 // If we're in the resize region, avoid overriding that with either a
6467 // drag or a client result; resize takes priority over either (but not
6468 // over the window controls, which is why we check this after those).
6469 if (mDraggableRegion.Contains(pt)) {
6470 testResult = HTCAPTION;
6471 } else {
6472 testResult = HTCLIENT;
6476 mCachedHitTestResult = testResult;
6479 return testResult;
6482 bool nsWindow::IsSimulatedClientArea(int32_t screenX, int32_t screenY) {
6483 int32_t testResult = ClientMarginHitTestPoint(screenX, screenY);
6484 return testResult == HTCAPTION || IsWindowButton(testResult);
6487 bool nsWindow::IsWindowButton(int32_t hitTestResult) {
6488 return hitTestResult == HTMINBUTTON || hitTestResult == HTMAXBUTTON ||
6489 hitTestResult == HTCLOSE;
6492 TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
6493 CurrentWindowsTimeGetter getCurrentTime(mWnd);
6494 return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
6497 void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
6498 // Retain the previous mode that was notified to observers
6499 static bool sWasSleepMode = false;
6501 // Only notify observers if mode changed
6502 if (aIsSleepMode == sWasSleepMode) return;
6504 sWasSleepMode = aIsSleepMode;
6506 nsCOMPtr<nsIObserverService> observerService =
6507 mozilla::services::GetObserverService();
6508 if (observerService)
6509 observerService->NotifyObservers(nullptr,
6510 aIsSleepMode
6511 ? NS_WIDGET_SLEEP_OBSERVER_TOPIC
6512 : NS_WIDGET_WAKE_OBSERVER_TOPIC,
6513 nullptr);
6516 LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
6517 if (IMEHandler::IsComposingOn(this)) {
6518 IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
6520 // These must be checked here too as a lone WM_CHAR could be received
6521 // if a child window didn't handle it (for example Alt+Space in a content
6522 // window)
6523 ModifierKeyState modKeyState;
6524 NativeKey nativeKey(this, aMsg, modKeyState);
6525 return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
6528 LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
6529 ModifierKeyState modKeyState;
6530 NativeKey nativeKey(this, aMsg, modKeyState);
6531 bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
6532 if (aMsg.wParam == VK_F10) {
6533 // Bug 1382199: Windows default behavior will trigger the System menu bar
6534 // when F10 is released. Among other things, this causes the System menu bar
6535 // to appear when a web page overrides the contextmenu event. We *never*
6536 // want this default behavior, so eat this key (never pass it to Windows).
6537 return true;
6539 return result;
6542 LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
6543 bool* aEventDispatched) {
6544 // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
6545 // must clean up the redirected message information itself. For more
6546 // information, see above comment of
6547 // RedirectedKeyDownMessageManager::AutoFlusher class definition in
6548 // KeyboardLayout.h.
6549 RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
6551 ModifierKeyState modKeyState;
6553 NativeKey nativeKey(this, aMsg, modKeyState);
6554 LRESULT result =
6555 static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
6556 // HandleKeyDownMessage cleaned up the redirected message information
6557 // itself, so, we should do nothing.
6558 redirectedMsgFlusher.Cancel();
6560 if (aMsg.wParam == VK_MENU ||
6561 (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
6562 // We need to let Windows handle this keypress,
6563 // by returning false, if there's a native menu
6564 // bar somewhere in our containing window hierarchy.
6565 // Otherwise we handle the keypress and don't pass
6566 // it on to Windows, by returning true.
6567 bool hasNativeMenu = false;
6568 HWND hWnd = mWnd;
6569 while (hWnd) {
6570 if (::GetMenu(hWnd)) {
6571 hasNativeMenu = true;
6572 break;
6574 hWnd = ::GetParent(hWnd);
6576 result = !hasNativeMenu;
6579 return result;
6582 nsresult nsWindow::SynthesizeNativeKeyEvent(
6583 int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
6584 uint32_t aModifierFlags, const nsAString& aCharacters,
6585 const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
6586 AutoObserverNotifier notifier(aObserver, "keyevent");
6588 KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
6589 return keyboardLayout->SynthesizeNativeKeyEvent(
6590 this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
6591 aUnmodifiedCharacters);
6594 nsresult nsWindow::SynthesizeNativeMouseEvent(
6595 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
6596 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
6597 nsIObserver* aObserver) {
6598 AutoObserverNotifier notifier(aObserver, "mouseevent");
6600 INPUT input;
6601 memset(&input, 0, sizeof(input));
6603 // TODO (bug 1693240):
6604 // Now, we synthesize native mouse events asynchronously since we want to
6605 // synthesize the event on the front window at the point. However, Windows
6606 // does not provide a way to set modifier only while a mouse message is
6607 // being handled, and MOUSEEVENTF_MOVE may be coalesced by Windows. So, we
6608 // need a trick for handling it.
6610 switch (aNativeMessage) {
6611 case NativeMouseMessage::Move:
6612 input.mi.dwFlags = MOUSEEVENTF_MOVE;
6613 // Reset sLastMouseMovePoint so that even if we're moving the mouse
6614 // to the position it's already at, we still dispatch a mousemove
6615 // event, because the callers of this function expect that.
6616 sLastMouseMovePoint = {0};
6617 break;
6618 case NativeMouseMessage::ButtonDown:
6619 case NativeMouseMessage::ButtonUp: {
6620 const bool isDown = aNativeMessage == NativeMouseMessage::ButtonDown;
6621 switch (aButton) {
6622 case MouseButton::ePrimary:
6623 input.mi.dwFlags = isDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
6624 break;
6625 case MouseButton::eMiddle:
6626 input.mi.dwFlags =
6627 isDown ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
6628 break;
6629 case MouseButton::eSecondary:
6630 input.mi.dwFlags =
6631 isDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
6632 break;
6633 case MouseButton::eX1:
6634 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6635 input.mi.mouseData = XBUTTON1;
6636 break;
6637 case MouseButton::eX2:
6638 input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
6639 input.mi.mouseData = XBUTTON2;
6640 break;
6641 default:
6642 return NS_ERROR_INVALID_ARG;
6644 break;
6646 case NativeMouseMessage::EnterWindow:
6647 case NativeMouseMessage::LeaveWindow:
6648 MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Windows");
6649 return NS_ERROR_INVALID_ARG;
6652 input.type = INPUT_MOUSE;
6653 ::SetCursorPos(aPoint.x, aPoint.y);
6654 ::SendInput(1, &input, sizeof(INPUT));
6656 return NS_OK;
6659 nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
6660 LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
6661 double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
6662 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
6663 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
6664 return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
6665 this, aPoint, aNativeMessage,
6666 (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
6667 ? static_cast<int32_t>(aDeltaY)
6668 : static_cast<int32_t>(aDeltaX),
6669 aModifierFlags, aAdditionalFlags);
6672 nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
6673 LayoutDeviceIntPoint aPoint,
6674 double aDeltaX, double aDeltaY,
6675 int32_t aModifierFlags,
6676 nsIObserver* aObserver) {
6677 AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
6678 DirectManipulationOwner::SynthesizeNativeTouchpadPan(
6679 this, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
6680 return NS_OK;
6683 static void MaybeLogPosChanged(HWND aWnd, WINDOWPOS* wp) {
6684 #ifdef WINSTATE_DEBUG_OUTPUT
6685 if (aWnd == WinUtils::GetTopLevelHWND(aWnd)) {
6686 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
6687 } else {
6688 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
6690 MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
6691 if (wp->flags & SWP_FRAMECHANGED) {
6692 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
6694 if (wp->flags & SWP_SHOWWINDOW) {
6695 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
6697 if (wp->flags & SWP_NOSIZE) {
6698 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
6700 if (wp->flags & SWP_HIDEWINDOW) {
6701 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
6703 if (wp->flags & SWP_NOZORDER) {
6704 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
6706 if (wp->flags & SWP_NOACTIVATE) {
6707 MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
6709 MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
6710 #endif
6713 /**************************************************************
6715 * SECTION: OnXXX message handlers
6717 * For message handlers that need to be broken out or
6718 * implemented in specific platform code.
6720 **************************************************************/
6722 void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
6723 if (!wp) {
6724 return;
6727 MaybeLogPosChanged(mWnd, wp);
6729 // Handle window size mode changes
6730 if (wp->flags & SWP_FRAMECHANGED) {
6731 // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
6732 // windows when fullscreen games disable desktop composition. If we're
6733 // minimized and not being activated, ignore the event and let windows
6734 // handle it.
6735 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized &&
6736 (wp->flags & SWP_NOACTIVATE)) {
6737 return;
6740 mFrameState->OnFrameChanged();
6742 if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
6743 // Skip window size change events below on minimization.
6744 return;
6748 // Notify visibility change when window is activated.
6749 if (!(wp->flags & SWP_NOACTIVATE) && NeedsToTrackWindowOcclusionState()) {
6750 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
6751 this, mFrameState->GetSizeMode() != nsSizeMode_Minimized);
6754 // Handle window position changes
6755 if (!(wp->flags & SWP_NOMOVE)) {
6756 mBounds.MoveTo(wp->x, wp->y);
6757 NotifyWindowMoved(wp->x, wp->y);
6760 // Handle window size changes
6761 if (!(wp->flags & SWP_NOSIZE)) {
6762 RECT r;
6763 int32_t newWidth, newHeight;
6765 ::GetWindowRect(mWnd, &r);
6767 newWidth = r.right - r.left;
6768 newHeight = r.bottom - r.top;
6770 if (newWidth > mLastSize.width) {
6771 RECT drect;
6773 // getting wider
6774 drect.left = wp->x + mLastSize.width;
6775 drect.top = wp->y;
6776 drect.right = drect.left + (newWidth - mLastSize.width);
6777 drect.bottom = drect.top + newHeight;
6779 ::RedrawWindow(mWnd, &drect, nullptr,
6780 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6781 RDW_ERASENOW | RDW_ALLCHILDREN);
6783 if (newHeight > mLastSize.height) {
6784 RECT drect;
6786 // getting taller
6787 drect.left = wp->x;
6788 drect.top = wp->y + mLastSize.height;
6789 drect.right = drect.left + newWidth;
6790 drect.bottom = drect.top + (newHeight - mLastSize.height);
6792 ::RedrawWindow(mWnd, &drect, nullptr,
6793 RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
6794 RDW_ERASENOW | RDW_ALLCHILDREN);
6797 mBounds.SizeTo(newWidth, newHeight);
6798 mLastSize.width = newWidth;
6799 mLastSize.height = newHeight;
6801 #ifdef WINSTATE_DEBUG_OUTPUT
6802 MOZ_LOG(gWindowsLog, LogLevel::Info,
6803 ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
6804 newHeight));
6805 #endif
6807 if (mAspectRatio > 0) {
6808 // It's possible (via Windows Aero Snap) that the size of the window
6809 // has changed such that it violates the aspect ratio constraint. If so,
6810 // queue up an event to enforce the aspect ratio constraint and repaint.
6811 // When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
6812 float newAspectRatio = (float)newWidth / newHeight;
6813 if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
6814 // Hold a reference to self alive and pass it into the lambda to make
6815 // sure this nsIWidget stays alive long enough to run this function.
6816 nsCOMPtr<nsIWidget> self(this);
6817 NS_DispatchToMainThread(NS_NewRunnableFunction(
6818 "EnforceAspectRatio", [self, this, newWidth]() -> void {
6819 if (mWnd) {
6820 Resize(newWidth, newWidth / mAspectRatio, true);
6822 }));
6826 // If a maximized window is resized, recalculate the non-client margins.
6827 if (mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
6828 if (UpdateNonClientMargins(true)) {
6829 // gecko resize event already sent by UpdateNonClientMargins.
6830 return;
6835 // Notify the widget listener for size change of client area for gecko
6836 // events. This needs to be done when either window size is changed,
6837 // or window frame is changed. They may not happen together.
6838 // However, we don't invoke that for popup when window frame changes,
6839 // because popups may trigger frame change before size change via
6840 // {Set,Clear}ThemeRegion they invoke in Resize. That would make the
6841 // code below call OnResize with a wrong client size first, which can
6842 // lead to flickerling for some popups.
6843 if (!(wp->flags & SWP_NOSIZE) ||
6844 ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
6845 RECT r;
6846 LayoutDeviceIntSize clientSize;
6847 if (::GetClientRect(mWnd, &r)) {
6848 clientSize = WinUtils::ToIntRect(r).Size();
6849 } else {
6850 clientSize = mBounds.Size();
6852 // Send a gecko resize event
6853 OnResize(clientSize);
6857 void nsWindow::OnWindowPosChanging(WINDOWPOS* info) {
6858 // Update non-client margins if the frame size is changing, and let the
6859 // browser know we are changing size modes, so alternative css can kick in.
6860 // If we're going into fullscreen mode, ignore this, since it'll reset
6861 // margins to normal mode.
6862 if (info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) {
6863 mFrameState->OnFrameChanging();
6866 // Force fullscreen. This works around a bug in Windows 10 1809 where
6867 // using fullscreen when a window is "snapped" causes a spurious resize
6868 // smaller than the full screen, see bug 1482920.
6869 if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
6870 !(info->flags & SWP_NOMOVE) && !(info->flags & SWP_NOSIZE)) {
6871 nsCOMPtr<nsIScreenManager> screenmgr =
6872 do_GetService(sScreenManagerContractID);
6873 if (screenmgr) {
6874 LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
6875 DesktopIntRect deskBounds =
6876 RoundedToInt(bounds / GetDesktopToDeviceScale());
6877 nsCOMPtr<nsIScreen> screen;
6878 screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
6879 deskBounds.Width(), deskBounds.Height(),
6880 getter_AddRefs(screen));
6882 if (screen) {
6883 auto rect = screen->GetRect();
6884 info->x = rect.x;
6885 info->y = rect.y;
6886 info->cx = rect.width;
6887 info->cy = rect.height;
6892 // enforce local z-order rules
6893 if (!(info->flags & SWP_NOZORDER)) {
6894 HWND hwndAfter = info->hwndInsertAfter;
6896 nsWindow* aboveWindow = 0;
6897 nsWindowZ placement;
6899 if (hwndAfter == HWND_BOTTOM)
6900 placement = nsWindowZBottom;
6901 else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST ||
6902 hwndAfter == HWND_NOTOPMOST)
6903 placement = nsWindowZTop;
6904 else {
6905 placement = nsWindowZRelative;
6906 aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter);
6909 if (mWidgetListener) {
6910 nsCOMPtr<nsIWidget> actualBelow = nullptr;
6911 if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow,
6912 getter_AddRefs(actualBelow))) {
6913 if (placement == nsWindowZBottom)
6914 info->hwndInsertAfter = HWND_BOTTOM;
6915 else if (placement == nsWindowZTop)
6916 info->hwndInsertAfter = HWND_TOP;
6917 else {
6918 info->hwndInsertAfter =
6919 (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW);
6924 // prevent rude external programs from making hidden window visible
6925 if (mWindowType == WindowType::Invisible) info->flags &= ~SWP_SHOWWINDOW;
6927 // When waking from sleep or switching out of tablet mode, Windows 10
6928 // Version 1809 will reopen popup windows that should be hidden. Detect
6929 // this case and refuse to show the window.
6930 static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
6931 if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
6932 mWindowType == WindowType::Popup && mWidgetListener &&
6933 mWidgetListener->ShouldNotBeVisible()) {
6934 info->flags &= ~SWP_SHOWWINDOW;
6938 void nsWindow::UserActivity() {
6939 // Check if we have the idle service, if not we try to get it.
6940 if (!mIdleService) {
6941 mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
6944 // Check that we now have the idle service.
6945 if (mIdleService) {
6946 mIdleService->ResetIdleTimeOut(0);
6950 // Helper function for TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT,
6951 // uint32_t).
6952 static bool TouchDeviceNeedsPanGestureConversion(HANDLE aSource) {
6953 std::string deviceName;
6954 UINT dataSize = 0;
6955 // The first call just queries how long the name string will be.
6956 GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, nullptr, &dataSize);
6957 if (!dataSize || dataSize > 0x10000) {
6958 return false;
6960 deviceName.resize(dataSize);
6961 // The second call actually populates the string.
6962 UINT result = GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, &deviceName[0],
6963 &dataSize);
6964 if (result == UINT_MAX) {
6965 return false;
6967 // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
6968 // needs to be escaped with another one.
6969 std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
6970 // For some reason, the dataSize returned by the first call is double the
6971 // actual length of the device name (as if it were returning the size of a
6972 // wide-character string in bytes) even though we are using the narrow
6973 // version of the API. For the comparison against the expected device name
6974 // to pass, we truncate the buffer to be no longer tha the expected device
6975 // name.
6976 if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
6977 return false;
6980 RID_DEVICE_INFO deviceInfo;
6981 deviceInfo.cbSize = sizeof(deviceInfo);
6982 dataSize = sizeof(deviceInfo);
6983 result =
6984 GetRawInputDeviceInfoA(aSource, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
6985 if (result == UINT_MAX) {
6986 return false;
6988 // The device identifiers that we check for here come from bug 1355162
6989 // comment 1 (see also bug 1511901 comment 35).
6990 return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
6991 deviceInfo.hid.dwProductId == 0 &&
6992 deviceInfo.hid.dwVersionNumber == 1 &&
6993 deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
6996 // Determine if the touch device that originated |aOSEvent| needs to have
6997 // touch events representing a two-finger gesture converted to pan
6998 // gesture events.
6999 // We only do this for touch devices with a specific name and identifiers.
7000 static bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
7001 uint32_t aTouchCount) {
7002 if (!StaticPrefs::apz_windows_check_for_pan_gesture_conversion()) {
7003 return false;
7005 if (aTouchCount == 0) {
7006 return false;
7008 HANDLE source = aOSEvent[0].hSource;
7010 // Cache the result of this computation for each touch device.
7011 // Touch devices are identified by the HANDLE stored in the hSource
7012 // field of TOUCHINPUT.
7013 static std::map<HANDLE, bool> sResultCache;
7014 auto [iter, inserted] = sResultCache.emplace(source, false);
7015 if (inserted) {
7016 iter->second = TouchDeviceNeedsPanGestureConversion(source);
7018 return iter->second;
7021 Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
7022 const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
7023 // Checks if the touch device that originated the touch event is one
7024 // for which we want to convert the touch events to pang gesture events.
7025 bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
7026 aOSEvent, aTouchInput.mTouches.Length());
7027 if (!shouldConvert) {
7028 return Nothing();
7031 // Only two-finger gestures need conversion.
7032 if (aTouchInput.mTouches.Length() != 2) {
7033 return Nothing();
7036 PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
7037 if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
7038 eventType = PanGestureInput::PANGESTURE_START;
7039 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
7040 eventType = PanGestureInput::PANGESTURE_END;
7041 } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
7042 eventType = PanGestureInput::PANGESTURE_CANCELLED;
7045 // Use the midpoint of the two touches as the start point of the pan gesture.
7046 ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
7047 aTouchInput.mTouches[1].mScreenPoint) /
7049 // To compute the displacement of the pan gesture, we keep track of the
7050 // location of the previous event.
7051 ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
7052 ? ScreenPoint(0, 0)
7053 : (focusPoint - mLastPanGestureFocus);
7054 mLastPanGestureFocus = focusPoint;
7056 // We need to negate the displacement because for a touch event, moving the
7057 // fingers down results in scrolling up, but for a touchpad gesture, we want
7058 // moving the fingers down to result in scrolling down.
7059 PanGestureInput result(eventType, aTouchInput.mTimeStamp, focusPoint,
7060 -displacement, aTouchInput.modifiers);
7061 result.mSimulateMomentum = true;
7063 return Some(result);
7066 // Dispatch an event that originated as an OS touch event.
7067 // Usually, we want to dispatch it as a touch event, but some touchpads
7068 // produce touch events for two-finger scrolling, which need to be converted
7069 // to pan gesture events for correct behaviour.
7070 void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
7071 PTOUCHINPUT aOSEvent) {
7072 if (Maybe<PanGestureInput> panInput =
7073 ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
7074 DispatchPanGestureInput(*panInput);
7075 return;
7078 DispatchTouchInput(aTouchInput);
7081 bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
7082 uint32_t cInputs = LOWORD(wParam);
7083 PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
7085 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
7086 sizeof(TOUCHINPUT))) {
7087 MultiTouchInput touchInput, touchEndInput;
7089 // Walk across the touch point array processing each contact point.
7090 for (uint32_t i = 0; i < cInputs; i++) {
7091 bool addToEvent = false, addToEndEvent = false;
7093 // N.B.: According with MS documentation
7094 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
7095 // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
7096 // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
7097 // TOUCHEVENTF_UP can be combined together.
7099 if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
7100 if (touchInput.mTimeStamp.IsNull()) {
7101 // Initialize a touch event to send.
7102 touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
7103 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
7104 ModifierKeyState modifierKeyState;
7105 touchInput.modifiers = modifierKeyState.GetModifiers();
7107 // Pres shell expects this event to be a eTouchStart
7108 // if any new contact points have been added since the last event sent.
7109 if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
7110 touchInput.mType = MultiTouchInput::MULTITOUCH_START;
7112 addToEvent = true;
7114 if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
7115 // Pres shell expects removed contacts points to be delivered in a
7116 // separate eTouchEnd event containing only the contact points that were
7117 // removed.
7118 if (touchEndInput.mTimeStamp.IsNull()) {
7119 // Initialize a touch event to send.
7120 touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
7121 touchEndInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
7122 ModifierKeyState modifierKeyState;
7123 touchEndInput.modifiers = modifierKeyState.GetModifiers();
7125 addToEndEvent = true;
7127 if (!addToEvent && !addToEndEvent) {
7128 // Filter out spurious Windows events we don't understand, like palm
7129 // contact.
7130 continue;
7133 // Setup the touch point we'll append to the touch event array.
7134 nsPointWin touchPoint;
7135 touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
7136 touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
7137 touchPoint.ScreenToClient(mWnd);
7139 // Initialize the touch data.
7140 SingleTouchData touchData(
7141 pInputs[i].dwID, // aIdentifier
7142 ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
7143 // The contact area info cannot be trusted even when
7144 // TOUCHINPUTMASKF_CONTACTAREA is set when the input source is pen,
7145 // which somehow violates the API docs. (bug 1710509) Ultimately the
7146 // dwFlags check will become redundant since we want to migrate to
7147 // WM_POINTER for pens. (bug 1707075)
7148 (pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA) &&
7149 !(pInputs[i].dwFlags & TOUCHEVENTF_PEN)
7150 ? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
7151 TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
7152 : ScreenSize(1, 1), // aRadius
7153 0.0f, // aRotationAngle
7154 0.0f); // aForce
7156 // Append touch data to the appropriate event.
7157 if (addToEvent) {
7158 touchInput.mTouches.AppendElement(touchData);
7160 if (addToEndEvent) {
7161 touchEndInput.mTouches.AppendElement(touchData);
7165 // Dispatch touch start and touch move event if we have one.
7166 if (!touchInput.mTimeStamp.IsNull()) {
7167 DispatchTouchOrPanGestureInput(touchInput, pInputs);
7169 // Dispatch touch end event if we have one.
7170 if (!touchEndInput.mTimeStamp.IsNull()) {
7171 DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
7175 delete[] pInputs;
7176 CloseTouchInputHandle((HTOUCHINPUT)lParam);
7177 return true;
7180 // Gesture event processing. Handles WM_GESTURE events.
7181 bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
7182 // Treatment for pan events which translate into scroll events:
7183 if (mGesture.IsPanEvent(lParam)) {
7184 if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
7185 return false; // ignore
7187 nsEventStatus status;
7189 WidgetWheelEvent wheelEvent(true, eWheel, this);
7191 ModifierKeyState modifierKeyState;
7192 modifierKeyState.InitInputEvent(wheelEvent);
7194 wheelEvent.mButton = 0;
7195 wheelEvent.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
7196 wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
7198 bool endFeedback = true;
7200 if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
7201 DispatchEvent(&wheelEvent, status);
7204 if (mDisplayPanFeedback) {
7205 mGesture.UpdatePanFeedbackX(
7206 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
7207 endFeedback);
7208 mGesture.UpdatePanFeedbackY(
7209 mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
7210 endFeedback);
7211 mGesture.PanFeedbackFinalize(mWnd, endFeedback);
7214 CloseGestureInfoHandle((HGESTUREINFO)lParam);
7216 return true;
7219 // Other gestures translate into simple gesture events:
7220 WidgetSimpleGestureEvent event(true, eVoidEvent, this);
7221 if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
7222 return false; // fall through to DefWndProc
7225 // Polish up and send off the new event
7226 ModifierKeyState modifierKeyState;
7227 modifierKeyState.InitInputEvent(event);
7228 event.mButton = 0;
7229 event.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
7230 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
7232 nsEventStatus status;
7233 DispatchEvent(&event, status);
7234 if (status == nsEventStatus_eIgnore) {
7235 return false; // Ignored, fall through
7238 // Only close this if we process and return true.
7239 CloseGestureInfoHandle((HGESTUREINFO)lParam);
7241 return true; // Handled
7244 // WM_DESTROY event handler
7245 void nsWindow::OnDestroy() {
7246 mOnDestroyCalled = true;
7248 // If this is a toplevel window, notify the taskbar concealer to clean up any
7249 // relevant state.
7250 if (!mParent) {
7251 TaskbarConcealer::OnWindowDestroyed(mWnd);
7254 // Make sure we don't get destroyed in the process of tearing down.
7255 nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
7257 // Dispatch the destroy notification.
7258 if (!mInDtor) NotifyWindowDestroyed();
7260 // Prevent the widget from sending additional events.
7261 mWidgetListener = nullptr;
7262 mAttachedWidgetListener = nullptr;
7264 DestroyDirectManipulation();
7266 if (mWnd == mLastKillFocusWindow) {
7267 mLastKillFocusWindow = nullptr;
7269 // Unregister notifications from terminal services
7270 ::WTSUnRegisterSessionNotification(mWnd);
7272 // We will stop receiving native events after dissociating from our native
7273 // window. We will also disappear from the output of WinUtils::GetNSWindowPtr
7274 // for that window.
7275 DissociateFromNativeWindow();
7277 // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
7278 // can be cleared. (It's used in tracking windows for mouse events.)
7279 if (sCurrentWindow == this) sCurrentWindow = nullptr;
7281 // Disconnects us from our parent, will call our GetParent().
7282 nsBaseWidget::Destroy();
7284 // Release references to children, device context, toolkit, and app shell.
7285 nsBaseWidget::OnDestroy();
7287 // Clear our native parent handle.
7288 // XXX Windows will take care of this in the proper order, and
7289 // SetParent(nullptr)'s remove child on the parent already took place in
7290 // nsBaseWidget's Destroy call above.
7291 // SetParent(nullptr);
7292 mParent = nullptr;
7294 // We have to destroy the native drag target before we null out our window
7295 // pointer.
7296 EnableDragDrop(false);
7298 // If we're going away and for some reason we're still the rollup widget,
7299 // rollup and turn off capture.
7300 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
7301 nsCOMPtr<nsIWidget> rollupWidget;
7302 if (rollupListener) {
7303 rollupWidget = rollupListener->GetRollupWidget();
7305 if (this == rollupWidget) {
7306 rollupListener->Rollup({});
7307 CaptureRollupEvents(false);
7310 IMEHandler::OnDestroyWindow(this);
7312 // Free GDI window class objects
7313 if (mBrush) {
7314 VERIFY(::DeleteObject(mBrush));
7315 mBrush = nullptr;
7318 // Destroy any custom cursor resources.
7319 if (mCursor.IsCustom()) {
7320 SetCursor(Cursor{eCursor_standard});
7323 if (mCompositorWidgetDelegate) {
7324 mCompositorWidgetDelegate->OnDestroyWindow();
7326 mBasicLayersSurface = nullptr;
7328 // Finalize panning feedback to possibly restore window displacement
7329 mGesture.PanFeedbackFinalize(mWnd, true);
7331 // Clear the main HWND.
7332 mWnd = nullptr;
7335 // Send a resize message to the listener
7336 bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
7337 if (mCompositorWidgetDelegate &&
7338 !mCompositorWidgetDelegate->OnWindowResize(aSize)) {
7339 return false;
7342 bool result = false;
7343 if (mWidgetListener) {
7344 result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
7347 // If there is an attached view, inform it as well as the normal widget
7348 // listener.
7349 if (mAttachedWidgetListener) {
7350 return mAttachedWidgetListener->WindowResized(this, aSize.width,
7351 aSize.height);
7354 return result;
7357 void nsWindow::OnSizeModeChange() {
7358 const nsSizeMode mode = mFrameState->GetSizeMode();
7360 MOZ_LOG(gWindowsLog, LogLevel::Info,
7361 ("nsWindow::OnSizeModeChange() sizeMode %d", mode));
7363 if (NeedsToTrackWindowOcclusionState()) {
7364 WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
7365 this, mode != nsSizeMode_Minimized);
7367 wr::DebugFlags flags{0};
7368 flags.bits = gfx::gfxVars::WebRenderDebugFlags();
7369 bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
7370 if (debugEnabled && mCompositorWidgetDelegate) {
7371 mCompositorWidgetDelegate->NotifyVisibilityUpdated(mode,
7372 mIsFullyOccluded);
7376 if (mCompositorWidgetDelegate) {
7377 mCompositorWidgetDelegate->OnWindowModeChange(mode);
7380 if (mWidgetListener) {
7381 mWidgetListener->SizeModeChanged(mode);
7385 bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
7387 bool nsWindow::IsPopup() { return mWindowType == WindowType::Popup; }
7389 bool nsWindow::ShouldUseOffMainThreadCompositing() {
7390 if (mWindowType == WindowType::Popup && mPopupType == PopupType::Tooltip) {
7391 return false;
7394 // Content rendering of popup is always done by child window.
7395 // See nsDocumentViewer::ShouldAttachToTopLevel().
7396 if (mWindowType == WindowType::Popup && !mIsChildWindow) {
7397 MOZ_ASSERT(!mParent);
7398 return false;
7401 return nsBaseWidget::ShouldUseOffMainThreadCompositing();
7404 void nsWindow::WindowUsesOMTC() {
7405 ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
7406 if (!style) {
7407 NS_WARNING("Could not get window class style");
7408 return;
7410 style |= CS_HREDRAW | CS_VREDRAW;
7411 DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
7412 NS_WARNING_ASSERTION(result, "Could not reset window class style");
7415 // See bug 603793
7416 bool nsWindow::HasBogusPopupsDropShadowOnMultiMonitor() {
7417 static const bool sHasBogusPopupsDropShadowOnMultiMonitor = [] {
7418 // Since any change in the preferences requires a restart, this can be
7419 // done just once.
7420 // Check for Direct2D first.
7421 if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) {
7422 return true;
7424 // Otherwise check if Direct3D 9 may be used.
7425 if (gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) &&
7426 !gfxConfig::IsEnabled(gfx::Feature::OPENGL_COMPOSITING)) {
7427 nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
7428 if (gfxInfo) {
7429 int32_t status;
7430 nsCString discardFailureId;
7431 if (NS_SUCCEEDED(
7432 gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
7433 discardFailureId, &status))) {
7434 if (status == nsIGfxInfo::FEATURE_STATUS_OK ||
7435 gfxConfig::IsForcedOnByUser(gfx::Feature::HW_COMPOSITING)) {
7436 return true;
7441 return false;
7442 }();
7443 return sHasBogusPopupsDropShadowOnMultiMonitor;
7446 void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
7447 int32_t height) {
7448 // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
7449 // they remain tied to their original parent's resolution.
7450 if (mWindowType == WindowType::Popup) {
7451 return;
7453 if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
7454 return;
7456 mDefaultScale = -1.0; // force recomputation of scale factor
7458 if (mResizeState != RESIZING &&
7459 mFrameState->GetSizeMode() == nsSizeMode_Normal) {
7460 // Limit the position (if not in the middle of a drag-move) & size,
7461 // if it would overflow the destination screen
7462 nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID);
7463 if (sm) {
7464 nsCOMPtr<nsIScreen> screen;
7465 sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
7466 if (screen) {
7467 int32_t availLeft, availTop, availWidth, availHeight;
7468 screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
7469 if (mResizeState != MOVING) {
7470 x = std::max(x, availLeft);
7471 y = std::max(y, availTop);
7473 width = std::min(width, availWidth);
7474 height = std::min(height, availHeight);
7478 Resize(x, y, width, height, true);
7480 UpdateNonClientMargins();
7481 ChangedDPI();
7482 ResetLayout();
7485 // Callback to generate OnCloakChanged pseudo-events.
7486 /* static */
7487 void nsWindow::OnCloakEvent(HWND aWnd, bool aCloaked) {
7488 MOZ_ASSERT(NS_IsMainThread());
7489 MOZ_ASSERT(IsWin8OrLater());
7491 const char* const kEventName = aCloaked ? "CLOAKED" : "UNCLOAKED";
7492 nsWindow* pWin = WinUtils::GetNSWindowPtr(aWnd);
7493 if (!pWin) {
7494 MOZ_LOG(
7495 sCloakingLog, LogLevel::Debug,
7496 ("Received %s event for HWND %p (not an nsWindow)", kEventName, aWnd));
7497 return;
7500 const char* const kWasCloakedStr = pWin->mIsCloaked ? "cloaked" : "uncloaked";
7501 if (mozilla::IsCloaked(aWnd) == pWin->mIsCloaked) {
7502 MOZ_LOG(sCloakingLog, LogLevel::Debug,
7503 ("Received redundant %s event for %s HWND %p; discarding",
7504 kEventName, kWasCloakedStr, aWnd));
7505 return;
7508 MOZ_LOG(
7509 sCloakingLog, LogLevel::Info,
7510 ("Received %s event for %s HWND %p", kEventName, kWasCloakedStr, aWnd));
7512 // Cloaking events like the one we've just received are sent asynchronously.
7513 // Rather than process them one-by-one, we jump the gun a bit and perform
7514 // updates on all newly cloaked/uncloaked nsWindows at once. This also lets us
7515 // batch operations that consider more than one window's state.
7516 struct Item {
7517 nsWindow* win;
7518 bool nowCloaked;
7520 nsTArray<Item> changedWindows;
7522 mozilla::EnumerateThreadWindows([&](HWND hwnd) {
7523 nsWindow* pWin = WinUtils::GetNSWindowPtr(hwnd);
7524 if (!pWin) {
7525 return;
7528 const bool isCloaked = mozilla::IsCloaked(hwnd);
7529 if (isCloaked != pWin->mIsCloaked) {
7530 changedWindows.AppendElement(Item{pWin, isCloaked});
7534 if (changedWindows.IsEmpty()) {
7535 return;
7538 for (const Item& item : changedWindows) {
7539 item.win->OnCloakChanged(item.nowCloaked);
7542 nsWindow::TaskbarConcealer::OnCloakChanged();
7545 void nsWindow::OnCloakChanged(bool aCloaked) {
7546 MOZ_LOG(sCloakingLog, LogLevel::Info,
7547 ("Calling OnCloakChanged(): HWND %p, aCloaked %s", mWnd,
7548 aCloaked ? "true" : "false"));
7549 mIsCloaked = aCloaked;
7552 /**************************************************************
7553 **************************************************************
7555 ** BLOCK: IME management and accessibility
7557 ** Handles managing IME input and accessibility.
7559 **************************************************************
7560 **************************************************************/
7562 void nsWindow::SetInputContext(const InputContext& aContext,
7563 const InputContextAction& aAction) {
7564 InputContext newInputContext = aContext;
7565 IMEHandler::SetInputContext(this, newInputContext, aAction);
7566 mInputContext = newInputContext;
7569 InputContext nsWindow::GetInputContext() {
7570 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7571 if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
7572 mInputContext.mIMEState.mOpen = IMEState::OPEN;
7573 } else {
7574 mInputContext.mIMEState.mOpen = IMEState::CLOSED;
7576 return mInputContext;
7579 TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
7580 return IMEHandler::GetNativeTextEventDispatcherListener();
7583 #ifdef ACCESSIBILITY
7584 # ifdef DEBUG
7585 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
7586 if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
7587 printf( \
7588 "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
7589 "%p,\n", \
7590 aHwnd, ::GetParent(aHwnd), aWnd); \
7591 printf(" acc: %p", aAcc); \
7592 if (aAcc) { \
7593 nsAutoString name; \
7594 aAcc->Name(name); \
7595 printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
7597 printf("\n }\n"); \
7600 # else
7601 # define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
7602 # endif
7604 a11y::LocalAccessible* nsWindow::GetAccessible() {
7605 // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
7606 if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
7607 return nullptr;
7609 if (mInDtor || mOnDestroyCalled || mWindowType == WindowType::Invisible) {
7610 return nullptr;
7613 // In case of popup window return a popup accessible.
7614 nsView* view = nsView::GetViewFor(this);
7615 if (view) {
7616 nsIFrame* frame = view->GetFrame();
7617 if (frame && nsLayoutUtils::IsPopup(frame)) {
7618 nsAccessibilityService* accService = GetOrCreateAccService();
7619 if (accService) {
7620 a11y::DocAccessible* docAcc =
7621 GetAccService()->GetDocAccessible(frame->PresShell());
7622 if (docAcc) {
7623 NS_LOG_WMGETOBJECT(
7624 this, mWnd,
7625 docAcc->GetAccessibleOrDescendant(frame->GetContent()));
7626 return docAcc->GetAccessibleOrDescendant(frame->GetContent());
7632 // otherwise root document accessible.
7633 NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
7634 return GetRootAccessible();
7636 #endif
7638 /**************************************************************
7639 **************************************************************
7641 ** BLOCK: Transparency
7643 ** Window transparency helpers.
7645 **************************************************************
7646 **************************************************************/
7648 void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) {
7649 if (aMode == mTransparencyMode) return;
7651 // stop on dialogs and popups!
7652 HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
7653 nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd);
7655 if (!parent) {
7656 NS_WARNING("Trying to use transparent chrome in an embedded context");
7657 return;
7660 if (parent != this) {
7661 NS_WARNING(
7662 "Setting SetWindowTranslucencyInner on a parent this is not us!");
7665 if (aMode == TransparencyMode::Transparent) {
7666 // If we're switching to the use of a transparent window, hide the chrome
7667 // on our parent.
7668 HideWindowChrome(true);
7669 } else if (mHideChrome &&
7670 mTransparencyMode == TransparencyMode::Transparent) {
7671 // if we're switching out of transparent, re-enable our parent's chrome.
7672 HideWindowChrome(false);
7675 LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE),
7676 exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
7678 if (parent->mIsVisible) {
7679 style |= WS_VISIBLE;
7680 if (parent->mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
7681 style |= WS_MAXIMIZE;
7682 } else if (parent->mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
7683 style |= WS_MINIMIZE;
7687 if (aMode == TransparencyMode::Transparent)
7688 exStyle |= WS_EX_LAYERED;
7689 else
7690 exStyle &= ~WS_EX_LAYERED;
7692 VERIFY_WINDOW_STYLE(style);
7693 ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
7694 ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
7696 mTransparencyMode = aMode;
7698 if (mCompositorWidgetDelegate) {
7699 mCompositorWidgetDelegate->UpdateTransparency(aMode);
7703 /**************************************************************
7704 **************************************************************
7706 ** BLOCK: Popup rollup hooks
7708 ** Deals with CaptureRollup on popup windows.
7710 **************************************************************
7711 **************************************************************/
7713 // Schedules a timer for a window, so we can rollup after processing the hook
7714 // event
7715 void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
7716 // In some cases multiple hooks may be scheduled
7717 // so ignore any other requests once one timer is scheduled
7718 if (sHookTimerId == 0) {
7719 // Remember the window handle and the message ID to be used later
7720 sRollupMsgId = aMsgId;
7721 sRollupMsgWnd = aWnd;
7722 // Schedule native timer for doing the rollup after
7723 // this event is done being processed
7724 sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
7725 NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
7729 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7730 int gLastMsgCode = 0;
7731 extern MSGFEventMsgInfo gMSGFEvents[];
7732 #endif
7734 // Process Menu messages, rollup when popup is clicked.
7735 LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
7736 LPARAM lParam) {
7737 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7738 if (sProcessHook) {
7739 MSG* pMsg = (MSG*)lParam;
7741 int inx = 0;
7742 while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
7743 inx++;
7745 if (code != gLastMsgCode) {
7746 if (gMSGFEvents[inx].mId == code) {
7747 # ifdef DEBUG
7748 MOZ_LOG(gWindowsLog, LogLevel::Info,
7749 ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
7750 gMSGFEvents[inx].mStr, pMsg->hwnd));
7751 # endif
7752 } else {
7753 # ifdef DEBUG
7754 MOZ_LOG(gWindowsLog, LogLevel::Info,
7755 ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
7756 gMSGFEvents[inx].mId, pMsg->hwnd));
7757 # endif
7759 gLastMsgCode = code;
7761 PrintEvent(pMsg->message, FALSE, FALSE);
7763 #endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7765 if (sProcessHook && code == MSGF_MENU) {
7766 MSG* pMsg = (MSG*)lParam;
7767 ScheduleHookTimer(pMsg->hwnd, pMsg->message);
7770 return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
7773 // Process all mouse messages. Roll up when a click is in a native window
7774 // that doesn't have an nsIWidget.
7775 LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
7776 LPARAM lParam) {
7777 if (sProcessHook) {
7778 switch (WinUtils::GetNativeMessage(wParam)) {
7779 case WM_LBUTTONDOWN:
7780 case WM_RBUTTONDOWN:
7781 case WM_MBUTTONDOWN:
7782 case WM_MOUSEWHEEL:
7783 case WM_MOUSEHWHEEL: {
7784 MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
7785 nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
7786 if (!mozWin) {
7787 ScheduleHookTimer(ms->hwnd, (UINT)wParam);
7789 break;
7793 return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
7796 // Process all messages. Roll up when the window is moving, or
7797 // is resizing or when maximized or mininized.
7798 LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
7799 LPARAM lParam) {
7800 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7801 if (sProcessHook) {
7802 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7803 PrintEvent(cwpt->message, FALSE, FALSE);
7805 #endif
7807 if (sProcessHook) {
7808 CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
7809 if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
7810 cwpt->message == WM_GETMINMAXINFO) {
7811 ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
7815 return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
7818 // Register the special "hooks" for dropdown processing.
7819 void nsWindow::RegisterSpecialDropdownHooks() {
7820 NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
7821 NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
7823 DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
7825 // Install msg hook for moving the window and resizing
7826 if (!sMsgFilterHook) {
7827 DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
7828 sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
7829 nullptr, GetCurrentThreadId());
7830 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7831 if (!sMsgFilterHook) {
7832 MOZ_LOG(gWindowsLog, LogLevel::Info,
7833 ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
7835 #endif
7838 // Install msg hook for menus
7839 if (!sCallProcHook) {
7840 DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
7841 sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
7842 GetCurrentThreadId());
7843 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7844 if (!sCallProcHook) {
7845 MOZ_LOG(
7846 gWindowsLog, LogLevel::Info,
7847 ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
7849 #endif
7852 // Install msg hook for the mouse
7853 if (!sCallMouseHook) {
7854 DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
7855 sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
7856 GetCurrentThreadId());
7857 #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
7858 if (!sCallMouseHook) {
7859 MOZ_LOG(gWindowsLog, LogLevel::Info,
7860 ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
7862 #endif
7866 // Unhook special message hooks for dropdowns.
7867 void nsWindow::UnregisterSpecialDropdownHooks() {
7868 DISPLAY_NMM_PRT(
7869 "***************** De-installing Msg Hooks ***************\n");
7871 if (sCallProcHook) {
7872 DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
7873 if (!::UnhookWindowsHookEx(sCallProcHook)) {
7874 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
7876 sCallProcHook = nullptr;
7879 if (sMsgFilterHook) {
7880 DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
7881 if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
7882 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
7884 sMsgFilterHook = nullptr;
7887 if (sCallMouseHook) {
7888 DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
7889 if (!::UnhookWindowsHookEx(sCallMouseHook)) {
7890 DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
7892 sCallMouseHook = nullptr;
7896 // This timer is designed to only fire one time at most each time a "hook"
7897 // function is used to rollup the dropdown. In some cases, the timer may be
7898 // scheduled from the hook, but that hook event or a subsequent event may roll
7899 // up the dropdown before this timer function is executed.
7901 // For example, if an MFC control takes focus, the combobox will lose focus and
7902 // rollup before this function fires.
7903 VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
7904 DWORD dwTime) {
7905 if (sHookTimerId != 0) {
7906 // if the window is nullptr then we need to use the ID to kill the timer
7907 DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
7908 NS_ASSERTION(status, "Hook Timer was not killed.");
7909 sHookTimerId = 0;
7912 if (sRollupMsgId != 0) {
7913 // Note: DealWithPopups does the check to make sure that the rollup widget
7914 // is set.
7915 LRESULT popupHandlingResult;
7916 nsAutoRollup autoRollup;
7917 DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
7918 sRollupMsgId = 0;
7919 sRollupMsgWnd = nullptr;
7923 static bool IsDifferentThreadWindow(HWND aWnd) {
7924 return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
7927 // static
7928 bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
7929 Maybe<POINT> aEventPoint) {
7930 RECT r;
7931 ::GetWindowRect(aWindow->mWnd, &r);
7932 POINT mp;
7933 if (aEventPoint) {
7934 mp = *aEventPoint;
7935 } else {
7936 DWORD pos = ::GetMessagePos();
7937 mp.x = GET_X_LPARAM(pos);
7938 mp.y = GET_Y_LPARAM(pos);
7941 auto margin = aWindow->mInputRegion.mMargin;
7942 if (margin > 0) {
7943 r.top += margin;
7944 r.bottom -= margin;
7945 r.left += margin;
7946 r.right -= margin;
7949 // was the event inside this window?
7950 return static_cast<bool>(::PtInRect(&r, mp));
7953 // static
7954 bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
7955 uint32_t* aPopupsToRollup,
7956 Maybe<POINT> aEventPoint) {
7957 // If we're dealing with menus, we probably have submenus and we don't want
7958 // to rollup some of them if the click is in a parent menu of the current
7959 // submenu.
7960 *aPopupsToRollup = UINT32_MAX;
7961 AutoTArray<nsIWidget*, 5> widgetChain;
7962 uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
7963 for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
7964 nsIWidget* widget = widgetChain[i];
7965 if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
7966 // Don't roll up if the mouse event occurred within a menu of the
7967 // same type. If the mouse event occurred in a menu higher than that,
7968 // roll up, but pass the number of popups to Rollup so that only those
7969 // of the same type close up.
7970 if (i < sameTypeCount) {
7971 return false;
7974 *aPopupsToRollup = sameTypeCount;
7975 break;
7978 return true;
7981 // static
7982 bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) {
7983 // While popup is open, popup window might be activated by other application.
7984 // At this time, we need to take back focus to the previous window but it
7985 // causes flickering its nonclient area because WM_NCACTIVATE comes before
7986 // WM_ACTIVATE and we cannot know which window will take focus at receiving
7987 // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
7989 // If non-popup window receives WM_NCACTIVATE at deactivating, default
7990 // wndproc shouldn't handle it as deactivating. Instead, at receiving
7991 // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
7992 // This returns true if the window needs to handle WM_NCACTIVATE later.
7994 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
7995 return window && !window->IsPopup();
7998 static bool IsTouchSupportEnabled(HWND aWnd) {
7999 nsWindow* topWindow =
8000 WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
8001 return topWindow ? topWindow->IsTouchWindow() : false;
8004 static Maybe<POINT> GetSingleTouch(WPARAM wParam, LPARAM lParam) {
8005 Maybe<POINT> ret;
8006 uint32_t cInputs = LOWORD(wParam);
8007 if (cInputs != 1) {
8008 return ret;
8010 TOUCHINPUT input;
8011 if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, &input,
8012 sizeof(TOUCHINPUT))) {
8013 ret.emplace();
8014 ret->x = TOUCH_COORD_TO_PIXEL(input.x);
8015 ret->y = TOUCH_COORD_TO_PIXEL(input.y);
8017 // Note that we don't call CloseTouchInputHandle here because we need
8018 // to read the touch input info again in OnTouch later.
8019 return ret;
8022 // static
8023 bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
8024 LPARAM aLParam, LRESULT* aResult) {
8025 NS_ASSERTION(aResult, "Bad outResult");
8027 // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
8028 *aResult = MA_NOACTIVATE;
8030 if (!::IsWindowVisible(aWnd)) {
8031 return false;
8034 if (MOZ_UNLIKELY(aMessage == WM_KILLFOCUS)) {
8035 // NOTE: We deal with this here rather than on the switch below because we
8036 // want to do this even if there are no menus to rollup (tooltips don't set
8037 // the rollup listener etc).
8038 if (RefPtr pm = nsXULPopupManager::GetInstance()) {
8039 pm->RollupTooltips();
8043 nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
8044 NS_ENSURE_TRUE(rollupListener, false);
8046 nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
8047 if (!popup) {
8048 return false;
8051 // Don't rollup a popup if it has an open file picker
8052 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
8053 if (!window || window->mPickerDisplayCount) {
8054 return false;
8057 static bool sSendingNCACTIVATE = false;
8058 static bool sPendingNCACTIVATE = false;
8059 uint32_t popupsToRollup = UINT32_MAX;
8061 bool consumeRollupEvent = false;
8062 Maybe<POINT> touchPoint; // In screen coords.
8064 // If we rollup with animations but get occluded right away, we might not
8065 // advance the refresh driver enough for the animation to finish.
8066 auto allowAnimations = nsIRollupListener::AllowAnimations::Yes;
8067 nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
8068 UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
8069 switch (nativeMessage) {
8070 case WM_TOUCH:
8071 if (!IsTouchSupportEnabled(aWnd)) {
8072 // If APZ is disabled, don't allow touch inputs to dismiss popups. The
8073 // compatibility mouse events will do it instead.
8074 return false;
8076 touchPoint = GetSingleTouch(aWParam, aLParam);
8077 if (!touchPoint) {
8078 return false;
8080 [[fallthrough]];
8081 case WM_LBUTTONDOWN:
8082 case WM_RBUTTONDOWN:
8083 case WM_MBUTTONDOWN:
8084 case WM_NCLBUTTONDOWN:
8085 case WM_NCRBUTTONDOWN:
8086 case WM_NCMBUTTONDOWN:
8087 if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
8088 MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
8089 // If any of these mouse events are really compatibility events that
8090 // Windows is sending for touch inputs, then don't allow them to dismiss
8091 // popups when APZ is enabled (instead we do the dismissing as part of
8092 // WM_TOUCH handling which is more correct).
8093 // If we don't do this, then when the user lifts their finger after a
8094 // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
8095 // us will dismiss the contextmenu popup that we displayed as part of
8096 // handling the long-tap-up.
8097 return false;
8099 if (!EventIsInsideWindow(popupWindow, touchPoint) &&
8100 GetPopupsToRollup(rollupListener, &popupsToRollup, touchPoint)) {
8101 break;
8103 return false;
8104 case WM_POINTERDOWN: {
8105 WinPointerEvents pointerEvents;
8106 if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) {
8107 return false;
8109 POINT pt;
8110 pt.x = GET_X_LPARAM(aLParam);
8111 pt.y = GET_Y_LPARAM(aLParam);
8112 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
8113 return false;
8115 if (EventIsInsideWindow(popupWindow, Some(pt))) {
8116 // Don't roll up if the event is inside the popup window.
8117 return false;
8119 } break;
8120 case MOZ_WM_DMANIP: {
8121 POINT pt;
8122 ::GetCursorPos(&pt);
8123 if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
8124 return false;
8126 if (EventIsInsideWindow(popupWindow, Some(pt))) {
8127 // Don't roll up if the event is inside the popup window
8128 return false;
8130 } break;
8131 case WM_MOUSEWHEEL:
8132 case WM_MOUSEHWHEEL:
8133 // We need to check if the popup thinks that it should cause closing
8134 // itself when mouse wheel events are fired outside the rollup widget.
8135 if (!EventIsInsideWindow(popupWindow)) {
8136 // Check if we should consume this event even if we don't roll-up:
8137 consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
8138 *aResult = MA_ACTIVATE;
8139 if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
8140 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
8141 break;
8144 return consumeRollupEvent;
8146 case WM_ACTIVATEAPP:
8147 allowAnimations = nsIRollupListener::AllowAnimations::No;
8148 break;
8150 case WM_ACTIVATE:
8151 // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
8152 // because we cannot distinguish it's caused by mouse or not.
8153 if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
8154 if (window && window->IsPopup()) {
8155 // Cancel notifying widget listeners of deactivating the previous
8156 // active window (see WM_KILLFOCUS case in ProcessMessage()).
8157 sJustGotDeactivate = false;
8158 // Reactivate the window later.
8159 ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
8160 return true;
8162 // Don't rollup the popup when focus moves back to the parent window
8163 // from a popup because such case is caused by strange mouse drivers.
8164 nsWindow* prevWindow =
8165 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
8166 if (prevWindow && prevWindow->IsPopup()) {
8167 // Consume this message here since previous window must not have
8168 // been inactivated since we've already stopped accepting the
8169 // inactivation below.
8170 return true;
8172 } else if (LOWORD(aWParam) == WA_INACTIVE) {
8173 nsWindow* activeWindow =
8174 WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
8175 if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) {
8176 // If focus moves to non-popup widget or focusable popup, the window
8177 // needs to update its nonclient area.
8178 if (!activeWindow || !activeWindow->IsPopup()) {
8179 sSendingNCACTIVATE = true;
8180 ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
8181 sSendingNCACTIVATE = false;
8183 sPendingNCACTIVATE = false;
8185 // If focus moves from/to popup, we don't need to rollup the popup
8186 // because such case is caused by strange mouse drivers. And in
8187 // such case, we should consume the message here since we need to
8188 // hide this odd focus move from our content. (If we didn't consume
8189 // the message here, ProcessMessage() will notify widget listener of
8190 // inactivation and that causes unnecessary reflow for supporting
8191 // -moz-window-inactive pseudo class.
8192 if (activeWindow) {
8193 if (activeWindow->IsPopup()) {
8194 return true;
8196 nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
8197 if (deactiveWindow && deactiveWindow->IsPopup()) {
8198 return true;
8201 } else if (LOWORD(aWParam) == WA_CLICKACTIVE) {
8202 // If the WM_ACTIVATE message is caused by a click in a popup,
8203 // we should not rollup any popups.
8204 nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
8205 if ((window && window->IsPopup()) ||
8206 !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
8207 return false;
8210 allowAnimations = nsIRollupListener::AllowAnimations::No;
8211 break;
8213 case MOZ_WM_REACTIVATE:
8214 // The previous active window should take back focus.
8215 if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
8216 // FYI: Even without this API call, you see expected result (e.g., the
8217 // owner window of the popup keeps active without flickering
8218 // the non-client area). And also this causes initializing
8219 // TSF and it causes using CPU time a lot. However, even if we
8220 // consume WM_ACTIVE messages, native focus change has already
8221 // been occurred. I.e., a popup window is active now. Therefore,
8222 // you'll see some odd behavior if we don't reactivate the owner
8223 // window here. For example, if you do:
8224 // 1. Turn wheel on a bookmark panel.
8225 // 2. Turn wheel on another window.
8226 // then, you'll see that the another window becomes active but the
8227 // owner window of the bookmark panel looks still active and the
8228 // bookmark panel keeps open. The reason is that the first wheel
8229 // operation gives focus to the bookmark panel. Therefore, when
8230 // the next operation gives focus to the another window, previous
8231 // focus window is the bookmark panel (i.e., a popup window).
8232 // So, in this case, our hack around here prevents to inactivate
8233 // the owner window and roll up the bookmark panel.
8234 ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
8236 return true;
8238 case WM_NCACTIVATE:
8239 if (!aWParam && !sSendingNCACTIVATE &&
8240 NeedsToHandleNCActivateDelayed(aWnd)) {
8241 // Don't just consume WM_NCACTIVATE. It doesn't handle only the
8242 // nonclient area state change.
8243 ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
8244 // Accept the deactivating because it's necessary to receive following
8245 // WM_ACTIVATE.
8246 *aResult = TRUE;
8247 sPendingNCACTIVATE = true;
8248 return true;
8250 return false;
8252 case WM_MOUSEACTIVATE:
8253 if (!EventIsInsideWindow(popupWindow) &&
8254 GetPopupsToRollup(rollupListener, &popupsToRollup)) {
8255 // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
8256 // of TweakUI is enabled. Then, check if the popup should be rolled up
8257 // with rollup listener. If not, just consume the message.
8258 if (HIWORD(aLParam) == WM_MOUSEMOVE &&
8259 !rollupListener->ShouldRollupOnMouseActivate()) {
8260 return true;
8262 // Otherwise, it should be handled by wndproc.
8263 return false;
8266 // Prevent the click inside the popup from causing a change in window
8267 // activation. Since the popup is shown non-activated, we need to eat any
8268 // requests to activate the window while it is displayed. Windows will
8269 // automatically activate the popup on the mousedown otherwise.
8270 return true;
8272 case WM_SHOWWINDOW:
8273 // If the window is being minimized, close popups.
8274 if (aLParam == SW_PARENTCLOSING) {
8275 allowAnimations = nsIRollupListener::AllowAnimations::No;
8276 break;
8278 return false;
8280 case WM_KILLFOCUS:
8281 // If focus moves to other window created in different process/thread,
8282 // e.g., a plugin window, popups should be rolled up.
8283 if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
8284 allowAnimations = nsIRollupListener::AllowAnimations::No;
8285 break;
8287 return false;
8289 case WM_MOVING:
8290 case WM_MENUSELECT:
8291 break;
8293 default:
8294 return false;
8297 // Only exit if the touchpoint is within the browser window
8298 // EventIsInsideWindow handles for Maybe conditions
8299 // It does not matter if not a touch because otherwise it justs gets the event
8300 // message
8301 if (!EventIsInsideWindow(window, touchPoint)) {
8302 return false;
8305 // Only need to deal with the last rollup for left mouse down events.
8306 NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
8308 nsIRollupListener::RollupOptions rollupOptions{
8309 popupsToRollup,
8310 nsIRollupListener::FlushViews::Yes,
8311 /* mPoint = */ nullptr,
8312 allowAnimations,
8315 if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
8316 nativeMessage == WM_POINTERDOWN) {
8317 LayoutDeviceIntPoint pos;
8318 if (nativeMessage == WM_TOUCH) {
8319 pos.x = touchPoint->x;
8320 pos.y = touchPoint->y;
8321 } else {
8322 POINT pt;
8323 pt.x = GET_X_LPARAM(aLParam);
8324 pt.y = GET_Y_LPARAM(aLParam);
8325 // POINTERDOWN is already in screen coords.
8326 if (nativeMessage == WM_LBUTTONDOWN) {
8327 ::ClientToScreen(aWnd, &pt);
8329 pos = LayoutDeviceIntPoint(pt.x, pt.y);
8332 rollupOptions.mPoint = &pos;
8333 nsIContent* lastRollup = nullptr;
8334 consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
8335 nsAutoRollup::SetLastRollup(lastRollup);
8336 } else {
8337 consumeRollupEvent = rollupListener->Rollup(rollupOptions);
8340 // Tell hook to stop processing messages
8341 sProcessHook = false;
8342 sRollupMsgId = 0;
8343 sRollupMsgWnd = nullptr;
8345 // If we are NOT supposed to be consuming events, let it go through
8346 if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) {
8347 *aResult = MA_ACTIVATE;
8348 return true;
8351 return false;
8354 /**************************************************************
8355 **************************************************************
8357 ** BLOCK: Misc. utility methods and functions.
8359 ** General use.
8361 **************************************************************
8362 **************************************************************/
8364 // Note that the result of GetTopLevelWindow method can be different from the
8365 // result of WinUtils::GetTopLevelHWND(). The result can be non-floating
8366 // window. Because our top level window may be contained in another window
8367 // which is not managed by us.
8368 nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
8369 nsWindow* curWindow = this;
8371 while (true) {
8372 if (aStopOnDialogOrPopup) {
8373 switch (curWindow->mWindowType) {
8374 case WindowType::Dialog:
8375 case WindowType::Popup:
8376 return curWindow;
8377 default:
8378 break;
8382 // Retrieve the top level parent or owner window
8383 nsWindow* parentWindow = curWindow->GetParentWindow(true);
8385 if (!parentWindow) return curWindow;
8387 curWindow = parentWindow;
8391 // Set a flag if hwnd is a (non-popup) visible window from this process,
8392 // and bail out of the enumeration. Otherwise leave the flag unmodified
8393 // and continue the enumeration.
8394 // lParam must be a bool* pointing at the flag to be set.
8395 static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
8396 DWORD pid;
8397 ::GetWindowThreadProcessId(hwnd, &pid);
8398 if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
8399 // Don't count popups as visible windows, since they don't take focus,
8400 // in case we only have a popup visible (see bug 1554490 where the gfx
8401 // test window is an offscreen popup).
8402 nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
8403 if (!window || !window->IsPopup()) {
8404 bool* windowsVisible = reinterpret_cast<bool*>(lParam);
8405 *windowsVisible = true;
8406 return FALSE;
8409 return TRUE;
8412 // Determine if it would be ok to activate a window, taking focus.
8413 // We want to avoid stealing focus from another app (bug 225305).
8414 bool nsWindow::CanTakeFocus() {
8415 HWND fgWnd = ::GetForegroundWindow();
8416 if (!fgWnd) {
8417 // There is no foreground window, so don't worry about stealing focus.
8418 return true;
8420 // We can take focus if the current foreground window is already from
8421 // this process.
8422 DWORD pid;
8423 ::GetWindowThreadProcessId(fgWnd, &pid);
8424 if (pid == ::GetCurrentProcessId()) {
8425 return true;
8428 bool windowsVisible = false;
8429 ::EnumWindows(EnumVisibleWindowsProc,
8430 reinterpret_cast<LPARAM>(&windowsVisible));
8432 if (!windowsVisible) {
8433 // We're probably creating our first visible window, allow that to
8434 // take focus.
8435 return true;
8437 return false;
8440 /* static */ const wchar_t* nsWindow::GetMainWindowClass() {
8441 static const wchar_t* sMainWindowClass = nullptr;
8442 if (!sMainWindowClass) {
8443 nsAutoString className;
8444 Preferences::GetString("ui.window_class_override", className);
8445 if (!className.IsEmpty()) {
8446 sMainWindowClass = wcsdup(className.get());
8447 } else {
8448 sMainWindowClass = kClassNameGeneral;
8451 return sMainWindowClass;
8454 LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
8455 POINT pt;
8456 pt.x = GET_X_LPARAM(lParam);
8457 pt.y = GET_Y_LPARAM(lParam);
8458 ::ClientToScreen(mWnd, &pt);
8459 return MAKELPARAM(pt.x, pt.y);
8462 LPARAM nsWindow::lParamToClient(LPARAM lParam) {
8463 POINT pt;
8464 pt.x = GET_X_LPARAM(lParam);
8465 pt.y = GET_Y_LPARAM(lParam);
8466 ::ScreenToClient(mWnd, &pt);
8467 return MAKELPARAM(pt.x, pt.y);
8470 WPARAM nsWindow::wParamFromGlobalMouseState() {
8471 WPARAM result = 0;
8473 if (!!::GetKeyState(VK_CONTROL)) {
8474 result |= MK_CONTROL;
8477 if (!!::GetKeyState(VK_SHIFT)) {
8478 result |= MK_SHIFT;
8481 if (!!::GetKeyState(VK_LBUTTON)) {
8482 result |= MK_LBUTTON;
8485 if (!!::GetKeyState(VK_MBUTTON)) {
8486 result |= MK_MBUTTON;
8489 if (!!::GetKeyState(VK_RBUTTON)) {
8490 result |= MK_RBUTTON;
8493 if (!!::GetKeyState(VK_XBUTTON1)) {
8494 result |= MK_XBUTTON1;
8497 if (!!::GetKeyState(VK_XBUTTON2)) {
8498 result |= MK_XBUTTON2;
8501 return result;
8504 void nsWindow::PickerOpen() { mPickerDisplayCount++; }
8506 void nsWindow::PickerClosed() {
8507 NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
8508 if (!mPickerDisplayCount) return;
8509 mPickerDisplayCount--;
8510 if (!mPickerDisplayCount && mDestroyCalled) {
8511 Destroy();
8515 bool nsWindow::WidgetTypeSupportsAcceleration() {
8516 // We don't currently support using an accelerated layer manager with
8517 // transparent windows so don't even try. I'm also not sure if we even
8518 // want to support this case. See bug 593471.
8520 // Windows' support for transparent accelerated surfaces isn't great.
8521 // Some possible approaches:
8522 // - Readback the data and update it using
8523 // UpdateLayeredWindow/UpdateLayeredWindowIndirect
8524 // This is what WPF does. See
8525 // CD3DDeviceLevel1::PresentWithGDI/CD3DSwapChainWithSwDC in WpfGfx. The
8526 // rationale for not using IDirect3DSurface9::GetDC is explained here:
8527 // https://web.archive.org/web/20160521191104/https://blogs.msdn.microsoft.com/dwayneneed/2008/09/08/transparent-windows-in-wpf/
8528 // - Use D3D11_RESOURCE_MISC_GDI_COMPATIBLE, IDXGISurface1::GetDC(),
8529 // and UpdateLayeredWindowIndirect.
8530 // This is suggested here:
8531 // https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/december/windows-with-c-layered-windows-with-direct2d
8532 // but might have the same problem that IDirect3DSurface9::GetDC has.
8533 // - Creating the window with the WS_EX_NOREDIRECTIONBITMAP flag and use
8534 // DirectComposition.
8535 // Not supported on Win7.
8536 // - Using DwmExtendFrameIntoClientArea with negative margins and something
8537 // to turn off the glass effect.
8538 // This doesn't work when the DWM is not running (Win7)
8540 // Also see bug 1150376, D3D11 composition can cause issues on some devices
8541 // on Windows 7 where presentation fails randomly for windows with drop
8542 // shadows.
8543 return mTransparencyMode != TransparencyMode::Transparent &&
8544 !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
8547 bool nsWindow::DispatchTouchEventFromWMPointer(
8548 UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo,
8549 mozilla::MouseButton aButton) {
8550 MultiTouchInput::MultiTouchType touchType;
8551 switch (msg) {
8552 case WM_POINTERDOWN:
8553 touchType = MultiTouchInput::MULTITOUCH_START;
8554 break;
8555 case WM_POINTERUPDATE:
8556 if (aPointerInfo.mPressure == 0) {
8557 return false; // hover
8559 touchType = MultiTouchInput::MULTITOUCH_MOVE;
8560 break;
8561 case WM_POINTERUP:
8562 touchType = MultiTouchInput::MULTITOUCH_END;
8563 break;
8564 default:
8565 return false;
8568 nsPointWin touchPoint;
8569 touchPoint.x = GET_X_LPARAM(aLParam);
8570 touchPoint.y = GET_Y_LPARAM(aLParam);
8571 touchPoint.ScreenToClient(mWnd);
8573 SingleTouchData touchData(static_cast<int32_t>(aPointerInfo.pointerId),
8574 ScreenIntPoint::FromUnknownPoint(touchPoint),
8575 ScreenSize(1, 1), // pixel size radius for pen
8576 0.0f, // no radius rotation
8577 aPointerInfo.mPressure);
8578 touchData.mTiltX = aPointerInfo.tiltX;
8579 touchData.mTiltY = aPointerInfo.tiltY;
8580 touchData.mTwist = aPointerInfo.twist;
8582 MultiTouchInput touchInput;
8583 touchInput.mType = touchType;
8584 touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
8585 touchInput.mTouches.AppendElement(touchData);
8586 touchInput.mButton = aButton;
8587 touchInput.mButtons = aPointerInfo.mButtons;
8589 // POINTER_INFO.dwKeyStates can't be used as it only supports Shift and Ctrl
8590 ModifierKeyState modifierKeyState;
8591 touchInput.modifiers = modifierKeyState.GetModifiers();
8593 DispatchTouchInput(touchInput, MouseEvent_Binding::MOZ_SOURCE_PEN);
8594 return true;
8597 static MouseButton PenFlagsToMouseButton(PEN_FLAGS aPenFlags) {
8598 // Theoretically flags can be set together but they do not
8599 if (aPenFlags & PEN_FLAG_BARREL) {
8600 return MouseButton::eSecondary;
8602 if (aPenFlags & PEN_FLAG_ERASER) {
8603 return MouseButton::eEraser;
8605 return MouseButton::ePrimary;
8608 bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
8609 if (!mAPZC) {
8610 // APZ is not available on context menu. Follow the behavior of touch input
8611 // which fallbacks to WM_LBUTTON* and WM_GESTURE, to keep consistency.
8612 return false;
8614 if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) {
8615 return false;
8617 if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
8618 // We have to handle WM_POINTER* to fetch and cache pen related information
8619 // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
8620 // handler. This is because Windows doesn't support ::DoDragDrop in the
8621 // touch or pen message handlers.
8622 mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam);
8623 // Don't consume the Windows WM_POINTER* messages
8624 return false;
8627 uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
8628 POINTER_PEN_INFO penInfo{};
8629 if (!mPointerEvents.GetPointerPenInfo(pointerId, &penInfo)) {
8630 return false;
8633 // When dispatching mouse events with pen, there may be some
8634 // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
8635 // small movements. Those events will reset sLastMousePoint and reset
8636 // sLastClickCount. To prevent that, we keep the last pen down position
8637 // and compare it with the subsequent WM_POINTERUPDATE. If the movement is
8638 // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
8639 // eMouseMove for WM_POINTERUPDATE.
8640 static POINT sLastPointerDownPoint = {0};
8642 // We don't support chorded buttons for pen. Keep the button at
8643 // WM_POINTERDOWN.
8644 static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
8645 static bool sPointerDown = false;
8647 EventMessage message;
8648 mozilla::MouseButton button = MouseButton::ePrimary;
8649 switch (msg) {
8650 case WM_POINTERDOWN: {
8651 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8652 GET_Y_LPARAM(aLParam));
8653 sLastPointerDownPoint.x = eventPoint.x;
8654 sLastPointerDownPoint.y = eventPoint.y;
8655 message = eMouseDown;
8656 button = PenFlagsToMouseButton(penInfo.penFlags);
8657 sLastPenDownButton = button;
8658 sPointerDown = true;
8659 } break;
8660 case WM_POINTERUP:
8661 message = eMouseUp;
8662 MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
8663 button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
8664 sPointerDown = false;
8665 break;
8666 case WM_POINTERUPDATE:
8667 message = eMouseMove;
8668 if (sPointerDown) {
8669 LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
8670 GET_Y_LPARAM(aLParam));
8671 int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
8672 ? sLastPointerDownPoint.x - eventPoint.x.value
8673 : eventPoint.x.value - sLastPointerDownPoint.x;
8674 int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
8675 ? sLastPointerDownPoint.y - eventPoint.y.value
8676 : eventPoint.y.value - sLastPointerDownPoint.y;
8677 bool insideMovementThreshold =
8678 movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
8679 movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
8681 if (insideMovementThreshold) {
8682 // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
8683 // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
8684 return false;
8686 button = sLastPenDownButton;
8688 break;
8689 case WM_POINTERLEAVE:
8690 message = eMouseExitFromWidget;
8691 break;
8692 default:
8693 return false;
8696 // Windows defines the pen pressure is normalized to a range between 0 and
8697 // 1024. Convert it to float.
8698 float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
8699 int16_t buttons = sPointerDown
8700 ? nsContentUtils::GetButtonsFlagForButton(button)
8701 : MouseButtonsFlag::eNoButtons;
8702 WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure,
8703 buttons);
8704 // Per
8705 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_pen_info,
8706 // the rotation is normalized in a range of 0 to 359.
8707 MOZ_ASSERT(penInfo.rotation <= 359);
8708 pointerInfo.twist = (int32_t)penInfo.rotation;
8710 // Fire touch events but not when the barrel button is pressed.
8711 if (button != MouseButton::eSecondary &&
8712 StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled() &&
8713 DispatchTouchEventFromWMPointer(msg, aLParam, pointerInfo, button)) {
8714 return true;
8717 // The aLParam of WM_POINTER* is the screen location. Convert it to client
8718 // location
8719 LPARAM newLParam = lParamToClient(aLParam);
8720 DispatchMouseEvent(message, aWParam, newLParam, false, button,
8721 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8723 if (button == MouseButton::eSecondary && message == eMouseUp) {
8724 // Fire eContextMenu manually since consuming WM_POINTER* blocks
8725 // WM_CONTEXTMENU
8726 DispatchMouseEvent(eContextMenu, aWParam, newLParam, false, button,
8727 MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
8729 // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
8730 // WM_MOUSEMOVE.
8731 return true;
8734 void nsWindow::GetCompositorWidgetInitData(
8735 mozilla::widget::CompositorWidgetInitData* aInitData) {
8736 *aInitData = WinCompositorWidgetInitData(
8737 reinterpret_cast<uintptr_t>(mWnd),
8738 reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
8739 mTransparencyMode, mFrameState->GetSizeMode());
8742 bool nsWindow::SynchronouslyRepaintOnResize() {
8743 return !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
8746 void nsWindow::MaybeDispatchInitialFocusEvent() {
8747 if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
8748 DispatchFocusToTopLevelWindow(true);
8752 already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
8753 nsCOMPtr<nsIWidget> window = new nsWindow();
8754 return window.forget();
8757 already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
8758 nsCOMPtr<nsIWidget> window = new nsWindow(true);
8759 return window.forget();
8762 // static
8763 bool nsWindow::InitTouchInjection() {
8764 if (!sTouchInjectInitialized) {
8765 // Initialize touch injection on the first call
8766 HMODULE hMod = LoadLibraryW(kUser32LibName);
8767 if (!hMod) {
8768 return false;
8771 InitializeTouchInjectionPtr func =
8772 (InitializeTouchInjectionPtr)GetProcAddress(hMod,
8773 "InitializeTouchInjection");
8774 if (!func) {
8775 WinUtils::Log("InitializeTouchInjection not available.");
8776 return false;
8779 if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
8780 WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
8781 GetLastError());
8782 return false;
8785 sInjectTouchFuncPtr =
8786 (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
8787 if (!sInjectTouchFuncPtr) {
8788 WinUtils::Log("InjectTouchInput not available.");
8789 return false;
8791 sTouchInjectInitialized = true;
8793 return true;
8796 bool nsWindow::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
8797 POINTER_FLAGS aFlags, uint32_t aPressure,
8798 uint32_t aOrientation) {
8799 if (aId > TOUCH_INJECT_MAX_POINTS) {
8800 WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
8801 return false;
8804 POINTER_TOUCH_INFO info{};
8806 info.touchFlags = TOUCH_FLAG_NONE;
8807 info.touchMask =
8808 TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
8809 info.pressure = aPressure;
8810 info.orientation = aOrientation;
8812 info.pointerInfo.pointerFlags = aFlags;
8813 info.pointerInfo.pointerType = PT_TOUCH;
8814 info.pointerInfo.pointerId = aId;
8815 info.pointerInfo.ptPixelLocation.x = aPoint.x;
8816 info.pointerInfo.ptPixelLocation.y = aPoint.y;
8818 info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
8819 info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
8820 info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
8821 info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
8823 for (int i = 0; i < 3; i++) {
8824 if (sInjectTouchFuncPtr(1, &info)) {
8825 break;
8827 DWORD error = GetLastError();
8828 if (error == ERROR_NOT_READY && i < 2) {
8829 // We sent it too quickly after the previous injection (see bug 1535140
8830 // comment 10). On the first loop iteration we just yield (via Sleep(0))
8831 // and try again. If it happens again on the second loop iteration we
8832 // explicitly Sleep(1) and try again. If that doesn't work either we just
8833 // error out.
8834 ::Sleep(i);
8835 continue;
8837 WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
8838 return false;
8840 return true;
8843 void nsWindow::ChangedDPI() {
8844 if (mWidgetListener) {
8845 if (PresShell* presShell = mWidgetListener->GetPresShell()) {
8846 presShell->BackingScaleFactorChanged();
8851 static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
8852 nsWindow::TouchPointerState aPointerState, bool isUpdate) {
8853 bool hover = aPointerState & nsWindow::TOUCH_HOVER;
8854 bool contact = aPointerState & nsWindow::TOUCH_CONTACT;
8855 bool remove = aPointerState & nsWindow::TOUCH_REMOVE;
8856 bool cancel = aPointerState & nsWindow::TOUCH_CANCEL;
8858 POINTER_FLAGS flags;
8859 if (isUpdate) {
8860 // We know about this pointer, send an update
8861 flags = POINTER_FLAG_UPDATE;
8862 if (hover) {
8863 flags |= POINTER_FLAG_INRANGE;
8864 } else if (contact) {
8865 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
8866 } else if (remove) {
8867 flags = POINTER_FLAG_UP;
8870 if (cancel) {
8871 flags |= POINTER_FLAG_CANCELED;
8873 } else {
8874 // Missing init state, error out
8875 if (remove || cancel) {
8876 return Err(NS_ERROR_INVALID_ARG);
8879 // Create a new pointer
8880 flags = POINTER_FLAG_INRANGE;
8881 if (contact) {
8882 flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
8885 return flags;
8888 nsresult nsWindow::SynthesizeNativeTouchPoint(
8889 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
8890 LayoutDeviceIntPoint aPoint, double aPointerPressure,
8891 uint32_t aPointerOrientation, nsIObserver* aObserver) {
8892 AutoObserverNotifier notifier(aObserver, "touchpoint");
8894 if (StaticPrefs::apz_test_fails_with_native_injection() ||
8895 !InitTouchInjection()) {
8896 // If we don't have touch injection from the OS, or if we are running a test
8897 // that cannot properly inject events to satisfy the OS requirements (see
8898 // bug 1313170) we can just fake it and synthesize the events from here.
8899 MOZ_ASSERT(NS_IsMainThread());
8900 if (aPointerState == TOUCH_HOVER) {
8901 return NS_ERROR_UNEXPECTED;
8904 if (!mSynthesizedTouchInput) {
8905 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
8908 WidgetEventTime time = CurrentMessageWidgetEventTime();
8909 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
8910 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
8911 mSynthesizedTouchInput.get(), time.mTimeStamp, aPointerId,
8912 aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
8913 DispatchTouchInput(inputToDispatch);
8914 return NS_OK;
8917 // win api expects a value from 0 to 1024. aPointerPressure is a value
8918 // from 0.0 to 1.0.
8919 uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
8921 // If we already know about this pointer id get it's record
8922 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
8923 POINTER_FLAGS flags;
8924 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
8925 auto result = PointerStateToFlag(aPointerState, !!entry);
8926 if (result.isOk()) {
8927 flags = result.unwrap();
8928 } else {
8929 return result.unwrapErr();
8932 if (!entry) {
8933 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
8934 PointerInfo::PointerType::TOUCH));
8935 } else {
8936 if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
8937 return NS_ERROR_UNEXPECTED;
8939 if (aPointerState & TOUCH_REMOVE) {
8940 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
8941 // so shouldn't leak.
8942 entry.Remove();
8946 return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
8947 aPointerOrientation)
8948 ? NS_ERROR_UNEXPECTED
8949 : NS_OK;
8953 nsresult nsWindow::ClearNativeTouchSequence(nsIObserver* aObserver) {
8954 AutoObserverNotifier notifier(aObserver, "cleartouch");
8955 if (!sTouchInjectInitialized) {
8956 return NS_OK;
8959 // cancel all input points
8960 for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
8961 auto* info = iter.UserData();
8962 if (info->mType != PointerInfo::PointerType::TOUCH) {
8963 continue;
8965 InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
8966 iter.Remove();
8969 nsBaseWidget::ClearNativeTouchSequence(nullptr);
8971 return NS_OK;
8974 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8975 static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
8976 static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
8977 static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
8978 #endif
8979 static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
8981 static bool InitPenInjection() {
8982 if (sSyntheticPenDevice) {
8983 return true;
8985 #if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
8986 HMODULE hMod = LoadLibraryW(kUser32LibName);
8987 if (!hMod) {
8988 return false;
8990 CreateSyntheticPointerDevice =
8991 (CreateSyntheticPointerDevicePtr)GetProcAddress(
8992 hMod, "CreateSyntheticPointerDevice");
8993 if (!CreateSyntheticPointerDevice) {
8994 WinUtils::Log("CreateSyntheticPointerDevice not available.");
8995 return false;
8997 DestroySyntheticPointerDevice =
8998 (DestroySyntheticPointerDevicePtr)GetProcAddress(
8999 hMod, "DestroySyntheticPointerDevice");
9000 if (!DestroySyntheticPointerDevice) {
9001 WinUtils::Log("DestroySyntheticPointerDevice not available.");
9002 return false;
9004 InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
9005 hMod, "InjectSyntheticPointerInput");
9006 if (!InjectSyntheticPointerInput) {
9007 WinUtils::Log("InjectSyntheticPointerInput not available.");
9008 return false;
9010 #endif
9011 sSyntheticPenDevice =
9012 CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
9013 return !!sSyntheticPenDevice;
9016 nsresult nsWindow::SynthesizeNativePenInput(
9017 uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
9018 LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
9019 int32_t aTiltX, int32_t aTiltY, int32_t aButton, nsIObserver* aObserver) {
9020 AutoObserverNotifier notifier(aObserver, "peninput");
9021 if (!InitPenInjection()) {
9022 return NS_ERROR_UNEXPECTED;
9025 // win api expects a value from 0 to 1024. aPointerPressure is a value
9026 // from 0.0 to 1.0.
9027 uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
9029 // If we already know about this pointer id get it's record
9030 return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
9031 POINTER_FLAGS flags;
9032 // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
9033 auto result = PointerStateToFlag(aPointerState, !!entry);
9034 if (result.isOk()) {
9035 flags = result.unwrap();
9036 } else {
9037 return result.unwrapErr();
9040 if (!entry) {
9041 entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
9042 PointerInfo::PointerType::PEN));
9043 } else {
9044 if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
9045 return NS_ERROR_UNEXPECTED;
9047 if (aPointerState & TOUCH_REMOVE) {
9048 // Remove the pointer from our tracking list. This is UniquePtr wrapped,
9049 // so shouldn't leak.
9050 entry.Remove();
9054 POINTER_TYPE_INFO info{};
9056 info.type = PT_PEN;
9057 info.penInfo.pointerInfo.pointerType = PT_PEN;
9058 info.penInfo.pointerInfo.pointerFlags = flags;
9059 info.penInfo.pointerInfo.pointerId = aPointerId;
9060 info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
9061 info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
9063 info.penInfo.penFlags = PEN_FLAG_NONE;
9064 // PEN_FLAG_ERASER is not supported this way, unfortunately.
9065 if (aButton == 2) {
9066 info.penInfo.penFlags |= PEN_FLAG_BARREL;
9068 info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
9069 PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
9070 info.penInfo.pressure = pressure;
9071 info.penInfo.rotation = aRotation;
9072 info.penInfo.tiltX = aTiltX;
9073 info.penInfo.tiltY = aTiltY;
9075 return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
9076 ? NS_OK
9077 : NS_ERROR_UNEXPECTED;
9081 bool nsWindow::HandleAppCommandMsg(const MSG& aAppCommandMsg,
9082 LRESULT* aRetValue) {
9083 ModifierKeyState modKeyState;
9084 NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
9085 bool consumed = nativeKey.HandleAppCommandMessage();
9086 *aRetValue = consumed ? 1 : 0;
9087 return consumed;
9090 #ifdef DEBUG
9091 nsresult nsWindow::SetHiDPIMode(bool aHiDPI) {
9092 return WinUtils::SetHiDPIMode(aHiDPI);
9095 nsresult nsWindow::RestoreHiDPIMode() { return WinUtils::RestoreHiDPIMode(); }
9096 #endif
9098 mozilla::Maybe<UINT> nsWindow::GetHiddenTaskbarEdge() {
9099 HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
9101 if (!IsWin8OrLater()) {
9102 // Per-monitor taskbar information is not available.
9103 APPBARDATA appBarData;
9104 appBarData.cbSize = sizeof(appBarData);
9105 UINT taskbarState = SHAppBarMessage(ABM_GETSTATE, &appBarData);
9106 if (ABS_AUTOHIDE & taskbarState) {
9107 appBarData.hWnd = FindWindow(L"Shell_TrayWnd", nullptr);
9108 if (appBarData.hWnd) {
9109 HMONITOR taskbarMonitor =
9110 ::MonitorFromWindow(appBarData.hWnd, MONITOR_DEFAULTTOPRIMARY);
9111 if (taskbarMonitor == windowMonitor) {
9112 SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData);
9113 return Some(appBarData.uEdge);
9117 return Nothing();
9120 // Check all four sides of our monitor for an appbar. Skip any that aren't
9121 // the system taskbar.
9122 MONITORINFO mi;
9123 mi.cbSize = sizeof(MONITORINFO);
9124 ::GetMonitorInfo(windowMonitor, &mi);
9126 APPBARDATA appBarData;
9127 appBarData.cbSize = sizeof(appBarData);
9128 appBarData.rc = mi.rcMonitor;
9129 const auto kEdges = {ABE_BOTTOM, ABE_TOP, ABE_LEFT, ABE_RIGHT};
9130 for (auto edge : kEdges) {
9131 appBarData.uEdge = edge;
9132 // ABM_GETAUTOHIDEBAREX is not defined before Windows 8.
9133 static constexpr DWORD ABM_GETAUTOHIDEBAREX = 0x000b;
9134 HWND appBarHwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &appBarData);
9135 if (appBarHwnd) {
9136 nsAutoString className;
9137 if (WinUtils::GetClassName(appBarHwnd, className)) {
9138 if (className.Equals(L"Shell_TrayWnd") ||
9139 className.Equals(L"Shell_SecondaryTrayWnd")) {
9140 return Some(edge);
9146 return Nothing();
9149 static nsSizeMode GetSizeModeForWindowFrame(HWND aWnd, bool aFullscreenMode) {
9150 WINDOWPLACEMENT pl;
9151 pl.length = sizeof(pl);
9152 ::GetWindowPlacement(aWnd, &pl);
9154 if (pl.showCmd == SW_SHOWMINIMIZED) {
9155 return nsSizeMode_Minimized;
9156 } else if (aFullscreenMode) {
9157 return nsSizeMode_Fullscreen;
9158 } else if (pl.showCmd == SW_SHOWMAXIMIZED) {
9159 return nsSizeMode_Maximized;
9160 } else {
9161 return nsSizeMode_Normal;
9165 static void ShowWindowWithMode(HWND aWnd, nsSizeMode aMode) {
9166 // This will likely cause a callback to
9167 // nsWindow::FrameState::{OnFrameChanging() and OnFrameChanged()}
9168 switch (aMode) {
9169 case nsSizeMode_Fullscreen:
9170 ::ShowWindow(aWnd, SW_SHOW);
9171 break;
9173 case nsSizeMode_Maximized:
9174 ::ShowWindow(aWnd, SW_MAXIMIZE);
9175 break;
9177 case nsSizeMode_Minimized:
9178 ::ShowWindow(aWnd, SW_MINIMIZE);
9179 break;
9181 default:
9182 // Don't call ::ShowWindow if we're trying to "restore" a window that is
9183 // already in a normal state. Prevents a bug where snapping to one side
9184 // of the screen and then minimizing would cause Windows to forget our
9185 // window's correct restored position/size.
9186 if (GetCurrentShowCmd(aWnd) != SW_SHOWNORMAL) {
9187 ::ShowWindow(aWnd, SW_RESTORE);
9192 nsWindow::FrameState::FrameState(nsWindow* aWindow) : mWindow(aWindow) {}
9194 nsSizeMode nsWindow::FrameState::GetSizeMode() const { return mSizeMode; }
9196 void nsWindow::FrameState::CheckInvariant() const {
9197 MOZ_ASSERT(mSizeMode >= 0 && mSizeMode < nsSizeMode_Invalid);
9198 MOZ_ASSERT(mLastSizeMode >= 0 && mLastSizeMode < nsSizeMode_Invalid);
9199 MOZ_ASSERT(mPreFullscreenSizeMode >= 0 &&
9200 mPreFullscreenSizeMode < nsSizeMode_Invalid);
9201 MOZ_ASSERT(mWindow);
9203 // We should never observe fullscreen sizemode unless fullscreen is enabled
9204 MOZ_ASSERT_IF(mSizeMode == nsSizeMode_Fullscreen, mFullscreenMode);
9205 MOZ_ASSERT_IF(!mFullscreenMode, mSizeMode != nsSizeMode_Fullscreen);
9207 // Something went wrong if we somehow saved fullscreen mode when we are
9208 // changing into fullscreen mode
9209 MOZ_ASSERT(mPreFullscreenSizeMode != nsSizeMode_Fullscreen);
9212 void nsWindow::FrameState::ConsumePreXULSkeletonState(bool aWasMaximized) {
9213 mSizeMode = aWasMaximized ? nsSizeMode_Maximized : nsSizeMode_Normal;
9216 void nsWindow::FrameState::EnsureSizeMode(nsSizeMode aMode,
9217 DoShowWindow aDoShowWindow) {
9218 if (mSizeMode == aMode) {
9219 return;
9222 if (aMode == nsSizeMode_Fullscreen) {
9223 EnsureFullscreenMode(true, aDoShowWindow);
9224 MOZ_ASSERT(mSizeMode == nsSizeMode_Fullscreen);
9225 } else if (mSizeMode == nsSizeMode_Fullscreen && aMode == nsSizeMode_Normal) {
9226 // If we are in fullscreen mode, minimize should work like normal and
9227 // return us to fullscreen mode when unminimized. Maximize isn't really
9228 // available and won't do anything. "Restore" should do the same thing as
9229 // requesting to end fullscreen.
9230 EnsureFullscreenMode(false, aDoShowWindow);
9231 } else {
9232 SetSizeModeInternal(aMode, aDoShowWindow);
9236 void nsWindow::FrameState::EnsureFullscreenMode(bool aFullScreen,
9237 DoShowWindow aDoShowWindow) {
9238 const bool changed = aFullScreen != mFullscreenMode;
9239 if (changed && aFullScreen) {
9240 // Save the size mode from before fullscreen.
9241 mPreFullscreenSizeMode = mSizeMode;
9243 mFullscreenMode = aFullScreen;
9244 if (changed || aFullScreen) {
9245 // NOTE(emilio): When minimizing a fullscreen window we remain with
9246 // mFullscreenMode = true, but mSizeMode = nsSizeMode_Minimized. We need to
9247 // make sure to call SetSizeModeInternal even if mFullscreenMode didn't
9248 // change, to ensure we actually end up with a fullscreen sizemode when
9249 // restoring a window from that state.
9250 SetSizeModeInternal(
9251 aFullScreen ? nsSizeMode_Fullscreen : mPreFullscreenSizeMode,
9252 aDoShowWindow);
9256 void nsWindow::FrameState::OnFrameChanging() {
9257 const nsSizeMode newSizeMode =
9258 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
9259 EnsureSizeMode(newSizeMode);
9260 mWindow->UpdateNonClientMargins(false);
9263 void nsWindow::FrameState::OnFrameChanged() {
9264 // We don't want to perform the ShowWindow ourselves if we're on the frame
9265 // changed message. Windows has done the frame change for us, and we take care
9266 // of activating as needed. We also don't want to potentially trigger
9267 // more focus / restore. Among other things, this addresses a bug on Win7
9268 // related to window docking. (bug 489258)
9269 const auto newSizeMode =
9270 GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
9271 EnsureSizeMode(newSizeMode, DoShowWindow::No);
9273 // If window was restored, activate the window now to get correct attributes.
9274 if (mWindow->mIsVisible && mWindow->IsForegroundWindow() &&
9275 mLastSizeMode == nsSizeMode_Minimized &&
9276 mSizeMode != nsSizeMode_Minimized) {
9277 mWindow->DispatchFocusToTopLevelWindow(true);
9279 mLastSizeMode = mSizeMode;
9282 static void MaybeLogSizeMode(nsSizeMode aMode) {
9283 #ifdef WINSTATE_DEBUG_OUTPUT
9284 MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** SizeMode: %d\n", int(aMode)));
9285 #endif
9288 void nsWindow::FrameState::SetSizeModeInternal(nsSizeMode aMode,
9289 DoShowWindow aDoShowWindow) {
9290 if (mSizeMode == aMode) {
9291 return;
9294 const auto oldSizeMode = mSizeMode;
9295 const bool fullscreenChange =
9296 mSizeMode == nsSizeMode_Fullscreen || aMode == nsSizeMode_Fullscreen;
9297 const bool fullscreen = aMode == nsSizeMode_Fullscreen;
9299 mLastSizeMode = mSizeMode;
9300 mSizeMode = aMode;
9302 MaybeLogSizeMode(mSizeMode);
9304 if (bool(aDoShowWindow) && mWindow->mIsVisible) {
9305 ShowWindowWithMode(mWindow->mWnd, aMode);
9308 mWindow->UpdateNonClientMargins(false);
9310 if (fullscreenChange) {
9311 mWindow->OnFullscreenChanged(oldSizeMode, fullscreen);
9314 mWindow->OnSizeModeChange();
9317 void nsWindow::ContextMenuPreventer::Update(
9318 const WidgetMouseEvent& aEvent,
9319 const nsIWidget::ContentAndAPZEventStatus& aEventStatus) {
9320 mNeedsToPreventContextMenu =
9321 aEvent.mMessage == eMouseUp &&
9322 aEvent.mButton == MouseButton::eSecondary &&
9323 aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
9324 aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault;